{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "docx-viewer",
  "title": "DOCX Viewer",
  "description": "A docx-preview-backed Word viewer: faithful paginated pages, continuous scroll, zoom, fit-to-width, and download. Off-screen pages are skipped via CSS content-visibility.",
  "dependencies": [
    "lucide-react",
    "docx-preview"
  ],
  "registryDependencies": [
    "@retab/docx-document-resource",
    "@retab/utils",
    "button",
    "@retab/scroll-area",
    "separator",
    "@retab/skeleton",
    "dropdown-menu",
    "@retab/viewer-controls",
    "@retab/use-mount-effect",
    "@retab/use-keyed-layout-effect"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/docx-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { clearDocxDocumentResource } from \"@/lib/docx-document-resource\";\nimport { isResourceError, isViewerFormatError } from \"@/lib/viewer-errors\";\nimport {\n  createViewerResource,\n  type ViewerResource,\n} from \"@/lib/viewer-resource\";\nimport { useIsClient } from \"@/components/ui/use-is-client\";\nimport { ViewerErrorBoundary } from \"@/components/ui/viewer-error\";\n\nimport { DocxViewerFallback } from \"./docx-viewer-chrome\";\nimport { DocxViewerContent } from \"./docx-viewer-content\";\nimport type {\n  DocxResourceContentProps,\n  DocxViewerHandle,\n  DocxViewerProps,\n} from \"./docx-viewer-types\";\n\nexport type {\n  DocxDocumentSource,\n  DocxResourceContentProps,\n  DocxTarget,\n  DocxViewerHandle,\n  DocxViewerProps,\n} from \"./docx-viewer-types\";\n\nexport type DocxViewerProviderProps = {\n  children: React.ReactNode;\n  resource: ViewerResource;\n};\n\nexport type DocxViewerDocumentProps = Omit<\n  DocxResourceContentProps,\n  \"resource\"\n>;\n\nexport const DocxViewer = React.forwardRef<DocxViewerHandle, DocxViewerProps>(\n  function DocxViewer(props, ref) {\n    const { source, ...resourceProps } = props;\n    const resource = React.useMemo(\n      () => createViewerResource(source),\n      [source],\n    );\n    return (\n      <DocxResourceContent {...resourceProps} ref={ref} resource={resource} />\n    );\n  },\n);\n\nconst DocxViewerResourceContext = React.createContext<ViewerResource | null>(\n  null,\n);\n\nexport function DocxViewerProvider({\n  children,\n  resource,\n}: DocxViewerProviderProps) {\n  return (\n    <DocxViewerResourceContext.Provider value={resource}>\n      {children}\n    </DocxViewerResourceContext.Provider>\n  );\n}\n\nfunction useDocxViewerResource(): ViewerResource {\n  const resource = React.useContext(DocxViewerResourceContext);\n  if (!resource) {\n    throw new Error(\n      \"DocxViewerDocument must be used within DocxViewerProvider.\",\n    );\n  }\n  return resource;\n}\n\nexport const DocxViewerDocument = React.forwardRef<\n  DocxViewerHandle,\n  DocxViewerDocumentProps\n>(function DocxViewerDocument(props, ref) {\n  const resource = useDocxViewerResource();\n  return <DocxResourceContent {...props} ref={ref} resource={resource} />;\n});\n\nexport const DocxResourceContent = React.forwardRef<\n  DocxViewerHandle,\n  DocxResourceContentProps\n>(function DocxResourceContent(props, ref) {\n  const isClient = useIsClient();\n  const resource = props.resource;\n  if (!isClient) {\n    return (\n      <DocxViewerFallback\n        bare={props.bare}\n        className={props.className}\n        controls={props.controls}\n      />\n    );\n  }\n  return (\n    <ViewerErrorBoundary\n      bare={props.bare}\n      className={props.className}\n      download={\n        props.controls === false || props.download === false\n          ? null\n          : resource.originalDownload\n      }\n      format=\"docx\"\n      onRetry={(error) => {\n        if (isResourceError(error) || !isViewerFormatError(error)) {\n          clearDocxDocumentResource(resource.content);\n        }\n      }}\n      resetKey={resource.keys.resource}\n      sourceKind={resource.sourceKind}\n    >\n      <React.Suspense\n        fallback={\n          <DocxViewerFallback\n            bare={props.bare}\n            className={props.className}\n            controls={props.controls}\n          />\n        }\n      >\n        <DocxViewerContent {...props} forwardedRef={ref} resource={resource} />\n      </React.Suspense>\n    </ViewerErrorBoundary>\n  );\n});\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-core.ts",
      "content": "import {\n  detectCategory,\n  extensionOf,\n  extractName,\n  resolveViewerDescriptor,\n  textPayloadKey,\n  type FileCategory,\n  type ViewerDescriptor,\n  type ViewerSource,\n} from \"@/lib/viewer-source\";\n\nexport type { FileCategory, ViewerSource };\nexport type FileViewerFallbackSize = { width: number; height: number };\n\nexport interface FileViewerCoreProps {\n  source: ViewerSource;\n  category?: FileCategory;\n  className?: string;\n  /** Intrinsic first-frame size for image/TIFF loading skeletons. */\n  fallbackFrameSize?: FileViewerFallbackSize;\n  /** Intrinsic first-slide size for PPTX loading skeletons. */\n  fallbackSlideSize?: FileViewerFallbackSize;\n  isolateStyles?: boolean;\n}\n\nexport type FileDescriptor = ViewerDescriptor;\n\nexport function resolveFileDescriptor({\n  source,\n  category,\n}: FileViewerCoreProps): FileDescriptor {\n  return resolveViewerDescriptor({\n    source,\n    category,\n  });\n}\n\nexport function descriptorResetKey(descriptor: FileDescriptor): string {\n  return [\n    descriptorIdentityResetKey(descriptor),\n    descriptor.displayName,\n    descriptor.mimeType ?? \"\",\n    descriptor.category,\n  ].join(\"\\u0000\");\n}\n\nfunction descriptorIdentityResetKey(descriptor: FileDescriptor): string {\n  if (\n    descriptor.source.kind === \"text\" &&\n    descriptor.source.identityKey == null\n  ) {\n    return textPayloadKey(descriptor.source.text);\n  }\n  return descriptor.identityKey;\n}\n\nexport function isProseTextDescriptor(descriptor: FileDescriptor): boolean {\n  if (descriptor.category !== \"text\") return false;\n\n  const extension = extensionOf(descriptor.fileName);\n  if (extension === \"txt\" || extension === \"text\") return true;\n  return !extension && descriptor.mimeType?.toLowerCase() === \"text/plain\";\n}\n\nexport { detectCategory, extensionOf, extractName };\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-core.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-types.ts",
      "content": "import type { ViewerResource } from \"@/lib/viewer-resource\";\nimport type { BlobViewerSource, UrlViewerSource } from \"@/lib/viewer-source\";\n\nexport type DocxDocumentSource = UrlViewerSource | BlobViewerSource;\n\nexport type DocxTarget =\n  | { kind: \"text\"; text: string }\n  | { kind: \"cell\"; table: number; row: number; column: number };\n\nexport interface DocxViewerHandle {\n  scrollToTarget: (target: DocxTarget, options?: ScrollIntoViewOptions) => void;\n  getViewportElement: () => HTMLDivElement | null;\n}\n\nexport interface DocxViewerProps {\n  source: DocxDocumentSource;\n  className?: string;\n  scale?: number;\n  defaultScale?: number;\n  onScaleChange?: (scale: number | null) => void;\n  controls?: boolean;\n  /** Show download actions in this viewer's controls/error state. */\n  download?: boolean;\n  highlight?: DocxTarget | null;\n  onVisiblePageChange?: (page: number) => void;\n  onScrollProgressChange?: (progress: number) => void;\n  bare?: boolean;\n}\n\nexport type DocxResourceContentProps = Omit<DocxViewerProps, \"source\"> & {\n  resource: ViewerResource;\n};\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-types.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-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/docx-viewer-scale.ts",
      "content": "import * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\n\nimport {\n  clampDocxScale,\n  DOCX_ZOOM_STEP,\n  normalizeDocxScale,\n} from \"./docx-viewer-core\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\n// The docx stage's own inline padding (p-4 on both sides). Fit-width sizes the\n// page to the layout width minus this padding, so the settled stage box\n// (page + padding) is an affine unit-slope function of the layout width — the\n// shape the fit-width surface motion resolver reprojects.\nexport const DOCX_STAGE_INLINE_PADDING_PX = 32;\n\nexport function useDocxViewerScale({\n  defaultScale,\n  layoutInlineSize,\n  onScaleChange,\n  pageWidth,\n  resetKey,\n  scale: controlledScale,\n}: {\n  defaultScale?: number;\n  layoutInlineSize: number | null;\n  onScaleChange?: (scale: number | null) => void;\n  pageWidth: number | null;\n  resetKey: string;\n  scale?: number;\n}) {\n  const normalizedControlledScale = normalizeDocxScale(controlledScale);\n  const isScaleControlled = controlledScale != null;\n  const normalizedDefaultScale = normalizeDocxScale(defaultScale);\n  const [manualScale, setManualScale] = React.useState<number | null>(\n    normalizedDefaultScale,\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"docx-scale-reset\", normalizedDefaultScale, resetKey]),\n    () => {\n      setManualScale(normalizedDefaultScale);\n    },\n  );\n\n  const fitScale =\n    layoutInlineSize && pageWidth\n      ? clampDocxScale(\n          (layoutInlineSize - DOCX_STAGE_INLINE_PADDING_PX) / pageWidth,\n        )\n      : 1;\n  const isFitWidth = normalizedControlledScale == null && manualScale == null;\n  const scale = normalizedControlledScale ?? manualScale ?? fitScale;\n\n  const setViewerScale = React.useCallback(\n    (nextScale: number | null) => {\n      const normalized =\n        nextScale == null ? null : normalizeDocxScale(nextScale);\n      if (isScaleControlled) {\n        onScaleChange?.(normalized);\n        return;\n      }\n      setManualScale(normalized);\n      onScaleChange?.(normalized);\n    },\n    [isScaleControlled, onScaleChange],\n  );\n\n  const zoomIn = React.useCallback(() => {\n    setViewerScale(clampDocxScale(scale * DOCX_ZOOM_STEP));\n  }, [scale, setViewerScale]);\n\n  const zoomOut = React.useCallback(() => {\n    setViewerScale(clampDocxScale(scale / DOCX_ZOOM_STEP));\n  }, [scale, setViewerScale]);\n\n  const fitWidth = React.useCallback(() => {\n    setViewerScale(null);\n  }, [setViewerScale]);\n\n  return {\n    fitWidth,\n    isFitWidth,\n    isScaleControlled,\n    scale,\n    setViewerScale,\n    zoomIn,\n    zoomOut,\n  };\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-scale.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-scroll.ts",
      "content": "import * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\n\nimport { clamp } from \"./docx-viewer-core\";\nimport {\n  captureDocxReadingAnchorFromLayout,\n  findDocxPageByMarker,\n  restoreDocxReadingAnchorFromLayout,\n  type DocxPageLayout,\n  type DocxReadingAnchor,\n} from \"./docx-viewer-layout\";\nimport {\n  DOCX_ZOOM_INTENT_MAX_AGE_MS,\n  type DocxZoomTransaction,\n} from \"./docx-viewer-zoom-motion\";\nimport type { ViewerDocumentZoomMotionController } from \"./viewer-types\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport function useDocxViewerScroll({\n  layoutKey,\n  pageLayout,\n  onScrollProgressChange,\n  onVisiblePageChange,\n  ready,\n  scale,\n  zoomMotion,\n}: {\n  layoutKey: unknown;\n  pageLayout: DocxPageLayout | null;\n  onScrollProgressChange?: (progress: number) => void;\n  onVisiblePageChange?: (page: number) => void;\n  ready: boolean;\n  scale: number;\n  zoomMotion?: ViewerDocumentZoomMotionController<DocxZoomTransaction>;\n}) {\n  const scrollViewportRef = React.useRef<HTMLDivElement | null>(null);\n  const lastReported = React.useRef(0);\n  const latestAnchorRef = React.useRef<DocxReadingAnchor>({ kind: \"top\" });\n  const committedLayoutKeyRef = React.useRef(layoutKey);\n  const [currentPage, setCurrentPage] = React.useState(1);\n  const scrollFrame = React.useRef(0);\n  const pendingZoomIntentRef = React.useRef<{\n    capturedAt: number;\n    transaction: DocxZoomTransaction;\n  } | null>(null);\n  const activeZoomMotionCancelRef = React.useRef<(() => void) | null>(null);\n\n  // Interrupting a zoom relax snaps to its committed endpoint: the layout and\n  // scroll landed in the zoom's own commit, so clearing the transform is\n  // always safe and never moves the settled geometry.\n  const cancelZoomMotion = React.useCallback(() => {\n    pendingZoomIntentRef.current = null;\n    const cancelActiveZoomMotion = activeZoomMotionCancelRef.current;\n    activeZoomMotionCancelRef.current = null;\n    cancelActiveZoomMotion?.();\n  }, []);\n\n  // Called in the zoom gesture's own task, against the pre-zoom layout and\n  // painted DOM; the layout commit the gesture causes consumes the intent.\n  const captureZoomIntent = React.useCallback(() => {\n    const viewport = scrollViewportRef.current;\n    if (!viewport || !zoomMotion) {\n      pendingZoomIntentRef.current = null;\n      return;\n    }\n    const transaction = zoomMotion.capture({\n      scrollTop: viewport.scrollTop,\n      viewportElement: viewport,\n    });\n    pendingZoomIntentRef.current =\n      transaction == null\n        ? null\n        : { capturedAt: readDocxScrollNow(), transaction };\n  }, [zoomMotion]);\n\n  const resetScroll = React.useCallback(() => {\n    setCurrentPage(1);\n    lastReported.current = 0;\n    latestAnchorRef.current = { kind: \"top\" };\n    cancelZoomMotion();\n    if (scrollViewportRef.current) scrollViewportRef.current.scrollTop = 0;\n  }, [cancelZoomMotion]);\n\n  const measureScroll = React.useCallback(() => {\n    scrollFrame.current = 0;\n    const viewport = scrollViewportRef.current;\n    if (!viewport) return;\n    const scrollable = viewport.scrollHeight - viewport.clientHeight;\n    onScrollProgressChange?.(\n      scrollable > 0 ? clamp(viewport.scrollTop / scrollable, 0, 1) : 0,\n    );\n    const current =\n      findDocxPageByMarker({\n        layout: pageLayout,\n        scale,\n        scrollTop: viewport.scrollTop,\n        viewportHeight: viewport.clientHeight,\n      })?.pageNumber ?? 1;\n    latestAnchorRef.current = captureDocxReadingAnchorFromLayout({\n      layout: pageLayout,\n      scale,\n      scrollTop: viewport.scrollTop,\n      viewportHeight: viewport.clientHeight,\n    });\n    if (current && current !== lastReported.current) {\n      lastReported.current = current;\n      setCurrentPage(current);\n      onVisiblePageChange?.(current);\n    }\n  }, [onScrollProgressChange, onVisiblePageChange, pageLayout, scale]);\n\n  const handleScroll = React.useCallback(() => {\n    if (scrollFrame.current) return;\n    scrollFrame.current = -1;\n    const requestedFrame = requestAnimationFrame(measureScroll);\n    if (scrollFrame.current === -1) scrollFrame.current = requestedFrame;\n  }, [measureScroll]);\n\n  useKeyedMountEffect(\n    joinEffectKey([\"docx-measure\", measureScroll, ready]),\n    () => {\n      if (ready) measureScroll();\n    },\n  );\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      \"docx-layout\",\n      layoutKey,\n      measureScroll,\n      pageLayout,\n      ready,\n      scale,\n      zoomMotion,\n    ]),\n    () => {\n      const previousLayoutKey = committedLayoutKeyRef.current;\n      committedLayoutKeyRef.current = layoutKey;\n      if (!ready) return;\n      if (Object.is(previousLayoutKey, layoutKey)) return;\n\n      const viewport = scrollViewportRef.current;\n      if (!viewport) return;\n\n      // A fresh layout commit owns the zoom stage; an in-flight relax against\n      // the previous layout can no longer settle correctly.\n      const pendingZoomIntent = pendingZoomIntentRef.current;\n      pendingZoomIntentRef.current = null;\n      cancelZoomMotion();\n\n      if (\n        pendingZoomIntent &&\n        zoomMotion &&\n        readDocxScrollNow() - pendingZoomIntent.capturedAt <=\n          DOCX_ZOOM_INTENT_MAX_AGE_MS\n      ) {\n        const zoomTarget = zoomMotion.resolveScrollTarget({\n          transaction: pendingZoomIntent.transaction,\n          viewportElement: viewport,\n        });\n        if (zoomTarget) {\n          // Commit-then-relax: land the centered scroll inside this commit,\n          // then relax the painted FLIP over it. Raw scrollLeft assignment on\n          // purpose — the browser clamps to the live scrollable range,\n          // including RTL's negative coordinate space.\n          viewport.scrollTop = zoomTarget.top;\n          if (zoomTarget.left != null && Number.isFinite(zoomTarget.left)) {\n            viewport.scrollLeft = zoomTarget.left;\n          }\n          activeZoomMotionCancelRef.current = zoomMotion.play({\n            transaction: pendingZoomIntent.transaction,\n            viewportElement: viewport,\n          });\n          measureScroll();\n          return;\n        }\n      }\n\n      const maxScrollTop = Math.max(\n        0,\n        viewport.scrollHeight - viewport.clientHeight,\n      );\n      const restored = restoreDocxReadingAnchorFromLayout({\n        anchor: latestAnchorRef.current,\n        layout: pageLayout,\n        maxScrollTop,\n        scale,\n        viewportHeight: viewport.clientHeight,\n      });\n      if (restored != null) viewport.scrollTop = restored;\n      measureScroll();\n    },\n  );\n\n  useMountEffect(() => {\n    return () => {\n      cancelZoomMotion();\n      if (scrollFrame.current > 0) cancelAnimationFrame(scrollFrame.current);\n    };\n  });\n\n  return {\n    captureZoomIntent,\n    currentPage,\n    handleScroll,\n    measureScroll,\n    resetScroll,\n    scrollViewportRef,\n  };\n}\n\nfunction readDocxScrollNow() {\n  return typeof performance !== \"undefined\" &&\n    typeof performance.now === \"function\"\n    ? performance.now()\n    : Date.now();\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-scroll.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-zoom-motion.ts",
      "content": "import { clamp } from \"./docx-viewer-core\";\nimport {\n  DOCX_VIEWER_PADDING_PX,\n  type DocxPageLayout,\n} from \"./docx-viewer-layout\";\nimport type { ViewerDocumentZoomMotionController } from \"./viewer-types\";\n\n// A toolbar zoom step re-anchors the viewport CENTER on both axes (Apple\n// Preview semantics): the content point under the viewport center before the\n// step is back under the viewport center after it, and a short FLIP relax\n// scales the painted surface about that fixed point. The reading-marker\n// restore (20% from the top, block axis only) stays the semantics for every\n// other geometry change — re-fits, container resizes — where the intent is\n// \"keep my reading position\", not \"zoom the camera\".\nconst DOCX_ZOOM_CENTER_MARKER_RATIO = 0.5;\nconst DOCX_ZOOM_MOTION_DURATION_MS = 200;\n// easeOutCubic: fast attack, gentle landing — a linear relax reads as a\n// hard stop at settle.\nconst DOCX_ZOOM_MOTION_EASING = \"cubic-bezier(0.33, 1, 0.68, 1)\";\nconst DOCX_ZOOM_MOTION_CLEANUP_MS = DOCX_ZOOM_MOTION_DURATION_MS + 50;\nconst DOCX_ZOOM_MOTION_MIN_TRANSLATE_PX = 0.5;\nconst DOCX_ZOOM_MOTION_MIN_SCALE_DELTA = 0.001;\n// A rebased (paged) physical scroll detaches the stage box from the content\n// scale, so the whole-surface FLIP would warp. Its signature is precise: the\n// rebased axis is PINNED (the container keeps its size while the content\n// rescales), so one axis barely moves while the other moves a lot. Testing for\n// that beats a plain ratio tolerance — an honest page stack carries small\n// constant terms (rounded gaps, fixed outer padding) that put the block axis a\n// couple of percent off the inline one. On a big jump a tight ratio tolerance\n// then refuses the relax and the whole scale change lands in one frame —\n// measured on the image viewer as an un-animated 620px snap on a multi-frame\n// TIFF's fit-width, and this stack has the same shape of layout. The\n// wide ratio net below still catches anything wilder, and the FLIP writes\n// per-axis scales, so a slightly non-affine layout renders exactly.\nconst DOCX_ZOOM_MOTION_AXIS_FROZEN_DELTA = 0.02;\nconst DOCX_ZOOM_MOTION_AXIS_MOVED_DELTA = 0.05;\nconst DOCX_ZOOM_MOTION_AXIS_MISMATCH_RATIO = 0.25;\n\n// The exact wait-out of the relax before the visual clip re-tightens; clip\n// release is React-rendered state, so it outlives the inline transition.\nexport const DOCX_ZOOM_MOTION_TOTAL_MS = DOCX_ZOOM_MOTION_CLEANUP_MS + 50;\n\n// A zoom intent is consumed by the layout commit its gesture causes; if no\n// commit claims it in this window (scale already clamped, controlled scale\n// ignored by the owner), it is stale and must not re-anchor a later,\n// unrelated layout change.\nexport const DOCX_ZOOM_INTENT_MAX_AGE_MS = 400;\n\n// The zoom stage wraps the CSS-zoomed docx host: it shrink-wraps the page box\n// (width = pageWidth × scale, exactly linear in scale), so it serves as both\n// the inline-anchor ruler and the FLIP layer. The transform must NOT go on\n// the `zoom`-styled host itself — zoom and transform-origin resolve in\n// different coordinate spaces — and not on the kernel-registered document\n// surface, whose style the shell motion owns.\nconst DOCX_ZOOM_STAGE_SELECTOR = '[data-slot=\"docx-viewer-zoom-stage\"]';\n\nexport type DocxZoomTransaction = {\n  pageNumber: number;\n  /**\n   * Deliberately unclamped, like the reading anchor: page offsets are linear\n   * in scale (intrinsic tops × scale + fixed outer padding), so a center\n   * marker resting in a gap restores by the same page-relative fraction.\n   */\n  yPercent: number;\n  /** Viewport-center position as a fraction of the stage's inline size. */\n  inlineFraction: number | null;\n  /**\n   * Same, on the BLOCK axis. Rect-derived like the inline one, so the solve\n   * cannot be thrown off by anything the layout model does not know about —\n   * chiefly the auto margins that centre a zoomed-out page inside the pane.\n   * The page model stays the fallback whenever the stage's painted height\n   * stops matching the scaled document.\n   */\n  blockFraction: number | null;\n  /** Painted stage rect at click time — the FLIP's \"first\" frame. */\n  previousVisualRect: DOMRectReadOnly | null;\n};\n\nexport function createDocxZoomMotionController({\n  layout,\n  scale,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n}): ViewerDocumentZoomMotionController<DocxZoomTransaction> {\n  return {\n    capture: ({ scrollTop, viewportElement }) =>\n      captureDocxZoomTransaction({ layout, scale, scrollTop, viewportElement }),\n    resolveScrollTarget: ({ transaction, viewportElement }) =>\n      resolveDocxZoomScrollTarget({\n        layout,\n        scale,\n        transaction,\n        viewportElement,\n      }),\n    play: ({ transaction, viewportElement }) =>\n      playDocxZoomMotion({ transaction, viewportElement }),\n  };\n}\n\nexport function captureDocxZoomTransaction({\n  layout,\n  scale,\n  scrollTop,\n  viewportElement,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n  scrollTop: number;\n  viewportElement: HTMLDivElement;\n}): DocxZoomTransaction | null {\n  const pages = layout?.pages;\n  if (!pages?.length) return null;\n\n  const viewportBlockSize = Math.max(0, viewportElement.clientHeight);\n  const centerOffset =\n    Math.max(0, scrollTop) + viewportBlockSize * DOCX_ZOOM_CENTER_MARKER_RATIO;\n  // Intrinsic (unscaled) document coordinate of the center marker.\n  const y = (centerOffset - DOCX_VIEWER_PADDING_PX) / safeDocxZoomScale(scale);\n  const page = findDocxPageAtIntrinsicOffset(pages, y);\n  if (!page || page.height <= 0) return null;\n\n  return {\n    pageNumber: page.pageNumber,\n    yPercent: (y - page.top) / page.height,\n    inlineFraction: captureDocxZoomInlineFraction(viewportElement),\n    blockFraction: captureDocxZoomBlockFraction(viewportElement),\n    previousVisualRect: readElementRect(findDocxZoomStage(viewportElement)),\n  };\n}\n\nexport function resolveDocxZoomScrollTarget({\n  layout,\n  scale,\n  transaction,\n  viewportElement,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n  transaction: DocxZoomTransaction;\n  viewportElement: HTMLDivElement;\n}): { left?: number; top: number } | null {\n  const page = layout?.pages[transaction.pageNumber - 1];\n  if (!page || page.height <= 0) return null;\n\n  const viewportBlockSize = Math.max(0, viewportElement.clientHeight);\n  const maxScrollTop = Math.max(\n    0,\n    viewportElement.scrollHeight - viewportBlockSize,\n  );\n  const top = clamp(\n    resolveDocxZoomScrollTop({ layout, scale, transaction, viewportElement }) ??\n      DOCX_VIEWER_PADDING_PX +\n        (page.top + page.height * transaction.yPercent) *\n          safeDocxZoomScale(scale) -\n        viewportBlockSize * DOCX_ZOOM_CENTER_MARKER_RATIO,\n    0,\n    maxScrollTop,\n  );\n\n  const left = resolveDocxZoomScrollLeft(viewportElement, transaction);\n  return { top, ...(left == null ? null : { left }) };\n}\n\n// The stage is measured by live rects rather than re-deriving its centered\n// offset, so the math is direction-agnostic: RTL scrollLeft coordinate spaces\n// and the browser's own clamping both fall out for free.\nfunction captureDocxZoomInlineFraction(viewportElement: HTMLDivElement) {\n  const stageRect = readElementRect(findDocxZoomStage(viewportElement));\n  if (!stageRect || stageRect.width <= 0) return null;\n  return (\n    (getViewportCenterX(viewportElement) - stageRect.left) / stageRect.width\n  );\n}\n\nfunction captureDocxZoomBlockFraction(viewportElement: HTMLDivElement) {\n  const stageRect = readElementRect(findDocxZoomStage(viewportElement));\n  if (!stageRect || stageRect.height <= 0) return null;\n  return (\n    (getViewportCenterY(viewportElement) - stageRect.top) / stageRect.height\n  );\n}\n\n// Scroll down by however far the anchored content point currently sits below\n// the viewport centre — the block mirror of the inline solve. Guarded on the\n// stage still painting the whole scaled document, so a virtualized or\n// otherwise detached stage falls back to the page model.\nfunction resolveDocxZoomScrollTop({\n  layout,\n  scale,\n  transaction,\n  viewportElement,\n}: {\n  layout: DocxPageLayout | null;\n  scale: number;\n  transaction: DocxZoomTransaction;\n  viewportElement: HTMLDivElement;\n}) {\n  if (transaction.blockFraction == null || !layout) return null;\n  const stageRect = readElementRect(findDocxZoomStage(viewportElement));\n  if (!stageRect || stageRect.height <= 0) return null;\n  const documentHeight = layout.totalHeight * safeDocxZoomScale(scale);\n  if (Math.abs(stageRect.height - documentHeight) > 2) return null;\n  return (\n    viewportElement.scrollTop +\n    (stageRect.top + stageRect.height * transaction.blockFraction) -\n    getViewportCenterY(viewportElement)\n  );\n}\n\nfunction resolveDocxZoomScrollLeft(\n  viewportElement: HTMLDivElement,\n  transaction: DocxZoomTransaction,\n) {\n  if (transaction.inlineFraction == null) return undefined;\n  const stageRect = readElementRect(findDocxZoomStage(viewportElement));\n  if (!stageRect || stageRect.width <= 0) return undefined;\n\n  // Scroll right by however far the anchored content point currently sits\n  // right of the viewport center; the browser clamps to the scrollable range\n  // (which also zeroes it out when the stage fits without overflow).\n  return (\n    viewportElement.scrollLeft +\n    (stageRect.left + stageRect.width * transaction.inlineFraction) -\n    getViewportCenterX(viewportElement)\n  );\n}\n\nexport function playDocxZoomMotion({\n  transaction,\n  viewportElement,\n}: {\n  transaction: DocxZoomTransaction;\n  viewportElement: HTMLDivElement;\n}): (() => void) | null {\n  if (typeof requestAnimationFrame !== \"function\") return null;\n  if (prefersReducedMotion()) return null;\n\n  const previousRect = transaction.previousVisualRect;\n  const stage = findDocxZoomStage(viewportElement);\n  if (!previousRect || !stage) return null;\n\n  const currentRect = readElementRect(stage);\n  if (\n    !currentRect ||\n    previousRect.width <= 0 ||\n    previousRect.height <= 0 ||\n    currentRect.width <= 0 ||\n    currentRect.height <= 0\n  ) {\n    return null;\n  }\n\n  const scaleX = previousRect.width / currentRect.width;\n  const scaleY = previousRect.height / currentRect.height;\n  if (hasDetachedDocxZoomAxes(scaleX, scaleY)) {\n    return null;\n  }\n\n  const translateX = previousRect.left - currentRect.left;\n  const translateY = previousRect.top - currentRect.top;\n  const hasVisibleDelta =\n    Math.abs(translateX) > DOCX_ZOOM_MOTION_MIN_TRANSLATE_PX ||\n    Math.abs(translateY) > DOCX_ZOOM_MOTION_MIN_TRANSLATE_PX ||\n    Math.abs(1 - scaleX) > DOCX_ZOOM_MOTION_MIN_SCALE_DELTA ||\n    Math.abs(1 - scaleY) > DOCX_ZOOM_MOTION_MIN_SCALE_DELTA;\n  if (!hasVisibleDelta) return null;\n\n  // Re-express the FLIP about the viewport center instead of the stage's\n  // top-left: the stage is the full document (potentially hundreds of\n  // thousands of px tall), and a scale that far from its origin runs into\n  // GPU float precision. Anchoring at the viewport keeps the rasterized\n  // region's coordinates small; the mapping is identical.\n  const originX = getViewportCenterX(viewportElement) - currentRect.left;\n  const originY =\n    viewportElement.getBoundingClientRect().top +\n    Math.max(0, viewportElement.clientHeight) / 2 -\n    currentRect.top;\n  const anchoredTranslateX = translateX + (scaleX - 1) * originX;\n  const anchoredTranslateY = translateY + (scaleY - 1) * originY;\n\n  let cleanupTimeout: ReturnType<typeof setTimeout> | null = null;\n  let startFrame = 0;\n  let finished = false;\n  const finish = () => {\n    if (finished) return;\n    finished = true;\n    if (startFrame !== 0) cancelAnimationFrame(startFrame);\n    if (cleanupTimeout !== null) clearTimeout(cleanupTimeout);\n    removeInterruptListeners();\n    stage.style.transition = \"\";\n    stage.style.transform = \"\";\n    stage.style.transformOrigin = \"\";\n    stage.style.willChange = \"\";\n  };\n  // A user gesture mid-relax snaps to the committed endpoint: layout and\n  // scroll already landed in the zoom's own commit, so clearing the transform\n  // is always safe — and content must never keep gliding under a live scroll.\n  // (Gesture events only; the zoom's own programmatic scroll writes do not\n  // fire these.)\n  const removeInterruptListeners = attachDocxZoomInterruptListeners(\n    viewportElement,\n    () => finish(),\n  );\n\n  stage.style.transition = \"none\";\n  stage.style.transformOrigin = `${originX}px ${originY}px`;\n  stage.style.transform = `translate3d(${anchoredTranslateX}px, ${anchoredTranslateY}px, 0px) scale(${scaleX}, ${scaleY})`;\n  stage.style.willChange = \"transform\";\n\n  startFrame = requestAnimationFrame(() => {\n    startFrame = 0;\n    if (finished) return;\n    stage.style.transition = `transform ${DOCX_ZOOM_MOTION_DURATION_MS}ms ${DOCX_ZOOM_MOTION_EASING}`;\n    stage.style.transform = \"translate3d(0px, 0px, 0px) scale(1, 1)\";\n  });\n  cleanupTimeout = setTimeout(finish, DOCX_ZOOM_MOTION_CLEANUP_MS);\n\n  return finish;\n}\n\nfunction attachDocxZoomInterruptListeners(\n  viewportElement: HTMLDivElement,\n  interrupt: () => void,\n) {\n  if (typeof viewportElement.addEventListener !== \"function\") return () => {};\n  const events = [\"wheel\", \"touchstart\", \"pointerdown\", \"keydown\"] as const;\n  for (const event of events) {\n    viewportElement.addEventListener(event, interrupt, { passive: true });\n  }\n  return () => {\n    for (const event of events) {\n      viewportElement.removeEventListener(event, interrupt);\n    }\n  };\n}\n\nfunction findDocxPageAtIntrinsicOffset(\n  pages: DocxPageLayout[\"pages\"],\n  y: number,\n) {\n  let low = 0;\n  let high = pages.length - 1;\n  let current = pages[0] ?? null;\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\nfunction findDocxZoomStage(viewportElement: HTMLDivElement) {\n  if (typeof viewportElement.querySelector !== \"function\") return null;\n  return viewportElement.querySelector<HTMLElement>(DOCX_ZOOM_STAGE_SELECTOR);\n}\n\nfunction getViewportCenterX(viewportElement: HTMLDivElement) {\n  return (\n    viewportElement.getBoundingClientRect().left +\n    Math.max(0, viewportElement.clientWidth) / 2\n  );\n}\n\nfunction getViewportCenterY(viewportElement: HTMLDivElement) {\n  return (\n    viewportElement.getBoundingClientRect().top +\n    Math.max(0, viewportElement.clientHeight) / 2\n  );\n}\n\n// True when one axis is pinned while the other rescales — the paged-scroll\n// signature — or when the two ratios are so far apart that the stage box\n// cannot be tracking the content at all.\nfunction hasDetachedDocxZoomAxes(scaleX: number, scaleY: number) {\n  const inlineDelta = Math.abs(scaleX - 1);\n  const blockDelta = Math.abs(scaleY - 1);\n  const frozenAxis =\n    (blockDelta < DOCX_ZOOM_MOTION_AXIS_FROZEN_DELTA &&\n      inlineDelta > DOCX_ZOOM_MOTION_AXIS_MOVED_DELTA) ||\n    (inlineDelta < DOCX_ZOOM_MOTION_AXIS_FROZEN_DELTA &&\n      blockDelta > DOCX_ZOOM_MOTION_AXIS_MOVED_DELTA);\n  return (\n    frozenAxis ||\n    Math.abs(scaleX - scaleY) >\n      DOCX_ZOOM_MOTION_AXIS_MISMATCH_RATIO * Math.max(scaleX, scaleY)\n  );\n}\n\nfunction readElementRect(element: HTMLElement | null) {\n  if (!element || typeof element.getBoundingClientRect !== \"function\") {\n    return null;\n  }\n  const rect = element.getBoundingClientRect();\n  return rect.width > 0 && rect.height > 0 ? rect : null;\n}\n\nfunction prefersReducedMotion() {\n  return (\n    typeof matchMedia === \"function\" &&\n    matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n  );\n}\n\nfunction safeDocxZoomScale(scale: number) {\n  return Number.isFinite(scale) && scale > 0 ? scale : 1;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-zoom-motion.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-targets.ts",
      "content": "import type { DocxTarget } from \"./docx-viewer-types\";\n\nexport interface DocxRenderIndex {\n  pages: readonly HTMLElement[];\n  root: HTMLElement;\n  text: DocxTextIndex | null;\n  cells: Map<string, DocxCellHit> | null;\n}\n\ninterface DocxTextIndex {\n  text: string;\n  spans: DocxTextSpan[];\n}\n\ninterface DocxTextSpan {\n  start: number;\n  end: number;\n  node: Text;\n  page: HTMLElement;\n  pageNumber: number;\n  sourceStartOffset: number;\n}\n\ninterface DocxTextPoint {\n  node: Text;\n  offset: number;\n  page: HTMLElement;\n  pageNumber: number;\n}\n\ninterface DocxCellHit {\n  cell: HTMLElement;\n  page: HTMLElement;\n  pageNumber: number;\n}\n\nexport interface DocxResolvedTarget {\n  page: HTMLElement;\n  pageNumber: number;\n  range: Range;\n  startContainer: Node;\n}\n\nconst INLINE_TAGS = new Set([\n  \"SPAN\",\n  \"A\",\n  \"B\",\n  \"I\",\n  \"EM\",\n  \"STRONG\",\n  \"U\",\n  \"S\",\n  \"STRIKE\",\n  \"DEL\",\n  \"INS\",\n  \"SMALL\",\n  \"BIG\",\n  \"SUB\",\n  \"SUP\",\n  \"MARK\",\n  \"FONT\",\n  \"CODE\",\n  \"ABBR\",\n  \"CITE\",\n  \"Q\",\n  \"TIME\",\n  \"BDI\",\n  \"BDO\",\n  \"WBR\",\n  \"LABEL\",\n  \"VAR\",\n  \"SAMP\",\n  \"KBD\",\n  \"TT\",\n  \"NOBR\",\n]);\n\nexport function buildDocxRenderIndex(\n  root: HTMLElement,\n  pages: readonly HTMLElement[] = Array.from(\n    root.querySelectorAll<HTMLElement>(\".docx-wrapper > section.docx\"),\n  ),\n): DocxRenderIndex {\n  return {\n    pages,\n    root,\n    text: null,\n    cells: null,\n  };\n}\n\nexport function resolveDocxTarget(\n  index: DocxRenderIndex,\n  target: DocxTarget,\n): Range | null {\n  return resolveDocxTargetHit(index, target)?.range ?? null;\n}\n\nexport function resolveDocxTargetHit(\n  index: DocxRenderIndex,\n  target: DocxTarget,\n): DocxResolvedTarget | null {\n  if (target.kind === \"cell\") {\n    const cells = index.cells ?? (index.cells = buildDocxCellIndex(index));\n    const hit = cells.get(cellKey(target.table, target.row, target.column));\n    if (!hit) return null;\n    const range = document.createRange();\n    range.selectNodeContents(hit.cell);\n    return {\n      page: hit.page,\n      pageNumber: hit.pageNumber,\n      range,\n      startContainer: hit.cell,\n    };\n  }\n\n  const needle = normalizeTextTarget(target.text);\n  if (!needle) return null;\n  const textIndex = index.text ?? (index.text = buildDocxTextIndex(index));\n  const idx = textIndex.text.indexOf(needle);\n  if (idx === -1) return null;\n  const start = findTextPoint(textIndex, idx);\n  const end = findTextPoint(textIndex, idx + needle.length - 1);\n  if (!start || !end) return null;\n  const range = document.createRange();\n  range.setStart(start.node, start.offset);\n  range.setEnd(end.node, end.offset + 1);\n  return {\n    page: start.page,\n    pageNumber: start.pageNumber,\n    range,\n    startContainer: start.node,\n  };\n}\n\nexport function targetKey(\n  target: DocxTarget | null | undefined,\n): string | null {\n  if (!target) return null;\n  return target.kind === \"cell\"\n    ? `cell:${target.table}:${target.row}:${target.column}`\n    : `text:${normalizeTextTarget(target.text)}`;\n}\n\nexport function normalizeTextTarget(text: string) {\n  return text.replace(/\\s+/g, \" \").trim();\n}\n\nfunction buildDocxCellIndex(index: DocxRenderIndex) {\n  const cells = new Map<string, DocxCellHit>();\n  let tableIndex = 0;\n  index.pages.forEach((page, pageIndex) => {\n    const tables = page.querySelectorAll(\"table\");\n    tables.forEach((table) => {\n      Array.from((table as HTMLTableElement).rows).forEach((row, rowIndex) => {\n        Array.from(row.cells).forEach((cell, columnIndex) => {\n          if (!hasHiddenAncestor(cell, page)) {\n            cells.set(cellKey(tableIndex, rowIndex, columnIndex), {\n              cell,\n              page,\n              pageNumber: pageIndex + 1,\n            });\n          }\n        });\n      });\n      tableIndex += 1;\n    });\n  });\n  return cells;\n}\n\nfunction buildDocxTextIndex(index: DocxRenderIndex) {\n  const pages = index.pages;\n  let normalized = \"\";\n  const spans: DocxTextSpan[] = [];\n  let prevSpace = false;\n  let prevBlock: HTMLElement | null = null;\n  let pendingBreak = false;\n\n  const append = (\n    text: string,\n    node: Text,\n    page: HTMLElement,\n    pageNumber: number,\n    sourceStartOffset: number,\n  ) => {\n    if (!text) return;\n    const start = normalized.length;\n    normalized += text;\n    spans.push({\n      start,\n      end: start + text.length,\n      node,\n      page,\n      pageNumber,\n      sourceStartOffset,\n    });\n  };\n\n  pages.forEach((page, pageIndex) => {\n    const pageNumber = pageIndex + 1;\n    const walker = document.createTreeWalker(\n      page,\n      NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\n    );\n    for (let n = walker.nextNode(); n; n = walker.nextNode()) {\n      if (n.nodeType === Node.ELEMENT_NODE) {\n        const tag = (n as HTMLElement).tagName;\n        if (\n          (tag === \"BR\" || tag === \"HR\") &&\n          isDocumentBreakElement(page, n as HTMLElement)\n        ) {\n          pendingBreak = true;\n        }\n        continue;\n      }\n      if (!isDocumentTextNode(page, n as Text)) continue;\n      const block = blockContainer((n as Text).parentElement, page);\n      const broke = pendingBreak || (prevBlock !== null && block !== prevBlock);\n      pendingBreak = false;\n      if (broke && !prevSpace && normalized) {\n        prevSpace = true;\n        append(\" \", n as Text, page, pageNumber, 0);\n      }\n      prevBlock = block;\n      const data = (n as Text).data;\n      for (let i = 0; i < data.length; i++) {\n        if (/\\s/.test(data[i])) {\n          if (prevSpace) continue;\n          prevSpace = true;\n          append(\" \", n as Text, page, pageNumber, i);\n        } else {\n          const start = i;\n          i += 1;\n          while (i < data.length && !/\\s/.test(data[i])) i += 1;\n          append(data.slice(start, i), n as Text, page, pageNumber, start);\n          i -= 1;\n          prevSpace = false;\n        }\n      }\n    }\n  });\n  return { text: normalized, spans };\n}\n\nfunction findTextPoint(\n  index: DocxTextIndex,\n  offset: number,\n): DocxTextPoint | null {\n  let low = 0;\n  let high = index.spans.length - 1;\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    const span = index.spans[mid]!;\n    if (offset < span.start) {\n      high = mid - 1;\n    } else if (offset >= span.end) {\n      low = mid + 1;\n    } else {\n      return {\n        node: span.node,\n        offset: span.sourceStartOffset + offset - span.start,\n        page: span.page,\n        pageNumber: span.pageNumber,\n      };\n    }\n  }\n  return null;\n}\n\nfunction cellKey(table: number, row: number, column: number) {\n  return `${table}:${row}:${column}`;\n}\n\nfunction blockContainer(\n  el: HTMLElement | null,\n  root: HTMLElement,\n): HTMLElement {\n  let cur = el;\n  while (cur && cur !== root && INLINE_TAGS.has(cur.tagName)) {\n    cur = cur.parentElement;\n  }\n  return cur ?? root;\n}\n\nfunction isDocumentTextNode(page: HTMLElement, node: Text) {\n  const parent = node.parentElement;\n  if (!parent || !page.contains(parent)) return false;\n  if (parent.closest(\"style, script, noscript, template\")) return false;\n  return !hasHiddenAncestor(parent, page);\n}\n\nfunction isDocumentBreakElement(page: HTMLElement, el: HTMLElement) {\n  if (!page.contains(el)) return false;\n  return !hasHiddenAncestor(el, page);\n}\n\nfunction hasHiddenAncestor(element: HTMLElement, root: HTMLElement) {\n  for (let el: HTMLElement | null = element; el; el = el.parentElement) {\n    if (\n      el.hidden ||\n      el.getAttribute(\"aria-hidden\")?.trim().toLowerCase() === \"true\"\n    ) {\n      return true;\n    }\n    const style =\n      typeof window !== \"undefined\" ? window.getComputedStyle(el) : el.style;\n    if (\n      style.display === \"none\" ||\n      style.visibility === \"hidden\" ||\n      style.visibility === \"collapse\" ||\n      style.getPropertyValue(\"content-visibility\") === \"hidden\"\n    ) {\n      return true;\n    }\n    if (el === root) break;\n  }\n  return false;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-targets.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-highlight.ts",
      "content": "import * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\n\nimport {\n  resolveDocxTarget,\n  targetKey,\n  type DocxRenderIndex,\n} from \"./docx-viewer-targets\";\nimport type { DocxTarget } from \"./docx-viewer-types\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport function useDocxHighlight({\n  highlight,\n  renderIndex,\n  ready,\n}: {\n  highlight?: DocxTarget | null;\n  renderIndex: DocxRenderIndex | null;\n  ready: boolean;\n}) {\n  const highlightName = \"docx-src-\" + React.useId().replace(/:/g, \"\");\n  const highlightKey = targetKey(highlight);\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      \"docx-highlight\",\n      highlightKey,\n      ready,\n      highlightName,\n      renderIndex,\n    ]),\n    () => {\n      const registry =\n        typeof CSS !== \"undefined\" && \"highlights\" in CSS\n          ? CSS.highlights\n          : null;\n      if (!registry || typeof Highlight === \"undefined\") return;\n      const deleteHighlight = () => {\n        try {\n          registry.delete(highlightName);\n        } catch {\n          // Highlighting is an enhancement; registry failures must not hide the document.\n        }\n      };\n      if (!highlight || !ready || !renderIndex) {\n        deleteHighlight();\n        return;\n      }\n      try {\n        const range = resolveDocxTarget(renderIndex, highlight);\n        if (range) registry.set(highlightName, new Highlight(range));\n        else deleteHighlight();\n      } catch {\n        deleteHighlight();\n      }\n      return deleteHighlight;\n    },\n  );\n\n  return highlightName;\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-highlight.ts"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-chrome.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\n\nimport { ViewerControlsSkeleton } from \"./viewer-controls\";\n\nexport function DocxViewerFrame({\n  bare = false,\n  children,\n  className,\n}: {\n  bare?: boolean;\n  children: React.ReactNode;\n  className?: string;\n}) {\n  return (\n    <div\n      className={cn(\n        \"flex min-h-0 flex-col overflow-hidden\",\n        bare ? \"bg-muted/20 h-full\" : \"bg-muted/30 rounded-xl border\",\n        className,\n      )}\n      data-slot=\"docx-viewer\"\n    >\n      {children}\n    </div>\n  );\n}\n\nexport function DocxViewerBody({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"relative flex min-h-0 flex-1\">\n      <div className=\"flex min-h-0 min-w-0 flex-1 flex-col\">{children}</div>\n    </div>\n  );\n}\n\nexport function DocxViewerFallback({\n  bare = false,\n  className,\n  controls = true,\n}: {\n  bare?: boolean;\n  className?: string;\n  controls?: boolean;\n}) {\n  return (\n    <DocxViewerFrame bare={bare} className={className}>\n      {controls ? <ViewerControlsSkeleton position zoom download /> : null}\n      <DocxViewerBody>\n        <div className=\"min-h-0 flex-1 overflow-auto\">\n          <div className=\"flex flex-col items-center p-4\">\n            <DocxSkeleton />\n          </div>\n        </div>\n      </DocxViewerBody>\n    </DocxViewerFrame>\n  );\n}\n\nexport function DocxSkeleton() {\n  return (\n    <Skeleton\n      aria-hidden\n      className=\"ring-border w-full rounded-none shadow-sm ring-1\"\n      data-slot=\"docx-page-skeleton\"\n      style={{ aspectRatio: \"8.5 / 11\" }}\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-chrome.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/docx-viewer-content.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\nimport { getDocxDocumentResource } from \"@/lib/docx-document-resource\";\nimport { isAbortError, isResourceError } from \"@/lib/viewer-errors\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport {\n  FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n  readFileViewerBeforeLayoutMotionFrame,\n} from \"./file-viewer-elements\";\nimport {\n  captureFileViewerFitWidthAnchorScreenOffset,\n  createFileViewerFitWidthSurfaceMotionResolver,\n  FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY,\n  resolveFileViewerFitWidthMotionAnchorBlock,\n} from \"./file-viewer-fit-width-motion\";\nimport type { FileViewerDocumentSurfaceMotionResolver } from \"./file-viewer-motion-kernel\";\nimport type { FileViewerMotionFrame } from \"./file-viewer-motion-plan\";\nimport { resolveFileViewerRendererLayoutInlineSize } from \"./file-viewer-renderer-contract\";\nimport {\n  useOptionalFileViewerRendererEnvironment,\n  useOptionalFileViewerRendererFrame,\n} from \"./file-viewer-renderer-frame\";\nimport {\n  DocxSkeleton,\n  DocxViewerBody,\n  DocxViewerFrame,\n} from \"./docx-viewer-chrome\";\nimport { DOCX_SCOPED_STYLES, toDocxFormatError } from \"./docx-viewer-core\";\nimport { useDocxHighlight } from \"./docx-viewer-highlight\";\nimport {\n  createDocxPageWindowForPage,\n  createDocxPageWindowFromScroll,\n  DOCX_READING_MARKER_RATIO,\n  DOCX_VIEWER_PADDING_PX,\n  findDocxPageByMarker,\n  type DocxPageLayout,\n} from \"./docx-viewer-layout\";\nimport {\n  commitDocxRender,\n  projectDocxPages,\n  loadDocxPreview,\n  renderCachedDocxPreview,\n  type DocxRenderedDocument,\n} from \"./docx-viewer-render\";\nimport {\n  DOCX_STAGE_INLINE_PADDING_PX,\n  useDocxViewerScale,\n} from \"./docx-viewer-scale\";\nimport { useDocxViewerScroll } from \"./docx-viewer-scroll\";\nimport { createDocxZoomMotionController } from \"./docx-viewer-zoom-motion\";\nimport {\n  buildDocxRenderIndex,\n  resolveDocxTargetHit,\n  type DocxRenderIndex,\n} from \"./docx-viewer-targets\";\nimport type {\n  DocxResourceContentProps,\n  DocxViewerHandle,\n} from \"./docx-viewer-types\";\nimport {\n  useViewerControlsRegistration,\n  ViewerControls,\n  ViewerControlsSkeleton,\n  type ViewerControlsState,\n} from \"./viewer-controls\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst DOCX_TRANSITION_WINDOW_RELEASE_MS = 260;\n\nexport function DocxViewerContent({\n  bare = false,\n  className,\n  defaultScale,\n  download = true,\n  forwardedRef,\n  highlight,\n  onScaleChange,\n  onScrollProgressChange,\n  onVisiblePageChange,\n  resource,\n  scale: controlledScale,\n  controls = true,\n}: DocxResourceContentProps & {\n  forwardedRef?: React.ForwardedRef<DocxViewerHandle>;\n}) {\n  const docxPreviewPromise = loadDocxPreview();\n  void docxPreviewPromise.catch(() => undefined);\n  const buffer = React.use(\n    getDocxDocumentResource(resource.content, { retainRejected: true }),\n  );\n  const renderCacheKey = resource.content.key;\n  const { registerDocumentSurface, usesShellGeometry } =\n    useOptionalFileViewerRendererEnvironment();\n  const { containerRef, containerWidth } = useMeasuredDocxContainerInlineSize({\n    enabled: !usesShellGeometry,\n  });\n  const rendererFrame = useOptionalFileViewerRendererFrame({\n    fallbackInlineSize: containerWidth,\n  });\n  const layoutInlineSize = resolveFileViewerRendererLayoutInlineSize({\n    fallbackInlineSize: containerWidth,\n    rendererFrame,\n  });\n  const [numPages, setNumPages] = React.useState(0);\n  const [pageWidth, setPageWidth] = React.useState<number | null>(null);\n  const [renderIndex, setRenderIndex] = React.useState<DocxRenderIndex | null>(\n    null,\n  );\n  const [pageLayout, setPageLayout] = React.useState<DocxPageLayout | null>(\n    null,\n  );\n  const [ready, setReady] = React.useState(false);\n  const [renderError, setRenderError] = React.useState<Error | null>(null);\n  if (renderError) throw renderError;\n\n  const { fitWidth, isFitWidth, scale, zoomIn, zoomOut } = useDocxViewerScale({\n    defaultScale,\n    layoutInlineSize,\n    onScaleChange,\n    pageWidth,\n    resetKey: resource.keys.resource,\n    scale: controlledScale,\n  });\n  const zoomMotion = React.useMemo(\n    () => createDocxZoomMotionController({ layout: pageLayout, scale }),\n    [pageLayout, scale],\n  );\n  const {\n    captureZoomIntent,\n    currentPage,\n    handleScroll,\n    measureScroll,\n    resetScroll,\n    scrollViewportRef,\n  } = useDocxViewerScroll({\n    layoutKey: scale,\n    pageLayout,\n    onScrollProgressChange,\n    onVisiblePageChange,\n    ready,\n    scale,\n    zoomMotion,\n  });\n  const isDocumentTransitioning = rendererFrame.phase !== \"idle\";\n  const beginZoomMotion = React.useCallback(() => {\n    // A zoom step mid shell-slide keeps the shell's own anchor solve in\n    // charge; the centered relax only owns quiet-state zooms.\n    if (isDocumentTransitioning) return;\n    captureZoomIntent();\n  }, [captureZoomIntent, isDocumentTransitioning]);\n  const zoomInCentered = React.useCallback(() => {\n    beginZoomMotion();\n    zoomIn();\n  }, [beginZoomMotion, zoomIn]);\n  const zoomOutCentered = React.useCallback(() => {\n    beginZoomMotion();\n    zoomOut();\n  }, [beginZoomMotion, zoomOut]);\n  const fitWidthCentered = React.useCallback(() => {\n    beginZoomMotion();\n    fitWidth();\n  }, [beginZoomMotion, fitWidth]);\n  const scaleRef = React.useRef(scale);\n  scaleRef.current = scale;\n\n  const hostRef = React.useRef<HTMLDivElement | null>(null);\n  const renderIndexRef = React.useRef<DocxRenderIndex | null>(null);\n  const virtualDocumentRef = React.useRef<DocxRenderedDocument | null>(null);\n  const projectVisiblePagesRef = React.useRef<() => void>(() => {});\n  const measureScrollRef = React.useRef(measureScroll);\n  measureScrollRef.current = measureScroll;\n  const transitionWindowReleaseTimerRef = React.useRef<number | null>(null);\n  const shouldReleaseTransitionProjectionRef = React.useRef(false);\n  const projectVisiblePages = React.useCallback(() => {\n    const virtualDocument = virtualDocumentRef.current;\n    const viewport = scrollViewportRef.current;\n    if (!virtualDocument || !viewport) return;\n    projectDocxPages(\n      virtualDocument,\n      createDocxPageWindowFromScroll({\n        layout: virtualDocument.pageLayout,\n        scale: scaleRef.current,\n        scrollTop: viewport.scrollTop,\n        viewportHeight: viewport.clientHeight,\n      }),\n    );\n  }, [scrollViewportRef]);\n  useKeyedLayoutEffect(joinEffectKey([projectVisiblePages]), () => {\n    projectVisiblePagesRef.current = projectVisiblePages;\n  });\n  const clearTransitionWindowReleaseTimer = React.useCallback(() => {\n    if (transitionWindowReleaseTimerRef.current === null) return;\n    window.clearTimeout(transitionWindowReleaseTimerRef.current);\n    transitionWindowReleaseTimerRef.current = null;\n  }, []);\n  const projectTargetPage = React.useCallback(\n    (pageNumber: number) => {\n      const virtualDocument = virtualDocumentRef.current;\n      const viewport = scrollViewportRef.current;\n      if (!virtualDocument || !viewport) return;\n      projectDocxPages(\n        virtualDocument,\n        createDocxPageWindowForPage({\n          layout: virtualDocument.pageLayout,\n          pageIndex: pageNumber - 1,\n          scale: scaleRef.current,\n          viewportHeight: viewport.clientHeight,\n        }),\n      );\n    },\n    [scrollViewportRef],\n  );\n  const handleViewportScroll = React.useCallback(() => {\n    if (!isDocumentTransitioning) projectVisiblePages();\n    handleScroll();\n  }, [handleScroll, isDocumentTransitioning, projectVisiblePages]);\n  // The settled stage box (page + its own p-4 padding). In fit-width this is\n  // exactly the layout width, so the fit-width resolver's affine unit-slope\n  // reprojection hides the slide-start re-fit behind one uniform transform.\n  const stageInlineSize =\n    pageWidth != null ? pageWidth * scale + DOCX_STAGE_INLINE_PADDING_PX : null;\n  const resolveSurfaceMotionStyle =\n    React.useMemo<FileViewerDocumentSurfaceMotionResolver>(\n      () =>\n        createFileViewerFitWidthSurfaceMotionResolver({\n          // The stage centres with auto margins whatever the renderer frame's\n          // align is (a zoomed-out document splits its leftover space evenly),\n          // so the margin model must say \"center\" too.\n          align: \"center\",\n          direction: rendererFrame.direction,\n          isFitWidth,\n          stageInlineSize: stageInlineSize ?? 0,\n          stageInlinePadding: DOCX_STAGE_INLINE_PADDING_PX,\n        }),\n      [isFitWidth, rendererFrame.direction, stageInlineSize],\n    );\n  const documentSurfaceRef = React.useRef<HTMLDivElement | null>(null);\n  const [documentSurfaceElement, setDocumentSurfaceElementState] =\n    React.useState<HTMLDivElement | null>(null);\n  const preMotionAnchorRef = React.useRef<{\n    pageNumber: number;\n    screenRelTop: number;\n  } | null>(null);\n  const lastAnchorBlockRef = React.useRef<number | null>(null);\n  const writeDocxAnchorBlockOffsetPx = React.useCallback(\n    (anchorBlock: number) => {\n      const element = documentSurfaceRef.current;\n      if (!element) return;\n      const safeAnchorBlock = Number.isFinite(anchorBlock) ? anchorBlock : 0;\n      lastAnchorBlockRef.current = safeAnchorBlock;\n      element.style.setProperty(\n        FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY,\n        `${safeAnchorBlock}px`,\n      );\n    },\n    [],\n  );\n  const writeDocxDocumentAnchorBlockOffset = React.useCallback(() => {\n    const viewport = scrollViewportRef.current;\n    if (!viewport) return;\n    writeDocxAnchorBlockOffsetPx(\n      Math.max(0, viewport.scrollTop) +\n        Math.max(0, viewport.clientHeight) * DOCX_READING_MARKER_RATIO,\n    );\n  }, [scrollViewportRef, writeDocxAnchorBlockOffsetPx]);\n  // The transform must pin the exact screen line the slide-start commit\n  // preserved. Measured against the page layout models (intrinsic page tops ×\n  // the old scale at capture, × the new scale at solve), which is exact\n  // across the constant page gap and padding, rebase clamps, and mid-flight\n  // retargets (the capture applies the in-flight transform it was seen under).\n  const writeDocxMotionAnchorBlockOffset = React.useCallback(() => {\n    const viewport = scrollViewportRef.current;\n    const preMotionAnchor = preMotionAnchorRef.current;\n    const page =\n      preMotionAnchor && pageLayout\n        ? (pageLayout.pages[preMotionAnchor.pageNumber - 1] ?? null)\n        : null;\n    const anchorBlock =\n      viewport && preMotionAnchor && page && stageInlineSize != null\n        ? resolveFileViewerFitWidthMotionAnchorBlock({\n            fromInlineSize: rendererFrame.fromInlineSize,\n            probeScreenOffset: preMotionAnchor.screenRelTop,\n            probeStageOffset: DOCX_VIEWER_PADDING_PX + page.top * scale,\n            scrollTop: viewport.scrollTop,\n            stageInlineSize,\n            stageInlinePadding: DOCX_STAGE_INLINE_PADDING_PX,\n            toInlineSize: rendererFrame.toInlineSize,\n          })\n        : null;\n\n    if (anchorBlock == null) {\n      writeDocxDocumentAnchorBlockOffset();\n      return;\n    }\n    writeDocxAnchorBlockOffsetPx(anchorBlock);\n  }, [\n    pageLayout,\n    rendererFrame.fromInlineSize,\n    rendererFrame.toInlineSize,\n    scale,\n    scrollViewportRef,\n    stageInlineSize,\n    writeDocxAnchorBlockOffsetPx,\n    writeDocxDocumentAnchorBlockOffset,\n  ]);\n  const measureBeforeLayoutMotionRef = React.useRef(\n    (_liveFrame: FileViewerMotionFrame | null) => {},\n  );\n  measureBeforeLayoutMotionRef.current = (liveFrame) => {\n    const viewport = scrollViewportRef.current;\n    const page =\n      viewport && pageLayout\n        ? findDocxPageByMarker({\n            layout: pageLayout,\n            scale,\n            scrollTop: viewport.scrollTop,\n            viewportHeight: viewport.clientHeight,\n          })\n        : null;\n    preMotionAnchorRef.current =\n      viewport && page && stageInlineSize != null\n        ? {\n            pageNumber: page.pageNumber,\n            screenRelTop: captureFileViewerFitWidthAnchorScreenOffset({\n              lastAnchorBlock: lastAnchorBlockRef.current,\n              liveFrame,\n              probeStageOffset: DOCX_VIEWER_PADDING_PX + page.top * scale,\n              scrollTop: viewport.scrollTop,\n              stageInlineSize,\n              stageInlinePadding: DOCX_STAGE_INLINE_PADDING_PX,\n            }),\n          }\n        : null;\n    measureScroll();\n  };\n  const handleBeforeLayoutMotion = React.useCallback((event: Event) => {\n    measureBeforeLayoutMotionRef.current(\n      readFileViewerBeforeLayoutMotionFrame(event),\n    );\n  }, []);\n  const setDocumentSurfaceElement = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      const previousElement = documentSurfaceRef.current;\n      if (previousElement === element) return;\n      previousElement?.removeEventListener(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        handleBeforeLayoutMotion,\n      );\n      documentSurfaceRef.current = element;\n      setDocumentSurfaceElementState((previous) =>\n        previous === element ? previous : element,\n      );\n      if (!element) return;\n      element.addEventListener(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        handleBeforeLayoutMotion,\n      );\n      writeDocxDocumentAnchorBlockOffset();\n    },\n    [handleBeforeLayoutMotion, writeDocxDocumentAnchorBlockOffset],\n  );\n  const motionProbePageNumberRef = React.useRef(currentPage);\n  motionProbePageNumberRef.current = currentPage;\n  const getDocxMotionProbeElement = React.useCallback(() => {\n    const surface = documentSurfaceRef.current;\n    if (!surface) return null;\n    const pageNumber =\n      preMotionAnchorRef.current?.pageNumber ??\n      motionProbePageNumberRef.current;\n    return (\n      surface.querySelector<HTMLElement>(\n        `.docx-wrapper > section.docx[data-page-number=\"${pageNumber}\"]`,\n      ) ?? surface.querySelector<HTMLElement>(\".docx-wrapper > section.docx\")\n    );\n  }, []);\n  const documentSurfaceKey = documentSurfaceElement\n    ? joinEffectKey([\n        \"docx-document-surface\",\n        documentSurfaceElement,\n        registerDocumentSurface,\n        resolveSurfaceMotionStyle,\n      ])\n    : null;\n  useKeyedLayoutEffect(documentSurfaceKey, () => {\n    if (!documentSurfaceElement) return;\n    return registerDocumentSurface({\n      element: documentSurfaceElement,\n      getMotionProbeElement: getDocxMotionProbeElement,\n      resolveMotionStyle: resolveSurfaceMotionStyle,\n    });\n  });\n  // Runs inside the slide-start commit after useDocxViewerScroll's layout\n  // effect has restored the reading anchor onto the target layout, pinning\n  // the transform before the first frame paints. Keyed on the transition id\n  // so a mid-flight retarget (same isTransitioning) re-solves against the\n  // new motion.\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      \"docx-anchor-rebase\",\n      rendererFrame.documentTransition.transitionId,\n      rendererFrame.isTransitioning,\n      writeDocxMotionAnchorBlockOffset,\n    ]),\n    () => {\n      if (!rendererFrame.isTransitioning) return;\n      writeDocxMotionAnchorBlockOffset();\n    },\n  );\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\"docx-transition\", isDocumentTransitioning, ready]),\n    () => {\n      clearTransitionWindowReleaseTimer();\n      if (!ready) return;\n\n      if (isDocumentTransitioning) {\n        shouldReleaseTransitionProjectionRef.current = true;\n        return;\n      }\n\n      if (!shouldReleaseTransitionProjectionRef.current) return;\n      transitionWindowReleaseTimerRef.current = window.setTimeout(() => {\n        transitionWindowReleaseTimerRef.current = null;\n        shouldReleaseTransitionProjectionRef.current = false;\n        projectVisiblePagesRef.current();\n        measureScrollRef.current();\n      }, DOCX_TRANSITION_WINDOW_RELEASE_MS);\n    },\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      \"docx-render\",\n      buffer,\n      docxPreviewPromise,\n      renderCacheKey,\n      resetScroll,\n    ]),\n    () => {\n      const host = hostRef.current;\n      if (!host) return;\n      let cancelled = false;\n      setReady(false);\n      setNumPages(0);\n      setRenderIndex(null);\n      setPageLayout(null);\n      renderIndexRef.current = null;\n      virtualDocumentRef.current = null;\n      resetScroll();\n      host.replaceChildren();\n      renderCachedDocxPreview({\n        buffer,\n        cacheKey: renderCacheKey,\n        docxPreviewPromise,\n        getScale: () => scaleRef.current,\n      })\n        .then(({ pageSizes, renderHost }) => {\n          if (cancelled) return;\n          const result = commitDocxRender({\n            host,\n            pageSizes,\n            renderHost,\n            scale: scaleRef.current,\n          });\n          virtualDocumentRef.current = result.virtualDocument;\n          projectVisiblePages();\n          const nextRenderIndex = buildDocxRenderIndex(\n            host,\n            result.virtualDocument.pages,\n          );\n          renderIndexRef.current = nextRenderIndex;\n          setRenderIndex(nextRenderIndex);\n          setNumPages(result.numPages);\n          setPageWidth(result.pageWidth);\n          setPageLayout(result.pageLayout);\n          setReady(true);\n        })\n        .catch((err) => {\n          if (!cancelled) {\n            setRenderError(\n              isResourceError(err) || isAbortError(err)\n                ? err\n                : toDocxFormatError(err, {\n                    kind: \"render_failed\",\n                    message: \"Failed to render DOCX.\",\n                  }),\n            );\n          }\n        });\n      return () => {\n        cancelled = true;\n      };\n    },\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      \"docx-content-measure\",\n      isDocumentTransitioning,\n      measureScroll,\n      ready,\n      scale,\n    ]),\n    () => {\n      if (!ready) return;\n      if (\n        isDocumentTransitioning ||\n        shouldReleaseTransitionProjectionRef.current\n      ) {\n        return;\n      }\n      projectVisiblePages();\n      measureScroll();\n    },\n  );\n\n  useMountEffect(() => () => {\n    clearTransitionWindowReleaseTimer();\n  });\n\n  renderIndexRef.current = renderIndex;\n\n  const highlightName = useDocxHighlight({\n    highlight,\n    renderIndex,\n    ready,\n  });\n  useDocxControlsRegistration({\n    currentPage,\n    download,\n    downloadAction: resource.originalDownload,\n    fitWidth: fitWidthCentered,\n    numPages,\n    ready,\n    scale,\n    zoomIn: zoomInCentered,\n    zoomOut: zoomOutCentered,\n  });\n\n  React.useImperativeHandle(\n    forwardedRef ?? null,\n    () => ({\n      scrollToTarget: (target, options) => {\n        const index = renderIndexRef.current;\n        if (!index) return;\n        const hit = resolveDocxTargetHit(index, target);\n        if (!hit) return;\n        projectTargetPage(hit.pageNumber);\n        const node = hit.startContainer;\n        const el =\n          node?.nodeType === Node.ELEMENT_NODE\n            ? (node as HTMLElement)\n            : (node?.parentElement ?? null);\n        el?.scrollIntoView({\n          block: \"center\",\n          inline: \"nearest\",\n          behavior: options?.behavior ?? \"smooth\",\n          ...options,\n        });\n      },\n      getViewportElement: () => scrollViewportRef.current,\n    }),\n    [projectTargetPage, scrollViewportRef],\n  );\n\n  return (\n    <DocxViewerFrame bare={bare} className={className}>\n      <style>{DOCX_SCOPED_STYLES}</style>\n      <style>{`::highlight(${highlightName}){background-color:color-mix(in oklab, var(--primary) 22%, transparent);}`}</style>\n      {controls ? (\n        ready ? (\n          <ViewerControls\n            position={{\n              kind: \"page\",\n              current: currentPage,\n              total: numPages,\n            }}\n            zoom={{\n              scale,\n              onZoomOut: zoomOutCentered,\n              onZoomIn: zoomInCentered,\n              onFit: fitWidthCentered,\n            }}\n            downloads={\n              download && resource.originalDownload\n                ? [resource.originalDownload]\n                : []\n            }\n          />\n        ) : (\n          <ViewerControlsSkeleton position zoom download={download} />\n        )\n      ) : null}\n      <DocxViewerBody>\n        <ScrollArea\n          className=\"min-h-0 flex-1\"\n          viewportRef={scrollViewportRef}\n          viewportProps={{ onScroll: handleViewportScroll }}\n        >\n          {/* The clip exists for ONE state: a fit-width shell slide, where\n              the kernel's counter-transform paints the surface past its\n              committed box, and that visual overflow would otherwise inflate\n              the scroller's scrollHeight and drag a max-clamped scroll\n              position down frame by frame as the transform relaxes (a 300px\n              in-flight swing at the document end). Every other state must\n              NOT clip: a zoomed-in surface's inline overflow IS the\n              horizontal scroll range (an unconditional clip froze\n              scrollWidth at the viewport width and made zoomed documents\n              horizontally unscrollable), and a zoom relax's enlarged opening\n              frame must not be cut at the committed box. At fit-width the\n              surface fits the layout width, so the active clip can never\n              eat scrollable overflow. */}\n          <div\n            ref={containerRef}\n            className={cn(\n              // Flex column so the stage's block-axis auto margin has free\n              // space to split once a zoomed-out document is shorter than the\n              // pane; a block container would give it none.\n              \"flex min-h-full flex-col\",\n              isDocumentTransitioning && isFitWidth\n                ? \"overflow-clip\"\n                : \"overflow-visible\",\n            )}\n          >\n            {!ready ? (\n              <div className=\"p-4\">\n                <DocxSkeleton />\n              </div>\n            ) : null}\n            {/* The registered document surface is the shrink-wrapped stage box\n                (page + its own padding) so the kernel's fit-width transform\n                scales it about its own laid-out origin. It is a camera view:\n                auto margins split the leftover space evenly on both axes when\n                the zoomed-out page is smaller than the pane, and collapse to 0\n                once it overflows — mirroring the resolver's \"center\" margin\n                model. */}\n            <div\n              ref={setDocumentSurfaceElement}\n              className={cn(\n                \"mx-auto shrink-0 p-4 transition-opacity duration-200\",\n                // Block-axis centring only outside fit-width: at fit-width a\n                // pane resize re-fits the page, and half of that height delta\n                // is motion the shell transform does not model. Zoomed, that\n                // transform is identity and the height is pane-independent.\n                !isFitWidth && \"my-auto\",\n                ready ? \"opacity-100\" : \"opacity-0\",\n              )}\n              style={{ width: stageInlineSize ?? undefined }}\n            >\n              {/* The zoom stage shrink-wraps the page box (width = pageWidth\n                  × scale, exactly linear in scale): it is the inline-anchor\n                  ruler AND the FLIP layer for toolbar zoom steps\n                  (docx-viewer-zoom-motion). The relax transform must not\n                  share an element with the CSS `zoom` below (their coordinate\n                  spaces disagree) nor with the kernel-owned surface above. */}\n              <div data-slot=\"docx-viewer-zoom-stage\">\n                <div ref={hostRef} style={{ zoom: scale }} />\n              </div>\n            </div>\n          </div>\n        </ScrollArea>\n      </DocxViewerBody>\n    </DocxViewerFrame>\n  );\n}\n\nfunction useMeasuredDocxContainerInlineSize({ enabled }: { enabled: boolean }) {\n  const [containerElement, setContainerElement] =\n    React.useState<HTMLDivElement | null>(null);\n  const [containerWidth, setContainerWidth] = React.useState<number | null>(\n    null,\n  );\n\n  useKeyedLayoutEffect(joinEffectKey([containerElement, enabled]), () => {\n    if (!enabled || !containerElement) {\n      if (!enabled) {\n        setContainerWidth((current) => (current == null ? current : null));\n      }\n      return;\n    }\n\n    let frame = 0;\n    let latest = resolveDocxMeasuredInlineSize(containerElement.clientWidth);\n    setContainerWidth(latest);\n    if (typeof ResizeObserver === \"undefined\") return;\n\n    let observer: ResizeObserver | null = null;\n    try {\n      observer = new ResizeObserver((entries) => {\n        for (const entry of entries) {\n          latest = resolveDocxMeasuredInlineSize(\n            (entry.target as HTMLElement).clientWidth,\n          );\n        }\n        if (frame) return;\n        frame = -1;\n        const requestedFrame = requestAnimationFrame(() => {\n          frame = 0;\n          setContainerWidth((current) =>\n            current === latest ? current : latest,\n          );\n        });\n        if (frame === -1) frame = requestedFrame;\n      });\n      observer.observe(containerElement);\n    } catch {\n      if (frame > 0) cancelAnimationFrame(frame);\n      observer?.disconnect();\n      return;\n    }\n\n    return () => {\n      if (frame > 0) cancelAnimationFrame(frame);\n      observer?.disconnect();\n    };\n  });\n\n  return {\n    containerRef: setContainerElement,\n    containerWidth,\n  };\n}\n\nfunction resolveDocxMeasuredInlineSize(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : null;\n}\n\n\nfunction useDocxControlsRegistration({\n  currentPage,\n  download,\n  downloadAction,\n  fitWidth,\n  numPages,\n  ready,\n  scale,\n  zoomIn,\n  zoomOut,\n}: {\n  currentPage: number;\n  download: boolean;\n  downloadAction: NonNullable<ViewerControlsState[\"downloads\"]>[number];\n  fitWidth: () => void;\n  numPages: number;\n  ready: boolean;\n  scale: number;\n  zoomIn: () => void;\n  zoomOut: () => void;\n}) {\n  const onControlsChange = useViewerControlsRegistration();\n  const controlsState = React.useMemo<ViewerControlsState>(\n    () => ({\n      loading: !ready,\n      position: ready\n        ? {\n            kind: \"page\",\n            current: currentPage,\n            total: numPages,\n          }\n        : null,\n      zoom: ready\n        ? {\n            scale,\n            onZoomOut: zoomOut,\n            onZoomIn: zoomIn,\n            onFit: fitWidth,\n          }\n        : null,\n      downloads: download && downloadAction ? [downloadAction] : [],\n    }),\n    [\n      currentPage,\n      download,\n      downloadAction,\n      fitWidth,\n      numPages,\n      ready,\n      scale,\n      zoomIn,\n      zoomOut,\n    ],\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"docx-controls\", onControlsChange, controlsState]),\n    () => {\n      if (!onControlsChange) return;\n      onControlsChange(controlsState);\n      return () => onControlsChange(null);\n    },\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/docx-viewer-content.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-download.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Download } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  ViewerDownloadError,\n  type ViewerDownloadAction,\n  type ViewerDownloadPayload,\n} from \"@/lib/viewer-download-actions\";\n\nimport { Spinner } from \"@/components/ui/spinner\";\n\nimport { Button, buttonVariants } from \"./button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./dropdown-menu\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport interface TriggerViewerDownloadOptions {\n  signal?: AbortSignal;\n}\n\nexport async function triggerViewerDownload(\n  action: ViewerDownloadAction,\n  options?: TriggerViewerDownloadOptions,\n): Promise<void> {\n  if (action.isDisabled) {\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"disabled\",\n      message: \"This download is disabled.\",\n    });\n  }\n\n  let payload: ViewerDownloadPayload;\n  try {\n    payload = await action.getPayload(options);\n  } catch (error) {\n    if (isAbortError(error)) {\n      throw new ViewerDownloadError({\n        actionId: action.id,\n        kind: \"aborted\",\n        message: \"Download was cancelled.\",\n        cause: error,\n      });\n    }\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"payload_failed\",\n      message: \"Could not prepare this download.\",\n      cause: error,\n    });\n  }\n\n  if (payload.kind === \"none\") return;\n  if (payload.kind === \"href\") {\n    try {\n      clickDownload(payload.href, action.fileName);\n    } catch (error) {\n      throw new ViewerDownloadError({\n        actionId: action.id,\n        kind: \"unsupported\",\n        message: \"Could not start this download.\",\n        cause: error,\n      });\n    }\n    return;\n  }\n\n  try {\n    const blob =\n      payload.kind === \"blob\"\n        ? payload.blob\n        : new Blob([payload.text], {\n            type: payload.mimeType ?? \"text/plain;charset=utf-8\",\n          });\n    const url = URL.createObjectURL(blob);\n    try {\n      clickDownload(url, action.fileName);\n    } finally {\n      URL.revokeObjectURL(url);\n    }\n  } catch (error) {\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"unsupported\",\n      message: \"Could not start this download.\",\n      cause: error,\n    });\n  }\n}\n\nexport type ViewerDownloadErrorHandler = (\n  error: ViewerDownloadError,\n  action: ViewerDownloadAction,\n) => void;\n\nexport interface ViewerDownloadTrigger {\n  pendingActionId: string | null;\n  triggerDownload: (action: ViewerDownloadAction) => void;\n}\n\nexport interface ViewerDownloadTriggerOptions {\n  /** Reports non-aborted action failures; visible failure UI belongs to consumers. */\n  onError?: ViewerDownloadErrorHandler;\n  resetKey?: unknown;\n}\n\nexport interface ViewerDownloadControlProps {\n  actions: Array<ViewerDownloadAction | null | undefined>;\n  variant?: React.ComponentProps<typeof Button>[\"variant\"];\n  size?: React.ComponentProps<typeof Button>[\"size\"];\n  className?: string;\n  showLabel?: boolean;\n  /** Reports non-aborted action failures; visible failure UI belongs to consumers. */\n  onError?: ViewerDownloadErrorHandler;\n}\n\nexport interface ViewerDownloadButtonProps\n  extends Omit<ViewerDownloadControlProps, \"actions\"> {\n  action: ViewerDownloadAction | null;\n}\n\nexport interface ViewerDownloadMenuProps\n  extends Omit<ViewerDownloadControlProps, \"actions\"> {\n  actions: ViewerDownloadAction[];\n}\n\nexport function useViewerDownloadHref(\n  action: ViewerDownloadAction | null,\n): string | null {\n  const shouldCreateHref = action?.origin !== \"derived\";\n  const payload = shouldCreateHref ? getSynchronousPayload(action) : null;\n\n  return shouldCreateHref && payload?.kind === \"href\" ? payload.href : null;\n}\n\nexport function useViewerDownloadTrigger({\n  onError,\n  resetKey = \"\",\n}: ViewerDownloadTriggerOptions = {}): ViewerDownloadTrigger {\n  const [pendingActionId, setPendingActionId] = React.useState<string | null>(\n    null,\n  );\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  useKeyedMountEffect(joinEffectKey([resetKey]), () => {\n    return () => {\n      abortControllerRef.current?.abort();\n      abortControllerRef.current = null;\n    };\n  });\n\n  const triggerDownload = React.useCallback(\n    (action: ViewerDownloadAction) => {\n      abortControllerRef.current?.abort();\n      const abortController = new AbortController();\n      abortControllerRef.current = abortController;\n      setPendingActionId(action.id);\n      void triggerViewerDownload(action, { signal: abortController.signal })\n        .catch((error) => {\n          reportDownloadError(error, action, onError);\n        })\n        .finally(() => {\n          if (abortControllerRef.current === abortController) {\n            abortControllerRef.current = null;\n            setPendingActionId(null);\n          }\n        });\n    },\n    [onError],\n  );\n\n  return { pendingActionId, triggerDownload };\n}\n\nexport function ViewerDownloadControl({\n  actions,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadControlProps) {\n  const enabledActions = actions.filter(\n    (action): action is ViewerDownloadAction => Boolean(action),\n  );\n\n  if (enabledActions.length <= 1) {\n    return (\n      <ViewerDownloadButton\n        action={enabledActions[0] ?? null}\n        variant={variant}\n        size={size}\n        className={className}\n        showLabel={showLabel}\n        onError={onError}\n      />\n    );\n  }\n\n  return (\n    <ViewerDownloadMenu\n      actions={enabledActions}\n      variant={variant}\n      size={size}\n      className={className}\n      showLabel={showLabel}\n      onError={onError}\n    />\n  );\n}\n\nexport function ViewerDownloadButton({\n  action,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadButtonProps) {\n  const href = useViewerDownloadHref(action);\n  const label = action?.label ?? \"Download\";\n  const disabled = !action || action.isDisabled;\n  const hasDownloadHref = Boolean(href);\n  const { pendingActionId, triggerDownload } = useViewerDownloadTrigger({\n    onError,\n    resetKey: action,\n  });\n  const isPending = Boolean(action && pendingActionId === action.id);\n\n  const handleClick = React.useCallback(() => {\n    if (!action || hasDownloadHref) return;\n    triggerDownload(action);\n  }, [action, hasDownloadHref, triggerDownload]);\n\n  if (href) {\n    return (\n      <a\n        href={href}\n        download={action?.fileName}\n        className={cn(buttonVariants({ variant, size }), className)}\n        aria-label={label}\n        title={label}\n        data-slot=\"button\"\n      >\n        <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n        {showLabel ? label : null}\n      </a>\n    );\n  }\n\n  return (\n    <Button\n      variant={variant}\n      size={size}\n      className={className}\n      aria-label={label}\n      title={label}\n      disabled={disabled || isPending}\n      onClick={handleClick}\n    >\n      {isPending ? (\n        <Spinner className=\"size-4 animate-spin\" />\n      ) : (\n        <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n      )}\n      {showLabel ? label : null}\n    </Button>\n  );\n}\n\nexport function ViewerDownloadMenu({\n  actions,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadMenuProps) {\n  const actionSetKey = actions.map((action) => action.id).join(\"\\u0000\");\n  const label = \"Download\";\n  const { pendingActionId, triggerDownload } = useViewerDownloadTrigger({\n    onError,\n    resetKey: actionSetKey,\n  });\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant={variant}\n          size={size}\n          className={className}\n          aria-label={label}\n          title={label}\n          disabled={pendingActionId != null}\n        >\n          {pendingActionId != null ? (\n            <Spinner className=\"size-4 animate-spin\" />\n          ) : (\n            <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n          )}\n          {showLabel ? label : null}\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        {actions.map((action) => (\n          <DropdownMenuItem\n            key={action.id}\n            disabled={action.isDisabled || pendingActionId != null}\n            onClick={() => triggerDownload(action)}\n          >\n            <Download />\n            <span>{action.label}</span>\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\nfunction clickDownload(href: string, fileName: string) {\n  const anchor = document.createElement(\"a\");\n  anchor.href = href;\n  anchor.download = fileName;\n  anchor.rel = \"noreferrer\";\n  document.body.appendChild(anchor);\n  anchor.click();\n  anchor.remove();\n}\n\nfunction getSynchronousPayload(\n  action: ViewerDownloadAction | null,\n): ViewerDownloadPayload | null {\n  if (!action || action.isDisabled) return null;\n  const payload = action.getPayload();\n  return payload instanceof Promise ? null : payload;\n}\n\nfunction reportDownloadError(\n  error: unknown,\n  action: ViewerDownloadAction,\n  onError: ViewerDownloadErrorHandler | undefined,\n) {\n  if (!(error instanceof ViewerDownloadError)) return;\n  if (error.kind === \"aborted\") return;\n  onError?.(error, action);\n}\n\nfunction isAbortError(error: unknown) {\n  return error instanceof DOMException && error.name === \"AbortError\";\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-download.tsx"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-source.ts",
      "content": "export type FileCategory =\n  | \"pdf\"\n  | \"docx\"\n  | \"xlsx\"\n  | \"pptx\"\n  | \"csv\"\n  | \"image\"\n  | \"markdown\"\n  | \"html\"\n  | \"email\"\n  | \"text\"\n  | \"unsupported\";\n\nexport type ViewerSource = UrlViewerSource | TextSource | BlobViewerSource;\n\nexport interface UrlViewerSource {\n  kind: \"url\";\n  url: string;\n  fileName?: string;\n  mimeType?: string;\n  downloadUrl?: string;\n  identityKey?: string;\n}\n\nexport interface TextSource {\n  kind: \"text\";\n  text: string;\n  fileName?: string;\n  mimeType?: string;\n  identityKey?: string;\n}\n\nexport interface BlobViewerSource {\n  kind: \"blob\";\n  blob: Blob;\n  identityKey: string;\n  fileName?: string;\n  mimeType?: string;\n  downloadUrl?: string;\n}\n\nexport interface ViewerDescriptor {\n  source: ViewerSource;\n  category: FileCategory;\n  identityKey: string;\n  displayName: string;\n  fileName: string;\n  mimeType?: string;\n}\n\nconst EXTENSION_CATEGORY: Record<string, FileCategory> = {\n  pdf: \"pdf\",\n  docx: \"docx\",\n  xlsx: \"xlsx\",\n  xls: \"xlsx\",\n  xlsm: \"xlsx\",\n  pptx: \"pptx\",\n  csv: \"csv\",\n  tsv: \"csv\",\n  png: \"image\",\n  jpg: \"image\",\n  jpeg: \"image\",\n  gif: \"image\",\n  webp: \"image\",\n  avif: \"image\",\n  bmp: \"image\",\n  svg: \"image\",\n  ico: \"image\",\n  tif: \"image\",\n  tiff: \"image\",\n  md: \"markdown\",\n  markdown: \"markdown\",\n  mdx: \"text\",\n  html: \"html\",\n  htm: \"html\",\n  eml: \"email\",\n  txt: \"text\",\n  text: \"text\",\n  log: \"text\",\n  json: \"text\",\n  jsonl: \"text\",\n  json5: \"text\",\n  ndjson: \"text\",\n  xml: \"text\",\n  yaml: \"text\",\n  yml: \"text\",\n  toml: \"text\",\n  ini: \"text\",\n  env: \"text\",\n  js: \"text\",\n  mjs: \"text\",\n  cjs: \"text\",\n  jsx: \"text\",\n  ts: \"text\",\n  tsx: \"text\",\n  css: \"text\",\n  scss: \"text\",\n  less: \"text\",\n  py: \"text\",\n  rb: \"text\",\n  go: \"text\",\n  rs: \"text\",\n  java: \"text\",\n  kt: \"text\",\n  c: \"text\",\n  h: \"text\",\n  cpp: \"text\",\n  cc: \"text\",\n  cs: \"text\",\n  php: \"text\",\n  sh: \"text\",\n  bash: \"text\",\n  zsh: \"text\",\n  sql: \"text\",\n  graphql: \"text\",\n  proto: \"text\",\n  lua: \"text\",\n  r: \"text\",\n  swift: \"text\",\n  scala: \"text\",\n  pl: \"text\",\n  vue: \"text\",\n  svelte: \"text\",\n};\n\nexport function extensionOf(name: string): string | null {\n  const clean = name.split(/[?#]/)[0];\n  const base = clean.split(\"/\").pop() ?? clean;\n  const dot = base.lastIndexOf(\".\");\n  return dot > 0 ? base.slice(dot + 1).toLowerCase() : null;\n}\n\nexport function extractName(url: string): string {\n  const clean = url.split(/[?#]/)[0];\n  return clean.split(\"/\").pop() || \"file\";\n}\n\nexport function detectCategory(\n  fileName: string,\n  mimeType?: string,\n): FileCategory {\n  const ext = extensionOf(fileName);\n  if (ext && EXTENSION_CATEGORY[ext]) return EXTENSION_CATEGORY[ext];\n  if (mimeType) {\n    const fromMime = categoryFromMime(mimeType);\n    if (fromMime) return fromMime;\n  }\n  return \"unsupported\";\n}\n\nexport function resolveViewerDescriptor({\n  source,\n  category,\n}: {\n  source: ViewerSource;\n  category?: FileCategory;\n}): ViewerDescriptor {\n  const resolvedMimeType =\n    source.mimeType ??\n    (source.kind === \"blob\" && source.blob.type ? source.blob.type : undefined);\n  const displayName = source.fileName ?? defaultDisplayName(source);\n  const fileName = source.fileName ?? defaultFileName(source);\n  const resolvedCategory =\n    category ?? detectCategory(displayName, resolvedMimeType);\n\n  return {\n    source,\n    category: resolvedCategory,\n    identityKey: source.identityKey ?? defaultIdentityKey(source),\n    displayName,\n    fileName,\n    mimeType: resolvedMimeType,\n  };\n}\n\nfunction categoryFromMime(mimeType: string): FileCategory | null {\n  const mime = mimeType.toLowerCase().split(\";\")[0].trim();\n  if (mime === \"application/pdf\") return \"pdf\";\n  if (mime.includes(\"wordprocessingml\")) return \"docx\";\n  if (mime.includes(\"spreadsheet\") || mime.includes(\"ms-excel\")) return \"xlsx\";\n  if (mime.includes(\"presentation\") || mime.includes(\"ms-powerpoint\")) {\n    return \"pptx\";\n  }\n  if (mime === \"text/csv\" || mime === \"text/tab-separated-values\") return \"csv\";\n  if (mime === \"text/markdown\") return \"markdown\";\n  if (mime === \"text/html\") return \"html\";\n  if (mime === \"message/rfc822\" || mime === \"message/global\") {\n    return \"email\";\n  }\n  if (mime.startsWith(\"image/\")) return \"image\";\n  if (mime === \"application/json\" || mime === \"application/xml\") return \"text\";\n  if (mime.startsWith(\"text/\")) return \"text\";\n  return null;\n}\n\nfunction defaultDisplayName(source: ViewerSource) {\n  if (source.kind === \"url\") return source.url;\n  if (source.kind === \"text\") return \"text.txt\";\n  return \"file\";\n}\n\nfunction defaultFileName(source: ViewerSource) {\n  if (source.kind === \"url\") return extractName(source.url);\n  if (source.kind === \"text\") return \"text.txt\";\n  return \"file\";\n}\n\nfunction defaultIdentityKey(source: ViewerSource) {\n  if (source.kind === \"url\") return `url:${source.url}`;\n  if (source.kind === \"text\") return textPayloadIdentityKey(source.text);\n  return source.identityKey;\n}\n\nexport function textPayloadIdentityKey(text: string) {\n  return textPayloadKey(text);\n}\n\nexport function textPayloadKey(text: string) {\n  return `text:${text.length}:${hashString(text)}`;\n}\n\nfunction hashString(text: string) {\n  let hash = 0x811c9dc5;\n  for (let index = 0; index < text.length; index += 1) {\n    hash ^= text.charCodeAt(index);\n    hash = Math.imul(hash, 0x01000193);\n  }\n  return (hash >>> 0).toString(36);\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-source.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-resource.ts",
      "content": "import {\n  createBlobDownloadAction,\n  createHrefDownloadAction,\n  createTextDownloadAction,\n  type ViewerDownloadAction,\n} from \"@/lib/viewer-download-actions\";\nimport {\n  isAbortError,\n  ResourceError,\n  type ResourceTooLargeReason,\n} from \"@/lib/viewer-errors\";\nimport {\n  resolveViewerDescriptor,\n  textPayloadKey,\n  type BlobViewerSource,\n  type FileCategory,\n  type TextSource,\n  type UrlViewerSource,\n  type ViewerDescriptor,\n  type ViewerSource,\n} from \"@/lib/viewer-source\";\n\nexport interface ResourceReadOptions {\n  cache?: RequestCache;\n  signal?: AbortSignal;\n}\n\nexport interface TextReadOptions extends ResourceReadOptions {\n  maxBytes?: number;\n  maxLines?: number;\n}\n\nexport interface ByteRange {\n  start: number;\n  end: number;\n}\n\nexport interface ByteRangeResult {\n  buffer: ArrayBuffer;\n  contentRange?: {\n    start: number;\n    end: number;\n    total: number | null;\n  };\n  isComplete: boolean;\n}\n\nexport interface ViewerResourceKeys {\n  readonly load: string;\n  readonly presentation: string;\n  readonly resource: string;\n}\n\nexport type ViewerResourcePayload =\n  | { kind: \"url\"; url: string }\n  | { kind: \"blob\"; blob: Blob }\n  | { kind: \"text\"; text: string };\n\nexport interface ViewerResourceContent {\n  readonly key: string;\n  readonly sourceKind: ViewerSource[\"kind\"];\n  readonly directUrl: string | null;\n  readonly mimeType?: string;\n  readonly payload: ViewerResourcePayload;\n  readBlob(options?: ResourceReadOptions): Promise<Blob>;\n  readBytes(options?: ResourceReadOptions): Promise<ArrayBuffer>;\n  readText(options?: TextReadOptions): Promise<string>;\n  readStream(\n    options?: ResourceReadOptions,\n  ): Promise<ReadableStream<Uint8Array>>;\n  readRange(\n    range: ByteRange,\n    options?: ResourceReadOptions,\n  ): Promise<ByteRangeResult>;\n}\n\nexport type ViewerContentIdentity = Pick<\n  ViewerResourceContent,\n  \"key\" | \"sourceKind\"\n>;\n\nexport type ViewerContentDirectUrl = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"directUrl\">;\n\nexport type ViewerContentPayload = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"payload\">;\n\nexport type ViewerContentMime = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"mimeType\">;\n\nexport type ViewerContentBlob = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readBlob\">;\n\nexport type ViewerContentBytes = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readBytes\">;\n\nexport type ViewerContentText = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readText\">;\n\nexport type ViewerContentStream = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readStream\">;\n\nexport type ViewerContentRange = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readRange\">;\n\nexport interface ViewerResource {\n  readonly descriptor: ViewerDescriptor;\n  readonly sourceKind: ViewerSource[\"kind\"];\n  readonly keys: ViewerResourceKeys;\n  readonly identityKey: string;\n  readonly fileName: string;\n  readonly mimeType?: string;\n  readonly content: ViewerResourceContent;\n  readonly originalDownload: ViewerDownloadAction;\n}\n\nconst URL_RESOURCE_REGISTRY_MAX = 128;\nconst TEXT_RESOURCE_REGISTRY_MAX = 64;\n// LF, CR, CRLF, LINE SEPARATOR (U+2028), and PARAGRAPH SEPARATOR (U+2029) — the\n// ECMAScript LineTerminator set, matching what a browser breaks on in a\n// `white-space: pre` block. Kept in sync with text-viewer-resource's splitter.\nconst TEXT_LINE_BREAK_PATTERN = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n\nconst urlViewerResourceRegistry = new Map<string, ViewerResource>();\nconst urlViewerResourceContentRegistry = new Map<\n  string,\n  ViewerResourceContent\n>();\nconst textViewerResourceRegistry = new Map<string, ViewerResource>();\nconst textViewerResourceContentRegistry = new Map<\n  string,\n  ViewerResourceContent\n>();\nlet blobViewerResourceRegistry = new WeakMap<\n  Blob,\n  Map<string, ViewerResource>\n>();\nlet blobViewerResourceContentRegistry = new WeakMap<\n  Blob,\n  Map<string, ViewerResourceContent>\n>();\nconst blobObjectKeys = new WeakMap<Blob, string>();\nlet nextBlobObjectKey = 0;\n\nexport function createViewerResource(\n  source: ViewerSource,\n  category?: FileCategory,\n): ViewerResource {\n  const descriptor = resolveViewerDescriptor({ source, category });\n  const keys = viewerResourceKeys(source, descriptor);\n\n  if (source.kind === \"url\") {\n    return internUrlResource(source, descriptor, keys);\n  }\n  if (source.kind === \"blob\") {\n    return internBlobResource(source, descriptor, keys);\n  }\n  return internTextResource(source, descriptor, keys);\n}\n\nexport function clearViewerResourceRegistryForTests() {\n  urlViewerResourceRegistry.clear();\n  urlViewerResourceContentRegistry.clear();\n  textViewerResourceRegistry.clear();\n  textViewerResourceContentRegistry.clear();\n  blobViewerResourceRegistry = new WeakMap();\n  blobViewerResourceContentRegistry = new WeakMap();\n}\n\nfunction internUrlResource(\n  source: UrlViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const cached = urlViewerResourceRegistry.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createUrlResource(source, descriptor, keys);\n  urlViewerResourceRegistry.set(keys.resource, resource);\n  pruneUrlResourceRegistry();\n  return resource;\n}\n\nfunction internBlobResource(\n  source: BlobViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  let resources = blobViewerResourceRegistry.get(source.blob);\n  if (!resources) {\n    resources = new Map();\n    blobViewerResourceRegistry.set(source.blob, resources);\n  }\n\n  const cached = resources.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createBlobResource(source, descriptor, keys);\n  resources.set(keys.resource, resource);\n  return resource;\n}\n\nfunction internTextResource(\n  source: TextSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const cached = textViewerResourceRegistry.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createTextResource(source, descriptor, keys);\n  textViewerResourceRegistry.set(keys.resource, resource);\n  pruneTextResourceRegistry();\n  return resource;\n}\n\nfunction pruneUrlResourceRegistry() {\n  while (urlViewerResourceRegistry.size > URL_RESOURCE_REGISTRY_MAX) {\n    const firstKey = urlViewerResourceRegistry.keys().next().value;\n    if (!firstKey) return;\n    urlViewerResourceRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneUrlResourceContentRegistry() {\n  while (urlViewerResourceContentRegistry.size > URL_RESOURCE_REGISTRY_MAX) {\n    const firstKey = urlViewerResourceContentRegistry.keys().next().value;\n    if (!firstKey) return;\n    urlViewerResourceContentRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneTextResourceRegistry() {\n  while (textViewerResourceRegistry.size > TEXT_RESOURCE_REGISTRY_MAX) {\n    const firstKey = textViewerResourceRegistry.keys().next().value;\n    if (!firstKey) return;\n    textViewerResourceRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneTextResourceContentRegistry() {\n  while (textViewerResourceContentRegistry.size > TEXT_RESOURCE_REGISTRY_MAX) {\n    const firstKey = textViewerResourceContentRegistry.keys().next().value;\n    if (!firstKey) return;\n    textViewerResourceContentRegistry.delete(firstKey);\n  }\n}\n\nfunction viewerResourceKeys(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n): ViewerResourceKeys {\n  const load = viewerResourceLoadKey(source, descriptor);\n  const presentation = viewerResourcePresentationKey(source, descriptor);\n  return {\n    load,\n    presentation,\n    resource: [load, presentation].join(\"\\u0000\"),\n  };\n}\n\nfunction viewerResourceLoadKey(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n) {\n  return [\n    source.kind,\n    source.identityKey ?? \"\",\n    sourceMimeType(source) ?? \"\",\n    directLoadCacheKey(source),\n    payloadCacheKey(source, descriptor),\n  ].join(\"\\u0000\");\n}\n\nfunction viewerResourcePresentationKey(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n) {\n  return [\n    descriptor.category,\n    descriptor.displayName,\n    descriptor.fileName,\n    descriptor.mimeType ?? \"\",\n    downloadCacheKey(source),\n  ].join(\"\\u0000\");\n}\n\nfunction directLoadCacheKey(source: ViewerSource) {\n  return source.kind === \"url\" ? source.url : \"\";\n}\n\nfunction downloadCacheKey(source: ViewerSource) {\n  if (source.kind === \"text\") return \"\";\n  return source.downloadUrl ?? \"\";\n}\n\nfunction payloadCacheKey(source: ViewerSource, descriptor: ViewerDescriptor) {\n  if (source.kind === \"url\") return \"\";\n  if (source.kind === \"blob\") return blobObjectKey(source.blob);\n  return source.identityKey ? \"\" : descriptor.identityKey;\n}\n\nexport function viewerResourceRenderKey(resource: ViewerResource): string {\n  const load = [\n    resource.sourceKind,\n    resource.identityKey,\n    resource.mimeType ?? resource.content.mimeType ?? \"\",\n    resource.content.directUrl ?? \"\",\n    viewerContentRenderKey(resource.content),\n  ].join(\"\\u0000\");\n\n  return [load, resource.keys.presentation].join(\"\\u0000\");\n}\n\nexport function viewerContentRenderKey(content: ViewerResourceContent): string {\n  if (content.payload.kind === \"text\")\n    return textPayloadKey(content.payload.text);\n  return content.key;\n}\n\nfunction sourceMimeType(source: ViewerSource) {\n  if (source.kind === \"blob\") return source.mimeType ?? source.blob.type;\n  return source.mimeType;\n}\n\nfunction blobObjectKey(blob: Blob) {\n  let key = blobObjectKeys.get(blob);\n  if (!key) {\n    nextBlobObjectKey += 1;\n    key = `blob-object:${nextBlobObjectKey}`;\n    blobObjectKeys.set(blob, key);\n  }\n  return key;\n}\n\nexport function blobSource(\n  bytes: Blob | ArrayBuffer | Uint8Array,\n  metadata: {\n    identityKey: string;\n    fileName?: string;\n    mimeType?: string;\n    downloadUrl?: string;\n  },\n): BlobViewerSource {\n  const blob =\n    bytes instanceof Blob\n      ? bytes\n      : new Blob(\n          [bytes instanceof ArrayBuffer ? bytes : new Uint8Array(bytes)],\n          {\n            type: metadata.mimeType ?? \"\",\n          },\n        );\n  return {\n    kind: \"blob\",\n    blob,\n    fileName: metadata.fileName,\n    mimeType: metadata.mimeType ?? blob.type,\n    downloadUrl: metadata.downloadUrl,\n    identityKey: metadata.identityKey,\n  };\n}\n\nfunction createUrlResource(\n  source: UrlViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const content = internUrlResourceContent(source, keys);\n  const originalDownload = createHrefDownloadAction({\n    id: \"download-original\",\n    label: \"Download\",\n    href: source.downloadUrl ?? source.url,\n    fileName: descriptor.fileName,\n  });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internUrlResourceContent(\n  source: UrlViewerSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const cached = urlViewerResourceContentRegistry.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: source.url,\n    payload: { kind: \"url\", url: source.url },\n    readBlob: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      return readResponseBlob(response);\n    },\n    readBytes: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      return readResponseArrayBuffer(response);\n    },\n    readText: async ({ cache, signal, maxBytes, maxLines } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      return readBoundedResponseText(response, { maxBytes, maxLines });\n    },\n    readStream: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      if (!response.body) {\n        if (response.status === 204 || response.status === 205) {\n          return emptyByteStream();\n        }\n        throw new ResourceError({\n          kind: \"unsupported_capability\",\n          message: \"This response cannot be streamed.\",\n        });\n      }\n      return response.body;\n    },\n    readRange: async (range, { cache, signal } = {}) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      const init = {\n        signal,\n        headers: { Range: `bytes=${start}-${end}` },\n      };\n      const response = await fetchResource(\n        source.url,\n        cache ? { ...init, cache } : init,\n      );\n      const buffer = await readResponseArrayBuffer(response);\n      const contentRange = parseContentRange(\n        response.headers.get(\"content-range\"),\n      );\n      validateUrlRangeResponse({\n        bufferLength: buffer.byteLength,\n        contentRange,\n        range,\n        status: response.status,\n      });\n      return {\n        buffer,\n        contentRange,\n        isComplete: isByteRangeComplete({\n          bufferLength: buffer.byteLength,\n          contentRange,\n          requestedLength: end - start + 1,\n          status: response.status,\n        }),\n      };\n    },\n  });\n  urlViewerResourceContentRegistry.set(keys.load, content);\n  pruneUrlResourceContentRegistry();\n  return content;\n}\n\nfunction emptyByteStream() {\n  return new ReadableStream<Uint8Array>({\n    start(controller) {\n      controller.close();\n    },\n  });\n}\n\nfunction createBlobResource(\n  source: BlobViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const blob = source.blob;\n  const content = internBlobResourceContent(source, keys);\n  const originalDownload = source.downloadUrl\n    ? createHrefDownloadAction({\n        id: \"download-original\",\n        label: \"Download\",\n        href: source.downloadUrl,\n        fileName: descriptor.fileName,\n      })\n    : createBlobDownloadAction({\n        id: \"download-original\",\n        label: \"Download\",\n        blob,\n        fileName: descriptor.fileName,\n      });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internBlobResourceContent(\n  source: BlobViewerSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const blob = source.blob;\n  let contents = blobViewerResourceContentRegistry.get(blob);\n  if (!contents) {\n    contents = new Map();\n    blobViewerResourceContentRegistry.set(blob, contents);\n  }\n\n  const cached = contents.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: null,\n    payload: { kind: \"blob\", blob },\n    readBlob: async () => blob,\n    readBytes: async () => blob.arrayBuffer(),\n    readText: async ({ maxBytes, maxLines } = {}) =>\n      readBoundedBlobText(blob, { maxBytes, maxLines }),\n    readStream: async () => blob.stream(),\n    readRange: async (range) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      validateKnownByteRangeStart(start, blob.size);\n      const rangeBlob = blob.slice(start, end + 1);\n      return {\n        buffer: await rangeBlob.arrayBuffer(),\n        contentRange: {\n          start,\n          end: Math.min(end, blob.size - 1),\n          total: blob.size,\n        },\n        isComplete: end >= blob.size - 1,\n      };\n    },\n  });\n  contents.set(keys.load, content);\n  return content;\n}\n\nfunction createTextResource(\n  source: TextSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const content = internTextResourceContent(source, keys);\n  const originalDownload = createTextDownloadAction({\n    id: \"download-original\",\n    label: \"Download\",\n    text: source.text,\n    fileName: descriptor.fileName,\n    mimeType: descriptor.mimeType,\n  });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internTextResourceContent(\n  source: TextSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const cached = textViewerResourceContentRegistry.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: null,\n    payload: { kind: \"text\", text: source.text },\n    readBlob: async () =>\n      new Blob([source.text], {\n        type: \"text/plain;charset=utf-8\",\n      }),\n    readBytes: async () =>\n      typedArrayBuffer(new TextEncoder().encode(source.text)),\n    readText: async ({ maxBytes, maxLines } = {}) =>\n      readBoundedInlineText(source.text, { maxBytes, maxLines }),\n    readStream: async () => new Blob([source.text]).stream(),\n    readRange: async (range) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      const buffer = new TextEncoder().encode(source.text);\n      validateKnownByteRangeStart(start, buffer.byteLength);\n      const slice = buffer.slice(start, end + 1);\n      return {\n        buffer: typedArrayBuffer(slice),\n        contentRange: {\n          start,\n          end: Math.min(end, buffer.byteLength - 1),\n          total: buffer.byteLength,\n        },\n        isComplete: end >= buffer.byteLength - 1,\n      };\n    },\n  });\n  textViewerResourceContentRegistry.set(keys.load, content);\n  pruneTextResourceContentRegistry();\n  return content;\n}\n\nfunction resourceBase(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n  options: {\n    content: ViewerResourceContent;\n    originalDownload: ViewerDownloadAction;\n  },\n): ViewerResource {\n  const { content, originalDownload } = options;\n  return Object.freeze({\n    descriptor,\n    sourceKind: source.kind,\n    keys,\n    identityKey: descriptor.identityKey,\n    fileName: descriptor.fileName,\n    mimeType: descriptor.mimeType,\n    content,\n    originalDownload,\n  });\n}\n\nfunction resourceContentBase(\n  source: ViewerSource,\n  keys: ViewerResourceKeys,\n  methods: Omit<ViewerResourceContent, \"key\" | \"sourceKind\" | \"mimeType\">,\n): ViewerResourceContent {\n  return Object.freeze({\n    key: keys.load,\n    sourceKind: source.kind,\n    mimeType: sourceMimeType(source),\n    ...methods,\n  });\n}\n\nfunction typedArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n  return bytes.buffer.slice(\n    bytes.byteOffset,\n    bytes.byteOffset + bytes.byteLength,\n  ) as ArrayBuffer;\n}\n\nasync function fetchResource(\n  input: RequestInfo | URL,\n  init?: RequestInit,\n): Promise<Response> {\n  let response: Response;\n  try {\n    response = await fetch(input, init);\n  } catch (error) {\n    if (isAbortError(error)) {\n      throw new ResourceError({\n        kind: \"aborted\",\n        message: \"Loading was cancelled.\",\n        cause: error,\n      });\n    }\n    throw new ResourceError({\n      kind: \"fetch_failed\",\n      message: \"Could not fetch this resource.\",\n      cause: error,\n    });\n  }\n\n  if (!response.ok && response.status !== 206) {\n    throw new ResourceError({\n      kind: \"http_error\",\n      message: `Failed to load resource: ${response.status}`,\n      status: response.status,\n    });\n  }\n\n  return response;\n}\n\nasync function readBoundedResponseText(\n  response: Response,\n  bounds: { maxBytes?: number; maxLines?: number },\n) {\n  validateFullContentResponse(response);\n\n  const maxBytes = bounds.maxBytes;\n  if (\n    isContentLengthOverLimit(response.headers.get(\"content-length\"), maxBytes)\n  ) {\n    throw tooLarge(\"bytes\");\n  }\n\n  const body = response.body;\n  if (!body) {\n    const buffer = await readResponseArrayBuffer(response);\n    if (maxBytes != null && buffer.byteLength > maxBytes) {\n      throw tooLarge(\"bytes\");\n    }\n    const text = new TextDecoder().decode(buffer);\n    assertLineLimit(text, bounds.maxLines);\n    return text;\n  }\n\n  const reader = body.getReader();\n  const decoder = new TextDecoder();\n  const lineLimitTracker = createLineLimitTracker(bounds.maxLines);\n  let receivedBytes = 0;\n  let text = \"\";\n\n  while (true) {\n    const { done, value } = await readResponseStreamChunk(reader);\n    if (done) break;\n    receivedBytes += value.byteLength;\n    if (maxBytes != null && receivedBytes > maxBytes) {\n      await cancelReaderSilently(reader);\n      throw tooLarge(\"bytes\");\n    }\n    const chunkText = decoder.decode(value, { stream: true });\n    try {\n      lineLimitTracker.push(chunkText);\n    } catch (error) {\n      await cancelReaderSilently(reader);\n      throw error;\n    }\n    text += chunkText;\n  }\n\n  const finalText = decoder.decode();\n  lineLimitTracker.push(finalText);\n  text += finalText;\n  return text;\n}\n\nfunction isContentLengthOverLimit(\n  contentLength: string | null,\n  maxBytes: number | undefined,\n) {\n  if (maxBytes == null || contentLength == null) return false;\n\n  const normalizedLength = contentLength.trim().replace(/^0+(?=\\d)/, \"\");\n  if (!/^\\d+$/.test(normalizedLength)) return false;\n\n  const maxLength = String(maxBytes);\n  return (\n    normalizedLength.length > maxLength.length ||\n    (normalizedLength.length === maxLength.length &&\n      normalizedLength > maxLength)\n  );\n}\n\nfunction validateFullContentResponse(response: Response) {\n  if (response.status !== 206) return;\n\n  const contentRange = parseContentRange(response.headers.get(\"content-range\"));\n  if (\n    contentRange?.total != null &&\n    contentRange.start === 0 &&\n    contentRange.end === contentRange.total - 1\n  ) {\n    return;\n  }\n\n  throw new ResourceError({\n    kind: \"partial_content\",\n    message: \"Full response returned partial content.\",\n    status: response.status,\n  });\n}\n\nasync function readResponseStreamChunk(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n) {\n  try {\n    return await reader.read();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nasync function readResponseArrayBuffer(response: Response) {\n  try {\n    return await response.arrayBuffer();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nasync function readResponseBlob(response: Response) {\n  try {\n    return await response.blob();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nfunction resourceReadError(error: unknown) {\n  if (isAbortError(error)) {\n    return new ResourceError({\n      kind: \"aborted\",\n      message: \"Loading was cancelled.\",\n      cause: error,\n    });\n  }\n  return new ResourceError({\n    kind: \"fetch_failed\",\n    message: \"Could not read this resource.\",\n    cause: error,\n  });\n}\n\nasync function readBoundedBlobText(\n  blob: Blob,\n  bounds: { maxBytes?: number; maxLines?: number },\n) {\n  if (bounds.maxBytes != null && blob.size > bounds.maxBytes) {\n    throw tooLarge(\"bytes\");\n  }\n  const text = await blob.text();\n  assertLineLimit(text, bounds.maxLines);\n  return text;\n}\n\nfunction readBoundedInlineText(\n  text: string,\n  { maxBytes, maxLines }: { maxBytes?: number; maxLines?: number },\n) {\n  // For inline sources the string *is* the resource, so its UTF-8 byte length\n  // is the authoritative size to measure against maxBytes.\n  if (\n    maxBytes != null &&\n    new TextEncoder().encode(text).byteLength > maxBytes\n  ) {\n    throw tooLarge(\"bytes\");\n  }\n  assertLineLimit(text, maxLines);\n  return text;\n}\n\n// Used after a transferred-byte check has already enforced maxBytes (URL/blob).\n// Re-encoding the decoded text here would double-count: invalid UTF-8 decodes to\n// U+FFFD (3 bytes each), inflating the measured size past the real wire bytes\n// and falsely rejecting small resources as \"too large\".\nfunction assertLineLimit(text: string, maxLines: number | undefined) {\n  if (\n    maxLines != null &&\n    text.split(TEXT_LINE_BREAK_PATTERN).length > maxLines\n  ) {\n    throw tooLarge(\"lines\");\n  }\n}\n\nfunction tooLarge(reason: ResourceTooLargeReason) {\n  return new ResourceError({\n    kind: \"too_large\",\n    tooLargeReason: reason,\n    message: `Resource exceeds ${reason} limit.`,\n  });\n}\n\nasync function cancelReaderSilently(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n) {\n  try {\n    await reader.cancel();\n  } catch {\n    // Preserve the user-facing load failure; cancellation is best-effort cleanup.\n  }\n}\n\nfunction validateByteRange({ start, end }: ByteRange) {\n  if (\n    !Number.isSafeInteger(start) ||\n    !Number.isSafeInteger(end) ||\n    start < 0 ||\n    end < start\n  ) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Byte range must use non-negative integer bounds.\",\n    });\n  }\n}\n\nfunction validateKnownByteRangeStart(start: number, total: number) {\n  if (start > 0 && start >= total) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Byte range starts past the available resource.\",\n    });\n  }\n}\n\nfunction validateUrlRangeResponse({\n  bufferLength,\n  contentRange,\n  range,\n  status,\n}: {\n  bufferLength: number;\n  contentRange: ByteRangeResult[\"contentRange\"];\n  range: ByteRange;\n  status: number;\n}) {\n  if (status === 200) {\n    if (range.start !== 0 || bufferLength > range.end - range.start + 1) {\n      throw new ResourceError({\n        kind: \"invalid_range\",\n        message: \"Full response does not match the requested byte range.\",\n      });\n    }\n    return;\n  }\n  if (status !== 206) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Range response must return full or partial content.\",\n    });\n  }\n  if (!contentRange) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Partial content response is missing a valid byte range.\",\n    });\n  }\n  const declaredLength = contentRange.end - contentRange.start + 1;\n  if (\n    contentRange.start !== range.start ||\n    contentRange.end < contentRange.start ||\n    contentRange.end > range.end ||\n    (contentRange.total != null && contentRange.end >= contentRange.total) ||\n    declaredLength !== bufferLength\n  ) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Response byte range does not match the requested range.\",\n    });\n  }\n}\n\nfunction isByteRangeComplete({\n  bufferLength,\n  contentRange,\n  requestedLength,\n  status,\n}: {\n  bufferLength: number;\n  contentRange: ByteRangeResult[\"contentRange\"];\n  requestedLength: number;\n  status: number;\n}) {\n  if (status === 200) return true;\n  if (contentRange?.total != null) {\n    if (contentRange.total <= 0) return true;\n    return contentRange.end >= contentRange.total - 1;\n  }\n  if (contentRange) return false;\n  return bufferLength < requestedLength;\n}\n\nfunction isStandaloneLineBreak(character: string) {\n  const code = character.charCodeAt(0);\n  return code === 0x0a || code === 0x2028 || code === 0x2029;\n}\n\nfunction createLineLimitTracker(maxLines: number | undefined) {\n  let lineCount = 1;\n  let previousWasCR = false;\n\n  return {\n    push(text: string) {\n      if (maxLines == null || text.length === 0) return;\n\n      for (const character of text) {\n        if (previousWasCR) {\n          previousWasCR = false;\n          if (character === \"\\n\") continue;\n        }\n\n        if (character === \"\\r\") {\n          lineCount += 1;\n          previousWasCR = true;\n        } else if (isStandaloneLineBreak(character)) {\n          // LF, plus LINE/PARAGRAPH SEPARATOR (U+2028/U+2029); none pair with CR.\n          lineCount += 1;\n        }\n\n        if (lineCount > maxLines) {\n          throw tooLarge(\"lines\");\n        }\n      }\n    },\n  };\n}\n\nfunction parseContentRange(value: string | null) {\n  if (!value) return undefined;\n  const match = value.match(/^bytes\\s+(\\d+)-(\\d+)\\/(\\d+|\\*)\\s*$/i);\n  if (!match) return undefined;\n  const start = parseContentRangeNumber(match[1]);\n  const end = parseContentRangeNumber(match[2]);\n  const total =\n    match[3] === \"*\" ? null : parseContentRangeNumber(match[3] ?? \"\");\n  if (start == null || end == null || total === undefined) return undefined;\n  return {\n    start,\n    end,\n    total,\n  };\n}\n\nfunction parseContentRangeNumber(value: string) {\n  const number = Number(value);\n  return Number.isSafeInteger(number) ? number : undefined;\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-resource.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-download-actions.ts",
      "content": "export type ViewerDownloadOrigin = \"original\" | \"derived\";\n\nexport type ViewerDownloadPayload =\n  | { kind: \"href\"; href: string }\n  | { kind: \"blob\"; blob: Blob }\n  | { kind: \"text\"; text: string; mimeType?: string }\n  | { kind: \"none\" };\n\nexport interface ViewerDownloadAction {\n  id: string;\n  label: string;\n  fileName: string;\n  origin: ViewerDownloadOrigin;\n  isDisabled?: boolean;\n  getPayload: (options?: {\n    signal?: AbortSignal;\n  }) => ViewerDownloadPayload | Promise<ViewerDownloadPayload>;\n}\n\nexport type ViewerDownloadErrorKind =\n  | \"disabled\"\n  | \"aborted\"\n  | \"payload_failed\"\n  | \"unsupported\";\n\nexport class ViewerDownloadError extends Error {\n  readonly kind: ViewerDownloadErrorKind;\n  readonly actionId: string;\n  override readonly cause?: unknown;\n\n  constructor({\n    actionId,\n    kind,\n    message,\n    cause,\n  }: {\n    actionId: string;\n    kind: ViewerDownloadErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerDownloadError\";\n    this.actionId = actionId;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport function createHrefDownloadAction({\n  id,\n  label = \"Download\",\n  href,\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  href: string;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"href\", href }),\n  };\n}\n\nexport function createBlobDownloadAction({\n  id,\n  label = \"Download\",\n  blob,\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  blob: Blob;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"blob\", blob }),\n  };\n}\n\nexport function createTextDownloadAction({\n  id,\n  label = \"Download\",\n  text,\n  fileName,\n  mimeType,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  text: string;\n  fileName: string;\n  mimeType?: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"text\", text, mimeType }),\n  };\n}\n\nexport function createDisabledDownloadAction({\n  id,\n  label = \"Download\",\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    isDisabled: true,\n    getPayload: () => ({ kind: \"none\" }),\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-download-actions.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-errors.ts",
      "content": "export type ViewerFormat =\n  | \"pdf\"\n  | \"image\"\n  | \"text\"\n  | \"csv\"\n  | \"docx\"\n  | \"xlsx\"\n  | \"pptx\"\n  | \"file\";\n\nexport type ViewerErrorDomain =\n  | \"resource\"\n  | \"format\"\n  | \"state\"\n  | \"unsupported\"\n  | \"unknown\";\n\nexport type ResourceErrorKind =\n  | \"fetch_failed\"\n  | \"http_error\"\n  | \"aborted\"\n  | \"invalid_range\"\n  | \"partial_content\"\n  | \"too_large\"\n  | \"unsupported_capability\"\n  | \"unknown\";\n\nexport type ResourceTooLargeReason = \"bytes\" | \"lines\";\n\nexport class ResourceError extends Error {\n  readonly domain = \"resource\";\n  readonly kind: ResourceErrorKind;\n  readonly status?: number;\n  readonly tooLargeReason?: ResourceTooLargeReason;\n  override readonly cause?: unknown;\n\n  constructor({\n    kind,\n    message,\n    status,\n    tooLargeReason,\n    cause,\n  }: {\n    kind: ResourceErrorKind;\n    message: string;\n    status?: number;\n    tooLargeReason?: ResourceTooLargeReason;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ResourceError\";\n    this.kind = kind;\n    this.status = status;\n    this.tooLargeReason = tooLargeReason;\n    this.cause = cause;\n  }\n}\n\nexport type ViewerFormatErrorKind =\n  | \"bounds\"\n  | \"decode_failed\"\n  | \"disposed\"\n  | \"index_out_of_range\"\n  | \"load_failed\"\n  | \"parse_failed\"\n  | \"render_failed\"\n  | \"worker_failed\"\n  | \"unknown\";\n\nexport interface ViewerFormatErrorMapperOptions {\n  kind: ViewerFormatErrorKind;\n  message: string;\n}\n\nexport class ViewerFormatError extends Error {\n  readonly domain = \"format\";\n  readonly format: ViewerFormat;\n  readonly kind: ViewerFormatErrorKind;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    kind,\n    message,\n    cause,\n  }: {\n    format: ViewerFormat;\n    kind: ViewerFormatErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerFormatError\";\n    this.format = format;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport type ViewerStateErrorKind =\n  | \"invalid_bounds\"\n  | \"invalid_target\"\n  | \"out_of_range\"\n  | \"stale_resource\"\n  | \"unknown\";\n\nexport class ViewerStateError extends Error {\n  readonly domain = \"state\";\n  readonly format?: ViewerFormat;\n  readonly kind: ViewerStateErrorKind;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    kind,\n    message,\n    cause,\n  }: {\n    format?: ViewerFormat;\n    kind: ViewerStateErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerStateError\";\n    this.format = format;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport class ViewerUnsupportedError extends Error {\n  readonly domain = \"unsupported\";\n  readonly format?: ViewerFormat;\n  readonly sourceKind?: string;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    sourceKind,\n    message,\n    cause,\n  }: {\n    format?: ViewerFormat;\n    sourceKind?: string;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerUnsupportedError\";\n    this.format = format;\n    this.sourceKind = sourceKind;\n    this.cause = cause;\n  }\n}\n\nexport interface ViewerErrorInfo {\n  domain: ViewerErrorDomain;\n  format?: ViewerFormat;\n  kind: string;\n  message: string;\n  status?: number;\n  isRetryable: boolean;\n  isDownloadUseful: boolean;\n  userMessage: string;\n  cause?: unknown;\n}\n\nexport interface ViewerErrorContext {\n  format?: ViewerFormat;\n  sourceKind?: \"url\" | \"blob\" | \"text\";\n  canDownload?: boolean;\n  retry?: \"auto\" | \"always\" | \"never\";\n}\n\nexport function isAbortError(error: unknown): boolean {\n  return (\n    (error instanceof DOMException && error.name === \"AbortError\") ||\n    (error instanceof Error && error.name === \"AbortError\")\n  );\n}\n\nexport function isResourceError(error: unknown): error is ResourceError {\n  return (\n    error instanceof ResourceError ||\n    isErrorLike(error, \"ResourceError\", \"resource\")\n  );\n}\n\nexport function isViewerFormatError(\n  error: unknown,\n): error is ViewerFormatError {\n  return (\n    error instanceof ViewerFormatError ||\n    isErrorLike(error, \"ViewerFormatError\", \"format\")\n  );\n}\n\nexport function isViewerStateError(error: unknown): error is ViewerStateError {\n  return (\n    error instanceof ViewerStateError ||\n    isErrorLike(error, \"ViewerStateError\", \"state\")\n  );\n}\n\nexport function isViewerUnsupportedError(\n  error: unknown,\n): error is ViewerUnsupportedError {\n  return (\n    error instanceof ViewerUnsupportedError ||\n    isErrorLike(error, \"ViewerUnsupportedError\", \"unsupported\")\n  );\n}\n\nexport function toViewerErrorInfo(\n  error: unknown,\n  context: ViewerErrorContext = {},\n): ViewerErrorInfo {\n  const canDownload = context.canDownload ?? true;\n\n  if (isResourceError(error)) {\n    return {\n      domain: \"resource\",\n      format: context.format,\n      kind: error.kind,\n      message: error.message,\n      status: error.status,\n      isRetryable: retryable(\n        context,\n        resourceErrorDefaultRetry(error, context),\n      ),\n      isDownloadUseful: canDownload && error.kind !== \"aborted\",\n      userMessage: resourceErrorUserMessage(error),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerFormatError(error)) {\n    const format = error.format ?? context.format;\n    return {\n      domain: \"format\",\n      format,\n      kind: error.kind,\n      message: error.message,\n      isRetryable: retryable(\n        context,\n        formatErrorDefaultRetry(error, context, format),\n      ),\n      isDownloadUseful: canDownload,\n      userMessage: formatErrorUserMessage(format, error.kind, error),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerStateError(error)) {\n    return {\n      domain: \"state\",\n      format: error.format ?? context.format,\n      kind: error.kind,\n      message: error.message,\n      isRetryable: retryable(context, false),\n      isDownloadUseful: canDownload,\n      userMessage: stateErrorUserMessage(error.kind),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerUnsupportedError(error)) {\n    return {\n      domain: \"unsupported\",\n      format: error.format ?? context.format,\n      kind: \"unsupported\",\n      message: error.message,\n      isRetryable: retryable(context, false),\n      isDownloadUseful: canDownload,\n      userMessage: \"This file cannot be previewed here.\",\n      cause: error.cause,\n    };\n  }\n\n  if (isAbortError(error)) {\n    return {\n      domain: \"resource\",\n      format: context.format,\n      kind: \"aborted\",\n      message: \"Loading was cancelled.\",\n      isRetryable: retryable(context, false),\n      isDownloadUseful: false,\n      userMessage: \"Loading was cancelled.\",\n      cause: error,\n    };\n  }\n\n  const message = error instanceof Error ? error.message : String(error);\n  return {\n    domain: \"unknown\",\n    format: context.format,\n    kind: \"unknown\",\n    message,\n    isRetryable: retryable(context, unknownErrorDefaultRetry(context)),\n    isDownloadUseful: canDownload,\n    userMessage: fallbackUserMessage(context.format),\n    cause: error,\n  };\n}\n\nfunction isErrorLike(error: unknown, name: string, domain: ViewerErrorDomain) {\n  if (!error || typeof error !== \"object\") return false;\n  const candidate = error as {\n    name?: unknown;\n    domain?: unknown;\n    kind?: unknown;\n  };\n  return (\n    (candidate.name === name || candidate.domain === domain) &&\n    typeof candidate.kind === \"string\"\n  );\n}\n\nfunction retryable(context: ViewerErrorContext, fallback: boolean) {\n  if (context.retry === \"always\") return true;\n  if (context.retry === \"never\") return false;\n  return fallback;\n}\n\nfunction resourceErrorDefaultRetry(\n  error: ResourceError,\n  context: ViewerErrorContext,\n) {\n  if (error.kind === \"aborted\") return false;\n  if (error.kind === \"invalid_range\") return false;\n  if (error.kind === \"too_large\") return false;\n  if (error.kind === \"unsupported_capability\") return false;\n  return context.sourceKind === \"url\";\n}\n\nfunction formatErrorDefaultRetry(\n  error: ViewerFormatError,\n  context: ViewerErrorContext,\n  format: ViewerFormat | undefined,\n) {\n  if (format === \"text\" && error.kind === \"bounds\") return false;\n  if (error.kind === \"disposed\") return false;\n  if (error.kind === \"index_out_of_range\") return false;\n  if (format === \"docx\") return true;\n  return context.sourceKind === \"url\";\n}\n\nfunction unknownErrorDefaultRetry(context: ViewerErrorContext) {\n  if (context.format === \"docx\") return true;\n  return context.sourceKind === \"url\";\n}\n\nfunction resourceErrorUserMessage(error: ResourceError) {\n  if (error.kind === \"http_error\") {\n    return error.status\n      ? `Failed to load file: ${error.status}.`\n      : \"Couldn't load this file.\";\n  }\n  if (error.kind === \"fetch_failed\") return \"Couldn't load this file.\";\n  if (error.kind === \"aborted\") return \"Loading was cancelled.\";\n  if (error.kind === \"invalid_range\") return \"This source range is invalid.\";\n  if (error.kind === \"too_large\") {\n    return error.tooLargeReason === \"lines\"\n      ? \"This file has too many lines to preview.\"\n      : \"This file is too large to preview.\";\n  }\n  if (error.kind === \"partial_content\") {\n    return \"This source returned partial content and cannot be previewed here.\";\n  }\n  if (error.kind === \"unsupported_capability\") {\n    return \"This source cannot be previewed here.\";\n  }\n  return \"Couldn't load this file.\";\n}\n\nfunction formatErrorUserMessage(\n  format: ViewerFormat | undefined,\n  kind: string,\n  error?: unknown,\n) {\n  if (format === \"pdf\") return \"Couldn't load this PDF.\";\n  if (format === \"image\") {\n    if (kind === \"index_out_of_range\")\n      return \"This image page is out of range.\";\n    if (kind === \"decode_failed\") return \"Couldn't decode this image.\";\n    return \"Couldn't load this image.\";\n  }\n  if (format === \"text\") {\n    if (kind === \"render_failed\") return \"Couldn't render this text file.\";\n    if (kind === \"bounds\") {\n      const boundsError = error as {\n        reason?: unknown;\n        boundName?: unknown;\n      };\n      if (boundsError.reason === \"lines\") {\n        return \"This text file has too many lines to preview.\";\n      }\n      if (boundsError.reason === \"bytes\") {\n        return \"This text file is too large to preview.\";\n      }\n      if (typeof boundsError.boundName === \"string\") {\n        return \"Text viewer bounds are invalid.\";\n      }\n    }\n    return \"Couldn't load this text file.\";\n  }\n  if (format === \"csv\") return \"Couldn't parse this table.\";\n  if (format === \"docx\") return \"Couldn't render this document.\";\n  if (format === \"xlsx\") return \"Couldn't parse this spreadsheet.\";\n  if (format === \"pptx\") {\n    if (kind === \"render_failed\") return \"Couldn't render this slide.\";\n    return \"Couldn't load this presentation.\";\n  }\n  return \"Couldn't load this file.\";\n}\n\nfunction stateErrorUserMessage(kind: ViewerStateErrorKind) {\n  if (kind === \"invalid_bounds\") return \"Viewer bounds are invalid.\";\n  if (kind === \"invalid_target\") return \"The requested target is invalid.\";\n  if (kind === \"out_of_range\") return \"The requested item is out of range.\";\n  if (kind === \"stale_resource\") return \"This viewer state is no longer valid.\";\n  return \"Couldn't load this file.\";\n}\n\nfunction fallbackUserMessage(format: ViewerFormat | undefined) {\n  if (format === \"pdf\") return \"Couldn't load this PDF.\";\n  if (format === \"image\") return \"Couldn't load this image.\";\n  if (format === \"text\") return \"Couldn't load this text file.\";\n  if (format === \"csv\") return \"Couldn't parse this table.\";\n  if (format === \"docx\") return \"Couldn't load this document.\";\n  if (format === \"xlsx\") return \"Couldn't load this spreadsheet.\";\n  if (format === \"pptx\") return \"Couldn't load this presentation.\";\n  return \"Couldn't load this file.\";\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-errors.ts"
    },
    {
      "path": "registry/new-york-v4/ui/use-is-client.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nconst emptySubscribe = () => () => {};\nconst getClientSnapshot = () => true;\nconst getServerSnapshot = () => false;\n\n/**\n * SSR gate: `false` on the server (and during hydration's first pass),\n * `true` on the client.\n *\n * This must stay a synchronous external-store read, NOT the\n * `useState(false)` + mount-effect flip. The flip pattern makes every\n * viewer mount its Suspense boundary in a later update; when two such\n * boundaries suspend on pending resources in the same flush as other\n * commit-phase updates (viewer sidebar/geometry registration), React 19's\n * retry lanes desynchronize and re-attempt each other's boundary on every\n * commit — an unbounded synchronous suspend/retry loop that starves the\n * event loop (jsdom tests OOM; browsers busy-spin until the resource\n * resolves). With the store read, client renders suspend on mount, which\n * never enters that loop. Regression-guarded in\n * tests/pdf-viewer-thumbnails.test.tsx (\"shares one document resource…\").\n */\nexport function useIsClient(): boolean {\n  return React.useSyncExternalStore(\n    emptySubscribe,\n    getClientSnapshot,\n    getServerSnapshot,\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/use-is-client.ts"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-error.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { RotateCcw } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\nimport {\n  toViewerErrorInfo,\n  type ViewerErrorContext,\n  type ViewerFormat,\n} from \"@/lib/viewer-errors\";\n\nimport { Button } from \"./button\";\nimport { ViewerDownloadButton } from \"./viewer-download\";\n\nexport interface ViewerErrorStateProps extends ViewerErrorContext {\n  error: unknown;\n  download?: ViewerDownloadAction | null;\n  className?: string;\n  bare?: boolean;\n  variant?: \"card\" | \"document\" | \"inline\";\n  onRetry?: () => void;\n}\n\nexport function ViewerErrorState({\n  error,\n  format,\n  sourceKind,\n  canDownload,\n  retry,\n  download,\n  className,\n  bare = false,\n  variant = \"card\",\n  onRetry,\n}: ViewerErrorStateProps) {\n  const info = toViewerErrorInfo(error, {\n    format,\n    sourceKind,\n    canDownload: canDownload ?? Boolean(download && !download.isDisabled),\n    retry,\n  });\n  const showRetry = info.isRetryable && onRetry;\n  const showDownload =\n    info.isDownloadUseful && download != null && !download.isDisabled;\n\n  return (\n    <div\n      className={cn(errorStateClassName({ bare, variant }), className)}\n      data-error-domain={info.domain}\n      data-error-format={info.format}\n      data-error-kind={info.kind}\n      data-error-message={info.message}\n      data-slot=\"viewer-error\"\n      role=\"alert\"\n    >\n      <p>{info.userMessage}</p>\n      {showRetry || showDownload ? (\n        <div className=\"flex items-center gap-2\">\n          {showRetry ? (\n            <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n              <RotateCcw className=\"mr-1.5 size-4\" />\n              Retry\n            </Button>\n          ) : null}\n          {showDownload ? (\n            <ViewerDownloadButton\n              action={download}\n              variant={showRetry ? \"ghost\" : \"outline\"}\n              size=\"sm\"\n              className=\"\"\n              showLabel\n            />\n          ) : null}\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\nexport interface ViewerErrorBoundaryProps extends ViewerErrorContext {\n  children: React.ReactNode;\n  resetKey?: unknown;\n  download?: ViewerDownloadAction | null;\n  className?: string;\n  bare?: boolean;\n  variant?: \"card\" | \"document\" | \"inline\";\n  mapError?: (error: unknown) => unknown;\n  onRetry?: (error: unknown) => void;\n  onCaughtError?: (error: unknown, errorInfo: React.ErrorInfo) => void;\n}\n\nexport class ViewerErrorBoundary extends React.Component<\n  ViewerErrorBoundaryProps,\n  { error: unknown | null; retryKey: number }\n> {\n  state: Readonly<{ error: unknown | null; retryKey: number }> = {\n    error: null,\n    retryKey: 0,\n  };\n\n  componentDidUpdate(previousProps: ViewerErrorBoundaryProps) {\n    if (\n      previousProps.resetKey !== this.props.resetKey &&\n      this.state.error != null\n    ) {\n      this.setState({ error: null });\n    }\n  }\n\n  static getDerivedStateFromError(error: unknown) {\n    return { error };\n  }\n\n  componentDidCatch(error: unknown, errorInfo: React.ErrorInfo) {\n    this.props.onCaughtError?.(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.error != null) {\n      const error = this.props.mapError\n        ? this.props.mapError(this.state.error)\n        : this.state.error;\n\n      return (\n        <ViewerErrorState\n          error={error}\n          format={this.props.format}\n          sourceKind={this.props.sourceKind}\n          canDownload={this.props.canDownload}\n          retry={this.props.retry}\n          download={this.props.download}\n          className={this.props.className}\n          bare={this.props.bare}\n          variant={this.props.variant}\n          onRetry={() => {\n            this.setState((state) => ({\n              error: null,\n              retryKey: state.retryKey + 1,\n            }));\n            this.props.onRetry?.(this.state.error);\n          }}\n        />\n      );\n    }\n\n    return (\n      <React.Fragment key={this.state.retryKey}>\n        {this.props.children}\n      </React.Fragment>\n    );\n  }\n}\n\nfunction errorStateClassName({\n  bare,\n  variant,\n}: {\n  bare: boolean;\n  variant: \"card\" | \"document\" | \"inline\";\n}) {\n  if (variant === \"inline\") {\n    return \"flex h-24 flex-col items-center justify-center gap-3 px-3 text-center text-xs text-muted-foreground\";\n  }\n  return cn(\n    \"flex min-h-64 flex-col items-center justify-center gap-3 p-6 text-center text-sm text-muted-foreground\",\n    bare ? \"bg-muted/20\" : \"rounded-xl border bg-muted/30\",\n    variant === \"document\" && \"min-h-full\",\n  );\n}\n\nexport type { ViewerFormat };\n",
      "type": "registry:ui",
      "target": "@ui/viewer-error.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/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/viewer-lru-cache.ts",
      "content": "export const VIEWER_LRU_CACHE_MAX = 12;\n\nexport function lruGet<K, V>(map: Map<K, V>, key: K): V | undefined {\n  const value = map.get(key);\n  if (value !== undefined) {\n    map.delete(key);\n    map.set(key, value);\n  }\n  return value;\n}\n\nexport function lruSet<K, V>(\n  map: Map<K, V>,\n  key: K,\n  value: V,\n  onEvict?: (key: K, value: V) => void,\n  max = VIEWER_LRU_CACHE_MAX,\n) {\n  map.delete(key);\n  map.set(key, value);\n  while (map.size > max) {\n    const oldest = map.keys().next().value as K | undefined;\n    if (oldest === undefined) break;\n    const dropped = map.get(oldest);\n    map.delete(oldest);\n    if (dropped !== undefined) onEvict?.(oldest, dropped);\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-lru-cache.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-context.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { FileCategory } from \"@/lib/viewer-source\";\nimport type { FileViewerElementRegistry } from \"./file-viewer-elements\";\nimport type { FileViewerMotionKernel } from \"./file-viewer-motion-kernel\";\n\nexport type FileViewerSidebarMode = \"inline\" | \"overlay\";\nexport type FileViewerSidebarRequestedMode = \"auto\" | FileViewerSidebarMode;\nexport type FileViewerSidebarState = \"expanded\" | \"collapsed\";\nexport type FileViewerSidebarSide = \"left\" | \"right\";\nexport type FileViewerSidebarCollapsible = \"offcanvas\" | \"none\";\n\nexport type FileViewerHeaderMode = \"inline\" | \"outlets\";\n\nexport const DEFAULT_FILE_VIEWER_SIDEBAR_WIDTH = \"10rem\";\n\nexport type FileViewerSetSidebarOpen = (\n  value: boolean | ((isSidebarOpen: boolean) => boolean),\n) => void;\n\nexport type FileViewerSidebarOpenProps = {\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n};\n\nexport type FileViewerContextValue = {\n  headerMode: FileViewerHeaderMode;\n  hasHeaderOutlets: boolean;\n  isInsideFileViewer: boolean;\n  resourceCategory: FileCategory;\n  sidebarOpenProps: FileViewerSidebarOpenProps;\n};\n\nexport type FileViewerSidebarRegistration = {\n  collapsible: FileViewerSidebarCollapsible;\n  id: string;\n  side: FileViewerSidebarSide;\n  width: string;\n  widthPixels: number;\n};\n\nexport type FileViewerSidebarValue = {\n  canToggleSidebar: boolean;\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  mode: FileViewerSidebarMode;\n  side: FileViewerSidebarSide;\n  sidebarId: string;\n  sidebarState: FileViewerSidebarState;\n  setSidebarOpen: FileViewerSetSidebarOpen;\n  toggleSidebar: () => void;\n};\n\nexport type FileViewerShellStaticContextValue = {\n  canToggleSidebar: boolean;\n  collapsible: FileViewerSidebarCollapsible;\n  elementRegistry: FileViewerElementRegistry;\n  mode: FileViewerSidebarMode;\n  motionDurationMs: number;\n  motionKernel: FileViewerMotionKernel;\n  registerSidebar: (registration: FileViewerSidebarRegistration) => () => void;\n  rootId: string;\n  setSidebarOpen: FileViewerSetSidebarOpen;\n  side: FileViewerSidebarSide;\n  sidebarId: string;\n  sidebarWidth: string;\n  toggleSidebar: () => void;\n};\n\nexport type FileViewerSidebarDynamicContextValue = {\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  isSidebarTransitioning: boolean;\n  sidebarState: FileViewerSidebarState;\n};\n\nexport const FileViewerContext = React.createContext<FileViewerContextValue>({\n  headerMode: \"inline\",\n  hasHeaderOutlets: false,\n  isInsideFileViewer: false,\n  resourceCategory: \"unsupported\",\n  sidebarOpenProps: {},\n});\n\nexport const FileViewerShellStaticContext =\n  React.createContext<FileViewerShellStaticContextValue | null>(null);\n\nexport const FileViewerSidebarDynamicContext =\n  React.createContext<FileViewerSidebarDynamicContextValue | null>(null);\n\nexport function useFileViewerContext() {\n  return React.useContext(FileViewerContext);\n}\n\nexport function useOptionalFileViewerShellStatic() {\n  return React.useContext(FileViewerShellStaticContext);\n}\n\nexport function useFileViewerShellStatic(consumer: string) {\n  const context = React.useContext(FileViewerShellStaticContext);\n  if (!context) {\n    throw new Error(`${consumer} must be rendered inside FileViewer.`);\n  }\n  return context;\n}\n\nexport function useOptionalFileViewerShell() {\n  const staticContext = React.useContext(FileViewerShellStaticContext);\n  const sidebarContext = React.useContext(FileViewerSidebarDynamicContext);\n\n  return React.useMemo(\n    () =>\n      staticContext && sidebarContext\n        ? { ...staticContext, ...sidebarContext }\n        : null,\n    [sidebarContext, staticContext],\n  );\n}\n\nexport function useFileViewerShell(consumer: string) {\n  const context = useOptionalFileViewerShell();\n  if (!context) {\n    throw new Error(`${consumer} must be rendered inside FileViewer.`);\n  }\n  return context;\n}\n\nexport function useFileViewerSidebar(): FileViewerSidebarValue {\n  const fileViewerContext = React.useContext(FileViewerContext);\n  const shellContext = useOptionalFileViewerShell();\n\n  if (!fileViewerContext.isInsideFileViewer || !shellContext) {\n    throw new Error(\"useFileViewerSidebar must be used within FileViewer.\");\n  }\n\n  return shellContext;\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-context.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-elements.ts",
      "content": "\"use client\";\n\nimport type {\n  FileViewerDocumentSurface,\n  FileViewerMotionKernel,\n} from \"./file-viewer-motion-kernel\";\nimport type { FileViewerMotionFrame } from \"./file-viewer-motion-plan\";\n\nexport const FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT =\n  \"file-viewer:before-layout-motion\";\n\n// The kernel dispatches the before-layout-motion event as a CustomEvent whose\n// detail is its LIVE interactive frame, so renderers can capture pre-commit\n// anchors against what is actually on screen (a mid-flight retarget sees the\n// settled layout plus the in-flight transform).\nexport function readFileViewerBeforeLayoutMotionFrame(\n  event: Event,\n): FileViewerMotionFrame | null {\n  if (!(event instanceof CustomEvent)) return null;\n  const detail: unknown = event.detail;\n  return detail != null && typeof detail === \"object\"\n    ? (detail as FileViewerMotionFrame)\n    : null;\n}\n\nexport type FileViewerElements = {\n  documentSurfaceElement: HTMLElement | null;\n  getDocumentSurfaceMotionProbeElement: (() => HTMLElement | null) | null;\n  sidebarElement: HTMLElement | null;\n  sidebarGapElement: HTMLDivElement | null;\n  sidebarTriggerElement: HTMLElement | null;\n  viewerShellElement: HTMLDivElement | null;\n};\n\nexport type FileViewerElementRegistry = {\n  getElements: () => FileViewerElements;\n  registerDocumentSurface: (surface: FileViewerDocumentSurface) => () => void;\n  registerSidebarElement: (element: HTMLElement | null) => void;\n  registerSidebarGapElement: (element: HTMLDivElement | null) => void;\n  registerSidebarTriggerElement: (element: HTMLElement | null) => void;\n  registerViewerShellElement: (element: HTMLDivElement | null) => void;\n};\n\nexport function createFileViewerElementRegistry({\n  motionKernel,\n  onViewerShellElementChange,\n}: {\n  motionKernel: FileViewerMotionKernel;\n  onViewerShellElementChange: (element: HTMLDivElement | null) => void;\n}): FileViewerElementRegistry {\n  const elements: FileViewerElements = {\n    documentSurfaceElement: null,\n    getDocumentSurfaceMotionProbeElement: null,\n    sidebarElement: null,\n    sidebarGapElement: null,\n    sidebarTriggerElement: null,\n    viewerShellElement: null,\n  };\n  let documentSurfaceRegistration = 0;\n\n  return {\n    getElements: () => elements,\n    registerDocumentSurface: (surface) => {\n      documentSurfaceRegistration += 1;\n      const registration = documentSurfaceRegistration;\n      elements.documentSurfaceElement = surface.element;\n      elements.getDocumentSurfaceMotionProbeElement =\n        surface.getMotionProbeElement ?? null;\n      motionKernel.setDocumentSurface(surface);\n      return () => {\n        if (documentSurfaceRegistration !== registration) return;\n        elements.documentSurfaceElement = null;\n        elements.getDocumentSurfaceMotionProbeElement = null;\n        motionKernel.setDocumentSurface(null);\n      };\n    },\n    registerSidebarElement: (element) => {\n      if (elements.sidebarElement === element) return;\n      elements.sidebarElement = element;\n    },\n    registerSidebarGapElement: (element) => {\n      if (elements.sidebarGapElement === element) return;\n      elements.sidebarGapElement = element;\n      motionKernel.setSidebarGapElement(element);\n    },\n    registerSidebarTriggerElement: (element) => {\n      elements.sidebarTriggerElement = element;\n    },\n    registerViewerShellElement: (element) => {\n      if (elements.viewerShellElement === element) return;\n      elements.viewerShellElement = element;\n      onViewerShellElementChange(element);\n    },\n  };\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-elements.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-motion-kernel.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { flushSync } from \"react-dom\";\n\nimport { FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT } from \"./file-viewer-elements\";\nimport {\n  easeFileViewerMotion,\n  FILE_VIEWER_MOTION_DURATION_MS,\n  areFileViewerMotionFramesEqual,\n  areFileViewerMotionRestFramesEqual,\n  createFileViewerIdleMotionFrame,\n  createFileViewerMotionPlan,\n  createFileViewerMotionRestFrame,\n  type FileViewerMotionFrame,\n  type FileViewerMotionPlan,\n  type FileViewerMotionRestFrame,\n  type FileViewerMotionTarget,\n} from \"./file-viewer-motion-plan\";\n\n// The kernel is the single owner of sidebar motion. It holds the clock (one\n// rAF loop from slide start through settle), writes the continuous inline\n// styles, and publishes to React subscribers only at phase edges\n// (idle → sliding → settling → idle). Everything discrete — data attributes,\n// inert/aria, the overlay translate classes — is owned by React renders.\n//\n// Commit-then-relax ordering: the first sliding frame is flushed\n// synchronously inside the toggle's own task, so renderers commit their\n// TARGET layout and rebase scroll before anything paints; the per-tick style\n// writes then only relax the counter-transform to identity. Settle removes a\n// no-op transform — it never commits layout, so no flushSync runs inside rAF.\nexport type FileViewerMotionKernel = {\n  getFlightRecords: () => readonly FileViewerMotionFlightRecord[];\n  getInteractiveSnapshot: () => FileViewerMotionFrame;\n  getSnapshot: () => FileViewerMotionFrame;\n  setDocumentSurface: (surface: FileViewerDocumentSurface | null) => void;\n  setSidebarGapElement: (element: HTMLElement | null) => void;\n  startMotion: (target: FileViewerMotionTarget) => void;\n  subscribe: (listener: () => void) => () => void;\n  syncTarget: (target: FileViewerMotionTarget) => void;\n};\n\n// Always-on flight recorder: every motion leaves a bounded trace (per-tick\n// widths, phase edges, settle holds, inter-frame gaps) so a blink report is\n// diagnosable after the fact without re-instrumenting.\nexport type FileViewerMotionFlightRecord = {\n  fromInlineSize: number;\n  id: number;\n  interrupted: boolean;\n  maxTickGapMs: number;\n  open: boolean;\n  settleHoldFrameCount: number;\n  startedAt: number;\n  ticks: FileViewerMotionFlightTick[];\n  toInlineSize: number;\n};\n\nexport type FileViewerMotionFlightTick = {\n  elapsedMs: number;\n  phase: FileViewerMotionFrame[\"phase\"];\n  sidebarInlineSize: number;\n};\n\nconst FILE_VIEWER_FLIGHT_RECORD_LIMIT = 8;\nconst FILE_VIEWER_FLIGHT_TICK_LIMIT = 240;\n\nexport type FileViewerDocumentSurface = {\n  element: HTMLElement;\n  getMotionProbeElement?: (() => HTMLElement | null) | null;\n  readSettleSnapshot?: FileViewerDocumentSurfaceSettleSnapshotReader | null;\n  resolveMotionStyle?: FileViewerDocumentSurfaceMotionResolver | null;\n};\n\nexport type FileViewerDocumentSurfaceSettleSnapshotReader = () =>\n  | readonly number[]\n  | null\n  | undefined;\n\n// Layout reads are quarantined outside the kernel (viewer-measurement / the\n// frame controller layer). The kernel owns time and style writes only, so the\n// settle-hold rect reader is injected by its creator rather than imported.\nexport type FileViewerElementRectSnapshotReader = (\n  element: HTMLElement | null,\n) => readonly number[];\n\nexport type FileViewerMotionKernelOptions = {\n  readElementRectSnapshot?: FileViewerElementRectSnapshotReader | null;\n};\n\nexport type FileViewerDocumentSurfaceMotionStyle = {\n  customProperties?: Readonly<Record<string, string | null>>;\n  transform: string;\n  transformOrigin: string;\n  willChange: string;\n};\n\nexport type FileViewerDocumentSurfaceMotionResolver = (\n  frame: FileViewerMotionFrame,\n) => FileViewerDocumentSurfaceMotionStyle | null;\n\ntype FileViewerActiveMotion = {\n  durationMs: number;\n  from: FileViewerMotionRestFrame;\n  // The clock re-anchors to the first tick's vsync frame time: startedAt is\n  // stamped inside the toggle's task, but the synchronous slide-start commit\n  // can burn 10ms+ before anything paints, and an ease anchored at the click\n  // lands its first painted frame that deep into the curve.\n  hasFrameClockAnchor: boolean;\n  id: number;\n  startedAt: number;\n  to: FileViewerMotionRestFrame;\n};\n\ntype FileViewerSettleRelease = {\n  idleFrame: FileViewerMotionFrame;\n  lastSnapshot: readonly number[];\n  remainingFrameCount: number;\n  settlingFrame: FileViewerMotionFrame;\n  stableFrameCount: number;\n};\n\nconst FILE_VIEWER_SETTLE_SCROLL_EPSILON_PX = 0.25;\nconst FILE_VIEWER_SETTLE_STABLE_FRAME_COUNT = 2;\nconst FILE_VIEWER_SETTLE_MAX_HOLD_FRAMES = 6;\nconst FILE_VIEWER_SUBPIXEL_ENDPOINT_EPSILON_PX = 1;\n\nexport const DEFAULT_FILE_VIEWER_MOTION_FRAME: FileViewerMotionFrame = {\n  shellInlineSize: 0,\n  durationMs: FILE_VIEWER_MOTION_DURATION_MS,\n  fromInlineSize: 0,\n  layoutInlineSize: 0,\n  mode: \"overlay\",\n  motionId: null,\n  motionProgress: 1,\n  open: false,\n  phase: \"idle\",\n  side: \"left\",\n  sidebarInlineSize: 0,\n  sidebarWidth: 0,\n  toInlineSize: 0,\n};\n\nexport function createFileViewerMotionKernel({\n  readElementRectSnapshot = null,\n}: FileViewerMotionKernelOptions = {}): FileViewerMotionKernel {\n  const listeners = new Set<() => void>();\n  let contractFrame = DEFAULT_FILE_VIEWER_MOTION_FRAME;\n  let interactiveFrame = DEFAULT_FILE_VIEWER_MOTION_FRAME;\n  let target: FileViewerMotionTarget = {\n    shellInlineSize: 0,\n    durationMs: DEFAULT_FILE_VIEWER_MOTION_FRAME.durationMs,\n    mode: DEFAULT_FILE_VIEWER_MOTION_FRAME.mode,\n    open: DEFAULT_FILE_VIEWER_MOTION_FRAME.open,\n    side: DEFAULT_FILE_VIEWER_MOTION_FRAME.side,\n    sidebarWidth: 0,\n  };\n  let documentSurface: FileViewerDocumentSurface | null = null;\n  let documentSurfaceCustomProperties = new Set<string>();\n  let sidebarGapElement: HTMLElement | null = null;\n  let activeMotion: FileViewerActiveMotion | null = null;\n  let settleRelease: FileViewerSettleRelease | null = null;\n  let rafHandle = 0;\n  let settleReleaseHandle = 0;\n  let motionSequence = 0;\n  const flightRecords: FileViewerMotionFlightRecord[] = [];\n  let activeFlightRecord: FileViewerMotionFlightRecord | null = null;\n  let lastFlightTickAt = 0;\n\n  const beginFlightRecord = (motion: FileViewerActiveMotion) => {\n    if (activeFlightRecord && activeFlightRecord.id !== motion.id) {\n      activeFlightRecord.interrupted = true;\n    }\n    activeFlightRecord = {\n      fromInlineSize: motion.from.layoutInlineSize,\n      id: motion.id,\n      interrupted: false,\n      maxTickGapMs: 0,\n      open: motion.to.open,\n      settleHoldFrameCount: 0,\n      startedAt: motion.startedAt,\n      ticks: [],\n      toInlineSize: motion.to.layoutInlineSize,\n    };\n    lastFlightTickAt = motion.startedAt;\n    flightRecords.push(activeFlightRecord);\n    if (flightRecords.length > FILE_VIEWER_FLIGHT_RECORD_LIMIT) {\n      flightRecords.splice(\n        0,\n        flightRecords.length - FILE_VIEWER_FLIGHT_RECORD_LIMIT,\n      );\n    }\n  };\n\n  const recordFlightTick = (frame: FileViewerMotionFrame, now = readNow()) => {\n    const record = activeFlightRecord;\n    if (!record || frame.motionId !== record.id) return;\n    record.maxTickGapMs = Math.max(record.maxTickGapMs, now - lastFlightTickAt);\n    lastFlightTickAt = now;\n    if (frame.phase === \"settling\") record.settleHoldFrameCount += 1;\n    if (record.ticks.length >= FILE_VIEWER_FLIGHT_TICK_LIMIT) return;\n    record.ticks.push({\n      elapsedMs: Math.max(0, now - record.startedAt),\n      phase: frame.phase,\n      sidebarInlineSize: frame.sidebarInlineSize,\n    });\n  };\n\n  const notify = () => {\n    for (const listener of listeners) listener();\n  };\n\n  const publishContractFrame = (\n    nextFrame: FileViewerMotionFrame,\n    { flushSubscribers = false }: { flushSubscribers?: boolean } = {},\n  ) => {\n    if (areFileViewerMotionFramesEqual(contractFrame, nextFrame)) return;\n    contractFrame = nextFrame;\n\n    if (flushSubscribers) {\n      flushSync(notify);\n      return;\n    }\n\n    notify();\n  };\n\n  // The gap's inline size and the document surface's counter-scale must land\n  // in the same frame: two independent CSS transitions (width on the gap,\n  // transform on the surface) can desync under main-thread jank, letting the\n  // document edge drift off the sidebar edge mid-slide. The kernel therefore\n  // writes both here, once per tick.\n  const writeElementStyles = (nextFrame: FileViewerMotionFrame) => {\n    writeSidebarGapStyle(nextFrame);\n    writeDocumentSurfaceStyle(nextFrame);\n  };\n\n  const commit = (\n    nextFrame: FileViewerMotionFrame,\n    { publish = true }: { publish?: boolean } = {},\n  ) => {\n    writeElementStyles(nextFrame);\n    interactiveFrame = nextFrame;\n    if (publish) publishContractFrame(nextFrame);\n  };\n\n  const cancelTick = () => {\n    if (rafHandle === 0) return;\n    getCancelAnimationFrame()(rafHandle);\n    rafHandle = 0;\n  };\n\n  const cancelSettleRelease = () => {\n    settleRelease = null;\n    if (settleReleaseHandle === 0) return;\n    getCancelAnimationFrame()(settleReleaseHandle);\n    settleReleaseHandle = 0;\n  };\n\n  const readMotionSample = (\n    motion: FileViewerActiveMotion,\n    now = readNow(),\n  ): FileViewerMotionFrame => {\n    const rawTimeProgress =\n      motion.durationMs <= 0\n        ? 1\n        : clamp((now - motion.startedAt) / motion.durationMs, 0, 1);\n    const rawMotionProgress = easeFileViewerMotion(rawTimeProgress);\n    const rawSidebarInlineSize = lerp(\n      motion.from.sidebarInlineSize,\n      motion.to.sidebarInlineSize,\n      rawMotionProgress,\n    );\n    const isSubpixelEndpoint =\n      rawMotionProgress > 0.98 &&\n      Math.abs(rawSidebarInlineSize - motion.to.sidebarInlineSize) <=\n        FILE_VIEWER_SUBPIXEL_ENDPOINT_EPSILON_PX;\n    const motionProgress = isSubpixelEndpoint ? 1 : rawMotionProgress;\n    const sidebarInlineSize = isSubpixelEndpoint\n      ? motion.to.sidebarInlineSize\n      : rawSidebarInlineSize;\n    const layoutInlineSize = Math.max(\n      0,\n      motion.to.shellInlineSize - sidebarInlineSize,\n    );\n    const fromInlineSize = motion.from.layoutInlineSize;\n\n    return {\n      shellInlineSize: motion.to.shellInlineSize,\n      durationMs: motion.durationMs,\n      fromInlineSize,\n      layoutInlineSize,\n      mode: motion.to.mode,\n      motionId: motion.id,\n      motionProgress,\n      open: motion.to.open,\n      phase: motionProgress < 1 ? \"sliding\" : \"settling\",\n      side: motion.to.side,\n      sidebarInlineSize,\n      sidebarWidth: motion.to.sidebarWidth,\n      toInlineSize: motion.to.layoutInlineSize,\n    };\n  };\n\n  const settle = () => {\n    if (!activeMotion) return;\n    const finishedMotion = activeMotion;\n    activeMotion = null;\n    cancelTick();\n\n    const idleFrame = createFileViewerIdleMotionFrame(finishedMotion.to);\n    const settlingFrame: FileViewerMotionFrame = {\n      ...idleFrame,\n      fromInlineSize: finishedMotion.from.layoutInlineSize,\n      motionId: finishedMotion.id,\n      phase: \"settling\",\n    };\n\n    // Layout and scroll were committed at slide start; settling only clears\n    // the (now identity) counter-transform and holds until shell geometry\n    // stops moving. Nothing here re-renders geometry, so no flushSync in rAF.\n    commit(settlingFrame, { publish: false });\n    recordFlightTick(settlingFrame);\n    publishContractFrame(settlingFrame);\n    scheduleSettleRelease(settlingFrame, idleFrame);\n  };\n\n  // Ticks sample the clock at the rAF FRAME timestamp, never the callback's\n  // execution time: the frame time is the vsync the paint belongs to, and a\n  // callback running late in a janky frame would otherwise write a position\n  // ahead of the frame's own time axis — a real paint-side velocity excess\n  // (the probes' rule 11, applied to the writer). The first tick also\n  // re-anchors startedAt to its frame time, so the ease starts at the first\n  // paintable frame rather than at the click that precedes the slide-start\n  // commit.\n  const tick = (frameTime: number) => {\n    rafHandle = 0;\n    if (!activeMotion) return;\n    const now = Number.isFinite(frameTime) ? frameTime : readNow();\n    if (!activeMotion.hasFrameClockAnchor) {\n      activeMotion.hasFrameClockAnchor = true;\n      activeMotion.startedAt = now;\n    }\n    const sample = readMotionSample(activeMotion, now);\n    if (sample.motionProgress >= 1) {\n      settle();\n      return;\n    }\n    commit(sample, { publish: false });\n    recordFlightTick(sample, now);\n    scheduleTick();\n  };\n\n  const scheduleTick = () => {\n    if (rafHandle !== 0) return;\n    rafHandle = getRequestAnimationFrame()(tick);\n  };\n\n  const scheduleSettleRelease = (\n    settlingFrame: FileViewerMotionFrame,\n    idleFrame: FileViewerMotionFrame,\n  ) => {\n    cancelSettleRelease();\n    settleRelease = {\n      idleFrame,\n      lastSnapshot: readSettleSnapshot(),\n      remainingFrameCount: FILE_VIEWER_SETTLE_MAX_HOLD_FRAMES,\n      settlingFrame,\n      stableFrameCount: 0,\n    };\n    scheduleSettleReleaseFrame();\n  };\n\n  const scheduleSettleReleaseFrame = () => {\n    if (settleReleaseHandle !== 0) return;\n    settleReleaseHandle = getRequestAnimationFrame()(holdSettleRelease);\n  };\n\n  const holdSettleRelease = () => {\n    settleReleaseHandle = 0;\n    if (!settleRelease) return;\n\n    commit(settleRelease.settlingFrame, { publish: false });\n    recordFlightTick(settleRelease.settlingFrame);\n\n    const snapshot = readSettleSnapshot();\n    const stableFrameCount = areSettleSnapshotsEqual(\n      settleRelease.lastSnapshot,\n      snapshot,\n    )\n      ? settleRelease.stableFrameCount + 1\n      : 0;\n    const remainingFrameCount = settleRelease.remainingFrameCount - 1;\n\n    if (\n      stableFrameCount >= FILE_VIEWER_SETTLE_STABLE_FRAME_COUNT ||\n      remainingFrameCount <= 0\n    ) {\n      const idleFrame = settleRelease.idleFrame;\n      settleRelease = null;\n      // Natural completion: close the flight record so the next motion does\n      // not mark this one interrupted.\n      activeFlightRecord = null;\n      commit(idleFrame);\n      return;\n    }\n\n    settleRelease = {\n      ...settleRelease,\n      lastSnapshot: snapshot,\n      remainingFrameCount,\n      stableFrameCount,\n    };\n    scheduleSettleReleaseFrame();\n  };\n\n  // The event carries the kernel's LIVE frame so renderers can capture their\n  // pre-commit anchor against what is actually on screen — during a mid-flight\n  // retarget that is the settled layout PLUS the in-flight transform, not the\n  // settled layout alone.\n  const dispatchBeforeLayoutMotion = (currentFrame: FileViewerMotionFrame) => {\n    documentSurface?.element.dispatchEvent(\n      new CustomEvent<FileViewerMotionFrame>(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        { detail: currentFrame },\n      ),\n    );\n  };\n\n  const interruptActiveFlightRecord = () => {\n    if (!activeFlightRecord) return;\n    activeFlightRecord.interrupted = true;\n    activeFlightRecord = null;\n  };\n\n  const retarget = (nextTarget: FileViewerMotionTarget, animate: boolean) => {\n    cancelSettleRelease();\n    // Continuity is with what is PAINTED, not with the clock: mid-flight the\n    // screen shows the last tick's commit (`interactiveFrame`), which can be\n    // a frame behind a fresh clock sample. Planning (and the before-motion\n    // capture renderers do off the event detail) from the painted frame keeps\n    // the retarget hand-off pixel-continuous; the new motion simply re-lerps\n    // from the painted geometry.\n    const currentFrame =\n      interactiveFrame.shellInlineSize > 0\n        ? interactiveFrame\n        : createFileViewerIdleMotionFrame(\n            createFileViewerMotionRestFrame(target),\n          );\n    const plan = createFileViewerMotionPlan({\n      animate: animate && !prefersReducedMotion(),\n      currentFrame,\n      nextTarget,\n    });\n    if (shouldDispatchBeforeLayoutMotion(plan)) {\n      dispatchBeforeLayoutMotion(currentFrame);\n    }\n    target = plan.resolvedTarget;\n\n    if (!plan.shouldAnimate) {\n      if (activeMotion) interruptActiveFlightRecord();\n      activeMotion = null;\n      cancelTick();\n      commit(createFileViewerIdleMotionFrame(plan.nextRestFrame));\n      return;\n    }\n\n    motionSequence += 1;\n    activeMotion = {\n      durationMs: plan.resolvedTarget.durationMs,\n      from: { ...plan.currentRestFrame, layoutInlineSize: plan.fromInlineSize },\n      hasFrameClockAnchor: false,\n      id: motionSequence,\n      startedAt: readNow(),\n      to: plan.nextRestFrame,\n    };\n    beginFlightRecord(activeMotion);\n    const startFrame = readMotionSample(activeMotion, activeMotion.startedAt);\n    // Commit the discontinuity while it cannot be seen: flush the first\n    // sliding frame synchronously (inside the toggle's own task) so renderers\n    // lay out at the target width and rebase scroll before first paint, hidden\n    // behind the counter-transform written above in the same task.\n    writeElementStyles(startFrame);\n    interactiveFrame = startFrame;\n    recordFlightTick(startFrame, activeMotion.startedAt);\n    publishContractFrame(startFrame, { flushSubscribers: true });\n    scheduleTick();\n  };\n\n  const syncTarget = (nextTarget: FileViewerMotionTarget) => {\n    const nextRestFrame = createFileViewerMotionRestFrame(nextTarget);\n\n    if (activeMotion) {\n      target = nextTarget;\n      if (areFileViewerMotionRestFramesEqual(activeMotion.to, nextRestFrame)) {\n        return;\n      }\n      // A mode flip mid-motion (breakpoint crossing during the slide) cannot\n      // be animated: React re-renders the new mode immediately, so an inline\n      // slide continuing against overlay DOM (or vice versa) double-moves the\n      // surface. Snap to the new rest geometry instead.\n      if (nextRestFrame.mode !== activeMotion.to.mode) {\n        interruptActiveFlightRecord();\n        activeMotion = null;\n        cancelTick();\n        cancelSettleRelease();\n        commit(createFileViewerIdleMotionFrame(nextRestFrame));\n        return;\n      }\n      retarget(nextTarget, true);\n      return;\n    }\n\n    cancelSettleRelease();\n    target = nextTarget;\n    commit(createFileViewerIdleMotionFrame(nextRestFrame));\n  };\n\n  return {\n    getFlightRecords: () => flightRecords.slice(),\n    getInteractiveSnapshot: () =>\n      activeMotion ? readMotionSample(activeMotion) : interactiveFrame,\n    getSnapshot: () => contractFrame,\n    setDocumentSurface: (surface) => {\n      const previousSurface = documentSurface;\n      if (\n        previousSurface &&\n        (!surface || previousSurface.element !== surface.element)\n      ) {\n        clearDocumentSurfaceStyle(previousSurface.element);\n      }\n      documentSurface = surface;\n      writeDocumentSurfaceStyle(interactiveFrame);\n    },\n    setSidebarGapElement: (element) => {\n      sidebarGapElement = element;\n      writeSidebarGapStyle(interactiveFrame);\n    },\n    startMotion: (nextTarget) => retarget(nextTarget, true),\n    subscribe: (listener) => {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n    syncTarget,\n  };\n\n  function writeSidebarGapStyle(nextFrame: FileViewerMotionFrame) {\n    if (!sidebarGapElement) return;\n\n    // Overlay motion is CSS-owned; relinquish the gap so its `w-0` class is\n    // the only writer outside inline mode.\n    if (nextFrame.mode !== \"inline\") {\n      sidebarGapElement.style.width = \"\";\n      sidebarGapElement.style.flexBasis = \"\";\n      return;\n    }\n\n    sidebarGapElement.style.width = `${nextFrame.sidebarInlineSize}px`;\n    sidebarGapElement.style.flexBasis = `${nextFrame.sidebarInlineSize}px`;\n  }\n\n  function writeDocumentSurfaceStyle(nextFrame: FileViewerMotionFrame) {\n    if (!documentSurface) return;\n\n    const { element, resolveMotionStyle } = documentSurface;\n    const resolvedStyle = resolveMotionStyle?.(nextFrame);\n    if (resolvedStyle) {\n      writeDocumentSurfaceCustomProperties(\n        element,\n        resolvedStyle.customProperties,\n      );\n      element.style.transform = resolvedStyle.transform;\n      element.style.transformOrigin = resolvedStyle.transformOrigin;\n      element.style.willChange = resolvedStyle.willChange;\n      return;\n    }\n\n    // Default (no motion resolver): identity. Fit-width renderers register\n    // the shared commit-then-relax resolver (file-viewer-fit-width-motion);\n    // a surface without one either tracks the live DOM width on its own or\n    // opts out of shell motion entirely, and must not be transformed here.\n    writeDocumentSurfaceCustomProperties(element, null);\n    element.style.transform = \"\";\n    element.style.transformOrigin = \"\";\n    element.style.willChange = \"\";\n  }\n\n  function writeDocumentSurfaceCustomProperties(\n    element: HTMLElement,\n    customProperties:\n      | Readonly<Record<string, string | null>>\n      | null\n      | undefined,\n  ) {\n    const nextNames = new Set(Object.keys(customProperties ?? {}));\n    for (const name of documentSurfaceCustomProperties) {\n      if (!nextNames.has(name)) {\n        element.style.removeProperty(name);\n      }\n    }\n\n    for (const [name, value] of Object.entries(customProperties ?? {})) {\n      if (value == null) {\n        element.style.removeProperty(name);\n      } else {\n        element.style.setProperty(name, value);\n      }\n    }\n\n    documentSurfaceCustomProperties = nextNames;\n  }\n\n  function clearDocumentSurfaceStyle(element: HTMLElement) {\n    element.style.transform = \"\";\n    element.style.transformOrigin = \"\";\n    element.style.willChange = \"\";\n    for (const name of documentSurfaceCustomProperties) {\n      element.style.removeProperty(name);\n    }\n    documentSurfaceCustomProperties = new Set();\n  }\n\n  function readSettleSnapshot(): readonly number[] {\n    const values: number[] = [];\n\n    appendElementRectSnapshot(values, sidebarGapElement);\n    appendElementRectSnapshot(values, documentSurface?.element ?? null);\n\n    try {\n      const surfaceSnapshot = documentSurface?.readSettleSnapshot?.();\n      if (surfaceSnapshot) {\n        values.push(...surfaceSnapshot.map(toSettleSnapshotNumber));\n      }\n    } catch {\n      // A renderer snapshot is diagnostic, not correctness-critical. If a\n      // renderer unmounts while settling, fall back to shell geometry.\n    }\n\n    return values.length > 0 ? values : [0];\n  }\n\n  function appendElementRectSnapshot(\n    values: number[],\n    element: HTMLElement | null,\n  ) {\n    if (!readElementRectSnapshot || !element) return;\n    for (const value of readElementRectSnapshot(element)) {\n      values.push(toSettleSnapshotNumber(value));\n    }\n  }\n}\n\nfunction areSettleSnapshotsEqual(\n  previous: readonly number[],\n  next: readonly number[],\n) {\n  if (previous.length !== next.length) return false;\n  return previous.every(\n    (value, index) =>\n      Math.abs(value - next[index]) <= FILE_VIEWER_SETTLE_SCROLL_EPSILON_PX,\n  );\n}\n\nfunction toSettleSnapshotNumber(value: number) {\n  return Number.isFinite(value) ? value : 0;\n}\n\nfunction shouldDispatchBeforeLayoutMotion({\n  currentRestFrame,\n  nextRestFrame,\n}: FileViewerMotionPlan) {\n  return (\n    currentRestFrame.mode === \"inline\" &&\n    nextRestFrame.mode === \"inline\" &&\n    Math.abs(\n      currentRestFrame.layoutInlineSize - nextRestFrame.layoutInlineSize,\n    ) > 0.001\n  );\n}\n\nexport function useFileViewerMotionFrame(\n  kernel: FileViewerMotionKernel | null | undefined,\n): FileViewerMotionFrame {\n  const subscribe = React.useCallback(\n    (listener: () => void) => kernel?.subscribe(listener) ?? (() => {}),\n    [kernel],\n  );\n  const getSnapshot = React.useCallback(\n    () => kernel?.getSnapshot() ?? DEFAULT_FILE_VIEWER_MOTION_FRAME,\n    [kernel],\n  );\n\n  return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\nfunction prefersReducedMotion() {\n  return (\n    typeof matchMedia === \"function\" &&\n    matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n  );\n}\n\nfunction readNow() {\n  return typeof performance !== \"undefined\" &&\n    typeof performance.now === \"function\"\n    ? performance.now()\n    : Date.now();\n}\n\nfunction getRequestAnimationFrame() {\n  return (\n    globalThis.requestAnimationFrame ??\n    ((callback: FrameRequestCallback) =>\n      window.setTimeout(() => callback(readNow()), 16))\n  );\n}\n\nfunction getCancelAnimationFrame() {\n  return (\n    globalThis.cancelAnimationFrame ??\n    ((id: number) => {\n      window.clearTimeout(id);\n    })\n  );\n}\n\nfunction lerp(from: number, to: number, progress: number) {\n  return from + (to - from) * progress;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-motion-kernel.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-renderer-contract.ts",
      "content": "\"use client\";\n\nimport {\n  getFileViewerMotionRasterInlineSize,\n  type FileViewerMotionFrame,\n  type FileViewerMotionPhase,\n} from \"./file-viewer-motion-plan\";\nimport type { ViewerDocumentTransition } from \"./viewer-types\";\n\nexport type FileViewerDocumentAlign = \"start\" | \"center\" | \"end\";\n\n// Physical inline direction of the document frame (computed CSS `direction`).\n// The fit-width motion transform works on the physical X axis, so it needs\n// the direction to model where auto-margin alignment actually puts the stage.\nexport type FileViewerInlineDirection = \"ltr\" | \"rtl\";\n\n// `phase` is the motion clock's state; `documentTransition` is the single\n// spelling of the policies derived from it. Renderers read layout/scroll/\n// visual decisions from the transition, never from duplicated top-level\n// fields. `isTransitioning` is shorthand for `phase === \"sliding\"`.\nexport type FileViewerRendererFrame = {\n  align: FileViewerDocumentAlign;\n  canToggleSidebar: boolean;\n  direction: FileViewerInlineDirection;\n  documentTransition: ViewerDocumentTransition;\n  element: HTMLDivElement | null;\n  fromInlineSize: number | null;\n  isTransitioning: boolean;\n  layoutInlineSize: number | null;\n  motionDurationMs: number;\n  phase: FileViewerMotionPhase;\n  rasterInlineSize: number | null;\n  settledInlineSize: number | null;\n  shellInlineSize: number | null;\n  toInlineSize: number | null;\n  usesShellGeometry: boolean;\n};\n\nexport function resolveFileViewerRendererLayoutInlineSize({\n  fallbackInlineSize,\n  rendererFrame,\n}: {\n  fallbackInlineSize: number | null;\n  rendererFrame: FileViewerRendererFrame;\n}) {\n  const fallbackSize = resolveMeasuredInlineSize(fallbackInlineSize);\n\n  // Commit-then-relax: the renderer lays out at the motion's TARGET width for\n  // the entire motion (layoutPolicy \"target\" from the first sliding frame).\n  // The in-flight visual is the surface motion transform reprojecting that\n  // settled layout, so settle never commits layout.\n  if (\n    rendererFrame.documentTransition.layoutPolicy === \"target\" &&\n    rendererFrame.toInlineSize != null\n  ) {\n    return rendererFrame.toInlineSize;\n  }\n\n  return rendererFrame.layoutInlineSize ?? fallbackSize;\n}\n\nexport function createFileViewerRendererFrame({\n  align,\n  canToggleSidebar,\n  direction = \"ltr\",\n  element,\n  fallbackInlineSize,\n  motionFrame,\n  motionDurationMs,\n  usesShellGeometry,\n}: {\n  align: FileViewerDocumentAlign;\n  canToggleSidebar: boolean;\n  direction?: FileViewerInlineDirection;\n  element: HTMLDivElement | null;\n  fallbackInlineSize: number | null;\n  motionFrame: FileViewerMotionFrame;\n  motionDurationMs: number;\n  usesShellGeometry: boolean;\n}): FileViewerRendererFrame {\n  const measuredInlineSize = resolveMeasuredInlineSize(fallbackInlineSize);\n  const shellInlineSize = usesShellGeometry\n    ? motionFrame.shellInlineSize\n    : null;\n  const layoutInlineSize = usesShellGeometry\n    ? motionFrame.layoutInlineSize\n    : measuredInlineSize;\n  const settledInlineSize = usesShellGeometry\n    ? motionFrame.toInlineSize\n    : measuredInlineSize;\n  const rasterInlineSize = usesShellGeometry\n    ? getFileViewerMotionRasterInlineSize(motionFrame)\n    : layoutInlineSize;\n  const fromInlineSize = usesShellGeometry\n    ? motionFrame.fromInlineSize\n    : settledInlineSize;\n  const toInlineSize = usesShellGeometry\n    ? motionFrame.toInlineSize\n    : settledInlineSize;\n  const documentTransition = createFileViewerRendererTransition({\n    motionFrame,\n    usesShellGeometry,\n  });\n\n  const phase = usesShellGeometry ? motionFrame.phase : \"idle\";\n\n  return {\n    align,\n    canToggleSidebar,\n    direction,\n    documentTransition,\n    element,\n    fromInlineSize,\n    isTransitioning: phase === \"sliding\",\n    layoutInlineSize,\n    motionDurationMs,\n    phase,\n    rasterInlineSize,\n    settledInlineSize: settledInlineSize ?? layoutInlineSize,\n    shellInlineSize,\n    toInlineSize: toInlineSize ?? layoutInlineSize,\n    usesShellGeometry,\n  };\n}\n\nfunction resolveMeasuredInlineSize(inlineSize: number | null | undefined) {\n  return inlineSize != null && Number.isFinite(inlineSize) && inlineSize > 0\n    ? inlineSize\n    : null;\n}\n\nfunction createFileViewerRendererTransition({\n  motionFrame,\n  usesShellGeometry,\n}: {\n  motionFrame: FileViewerMotionFrame;\n  usesShellGeometry: boolean;\n}): ViewerDocumentTransition {\n  if (!usesShellGeometry) {\n    return {\n      layoutPolicy: \"live\",\n      scrollPolicy: \"preserve\",\n      source: \"none\",\n      transitionId: null,\n      visualPolicy: \"none\",\n    };\n  }\n\n  switch (motionFrame.phase) {\n    // Sliding commits the TARGET layout immediately (inside the toggle's own\n    // task, before first paint) and rebases scroll to the reading anchor in\n    // the same commit; the shell transform hides the jump. Settling then has\n    // no layout or scroll work left — it only clears the identity transform.\n    case \"sliding\":\n      return {\n        layoutPolicy: \"target\",\n        scrollPolicy: \"rebase\",\n        source: \"viewer-shell\",\n        transitionId: motionFrame.motionId,\n        visualPolicy: \"shell-transform\",\n      };\n    case \"settling\":\n      return {\n        layoutPolicy: \"target\",\n        scrollPolicy: \"rebase\",\n        source: \"viewer-shell\",\n        transitionId: motionFrame.motionId,\n        visualPolicy: \"shell-transform\",\n      };\n    case \"idle\":\n      return {\n        layoutPolicy: \"live\",\n        scrollPolicy: \"preserve\",\n        source: \"none\",\n        transitionId: null,\n        visualPolicy: \"none\",\n      };\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-renderer-contract.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-renderer-frame.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  useOptionalFileViewerShell,\n  useOptionalFileViewerShellStatic,\n  useFileViewerShellStatic,\n} from \"./file-viewer-context\";\nimport type { FileViewerDocumentSurface } from \"./file-viewer-motion-kernel\";\nimport {\n  createFileViewerRendererFrame,\n  type FileViewerDocumentAlign,\n  type FileViewerRendererFrame,\n} from \"./file-viewer-renderer-contract\";\nimport { useFileViewerMotionFrame } from \"./file-viewer-motion-kernel\";\nimport { useViewerInlineDirection } from \"./viewer-measurement\";\n\nexport type FileViewerRendererEnvironment = {\n  registerDocumentSurface: (surface: FileViewerDocumentSurface) => () => void;\n  usesShellGeometry: boolean;\n};\n\nexport type FileViewerDocumentFrameState = {\n  align: FileViewerDocumentAlign;\n  element: HTMLDivElement | null;\n  inlineSize: number | null;\n};\n\nconst FileViewerDocumentFrameContext =\n  React.createContext<FileViewerDocumentFrameState | null>(null);\n\nexport function FileViewerDocumentFrameProvider({\n  children,\n  value,\n}: {\n  children: React.ReactNode;\n  value: FileViewerDocumentFrameState;\n}) {\n  return (\n    <FileViewerDocumentFrameContext.Provider value={value}>\n      {children}\n    </FileViewerDocumentFrameContext.Provider>\n  );\n}\n\nexport function useOptionalFileViewerDocumentFrame(): FileViewerDocumentFrameState | null {\n  return React.useContext(FileViewerDocumentFrameContext);\n}\n\nexport function useOptionalFileViewerRendererEnvironment(): FileViewerRendererEnvironment {\n  const { elementRegistry, usesShellGeometry } =\n    useFileViewerRendererEnvironmentState();\n  const registerDocumentSurface = React.useCallback(\n    (surface: FileViewerDocumentSurface) =>\n      elementRegistry?.registerDocumentSurface(surface) ?? (() => {}),\n    [elementRegistry],\n  );\n\n  return React.useMemo(\n    () => ({\n      registerDocumentSurface,\n      usesShellGeometry,\n    }),\n    [registerDocumentSurface, usesShellGeometry],\n  );\n}\n\nexport type FileViewerSidebarMotion = {\n  /** True when the shell animates the sidebar (inline mode with a toggle). */\n  isMotionManaged: boolean;\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  isSidebarTransitioning: boolean;\n};\n\nexport function useOptionalFileViewerSidebarMotion(): FileViewerSidebarMotion | null {\n  const shell = useOptionalFileViewerShell();\n\n  return React.useMemo(\n    () =>\n      shell\n        ? {\n            isMotionManaged: shell.mode === \"inline\" && shell.canToggleSidebar,\n            isSidebarInteractive: shell.isSidebarInteractive,\n            isSidebarOpen: shell.isSidebarOpen,\n            isSidebarTransitioning: shell.isSidebarTransitioning,\n          }\n        : null,\n    [shell],\n  );\n}\n\nexport function useFileViewerRendererFrame({\n  fallbackInlineSize,\n}: {\n  fallbackInlineSize?: number | null;\n} = {}): FileViewerRendererFrame {\n  useFileViewerShellStatic(\"useFileViewerRendererFrame\");\n  return useResolvedFileViewerRendererFrame({\n    fallbackInlineSize,\n    required: true,\n  });\n}\n\nexport function useOptionalFileViewerRendererFrame({\n  fallbackInlineSize,\n}: {\n  fallbackInlineSize?: number | null;\n} = {}): FileViewerRendererFrame {\n  return useResolvedFileViewerRendererFrame({\n    fallbackInlineSize,\n    required: false,\n  });\n}\n\nfunction useResolvedFileViewerRendererFrame({\n  fallbackInlineSize,\n  required,\n}: {\n  fallbackInlineSize?: number | null;\n  required: boolean;\n}): FileViewerRendererFrame {\n  const { motionFrame, shell, usesShellGeometry } =\n    useFileViewerRendererEnvironmentState();\n  const documentFrame = useOptionalFileViewerDocumentFrame();\n\n  if (required && !documentFrame) {\n    throw new Error(\n      \"useFileViewerRendererFrame must be used within FileViewerInset.\",\n    );\n  }\n\n  const fallbackSize =\n    fallbackInlineSize != null && Number.isFinite(fallbackInlineSize)\n      ? fallbackInlineSize\n      : null;\n\n  // The fit-width motion transform is a physical-X computation, so renderers\n  // need the frame's computed CSS `direction` alongside its logical align.\n  const direction = useViewerInlineDirection(documentFrame?.element ?? null);\n\n  return React.useMemo(\n    () =>\n      createFileViewerRendererFrame({\n        align: documentFrame?.align ?? \"center\",\n        canToggleSidebar: shell?.canToggleSidebar ?? false,\n        direction,\n        element: documentFrame?.element ?? null,\n        fallbackInlineSize: documentFrame?.inlineSize ?? fallbackSize,\n        motionFrame,\n        motionDurationMs: shell?.motionDurationMs ?? 0,\n        usesShellGeometry,\n      }),\n    [\n      direction,\n      documentFrame?.align,\n      documentFrame?.element,\n      documentFrame?.inlineSize,\n      fallbackSize,\n      shell?.canToggleSidebar,\n      motionFrame,\n      shell?.motionDurationMs,\n      usesShellGeometry,\n    ],\n  );\n}\n\nfunction useFileViewerRendererEnvironmentState() {\n  const shell = useOptionalFileViewerShellStatic();\n  const motionFrame = useFileViewerMotionFrame(shell?.motionKernel);\n  const usesShellGeometry = Boolean(\n    shell &&\n      motionFrame.shellInlineSize > 0 &&\n      shell.mode === \"inline\" &&\n      (shell.canToggleSidebar || shell.collapsible === \"none\"),\n  );\n\n  return React.useMemo(\n    () => ({\n      elementRegistry: shell?.elementRegistry,\n      motionFrame,\n      shell,\n      usesShellGeometry,\n    }),\n    [motionFrame, shell, usesShellGeometry],\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-renderer-frame.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-motion-plan.ts",
      "content": "\"use client\";\n\nimport type {\n  FileViewerSidebarMode,\n  FileViewerSidebarSide,\n} from \"./file-viewer-context\";\n\nexport const FILE_VIEWER_MOTION_EPSILON = 0.5;\n\n// The one duration for every sidebar motion timeline — the kernel clock, the\n// motion targets, and the overlay panel's CSS transition all read this value.\nexport const FILE_VIEWER_MOTION_DURATION_MS = 150;\n\n// The one easing for every sidebar motion timeline. Cubic ease-out: the slide\n// decelerates into rest. Linear progress ends at full velocity, and content\n// far from the reading anchor (the bottom of a tall fit-width document)\n// travels several pixels per millisecond straight into a hard stop — a\n// visible jolt the anchor line never shows.\nexport function easeFileViewerMotion(timeProgress: number) {\n  return 1 - (1 - timeProgress) ** 3;\n}\n\nexport type FileViewerMotionPhase = \"idle\" | \"sliding\" | \"settling\";\n\nexport type FileViewerMotionTarget = {\n  shellInlineSize: number;\n  durationMs: number;\n  mode: FileViewerSidebarMode;\n  open: boolean;\n  side: FileViewerSidebarSide;\n  sidebarWidth: number;\n};\n\nexport type FileViewerMotionFrame = {\n  shellInlineSize: number;\n  durationMs: number;\n  fromInlineSize: number;\n  layoutInlineSize: number;\n  mode: FileViewerSidebarMode;\n  motionId: number | null;\n  motionProgress: number;\n  open: boolean;\n  phase: FileViewerMotionPhase;\n  side: FileViewerSidebarSide;\n  sidebarInlineSize: number;\n  sidebarWidth: number;\n  toInlineSize: number;\n};\n\nexport type FileViewerMotionRestFrame = Pick<\n  FileViewerMotionFrame,\n  | \"shellInlineSize\"\n  | \"durationMs\"\n  | \"layoutInlineSize\"\n  | \"mode\"\n  | \"open\"\n  | \"side\"\n  | \"sidebarInlineSize\"\n  | \"sidebarWidth\"\n>;\n\nexport type FileViewerMotionPlan = {\n  currentRestFrame: FileViewerMotionRestFrame;\n  fromInlineSize: number;\n  nextRestFrame: FileViewerMotionRestFrame;\n  resolvedTarget: FileViewerMotionTarget;\n  shouldAnimate: boolean;\n};\n\nexport function createFileViewerMotionRestFrame(\n  target: FileViewerMotionTarget,\n): FileViewerMotionRestFrame {\n  const shellInlineSize = target.shellInlineSize;\n  const sidebarInlineSize =\n    target.mode === \"inline\" && target.open\n      ? Math.min(target.sidebarWidth, shellInlineSize)\n      : 0;\n\n  return {\n    shellInlineSize,\n    durationMs: target.durationMs,\n    layoutInlineSize: Math.max(0, shellInlineSize - sidebarInlineSize),\n    mode: target.mode,\n    open: target.open,\n    side: target.side,\n    sidebarInlineSize,\n    sidebarWidth: target.sidebarWidth,\n  };\n}\n\nexport function createFileViewerIdleMotionFrame(\n  restFrame: FileViewerMotionRestFrame,\n): FileViewerMotionFrame {\n  return {\n    ...restFrame,\n    fromInlineSize: restFrame.layoutInlineSize,\n    motionId: null,\n    motionProgress: 1,\n    phase: \"idle\",\n    toInlineSize: restFrame.layoutInlineSize,\n  };\n}\n\nexport function getFileViewerMotionRasterInlineSize(\n  frame: Pick<\n    FileViewerMotionFrame,\n    \"fromInlineSize\" | \"layoutInlineSize\" | \"toInlineSize\"\n  >,\n): number {\n  return Math.max(\n    frame.fromInlineSize,\n    frame.toInlineSize,\n    frame.layoutInlineSize,\n  );\n}\n\nexport function createFileViewerMotionPlan({\n  animate,\n  currentFrame,\n  nextTarget,\n}: {\n  animate: boolean;\n  currentFrame: FileViewerMotionFrame;\n  nextTarget: FileViewerMotionTarget;\n}): FileViewerMotionPlan {\n  const resolvedTarget = resolveFileViewerMotionTarget({\n    currentFrame,\n    nextTarget,\n  });\n  const currentRestFrame = pickFileViewerMotionRestFrame(currentFrame);\n  const nextRestFrame = createFileViewerMotionRestFrame(resolvedTarget);\n  // The motion's visual origin is what is on screen RIGHT NOW: for a fresh\n  // motion that is the rest layout; for a mid-flight retarget it is the live\n  // interpolated width, so the new motion's first frame (and every renderer's\n  // anchor solve) continues from the picture the reader is looking at rather\n  // than the interrupted motion's origin.\n  const fromInlineSize =\n    currentFrame.phase === \"sliding\"\n      ? currentFrame.layoutInlineSize\n      : currentRestFrame.layoutInlineSize;\n  const shouldAnimate =\n    animate &&\n    resolvedTarget.mode === \"inline\" &&\n    currentFrame.shellInlineSize > 0 &&\n    Math.abs(\n      currentRestFrame.sidebarInlineSize - nextRestFrame.sidebarInlineSize,\n    ) > FILE_VIEWER_MOTION_EPSILON &&\n    resolvedTarget.durationMs > 0;\n\n  return {\n    currentRestFrame,\n    fromInlineSize,\n    nextRestFrame,\n    resolvedTarget,\n    shouldAnimate,\n  };\n}\n\nexport function areFileViewerMotionRestFramesEqual(\n  previous: FileViewerMotionRestFrame,\n  next: FileViewerMotionRestFrame,\n) {\n  return (\n    areFileViewerMotionNumbersEqual(\n      previous.shellInlineSize,\n      next.shellInlineSize,\n    ) &&\n    previous.durationMs === next.durationMs &&\n    areFileViewerMotionNumbersEqual(\n      previous.layoutInlineSize,\n      next.layoutInlineSize,\n    ) &&\n    previous.mode === next.mode &&\n    previous.open === next.open &&\n    previous.side === next.side &&\n    areFileViewerMotionNumbersEqual(\n      previous.sidebarInlineSize,\n      next.sidebarInlineSize,\n    ) &&\n    areFileViewerMotionNumbersEqual(previous.sidebarWidth, next.sidebarWidth)\n  );\n}\n\nexport function areFileViewerMotionFramesEqual(\n  previous: FileViewerMotionFrame,\n  next: FileViewerMotionFrame,\n) {\n  return (\n    areFileViewerMotionRestFramesEqual(previous, next) &&\n    previous.motionId === next.motionId &&\n    areFileViewerMotionNumbersEqual(\n      previous.motionProgress,\n      next.motionProgress,\n    ) &&\n    previous.phase === next.phase &&\n    areFileViewerMotionNumbersEqual(\n      previous.fromInlineSize,\n      next.fromInlineSize,\n    ) &&\n    areFileViewerMotionNumbersEqual(previous.toInlineSize, next.toInlineSize)\n  );\n}\n\nfunction pickFileViewerMotionRestFrame(\n  frame: FileViewerMotionFrame,\n): FileViewerMotionRestFrame {\n  return {\n    shellInlineSize: frame.shellInlineSize,\n    durationMs: frame.durationMs,\n    layoutInlineSize: frame.layoutInlineSize,\n    mode: frame.mode,\n    open: frame.open,\n    side: frame.side,\n    sidebarInlineSize: frame.sidebarInlineSize,\n    sidebarWidth: frame.sidebarWidth,\n  };\n}\n\nfunction resolveFileViewerMotionTarget({\n  currentFrame,\n  nextTarget,\n}: {\n  currentFrame: FileViewerMotionFrame;\n  nextTarget: FileViewerMotionTarget;\n}): FileViewerMotionTarget {\n  if (\n    nextTarget.mode !== \"overlay\" ||\n    currentFrame.mode !== \"inline\" ||\n    currentFrame.shellInlineSize <= 0\n  ) {\n    return nextTarget;\n  }\n\n  return {\n    ...nextTarget,\n    shellInlineSize:\n      nextTarget.shellInlineSize > 0\n        ? nextTarget.shellInlineSize\n        : currentFrame.shellInlineSize,\n    mode: \"inline\",\n  };\n}\n\nfunction areFileViewerMotionNumbersEqual(previous: number, next: number) {\n  return Math.abs(previous - next) <= 0.001;\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-motion-plan.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-fit-width-motion.ts",
      "content": "\"use client\";\n\nimport type { FileViewerDocumentSurfaceMotionResolver } from \"./file-viewer-motion-kernel\";\nimport type { FileViewerMotionFrame } from \"./file-viewer-motion-plan\";\nimport type {\n  FileViewerDocumentAlign,\n  FileViewerInlineDirection,\n} from \"./file-viewer-renderer-contract\";\n\nexport const FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY =\n  \"--file-viewer-fit-width-anchor-block\";\n\n// Commit-then-relax: the renderer lays out at the motion's TARGET width from\n// the first commit, and this resolver reprojects that settled layout to the\n// in-flight visual width with one uniform transform. The transform terminates\n// on identity, so settle removes a no-op style instead of committing layout.\n//\n// The anchor custom property is the reading line's block offset in the settled\n// stage's own coordinates (post-rebase scrollTop + marker offset). It is read\n// live via var(), so the renderer writes it once per motion (in a layout\n// effect after the slide-start scroll rebase) without re-entering the kernel.\nexport function createFileViewerFitWidthSurfaceMotionResolver({\n  align,\n  anchorBlockProperty = FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY,\n  direction = \"ltr\",\n  isFitWidth,\n  stageInlineSize,\n  stageOuterInlinePadding = 0,\n  stageInlinePadding = 0,\n  stageInlineSlope = 1,\n  stageBlockSlope = stageInlineSlope,\n}: {\n  align: FileViewerDocumentAlign;\n  anchorBlockProperty?: string;\n  direction?: FileViewerInlineDirection;\n  isFitWidth: boolean;\n  stageInlineSize: number;\n  /** Constant symmetric padding around the transformed stage. */\n  stageOuterInlinePadding?: number;\n  /** Constant symmetric inline padding inside the transformed stage. */\n  stageInlinePadding?: number;\n  stageInlineSlope?: number;\n  /**\n   * Slope for the BLOCK axis when it differs from the inline one. A stage\n   * whose inline box carries constant padding while its block stack scales\n   * with the content (the image viewer: fit subtracts the horizontal\n   * padding, vertical gaps/padding scale) has two different affine models —\n   * X tracks the pane 1:1 while Y scales by the content ratio — and a\n   * uniform scale cannot land both axes exactly. Defaults to the inline\n   * slope (uniform scale) for fully proportional stages like the PDF.\n   */\n  stageBlockSlope?: number;\n}): FileViewerDocumentSurfaceMotionResolver {\n  return (frame) => {\n    if (!isFitWidth || frame.phase !== \"sliding\") {\n      return {\n        transform: \"\",\n        transformOrigin: \"\",\n        willChange: \"\",\n      };\n    }\n\n    return {\n      transform: getFileViewerFitWidthSurfaceMotionTransform({\n        align,\n        anchorBlockProperty,\n        direction,\n        frame,\n        stageInlineSize,\n        stageOuterInlinePadding,\n        stageInlinePadding,\n        stageInlineSlope,\n        stageBlockSlope,\n      }),\n      transformOrigin: \"0px 0px\",\n      willChange: \"transform\",\n    };\n  };\n}\n\n// Commit-then-relax for a CLAMPED reading column rather than a fit-width\n// stage: the stage's inline size is min(canvas, maxStageInlineSize), so it\n// does not scale with the pane — the only thing a width change moves is the\n// align margin. The canvas commits the motion's TARGET width from the first\n// sliding frame (minWidth under layoutPolicy \"target\"), which means a\n// widening pane's chunks land at the settled margin synchronously with the\n// click; this resolver reprojects them back to the live width's margin with\n// a translate that terminates on identity. A narrowing pane never engages it\n// (the canvas tracks the live width above its minWidth, so live and settled\n// margins agree) — exactly the leg that already glides on layout.\nexport function createFileViewerAlignTranslateSurfaceMotionResolver({\n  align,\n  direction = \"ltr\",\n  maxStageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  direction?: FileViewerInlineDirection;\n  /** The column's max inline size (the chunk's max-width, in px). */\n  maxStageInlineSize: number;\n}): FileViewerDocumentSurfaceMotionResolver {\n  return (frame) => {\n    if (frame.phase !== \"sliding\") {\n      return {\n        transform: \"\",\n        transformOrigin: \"\",\n        willChange: \"\",\n      };\n    }\n\n    return {\n      transform: getFileViewerAlignTranslateSurfaceMotionTransform({\n        align,\n        direction,\n        frame,\n        maxStageInlineSize,\n      }),\n      transformOrigin: \"0px 0px\",\n      willChange: \"transform\",\n    };\n  };\n}\n\nfunction getFileViewerAlignTranslateSurfaceMotionTransform({\n  align,\n  direction,\n  frame,\n  maxStageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  direction: FileViewerInlineDirection;\n  frame: FileViewerMotionFrame;\n  maxStageInlineSize: number;\n}) {\n  if (\n    !Number.isFinite(maxStageInlineSize) ||\n    maxStageInlineSize <= 0 ||\n    frame.layoutInlineSize <= 0 ||\n    frame.toInlineSize <= 0\n  ) {\n    return \"\";\n  }\n\n  // The canvas lays out at max(live, target): minWidth holds the committed\n  // target under a still-narrow pane, and a pane wider than the target just\n  // fills. The stage (reading column) centers/aligns INSIDE the canvas, and\n  // an overflowing canvas itself pins to the pane's start edge — left in\n  // LTR, right in RTL — so the stage's pane-space position carries the\n  // canvas offset too.\n  const canvasInlineSize = Math.max(frame.layoutInlineSize, frame.toInlineSize);\n  const stageInlineSize = Math.min(canvasInlineSize, maxStageInlineSize);\n  const canvasInlineOffset =\n    direction === \"rtl\"\n      ? Math.min(0, frame.layoutInlineSize - canvasInlineSize)\n      : 0;\n  const settledStageLeft =\n    canvasInlineOffset +\n    getFileViewerStageInlineMargin({\n      align,\n      availableInlineSize: canvasInlineSize,\n      direction,\n      stageInlineSize,\n    });\n  const liveStageLeft = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: frame.layoutInlineSize,\n    direction,\n    stageInlineSize,\n  });\n  const translateX = liveStageLeft - settledStageLeft;\n\n  if (Math.abs(translateX) <= 0.001) return \"\";\n\n  return `translate3d(${formatFileViewerMotionPixel(translateX)}px, 0px, 0)`;\n}\n\nexport function getFileViewerFitWidthScale({\n  availableInlineSize,\n  contentInlineSize,\n  stageInlinePadding = 0,\n}: {\n  availableInlineSize: number;\n  contentInlineSize: number;\n  stageInlinePadding?: number;\n}) {\n  if (availableInlineSize <= 0 || contentInlineSize <= 0) return 1;\n\n  const contentAvailableInlineSize = Math.max(\n    1,\n    availableInlineSize - stageInlinePadding,\n  );\n  return contentAvailableInlineSize / contentInlineSize;\n}\n\n// The visual scale the resolver renders for a given live width — the same\n// affine reprojection as the transform itself. Renderers use it to reason\n// about the on-screen state (anchor capture/solve) without duplicating the\n// formula.\n//\n// stageInlineSlope is how many stage pixels the settled stage grows per pane\n// pixel. It is 1 whenever the stage IS the fit-width content (image, docx,\n// pptx, uniform-width PDFs: stage = pane − constant padding), but a stage\n// that is WIDER than its fit basis grows faster than the pane — a PDF fits\n// its FIRST page while the stage spans its WIDEST page, so a mixed-width\n// document has slope maxBase/fitBase > 1. A unit-slope assumption there\n// under-scales the first frame by (slope − 1)·delta/stage — measured as a\n// ~7px content step at the anchor-hold frame of a 355-page prospectus.\nexport function getFileViewerFitWidthVisualScale({\n  liveInlineSize,\n  stageInlineSize,\n  stageInlineSlope = 1,\n  targetInlineSize,\n}: {\n  liveInlineSize: number;\n  stageInlineSize: number;\n  stageInlineSlope?: number;\n  targetInlineSize: number;\n}) {\n  if (\n    stageInlineSize <= 0 ||\n    !Number.isFinite(liveInlineSize) ||\n    !Number.isFinite(targetInlineSize)\n  ) {\n    return 1;\n  }\n  const slope =\n    Number.isFinite(stageInlineSlope) && stageInlineSlope > 0\n      ? stageInlineSlope\n      : 1;\n  return (\n    Math.max(1, stageInlineSize + slope * (liveInlineSize - targetInlineSize)) /\n    stageInlineSize\n  );\n}\n\n// Capture side of the motion anchor: the probe content line's on-screen block\n// offset relative to the scroll box, taken just before a motion (or retarget)\n// commits. When a motion is already in flight the DOM is the settled layout\n// PLUS the live transform, so the capture applies that transform — otherwise\n// a retarget would solve continuity against a picture the reader never saw.\nexport function captureFileViewerFitWidthAnchorScreenOffset({\n  lastAnchorBlock,\n  liveFrame,\n  probeStageOffset,\n  scrollTop,\n  stageInlineSize,\n  stageInlinePadding = 0,\n  stageBlockSlope = 1,\n}: {\n  lastAnchorBlock: number | null;\n  liveFrame: FileViewerMotionFrame | null;\n  probeStageOffset: number;\n  scrollTop: number;\n  stageInlineSize: number;\n  stageInlinePadding?: number;\n  /** The BLOCK-axis slope — anchor capture/solve is block-axis math. */\n  stageBlockSlope?: number;\n}) {\n  const untransformed = probeStageOffset - scrollTop;\n  if (!liveFrame || liveFrame.phase !== \"sliding\") return untransformed;\n\n  const liveScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: liveFrame.layoutInlineSize,\n    stageInlineSize: getFileViewerFitWidthContentInlineSize({\n      stageInlinePadding,\n      stageInlineSize,\n    }),\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: liveFrame.toInlineSize,\n  });\n  if (Math.abs(1 - liveScale) <= 0.001) return untransformed;\n\n  return (\n    liveScale * probeStageOffset +\n    (1 - liveScale) * (lastAnchorBlock ?? 0) -\n    scrollTop\n  );\n}\n\n// Solve side: the anchor block offset that puts the probe content line back on\n// its captured screen position under the NEW layout model at the motion's\n// first-frame scale. Exact regardless of how the rebase clamped or how the\n// old/new layout models relate (measured page sizes, constant gaps/padding).\n// Returns null when the motion is degenerate (caller falls back to the live\n// reading marker).\nexport function resolveFileViewerFitWidthMotionAnchorBlock({\n  fromInlineSize,\n  probeScreenOffset,\n  probeStageOffset,\n  scrollTop,\n  stageInlineSize,\n  stageInlinePadding = 0,\n  stageBlockSlope = 1,\n  toInlineSize,\n}: {\n  fromInlineSize: number | null;\n  probeScreenOffset: number;\n  probeStageOffset: number;\n  scrollTop: number;\n  stageInlineSize: number;\n  stageInlinePadding?: number;\n  /** The BLOCK-axis slope — anchor capture/solve is block-axis math. */\n  stageBlockSlope?: number;\n  toInlineSize: number | null;\n}) {\n  if (fromInlineSize == null || toInlineSize == null) return null;\n\n  const startScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: fromInlineSize,\n    stageInlineSize: getFileViewerFitWidthContentInlineSize({\n      stageInlinePadding,\n      stageInlineSize,\n    }),\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: toInlineSize,\n  });\n  if (!Number.isFinite(startScale) || Math.abs(1 - startScale) <= 0.001) {\n    return null;\n  }\n\n  return (\n    (probeScreenOffset + scrollTop - startScale * probeStageOffset) /\n    (1 - startScale)\n  );\n}\n\nfunction getFileViewerFitWidthSurfaceMotionTransform({\n  align,\n  anchorBlockProperty,\n  direction,\n  frame,\n  stageInlineSize,\n  stageOuterInlinePadding,\n  stageInlinePadding,\n  stageInlineSlope,\n  stageBlockSlope,\n}: {\n  align: FileViewerDocumentAlign;\n  anchorBlockProperty: string;\n  direction: FileViewerInlineDirection;\n  frame: FileViewerMotionFrame;\n  stageInlineSize: number;\n  stageOuterInlinePadding: number;\n  stageInlinePadding: number;\n  stageInlineSlope: number;\n  stageBlockSlope: number;\n}) {\n  if (\n    stageInlineSize <= 0 ||\n    frame.layoutInlineSize <= 0 ||\n    frame.toInlineSize <= 0\n  ) {\n    return \"\";\n  }\n\n  // Fit-width renderers size their stage as an affine function of the\n  // available width (stage = slope × width − constant padding), so the\n  // in-flight visual stage is the settled stage plus the scaled live width\n  // delta. At the first frame this resolves to exactly the pre-toggle stage\n  // size, and at the last frame to the settled stage — identity. Each axis\n  // carries its own slope: they differ when the stage's inline box holds\n  // constant padding while its block stack scales with the content.\n  const contentInlineSize = getFileViewerFitWidthContentInlineSize({\n    stageInlinePadding,\n    stageInlineSize,\n  });\n  const inlineScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: frame.layoutInlineSize,\n    stageInlineSize: contentInlineSize,\n    stageInlineSlope,\n    targetInlineSize: frame.toInlineSize,\n  });\n  const blockScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: frame.layoutInlineSize,\n    stageInlineSize: contentInlineSize,\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: frame.toInlineSize,\n  });\n  const visualStageInlineSize =\n    inlineScale * contentInlineSize + stageInlinePadding;\n  const availableStageInlineSize = Math.max(\n    1,\n    frame.layoutInlineSize - Math.max(0, stageOuterInlinePadding),\n  );\n  const settledMargin = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: availableStageInlineSize,\n    direction,\n    stageInlineSize,\n  });\n  const visualMargin = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: availableStageInlineSize,\n    direction,\n    stageInlineSize: visualStageInlineSize,\n  });\n  // The padding is constant in both endpoint layouts. Scaling the outer stage\n  // would scale that inset too, making the visible page briefly too wide or\n  // narrow on the first frame. Rebase the symmetric start inset so the inner\n  // content edge, not the transparent wrapper edge, is pixel-continuous.\n  const inlinePaddingStart = stageInlinePadding / 2;\n  const translateX =\n    visualMargin - settledMargin + (1 - inlineScale) * inlinePaddingStart;\n\n  if (Math.abs(frame.layoutInlineSize - frame.toInlineSize) <= 0.001) {\n    return \"\";\n  }\n\n  const formattedInlineScale = formatFileViewerMotionScale(inlineScale);\n  const formattedBlockScale = formatFileViewerMotionScale(blockScale);\n  const formattedTranslateX = formatFileViewerMotionPixel(translateX);\n  // Scale about the stage origin; the anchor term keeps the reading line\n  // fixed on the block axis: y' = s·y + (1 − s)·anchor equals y at\n  // y = anchor.\n  const translateY = `calc((1 - ${formattedBlockScale}) * var(${anchorBlockProperty}, 0px))`;\n  const formattedScale =\n    formattedInlineScale === formattedBlockScale\n      ? formattedInlineScale\n      : `${formattedInlineScale}, ${formattedBlockScale}`;\n\n  return `translate3d(${formattedTranslateX}px, ${translateY}, 0) scale(${formattedScale})`;\n}\n\nfunction getFileViewerFitWidthContentInlineSize({\n  stageInlinePadding,\n  stageInlineSize,\n}: {\n  stageInlinePadding: number;\n  stageInlineSize: number;\n}) {\n  const padding = Number.isFinite(stageInlinePadding)\n    ? Math.max(0, stageInlinePadding)\n    : 0;\n  return Math.max(1, stageInlineSize - padding);\n}\n\n// Physical LEFT offset of the stage box inside the available inline size —\n// translateX shifts along the physical X axis, so the model must speak\n// physical-left in both directions. Stages align with physical auto margins\n// (mx-auto for center, ml-auto for end, plain flow for start), so:\n// - free space ≥ 0: center splits it; end pins right in both directions\n//   (ml-auto is physical); start follows flow (left in LTR, right in RTL).\n// - free space < 0 (the settled stage overflows the live container — the\n//   close leg's early frames): auto margins collapse to 0 and CSS resolves\n//   the over-constraint against the direction's end edge, pinning the box to\n//   the start edge — left edge at 0 in LTR, at the negative free space in\n//   RTL. The old unconditional max(0, …) clamp encoded only the LTR half and\n//   made the RTL close leg overshoot by the overflow amount.\nfunction getFileViewerStageInlineMargin({\n  align,\n  availableInlineSize,\n  direction,\n  stageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  availableInlineSize: number;\n  direction: FileViewerInlineDirection;\n  stageInlineSize: number;\n}) {\n  const freeInlineSize = availableInlineSize - stageInlineSize;\n  if (freeInlineSize < 0) return direction === \"rtl\" ? freeInlineSize : 0;\n\n  switch (align) {\n    case \"start\":\n      return direction === \"rtl\" ? freeInlineSize : 0;\n    case \"end\":\n      return freeInlineSize;\n    case \"center\":\n      return freeInlineSize / 2;\n  }\n}\n\nfunction formatFileViewerMotionPixel(value: number) {\n  return Number.isFinite(value) ? Number(value.toFixed(3)) : 0;\n}\n\nfunction formatFileViewerMotionScale(value: number) {\n  return Number.isFinite(value) ? String(Number(value.toFixed(6))) : \"1\";\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-fit-width-motion.ts"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-types.ts",
      "content": "import type * as React from \"react\";\n\nexport type ViewerSidebarMode = \"inline\" | \"overlay\";\nexport type ViewerSidebarRequestedMode = \"auto\" | ViewerSidebarMode;\nexport type ViewerSidebarGapTransition = \"width\" | \"none\";\nexport type ViewerSidebarState = \"expanded\" | \"collapsed\";\nexport type ViewerSidebarSide = \"left\" | \"right\";\nexport type ViewerSidebarCollapsible = \"offcanvas\" | \"none\";\nexport type ViewerDocumentFrameAlign = \"start\" | \"center\" | \"end\";\nexport type ViewerGeometryTransitionPhase = \"idle\" | \"sliding\";\n\nexport type ViewerDocumentReadingAnchorInput = {\n  scrollTop: number;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentReadingAnchorTarget<Anchor> = {\n  anchor: Anchor;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentTransitionSource =\n  | \"none\"\n  | \"viewer-shell\"\n  | \"document-layout\";\n\n// Commit-then-relax: layout commits its target inside the motion's first\n// frame and scroll rebases in the same commit, so there is no frozen layout\n// and no deferred scroll left in the vocabulary.\nexport type ViewerDocumentLayoutPolicy = \"live\" | \"target\";\nexport type ViewerDocumentScrollPolicy = \"preserve\" | \"rebase\";\nexport type ViewerDocumentVisualPolicy =\n  | \"none\"\n  | \"document-flip\"\n  | \"shell-transform\";\n\nexport type ViewerDocumentTransition = {\n  layoutPolicy: ViewerDocumentLayoutPolicy;\n  scrollPolicy: ViewerDocumentScrollPolicy;\n  source: ViewerDocumentTransitionSource;\n  transitionId: number | string | null;\n  visualPolicy: ViewerDocumentVisualPolicy;\n};\n\nexport type ViewerDocumentLayoutModel<Anchor> = {\n  blockSize: number;\n  captureReadingAnchor: (\n    input: ViewerDocumentReadingAnchorInput,\n  ) => Anchor | null;\n  getReadingAnchorScrollTop: (\n    target: ViewerDocumentReadingAnchorTarget<Anchor>,\n  ) => number | null;\n  inlineSize: number;\n  isTransitioning?: boolean;\n  transition?: ViewerDocumentTransition;\n};\n\nexport type ViewerDocumentPhysicalScrollPosition = {\n  physicalScrollTop: number;\n  scrollPageOffset: number;\n};\n\nexport type ViewerDocumentResolvedScrollTarget = {\n  left?: number;\n  top: number;\n};\n\nexport type ViewerDocumentScrollMapper = {\n  getLogicalScrollTop: (input: {\n    blockSize: number;\n    physicalScrollTop: number;\n    scrollPageOffset: number;\n    viewportBlockSize: number;\n  }) => number;\n  getPhysicalScrollSize: (input: {\n    blockSize: number;\n    viewportBlockSize: number;\n  }) => number;\n  resolvePhysicalScrollPosition: (input: {\n    blockSize: number;\n    logicalScrollTop: number;\n    scrollPageOffset: number;\n    viewportBlockSize: number;\n  }) => ViewerDocumentPhysicalScrollPosition;\n};\n\nexport type ViewerDocumentScrollMetrics = {\n  physicalScrollSize: number;\n  physicalScrollTop: number;\n  scrollPageOffset: number;\n  scrollTop: number;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentScrollTargetResolver<Anchor, Target> = (input: {\n  layout: ViewerDocumentLayoutModel<Anchor>;\n  scrollTop: number;\n  target: Target;\n  viewportElement: HTMLDivElement;\n}) => ViewerDocumentResolvedScrollTarget | null;\n\n// A zoom step is the one geometry change whose intent is \"zoom the camera\",\n// not \"keep my reading position\": it re-anchors the viewport CENTER on both\n// axes and relaxes a FLIP about that fixed point. `capture` runs in the zoom\n// gesture's own task against the pre-zoom layout and painted DOM;\n// `resolveScrollTarget` and `play` run inside the geometry commit against the\n// post-zoom layout (commit-then-relax).\nexport type ViewerDocumentZoomMotionBypassReason =\n  | \"resolve-failed\"\n  | \"shell-transition\"\n  | \"stale-intent\";\n\nexport type ViewerDocumentZoomMotionController<Transaction = unknown> = {\n  capture: (input: {\n    scrollTop: number;\n    viewportElement: HTMLDivElement;\n  }) => Transaction | null;\n  /**\n   * Telemetry tap: a captured zoom intent reached a geometry commit but the\n   * zoom lane declined it. Without this the bypass is invisible — the commit\n   * falls back to the reading-anchor restore and no flight is recorded.\n   */\n  noteBypass?: (reason: ViewerDocumentZoomMotionBypassReason) => void;\n  resolveScrollTarget: (input: {\n    transaction: Transaction;\n    viewportElement: HTMLDivElement;\n  }) => ViewerDocumentResolvedScrollTarget | null;\n  play: (input: {\n    transaction: Transaction;\n    viewportElement: HTMLDivElement;\n  }) => (() => void) | null;\n};\n\nexport type ViewerGeometrySnapshot = {\n  bodyInlineSize: number;\n  documentInlineSize: number;\n  hasMeasuredBody: boolean;\n  isTransitioning: boolean;\n  mode: ViewerSidebarMode;\n  open: boolean;\n  progress: number;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarInlineSize: number;\n  sidebarWidth: number;\n  side: ViewerSidebarSide;\n  state: ViewerSidebarState;\n  transitionPhase: ViewerGeometryTransitionPhase;\n};\n\nexport type ViewerGeometryStore = {\n  getSnapshot: () => ViewerGeometrySnapshot;\n  setTarget: (target: ViewerGeometryTarget) => void;\n  subscribe: (listener: () => void) => () => void;\n};\n\nexport type ViewerGeometryTarget = {\n  bodyElement: HTMLElement | null;\n  mode: ViewerSidebarMode;\n  open: boolean;\n  rootElement: HTMLElement | null;\n  sidebarElement: HTMLElement | null;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarWidth: number;\n  side: ViewerSidebarSide;\n  state: ViewerSidebarState;\n};\n\nexport type ViewerSidebarStateValue = {\n  state: ViewerSidebarState;\n  open: boolean;\n  setOpen: (value: boolean | ((open: boolean) => boolean)) => void;\n  toggleSidebar: () => void;\n  canToggleSidebar: boolean;\n  mode: ViewerSidebarMode;\n  side: ViewerSidebarSide;\n};\n\nexport type ViewerRootProps = React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  inlineBreakpoint?: number;\n  mode?: ViewerSidebarRequestedMode;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  sidebarCollapsible?: ViewerSidebarCollapsible;\n  sidebarGapTransition?: ViewerSidebarGapTransition;\n  sidebarSide?: ViewerSidebarSide;\n  stateNamespace?: ViewerStateAttributeNamespace;\n};\n\nexport type ViewerFrameProps = React.ComponentProps<\"div\">;\nexport type ViewerHeaderProps = React.ComponentProps<\"div\">;\nexport type ViewerBodyProps = React.ComponentProps<\"div\">;\nexport type ViewerSurfaceProps = React.ComponentProps<\"div\">;\nexport type ViewerViewportProps = React.ComponentProps<\"div\">;\nexport type ViewerDocumentFrameProps = React.ComponentProps<\"div\"> & {\n  align?: ViewerDocumentFrameAlign;\n  maxInlineSize?: React.CSSProperties[\"maxInlineSize\"];\n};\n\nexport type ViewerStateAttributeNamespace = {\n  prefix: string;\n  slots?: {\n    body?: boolean;\n    root?: boolean;\n    sidebar?: boolean;\n  };\n};\n\nexport type ViewerSidebarRegistration = {\n  collapsible: ViewerSidebarCollapsible;\n  element: HTMLElement;\n  id: string;\n  instanceId: string;\n  side: ViewerSidebarSide;\n  width: string;\n  widthPixels: number;\n};\n\nexport type ViewerPortalContainmentAttributes = {\n  \"data-viewer-portal-root-id\": string;\n};\n\nexport type ViewerSidebarRegistrationState = {\n  defaultSidebarCollapsible: ViewerSidebarCollapsible;\n  defaultSidebarSide: ViewerSidebarSide;\n  geometryStore: ViewerGeometryStore;\n  getRootElement: () => HTMLElement | null;\n  hasSidebar: boolean;\n  registerBody: (element: HTMLElement) => () => void;\n  registerSidebar: (registration: ViewerSidebarRegistration) => () => void;\n  rootId: string;\n  sidebarId: string;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarSide: ViewerSidebarSide;\n  setLastTriggerElement: (element: HTMLElement | null) => void;\n  stateNamespace?: ViewerStateAttributeNamespace;\n};\n\nexport type ViewerRootDiagnostics = {\n  getRootElement: () => HTMLElement | null;\n  layoutSignature: string;\n  rootId: string;\n};\n\nexport type ViewerSurfaceMeasurement = {\n  hasMeasured: boolean;\n  setViewportElement: React.RefCallback<HTMLDivElement>;\n  viewportElement: HTMLDivElement | null;\n  viewportHeight: number | null;\n  viewportWidth: number | null;\n};\n\nexport type ViewerSidebarSlotNames = {\n  container?: string;\n  gap?: string;\n  inner?: string;\n};\n\nexport type ViewerStateAttributeSlot = \"body\" | \"root\" | \"sidebar\";\nexport type ViewerStateAttributeValues = {\n  hasSidebar?: boolean;\n  sidebarCollapsible?: ViewerSidebarCollapsible;\n  sidebarMode?: ViewerSidebarMode;\n  sidebarOpen?: boolean;\n  sidebarSide?: ViewerSidebarSide;\n  sidebarState?: ViewerSidebarState;\n};\nexport type ViewerDataAttributes = Record<`data-${string}`, string | undefined>;\n\nexport type ViewerSidebarProps = React.ComponentProps<\"aside\"> &\n  ViewerDataAttributes & {\n    side?: ViewerSidebarSide;\n    collapsible?: ViewerSidebarCollapsible;\n    innerClassName?: string;\n    namespacedSlot?: string;\n    namespacedSlotNames?: ViewerSidebarSlotNames;\n    slotNames?: ViewerSidebarSlotNames;\n    width?: string;\n  };\n",
      "type": "registry:ui",
      "target": "@ui/viewer-types.ts"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-measurement.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport type StableElementSize<Element extends HTMLElement = HTMLElement> = {\n  element: Element | null;\n  hasMeasured: boolean;\n  height: number | null;\n  setElement: React.RefCallback<Element>;\n  width: number | null;\n};\n\nexport type StableElementSizeOptions = {\n  enabled?: boolean;\n  observe?: boolean;\n  retainLastNonZero?: boolean;\n};\n\nexport type StableCssLengthOptions = {\n  element: HTMLElement | null;\n  retainLastNonZero?: boolean;\n  value: string;\n};\n\ntype MeasuredSize = {\n  height: number | null;\n  width: number | null;\n};\n\ntype RawMeasuredSize = {\n  height: number;\n  width: number;\n};\n\n// DOM layout reads for viewer chrome are quarantined in this module. The\n// file-viewer motion kernel (time + style writes only) receives this reader by\n// injection from the frame controller instead of touching layout APIs itself.\nexport function readElementRectSnapshot(\n  element: HTMLElement | null,\n): readonly number[] {\n  if (!element) return [];\n  const rect = element.getBoundingClientRect();\n  return [rect.left, rect.top, rect.width, rect.height];\n}\n\n// Computed CSS inline direction of an element, sampled when it attaches (a\n// runtime `dir` flip is picked up on the next mount). The fit-width motion\n// transform works on the physical X axis, so the renderer frame needs to\n// know which edge auto-margin alignment pins the stage to.\nexport function useViewerInlineDirection(\n  element: HTMLElement | null,\n): \"ltr\" | \"rtl\" {\n  const [direction, setDirection] = React.useState<\"ltr\" | \"rtl\">(\"ltr\");\n\n  useKeyedLayoutEffect(element ? joinEffectKey([element]) : null, () => {\n    if (!element) return;\n    setDirection(getComputedStyle(element).direction === \"rtl\" ? \"rtl\" : \"ltr\");\n  });\n\n  return direction;\n}\n\nfunction readElementSize(element: HTMLElement): RawMeasuredSize {\n  const rect =\n    typeof element.getBoundingClientRect === \"function\"\n      ? element.getBoundingClientRect()\n      : null;\n\n  return {\n    height: rect?.height || element.clientHeight,\n    width: rect?.width || element.clientWidth,\n  };\n}\n\nfunction resolveMeasuredElementSize({\n  currentSize,\n  nextSize,\n  retainLastNonZero,\n}: {\n  currentSize: MeasuredSize;\n  nextSize: RawMeasuredSize;\n  retainLastNonZero: boolean;\n}): MeasuredSize {\n  const width =\n    Number.isFinite(nextSize.width) &&\n    (!retainLastNonZero || nextSize.width > 0)\n      ? nextSize.width\n      : currentSize.width;\n  const height =\n    Number.isFinite(nextSize.height) &&\n    (!retainLastNonZero || nextSize.height > 0)\n      ? nextSize.height\n      : currentSize.height;\n\n  if (currentSize.width === width && currentSize.height === height) {\n    return currentSize;\n  }\n\n  return { height, width };\n}\n\nexport function useStableElementSize<Element extends HTMLElement = HTMLElement>(\n  options: StableElementSizeOptions = {},\n): StableElementSize<Element> {\n  const enabled = options.enabled ?? true;\n  const observe = options.observe ?? true;\n  const retainLastNonZero = options.retainLastNonZero ?? false;\n  const [element, setElementState] = React.useState<Element | null>(null);\n  const [size, setSize] = React.useState<MeasuredSize>({\n    height: null,\n    width: null,\n  });\n  const hasMeasured = size.height !== null || size.width !== null;\n\n  const setElement = React.useCallback((nextElement: Element | null) => {\n    setElementState(nextElement);\n  }, []);\n\n  useKeyedLayoutEffect(enabled ? null : \"reset\", () => {\n    setSize({ height: null, width: null });\n  });\n\n  useKeyedLayoutEffect(\n    enabled && element\n      ? joinEffectKey([element, observe, retainLastNonZero])\n      : null,\n    () => {\n      if (!element) return;\n\n      setSize((currentSize) =>\n        resolveMeasuredElementSize({\n          currentSize,\n          nextSize: readElementSize(element),\n          retainLastNonZero,\n        }),\n      );\n\n      const ResizeObserverConstructor = observe\n        ? globalThis.ResizeObserver\n        : undefined;\n      if (typeof ResizeObserverConstructor === \"undefined\") return;\n\n      let frame = 0;\n      let latestSize = readElementSize(element);\n      const observer = new ResizeObserverConstructor((entries) => {\n        for (const entry of entries) {\n          latestSize = readElementSize(entry.target as HTMLElement);\n        }\n\n        if (frame) return;\n        frame = requestAnimationFrame(() => {\n          frame = 0;\n          setSize((currentSize) =>\n            resolveMeasuredElementSize({\n              currentSize,\n              nextSize: latestSize,\n              retainLastNonZero,\n            }),\n          );\n        });\n      });\n\n      observer.observe(element);\n\n      return () => {\n        if (frame) cancelAnimationFrame(frame);\n        observer.disconnect();\n      };\n    },\n  );\n\n  return React.useMemo(\n    () => ({\n      element,\n      hasMeasured,\n      height: size.height,\n      setElement,\n      width: size.width,\n    }),\n    [element, hasMeasured, setElement, size.height, size.width],\n  );\n}\n\nexport function useStableCssLength({\n  element,\n  retainLastNonZero = true,\n  value,\n}: StableCssLengthOptions) {\n  const [resolvedLength, setResolvedLength] = React.useState(0);\n\n  useKeyedLayoutEffect(\n    value ? joinEffectKey([element, retainLastNonZero, value]) : null,\n    () => {\n      const nextLength = resolveCssLength(value, element);\n\n      setResolvedLength((currentLength) => {\n        if (retainLastNonZero && nextLength <= 0) return currentLength;\n        return areCssLengthsEqual(currentLength, nextLength)\n          ? currentLength\n          : nextLength;\n      });\n    },\n  );\n\n  return resolvedLength;\n}\n\nfunction resolveCssLength(value: string, element: HTMLElement | null) {\n  const trimmedValue = value.trim();\n  const pixelMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)px$/);\n  if (pixelMatch) return Math.max(0, Number(pixelMatch[1]));\n\n  if (typeof window === \"undefined\") return 0;\n\n  const remMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)rem$/);\n  if (remMatch) {\n    return (\n      Math.max(0, Number(remMatch[1])) *\n      readComputedFontSize(window.document.documentElement)\n    );\n  }\n\n  const emMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)em$/);\n  if (emMatch) {\n    return Math.max(0, Number(emMatch[1])) * readComputedFontSize(element);\n  }\n\n  const measuringElement = window.document.createElement(\"div\");\n  measuringElement.style.contain = \"strict\";\n  measuringElement.style.inlineSize = trimmedValue;\n  measuringElement.style.position = \"absolute\";\n  measuringElement.style.visibility = \"hidden\";\n  (element ?? window.document.body).appendChild(measuringElement);\n  const width = readElementInlineSize(measuringElement);\n  measuringElement.remove();\n  return width;\n}\n\nfunction readElementInlineSize(element: HTMLElement) {\n  const rect =\n    typeof element.getBoundingClientRect === \"function\"\n      ? element.getBoundingClientRect()\n      : null;\n  const width = rect?.width || element.clientWidth || 0;\n  return Number.isFinite(width) && width > 0 ? width : 0;\n}\n\nfunction readComputedFontSize(element: Element | null) {\n  if (typeof window === \"undefined\") return 16;\n  const fontSize = element ? window.getComputedStyle(element).fontSize : \"16px\";\n  const value = Number.parseFloat(fontSize);\n  return Number.isFinite(value) && value > 0 ? value : 16;\n}\n\nfunction areCssLengthsEqual(previous: number, next: number) {\n  return Math.abs(previous - next) <= 0.001;\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-measurement.ts"
    }
  ],
  "type": "registry:ui"
}