{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-thumbnail",
  "title": "File Thumbnail",
  "description": "Render complete file thumbnails from browser files, URLs, blobs, text sources, external images, or custom preview content.",
  "dependencies": [
    "@e965/xlsx@0.20.3",
    "dompurify@^3.3.3",
    "marked@^15.0.12",
    "@kenjiuno/msgreader@^1.28.0",
    "pptxviewjs@1.1.9",
    "utif@^3.1.0",
    "docx-preview@^0.3.7"
  ],
  "registryDependencies": [
    "@retab/file-thumbnail-frame",
    "@retab/pdf-document-resource",
    "@retab/docx-document-resource",
    "@retab/csv",
    "@retab/xlsx-worker-protocol",
    "@retab/utils",
    "@retab/pptx-viewer"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/file-thumbnail.tsx",
      "content": "\"use client\";\n\nimport type { ViewerSource } from \"@/lib/viewer-source\";\nimport { GeneratedFileThumbnail } from \"@/components/file-thumbnail/generated-preview\";\n\nimport {\n  FileThumbnailFrame,\n  FileThumbnailShimmer,\n  hasRenderablePreviewContent,\n  resolveFileThumbnailState,\n} from \"./file-thumbnail-frame\";\nimport type {\n  FileThumbnailFrameProps,\n  FileThumbnailProps,\n  FileThumbnailShape,\n  FileThumbnailSize,\n  FileThumbnailSource,\n  FileThumbnailState,\n  ThumbnailFile,\n} from \"./file-thumbnail-types\";\n\nexport {\n  FileThumbnailFrame,\n  FileThumbnailShimmer,\n  hasRenderablePreviewContent,\n  resolveFileThumbnailState,\n};\nexport type {\n  FileThumbnailFrameProps,\n  FileThumbnailProps,\n  FileThumbnailShape,\n  FileThumbnailSize,\n  FileThumbnailSource,\n  FileThumbnailState,\n  ThumbnailFile,\n};\n\n/**\n * Render a complete file thumbnail from a browser File, a viewer source, an\n * externally generated image, custom preview content, or plain file metadata.\n */\nexport function FileThumbnail({\n  source,\n  file,\n  as,\n  anchor,\n  presentation = \"document\",\n  retryKey,\n  onError,\n  ...frameProps\n}: FileThumbnailProps) {\n  const resolvedSource = resolveFileThumbnailSource(source, file);\n  const accessibilityProps =\n    presentation === \"decorative\"\n      ? ({\n          \"aria-hidden\": true,\n          role: \"presentation\",\n        } as const)\n      : {};\n  const {\n    previewClassName: _previewClassName,\n    previewContent: _previewContent,\n    previewImageUrl: _previewImageUrl,\n    onPreviewError: _onPreviewError,\n    state: _state,\n    ...thumbnailProps\n  } = frameProps;\n\n  if (shouldUseFrame(frameProps) || !resolvedSource) {\n    return (\n      <FileThumbnailFrame\n        {...frameProps}\n        {...accessibilityProps}\n        file={file ?? fileFromSource(resolvedSource)}\n      />\n    );\n  }\n\n  return (\n    <GeneratedFileThumbnail\n      {...thumbnailProps}\n      {...accessibilityProps}\n      source={resolvedSource}\n      as={as}\n      anchor={anchor ?? \"top-left\"}\n      retryKey={retryKey}\n      onError={onError}\n    />\n  );\n}\n\nfunction shouldUseFrame({\n  previewContent,\n  previewImageUrl,\n  state,\n}: Pick<\n  FileThumbnailProps,\n  \"previewContent\" | \"previewImageUrl\" | \"state\"\n>): boolean {\n  return (\n    hasRenderablePreviewContent(previewContent) ||\n    Boolean(previewImageUrl) ||\n    state !== undefined\n  );\n}\n\nfunction resolveFileThumbnailSource(\n  source: FileThumbnailProps[\"source\"],\n  file: FileThumbnailProps[\"file\"],\n): ViewerSource | null {\n  if (isFile(source)) return fileSource(source);\n  if (source) return source;\n  if (isFile(file)) return fileSource(file);\n  return null;\n}\n\nfunction isFile(value: unknown): value is File {\n  return typeof File !== \"undefined\" && value instanceof File;\n}\n\nfunction fileSource(file: File): ViewerSource {\n  return {\n    kind: \"blob\",\n    blob: file,\n    identityKey: `${file.name}-${file.size}-${file.lastModified}`,\n    fileName: file.name,\n    mimeType: file.type,\n  };\n}\n\nfunction fileFromSource(source: ViewerSource | null): ThumbnailFile {\n  if (!source) return { name: \"file\", type: \"\" };\n  return {\n    name: source.fileName ?? \"file\",\n    type: source.mimeType ?? \"\",\n  };\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-thumbnail.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-thumbnail-types.ts",
      "content": "import type * as React from \"react\";\n\nimport type { ViewerErrorInfo } from \"@/lib/viewer-errors\";\nimport type { FileCategory, ViewerSource } from \"@/lib/viewer-source\";\nimport type { ThumbnailAnchor } from \"@/components/file-thumbnail/types\";\n\nimport type {\n  FileThumbnailFrameProps,\n  FileThumbnailShape,\n  FileThumbnailSize,\n  FileThumbnailState,\n  ThumbnailFile,\n} from \"./file-thumbnail-frame-types\";\n\nexport type {\n  FileThumbnailFrameProps,\n  FileThumbnailShape,\n  FileThumbnailSize,\n  FileThumbnailState,\n  ThumbnailFile,\n} from \"./file-thumbnail-frame-types\";\n\nexport type FileThumbnailSource = ViewerSource | File;\n\nexport interface FileThumbnailProps\n  extends Omit<FileThumbnailFrameProps, \"file\" | \"onError\"> {\n  /** File metadata renders a static shell; a browser File renders a generated preview. */\n  file?: ThumbnailFile | File;\n  /** URL, Blob/File, or inline text source for generated file thumbnails. */\n  source?: FileThumbnailSource;\n  /** Override auto-detection when name or MIME type is missing or wrong. */\n  as?: FileCategory;\n  /** Corner pinned when rendered content overflows the frame. */\n  anchor?: ThumbnailAnchor;\n  /** Hide rich preview internals from assistive tech when the parent row owns the accessible name. */\n  presentation?: \"document\" | \"decorative\";\n  /** Change to retry a failed generated thumbnail for the same source. */\n  retryKey?: React.Key;\n  /** Receives generated thumbnail failures with canonical viewer error info. */\n  onError?: (error: unknown, info: ViewerErrorInfo) => void;\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-thumbnail-types.ts"
    },
    {
      "path": "components/file-thumbnail/descriptor.ts",
      "content": "import {\n  extensionOf,\n  resolveViewerDescriptor,\n  type FileCategory,\n  type ViewerDescriptor,\n  type ViewerSource,\n} from \"@/lib/viewer-source\";\n\nexport function resolveThumbnailDescriptor({\n  source,\n  as,\n}: {\n  source: ViewerSource;\n  as?: FileCategory;\n}): ViewerDescriptor {\n  return resolveViewerDescriptor({ source, category: as });\n}\n\nexport function isTiffDescriptor(descriptor: ViewerDescriptor): boolean {\n  const mime = descriptor.mimeType?.toLowerCase().split(\";\")[0].trim();\n  if (mime === \"image/tiff\" || mime === \"image/tif\") return true;\n  const extension = extensionOf(descriptor.displayName);\n  return extension === \"tif\" || extension === \"tiff\";\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/descriptor.ts"
    },
    {
      "path": "components/file-thumbnail/errors.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n// A failed parse updates the outer FileThumbnail state so it degrades to the\n// shared fallback surface instead of an empty preview layer.\nexport class ThumbnailErrorBoundary extends React.Component<\n  {\n    children: React.ReactNode;\n    fallback: React.ReactNode;\n    onError: (error: unknown) => void;\n  },\n  { failed: boolean }\n> {\n  constructor(props: {\n    children: React.ReactNode;\n    fallback: React.ReactNode;\n    onError: (error: unknown) => void;\n  }) {\n    super(props);\n    this.state = { failed: false };\n  }\n\n  static getDerivedStateFromError() {\n    return { failed: true };\n  }\n\n  componentDidCatch(error: unknown) {\n    this.props.onError(error);\n  }\n\n  render() {\n    if (this.state.failed) return this.props.fallback;\n    return this.props.children;\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/errors.tsx"
    },
    {
      "path": "components/file-thumbnail/generated-preview.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerErrorInfo } from \"@/lib/viewer-errors\";\nimport { createViewerResource } from \"@/lib/viewer-resource\";\nimport type { FileCategory, ViewerSource } from \"@/lib/viewer-source\";\nimport {\n  FileThumbnailFrame,\n  type FileThumbnailFrameProps,\n} from \"@/components/ui/file-thumbnail-frame\";\n\nimport { resolveThumbnailDescriptor } from \"./descriptor\";\nimport { getThumbnailKey, getThumbnailRenderKey } from \"./keys\";\nimport { ThumbnailClientPreview } from \"./thumbnail-client-preview\";\nimport {\n  DirectImageThumbnail,\n  isDirectImageThumbnail,\n} from \"./thumbnail-direct-image\";\nimport {\n  createThumbnailErrorState,\n  type ThumbnailErrorState,\n} from \"./thumbnail-error-state\";\nimport { getThumbnailOptions } from \"./thumbnail-options\";\nimport type { ThumbnailAnchor } from \"./types\";\n\nexport interface GeneratedFileThumbnailProps\n  extends Omit<FileThumbnailFrameProps, \"file\" | \"onError\"> {\n  source: ViewerSource;\n  as?: FileCategory;\n  anchor: ThumbnailAnchor;\n  retryKey?: React.Key;\n  onError?: (error: unknown, info: ViewerErrorInfo) => void;\n}\n\nexport function GeneratedFileThumbnail({\n  source,\n  as,\n  className,\n  previewAspectRatio,\n  anchor,\n  retryKey,\n  onError,\n  ...props\n}: GeneratedFileThumbnailProps) {\n  const descriptor = resolveThumbnailDescriptor({ source, as });\n  const resource = React.useMemo(() => createViewerResource(source), [source]);\n  const thumbnailKey = getThumbnailKey({\n    resource,\n    descriptor,\n    options: getThumbnailOptions(descriptor),\n  });\n  const renderKey = getThumbnailRenderKey({\n    thumbnailKey,\n    anchor,\n    retryKey: retryKey ?? null,\n  });\n  const [errorState, setErrorState] =\n    React.useState<ThumbnailErrorState | null>(null);\n  const currentErrorState =\n    errorState?.renderKey === renderKey ? errorState : null;\n  const directUrl = resource.content.directUrl;\n\n  if (descriptor.category === \"unsupported\") {\n    return (\n      <FileThumbnailFrame\n        {...props}\n        file={{ name: descriptor.displayName, type: descriptor.mimeType ?? \"\" }}\n        previewAspectRatio={previewAspectRatio}\n        className={className}\n      />\n    );\n  }\n\n  if (isDirectImageThumbnail({ descriptor, directUrl })) {\n    return (\n      <DirectImageThumbnail\n        descriptor={descriptor}\n        resource={resource}\n        directUrl={directUrl!}\n        previewAspectRatio={previewAspectRatio}\n        className={className}\n        anchor={anchor}\n        renderKey={renderKey}\n        errorState={currentErrorState}\n        thumbnailProps={props}\n        onError={(error, nextErrorState) => {\n          setErrorState(nextErrorState);\n          onError?.(error, nextErrorState.info);\n        }}\n      />\n    );\n  }\n\n  return (\n    <FileThumbnailFrame\n      {...props}\n      file={{ name: descriptor.displayName, type: descriptor.mimeType ?? \"\" }}\n      previewAspectRatio={previewAspectRatio}\n      className={className}\n      state={currentErrorState ? \"error\" : \"loaded\"}\n      aria-label={currentErrorState?.info.userMessage}\n      title={currentErrorState?.info.userMessage}\n      data-error-domain={currentErrorState?.info.domain}\n      data-error-format={currentErrorState?.info.format}\n      data-error-kind={currentErrorState?.info.kind}\n      data-error-message={currentErrorState?.info.message}\n      previewContent={\n        <ThumbnailClientPreview\n          key={renderKey}\n          resource={resource}\n          descriptor={descriptor}\n          thumbnailKey={thumbnailKey}\n          anchor={anchor}\n          onError={(error) => {\n            const nextErrorState = createThumbnailErrorState({\n              renderKey,\n              error,\n              resource,\n              descriptor,\n            });\n            setErrorState(nextErrorState);\n            onError?.(error, nextErrorState.info);\n          }}\n        />\n      }\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/generated-preview.tsx"
    },
    {
      "path": "components/file-thumbnail/keys.ts",
      "content": "import type * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { ViewerDescriptor } from \"@/lib/viewer-source\";\n\nimport type { ThumbnailAnchor } from \"./types\";\n\nexport interface ThumbnailKeyInput {\n  resource: ViewerResource;\n  descriptor: ViewerDescriptor;\n  unit?: string;\n  options?: readonly ThumbnailOption[];\n}\n\nexport interface ThumbnailRenderKeyInput {\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n  retryKey: React.Key | null;\n}\n\nexport function getThumbnailKey({\n  resource,\n  descriptor,\n  unit = \"first\",\n  options = [],\n}: ThumbnailKeyInput): string {\n  return [\n    encodePart(`resource:${resource.keys.load}`),\n    encodePart(`category:${descriptor.category}`),\n    encodePart(`unit:${unit}`),\n    ...options.map((option) => encodePart(`option:${option}`)),\n  ].join(\"\");\n}\n\nexport type ThumbnailOption = string & {\n  readonly __thumbnailOption: unique symbol;\n};\n\nexport function thumbnailOption(\n  name: string,\n  value: string | number,\n): ThumbnailOption {\n  return `${name}:${String(value)}` as ThumbnailOption;\n}\n\nexport function getThumbnailRenderKey({\n  thumbnailKey,\n  anchor,\n  retryKey,\n}: ThumbnailRenderKeyInput): string {\n  return [\n    encodePart(`thumbnail:${thumbnailKey}`),\n    encodePart(`anchor:${anchor}`),\n    encodeRetryKey(retryKey),\n  ].join(\"\");\n}\n\nfunction encodePart(value: string): string {\n  return `${value.length}:${value}`;\n}\n\nfunction encodeRetryKey(retryKey: React.Key | null): string {\n  if (retryKey === null) return encodePart(\"retry:null:\");\n  return encodePart(`retry:${typeof retryKey}:${String(retryKey)}`);\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/keys.ts"
    },
    {
      "path": "components/file-thumbnail/renderer-registry.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { FileCategory, ViewerDescriptor } from \"@/lib/viewer-source\";\n\nimport { isTiffDescriptor } from \"./descriptor\";\nimport { CodeThumbnail } from \"./renderers/code-thumbnail\";\nimport { CsvFirstRows } from \"./renderers/csv-thumbnail\";\nimport { DocxFirstPage } from \"./renderers/docx-thumbnail\";\nimport { HtmlFirstPage } from \"./renderers/html-thumbnail\";\nimport { ImageFirstFrame } from \"./renderers/image-thumbnail\";\nimport { MarkdownFirstPage } from \"./renderers/markdown-thumbnail\";\nimport { MsgEmailThumbnail } from \"./renderers/msg-thumbnail\";\nimport { PdfFirstPage } from \"./renderers/pdf-thumbnail\";\nimport { PptxFirstSlide } from \"./renderers/pptx-thumbnail\";\nimport { TextThumbnail } from \"./renderers/text-thumbnail\";\nimport { TiffFirstPage } from \"./renderers/tiff-thumbnail\";\nimport { XlsxFirstSheet } from \"./renderers/xlsx-thumbnail\";\nimport { isCodeThumbnailDescriptor } from \"./thumbnail-code\";\nimport type { ThumbnailAnchor } from \"./types\";\n\ntype DocumentRenderer = (props: {\n  resource: ViewerResource;\n  descriptor: ViewerDescriptor;\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) => React.ReactNode;\n\nconst DOCUMENT_RENDERERS: Record<\n  Exclude<FileCategory, \"unsupported\">,\n  DocumentRenderer\n> = {\n  pdf: ({ resource, anchor }) => (\n    <PdfFirstPage resource={resource} anchor={anchor} />\n  ),\n  xlsx: ({ resource, thumbnailKey }) => (\n    <XlsxFirstSheet resource={resource} thumbnailKey={thumbnailKey} />\n  ),\n  pptx: ({ resource, thumbnailKey, anchor }) => (\n    <PptxFirstSlide\n      resource={resource}\n      thumbnailKey={thumbnailKey}\n      anchor={anchor}\n    />\n  ),\n  docx: ({ resource }) => <DocxFirstPage resource={resource} />,\n  image: ({ resource, descriptor, thumbnailKey, anchor, onError }) =>\n    isTiffDescriptor(descriptor) ? (\n      <TiffFirstPage\n        resource={resource}\n        thumbnailKey={thumbnailKey}\n        anchor={anchor}\n        onError={onError}\n      />\n    ) : (\n      <ImageFirstFrame resource={resource} anchor={anchor} onError={onError} />\n    ),\n  csv: ({ resource, thumbnailKey }) => (\n    <CsvFirstRows resource={resource} thumbnailKey={thumbnailKey} />\n  ),\n  markdown: ({ resource, thumbnailKey }) => (\n    <MarkdownFirstPage resource={resource} thumbnailKey={thumbnailKey} />\n  ),\n  html: ({ resource, thumbnailKey }) => (\n    <HtmlFirstPage resource={resource} thumbnailKey={thumbnailKey} />\n  ),\n  email: ({ resource, thumbnailKey }) => (\n    <MsgEmailThumbnail resource={resource} thumbnailKey={thumbnailKey} />\n  ),\n  text: ({ resource, descriptor, thumbnailKey }) =>\n    isCodeThumbnailDescriptor(descriptor) ? (\n      <CodeThumbnail resource={resource} thumbnailKey={thumbnailKey} />\n    ) : (\n      <TextThumbnail resource={resource} thumbnailKey={thumbnailKey} />\n    ),\n};\n\nexport function FirstThumbnailUnit({\n  resource,\n  descriptor,\n  thumbnailKey,\n  anchor,\n  onError,\n}: {\n  resource: ViewerResource;\n  descriptor: ViewerDescriptor;\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  if (descriptor.category === \"unsupported\") return null;\n  const render = DOCUMENT_RENDERERS[descriptor.category];\n  return render({ resource, descriptor, thumbnailKey, anchor, onError });\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderer-registry.tsx"
    },
    {
      "path": "components/file-thumbnail/thumbnail-cache.ts",
      "content": "import { registerThumbnailTestReset } from \"./thumbnail-test-reset\";\n\nexport interface ThumbnailCacheEntry<T> {\n  promise: Promise<T>;\n  status: \"pending\" | \"resolved\" | \"rejected\";\n  value?: T;\n  onResolve?: (value: T) => void;\n}\n\nexport interface ThumbnailCacheStore<T> {\n  get(key: string): ThumbnailCacheEntry<T> | undefined;\n  set(key: string, entry: ThumbnailCacheEntry<T>): void;\n  delete(key: string): boolean;\n  clear?(): void;\n  prune?(): void;\n}\n\nexport interface ThumbnailArtifactCacheOptions<T> {\n  maxEntries: number;\n  dispose?: (value: T) => void;\n}\n\nexport interface ThumbnailArtifactCache<T> extends ThumbnailCacheStore<T> {\n  readonly size: number;\n  clear(): void;\n  prune(): void;\n}\n\nexport function cachedThumbnailResource<T>(\n  cache: ThumbnailCacheStore<T>,\n  key: string,\n  load: () => Promise<T>,\n): Promise<T> {\n  const cached = cache.get(key);\n  if (cached) {\n    if (cached.status === \"rejected\") {\n      cache.delete(key);\n    }\n    return cached.promise;\n  }\n\n  const entry: ThumbnailCacheEntry<T> = {\n    status: \"pending\",\n    promise: load().then(\n      (value) => {\n        entry.status = \"resolved\";\n        entry.value = value;\n        entry.onResolve?.(value);\n        cache.prune?.();\n        return value;\n      },\n      (error) => {\n        entry.status = \"rejected\";\n        throw error;\n      },\n    ),\n  };\n  cache.set(key, entry);\n  return entry.promise;\n}\n\nexport function createThumbnailArtifactCache<T>({\n  maxEntries,\n  dispose,\n}: ThumbnailArtifactCacheOptions<T>): ThumbnailArtifactCache<T> {\n  const entries = new Map<string, ThumbnailCacheEntry<T>>();\n\n  const cache: ThumbnailArtifactCache<T> = {\n    get size() {\n      return entries.size;\n    },\n    get(key) {\n      const entry = entries.get(key);\n      if (!entry) return undefined;\n      entries.delete(key);\n      entries.set(key, entry);\n      return entry;\n    },\n    set(key, entry) {\n      entries.set(key, entry);\n      entry.onResolve = (value) => {\n        if (entries.get(key) !== entry) dispose?.(value);\n      };\n      cache.prune?.();\n    },\n    delete(key) {\n      const entry = entries.get(key);\n      if (!entry) return false;\n      entries.delete(key);\n      disposeCacheEntry(entry, dispose);\n      return true;\n    },\n    clear() {\n      for (const entry of entries.values()) {\n        disposeCacheEntry(entry, dispose);\n      }\n      entries.clear();\n    },\n    prune() {\n      while (entries.size > maxEntries) {\n        const evicted = evictOldestResolvedEntry(entries, dispose);\n        if (!evicted) return;\n      }\n    },\n  };\n\n  registerThumbnailTestReset(() => cache.clear?.());\n\n  return cache;\n}\n\nfunction evictOldestResolvedEntry<T>(\n  entries: Map<string, ThumbnailCacheEntry<T>>,\n  dispose?: (value: T) => void,\n): boolean {\n  for (const [key, entry] of entries) {\n    if (entry.status !== \"pending\") {\n      entries.delete(key);\n      disposeCacheEntry(entry, dispose);\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction disposeCacheEntry<T>(\n  entry: ThumbnailCacheEntry<T>,\n  dispose?: (value: T) => void,\n) {\n  if (entry.status === \"resolved\" && entry.value !== undefined) {\n    dispose?.(entry.value);\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-cache.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-client-preview.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { ViewerDescriptor } from \"@/lib/viewer-source\";\nimport { FileThumbnailShimmer } from \"@/components/ui/file-thumbnail-frame\";\nimport { useIsClient } from \"@/components/ui/use-is-client\";\n\nimport { ThumbnailErrorBoundary } from \"./errors\";\nimport { FirstThumbnailUnit } from \"./renderer-registry\";\nimport { useThumbnailInView } from \"./thumbnail-in-view\";\nimport type { ThumbnailAnchor } from \"./types\";\n\nexport function ThumbnailClientPreview({\n  resource,\n  descriptor,\n  thumbnailKey,\n  anchor,\n  onError,\n}: {\n  resource: ViewerResource;\n  descriptor: ViewerDescriptor;\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  const isClient = useIsClient();\n  const { ref: inViewRef, seen: isSeen } = useThumbnailInView();\n\n  return (\n    <div ref={inViewRef} className=\"absolute inset-0\">\n      {isClient && isSeen ? (\n        <ThumbnailErrorBoundary fallback={null} onError={onError}>\n          <React.Suspense fallback={<FileThumbnailShimmer />}>\n            <FirstThumbnailUnit\n              resource={resource}\n              descriptor={descriptor}\n              thumbnailKey={thumbnailKey}\n              anchor={anchor}\n              onError={onError}\n            />\n          </React.Suspense>\n        </ThumbnailErrorBoundary>\n      ) : (\n        <FileThumbnailShimmer />\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-client-preview.tsx"
    },
    {
      "path": "components/file-thumbnail/thumbnail-decode-queue.ts",
      "content": "import { registerThumbnailTestReset } from \"./thumbnail-test-reset\";\n\nconst MAX_CONCURRENT_DECODES = 3;\nlet activeDecodes = 0;\nlet decodeGeneration = 0;\nconst decodeQueue: Array<() => void> = [];\n\nfunction acquireThumbnailDecodeSlot(): Promise<() => void> {\n  return new Promise((resolve) => {\n    const generation = decodeGeneration;\n    const grant = () => {\n      if (generation !== decodeGeneration) return;\n      activeDecodes++;\n      let hasReleased = false;\n      resolve(() => {\n        if (hasReleased) return;\n        hasReleased = true;\n        if (generation !== decodeGeneration) return;\n        activeDecodes--;\n        decodeQueue.shift()?.();\n      });\n    };\n    if (activeDecodes < MAX_CONCURRENT_DECODES) grant();\n    else decodeQueue.push(grant);\n  });\n}\n\nexport async function withThumbnailDecodeSlot<T>(\n  fn: () => Promise<T>,\n): Promise<T> {\n  const release = await acquireThumbnailDecodeSlot();\n  try {\n    return await fn();\n  } finally {\n    release();\n  }\n}\n\nexport function getThumbnailDecodeQueueSnapshot() {\n  return {\n    activeDecodes,\n    queuedDecodes: decodeQueue.length,\n    maxConcurrentDecodes: MAX_CONCURRENT_DECODES,\n  };\n}\n\nfunction resetThumbnailDecodeQueueForTests() {\n  decodeGeneration++;\n  activeDecodes = 0;\n  decodeQueue.splice(0);\n}\n\nregisterThumbnailTestReset(resetThumbnailDecodeQueueForTests);\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-decode-queue.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-direct-image.tsx",
      "content": "\"use client\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { ViewerDescriptor } from \"@/lib/viewer-source\";\nimport {\n  FileThumbnailFrame,\n  type FileThumbnailFrameProps,\n} from \"@/components/ui/file-thumbnail-frame\";\n\nimport { isTiffDescriptor } from \"./descriptor\";\nimport {\n  createThumbnailErrorState,\n  type ThumbnailErrorState,\n} from \"./thumbnail-error-state\";\nimport { createThumbnailImageLoadError } from \"./thumbnail-errors\";\nimport { ANCHOR_OBJECT_POSITION, type ThumbnailAnchor } from \"./types\";\n\nexport function isDirectImageThumbnail({\n  descriptor,\n  directUrl,\n}: {\n  descriptor: ViewerDescriptor;\n  directUrl?: string | null;\n}): boolean {\n  return (\n    descriptor.category === \"image\" &&\n    !isTiffDescriptor(descriptor) &&\n    Boolean(directUrl)\n  );\n}\n\nexport function DirectImageThumbnail({\n  descriptor,\n  resource,\n  directUrl,\n  previewAspectRatio,\n  className,\n  anchor,\n  renderKey,\n  errorState,\n  thumbnailProps,\n  onError,\n}: {\n  descriptor: ViewerDescriptor;\n  resource: ViewerResource;\n  directUrl: string;\n  previewAspectRatio?: number;\n  className?: string;\n  anchor: ThumbnailAnchor;\n  renderKey: string;\n  errorState: ThumbnailErrorState | null;\n  thumbnailProps?: Omit<FileThumbnailFrameProps, \"file\" | \"onError\">;\n  onError?: (error: unknown, errorState: ThumbnailErrorState) => void;\n}) {\n  const failedDirectImage =\n    errorState?.info.format === \"image\" ? errorState : null;\n\n  return (\n    <FileThumbnailFrame\n      {...thumbnailProps}\n      file={{ name: descriptor.displayName, type: descriptor.mimeType ?? \"\" }}\n      previewImageUrl={directUrl}\n      previewAspectRatio={previewAspectRatio}\n      className={className}\n      previewClassName={ANCHOR_OBJECT_POSITION[anchor]}\n      state={failedDirectImage ? \"error\" : \"loaded\"}\n      aria-label={failedDirectImage?.info.userMessage}\n      title={failedDirectImage?.info.userMessage}\n      data-error-domain={failedDirectImage?.info.domain}\n      data-error-format={failedDirectImage?.info.format}\n      data-error-kind={failedDirectImage?.info.kind}\n      data-error-message={failedDirectImage?.info.message}\n      onPreviewError={() => {\n        const error = createThumbnailImageLoadError();\n        const nextErrorState = createThumbnailErrorState({\n          renderKey,\n          error,\n          resource,\n          descriptor,\n        });\n        onError?.(error, nextErrorState);\n      }}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-direct-image.tsx"
    },
    {
      "path": "components/file-thumbnail/thumbnail-error-state.ts",
      "content": "import { toViewerErrorInfo, type ViewerErrorInfo } from \"@/lib/viewer-errors\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { ViewerDescriptor } from \"@/lib/viewer-source\";\n\nimport { thumbnailCategoryFormat } from \"./thumbnail-errors\";\n\nexport interface ThumbnailErrorState {\n  renderKey: string;\n  info: ViewerErrorInfo;\n}\n\nexport function createThumbnailErrorState({\n  renderKey,\n  error,\n  resource,\n  descriptor,\n}: {\n  renderKey: string;\n  error: unknown;\n  resource: ViewerResource;\n  descriptor: ViewerDescriptor;\n}): ThumbnailErrorState {\n  return {\n    renderKey,\n    info: toViewerErrorInfo(error, {\n      format: thumbnailCategoryFormat(descriptor.category),\n      sourceKind: resource.sourceKind,\n      canDownload: !resource.originalDownload.isDisabled,\n    }),\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-error-state.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-errors.ts",
      "content": "import {\n  isResourceError,\n  ViewerFormatError,\n  type ViewerFormat,\n} from \"@/lib/viewer-errors\";\nimport type { FileCategory } from \"@/lib/viewer-source\";\n\nexport async function withThumbnailFormatError<T>(\n  category: FileCategory,\n  kind:\n    | \"decode_failed\"\n    | \"load_failed\"\n    | \"parse_failed\"\n    | \"render_failed\"\n    | \"unknown\",\n  fileName: string,\n  message: string,\n  load: () => Promise<T>,\n): Promise<T> {\n  try {\n    return await load();\n  } catch (error) {\n    if (isResourceError(error)) throw error;\n    throw new ViewerFormatError({\n      format: thumbnailCategoryFormat(category),\n      kind,\n      message: `${message}: ${fileName}`,\n      cause: error,\n    });\n  }\n}\n\nexport function createThumbnailImageLoadError(): ViewerFormatError {\n  return new ViewerFormatError({\n    format: \"image\",\n    kind: \"load_failed\",\n    message: \"Could not load image preview.\",\n  });\n}\n\nexport function thumbnailCategoryFormat(category: FileCategory): ViewerFormat {\n  if (category === \"markdown\" || category === \"html\" || category === \"email\") {\n    return \"text\";\n  }\n  if (category === \"unsupported\") return \"file\";\n  return category;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-errors.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-in-view.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport function useThumbnailInView() {\n  const [seen, setSeen] = React.useState(false);\n  const [node, setNode] = React.useState<HTMLElement | null>(null);\n  const seenRef = React.useRef(false);\n  const ref = React.useCallback((el: HTMLElement | null) => setNode(el), []);\n\n  useKeyedMountEffect(joinEffectKey([node]), () => {\n    if (!node || seenRef.current) return;\n    if (typeof IntersectionObserver === \"undefined\") {\n      seenRef.current = true;\n      setSeen(true);\n      return;\n    }\n    const observer = new IntersectionObserver(\n      (entries) => {\n        if (entries.some((entry) => entry.isIntersecting)) {\n          seenRef.current = true;\n          setSeen(true);\n          observer.disconnect();\n        }\n      },\n      { rootMargin: \"300px\" },\n    );\n    observer.observe(node);\n    return () => observer.disconnect();\n  });\n\n  return { ref, seen };\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-in-view.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-limits.ts",
      "content": "export const TEXT_THUMBNAIL_MAX_BYTES = 64 * 1024;\nexport const CSV_THUMBNAIL_MAX_ROWS = 16;\nexport const CSV_THUMBNAIL_MAX_COLUMNS = 6;\nexport const XLSX_THUMBNAIL_MAX_ROWS = 16;\nexport const XLSX_THUMBNAIL_MAX_COLUMNS = 6;\nexport const TIFF_THUMBNAIL_TARGET_WIDTH = 320;\nexport const TEXT_THUMBNAIL_CACHE_MAX_ENTRIES = 96;\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-limits.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-options.ts",
      "content": "import type { ViewerDescriptor } from \"@/lib/viewer-source\";\n\nimport { isTiffDescriptor } from \"./descriptor\";\nimport { thumbnailOption, type ThumbnailOption } from \"./keys\";\nimport {\n  CSV_THUMBNAIL_MAX_COLUMNS,\n  CSV_THUMBNAIL_MAX_ROWS,\n  TEXT_THUMBNAIL_MAX_BYTES,\n  TIFF_THUMBNAIL_TARGET_WIDTH,\n  XLSX_THUMBNAIL_MAX_COLUMNS,\n  XLSX_THUMBNAIL_MAX_ROWS,\n} from \"./thumbnail-limits\";\n\nexport function getThumbnailOptions(\n  descriptor: ViewerDescriptor,\n): ThumbnailOption[] {\n  switch (descriptor.category) {\n    case \"text\":\n    case \"markdown\":\n    case \"html\":\n      return [thumbnailOption(\"text-max-bytes\", TEXT_THUMBNAIL_MAX_BYTES)];\n    case \"csv\":\n      return [\n        thumbnailOption(\"text-max-bytes\", TEXT_THUMBNAIL_MAX_BYTES),\n        thumbnailOption(\"csv-max-rows\", CSV_THUMBNAIL_MAX_ROWS),\n        thumbnailOption(\"csv-max-columns\", CSV_THUMBNAIL_MAX_COLUMNS),\n      ];\n    case \"xlsx\":\n      return [\n        thumbnailOption(\"xlsx-max-rows\", XLSX_THUMBNAIL_MAX_ROWS),\n        thumbnailOption(\"xlsx-max-columns\", XLSX_THUMBNAIL_MAX_COLUMNS),\n      ];\n    case \"image\":\n      return isTiffDescriptor(descriptor)\n        ? [thumbnailOption(\"tiff-target-width\", TIFF_THUMBNAIL_TARGET_WIDTH)]\n        : [];\n    default:\n      return [];\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-options.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-profile.ts",
      "content": "import type { ThumbnailFileMeta } from \"./thumbnail-text\";\n\n/**\n * Profiling helper: logs `[thumb] <label> <ms>` when enabled. Gated on a global\n * so it costs nothing in normal use; the profiler sets it before navigation.\n */\nexport async function timedThumbnail<T>(\n  label: string,\n  fn: () => Promise<T>,\n): Promise<T> {\n  const on =\n    typeof globalThis !== \"undefined\" &&\n    (globalThis as { __THUMB_PROFILE__?: boolean }).__THUMB_PROFILE__;\n  if (!on) return fn();\n  const t0 = performance.now();\n  try {\n    return await fn();\n  } finally {\n    console.log(`[thumb] ${label} ${(performance.now() - t0).toFixed(1)}ms`);\n  }\n}\n\nexport function shortName(meta: ThumbnailFileMeta): string {\n  return meta.fileName;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-profile.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-resource.ts",
      "content": "import { registerThumbnailTestReset } from \"./thumbnail-test-reset\";\n\nexport function useThumbnailResource<T>(promise: Promise<T>): T {\n  const record = getThumbnailResourceRecord(promise);\n  if (record.status === \"pending\") throw record.promise;\n  if (record.status === \"rejected\") throw record.error;\n  return record.value as T;\n}\n\nlet thumbnailResourceRecords = new WeakMap<\n  Promise<unknown>,\n  ThumbnailResourceRecord<unknown>\n>();\n\nfunction getThumbnailResourceRecord<T>(\n  promise: Promise<T>,\n): ThumbnailResourceRecord<T> {\n  const cached = thumbnailResourceRecords.get(promise) as\n    | ThumbnailResourceRecord<T>\n    | undefined;\n  if (cached) return cached;\n\n  const record: ThumbnailResourceRecord<T> = {\n    promise,\n    status: \"pending\",\n  };\n  promise.then(\n    (value) => {\n      record.status = \"resolved\";\n      record.value = value;\n    },\n    (error) => {\n      record.status = \"rejected\";\n      record.error = error;\n    },\n  );\n  thumbnailResourceRecords.set(promise, record);\n  return record;\n}\n\ninterface ThumbnailResourceRecord<T> {\n  promise: Promise<T>;\n  status: \"pending\" | \"resolved\" | \"rejected\";\n  value?: T;\n  error?: unknown;\n}\n\nfunction resetThumbnailResourceRecordsForTests() {\n  thumbnailResourceRecords = new WeakMap();\n}\n\nregisterThumbnailTestReset(resetThumbnailResourceRecordsForTests);\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-resource.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-test-reset.ts",
      "content": "const resetHandlers = new Set<() => void>();\n\nexport function registerThumbnailTestReset(handler: () => void): () => void {\n  resetHandlers.add(handler);\n  return () => {\n    resetHandlers.delete(handler);\n  };\n}\n\nexport function clearThumbnailCachesForTests() {\n  for (const reset of [...resetHandlers]) reset();\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-test-reset.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-text.ts",
      "content": "import {\n  isAbortError,\n  isResourceError,\n  ResourceError,\n} from \"@/lib/viewer-errors\";\nimport type {\n  ViewerContentBytes,\n  ViewerContentIdentity,\n  ViewerContentRange,\n  ViewerContentStream,\n} from \"@/lib/viewer-resource\";\nimport type { ViewerSource } from \"@/lib/viewer-source\";\n\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"./thumbnail-cache\";\nimport {\n  TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,\n  TEXT_THUMBNAIL_MAX_BYTES,\n} from \"./thumbnail-limits\";\nimport { shortName, timedThumbnail } from \"./thumbnail-profile\";\n\nexport interface ThumbnailFileMeta {\n  fileName: string;\n  mimeType?: string;\n  sourceKind: ViewerSource[\"kind\"];\n}\n\nexport type ThumbnailTextContent = ViewerContentIdentity &\n  ViewerContentRange &\n  ViewerContentStream;\n\nexport type ThumbnailBytesContent = ViewerContentIdentity & ViewerContentBytes;\n\nconst textCache = createThumbnailArtifactCache<string>({\n  maxEntries: TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,\n});\n\nexport function getThumbnailText(\n  meta: ThumbnailFileMeta,\n  content: ThumbnailTextContent,\n  thumbnailKey: string,\n): Promise<string> {\n  return cachedThumbnailResource(textCache, thumbnailKey, () =>\n    timedThumbnail(`text:fetch ${shortName(meta)}`, async () => {\n      if (meta.sourceKind === \"url\" || content.sourceKind === \"url\") {\n        try {\n          return await readThumbnailTextRange(content, \"no-store\");\n        } catch (error) {\n          if (shouldReadTextStreamPrefix(error)) {\n            return readThumbnailTextStreamPrefix(content, \"no-store\");\n          }\n          throw error;\n        }\n      }\n      return readThumbnailTextRange(content);\n    }),\n  );\n}\n\nexport function thumbnailFileMeta({\n  fileName,\n  mimeType,\n  sourceKind,\n}: ThumbnailFileMeta): ThumbnailFileMeta {\n  return { fileName, mimeType, sourceKind };\n}\n\nfunction shouldReadTextStreamPrefix(error: unknown): boolean {\n  const candidate = resourceErrorCandidate(error);\n  return Boolean(\n    candidate &&\n      (candidate.kind === \"invalid_range\" ||\n        (candidate.kind === \"http_error\" && candidate.status === 416)),\n  );\n}\n\nfunction resourceErrorCandidate(error: unknown):\n  | {\n      kind?: unknown;\n      status?: unknown;\n    }\n  | undefined {\n  if (!error || typeof error !== \"object\") return undefined;\n  return error as { kind?: unknown; status?: unknown };\n}\n\nasync function readThumbnailTextRange(\n  content: ThumbnailTextContent,\n  cache?: RequestCache,\n) {\n  const rangeRequest = {\n    start: 0,\n    end: TEXT_THUMBNAIL_MAX_BYTES - 1,\n  };\n  const range = cache\n    ? await content.readRange(rangeRequest, { cache })\n    : await content.readRange(rangeRequest);\n  return new TextDecoder().decode(range.buffer);\n}\n\nasync function readThumbnailTextStreamPrefix(\n  content: ThumbnailTextContent,\n  cache?: RequestCache,\n) {\n  const stream = cache\n    ? await content.readStream({ cache })\n    : await content.readStream();\n  const reader = stream.getReader();\n  const decoder = new TextDecoder();\n  let remainingBytes = TEXT_THUMBNAIL_MAX_BYTES;\n  let text = \"\";\n\n  try {\n    while (remainingBytes > 0) {\n      const { done, value } = await reader.read();\n      if (done) return text + decoder.decode();\n\n      const chunk =\n        value.byteLength > remainingBytes\n          ? value.slice(0, remainingBytes)\n          : value;\n      text += decoder.decode(chunk, { stream: true });\n      remainingBytes -= chunk.byteLength;\n\n      if (chunk.byteLength < value.byteLength || remainingBytes === 0) {\n        cancelThumbnailTextReader(reader);\n        return text + decoder.decode();\n      }\n    }\n\n    cancelThumbnailTextReader(reader);\n    return text + decoder.decode();\n  } catch (error) {\n    throw createThumbnailTextReadError(error);\n  }\n}\n\nfunction createThumbnailTextReadError(error: unknown): ResourceError {\n  if (isResourceError(error)) return error;\n  if (isAbortError(error)) {\n    return new ResourceError({\n      kind: \"aborted\",\n      message: \"Resource load was aborted.\",\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\nfunction cancelThumbnailTextReader(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n) {\n  try {\n    void reader.cancel().catch(() => {});\n  } catch {\n    /* The preview prefix is already available; cancellation is best-effort. */\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-text.ts"
    },
    {
      "path": "components/file-thumbnail/thumbnail-worker-client.ts",
      "content": "import { registerThumbnailTestReset } from \"./thumbnail-test-reset\";\n\nexport interface ThumbnailWorkerMessage {\n  id: number;\n}\n\nexport interface ThumbnailWorkerClientOptions<Request, Response> {\n  createWorker: () => Worker;\n  resolve: (response: Response) => unknown;\n  reject: (response: Response) => string | Error;\n}\n\nexport interface ThumbnailWorkerRequest<Request> {\n  request: Request;\n  transfer?: Transferable[];\n}\n\nexport interface ThumbnailWorkerClient<Request, Response> {\n  request<T = unknown>(input: ThumbnailWorkerRequest<Request>): Promise<T>;\n  reset(): void;\n  pendingCount(): number;\n}\n\nexport function createThumbnailWorkerClient<\n  Request extends ThumbnailWorkerMessage,\n  Response extends ThumbnailWorkerMessage,\n>({\n  createWorker,\n  resolve,\n  reject,\n}: ThumbnailWorkerClientOptions<Request, Response>): ThumbnailWorkerClient<\n  Omit<Request, \"id\">,\n  Response\n> {\n  let worker: Worker | null = null;\n  let requestId = 0;\n  const pending = new Map<\n    number,\n    { resolve: (value: unknown) => void; reject: (error: Error) => void }\n  >();\n\n  const getWorker = () => {\n    if (!worker) {\n      const nextWorker = createWorker();\n      worker = nextWorker;\n      nextWorker.onmessage = (event: MessageEvent<Response>) => {\n        if (worker !== nextWorker) return;\n        const response = event.data;\n        const entry = pending.get(response.id);\n        if (!entry) return;\n        pending.delete(response.id);\n        try {\n          const value = resolve(response);\n          if (value !== undefined) entry.resolve(value);\n          else entry.reject(toWorkerError(reject(response)));\n        } catch (error) {\n          entry.reject(toWorkerError(error));\n        }\n      };\n      nextWorker.onerror = (event) => {\n        if (worker !== nextWorker) return;\n        nextWorker.terminate();\n        worker = null;\n        requestId = 0;\n        rejectPendingRequests(\n          event.error instanceof Error\n            ? event.error\n            : new Error(event.message || \"Thumbnail worker failed.\"),\n        );\n      };\n    }\n    return worker;\n  };\n\n  const rejectPendingRequests = (error: Error) => {\n    for (const entry of pending.values()) entry.reject(error);\n    pending.clear();\n  };\n\n  const reset = () => {\n    worker?.terminate();\n    worker = null;\n    requestId = 0;\n    rejectPendingRequests(new Error(\"Thumbnail worker reset.\"));\n  };\n\n  const client: ThumbnailWorkerClient<Omit<Request, \"id\">, Response> = {\n    request<T = unknown>(\n      input: ThumbnailWorkerRequest<Omit<Request, \"id\">>,\n    ): Promise<T> {\n      const { request, transfer } = input;\n      const id = ++requestId;\n      return new Promise<T>((resolveRequest, rejectRequest) => {\n        pending.set(id, {\n          resolve: (value) => resolveRequest(value as T),\n          reject: rejectRequest,\n        });\n        try {\n          getWorker().postMessage({ id, ...request }, transfer ?? []);\n        } catch (error) {\n          pending.delete(id);\n          rejectRequest(toWorkerError(error));\n        }\n      });\n    },\n    reset,\n    pendingCount: () => pending.size,\n  };\n\n  registerThumbnailTestReset(reset);\n\n  return client;\n}\n\nfunction toWorkerError(error: unknown): Error {\n  if (error instanceof Error) return error;\n  return new Error(String(error || \"Thumbnail worker failed.\"));\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-worker-client.ts"
    },
    {
      "path": "components/file-thumbnail/types.ts",
      "content": "/**\n * Which corner of a generated preview stays pinned when its content is larger\n * than the thumbnail frame.\n */\nexport type ThumbnailAnchor = \"top-left\" | \"top-right\" | \"bottom-left\";\n\n/** Absolute-position classes that pin a covered element to its anchor corner. */\nexport const ANCHOR_CORNER: Record<ThumbnailAnchor, string> = {\n  \"top-left\": \"top-0 left-0\",\n  \"top-right\": \"top-0 right-0\",\n  \"bottom-left\": \"bottom-0 left-0\",\n};\n\n/** `object-position` classes for the <img> cover path (raster images). */\nexport const ANCHOR_OBJECT_POSITION: Record<ThumbnailAnchor, string> = {\n  \"top-left\": \"object-left-top\",\n  \"top-right\": \"object-right-top\",\n  \"bottom-left\": \"object-left-bottom\",\n};\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/types.ts"
    },
    {
      "path": "components/file-thumbnail/renderers/csv-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { inferCsvDialect, parseCsv } from \"@/lib/csv\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { GridTable } from \"@/components/file-thumbnail/renderers/layout\";\nimport {\n  CSV_THUMBNAIL_MAX_COLUMNS,\n  CSV_THUMBNAIL_MAX_ROWS,\n} from \"@/components/file-thumbnail/thumbnail-limits\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  getThumbnailText,\n  thumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\n\nexport function CsvFirstRows({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const raw = useThumbnailResource(\n    getThumbnailText(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  const rows = React.useMemo(() => {\n    const dialect = inferCsvDialect({\n      fileName: resource.fileName,\n      mimeType: resource.mimeType,\n    });\n    const table = parseCsv(raw, dialect);\n    const header = table.columns.length\n      ? [table.columns.slice(0, CSV_THUMBNAIL_MAX_COLUMNS)]\n      : [];\n    return header.concat(\n      table.rows\n        .slice(0, CSV_THUMBNAIL_MAX_ROWS - header.length)\n        .map((row) => row.slice(0, CSV_THUMBNAIL_MAX_COLUMNS)),\n    );\n  }, [raw, resource.fileName, resource.mimeType]);\n  return <GridTable rows={rows} headerRow />;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/csv-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/docx-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { getDocxDocumentResource } from \"@/lib/docx-document-resource\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { renderCachedDocxPreview } from \"@/components/ui/docx-viewer-render\";\nimport {\n  Surface,\n  useElementWidth,\n} from \"@/components/file-thumbnail/renderers/layout\";\nimport { withThumbnailDecodeSlot } from \"@/components/file-thumbnail/thumbnail-decode-queue\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\n\nconst DOCX_PAGE_W = 816; // US Letter at 96dpi\nconst DOCX_THUMBNAIL_PAGE_STYLE = {\n  backgroundColor: \"white\",\n  color: \"black\",\n  colorScheme: \"light\",\n} satisfies React.CSSProperties;\n\nexport function DocxFirstPage({ resource }: { resource: ViewerResource }) {\n  const bytes = useThumbnailResource(getDocxDocumentResource(resource.content));\n  const { ref: frameRef, width: frameWidth } = useElementWidth();\n  const [renderError, setRenderError] = React.useState<unknown>(null);\n\n  const renderRef = React.useCallback(\n    (el: HTMLDivElement | null) => {\n      if (!el) return;\n      let active = true;\n      void withThumbnailDecodeSlot(() =>\n        withThumbnailFormatError(\n          \"docx\",\n          \"render_failed\",\n          resource.fileName,\n          \"Failed to render DOCX thumbnail\",\n          () =>\n            timedThumbnail(`docx:render ${shortName(resource)}`, async () => {\n              if (!active) return;\n              const { renderHost } = await renderCachedDocxPreview({\n                buffer: () => bytes.slice(0),\n                cacheKey: resource.content.key,\n                getScale: () => 1,\n              });\n              if (!active) return;\n              commitDocxThumbnailRender(el, renderHost);\n            }),\n        ),\n      ).catch((error) => {\n        if (active) setRenderError(error);\n      });\n      return () => {\n        active = false;\n      };\n    },\n    [bytes, resource],\n  );\n\n  const scale = frameWidth ? frameWidth / DOCX_PAGE_W : null;\n\n  if (renderError) throw renderError;\n\n  return (\n    <Surface>\n      <div ref={frameRef} className=\"absolute inset-0 overflow-hidden bg-white\">\n        <div\n          className=\"absolute top-0 left-0 origin-top-left [&_.docx-wrapper]:!bg-transparent [&_.docx-wrapper]:!p-0 [&_section.docx]:!mb-0 [&_section.docx]:!bg-white [&_section.docx]:!text-black [&_section.docx]:!shadow-none\"\n          style={{\n            ...DOCX_THUMBNAIL_PAGE_STYLE,\n            width: DOCX_PAGE_W,\n            transform: scale ? `scale(${scale})` : undefined,\n            visibility: scale ? \"visible\" : \"hidden\",\n          }}\n        >\n          <div ref={renderRef} />\n        </div>\n      </div>\n    </Surface>\n  );\n}\n\nfunction commitDocxThumbnailRender(host: HTMLElement, renderHost: HTMLElement) {\n  const wrapper = renderHost.querySelector<HTMLElement>(\".docx-wrapper\");\n  const firstPage = renderHost.querySelector<HTMLElement>(\n    \".docx-wrapper > section.docx\",\n  );\n  if (!wrapper || !firstPage) {\n    throw new Error(\"DOCX render produced no pages.\");\n  }\n\n  const staticNodes = Array.from(renderHost.childNodes).filter(\n    (node) => node !== wrapper,\n  );\n  wrapper.replaceChildren(firstPage);\n  host.replaceChildren(...staticNodes, wrapper);\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/docx-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/html-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { IframeDoc } from \"@/components/file-thumbnail/renderers/layout\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  getThumbnailText,\n  thumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\n\nexport function HtmlFirstPage({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const html = useThumbnailResource(\n    getThumbnailText(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  return <IframeDoc html={html} />;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/html-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/image-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerContentBlob, ViewerResource } from \"@/lib/viewer-resource\";\nimport { FileThumbnailShimmer } from \"@/components/ui/file-thumbnail-frame\";\nimport { useObjectUrl } from \"@/components/file-thumbnail/renderers/use-object-url\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { createThumbnailImageLoadError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport type { ThumbnailAnchor } from \"@/components/file-thumbnail/types\";\nimport { ANCHOR_CORNER } from \"@/components/file-thumbnail/types\";\n\nconst IMAGE_BLOB_THUMBNAIL_CACHE_MAX_ENTRIES = 96;\n\nconst imageBlobCache = createThumbnailArtifactCache<Blob>({\n  maxEntries: IMAGE_BLOB_THUMBNAIL_CACHE_MAX_ENTRIES,\n});\n\nfunction getImageBlob(content: ViewerContentBlob): Promise<Blob> {\n  return cachedThumbnailResource(imageBlobCache, content.key, () =>\n    content.readBlob(),\n  );\n}\n\nexport function ImageFirstFrame({\n  resource,\n  anchor,\n  onError,\n}: {\n  resource: ViewerResource;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  const directUrl = resource.content.directUrl;\n  if (directUrl) {\n    return (\n      <ImageUrlPreview url={directUrl} anchor={anchor} onError={onError} />\n    );\n  }\n\n  return (\n    <ImageBlobPreview\n      content={resource.content}\n      anchor={anchor}\n      onError={onError}\n    />\n  );\n}\n\nfunction ImageUrlPreview({\n  url,\n  anchor,\n  onError,\n}: {\n  url: string;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  return (\n    <div className=\"absolute inset-0 overflow-hidden bg-white\">\n      <img\n        src={url}\n        alt=\"\"\n        loading=\"lazy\"\n        decoding=\"async\"\n        className={cn(\"absolute block w-full\", ANCHOR_CORNER[anchor])}\n        onError={() => onError(createThumbnailImageLoadError())}\n      />\n    </div>\n  );\n}\n\nfunction ImageBlobPreview({\n  content,\n  anchor,\n  onError,\n}: {\n  content: ViewerContentBlob;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  const blob = useThumbnailResource(getImageBlob(content));\n  const url = useObjectUrl(blob);\n\n  if (!url) return <FileThumbnailShimmer />;\n\n  return <ImageUrlPreview url={url} anchor={anchor} onError={onError} />;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/image-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/layout.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Centered, clipped white surface that hosts a rendered first unit. */\nexport function Surface({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"absolute inset-0 flex items-center justify-center overflow-hidden bg-white\">\n      {children}\n    </div>\n  );\n}\n\nexport function useElementWidth() {\n  const [width, setWidth] = React.useState<number | null>(null);\n  const ref = React.useCallback((el: HTMLElement | null) => {\n    if (!el) return;\n    setWidth(el.clientWidth);\n    const observer = new ResizeObserver((entries) => {\n      const nextWidth = entries[0]?.contentRect.width;\n      if (nextWidth) setWidth(nextWidth);\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, []);\n\n  return { ref, width };\n}\n\nconst GRID_COL_W = 46;\n\n/** Internal-gridline table shared by the XLSX and CSV previews. */\nexport function GridTable({\n  rows,\n  headerRow,\n}: {\n  rows: string[][];\n  headerRow?: boolean;\n}) {\n  const colCount = Math.max(1, ...rows.map((r) => r.length));\n  return (\n    <div className=\"absolute inset-0 overflow-hidden bg-white text-slate-700 dark:bg-slate-950 dark:text-slate-300\">\n      <table\n        className=\"border-collapse leading-tight\"\n        style={{\n          width: colCount * GRID_COL_W,\n          tableLayout: \"fixed\",\n          fontSize: 7,\n        }}\n      >\n        <tbody>\n          {rows.map((row, r) => (\n            <tr key={r}>\n              {Array.from({ length: colCount }, (_, c) => (\n                <td\n                  key={c}\n                  className={cn(\n                    \"truncate border-r border-b border-slate-200 px-1 py-0.5 last:border-r-0 dark:border-slate-800\",\n                    headerRow && r === 0\n                      ? \"bg-slate-50 font-semibold text-slate-900 dark:bg-slate-900/70 dark:text-slate-100\"\n                      : \"text-slate-700 dark:text-slate-300\",\n                  )}\n                >\n                  {row[c] ?? \"\"}\n                </td>\n              ))}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  );\n}\n\n/**\n * Renders an HTML string into a fixed-size (square) sandboxed iframe and scales\n * it to the measured frame width, so the top of the page fills the tile. The\n * iframe is inert (no scripts, no pointer events).\n */\nexport function IframeDoc({ html }: { html: string }) {\n  const { ref, width } = useElementWidth();\n\n  const BASE = 820;\n  const scale = width ? width / BASE : null;\n\n  return (\n    <div ref={ref} className=\"absolute inset-0 overflow-hidden bg-white\">\n      <iframe\n        srcDoc={html}\n        title=\"\"\n        sandbox=\"\"\n        scrolling=\"no\"\n        tabIndex={-1}\n        aria-hidden\n        className=\"pointer-events-none origin-top-left border-0\"\n        style={{\n          width: BASE,\n          height: BASE,\n          transform: scale ? `scale(${scale})` : undefined,\n          visibility: scale ? \"visible\" : \"hidden\",\n        }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/layout.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/markdown-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport type * as DOMPurify from \"dompurify\";\nimport type * as Marked from \"marked\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { IframeDoc } from \"@/components/file-thumbnail/renderers/layout\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  getThumbnailText,\n  thumbnailFileMeta,\n  type ThumbnailFileMeta,\n  type ThumbnailTextContent,\n} from \"@/components/file-thumbnail/thumbnail-text\";\n\nlet mdLibs: Promise<[typeof Marked, typeof DOMPurify]> | null = null;\n\nfunction loadMarkdown() {\n  if (!mdLibs) mdLibs = Promise.all([import(\"marked\"), import(\"dompurify\")]);\n  return mdLibs;\n}\n\nconst MARKDOWN_THUMBNAIL_CACHE_MAX_ENTRIES = 64;\n\nconst markdownBodyCache = createThumbnailArtifactCache<string>({\n  maxEntries: MARKDOWN_THUMBNAIL_CACHE_MAX_ENTRIES,\n});\n\ntype MarkdownThumbnailColorScheme = \"light\" | \"dark\";\n\nfunction getMarkdownBody(\n  meta: ThumbnailFileMeta,\n  content: ThumbnailTextContent,\n  thumbnailKey: string,\n): Promise<string> {\n  return cachedThumbnailResource(markdownBodyCache, thumbnailKey, () =>\n    withThumbnailFormatError(\n      \"markdown\",\n      \"render_failed\",\n      meta.fileName,\n      \"Failed to render markdown thumbnail\",\n      () =>\n        timedThumbnail(`markdown:total ${shortName(meta)}`, async () => {\n          const [text, [{ marked }, DOMPurifyMod]] = await Promise.all([\n            getThumbnailText(meta, content, thumbnailKey),\n            loadMarkdown(),\n          ]);\n          const purifier = DOMPurifyMod as unknown as {\n            default?: { sanitize?: (html: string) => string };\n            sanitize?: (html: string) => string;\n          };\n          const sanitize = purifier.default?.sanitize ?? purifier.sanitize;\n          if (!sanitize) throw new Error(\"DOMPurify sanitize unavailable\");\n          return sanitize(await marked.parse(text));\n        }),\n    ),\n  );\n}\n\nfunction createMarkdownDoc(\n  body: string,\n  colorScheme: MarkdownThumbnailColorScheme,\n) {\n  return `<!doctype html><html data-color-scheme=\"${colorScheme}\"><head><meta charset=\"utf-8\"><style>\n        :root{color-scheme:light;--md-bg:#ffffff;--md-fg:#0f172a;--md-muted:#f1f5f9;--md-border:#e2e8f0;--md-quote:#475569;--md-link:#4f46e5}\n        :root[data-color-scheme=\"dark\"]{color-scheme:dark;--md-bg:#111113;--md-fg:#e4e4e7;--md-muted:#1f2937;--md-border:#3f3f46;--md-quote:#a1a1aa;--md-link:#a5b4fc}\n        html,body{min-height:100%;background:var(--md-bg)}\n        body{margin:0;padding:18px;font:14px/1.6 -apple-system,BlinkMacSystemFont,system-ui,sans-serif;color:var(--md-fg)}\n        h1{font-size:1.6em;margin:.1em 0 .5em;border-bottom:1px solid var(--md-border);padding-bottom:.25em}\n        h2{font-size:1.3em;margin:1em 0 .4em;border-bottom:1px solid var(--md-border);padding-bottom:.25em}\n        h3{font-size:1.1em;margin:1em 0 .3em}\n        p,ul,ol{margin:0 0 .8em}ul,ol{padding-left:1.4em}\n        code{font-family:ui-monospace,SFMono-Regular,monospace;background:var(--md-muted);padding:.1em .35em;border-radius:4px;font-size:.85em}\n        pre{background:var(--md-muted);padding:12px;border-radius:8px;overflow:hidden}pre code{background:none;padding:0}\n        table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--md-border);padding:4px 8px;text-align:left}\n        a{color:var(--md-link)}blockquote{margin:0 0 .8em;padding-left:12px;border-left:3px solid var(--md-border);color:var(--md-quote)}\n      </style></head><body>${body}</body></html>`;\n}\n\nfunction useMarkdownThumbnailColorScheme(): MarkdownThumbnailColorScheme {\n  return React.useSyncExternalStore(\n    subscribeMarkdownThumbnailColorScheme,\n    getMarkdownThumbnailColorScheme,\n    () => \"light\",\n  );\n}\n\nfunction subscribeMarkdownThumbnailColorScheme(onChange: () => void) {\n  if (\n    typeof document === \"undefined\" ||\n    typeof MutationObserver === \"undefined\"\n  ) {\n    return () => {};\n  }\n\n  const observer = new MutationObserver(onChange);\n  observer.observe(document.documentElement, {\n    attributes: true,\n    attributeFilter: [\"class\"],\n  });\n  return () => observer.disconnect();\n}\n\nfunction getMarkdownThumbnailColorScheme(): MarkdownThumbnailColorScheme {\n  if (\n    typeof document !== \"undefined\" &&\n    document.documentElement.classList.contains(\"dark\")\n  ) {\n    return \"dark\";\n  }\n  return \"light\";\n}\n\nexport function MarkdownFirstPage({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const colorScheme = useMarkdownThumbnailColorScheme();\n  const body = useThumbnailResource(\n    getMarkdownBody(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  return <IframeDoc html={createMarkdownDoc(body, colorScheme)} />;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/markdown-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/msg-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport type MsgReader from \"@kenjiuno/msgreader\";\nimport type { FieldsData } from \"@kenjiuno/msgreader\";\n\nimport type {\n  ViewerContentBytes,\n  ViewerContentIdentity,\n  ViewerResource,\n} from \"@/lib/viewer-resource\";\nimport { IframeDoc } from \"@/components/file-thumbnail/renderers/layout\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport {\n  TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,\n  TEXT_THUMBNAIL_MAX_BYTES,\n} from \"@/components/file-thumbnail/thumbnail-limits\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\n\ntype MsgReaderConstructor = new (\n  arrayBuffer: ArrayBuffer | DataView,\n) => MsgReader;\ntype MsgThumbnailContent = ViewerContentIdentity & ViewerContentBytes;\n\nlet msgReaderLib: Promise<{ default: MsgReaderConstructor }> | null = null;\n\nconst msgHtmlCache = createThumbnailArtifactCache<string>({\n  maxEntries: TEXT_THUMBNAIL_CACHE_MAX_ENTRIES,\n});\n\nfunction loadMsgReader() {\n  if (!msgReaderLib) {\n    msgReaderLib = import(\"@kenjiuno/msgreader\") as Promise<{\n      default: MsgReaderConstructor;\n    }>;\n  }\n  return msgReaderLib;\n}\n\nexport function MsgEmailThumbnail({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const html = useThumbnailResource(\n    getMsgThumbnailHtml(resource, resource.content, thumbnailKey),\n  );\n\n  return <IframeDoc html={html} />;\n}\n\nfunction getMsgThumbnailHtml(\n  meta: Pick<ViewerResource, \"fileName\" | \"sourceKind\">,\n  content: MsgThumbnailContent,\n  thumbnailKey: string,\n): Promise<string> {\n  return cachedThumbnailResource(msgHtmlCache, thumbnailKey, () =>\n    withThumbnailFormatError(\n      \"email\",\n      \"parse_failed\",\n      meta.fileName,\n      \"Failed to parse MSG thumbnail\",\n      () =>\n        timedThumbnail(`msg:parse ${shortName(meta)}`, async () => {\n          const bytes = await content.readBytes();\n          const { default: MsgReaderClass } = await loadMsgReader();\n          const reader = new MsgReaderClass(bytes.slice(0));\n          const fields = reader.getFileData();\n          if (fields.error) throw new Error(fields.error);\n          return msgFieldsToHtml(fields);\n        }),\n    ),\n  );\n}\n\nexport function msgFieldsToHtml(\n  fields: Pick<FieldsData, \"body\" | \"bodyHtml\" | \"html\">,\n) {\n  const bodyHtml = cleanMsgHtml(fields.bodyHtml);\n  if (bodyHtml) return bodyHtml;\n\n  if (fields.html?.byteLength) {\n    const html = cleanMsgHtml(decodeMsgHtmlBytes(fields.html));\n    if (html) return html;\n  }\n\n  const body = fields.body?.trim();\n  if (body) return plainTextEmailHtml(body);\n\n  throw new Error(\"MSG file does not contain a previewable email body.\");\n}\n\nexport function decodeMsgHtmlBytes(bytes: Uint8Array) {\n  if (bytes[0] === 0xff && bytes[1] === 0xfe) {\n    return new TextDecoder(\"utf-16le\").decode(bytes.subarray(2));\n  }\n  if (bytes[0] === 0xfe && bytes[1] === 0xff) {\n    return new TextDecoder(\"utf-16be\").decode(bytes.subarray(2));\n  }\n  if (looksLikeUtf16Le(bytes)) return new TextDecoder(\"utf-16le\").decode(bytes);\n  return new TextDecoder(\"utf-8\").decode(bytes);\n}\n\nfunction looksLikeUtf16Le(bytes: Uint8Array) {\n  const sampleLength = Math.min(bytes.byteLength, TEXT_THUMBNAIL_MAX_BYTES);\n  if (sampleLength < 4) return false;\n\n  let oddNulls = 0;\n  let evenNulls = 0;\n  for (let index = 0; index < sampleLength; index += 1) {\n    if (bytes[index] !== 0) continue;\n    if (index % 2 === 0) evenNulls += 1;\n    else oddNulls += 1;\n  }\n\n  return oddNulls > evenNulls * 4 && oddNulls > sampleLength / 8;\n}\n\nfunction cleanMsgHtml(html: string | null | undefined) {\n  return html?.replace(/\\0+$/g, \"\").trim() ?? \"\";\n}\n\nfunction plainTextEmailHtml(text: string) {\n  return `<!doctype html><html><head><meta charset=\"utf-8\" /></head><body><pre style=\"margin:0;white-space:pre-wrap;font:14px/1.5 system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#111827;\">${escapeHtml(text)}</pre></body></html>`;\n}\n\nfunction escapeHtml(text: string) {\n  return text\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#39;\");\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/msg-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/pdf-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { PdfDocumentProxy } from \"@/lib/pdf-document-types\";\nimport {\n  getPdfDocumentResource,\n  getPdfPageResource,\n} from \"@/lib/pdf-document-resource\";\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport type { ThumbnailAnchor } from \"@/components/file-thumbnail/types\";\nimport { ANCHOR_CORNER } from \"@/components/file-thumbnail/types\";\nimport { getPdfCanvasPixelSize } from \"@/components/ui/pdf-viewer-canvas\";\nimport {\n  readPdfRenderedPageCache,\n  usePdfRenderedPageCache,\n  writePdfRenderedPageCache,\n  type PdfRenderedPageSignature,\n} from \"@/components/ui/pdf-viewer-render-cache\";\n\nconst PDF_FIRST_PAGE_THUMBNAIL_WIDTH = 520;\n\n// Page 1 via pdfjs, reusing the PdfViewer's cached document.\nexport function PdfFirstPage({\n  resource,\n  anchor,\n}: {\n  resource: ViewerResource;\n  anchor: ThumbnailAnchor;\n}) {\n  const doc = useThumbnailResource(\n    getPdfDocumentResource(resource.content),\n  ) as PdfDocumentProxy;\n  const renderCache = usePdfRenderedPageCache(doc);\n  const page = useThumbnailResource(getPdfPageResource(doc, 1));\n  const [renderError, setRenderError] = React.useState<unknown>(null);\n\n  const baseViewport = React.useMemo(\n    () => page.getViewport({ scale: 1 }),\n    [page],\n  );\n  const viewport = React.useMemo(() => {\n    return page.getViewport({\n      scale: PDF_FIRST_PAGE_THUMBNAIL_WIDTH / baseViewport.width,\n    });\n  }, [baseViewport.width, page]);\n  const dpr =\n    (typeof window !== \"undefined\" ? window.devicePixelRatio : 1) || 1;\n\n  if (renderError) throw renderError;\n\n  const canvasRef = React.useCallback(\n    (canvas: HTMLCanvasElement | null) => {\n      if (!canvas) return;\n      const context = canvas.getContext(\"2d\");\n      if (!context) return;\n      const renderSignature = {\n        documentKey: resource.content.key,\n        pageNumber: 1,\n        scale: PDF_FIRST_PAGE_THUMBNAIL_WIDTH / baseViewport.width,\n        rotation: 0,\n        devicePixelRatio: dpr,\n        viewportWidth: viewport.width,\n        viewportHeight: viewport.height,\n      } satisfies PdfRenderedPageSignature;\n      const canvasWidth = getPdfCanvasPixelSize(viewport.width, dpr);\n      const canvasHeight = getPdfCanvasPixelSize(viewport.height, dpr);\n      const cachedPage = readPdfRenderedPageCache(renderCache, renderSignature);\n\n      resizeCanvas(canvas, canvasWidth, canvasHeight);\n      if (cachedPage) {\n        drawCanvasImage(context, cachedPage.canvas, canvasWidth, canvasHeight);\n        return;\n      }\n\n      let active = true;\n      let didFinishPdfRender = false;\n      let task: ReturnType<typeof page.render>;\n      try {\n        task = page.render({\n          canvas,\n          canvasContext: context,\n          viewport,\n          transform: dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : undefined,\n        });\n      } catch (error) {\n        void normalizePdfThumbnailRenderError(\n          resource.fileName,\n          Promise.reject(error),\n        ).catch((normalizedError: unknown) => {\n          if (active) setRenderError(normalizedError);\n        });\n        return () => {\n          active = false;\n        };\n      }\n      void normalizePdfThumbnailRenderError(\n        resource.fileName,\n        task.promise,\n      ).then(\n        () => {\n          if (!active) return;\n          didFinishPdfRender = true;\n          writePdfRenderedPageCache({\n            cache: renderCache,\n            rendered: renderSignature,\n            sourceCanvas: canvas,\n          });\n        },\n        (error: unknown) => {\n          didFinishPdfRender = true;\n          if (active) setRenderError(error);\n        },\n      );\n      return () => {\n        active = false;\n        if (!didFinishPdfRender) task.cancel();\n      };\n    },\n    [\n      baseViewport.width,\n      dpr,\n      page,\n      renderCache,\n      resource.content.key,\n      resource.fileName,\n      viewport,\n    ],\n  );\n\n  return (\n    <div className=\"absolute inset-0 overflow-hidden bg-white\">\n      <canvas\n        ref={canvasRef}\n        className={cn(\"absolute block w-full\", ANCHOR_CORNER[anchor])}\n      />\n    </div>\n  );\n}\n\nfunction normalizePdfThumbnailRenderError(\n  fileName: string,\n  promise: Promise<void>,\n) {\n  return withThumbnailFormatError(\n    \"pdf\",\n    \"render_failed\",\n    fileName,\n    \"Failed to render PDF thumbnail\",\n    () => promise,\n  );\n}\n\nfunction resizeCanvas(\n  canvas: HTMLCanvasElement,\n  width: number,\n  height: number,\n) {\n  if (canvas.width !== width) canvas.width = width;\n  if (canvas.height !== height) canvas.height = height;\n}\n\nfunction drawCanvasImage(\n  context: CanvasRenderingContext2D,\n  image: HTMLCanvasElement,\n  width: number,\n  height: number,\n) {\n  if (typeof context.setTransform === \"function\") {\n    context.setTransform(1, 0, 0, 1, 0, 0);\n  }\n  if (typeof context.drawImage === \"function\") {\n    context.drawImage(image, 0, 0, width, height);\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/pdf-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/pptx-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { getPptxSource } from \"@/components/ui/pptx-viewer-source\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { withThumbnailDecodeSlot } from \"@/components/file-thumbnail/thumbnail-decode-queue\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  thumbnailFileMeta,\n  type ThumbnailBytesContent,\n  type ThumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\nimport type { ThumbnailAnchor } from \"@/components/file-thumbnail/types\";\nimport { ANCHOR_CORNER } from \"@/components/file-thumbnail/types\";\n\ninterface PptxFirstSlideSource {\n  render: (canvas: HTMLCanvasElement, scale: number) => Promise<void>;\n  baseWidth: number;\n  baseHeight: number;\n  dispose?: () => void;\n}\n\nconst pptxCache = createThumbnailArtifactCache<PptxFirstSlideSource>({\n  maxEntries: 4,\n  dispose: (source) => source.dispose?.(),\n});\n\nfunction getPptxFirstSlide(\n  meta: ThumbnailFileMeta,\n  content: ThumbnailBytesContent,\n  thumbnailKey: string,\n): Promise<PptxFirstSlideSource> {\n  return cachedThumbnailResource(pptxCache, thumbnailKey, () =>\n    withThumbnailDecodeSlot(() =>\n      withThumbnailFormatError(\n        \"pptx\",\n        \"parse_failed\",\n        meta.fileName,\n        \"Failed to parse presentation thumbnail\",\n        () =>\n          timedThumbnail(`pptx:total ${shortName(meta)}`, async () => {\n            const source = await getPptxSource(content);\n            const release = source.retain();\n            const render = async (canvas: HTMLCanvasElement, scale: number) => {\n              await withThumbnailFormatError(\n                \"pptx\",\n                \"render_failed\",\n                meta.fileName,\n                \"Failed to render presentation thumbnail\",\n                async () => {\n                  const result = await source.renderSlide({\n                    canvas,\n                    renderScale: scale,\n                    slideIndex: 0,\n                    priority: {\n                      distanceFromReadingMarker: 0,\n                      isCurrentSlide: true,\n                      isInViewport: true,\n                      isScrollLead: true,\n                    },\n                  });\n                  if (result.status === \"failed\") throw result.error;\n                },\n              );\n            };\n            return {\n              render,\n              baseWidth: source.baseSize.width,\n              baseHeight: source.baseSize.height,\n              dispose: release,\n            };\n          }),\n      ),\n    ),\n  );\n}\n\nexport function PptxFirstSlide({\n  resource,\n  thumbnailKey,\n  anchor,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n}) {\n  useMountEffect(() => {\n    const preload = () => {\n      void import(\"@/components/ui/pptx-viewer\").then((module) => {\n        module.preloadPptxViewer();\n      });\n    };\n    if (typeof window.requestIdleCallback === \"function\") {\n      const id = window.requestIdleCallback(preload);\n      return () => window.cancelIdleCallback(id);\n    }\n    const id = window.setTimeout(preload, 0);\n    return () => window.clearTimeout(id);\n  });\n\n  const source = useThumbnailResource(\n    getPptxFirstSlide(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  const [renderError, setRenderError] = React.useState<unknown>(null);\n  const baseW = source.baseWidth || 960;\n  const baseH = source.baseHeight || 720;\n  const FILL_PX = 1024;\n  const scale = FILL_PX / Math.min(baseW, baseH);\n\n  if (renderError) throw renderError;\n\n  const canvasRef = React.useCallback(\n    (canvas: HTMLCanvasElement | null) => {\n      if (!canvas) return;\n      let active = true;\n      source.render(canvas, scale).catch((error: unknown) => {\n        if (active) setRenderError(error);\n      });\n      return () => {\n        active = false;\n      };\n    },\n    [source, scale],\n  );\n\n  const landscape = baseW >= baseH;\n  return (\n    <div className=\"absolute inset-0 overflow-hidden bg-white\">\n      <canvas\n        ref={canvasRef}\n        className={cn(\n          \"absolute block\",\n          ANCHOR_CORNER[anchor],\n          landscape ? \"h-full w-auto max-w-none\" : \"h-auto w-full\",\n        )}\n        style={{ aspectRatio: `${baseW} / ${baseH}` }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/pptx-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/text-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  getThumbnailText,\n  thumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\n\nconst TEXT_THUMBNAIL_MAX_PARAGRAPHS = 12;\nconst TEXT_THUMBNAIL_FONT_SIZE = 7;\nconst TEXT_THUMBNAIL_LINE_HEIGHT = 1.55;\n\nexport function TextThumbnail({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const raw = useThumbnailResource(\n    getThumbnailText(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  const paragraphs = React.useMemo(() => proseThumbnailParagraphs(raw), [raw]);\n\n  if (!paragraphs.length) return <EmptyTextThumbnail />;\n\n  return (\n    <div\n      data-slot=\"text-thumbnail\"\n      className=\"absolute inset-0 overflow-hidden bg-white p-3 font-sans text-slate-700 dark:bg-slate-950 dark:text-slate-300\"\n      style={{\n        fontSize: TEXT_THUMBNAIL_FONT_SIZE,\n        lineHeight: TEXT_THUMBNAIL_LINE_HEIGHT,\n      }}\n    >\n      <div className=\"space-y-1.5\">\n        {paragraphs.map((paragraph, i) => (\n          <p key={i} className=\"text-pretty break-words\">\n            {paragraph}\n          </p>\n        ))}\n      </div>\n    </div>\n  );\n}\n\nfunction EmptyTextThumbnail() {\n  return (\n    <div\n      aria-label=\"Empty text file\"\n      data-slot=\"text-thumbnail-empty\"\n      className=\"absolute inset-0 bg-white dark:bg-slate-950\"\n    />\n  );\n}\n\nfunction proseThumbnailParagraphs(text: string) {\n  return text\n    .replace(/\\r\\n?/g, \"\\n\")\n    .trim()\n    .split(/\\n{2,}/)\n    .map((paragraph) => paragraph.replace(/\\s*\\n\\s*/g, \" \").trim())\n    .filter(Boolean)\n    .slice(0, TEXT_THUMBNAIL_MAX_PARAGRAPHS);\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/text-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/tiff-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { FileThumbnailShimmer } from \"@/components/ui/file-thumbnail-frame\";\nimport { useObjectUrl } from \"@/components/file-thumbnail/renderers/use-object-url\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { withThumbnailDecodeSlot } from \"@/components/file-thumbnail/thumbnail-decode-queue\";\nimport {\n  createThumbnailImageLoadError,\n  withThumbnailFormatError,\n} from \"@/components/file-thumbnail/thumbnail-errors\";\nimport { TIFF_THUMBNAIL_TARGET_WIDTH } from \"@/components/file-thumbnail/thumbnail-limits\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  thumbnailFileMeta,\n  type ThumbnailBytesContent,\n  type ThumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\nimport {\n  createThumbnailWorkerClient,\n  type ThumbnailWorkerMessage,\n} from \"@/components/file-thumbnail/thumbnail-worker-client\";\nimport type { ThumbnailAnchor } from \"@/components/file-thumbnail/types\";\nimport { ANCHOR_CORNER } from \"@/components/file-thumbnail/types\";\n\ninterface TiffWorkerRequest extends ThumbnailWorkerMessage {\n  buffer: ArrayBuffer;\n  targetWidth: number;\n}\n\ninterface TiffWorkerReply extends ThumbnailWorkerMessage {\n  id: number;\n  ok: boolean;\n  blob?: Blob;\n  error?: string;\n}\n\nconst tiffWorkerClient = createThumbnailWorkerClient<\n  TiffWorkerRequest,\n  TiffWorkerReply\n>({\n  createWorker: () =>\n    new Worker(new URL(\"../../file-thumbnail-tiff.worker\", import.meta.url)),\n  resolve: (response) =>\n    response.ok && response.blob ? response.blob : undefined,\n  reject: (response) => response.error ?? \"TIFF decode failed\",\n});\n\nfunction decodeTiffInWorker(buffer: ArrayBuffer): Promise<Blob> {\n  return tiffWorkerClient.request<Blob>({\n    request: { buffer, targetWidth: TIFF_THUMBNAIL_TARGET_WIDTH },\n    transfer: [buffer],\n  });\n}\n\nconst tiffCache = createThumbnailArtifactCache<Blob>({ maxEntries: 48 });\n\nfunction getTiffFirstPageBlob(\n  meta: ThumbnailFileMeta,\n  content: ThumbnailBytesContent,\n  thumbnailKey: string,\n): Promise<Blob> {\n  return cachedThumbnailResource(tiffCache, thumbnailKey, () =>\n    withThumbnailDecodeSlot(() =>\n      withThumbnailFormatError(\n        \"image\",\n        \"decode_failed\",\n        meta.fileName,\n        \"Failed to decode TIFF thumbnail\",\n        () =>\n          timedThumbnail(`tiff:total ${shortName(meta)}`, async () => {\n            const buf = await timedThumbnail(\"tiff:fetch\", () =>\n              content.readBytes(),\n            );\n            return timedThumbnail(\"tiff:worker-decode\", () =>\n              decodeTiffInWorker(buf),\n            );\n          }),\n      ),\n    ),\n  );\n}\n\nexport function TiffFirstPage({\n  resource,\n  thumbnailKey,\n  anchor,\n  onError,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  const blob = useThumbnailResource(\n    getTiffFirstPageBlob(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  return <TiffBlobImage blob={blob} anchor={anchor} onError={onError} />;\n}\n\nfunction TiffBlobImage({\n  blob,\n  anchor,\n  onError,\n}: {\n  blob: Blob;\n  anchor: ThumbnailAnchor;\n  onError: (error: unknown) => void;\n}) {\n  const url = useObjectUrl(blob);\n\n  if (!url) return <FileThumbnailShimmer />;\n\n  return (\n    <div className=\"absolute inset-0 overflow-hidden bg-white\">\n      <img\n        src={url}\n        alt=\"\"\n        className={cn(\"absolute block w-full\", ANCHOR_CORNER[anchor])}\n        onError={() => onError(createThumbnailImageLoadError())}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/tiff-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/renderers/use-object-url.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport function useObjectUrl(blob: Blob | null): string | null {\n  const [url, setUrl] = React.useState<string | null>(null);\n\n  useKeyedMountEffect(joinEffectKey([blob]), () => {\n    if (!blob) {\n      setUrl(null);\n      return;\n    }\n\n    const nextUrl = URL.createObjectURL(blob);\n    setUrl(nextUrl);\n    return () => URL.revokeObjectURL(nextUrl);\n  });\n\n  return url;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/use-object-url.ts"
    },
    {
      "path": "components/file-thumbnail/renderers/xlsx-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { GridTable } from \"@/components/file-thumbnail/renderers/layout\";\nimport {\n  cachedThumbnailResource,\n  createThumbnailArtifactCache,\n} from \"@/components/file-thumbnail/thumbnail-cache\";\nimport { withThumbnailDecodeSlot } from \"@/components/file-thumbnail/thumbnail-decode-queue\";\nimport { withThumbnailFormatError } from \"@/components/file-thumbnail/thumbnail-errors\";\nimport {\n  XLSX_THUMBNAIL_MAX_COLUMNS,\n  XLSX_THUMBNAIL_MAX_ROWS,\n} from \"@/components/file-thumbnail/thumbnail-limits\";\nimport {\n  shortName,\n  timedThumbnail,\n} from \"@/components/file-thumbnail/thumbnail-profile\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  thumbnailFileMeta,\n  type ThumbnailBytesContent,\n  type ThumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\nimport {\n  createThumbnailWorkerClient,\n  type ThumbnailWorkerMessage,\n} from \"@/components/file-thumbnail/thumbnail-worker-client\";\n\ninterface XlsxPreview {\n  rows: string[][];\n}\n\ninterface XlsxWorkerRequest extends ThumbnailWorkerMessage {\n  buffer: ArrayBuffer;\n  maxRows: number;\n  maxCols: number;\n}\n\ninterface XlsxWorkerReply extends ThumbnailWorkerMessage {\n  id: number;\n  ok: boolean;\n  rows?: string[][];\n  error?: string;\n}\n\nconst xlsxWorkerClient = createThumbnailWorkerClient<\n  XlsxWorkerRequest,\n  XlsxWorkerReply\n>({\n  createWorker: () =>\n    new Worker(new URL(\"../../file-thumbnail-xlsx.worker\", import.meta.url)),\n  resolve: (response) =>\n    response.ok && response.rows ? response.rows : undefined,\n  reject: (response) => response.error ?? \"XLSX parse failed\",\n});\n\nfunction parseXlsxInWorker(buffer: ArrayBuffer): Promise<string[][]> {\n  return xlsxWorkerClient.request<string[][]>({\n    request: {\n      buffer,\n      maxRows: XLSX_THUMBNAIL_MAX_ROWS,\n      maxCols: XLSX_THUMBNAIL_MAX_COLUMNS,\n    },\n    transfer: [buffer],\n  });\n}\n\nconst xlsxCache = createThumbnailArtifactCache<XlsxPreview>({\n  maxEntries: 64,\n});\n\nfunction getXlsxPreview(\n  meta: ThumbnailFileMeta,\n  content: ThumbnailBytesContent,\n  thumbnailKey: string,\n): Promise<XlsxPreview> {\n  return cachedThumbnailResource(xlsxCache, thumbnailKey, () =>\n    withThumbnailDecodeSlot(() =>\n      timedThumbnail(`xlsx:total ${shortName(meta)}`, async () => {\n        const rows = await withThumbnailFormatError(\n          \"xlsx\",\n          \"parse_failed\",\n          meta.fileName,\n          \"Failed to parse spreadsheet thumbnail\",\n          async () => {\n            const buf = await content.readBytes();\n            return timedThumbnail(\"xlsx:worker-parse\", () =>\n              parseXlsxInWorker(buf),\n            );\n          },\n        );\n        return { rows };\n      }),\n    ),\n  );\n}\n\nexport function XlsxFirstSheet({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const { rows } = useThumbnailResource(\n    getXlsxPreview(thumbnailFileMeta(resource), resource.content, thumbnailKey),\n  );\n  return <GridTable rows={rows} />;\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/xlsx-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail-tiff.worker.ts",
      "content": "// Decodes the FIRST page of a TIFF and downscales it to a thumbnail-sized PNG,\n// entirely off the main thread. UTIF's decode, the RGBA conversion, the resize,\n// and the PNG encode are all synchronous CPU — running them here keeps a grid of\n// TIFF thumbnails from janking the UI. We only ever touch ifds[0], so a 200-page\n// scan costs the same as a one-page one.\nimport UTIF from \"utif\";\n\ninterface Ifd {\n  t256?: number[];\n  t257?: number[];\n  width?: number;\n  height?: number;\n}\ninterface Utif {\n  decode(buf: ArrayBuffer): Ifd[];\n  decodeImage(buf: ArrayBuffer, ifd: Ifd): void;\n  toRGBA8(ifd: Ifd): Uint8Array;\n}\nconst utif = UTIF as Utif;\n\ninterface Req {\n  id: number;\n  buffer: ArrayBuffer;\n  targetWidth: number;\n}\n\nconst ctx = self as unknown as Worker;\n\nctx.onmessage = async (event: MessageEvent<Req>) => {\n  const { id, buffer, targetWidth } = event.data;\n  try {\n    const ifds = utif.decode(buffer);\n    if (!ifds.length) throw new Error(\"TIFF has no frames\");\n    const ifd = ifds[0];\n    utif.decodeImage(buffer, ifd);\n    const rgba = utif.toRGBA8(ifd);\n    const width = ifd.t256?.[0] ?? ifd.width ?? 0;\n    const height = ifd.t257?.[0] ?? ifd.height ?? 0;\n    if (!width || !height) throw new Error(\"TIFF has no dimensions\");\n\n    const dw = Math.min(width, targetWidth);\n    const dh = Math.max(1, Math.round((dw / width) * height));\n    const pixels = new Uint8ClampedArray(rgba.length);\n    pixels.set(rgba);\n    const source = new ImageData(pixels, width, height);\n    const bitmap = await createImageBitmap(source, {\n      resizeWidth: dw,\n      resizeHeight: dh,\n      resizeQuality: \"high\",\n    });\n    const canvas = new OffscreenCanvas(dw, dh);\n    const c = canvas.getContext(\"2d\");\n    if (!c) throw new Error(\"No 2d context\");\n    c.drawImage(bitmap, 0, 0);\n    bitmap.close();\n    const blob = await canvas.convertToBlob({ type: \"image/png\" });\n    ctx.postMessage({ id, ok: true, blob });\n  } catch (err) {\n    ctx.postMessage({\n      id,\n      ok: false,\n      error: err instanceof Error ? err.message : String(err),\n    });\n  }\n};\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail-tiff.worker.ts"
    },
    {
      "path": "components/file-thumbnail-xlsx.worker.ts",
      "content": "// Parses the first rows of a spreadsheet's first sheet off the main thread.\n// XLSX.read is fully synchronous CPU and was the largest UI-thread block of the\n// thumbnail grid; running it here keeps the page responsive. `sheetRows` stops\n// the parser early so a 100k-row workbook costs the same as a 16-row one.\nimport * as XLSX from \"@e965/xlsx\";\n\nimport { isSpreadsheetContainer } from \"@/lib/xlsx-worker-protocol\";\n\ninterface Req {\n  id: number;\n  buffer: ArrayBuffer;\n  maxRows: number;\n  maxCols: number;\n}\n\nconst ctx = self as unknown as Worker;\n\nctx.onmessage = (event: MessageEvent<Req>) => {\n  const { id, buffer, maxRows, maxCols } = event.data;\n  try {\n    // SheetJS sniffs arbitrary bytes into a stub sheet; reject non-spreadsheet\n    // input so a mislabeled file yields no thumbnail rather than junk cells.\n    if (!isSpreadsheetContainer(buffer)) {\n      throw new Error(\"File is not a recognized spreadsheet (.xlsx or .xls).\");\n    }\n    const wb = XLSX.read(new Uint8Array(buffer), {\n      type: \"array\",\n      sheetRows: maxRows,\n    });\n    const ws = wb.Sheets[wb.SheetNames[0]];\n    const grid = XLSX.utils.sheet_to_json<string[]>(ws, {\n      header: 1,\n      blankrows: false,\n      defval: \"\",\n      raw: false,\n    });\n    const rows = grid\n      .slice(0, maxRows)\n      .map((r) =>\n        Array.isArray(r) ? r.slice(0, maxCols).map((c) => `${c ?? \"\"}`) : [],\n      );\n    ctx.postMessage({ id, ok: true, rows });\n  } catch (err) {\n    ctx.postMessage({\n      id,\n      ok: false,\n      error: err instanceof Error ? err.message : String(err),\n    });\n  }\n};\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail-xlsx.worker.ts"
    },
    {
      "path": "components/file-thumbnail/renderers/code-thumbnail.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { formatCodeThumbnailText } from \"@/components/file-thumbnail/thumbnail-code\";\nimport { useThumbnailResource } from \"@/components/file-thumbnail/thumbnail-resource\";\nimport {\n  getThumbnailText,\n  thumbnailFileMeta,\n} from \"@/components/file-thumbnail/thumbnail-text\";\n\nconst CODE_THUMBNAIL_MAX_LINES = 60;\nconst CODE_THUMBNAIL_FONT_SIZE = 5;\nconst CODE_THUMBNAIL_LINE_HEIGHT = 1.5;\nconst CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS = \"w-2.5\";\n\nexport function CodeThumbnail({\n  resource,\n  thumbnailKey,\n}: {\n  resource: ViewerResource;\n  thumbnailKey: string;\n}) {\n  const raw = useThumbnailResource(\n    getThumbnailText(\n      thumbnailFileMeta(resource),\n      resource.content,\n      thumbnailKey,\n    ),\n  );\n  const text = React.useMemo(\n    () => formatCodeThumbnailText(raw, resource.fileName, resource.mimeType),\n    [raw, resource.fileName, resource.mimeType],\n  );\n  const lines = React.useMemo(\n    () =>\n      text\n        .replace(/\\r?\\n$/, \"\")\n        .split(/\\r\\n?|\\n/)\n        .slice(0, CODE_THUMBNAIL_MAX_LINES),\n    [text],\n  );\n\n  return (\n    <div\n      data-slot=\"code-thumbnail\"\n      className=\"bg-code text-code-foreground absolute inset-0 overflow-hidden\"\n    >\n      <div\n        aria-hidden\n        className={`absolute inset-y-0 left-0 ${CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS} bg-code-highlight`}\n      />\n      <div\n        className=\"relative font-mono\"\n        style={{\n          fontSize: CODE_THUMBNAIL_FONT_SIZE,\n          lineHeight: CODE_THUMBNAIL_LINE_HEIGHT,\n        }}\n      >\n        {lines.map((line, i) => (\n          <div key={i} className=\"flex\">\n            <span\n              className={`${CODE_THUMBNAIL_LINE_NUMBER_WIDTH_CLASS} text-code-number shrink-0 pr-px text-right select-none`}\n            >\n              {i + 1}\n            </span>\n            <span className=\"pl-0.5 whitespace-pre\">{line || \" \"}</span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/renderers/code-thumbnail.tsx"
    },
    {
      "path": "components/file-thumbnail/thumbnail-code.ts",
      "content": "import type { ViewerDescriptor } from \"@/lib/viewer-source\";\nimport { extensionOf } from \"@/lib/viewer-source\";\n\nconst CODE_THUMBNAIL_EXTENSIONS = new Set([\n  \"bash\",\n  \"c\",\n  \"cc\",\n  \"cjs\",\n  \"cpp\",\n  \"cs\",\n  \"css\",\n  \"env\",\n  \"go\",\n  \"graphql\",\n  \"h\",\n  \"ini\",\n  \"java\",\n  \"js\",\n  \"json\",\n  \"json5\",\n  \"jsonl\",\n  \"jsx\",\n  \"kt\",\n  \"less\",\n  \"log\",\n  \"lua\",\n  \"mjs\",\n  \"ndjson\",\n  \"php\",\n  \"pl\",\n  \"proto\",\n  \"py\",\n  \"r\",\n  \"rb\",\n  \"rs\",\n  \"scala\",\n  \"scss\",\n  \"sh\",\n  \"sql\",\n  \"svelte\",\n  \"swift\",\n  \"toml\",\n  \"ts\",\n  \"tsx\",\n  \"vue\",\n  \"xml\",\n  \"yaml\",\n  \"yml\",\n  \"zsh\",\n]);\n\nexport function isCodeThumbnailDescriptor(\n  descriptor: ViewerDescriptor,\n): boolean {\n  const extension = extensionOf(descriptor.displayName);\n  if (extension && CODE_THUMBNAIL_EXTENSIONS.has(extension)) return true;\n\n  const mime = descriptor.mimeType?.toLowerCase().split(\";\")[0].trim();\n  return (\n    mime === \"application/json\" ||\n    mime === \"application/xml\" ||\n    mime === \"text/javascript\" ||\n    mime === \"application/javascript\" ||\n    Boolean(mime?.endsWith(\"+json\") || mime?.endsWith(\"+xml\"))\n  );\n}\n\nexport function formatCodeThumbnailText(\n  text: string,\n  fileName: string,\n  mimeType?: string,\n) {\n  if (!isStrictJson(fileName, mimeType)) return text;\n\n  try {\n    return JSON.stringify(JSON.parse(text), null, 2);\n  } catch {\n    return text;\n  }\n}\n\nfunction isStrictJson(fileName: string, mimeType?: string) {\n  const extension = extensionOf(fileName);\n  if (extension === \"json\") return true;\n  if (\n    extension === \"json5\" ||\n    extension === \"jsonl\" ||\n    extension === \"ndjson\"\n  ) {\n    return false;\n  }\n\n  const mime = mimeType?.toLowerCase().split(\";\")[0].trim();\n  return mime === \"application/json\" || Boolean(mime?.endsWith(\"+json\"));\n}\n",
      "type": "registry:component",
      "target": "@components/file-thumbnail/thumbnail-code.ts"
    },
    {
      "path": "registry/new-york-v4/ui/utif.d.ts",
      "content": "// Ambient shim for the `utif` package, which ships no type declarations\n// (and has no @types/utif on npm). shadcn's registry build strips leading\n// comments from source files, so an inline `@ts-expect-error` above the import\n// does not survive into the published item — a real declaration is required for\n// a fresh consumer install to typecheck. The decode workers cast the default\n// export to their own local shapes, so an opaque `any` default is sufficient.\ndeclare module \"utif\" {\n   \n  const UTIF: any;\n  export default UTIF;\n}\n",
      "type": "registry:ui",
      "target": "@ui/utif.d.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-core.ts",
      "content": "import type * as DocxPreview from \"docx-preview\";\n\nimport {\n  isViewerFormatError,\n  ViewerFormatError,\n  type ViewerFormatErrorMapperOptions,\n} from \"@/lib/viewer-errors\";\n\nexport const DOCX_MIN_SCALE = 0.1;\nexport const DOCX_MAX_SCALE = 5;\nexport const DOCX_ZOOM_STEP = 1.2;\n\nexport const DEFAULT_DOCX_PAGE_WIDTH = 816;\nexport const DEFAULT_DOCX_PAGE_HEIGHT = 1056;\n\nexport const DOCX_RENDER_OPTIONS: Partial<DocxPreview.Options> = {\n  inWrapper: true,\n  breakPages: true,\n  ignoreLastRenderedPageBreak: false,\n  experimental: true,\n  renderHeaders: true,\n  renderFooters: true,\n  renderFootnotes: true,\n};\n\nexport const DOCX_SCOPED_STYLES = `\n[data-slot=\"docx-viewer\"] .docx-wrapper {\n  background: transparent;\n  padding: 0;\n  gap: 1rem;\n}\n[data-slot=\"docx-viewer\"] .docx-wrapper > section.docx {\n  margin-bottom: 0;\n  box-shadow: 0 0 0 1px var(--border), 0 1px 2px 0 rgb(0 0 0 / 0.05);\n}`;\n\nexport function clampDocxScale(value: number) {\n  return clamp(value, DOCX_MIN_SCALE, DOCX_MAX_SCALE);\n}\n\nexport function normalizeDocxScale(value: number | null | undefined) {\n  if (value == null) return null;\n  if (Number.isNaN(value)) return 1;\n  return clampDocxScale(value);\n}\n\nexport function clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n\nexport function positivePixel(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : null;\n}\n\nexport function toDocxFormatError(\n  error: unknown,\n  options: ViewerFormatErrorMapperOptions,\n): ViewerFormatError {\n  if (isViewerFormatError(error)) return error;\n  return new ViewerFormatError({\n    format: \"docx\",\n    kind: options.kind,\n    message: options.message,\n    cause: error,\n  });\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-core.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-layout.ts",
      "content": "import { clamp } from \"./docx-viewer-core\";\n\nexport const DOCX_PAGE_GAP_PX = 16;\nexport const DOCX_VIEWER_PADDING_PX = 16;\nexport const DOCX_READING_MARKER_RATIO = 0.2;\nexport const DOCX_VIRTUAL_OVERSCAN_PX = 1000;\nexport const DOCX_FALLBACK_VIEWPORT_HEIGHT_PX = 768;\n\nexport interface DocxPageMetric {\n  pageNumber: number;\n  width: number;\n  height: number;\n  top: number;\n  bottom: number;\n}\n\nexport interface DocxPageLayout {\n  pages: readonly DocxPageMetric[];\n  totalHeight: number;\n}\n\nexport interface DocxPageWindow {\n  afterHeight: number;\n  beforeHeight: number;\n  endIndex: number;\n  renderedBottom: number;\n  renderedHeight: number;\n  renderedTop: number;\n  startIndex: number;\n  stickyOffset: number;\n}\n\nexport type DocxReadingAnchor =\n  | {\n      kind: \"top\";\n    }\n  | {\n      kind: \"page\";\n      pageNumber: number;\n      yPercent: number;\n    };\n\nexport function createDocxPageLayout(\n  sizes: readonly (readonly [number, number])[],\n  gap = DOCX_PAGE_GAP_PX,\n): DocxPageLayout {\n  let top = 0;\n  const pages = sizes.map(([width, height], index) => {\n    const page: DocxPageMetric = {\n      pageNumber: index + 1,\n      width,\n      height,\n      top,\n      bottom: top + height,\n    };\n    top = page.bottom + gap;\n    return page;\n  });\n\n  return {\n    pages,\n    totalHeight: pages.length ? pages[pages.length - 1]!.bottom : 0,\n  };\n}\n\nexport function findDocxPageByMarker({\n  layout,\n  scale,\n  scrollTop,\n  viewportHeight,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n  scrollTop: number;\n  viewportHeight: number;\n}) {\n  const pages = layout?.pages;\n  if (!pages?.length) return null;\n\n  const marker = scrollTop + viewportHeight * DOCX_READING_MARKER_RATIO;\n  const y = Math.max(0, (marker - DOCX_VIEWER_PADDING_PX) / safeScale(scale));\n  let low = 0;\n  let high = pages.length - 1;\n  let current = pages[0]!;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    const page = pages[mid]!;\n    if (page.top <= y) {\n      current = page;\n      low = mid + 1;\n    } else {\n      high = mid - 1;\n    }\n  }\n\n  return current;\n}\n\nexport function captureDocxReadingAnchorFromLayout({\n  layout,\n  scale,\n  scrollTop,\n  viewportHeight,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): DocxReadingAnchor {\n  if (scrollTop <= 0) return { kind: \"top\" };\n\n  const page = findDocxPageByMarker({\n    layout,\n    scale,\n    scrollTop,\n    viewportHeight,\n  });\n  if (!page || page.height <= 0) return { kind: \"top\" };\n\n  const marker = scrollTop + viewportHeight * DOCX_READING_MARKER_RATIO;\n  const y = Math.max(0, (marker - DOCX_VIEWER_PADDING_PX) / safeScale(scale));\n\n  return {\n    kind: \"page\",\n    pageNumber: page.pageNumber,\n    yPercent: clamp((y - page.top) / page.height, 0, 1),\n  };\n}\n\nexport function restoreDocxReadingAnchorFromLayout({\n  anchor,\n  layout,\n  maxScrollTop,\n  scale,\n  viewportHeight,\n}: {\n  anchor: DocxReadingAnchor;\n  layout: DocxPageLayout | null;\n  maxScrollTop: number;\n  scale: number;\n  viewportHeight: number;\n}) {\n  if (anchor.kind === \"top\") return 0;\n\n  const page = layout?.pages[anchor.pageNumber - 1];\n  if (!page || page.height <= 0) return null;\n\n  const y = (page.top + page.height * anchor.yPercent) * safeScale(scale);\n  const marker = viewportHeight * DOCX_READING_MARKER_RATIO;\n  return clamp(DOCX_VIEWER_PADDING_PX + y - marker, 0, maxScrollTop);\n}\n\nexport function createDocxPageWindowFromScroll({\n  layout,\n  overscanPx = DOCX_VIRTUAL_OVERSCAN_PX,\n  scale,\n  scrollTop,\n  viewportHeight,\n}: {\n  layout: DocxPageLayout | null;\n  overscanPx?: number;\n  scale: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): DocxPageWindow {\n  const pages = layout?.pages;\n  if (!layout || !pages?.length) {\n    return emptyDocxPageWindow();\n  }\n\n  const z = safeScale(scale);\n  const viewHeight = safeViewportHeight(viewportHeight) / z;\n  const overscan = Math.max(0, overscanPx) / z;\n  const viewTop = Math.max(0, (scrollTop - DOCX_VIEWER_PADDING_PX) / z);\n  const windowTop = Math.max(0, viewTop - overscan);\n  const windowBottom = Math.min(\n    layout.totalHeight,\n    viewTop + viewHeight + overscan,\n  );\n\n  let startIndex = pages.findIndex((page) => page.bottom >= windowTop);\n  if (startIndex === -1) startIndex = pages.length - 1;\n\n  let endIndex = startIndex;\n  while (\n    endIndex < pages.length &&\n    pages[endIndex]!.top <= Math.max(windowBottom, pages[startIndex]!.top)\n  ) {\n    endIndex += 1;\n  }\n  if (endIndex === startIndex)\n    endIndex = Math.min(pages.length, startIndex + 1);\n\n  return createDocxPageWindowFromRange({\n    endIndex,\n    layout,\n    startIndex,\n    viewportHeight: viewHeight,\n  });\n}\n\nexport function createDocxPageWindowForPage({\n  layout,\n  pageIndex,\n  scale,\n  viewportHeight,\n}: {\n  layout: DocxPageLayout | null;\n  pageIndex: number;\n  scale: number;\n  viewportHeight: number;\n}): DocxPageWindow {\n  const pages = layout?.pages;\n  if (!layout || !pages?.length) return emptyDocxPageWindow();\n  const safePageIndex = clamp(Math.floor(pageIndex), 0, pages.length - 1);\n  return createDocxPageWindowFromRange({\n    endIndex: safePageIndex + 1,\n    layout,\n    startIndex: safePageIndex,\n    viewportHeight: safeViewportHeight(viewportHeight) / safeScale(scale),\n  });\n}\n\nfunction createDocxPageWindowFromRange({\n  endIndex,\n  layout,\n  startIndex,\n  viewportHeight,\n}: {\n  endIndex: number;\n  layout: DocxPageLayout;\n  startIndex: number;\n  viewportHeight: number;\n}): DocxPageWindow {\n  const pages = layout.pages;\n  const start = clamp(Math.floor(startIndex), 0, pages.length);\n  const end = clamp(Math.ceil(endIndex), start, pages.length);\n  const first = pages[start];\n  const last = pages[end - 1];\n  if (!first || !last) return emptyDocxPageWindow();\n\n  const renderedTop = first.top;\n  const renderedBottom = last.bottom;\n  const renderedHeight = renderedBottom - renderedTop;\n\n  return {\n    afterHeight: Math.max(0, layout.totalHeight - renderedBottom),\n    beforeHeight: renderedTop,\n    endIndex: end,\n    renderedBottom,\n    renderedHeight,\n    renderedTop,\n    startIndex: start,\n    stickyOffset: -Math.max(0, renderedHeight - Math.max(1, viewportHeight)),\n  };\n}\n\nfunction emptyDocxPageWindow(): DocxPageWindow {\n  return {\n    afterHeight: 0,\n    beforeHeight: 0,\n    endIndex: 0,\n    renderedBottom: 0,\n    renderedHeight: 0,\n    renderedTop: 0,\n    startIndex: 0,\n    stickyOffset: 0,\n  };\n}\n\nfunction safeViewportHeight(value: number) {\n  return Number.isFinite(value) && value > 0\n    ? value\n    : DOCX_FALLBACK_VIEWPORT_HEIGHT_PX;\n}\n\nfunction safeScale(scale: number) {\n  return Number.isFinite(scale) && scale > 0 ? scale : 1;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-layout.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-render-cache.ts",
      "content": "import { lruGet, lruSet } from \"./viewer-lru-cache\";\n\nexport const DOCX_RENDER_CACHE_MAX_ENTRIES = 4;\n\nexport type DocxPageSize = readonly [number, number];\n\nexport interface DocxRenderCacheEntry {\n  pageSizes: readonly DocxPageSize[];\n  renderHost: HTMLElement;\n}\n\nexport interface DocxRenderCacheHit {\n  pageSizes: readonly DocxPageSize[];\n  renderHost: HTMLElement;\n}\n\nconst UNSAFE_DOCX_RENDER_CACHE_SELECTOR =\n  \"audio, canvas, embed, iframe, object, video\";\n\nconst docxRenderCache = new Map<string, DocxRenderCacheEntry>();\nconst pendingDocxRenderCache = new Map<\n  string,\n  Promise<DocxRenderCacheEntry | null>\n>();\n\nexport function readDocxRenderCache(key: string): DocxRenderCacheHit | null {\n  const entry = lruGet(docxRenderCache, key);\n  return entry ? cloneDocxRenderCacheEntry(entry) : null;\n}\n\nexport function readPendingDocxRenderCache(\n  key: string,\n): Promise<DocxRenderCacheEntry | null> | null {\n  return pendingDocxRenderCache.get(key) ?? null;\n}\n\nexport function writePendingDocxRenderCache(\n  key: string,\n  promise: Promise<DocxRenderCacheEntry | null>,\n) {\n  pendingDocxRenderCache.set(key, promise);\n  void promise\n    .finally(() => {\n      if (pendingDocxRenderCache.get(key) === promise) {\n        pendingDocxRenderCache.delete(key);\n      }\n    })\n    .catch(() => undefined);\n}\n\nexport function writeDocxRenderCache({\n  key,\n  pageSizes,\n  renderHost,\n}: {\n  key: string;\n  pageSizes: readonly DocxPageSize[];\n  renderHost: HTMLElement;\n}): DocxRenderCacheEntry | null {\n  if (\n    pageSizes.length === 0 ||\n    !renderHost.querySelector(\".docx-wrapper > section.docx\")\n  ) {\n    return null;\n  }\n  if (!canCacheDocxRenderHost(renderHost)) return null;\n\n  const cachedRenderHost = renderHost.cloneNode(true);\n  if (!(cachedRenderHost instanceof HTMLElement)) return null;\n\n  const entry: DocxRenderCacheEntry = {\n    pageSizes,\n    renderHost: cachedRenderHost,\n  };\n  lruSet(docxRenderCache, key, entry, undefined, DOCX_RENDER_CACHE_MAX_ENTRIES);\n  return entry;\n}\n\nexport function cloneDocxRenderCacheEntry(\n  entry: DocxRenderCacheEntry,\n): DocxRenderCacheHit {\n  return {\n    pageSizes: entry.pageSizes,\n    renderHost: entry.renderHost.cloneNode(true) as HTMLElement,\n  };\n}\n\nexport function resetDocxRenderCacheForTests() {\n  docxRenderCache.clear();\n  pendingDocxRenderCache.clear();\n}\n\nfunction canCacheDocxRenderHost(renderHost: HTMLElement) {\n  return !renderHost.querySelector(UNSAFE_DOCX_RENDER_CACHE_SELECTOR);\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-render-cache.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-render.ts",
      "content": "import type * as DocxPreview from \"docx-preview\";\n\nimport { ViewerFormatError } from \"@/lib/viewer-errors\";\n\nimport {\n  DEFAULT_DOCX_PAGE_HEIGHT,\n  DEFAULT_DOCX_PAGE_WIDTH,\n  DOCX_RENDER_OPTIONS,\n  positivePixel,\n} from \"./docx-viewer-core\";\nimport {\n  createDocxPageLayout,\n  type DocxPageLayout,\n  type DocxPageWindow,\n} from \"./docx-viewer-layout\";\nimport {\n  readDocxRenderCache,\n  readPendingDocxRenderCache,\n  writeDocxRenderCache,\n  writePendingDocxRenderCache,\n  type DocxPageSize,\n  type DocxRenderCacheHit,\n} from \"./docx-viewer-render-cache\";\n\nlet docxPromise: Promise<typeof DocxPreview> | null = null;\n\nexport interface DocxRenderedDocument {\n  after: HTMLDivElement;\n  before: HTMLDivElement;\n  mountedEnd: number;\n  mountedStart: number;\n  pageLayout: DocxPageLayout;\n  pages: readonly HTMLElement[];\n  sticky: HTMLElement;\n  wrapper: HTMLElement;\n}\n\ntype DocxRenderBuffer = ArrayBuffer | (() => ArrayBuffer);\n\nexport function loadDocxPreview() {\n  if (!docxPromise) {\n    docxPromise = import(\"docx-preview\").catch((error) => {\n      docxPromise = null;\n      throw error;\n    });\n  }\n  return docxPromise;\n}\n\nexport async function renderDocxPreview(\n  buffer: ArrayBuffer,\n  docxPreviewPromise = loadDocxPreview(),\n) {\n  const { renderAsync } = await docxPreviewPromise;\n  const renderHost = document.createElement(\"div\");\n  await renderAsync(buffer, renderHost, undefined, DOCX_RENDER_OPTIONS);\n  return renderHost;\n}\n\nexport async function renderCachedDocxPreview({\n  buffer,\n  cacheKey,\n  docxPreviewPromise,\n  getScale,\n}: {\n  buffer: DocxRenderBuffer;\n  cacheKey: string;\n  docxPreviewPromise?: Promise<typeof DocxPreview>;\n  getScale: () => number;\n}): Promise<DocxRenderCacheHit> {\n  const cached = readDocxRenderCache(cacheKey);\n  if (cached) return cached;\n\n  const pending = readPendingDocxRenderCache(cacheKey);\n  if (pending) {\n    const pendingEntry = await pending;\n    if (pendingEntry) {\n      return {\n        pageSizes: pendingEntry.pageSizes,\n        renderHost: pendingEntry.renderHost.cloneNode(true) as HTMLElement,\n      };\n    }\n  }\n\n  const renderPromise = renderDocxPreview(\n    resolveDocxRenderBuffer(buffer),\n    docxPreviewPromise ?? loadDocxPreview(),\n  ).then((renderHost) => {\n    const pageSizes = collectDocxPageSizes(renderHost, getScale());\n    const cacheEntry = writeDocxRenderCache({\n      key: cacheKey,\n      pageSizes,\n      renderHost,\n    });\n\n    return { cacheEntry, pageSizes, renderHost };\n  });\n  writePendingDocxRenderCache(\n    cacheKey,\n    renderPromise.then((result) => result.cacheEntry),\n  );\n\n  const result = await renderPromise;\n  return {\n    pageSizes: result.pageSizes,\n    renderHost: result.renderHost,\n  };\n}\n\nfunction resolveDocxRenderBuffer(buffer: DocxRenderBuffer) {\n  return typeof buffer === \"function\" ? buffer() : buffer;\n}\n\nexport function commitDocxRender({\n  host,\n  pageSizes,\n  renderHost,\n  scale,\n}: {\n  host: HTMLElement;\n  pageSizes?: readonly DocxPageSize[];\n  renderHost: HTMLElement;\n  scale: number;\n}) {\n  const wrapper = renderHost.querySelector<HTMLElement>(\".docx-wrapper\");\n  if (!wrapper) {\n    throw new ViewerFormatError({\n      format: \"docx\",\n      kind: \"render_failed\",\n      message: \"DOCX render produced no pages.\",\n    });\n  }\n  const pages = Array.from(\n    renderHost.querySelectorAll<HTMLElement>(\".docx-wrapper > section.docx\"),\n  );\n  if (!pages.length) {\n    throw new ViewerFormatError({\n      format: \"docx\",\n      kind: \"render_failed\",\n      message: \"DOCX render produced no pages.\",\n    });\n  }\n  const z = scale || 1;\n  const sizes =\n    pageSizes && pageSizes.length === pages.length\n      ? pageSizes\n      : pages.map((el) => pageSize(el, z));\n  pages.forEach((el, i) => {\n    el.dataset.pageNumber = String(i + 1);\n    el.style.contentVisibility = \"auto\";\n    const [width, height] = sizes[i]!;\n    el.style.containIntrinsicSize = `${width}px ${height}px`;\n  });\n  const pageLayout = createDocxPageLayout(sizes);\n  const before = document.createElement(\"div\");\n  const after = document.createElement(\"div\");\n  before.dataset.slot = \"docx-sticky-before-buffer\";\n  wrapper.dataset.slot = \"docx-sticky-window\";\n  after.dataset.slot = \"docx-sticky-after-buffer\";\n  before.setAttribute(\"aria-hidden\", \"true\");\n  after.setAttribute(\"aria-hidden\", \"true\");\n  wrapper.style.position = \"sticky\";\n  wrapper.style.left = \"0\";\n  wrapper.style.overflow = \"visible\";\n  wrapper.style.width = \"100%\";\n\n  const staticNodes = Array.from(renderHost.childNodes).filter(\n    (node) => node !== wrapper,\n  );\n  wrapper.replaceChildren();\n  host.replaceChildren(...staticNodes, before, wrapper, after);\n\n  const virtualDocument: DocxRenderedDocument = {\n    after,\n    before,\n    mountedEnd: 0,\n    mountedStart: 0,\n    pageLayout,\n    pages,\n    sticky: wrapper,\n    wrapper,\n  };\n  return {\n    numPages: pages.length,\n    pageWidth: pages.length ? sizes[0][0] : null,\n    pageLayout,\n    virtualDocument,\n  };\n}\n\nexport function projectDocxPages(\n  document: DocxRenderedDocument,\n  window: DocxPageWindow,\n) {\n  document.before.style.height = `${window.beforeHeight}px`;\n  document.after.style.height = `${window.afterHeight}px`;\n  document.sticky.style.top = `${window.stickyOffset}px`;\n  document.sticky.style.bottom = `${window.stickyOffset}px`;\n  document.sticky.style.height = `${window.renderedHeight}px`;\n\n  const start = window.startIndex;\n  const end = window.endIndex;\n  if (start === document.mountedStart && end === document.mountedEnd) return;\n\n  if (\n    document.mountedEnd <= document.mountedStart ||\n    end <= start ||\n    end <= document.mountedStart ||\n    start >= document.mountedEnd\n  ) {\n    document.wrapper.replaceChildren(...document.pages.slice(start, end));\n    document.mountedStart = start;\n    document.mountedEnd = end;\n    return;\n  }\n\n  for (\n    let index = document.mountedStart;\n    index < Math.min(start, document.mountedEnd);\n    index += 1\n  ) {\n    document.pages[index]?.remove();\n  }\n  for (\n    let index = Math.max(end, document.mountedStart);\n    index < document.mountedEnd;\n    index += 1\n  ) {\n    document.pages[index]?.remove();\n  }\n\n  if (start < document.mountedStart) {\n    const fragment = globalThis.document.createDocumentFragment();\n    for (let index = start; index < document.mountedStart; index += 1) {\n      const page = document.pages[index];\n      if (page) fragment.append(page);\n    }\n    document.wrapper.insertBefore(fragment, document.wrapper.firstChild);\n  }\n\n  if (end > document.mountedEnd) {\n    const fragment = globalThis.document.createDocumentFragment();\n    for (\n      let index = Math.max(document.mountedEnd, start);\n      index < end;\n      index += 1\n    ) {\n      const page = document.pages[index];\n      if (page) fragment.append(page);\n    }\n    document.wrapper.append(fragment);\n  }\n\n  document.mountedStart = start;\n  document.mountedEnd = end;\n}\n\nfunction collectDocxPageSizes(\n  renderHost: HTMLElement,\n  scale: number,\n): readonly DocxPageSize[] {\n  const pages = Array.from(\n    renderHost.querySelectorAll<HTMLElement>(\".docx-wrapper > section.docx\"),\n  );\n  const z = scale || 1;\n  return pages.map((el) => pageSize(el, z));\n}\n\nfunction pageSize(el: HTMLElement, scale: number) {\n  const styledWidth = positivePixel(Math.round(cssLengthToPx(el.style.width)));\n  const styledHeight = positivePixel(\n    Math.round(\n      cssLengthToPx(el.style.height) || cssLengthToPx(el.style.minHeight),\n    ),\n  );\n  if (styledWidth && styledHeight) {\n    return [styledWidth, styledHeight] as const;\n  }\n\n  const r = el.getBoundingClientRect();\n  const width = positivePixel(Math.round(r.width / scale));\n  const height = positivePixel(Math.round(r.height / scale));\n  return [\n    styledWidth ?? width ?? DEFAULT_DOCX_PAGE_WIDTH,\n    styledHeight ?? height ?? DEFAULT_DOCX_PAGE_HEIGHT,\n  ] as const;\n}\n\nfunction cssLengthToPx(value: string) {\n  const match = value.trim().match(/^(-?\\d*\\.?\\d+)(px|pt|in|cm|mm|pc)?$/i);\n  if (!match) return 0;\n\n  const amount = Number(match[1]);\n  if (!Number.isFinite(amount) || amount <= 0) return 0;\n\n  const unit = (match[2] ?? \"px\").toLowerCase();\n  if (unit === \"px\") return amount;\n  if (unit === \"pt\") return (amount * 96) / 72;\n  if (unit === \"in\") return amount * 96;\n  if (unit === \"cm\") return (amount * 96) / 2.54;\n  if (unit === \"mm\") return (amount * 96) / 25.4;\n  if (unit === \"pc\") return amount * 16;\n  return 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-render.ts"
    },
    {
      "path": "registry/new-york-v4/ui/pdf-viewer-canvas.ts",
      "content": "export function getPdfCanvasPixelSize(\n  cssSize: number,\n  devicePixelRatio: number,\n) {\n  if (\n    !Number.isFinite(cssSize) ||\n    !Number.isFinite(devicePixelRatio) ||\n    cssSize <= 0 ||\n    devicePixelRatio <= 0\n  ) {\n    return 1;\n  }\n  return Math.max(1, Math.floor(cssSize * devicePixelRatio));\n}\n",
      "type": "registry:ui",
      "target": "@ui/pdf-viewer-canvas.ts"
    },
    {
      "path": "registry/new-york-v4/ui/pdf-viewer-render-cache.ts",
      "content": "import { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport const PDF_RENDERED_PAGE_CACHE_MAX_ENTRIES = 16;\nexport const PDF_RENDERED_PAGE_CACHE_MAX_PIXELS = 24_000_000;\n// Only close lower-resolution renders of the same page may be stretched as\n// resize previews; thumbnail-resolution bitmaps below this ratio would blur.\nexport const PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO = 0.5;\n\nexport type PdfRenderedPageSignature = {\n  documentKey: string;\n  pageNumber: number;\n  scale: number;\n  rotation: number;\n  devicePixelRatio: number;\n  viewportWidth: number;\n  viewportHeight: number;\n};\n\nexport type PdfRenderedPageCache = {\n  clock: number;\n  entries: Map<string, PdfRenderedPageCacheEntry>;\n  resetKey: unknown;\n};\n\nexport type PdfRenderedPageCacheEntry = PdfRenderedPageSignature & {\n  canvas: HTMLCanvasElement;\n  lastUsed: number;\n  pixels: number;\n};\n\ntype PdfRenderedPageCacheRecord = {\n  cache: PdfRenderedPageCache;\n  retainCount: number;\n  resetKey: unknown;\n};\n\nconst objectCacheRecords = new WeakMap<object, PdfRenderedPageCacheRecord>();\nconst primitiveCacheRecords = new Map<unknown, PdfRenderedPageCacheRecord>();\n\nexport function createPdfRenderedPageCache(\n  resetKey: unknown,\n): PdfRenderedPageCache {\n  return {\n    clock: 0,\n    entries: new Map(),\n    resetKey,\n  };\n}\n\nexport function usePdfRenderedPageCache(resetKey: unknown) {\n  const record = getPdfRenderedPageCacheRecord(resetKey);\n\n  useKeyedMountEffect(joinEffectKey([record]), () => {\n    record.retainCount += 1;\n    return () => releasePdfRenderedPageCacheRecord(record);\n  });\n\n  return record.cache;\n}\n\nexport function readPdfRenderedPageCache(\n  cache: PdfRenderedPageCache | undefined,\n  requested: PdfRenderedPageSignature,\n) {\n  if (!cache) return null;\n\n  let bestEntry: PdfRenderedPageCacheEntry | null = null;\n  for (const entry of cache.entries.values()) {\n    if (!doesRenderedPageSatisfyRequest(entry, requested)) continue;\n    if (\n      !bestEntry ||\n      entry.pixels < bestEntry.pixels ||\n      (entry.pixels === bestEntry.pixels && entry.lastUsed > bestEntry.lastUsed)\n    ) {\n      bestEntry = entry;\n    }\n  }\n\n  if (!bestEntry) return null;\n  cache.clock += 1;\n  bestEntry.lastUsed = cache.clock;\n  return bestEntry;\n}\n\nexport function readPdfRenderedPageCachePreview(\n  cache: PdfRenderedPageCache | undefined,\n  requested: PdfRenderedPageSignature,\n) {\n  if (!cache) return null;\n\n  let bestEntry: PdfRenderedPageCacheEntry | null = null;\n  for (const entry of cache.entries.values()) {\n    if (!doesRenderedPageMatchPreview(entry, requested)) continue;\n    if (!bestEntry || entry.pixels > bestEntry.pixels) {\n      bestEntry = entry;\n    }\n  }\n\n  if (!bestEntry) return null;\n  cache.clock += 1;\n  bestEntry.lastUsed = cache.clock;\n  return bestEntry;\n}\n\nexport function writePdfRenderedPageCache({\n  cache,\n  rendered,\n  sourceCanvas,\n}: {\n  cache: PdfRenderedPageCache | undefined;\n  rendered: PdfRenderedPageSignature;\n  sourceCanvas: HTMLCanvasElement;\n}) {\n  if (!cache || sourceCanvas.width <= 0 || sourceCanvas.height <= 0) return;\n\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = sourceCanvas.width;\n  canvas.height = sourceCanvas.height;\n  const context = canvas.getContext(\"2d\");\n  if (!context || typeof context.drawImage !== \"function\") return;\n\n  context.drawImage(sourceCanvas, 0, 0);\n  cache.clock += 1;\n  const key = getPdfRenderedPageCacheKey(rendered);\n  cache.entries.set(key, {\n    ...rendered,\n    canvas,\n    lastUsed: cache.clock,\n    pixels: canvas.width * canvas.height,\n  });\n  evictPdfRenderedPageCache(cache, key);\n}\n\nexport function clearPdfRenderedPageCache(cache: PdfRenderedPageCache) {\n  cache.clock = 0;\n  cache.entries.clear();\n}\n\nfunction evictPdfRenderedPageCache(\n  cache: PdfRenderedPageCache,\n  retainedKey: string,\n) {\n  let totalPixels = getTotalCachedPixels(cache);\n\n  while (\n    cache.entries.size > PDF_RENDERED_PAGE_CACHE_MAX_ENTRIES ||\n    totalPixels > PDF_RENDERED_PAGE_CACHE_MAX_PIXELS\n  ) {\n    const evicted = findPdfRenderedPageCacheEviction(cache, retainedKey);\n    if (!evicted) return;\n\n    totalPixels -= evicted.entry.pixels;\n    cache.entries.delete(evicted.key);\n  }\n}\n\nfunction findPdfRenderedPageCacheEviction(\n  cache: PdfRenderedPageCache,\n  retainedKey: string,\n) {\n  let oldest: { key: string; entry: PdfRenderedPageCacheEntry } | null = null;\n\n  for (const [key, entry] of cache.entries) {\n    if (key === retainedKey) continue;\n    if (!oldest || entry.lastUsed < oldest.entry.lastUsed) {\n      oldest = { key, entry };\n    }\n  }\n\n  return oldest;\n}\n\nfunction getTotalCachedPixels(cache: PdfRenderedPageCache) {\n  let pixels = 0;\n  for (const entry of cache.entries.values()) {\n    pixels += entry.pixels;\n  }\n  return pixels;\n}\n\nfunction doesRenderedPageSatisfyRequest(\n  rendered: PdfRenderedPageSignature,\n  requested: PdfRenderedPageSignature,\n) {\n  return (\n    rendered.documentKey === requested.documentKey &&\n    rendered.pageNumber === requested.pageNumber &&\n    rendered.rotation === requested.rotation &&\n    rendered.scale >= requested.scale &&\n    rendered.viewportWidth >= requested.viewportWidth &&\n    rendered.viewportHeight >= requested.viewportHeight &&\n    hasMatchingPageAspectRatio(rendered, requested) &&\n    rendered.devicePixelRatio >= requested.devicePixelRatio\n  );\n}\n\nfunction doesRenderedPageMatchPreview(\n  rendered: PdfRenderedPageSignature,\n  requested: PdfRenderedPageSignature,\n) {\n  return (\n    rendered.documentKey === requested.documentKey &&\n    rendered.pageNumber === requested.pageNumber &&\n    rendered.rotation === requested.rotation &&\n    rendered.viewportWidth >=\n      requested.viewportWidth * PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO &&\n    rendered.viewportHeight >=\n      requested.viewportHeight * PDF_RENDERED_PAGE_PREVIEW_MIN_SCALE_RATIO &&\n    hasMatchingPageAspectRatio(rendered, requested)\n  );\n}\n\nfunction getPdfRenderedPageCacheKey(rendered: PdfRenderedPageSignature) {\n  return [\n    rendered.documentKey,\n    rendered.pageNumber,\n    rendered.scale,\n    rendered.rotation,\n    rendered.devicePixelRatio,\n    rendered.viewportWidth,\n    rendered.viewportHeight,\n  ].join(\":\");\n}\n\nfunction getPdfRenderedPageCacheRecord(\n  resetKey: unknown,\n): PdfRenderedPageCacheRecord {\n  const records = isObjectCacheKey(resetKey)\n    ? objectCacheRecords\n    : primitiveCacheRecords;\n  const cached = records.get(resetKey as never);\n  if (cached) return cached;\n\n  const record: PdfRenderedPageCacheRecord = {\n    cache: createPdfRenderedPageCache(resetKey),\n    retainCount: 0,\n    resetKey,\n  };\n  records.set(resetKey as never, record);\n  return record;\n}\n\nfunction releasePdfRenderedPageCacheRecord(record: PdfRenderedPageCacheRecord) {\n  record.retainCount = Math.max(0, record.retainCount - 1);\n  if (record.retainCount > 0) return;\n\n  clearPdfRenderedPageCache(record.cache);\n  if (isObjectCacheKey(record.resetKey)) {\n    objectCacheRecords.delete(record.resetKey);\n  } else {\n    primitiveCacheRecords.delete(record.resetKey);\n  }\n}\n\nfunction isObjectCacheKey(value: unknown): value is object {\n  return (\n    (typeof value === \"object\" && value !== null) || typeof value === \"function\"\n  );\n}\n\nfunction hasMatchingPageAspectRatio(\n  rendered: PdfRenderedPageSignature,\n  requested: PdfRenderedPageSignature,\n) {\n  const renderedRatio = rendered.viewportWidth / rendered.viewportHeight;\n  const requestedRatio = requested.viewportWidth / requested.viewportHeight;\n  return Math.abs(renderedRatio - requestedRatio) < 0.0001;\n}\n",
      "type": "registry:ui",
      "target": "@ui/pdf-viewer-render-cache.ts"
    }
  ],
  "type": "registry:ui"
}
