{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "edit-viewer-block",
  "title": "Edit Viewer",
  "description": "Detected form fields beside a PDF source document, linked by their bbox — hover or select a field to highlight its region on the page; Preview stamps values over the source, and Filled renders a supplied output file.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@retab/pdf-viewer",
    "@retab/file-viewer",
    "@retab/scroll-area",
    "tabs",
    "@retab/segmented-document",
    "@retab/document-anchor"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/blocks/edit-viewer-block.tsx",
      "content": "\"use client\";\n\nimport { EditViewer } from \"@/components/viewers/edit/edit-viewer\";\nimport type { FormField } from \"@/components/viewers/lib/edit-types\";\nimport editSample from \"@/components/viewers/sample-data/edit.json\";\n\nconst ORIGINAL_PDF_URL = \"/samples/fidelity-edit/fidelity_original.pdf\";\n\n// A template-fill of the Fidelity \"Bank Wire Authorization\" form: 29 detected\n// form fields across 3 pages, each with a normalized bbox and an inferred value.\nconst EDIT_FIELDS = editSample as FormField[];\n\n/**\n * Edit viewer block — detected fields beside the source document, linked by\n * their bbox. `EditViewer` owns edit-specific modes, field visuals, search,\n * filters, and page grouping while segmented-document owns hover,\n * selection, and scroll-to-field behavior.\n */\nexport function EditViewerBlock() {\n  return (\n    <div className=\"bg-background flex h-full min-h-[680px] flex-col\">\n      <EditViewer\n        result={{ fields: EDIT_FIELDS }}\n        sourceDocument={{\n          src: ORIGINAL_PDF_URL,\n          mimeType: \"application/pdf\",\n          filename: \"fidelity-bank-wire-authorization.pdf\",\n        }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/blocks/edit-viewer-block.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer.tsx",
      "content": "\"use client\";\n\nexport {\n  EditViewer,\n  EditViewerDocument,\n  EditViewerFields,\n  EditViewerHeader,\n} from \"./edit-viewer-anatomy\";\nexport type {\n  EditViewerDocumentProps,\n  EditViewerFieldsProps,\n  EditViewerHeaderProps,\n} from \"./edit-viewer-anatomy\";\nexport { EditViewerProvider } from \"./edit-viewer-provider\";\nexport type { EditViewerProviderProps } from \"./edit-viewer-provider\";\nexport type { EditViewerDocumentTarget } from \"./edit-viewer-model\";\nexport { EditViewerControls } from \"./edit-viewer-controls\";\n\nexport type {\n  EditViewerField,\n  EditViewerDocumentSource,\n  EditViewerInputField,\n  EditViewerInputResult,\n  EditViewerMode,\n  EditViewerOptions,\n  EditViewerProps,\n  EditViewerResult,\n  EditViewerStatus,\n} from \"./edit-viewer-types\";\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-types.ts",
      "content": "import type { DocumentAnchor } from \"@/components/ui/document-anchor\";\nimport type { BBox } from \"@/components/viewers/lib/edit-types\";\n\nexport type EditViewerMode = \"source\" | \"preview\" | \"filled\";\n\nexport type EditViewerStatus =\n  | { state: \"idle\" }\n  | { state: \"detecting\"; message?: string }\n  | { state: \"filling\"; message?: string }\n  | { state: \"error\"; message: string };\n\nexport type EditViewerFieldTargetStatus =\n  | { state: \"resolved\" }\n  | { state: \"missing\" }\n  | { state: \"invalid\"; reason: string };\n\nexport interface EditViewerOptions {\n  fieldPanel?: boolean;\n  search?: boolean;\n  filters?: boolean;\n  preview?: boolean;\n  filledOutput?: boolean;\n}\n\nexport interface EditViewerField {\n  key: string;\n  description?: string;\n  type: \"text\" | \"checkbox\";\n  value?: string | boolean | null;\n  target: DocumentAnchor | null;\n  targetStatus: EditViewerFieldTargetStatus;\n  bbox?: BBox;\n  combing?: boolean;\n  maxLength?: number;\n}\n\nexport interface EditViewerResult {\n  fields: EditViewerField[];\n  editType?: \"agent\" | \"template\";\n}\n\nexport interface EditViewerInputField {\n  key?: string;\n  description?: string;\n  type?: \"text\" | \"checkbox\";\n  value?: string | boolean | null;\n  target?: DocumentAnchor | null;\n  bbox?: BBox;\n  combing?: boolean;\n  maxLength?: number;\n  max_length?: number;\n}\n\nexport interface EditViewerInputResult {\n  fields?: readonly EditViewerInputField[] | null;\n  editType?: \"agent\" | \"template\";\n}\n\nexport interface EditViewerDocumentSource {\n  buffer?: ArrayBuffer | null;\n  src?: string | null;\n  mimeType: string;\n  filename?: string;\n}\n\nexport interface EditViewerProps {\n  result: EditViewerInputResult | null;\n  sourceDocument?: EditViewerDocumentSource | null;\n  filledDocument?: EditViewerDocumentSource | null;\n  mode?: EditViewerMode | null;\n  onModeChange?: (mode: EditViewerMode) => void;\n  selectedFieldKey?: string | null;\n  onSelectedFieldKeyChange?: (key: string | null) => void;\n  status?: EditViewerStatus;\n  className?: string;\n  options?: EditViewerOptions;\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-types.ts"
    },
    {
      "path": "components/viewers/edit/edit-viewer-model.ts",
      "content": "import type {\n  DocumentAnchor,\n  PdfAreaAnchor,\n} from \"@/components/ui/document-anchor\";\nimport {\n  createSegmentedDocumentModel,\n  type DocumentSegment,\n  type SegmentAnchor,\n  type SegmentedDocumentModel,\n} from \"@/components/ui/segmented-document-model\";\nimport type { BBox } from \"@/components/viewers/lib/edit-types\";\n\nimport type {\n  EditViewerDocumentSource,\n  EditViewerField,\n  EditViewerFieldTargetStatus,\n  EditViewerInputField,\n  EditViewerInputResult,\n  EditViewerMode,\n  EditViewerOptions,\n  EditViewerResult,\n  EditViewerStatus,\n} from \"./edit-viewer-types\";\n\nexport type EditViewerFilter =\n  | \"all\"\n  | \"filled\"\n  | \"empty\"\n  | \"text\"\n  | \"checkbox\"\n  | \"no_location\";\n\nexport interface EditViewerModeInput {\n  fields: readonly EditViewerField[];\n  sourceDocument?: EditViewerDocumentSource | null;\n  filledDocument?: EditViewerDocumentSource | null;\n  options: Required<EditViewerOptions>;\n}\n\nexport type EditViewerDocumentTarget =\n  | { kind: \"error\"; message: string }\n  | { kind: \"empty\"; message: string }\n  | { kind: \"source\"; document: EditViewerDocumentSource; showOverlay: false }\n  | { kind: \"preview\"; document: EditViewerDocumentSource; showOverlay: true }\n  | { kind: \"filled\"; document: EditViewerDocumentSource; showOverlay: false };\n\nconst DEFAULT_OPTIONS: Required<EditViewerOptions> = {\n  fieldPanel: true,\n  search: true,\n  filters: true,\n  preview: true,\n  filledOutput: true,\n};\n\nconst MODE_FALLBACK_ORDER: EditViewerMode[] = [\"filled\", \"preview\", \"source\"];\n\nconst MODE_DISPLAY_ORDER: EditViewerMode[] = [\"source\", \"preview\", \"filled\"];\n\nexport function resolveEditViewerOptions(\n  options: EditViewerOptions | undefined,\n): Required<EditViewerOptions> {\n  return { ...DEFAULT_OPTIONS, ...options };\n}\n\nexport function normalizeEditViewerResult(\n  result: EditViewerInputResult | null | undefined,\n): EditViewerResult {\n  const fields = result?.fields ?? [];\n  return {\n    fields: fields.map(normalizeEditViewerField),\n    editType: result?.editType,\n  };\n}\n\nfunction normalizeEditViewerField(\n  field: EditViewerInputField,\n  index: number,\n): EditViewerField {\n  const key = field.key || `field_${index}`;\n  const location = normalizeEditViewerFieldLocation(field);\n  return {\n    key,\n    description: field.description || key || `Field ${index + 1}`,\n    type: field.type === \"checkbox\" ? \"checkbox\" : \"text\",\n    value: normalizeFieldValue(field.value),\n    target: location.target,\n    targetStatus: location.targetStatus,\n    bbox: location.bbox,\n    combing: field.combing,\n    maxLength: normalizeMaxLength(field),\n  };\n}\n\nfunction normalizeEditViewerFieldLocation(field: EditViewerInputField): {\n  bbox?: BBox;\n  target: DocumentAnchor | null;\n  targetStatus: EditViewerFieldTargetStatus;\n} {\n  if (field.target !== undefined) {\n    return {\n      target: field.target,\n      targetStatus: field.target ? { state: \"resolved\" } : { state: \"missing\" },\n    };\n  }\n\n  if (!field.bbox) {\n    return { target: null, targetStatus: { state: \"missing\" } };\n  }\n\n  const bbox = normalizeBBox(field.bbox);\n  if (!bbox) {\n    return {\n      target: null,\n      targetStatus: { state: \"invalid\", reason: \"Invalid field bbox\" },\n    };\n  }\n\n  return {\n    bbox,\n    target: editFieldTargetFromBBox(bbox),\n    targetStatus: { state: \"resolved\" },\n  };\n}\n\nexport function editFieldTargetFromBBox(\n  bbox: BBox | null | undefined,\n): DocumentAnchor | null {\n  if (!bbox) return null;\n  return {\n    kind: \"pdf-area\",\n    pageNumber: bbox.page,\n    left: bbox.left * 100,\n    top: bbox.top * 100,\n    width: bbox.width * 100,\n    height: bbox.height * 100,\n  };\n}\n\nfunction normalizeFieldValue(value: string | boolean | null | undefined) {\n  if (typeof value === \"boolean\") return value;\n  if (value === null || value === undefined) return null;\n  return String(value);\n}\n\nfunction normalizeMaxLength(field: EditViewerInputField) {\n  if (\"maxLength\" in field && typeof field.maxLength === \"number\") {\n    return field.maxLength;\n  }\n  if (\"max_length\" in field && typeof field.max_length === \"number\") {\n    return field.max_length;\n  }\n  return undefined;\n}\n\nfunction normalizeBBox(bbox: BBox): BBox | undefined {\n  const page = Number(bbox.page);\n  const left = Number(bbox.left);\n  const top = Number(bbox.top);\n  const width = Number(bbox.width);\n  const height = Number(bbox.height);\n\n  if (\n    !Number.isFinite(page) ||\n    !Number.isFinite(left) ||\n    !Number.isFinite(top) ||\n    !Number.isFinite(width) ||\n    !Number.isFinite(height) ||\n    page < 1 ||\n    width <= 0 ||\n    height <= 0\n  ) {\n    return undefined;\n  }\n\n  const normalizedLeft = clamp01(left);\n  const normalizedTop = clamp01(top);\n  const normalizedWidth = Math.min(clamp01(width), 1 - normalizedLeft);\n  const normalizedHeight = Math.min(clamp01(height), 1 - normalizedTop);\n\n  if (normalizedWidth <= 0 || normalizedHeight <= 0) return undefined;\n\n  return {\n    page: Math.round(page),\n    left: normalizedLeft,\n    top: normalizedTop,\n    width: normalizedWidth,\n    height: normalizedHeight,\n  };\n}\n\nfunction clamp01(value: number) {\n  if (!Number.isFinite(value)) return 0;\n  return Math.max(0, Math.min(1, value));\n}\n\nexport function isEditFieldChecked(field: EditViewerField) {\n  if (typeof field.value === \"boolean\") return field.value;\n  if (typeof field.value !== \"string\") return false;\n  const value = field.value.trim().toLowerCase();\n  return (\n    value === \"true\" || value === \"checked\" || value === \"yes\" || value === \"1\"\n  );\n}\n\nexport function isEditFieldFilled(field: EditViewerField) {\n  if (field.type === \"checkbox\") return isEditFieldChecked(field);\n  return Boolean(\n    typeof field.value === \"string\"\n      ? field.value.trim().length > 0\n      : field.value !== null && field.value !== undefined,\n  );\n}\n\nexport function displayEditFieldValue(field: EditViewerField): string {\n  if (field.type === \"checkbox\") {\n    return isEditFieldChecked(field) ? \"Checked\" : \"Unchecked\";\n  }\n  if (field.value === null || field.value === undefined) return \"\";\n  return String(field.value).trim();\n}\n\nexport function deriveEditViewerModes(input: EditViewerModeInput) {\n  const hasPreviewTargets = input.fields.some((field) =>\n    Boolean(getEditViewerPdfAreaAnchor(field)),\n  );\n  const hasSource = Boolean(input.sourceDocument);\n  const filledOutputAvailable = Boolean(input.filledDocument);\n  const modes: EditViewerMode[] = [];\n\n  if (hasSource) {\n    modes.push(\"source\");\n  }\n\n  if (\n    input.options.preview &&\n    hasSource &&\n    hasPreviewTargets &&\n    input.sourceDocument &&\n    canPreviewEditViewerDocument(input.sourceDocument)\n  ) {\n    modes.push(\"preview\");\n  }\n\n  if (input.options.filledOutput && filledOutputAvailable) {\n    modes.push(\"filled\");\n  }\n\n  return sortModes(modes);\n}\n\nfunction sortModes(modes: EditViewerMode[]) {\n  const rank = new Map(MODE_DISPLAY_ORDER.map((mode, index) => [mode, index]));\n  return Array.from(new Set(modes)).sort(\n    (a, b) => (rank.get(a) ?? 99) - (rank.get(b) ?? 99),\n  );\n}\n\nexport function resolveEditViewerMode({\n  availableModes,\n  requestedMode,\n  currentMode,\n}: {\n  availableModes: readonly EditViewerMode[];\n  requestedMode?: EditViewerMode | null;\n  currentMode?: EditViewerMode | null;\n}): EditViewerMode | null {\n  if (requestedMode && availableModes.includes(requestedMode)) {\n    return requestedMode;\n  }\n  if (currentMode && availableModes.includes(currentMode)) {\n    return currentMode;\n  }\n  return (\n    MODE_FALLBACK_ORDER.find((mode) => availableModes.includes(mode)) ?? null\n  );\n}\n\nexport function filterEditViewerFields({\n  fields,\n  query,\n  filter,\n}: {\n  fields: readonly EditViewerField[];\n  query?: string;\n  filter?: EditViewerFilter;\n}) {\n  const normalizedQuery = (query ?? \"\").trim().toLowerCase();\n  const resolvedFilter = filter ?? \"all\";\n\n  return fields.filter((field) => {\n    if (resolvedFilter === \"filled\" && !isEditFieldFilled(field)) return false;\n    if (resolvedFilter === \"empty\" && isEditFieldFilled(field)) return false;\n    if (resolvedFilter === \"text\" && field.type !== \"text\") return false;\n    if (resolvedFilter === \"checkbox\" && field.type !== \"checkbox\") {\n      return false;\n    }\n    if (resolvedFilter === \"no_location\" && field.target) return false;\n    if (!normalizedQuery) return true;\n    return (\n      field.key.toLowerCase().includes(normalizedQuery) ||\n      (field.description ?? \"\").toLowerCase().includes(normalizedQuery) ||\n      displayEditFieldValue(field).toLowerCase().includes(normalizedQuery)\n    );\n  });\n}\n\nexport interface EditViewerFieldGroup {\n  key: string;\n  label: string;\n  page: number | null;\n  fields: EditViewerField[];\n}\n\nexport type EditViewerFieldProjection = {\n  fields: readonly EditViewerField[];\n  fieldByKey: ReadonlyMap<string, EditViewerField>;\n  fieldsByPage: ReadonlyMap<number, readonly EditViewerField[]>;\n  locatedFields: readonly EditViewerField[];\n  unlocatedFields: readonly EditViewerField[];\n  visibleFields: readonly EditViewerField[];\n  fieldGroups: readonly EditViewerFieldGroup[];\n  fieldCount: number;\n  visibleFieldCount: number;\n  filledCount: number;\n};\n\nexport function createEditViewerFieldProjection({\n  fields,\n  query,\n  filter,\n}: {\n  fields: readonly EditViewerField[];\n  query: string;\n  filter: EditViewerFilter;\n}): EditViewerFieldProjection {\n  const visibleFields = filterEditViewerFields({ fields, query, filter });\n  return {\n    fields,\n    fieldByKey: createEditViewerFieldMap(fields),\n    fieldsByPage: groupLocatedEditViewerFieldsByPage(fields),\n    locatedFields: fields.filter((field) => Boolean(field.target)),\n    unlocatedFields: fields.filter((field) => !field.target),\n    visibleFields,\n    fieldGroups: groupEditViewerFieldsByPage(visibleFields),\n    fieldCount: fields.length,\n    visibleFieldCount: visibleFields.length,\n    filledCount: fields.filter(isEditFieldFilled).length,\n  };\n}\n\nexport function createEditViewerFieldMap(fields: readonly EditViewerField[]) {\n  const fieldByKey = new Map<string, EditViewerField>();\n  for (const field of fields) {\n    if (!fieldByKey.has(field.key)) {\n      fieldByKey.set(field.key, field);\n    }\n  }\n  return fieldByKey;\n}\n\nexport function createEditViewerSegmentedDocumentModel(\n  fields: readonly EditViewerField[],\n): SegmentedDocumentModel {\n  const segments: DocumentSegment[] = [];\n  const anchors: SegmentAnchor[] = [];\n\n  fields.forEach((field, index) => {\n    const pdfAnchor = getEditViewerPdfAreaAnchor(field);\n    const segment: DocumentSegment = {\n      id: editViewerSegmentId(field.key, index),\n      label: field.description || field.key,\n      pages: pdfAnchor ? [pdfAnchor.pageNumber] : [],\n      color: field.type === \"checkbox\" ? \"var(--chart-2)\" : \"var(--chart-1)\",\n      index,\n      sourceId: field.key,\n    };\n\n    if (pdfAnchor) {\n      anchors.push(editViewerPdfAnchorToSegmentAnchor(pdfAnchor, segment.id));\n    }\n\n    segments.push(segment);\n  });\n\n  return createSegmentedDocumentModel({ anchors, segments });\n}\n\nfunction editViewerSegmentId(fieldKey: string, index: number) {\n  return `edit:${fieldKey || \"field\"}:${index}`;\n}\n\nfunction editViewerPdfAnchorToSegmentAnchor(\n  anchor: PdfAreaAnchor,\n  segmentId: string,\n): SegmentAnchor {\n  return {\n    id: `${segmentId}:anchor`,\n    segmentId,\n    pageNumber: anchor.pageNumber,\n    bounds: {\n      x: pdfPercentToUnit(anchor.left),\n      y: pdfPercentToUnit(anchor.top),\n      width: pdfPercentToUnit(anchor.width),\n      height: pdfPercentToUnit(anchor.height),\n    },\n  };\n}\n\nfunction pdfPercentToUnit(value: number) {\n  if (!Number.isFinite(value)) return 0;\n  return value > 1 ? value / 100 : value;\n}\n\nexport function groupEditViewerFieldsByPage(\n  fields: readonly EditViewerField[],\n): EditViewerFieldGroup[] {\n  const pageGroups = groupLocatedEditViewerFieldsByPage(fields);\n  const unlocatedFields: EditViewerField[] = [];\n\n  for (const field of fields) {\n    if (!field.target) {\n      unlocatedFields.push(field);\n    }\n  }\n\n  const groups: EditViewerFieldGroup[] = Array.from(pageGroups.entries())\n    .sort((a, b) => a[0] - b[0])\n    .map(([page, pageFields]) => ({\n      key: `page:${page}`,\n      label: `Page ${page}`,\n      page,\n      fields: pageFields,\n    }));\n\n  if (unlocatedFields.length > 0) {\n    groups.push({\n      key: \"no-location\",\n      label: \"No location\",\n      page: null,\n      fields: unlocatedFields,\n    });\n  }\n\n  return groups;\n}\n\nexport function groupLocatedEditViewerFieldsByPage(\n  fields: readonly EditViewerField[],\n): Map<number, EditViewerField[]> {\n  const pageGroups = new Map<number, EditViewerField[]>();\n\n  for (const field of fields) {\n    const anchor = getEditViewerPdfAreaAnchor(field);\n    if (!anchor) continue;\n    const pageFields = pageGroups.get(anchor.pageNumber) ?? [];\n    pageFields.push(field);\n    pageGroups.set(anchor.pageNumber, pageFields);\n  }\n\n  return pageGroups;\n}\n\nexport function getEditViewerPdfAreaAnchor(\n  field: EditViewerField,\n): PdfAreaAnchor | null {\n  return field.target?.kind === \"pdf-area\" ? field.target : null;\n}\n\nexport function resolveEditViewerDocumentTarget({\n  filledDocument,\n  mode,\n  sourceDocument,\n  status,\n}: {\n  filledDocument: EditViewerDocumentSource | null;\n  mode: EditViewerMode | null;\n  sourceDocument: EditViewerDocumentSource | null;\n  status: EditViewerStatus;\n}): EditViewerDocumentTarget {\n  if (status.state === \"error\") {\n    return { kind: \"error\", message: status.message };\n  }\n\n  if (mode === \"filled\" && filledDocument) {\n    return { kind: \"filled\", document: filledDocument, showOverlay: false };\n  }\n\n  if (mode === \"preview\" && sourceDocument) {\n    return { kind: \"preview\", document: sourceDocument, showOverlay: true };\n  }\n\n  if (mode === \"source\" && sourceDocument) {\n    return { kind: \"source\", document: sourceDocument, showOverlay: false };\n  }\n\n  if (!mode) {\n    return { kind: \"empty\", message: \"No edit view is available.\" };\n  }\n\n  return { kind: \"empty\", message: \"Document preview is unavailable.\" };\n}\n\nexport function canPreviewEditViewerDocument(\n  document: EditViewerDocumentSource,\n) {\n  const filename = document.filename ?? \"\";\n  return (\n    document.mimeType.toLowerCase().includes(\"application/pdf\") ||\n    filename.toLowerCase().endsWith(\".pdf\")\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-model.ts"
    },
    {
      "path": "components/viewers/edit/edit-viewer-field-style.ts",
      "content": "import type { EditViewerField } from \"./edit-viewer-types\";\n\nexport const EDIT_FIELD_ACCENTS: Record<\n  EditViewerField[\"type\"],\n  { line: string; tint: string; text: string; badge: string }\n> = {\n  text: {\n    line: \"var(--color-chart-3)\",\n    tint: \"color-mix(in oklab, var(--color-chart-3) 12%, transparent)\",\n    text: \"var(--color-chart-4)\",\n    badge: \"border-chart-3/30 bg-chart-3/10 text-chart-4 dark:text-chart-2\",\n  },\n  checkbox: {\n    line: \"var(--color-amber-500)\",\n    tint: \"color-mix(in oklab, var(--color-amber-500) 14%, transparent)\",\n    text: \"var(--color-amber-600)\",\n    badge: \"border-warning/30 bg-warning/10 text-warning-foreground\",\n  },\n};\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-field-style.ts"
    },
    {
      "path": "components/viewers/edit/edit-viewer-store.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type {\n  PageOverlayProps,\n  PdfViewerHandle,\n} from \"@/components/ui/pdf-viewer\";\n\nimport type {\n  EditViewerDocumentTarget,\n  EditViewerFieldGroup,\n  EditViewerFilter,\n} from \"./edit-viewer-model\";\nimport type {\n  EditViewerDocumentSource,\n  EditViewerField,\n  EditViewerMode,\n  EditViewerOptions,\n  EditViewerResult,\n  EditViewerStatus,\n} from \"./edit-viewer-types\";\n\nexport type EditViewerProviderState = {\n  status: EditViewerStatus;\n  result: EditViewerResult;\n  fields: readonly EditViewerField[];\n  filledCount: number;\n  hasOutput: boolean;\n  fieldByKey: ReadonlyMap<string, EditViewerField>;\n  fieldsByPage: ReadonlyMap<number, readonly EditViewerField[]>;\n};\n\nexport type EditViewerModeState = {\n  mode: EditViewerMode | null;\n  modes: readonly EditViewerMode[];\n  setMode: (mode: EditViewerMode) => void;\n};\n\nexport type EditViewerFieldsState = {\n  fields: readonly EditViewerField[];\n  visibleFields: readonly EditViewerField[];\n  fieldGroups: readonly EditViewerFieldGroup[];\n  locatedFields: readonly EditViewerField[];\n  unlocatedFields: readonly EditViewerField[];\n  filledCount: number;\n  fieldCount: number;\n  visibleFieldCount: number;\n  query: string;\n  setQuery: (query: string) => void;\n  filter: EditViewerFilter;\n  setFilter: (filter: EditViewerFilter) => void;\n  canSearch: boolean;\n  canFilter: boolean;\n};\n\nexport type EditViewerSelectionState = {\n  selectedFieldKey: string | null;\n  activeFieldKey: string | null;\n  selectField: (fieldKey: string) => void;\n  clearFieldSelection: () => void;\n  previewField: (fieldKey: string | null) => void;\n};\n\nexport type EditViewerDocumentState = {\n  target: EditViewerDocumentTarget;\n  mode: EditViewerMode | null;\n  sourceDocument: EditViewerDocumentSource | null;\n  filledDocument: EditViewerDocumentSource | null;\n  viewerRef: React.RefObject<PdfViewerHandle | null>;\n  renderPageOverlay: (props: PageOverlayProps) => React.ReactNode;\n};\n\nexport type EditStore = {\n  state: EditViewerProviderState;\n  mode: EditViewerModeState;\n  fields: EditViewerFieldsState;\n  selection: EditViewerSelectionState;\n  document: EditViewerDocumentState;\n  options: Required<EditViewerOptions>;\n};\n\nconst EditStoreContext = React.createContext<EditStore | null>(null);\n\nexport function EditStoreProvider({\n  children,\n  value,\n}: {\n  children: React.ReactNode;\n  value: EditStore;\n}) {\n  return (\n    <EditStoreContext.Provider value={value}>\n      {children}\n    </EditStoreContext.Provider>\n  );\n}\n\nexport function useEditStore(): EditStore {\n  const store = React.useContext(EditStoreContext);\n  if (!store) {\n    throw new Error(\"EditViewer parts must be used within EditViewerProvider.\");\n  }\n  return store;\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-store.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-provider.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type {\n  PageOverlayProps,\n  PdfViewerHandle,\n} from \"@/components/ui/pdf-viewer\";\nimport { SegmentedDocumentProvider } from \"@/components/ui/segmented-document-provider\";\nimport { useSegmentedItemLink } from \"@/components/ui/segmented-item-link\";\n\nimport {\n  createEditViewerFieldProjection,\n  createEditViewerSegmentedDocumentModel,\n  deriveEditViewerModes,\n  normalizeEditViewerResult,\n  resolveEditViewerDocumentTarget,\n  resolveEditViewerMode,\n  resolveEditViewerOptions,\n  type EditViewerDocumentTarget,\n  type EditViewerFieldGroup,\n  type EditViewerFieldProjection,\n  type EditViewerFilter,\n} from \"./edit-viewer-model\";\nimport { EditFieldOverlayLayer } from \"./edit-viewer-overlays\";\nimport {\n  EditStoreProvider,\n  type EditStore,\n  type EditViewerDocumentState,\n  type EditViewerFieldsState,\n  type EditViewerModeState,\n  type EditViewerProviderState,\n  type EditViewerSelectionState,\n} from \"./edit-viewer-store\";\nimport type {\n  EditViewerDocumentSource,\n  EditViewerField,\n  EditViewerMode,\n  EditViewerOptions,\n  EditViewerProps,\n  EditViewerResult,\n  EditViewerStatus,\n} from \"./edit-viewer-types\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport type EditViewerProviderProps = Omit<EditViewerProps, \"className\"> & {\n  children: React.ReactNode;\n};\n\nexport function EditViewerProvider({\n  result,\n  sourceDocument = null,\n  filledDocument = null,\n  mode,\n  onModeChange,\n  selectedFieldKey,\n  onSelectedFieldKeyChange,\n  status = { state: \"idle\" },\n  options,\n  children,\n}: EditViewerProviderProps) {\n  const viewerRef = React.useRef<PdfViewerHandle>(null);\n  const resolvedOptions = React.useMemo(\n    () => resolveEditViewerOptions(options),\n    [options],\n  );\n  const editResult = React.useMemo(\n    () => normalizeEditViewerResult(result),\n    [result],\n  );\n  const [query, setQuery] = React.useState(\"\");\n  const [filter, setFilter] = React.useState<EditViewerFilter>(\"all\");\n  const fieldProjection = React.useMemo(\n    () =>\n      createEditViewerFieldProjection({\n        fields: editResult.fields,\n        query,\n        filter,\n      }),\n    [editResult.fields, filter, query],\n  );\n  const modeState = useEditViewerModeState({\n    fields: fieldProjection.fields,\n    sourceDocument,\n    filledDocument,\n    options: resolvedOptions,\n    mode,\n    onModeChange,\n  });\n  const documentTarget = React.useMemo(\n    () =>\n      resolveEditViewerDocumentTarget({\n        filledDocument,\n        mode: modeState.mode,\n        sourceDocument,\n        status,\n      }),\n    [filledDocument, modeState.mode, sourceDocument, status],\n  );\n  const segmentedDocument = React.useMemo(\n    () => createEditViewerSegmentedDocumentModel(fieldProjection.fields),\n    [fieldProjection.fields],\n  );\n\n  return (\n    <SegmentedDocumentProvider model={segmentedDocument}>\n      <EditViewerResolvedProvider\n        documentTarget={documentTarget}\n        editResult={editResult}\n        filledDocument={filledDocument}\n        filter={filter}\n        fieldProjection={fieldProjection}\n        modeState={modeState}\n        onSelectedFieldKeyChange={onSelectedFieldKeyChange}\n        options={resolvedOptions}\n        query={query}\n        selectedFieldKey={selectedFieldKey}\n        setFilter={setFilter}\n        setQuery={setQuery}\n        sourceDocument={sourceDocument}\n        status={status}\n        viewerRef={viewerRef}\n      >\n        {children}\n      </EditViewerResolvedProvider>\n    </SegmentedDocumentProvider>\n  );\n}\n\nfunction useEditViewerModeState({\n  fields,\n  filledDocument,\n  mode,\n  onModeChange,\n  options,\n  sourceDocument,\n}: {\n  fields: readonly EditViewerField[];\n  filledDocument: EditViewerDocumentSource | null;\n  mode?: EditViewerMode | null;\n  onModeChange?: EditViewerProps[\"onModeChange\"];\n  options: Required<EditViewerOptions>;\n  sourceDocument: EditViewerDocumentSource | null;\n}): EditViewerModeState {\n  const modes = React.useMemo(\n    () =>\n      deriveEditViewerModes({\n        fields,\n        sourceDocument,\n        filledDocument,\n        options,\n      }),\n    [fields, filledDocument, options, sourceDocument],\n  );\n  const isModeControlled = mode !== undefined;\n  const [uncontrolledMode, setUncontrolledMode] =\n    React.useState<EditViewerMode | null>(null);\n  const resolvedMode = resolveEditViewerMode({\n    availableModes: modes,\n    requestedMode: mode,\n    currentMode: isModeControlled ? null : uncontrolledMode,\n  });\n\n  useKeyedMountEffect(\n    joinEffectKey([isModeControlled, resolvedMode, uncontrolledMode]),\n    () => {\n      if (isModeControlled || uncontrolledMode === resolvedMode) return;\n      setUncontrolledMode(resolvedMode);\n    },\n  );\n\n  const setMode = React.useCallback(\n    (nextMode: EditViewerMode) => {\n      if (!modes.includes(nextMode)) return;\n      if (!isModeControlled) {\n        setUncontrolledMode(nextMode);\n      }\n      onModeChange?.(nextMode);\n    },\n    [isModeControlled, modes, onModeChange],\n  );\n\n  return React.useMemo(\n    () => ({\n      mode: resolvedMode,\n      modes,\n      setMode,\n    }),\n    [modes, resolvedMode, setMode],\n  );\n}\n\nfunction EditViewerResolvedProvider({\n  children,\n  documentTarget,\n  editResult,\n  filledDocument,\n  filter,\n  fieldProjection,\n  modeState,\n  onSelectedFieldKeyChange,\n  options,\n  query,\n  selectedFieldKey,\n  setFilter,\n  setQuery,\n  sourceDocument,\n  status,\n  viewerRef,\n}: {\n  children: React.ReactNode;\n  documentTarget: EditViewerDocumentTarget;\n  editResult: EditViewerResult;\n  fieldProjection: EditViewerFieldProjection;\n  filledDocument: EditViewerDocumentSource | null;\n  filter: EditViewerFilter;\n  modeState: EditViewerModeState;\n  onSelectedFieldKeyChange?: EditViewerProps[\"onSelectedFieldKeyChange\"];\n  options: Required<EditViewerOptions>;\n  query: string;\n  selectedFieldKey?: string | null;\n  setFilter: (filter: EditViewerFilter) => void;\n  setQuery: (query: string) => void;\n  sourceDocument: EditViewerDocumentSource | null;\n  status: EditViewerStatus;\n  viewerRef: React.RefObject<PdfViewerHandle | null>;\n}) {\n  const {\n    fieldByKey,\n    fieldGroups,\n    fields,\n    fieldsByPage,\n    filledCount,\n    locatedFields,\n    unlocatedFields,\n    visibleFields,\n  } = fieldProjection;\n  const selection = useEditViewerSelectionBridge({\n    fieldByKey,\n    onSelectedFieldKeyChange,\n    selectedFieldKey,\n  });\n  const renderPageOverlay = useEditViewerPageOverlay({\n    activeFieldKey: selection.activeFieldKey,\n    fieldsByPage,\n    mode: modeState.mode,\n    previewField: selection.previewField,\n    selectField: selection.selectField,\n  });\n  const state = React.useMemo<EditViewerProviderState>(\n    () => ({\n      status,\n      result: editResult,\n      fields,\n      fieldByKey,\n      fieldsByPage,\n      filledCount,\n      hasOutput:\n        status.state === \"error\" ||\n        modeState.modes.length > 0 ||\n        fields.length > 0,\n    }),\n    [\n      editResult,\n      fieldByKey,\n      fields,\n      fieldsByPage,\n      filledCount,\n      modeState.modes.length,\n      status,\n    ],\n  );\n  const fieldsState = React.useMemo<EditViewerFieldsState>(\n    () => ({\n      fields,\n      visibleFields,\n      fieldGroups,\n      locatedFields,\n      unlocatedFields,\n      filledCount,\n      fieldCount: fieldProjection.fieldCount,\n      visibleFieldCount: fieldProjection.visibleFieldCount,\n      query,\n      setQuery,\n      filter,\n      setFilter,\n      canSearch: options.search,\n      canFilter: options.filters,\n    }),\n    [\n      fieldGroups,\n      fieldProjection.fieldCount,\n      fieldProjection.visibleFieldCount,\n      fields,\n      filledCount,\n      filter,\n      locatedFields,\n      options.filters,\n      options.search,\n      query,\n      setFilter,\n      setQuery,\n      unlocatedFields,\n      visibleFields,\n    ],\n  );\n  const selectionState = React.useMemo<EditViewerSelectionState>(\n    () => selection,\n    [selection],\n  );\n  const documentState = React.useMemo<EditViewerDocumentState>(\n    () => ({\n      target: documentTarget,\n      mode: modeState.mode,\n      sourceDocument,\n      filledDocument,\n      viewerRef,\n      renderPageOverlay,\n    }),\n    [\n      documentTarget,\n      filledDocument,\n      modeState.mode,\n      renderPageOverlay,\n      sourceDocument,\n      viewerRef,\n    ],\n  );\n  const value = React.useMemo<EditStore>(\n    () => ({\n      state,\n      mode: modeState,\n      fields: fieldsState,\n      selection: selectionState,\n      document: documentState,\n      options,\n    }),\n    [documentState, fieldsState, modeState, options, selectionState, state],\n  );\n\n  return <EditStoreProvider value={value}>{children}</EditStoreProvider>;\n}\n\nfunction useEditViewerSelectionBridge({\n  fieldByKey,\n  onSelectedFieldKeyChange,\n  selectedFieldKey,\n}: {\n  fieldByKey: ReadonlyMap<string, EditViewerField>;\n  onSelectedFieldKeyChange?: EditViewerProps[\"onSelectedFieldKeyChange\"];\n  selectedFieldKey?: string | null;\n}): EditViewerSelectionState {\n  const {\n    activeItemId,\n    navigateItem,\n    previewItem,\n    selectItem,\n    selectedItemId,\n  } = useSegmentedItemLink({ initialItemId: selectedFieldKey });\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      fieldByKey,\n      onSelectedFieldKeyChange,\n      selectItem,\n      selectedFieldKey,\n    ]),\n    () => {\n      if (selectedFieldKey === undefined) return;\n      if (selectedFieldKey && !fieldByKey.has(selectedFieldKey)) {\n        selectItem(null);\n        onSelectedFieldKeyChange?.(null);\n        return;\n      }\n      selectItem(selectedFieldKey ?? null);\n    },\n  );\n\n  const selectField = React.useCallback(\n    (fieldKey: string) => {\n      if (!fieldByKey.has(fieldKey)) return;\n      selectItem(fieldKey);\n      navigateItem(fieldKey, { behavior: \"smooth\" });\n      onSelectedFieldKeyChange?.(fieldKey);\n    },\n    [fieldByKey, navigateItem, onSelectedFieldKeyChange, selectItem],\n  );\n  const clearFieldSelection = React.useCallback(() => {\n    selectItem(null);\n    onSelectedFieldKeyChange?.(null);\n  }, [onSelectedFieldKeyChange, selectItem]);\n  const previewField = React.useCallback(\n    (fieldKey: string | null) => {\n      if (fieldKey && !fieldByKey.has(fieldKey)) return;\n      previewItem(fieldKey);\n      if (fieldKey) {\n        navigateItem(fieldKey, { behavior: \"auto\", clearPreview: false });\n      }\n    },\n    [fieldByKey, navigateItem, previewItem],\n  );\n\n  return React.useMemo(\n    () => ({\n      selectedFieldKey: selectedItemId,\n      activeFieldKey: activeItemId,\n      selectField,\n      clearFieldSelection,\n      previewField,\n    }),\n    [\n      activeItemId,\n      clearFieldSelection,\n      previewField,\n      selectField,\n      selectedItemId,\n    ],\n  );\n}\n\nfunction useEditViewerPageOverlay({\n  activeFieldKey,\n  fieldsByPage,\n  mode,\n  previewField,\n  selectField,\n}: {\n  activeFieldKey: string | null;\n  fieldsByPage: ReadonlyMap<number, readonly EditViewerField[]>;\n  mode: EditViewerMode | null;\n  previewField: (fieldKey: string | null) => void;\n  selectField: (fieldKey: string) => void;\n}) {\n  return React.useCallback(\n    ({ pageNumber }: PageOverlayProps) => (\n      <EditFieldOverlayLayer\n        fieldsByPage={fieldsByPage}\n        pageNumber={pageNumber}\n        mode={mode}\n        effectiveFieldKey={activeFieldKey}\n        onFieldHover={previewField}\n        onFieldSelect={selectField}\n      />\n    ),\n    [activeFieldKey, fieldsByPage, mode, previewField, selectField],\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-provider.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-anatomy.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  ViewerBody,\n  ViewerRoot,\n  ViewerSidebar,\n  ViewerSurface,\n} from \"@/components/ui/viewer\";\nimport type { ViewerHeader } from \"@/components/ui/viewer\";\n\nimport { EditViewerDocumentView } from \"./edit-viewer-document\";\nimport { EditViewerFieldsView } from \"./edit-viewer-fields\";\nimport { EditViewerHeaderView } from \"./edit-viewer-header\";\nimport { EditViewerProvider } from \"./edit-viewer-provider\";\nimport {\n  EditViewerBusyOverlay as EditViewerBusyOverlayContent,\n  EmptyEditViewerState,\n} from \"./edit-viewer-states\";\nimport { useEditStore } from \"./edit-viewer-store\";\nimport type {\n  EditViewerMode,\n  EditViewerProps,\n  EditViewerStatus,\n} from \"./edit-viewer-types\";\n\ntype EditOutputState = {\n  hasFieldPanel: boolean;\n  hasOutput: boolean;\n  busyStatus: Extract<\n    EditViewerStatus,\n    { state: \"detecting\" } | { state: \"filling\" }\n  > | null;\n};\n\ntype EditToolbarState = {\n  hasFieldPanel: boolean;\n  mode: EditViewerMode | null;\n  modes: readonly EditViewerMode[];\n  setMode: (mode: EditViewerMode) => void;\n  status: Exclude<EditViewerStatus, { state: \"idle\" }> | null;\n};\n\nexport type EditViewerHeaderProps = React.ComponentProps<typeof ViewerHeader>;\n\nexport type EditViewerDocumentProps = React.ComponentProps<\"div\">;\n\nexport type EditViewerFieldsProps = React.ComponentProps<\"div\">;\n\nexport function EditViewer({ className, ...providerProps }: EditViewerProps) {\n  return (\n    <EditViewerProvider {...providerProps}>\n      <EditViewerRoot className={className} />\n    </EditViewerProvider>\n  );\n}\n\nfunction EditViewerRoot({ className }: { className?: string }) {\n  const { hasFieldPanel, hasOutput } = useEditOutput();\n\n  return (\n    <ViewerRoot\n      data-edit-viewer-root\n      defaultOpen\n      className={cn(\"bg-background h-full w-full flex-1\", className)}\n    >\n      <EditViewerBusyOverlay />\n      <EditViewerEmptyState />\n\n      {hasOutput ? (\n        <>\n          <EditViewerHeader />\n          <ViewerBody className=\"flex-col md:flex-row\">\n            <ViewerSurface className=\"relative\">\n              <EditViewerDocument className=\"h-full\" />\n            </ViewerSurface>\n\n            {hasFieldPanel ? (\n              <ViewerSidebar\n                aria-label=\"Document fields\"\n                side=\"right\"\n                width=\"320px\"\n                className=\"bg-background max-h-[42%] min-h-[220px] border-t md:max-h-none md:max-w-[50%] md:border-t-0 md:border-l\"\n              >\n                <EditViewerFields />\n              </ViewerSidebar>\n            ) : null}\n          </ViewerBody>\n        </>\n      ) : null}\n    </ViewerRoot>\n  );\n}\n\nexport function EditViewerHeader(props: EditViewerHeaderProps) {\n  const edit = useEditToolbar();\n\n  return (\n    <EditViewerHeaderView\n      {...props}\n      hasFieldPanel={edit.hasFieldPanel}\n      mode={edit.mode}\n      modes={edit.modes}\n      onModeChange={edit.setMode}\n      status={edit.status}\n    />\n  );\n}\n\nexport function EditViewerDocument({\n  className,\n  ...props\n}: EditViewerDocumentProps) {\n  const document = useEditStore().document;\n\n  return (\n    <EditViewerDocumentView\n      {...props}\n      className={className}\n      target={document.target}\n      renderPageOverlay={document.renderPageOverlay}\n      viewerRef={document.viewerRef}\n    />\n  );\n}\n\nexport function EditViewerFields(props: EditViewerFieldsProps) {\n  const edit = useEditStore();\n  const fields = {\n    ...edit.fields,\n    ...edit.selection,\n  };\n\n  return (\n    <EditViewerFieldsView\n      {...props}\n      fieldGroups={fields.fieldGroups}\n      fieldCount={fields.fieldCount}\n      filledCount={fields.filledCount}\n      visibleFieldCount={fields.visibleFieldCount}\n      effectiveFieldKey={fields.activeFieldKey}\n      selectedFieldKey={fields.selectedFieldKey}\n      query={fields.query}\n      onQueryChange={fields.setQuery}\n      filter={fields.filter}\n      onFilterChange={fields.setFilter}\n      onFieldHover={fields.previewField}\n      onFieldSelect={fields.selectField}\n      showSearch={fields.canSearch}\n      showFilters={fields.canFilter}\n    />\n  );\n}\n\nfunction EditViewerBusyOverlay() {\n  const { busyStatus } = useEditOutput();\n\n  return busyStatus ? (\n    <EditViewerBusyOverlayContent status={busyStatus} />\n  ) : null;\n}\n\nfunction EditViewerEmptyState() {\n  const { hasOutput } = useEditOutput();\n\n  return hasOutput ? null : <EmptyEditViewerState />;\n}\n\nfunction useEditOutput(): EditOutputState {\n  const edit = useEditStore();\n  const status = edit.state.status;\n  return {\n    busyStatus:\n      status.state === \"detecting\" || status.state === \"filling\"\n        ? status\n        : null,\n    hasFieldPanel: edit.options.fieldPanel,\n    hasOutput: edit.state.hasOutput,\n  };\n}\n\nfunction useEditToolbar(): EditToolbarState {\n  const edit = useEditStore();\n  return {\n    hasFieldPanel: edit.options.fieldPanel,\n    mode: edit.mode.mode,\n    modes: edit.mode.modes,\n    setMode: edit.mode.setMode,\n    status: edit.state.status.state === \"idle\" ? null : edit.state.status,\n  };\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-anatomy.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-document.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type {\n  PageOverlayProps,\n  PdfViewerHandle,\n} from \"@/components/ui/pdf-viewer\";\n\nimport { EditViewerDocumentPane } from \"./edit-viewer-document-pane\";\nimport type { EditViewerDocumentTarget } from \"./edit-viewer-model\";\n\nexport type EditViewerDocumentViewProps = React.ComponentProps<\"div\"> & {\n  target: EditViewerDocumentTarget;\n  renderPageOverlay: (props: PageOverlayProps) => React.ReactNode;\n  viewerRef: React.RefObject<PdfViewerHandle | null>;\n};\n\nexport function EditViewerDocumentView({\n  className,\n  renderPageOverlay,\n  target,\n  viewerRef,\n  ...props\n}: EditViewerDocumentViewProps) {\n  return (\n    <div className={cn(\"h-full\", className)} {...props}>\n      <EditViewerDocumentPane\n        target={target}\n        renderPageOverlay={renderPageOverlay}\n        viewerRef={viewerRef}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-document.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-fields.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { EditViewerFieldPanel } from \"./edit-viewer-field-panel\";\n\nexport type EditViewerFieldsViewProps = React.ComponentProps<\n  typeof EditViewerFieldPanel\n>;\n\nexport function EditViewerFieldsView(props: EditViewerFieldsViewProps) {\n  return <EditViewerFieldPanel {...props} />;\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-fields.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-header.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ViewerHeader, ViewerSidebarTrigger } from \"@/components/ui/viewer\";\n\nimport { EditViewerControls } from \"./edit-viewer-controls\";\nimport type { EditViewerMode, EditViewerStatus } from \"./edit-viewer-types\";\n\nexport type EditViewerHeaderViewProps = React.ComponentProps<\n  typeof ViewerHeader\n> & {\n  hasFieldPanel: boolean;\n  mode: EditViewerMode | null;\n  modes: readonly EditViewerMode[];\n  onModeChange: (mode: EditViewerMode) => void;\n  status: Exclude<EditViewerStatus, { state: \"idle\" }> | null;\n};\n\nexport function EditViewerHeaderView({\n  hasFieldPanel,\n  mode,\n  modes,\n  onModeChange,\n  status,\n  className,\n  ...props\n}: EditViewerHeaderViewProps) {\n  if (modes.length === 0) return null;\n\n  return (\n    <ViewerHeader\n      className={cn(\"bg-background flex h-11 items-center px-2\", className)}\n      {...props}\n    >\n      <div className=\"flex h-full min-w-0 items-center gap-2\">\n        {hasFieldPanel ? <ViewerSidebarTrigger /> : null}\n        <EditViewerControls\n          modes={modes}\n          mode={mode}\n          onModeChange={onModeChange}\n          status={status}\n        />\n      </div>\n    </ViewerHeader>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-header.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-states.tsx",
      "content": "import { AlertCircle, FileText, Loader2, Pencil } from \"lucide-react\";\n\nimport type { EditViewerStatus } from \"./edit-viewer-types\";\n\nexport function EditViewerBusyOverlay({\n  status,\n}: {\n  status: Extract<EditViewerStatus, { state: \"detecting\" | \"filling\" }>;\n}) {\n  return (\n    <div\n      className=\"bg-background/80 absolute inset-0 z-20 flex items-center justify-center backdrop-blur-sm\"\n      role=\"status\"\n      aria-live=\"polite\"\n    >\n      <div className=\"flex flex-col items-center gap-2\">\n        <Loader2 className=\"text-muted-foreground size-6 animate-spin\" />\n        <span className=\"text-muted-foreground text-sm\">\n          {status.message ??\n            (status.state === \"detecting\"\n              ? \"Detecting form fields...\"\n              : \"Filling document...\")}\n        </span>\n      </div>\n    </div>\n  );\n}\n\nexport function EmptyEditViewerState() {\n  return (\n    <div className=\"bg-muted text-muted-foreground flex flex-1 flex-col items-center justify-center gap-4 px-8\">\n      <Pencil className=\"text-muted-foreground/70 size-16\" />\n      <p className=\"text-center text-base\">Run edit to see output</p>\n      <p className=\"text-muted-foreground/80 max-w-sm text-center text-sm\">\n        Upload a document, add filling instructions, and click Run Edit\n      </p>\n    </div>\n  );\n}\n\nexport function NoDocumentState({ message }: { message: string }) {\n  return (\n    <div className=\"bg-muted text-muted-foreground flex h-full min-h-[280px] flex-col items-center justify-center gap-3 px-6 text-center\">\n      <FileText className=\"text-muted-foreground/70 size-10\" />\n      <p className=\"text-sm\">{message}</p>\n    </div>\n  );\n}\n\nexport function EditViewerErrorState({ message }: { message: string }) {\n  return (\n    <div\n      className=\"bg-muted flex h-full min-h-[280px] flex-col items-center justify-center gap-3 px-6 text-center\"\n      role=\"alert\"\n    >\n      <AlertCircle className=\"text-destructive size-10\" />\n      <p className=\"text-destructive max-w-md text-sm\">{message}</p>\n    </div>\n  );\n}\n\nexport function EditViewerStatusBadge({\n  status,\n}: {\n  status: Exclude<EditViewerStatus, { state: \"idle\" }>;\n}) {\n  if (status.state === \"error\") {\n    return (\n      <span className=\"border-destructive/30 bg-destructive/10 text-destructive inline-flex items-center gap-1 rounded border px-2 py-0.5 text-xs\">\n        <AlertCircle className=\"size-3\" />\n        {status.message}\n      </span>\n    );\n  }\n\n  return (\n    <span className=\"border-border bg-muted text-muted-foreground inline-flex items-center gap-1 rounded border px-2 py-0.5 text-xs\">\n      <Loader2 className=\"size-3 animate-spin\" />\n      {status.message ??\n        (status.state === \"detecting\"\n          ? \"Detecting fields\"\n          : \"Filling document\")}\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-states.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-controls.tsx",
      "content": "import { FileText } from \"lucide-react\";\n\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\n\nimport { EditViewerStatusBadge } from \"./edit-viewer-states\";\nimport type { EditViewerMode, EditViewerStatus } from \"./edit-viewer-types\";\n\nconst MODE_LABELS: Record<EditViewerMode, string> = {\n  source: \"Source\",\n  preview: \"Preview\",\n  filled: \"Filled\",\n};\n\nexport function EditViewerControls({\n  modes,\n  mode,\n  onModeChange,\n  status,\n}: {\n  modes: readonly EditViewerMode[];\n  mode: EditViewerMode | null;\n  onModeChange: (mode: EditViewerMode) => void;\n  status: Exclude<EditViewerStatus, { state: \"idle\" }> | null;\n}) {\n  if (modes.length === 0) return null;\n\n  return (\n    <div className=\"flex h-full min-w-0 items-center gap-3\">\n      {modes.length > 1 && mode ? (\n        <Tabs\n          value={mode}\n          onValueChange={(value) => onModeChange(value as EditViewerMode)}\n        >\n          <TabsList variant=\"line\" className=\"py-0\">\n            {modes.map((availableMode) => (\n              <TabsTrigger\n                key={availableMode}\n                value={availableMode}\n                className=\"h-9 text-xs sm:text-xs\"\n                aria-label={`${MODE_LABELS[availableMode]} view`}\n              >\n                {MODE_LABELS[availableMode]}\n              </TabsTrigger>\n            ))}\n          </TabsList>\n        </Tabs>\n      ) : (\n        <div className=\"flex items-center gap-2 text-sm font-medium\">\n          <FileText className=\"text-muted-foreground size-4\" />\n          {mode ? MODE_LABELS[mode] : \"Edit output\"}\n        </div>\n      )}\n      {status ? <EditViewerStatusBadge status={status} /> : null}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-controls.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-overlays.tsx",
      "content": "import { Check } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { PageOverlayProps } from \"@/components/ui/pdf-viewer\";\n\nimport { EDIT_FIELD_ACCENTS } from \"./edit-viewer-field-style\";\nimport {\n  displayEditFieldValue,\n  getEditViewerPdfAreaAnchor,\n  isEditFieldFilled,\n} from \"./edit-viewer-model\";\nimport type { EditViewerField, EditViewerMode } from \"./edit-viewer-types\";\n\nexport function EditFieldOverlayLayer({\n  fieldsByPage,\n  pageNumber,\n  mode,\n  effectiveFieldKey,\n  onFieldHover,\n  onFieldSelect,\n}: Pick<PageOverlayProps, \"pageNumber\"> & {\n  fieldsByPage: ReadonlyMap<number, readonly EditViewerField[]>;\n  mode: EditViewerMode | null;\n  effectiveFieldKey: string | null;\n  onFieldHover: (key: string | null) => void;\n  onFieldSelect: (key: string) => void;\n}) {\n  const pageFields = fieldsByPage.get(pageNumber) ?? [];\n  if (pageFields.length === 0) return null;\n\n  return (\n    <>\n      {pageFields.map((field) => (\n        <EditFieldOverlayButton\n          key={field.key}\n          field={field}\n          active={field.key === effectiveFieldKey}\n          showValue={mode === \"preview\"}\n          onFieldHover={onFieldHover}\n          onFieldSelect={onFieldSelect}\n        />\n      ))}\n    </>\n  );\n}\n\nfunction EditFieldOverlayButton({\n  field,\n  active,\n  showValue,\n  onFieldHover,\n  onFieldSelect,\n}: {\n  field: EditViewerField;\n  active: boolean;\n  showValue: boolean;\n  onFieldHover: (key: string | null) => void;\n  onFieldSelect: (key: string) => void;\n}) {\n  const anchor = getEditViewerPdfAreaAnchor(field);\n  if (!anchor) return null;\n\n  const accent = EDIT_FIELD_ACCENTS[field.type];\n  const filled = isEditFieldFilled(field);\n  const value = displayEditFieldValue(field);\n  const shouldShowValue = showValue && filled;\n  const maxLength = field.maxLength;\n\n  return (\n    <button\n      type=\"button\"\n      onMouseEnter={() => onFieldHover(field.key)}\n      onMouseLeave={() => onFieldHover(null)}\n      onFocus={() => onFieldHover(field.key)}\n      onBlur={() => onFieldHover(null)}\n      onClick={() => onFieldSelect(field.key)}\n      className={cn(\n        \"focus-visible:ring-ring/30 pointer-events-auto absolute flex items-center overflow-hidden rounded-[2px] border px-1 text-left transition-all focus-visible:ring-[3px] focus-visible:outline-none\",\n        active ? \"z-10 border-2 shadow-sm\" : \"border-dashed\",\n      )}\n      style={{\n        left: `${anchor.left}%`,\n        top: `${anchor.top}%`,\n        width: `${anchor.width}%`,\n        height: `${anchor.height}%`,\n        borderColor: accent.line,\n        backgroundColor: active ? accent.tint : \"transparent\",\n        boxShadow: active\n          ? `0 0 0 3px color-mix(in oklab, ${accent.line} 22%, transparent)`\n          : undefined,\n      }}\n      aria-label={`${field.key}, ${field.type}, ${filled ? value : \"empty\"}`}\n    >\n      {field.combing && maxLength && maxLength > 1 ? (\n        <>\n          {Array.from({ length: maxLength - 1 }, (_, index) => (\n            <span\n              key={index}\n              className=\"pointer-events-none absolute top-0 bottom-0 w-px bg-current opacity-35\"\n              style={{\n                left: `${((index + 1) / maxLength) * 100}%`,\n                color: accent.line,\n              }}\n            />\n          ))}\n        </>\n      ) : null}\n      {shouldShowValue && field.type === \"checkbox\" ? (\n        <Check\n          className=\"mx-auto size-3.5\"\n          strokeWidth={3}\n          style={{ color: accent.text }}\n        />\n      ) : shouldShowValue ? (\n        <span\n          className=\"truncate font-mono leading-none\"\n          style={{\n            color: accent.text,\n            fontSize: \"clamp(8px, 1.35cqh, 12px)\",\n          }}\n        >\n          {value}\n        </span>\n      ) : null}\n    </button>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-overlays.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-field-panel.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Search } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\nimport { EDIT_FIELD_ACCENTS } from \"./edit-viewer-field-style\";\nimport {\n  displayEditFieldValue,\n  isEditFieldFilled,\n  type EditViewerFieldGroup,\n  type EditViewerFilter,\n} from \"./edit-viewer-model\";\nimport type { EditViewerField } from \"./edit-viewer-types\";\n\nexport function EditViewerFieldPanel({\n  className,\n  fieldGroups,\n  fieldCount,\n  filledCount,\n  visibleFieldCount,\n  effectiveFieldKey,\n  selectedFieldKey,\n  query,\n  onQueryChange,\n  filter,\n  onFilterChange,\n  onFieldHover,\n  onFieldSelect,\n  showSearch,\n  showFilters,\n  ...props\n}: {\n  fieldGroups: readonly EditViewerFieldGroup[];\n  fieldCount: number;\n  filledCount: number;\n  visibleFieldCount: number;\n  effectiveFieldKey: string | null;\n  selectedFieldKey: string | null;\n  query: string;\n  onQueryChange: (query: string) => void;\n  filter: EditViewerFilter;\n  onFilterChange: (filter: EditViewerFilter) => void;\n  onFieldHover: (key: string | null) => void;\n  onFieldSelect: (key: string) => void;\n  showSearch: boolean;\n  showFilters: boolean;\n} & React.ComponentProps<\"div\">) {\n  return (\n    <div\n      {...props}\n      data-edit-viewer-fields-panel\n      className={cn(\"bg-background flex h-full min-h-0 flex-col\", className)}\n    >\n      <div className=\"flex h-10 flex-shrink-0 items-center gap-2 border-b px-3\">\n        <h2 className=\"px-1 text-sm font-medium\">Form fields</h2>\n        <span className=\"text-muted-foreground ml-auto pr-1 text-xs tabular-nums\">\n          <span className=\"text-success-foreground\">{filledCount}</span>\n          {\" / \"}\n          {fieldCount} filled\n        </span>\n      </div>\n\n      {(showSearch || showFilters) && (\n        <div className=\"flex flex-shrink-0 flex-col gap-2 border-b px-3 py-2.5\">\n          {showSearch ? (\n            <label className=\"relative block\">\n              <span className=\"sr-only\">Search form fields</span>\n              <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\" />\n              <input\n                value={query}\n                onChange={(event) => onQueryChange(event.target.value)}\n                placeholder=\"Search key, description, value...\"\n                className=\"placeholder:text-muted-foreground/70 focus-visible:border-ring focus-visible:ring-ring/20 h-7 w-full rounded-md border bg-transparent pr-2 pl-8 text-xs outline-none focus-visible:ring-[3px]\"\n              />\n            </label>\n          ) : null}\n\n          {showFilters ? (\n            <div className=\"flex flex-wrap items-center gap-1\">\n              {EDIT_VIEWER_FILTER_OPTIONS.map(({ value, label }) => (\n                <button\n                  key={value}\n                  type=\"button\"\n                  onClick={() => onFilterChange(value)}\n                  aria-pressed={filter === value}\n                  className={cn(\n                    \"rounded px-2 py-0.5 text-[11px] font-medium transition-colors\",\n                    filter === value\n                      ? \"bg-muted text-foreground\"\n                      : \"text-muted-foreground hover:bg-muted/60\",\n                  )}\n                >\n                  {label}\n                </button>\n              ))}\n            </div>\n          ) : null}\n        </div>\n      )}\n\n      <ScrollArea className=\"min-h-0 flex-1\">\n        {visibleFieldCount === 0 ? (\n          <p className=\"text-muted-foreground px-4 py-6 text-center text-xs\">\n            No fields match.\n          </p>\n        ) : (\n          fieldGroups.map((fieldGroup) => (\n            <div key={fieldGroup.key}>\n              <div className=\"bg-background/95 text-muted-foreground sticky top-0 z-10 flex items-center justify-between border-b px-4 py-1 text-[10px] font-medium tracking-wide uppercase backdrop-blur\">\n                <span>{fieldGroup.label}</span>\n                <span className=\"tabular-nums\">{fieldGroup.fields.length}</span>\n              </div>\n              {fieldGroup.fields.map((field) => (\n                <EditViewerFieldRow\n                  key={field.key}\n                  field={field}\n                  active={field.key === effectiveFieldKey}\n                  selected={field.key === selectedFieldKey}\n                  onFieldHover={onFieldHover}\n                  onFieldSelect={onFieldSelect}\n                />\n              ))}\n            </div>\n          ))\n        )}\n      </ScrollArea>\n    </div>\n  );\n}\n\nconst EDIT_VIEWER_FILTER_OPTIONS: Array<{\n  value: EditViewerFilter;\n  label: string;\n}> = [\n  { value: \"all\", label: \"All\" },\n  { value: \"filled\", label: \"Filled\" },\n  { value: \"empty\", label: \"Empty\" },\n  { value: \"text\", label: \"Text\" },\n  { value: \"checkbox\", label: \"Checkbox\" },\n  { value: \"no_location\", label: \"No location\" },\n];\n\nfunction EditViewerFieldRow({\n  field,\n  active,\n  selected,\n  onFieldHover,\n  onFieldSelect,\n}: {\n  field: EditViewerField;\n  active: boolean;\n  selected: boolean;\n  onFieldHover: (key: string | null) => void;\n  onFieldSelect: (key: string) => void;\n}) {\n  const accent = EDIT_FIELD_ACCENTS[field.type];\n  const filled = isEditFieldFilled(field);\n  const value = displayEditFieldValue(field);\n\n  return (\n    <button\n      type=\"button\"\n      onMouseEnter={() => onFieldHover(field.key)}\n      onMouseLeave={() => onFieldHover(null)}\n      onFocus={() => onFieldHover(field.key)}\n      onBlur={() => onFieldHover(null)}\n      onClick={() => onFieldSelect(field.key)}\n      aria-current={selected ? \"true\" : undefined}\n      className={cn(\n        \"border-border/60 focus-visible:ring-ring/20 flex w-full flex-col items-start gap-1 border-b px-4 py-2.5 text-left transition-colors focus-visible:ring-[3px] focus-visible:outline-none\",\n        active ? \"bg-muted\" : \"hover:bg-muted/50\",\n      )}\n    >\n      <div className=\"flex w-full items-center gap-2\">\n        <span\n          className={cn(\n            \"h-3 w-0.5 flex-shrink-0 rounded-full transition-opacity\",\n            selected ? \"opacity-100\" : \"opacity-0\",\n          )}\n          style={{ backgroundColor: accent.line }}\n        />\n        <span className=\"text-foreground truncate font-mono text-[11px]\">\n          {field.key}\n        </span>\n        <span\n          className={cn(\n            \"ml-auto flex-shrink-0 rounded border px-1.5 py-0 text-[9px] font-medium tracking-wide uppercase\",\n            accent.badge,\n          )}\n        >\n          {field.type}\n        </span>\n      </div>\n      {field.description ? (\n        <span className=\"text-muted-foreground line-clamp-1 text-[11px]\">\n          {field.description}\n        </span>\n      ) : null}\n      <span\n        className={cn(\n          \"w-full truncate font-mono text-[11px]\",\n          filled\n            ? \"text-success-foreground\"\n            : \"text-muted-foreground/50 italic\",\n        )}\n      >\n        {filled ? value : \"- empty -\"}\n      </span>\n    </button>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-field-panel.tsx"
    },
    {
      "path": "components/viewers/edit/edit-viewer-document-pane.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\nimport type { BlobViewerSource, UrlViewerSource } from \"@/lib/viewer-source\";\nimport {\n  FileViewerContent,\n  FileViewerDocument,\n  FileViewer,\n  FileViewerProvider,\n  FileViewerInset,\n  FileViewerViewport,\n} from \"@/components/ui/file-viewer\";\nimport {\n  PdfViewerPages,\n  PdfViewerProvider,\n  type PageOverlayProps,\n  type PdfViewerHandle,\n} from \"@/components/ui/pdf-viewer\";\nimport { useSegmentedDocumentViewport } from \"@/components/ui/segmented-document-provider\";\n\nimport {\n  canPreviewEditViewerDocument,\n  type EditViewerDocumentTarget,\n} from \"./edit-viewer-model\";\nimport { EditViewerErrorState, NoDocumentState } from \"./edit-viewer-states\";\nimport type { EditViewerDocumentSource } from \"./edit-viewer-types\";\n\nexport function EditViewerDocumentPane({\n  target,\n  renderPageOverlay,\n  viewerRef,\n}: {\n  target: EditViewerDocumentTarget;\n  renderPageOverlay: (props: PageOverlayProps) => React.ReactNode;\n  viewerRef: React.RefObject<PdfViewerHandle | null>;\n}) {\n  if (target.kind === \"error\") {\n    return <EditViewerErrorState message={target.message} />;\n  }\n\n  if (target.kind === \"filled\") {\n    return <FilledDocumentRenderer document={target.document} />;\n  }\n\n  if (target.kind === \"source\" || target.kind === \"preview\") {\n    return (\n      <SourceDocumentRenderer\n        document={target.document}\n        renderPageOverlay={renderPageOverlay}\n        viewerRef={viewerRef}\n        showPreview={target.showOverlay}\n      />\n    );\n  }\n\n  return <NoDocumentState message={target.message} />;\n}\n\nfunction SourceDocumentRenderer({\n  document,\n  renderPageOverlay,\n  viewerRef,\n  showPreview,\n}: {\n  document: EditViewerDocumentSource;\n  renderPageOverlay: (props: PageOverlayProps) => React.ReactNode;\n  viewerRef: React.RefObject<PdfViewerHandle | null>;\n  showPreview: boolean;\n}) {\n  const { documentHandlers } = useSegmentedDocumentViewport();\n  const documentHandlersRef = React.useRef(documentHandlers);\n  const source = useDocumentViewerSource(document);\n  documentHandlersRef.current = documentHandlers;\n  const setPdfViewerHandle = React.useCallback(\n    (handle: PdfViewerHandle | null) => {\n      viewerRef.current = handle;\n      documentHandlers.setDocumentHandle(handle);\n    },\n    [documentHandlers, viewerRef],\n  );\n\n  useMountEffect(() => () => {\n    documentHandlersRef.current.setDocumentHandle(null);\n  });\n\n  if (!source) {\n    return <NoDocumentState message=\"Document preview is unavailable.\" />;\n  }\n\n  if (canPreviewEditViewerDocument(document)) {\n    return (\n      <FileViewerProvider source={source}>\n        <FileViewer>\n          <PdfViewerProvider>\n            <FileViewerContent>\n              <FileViewerInset>\n                <FileViewerViewport>\n                  <PdfViewerPages\n                    ref={setPdfViewerHandle}\n                    bare\n                    className=\"h-full\"\n                    onScrollProgressChange={\n                      documentHandlers.onScrollProgressChange\n                    }\n                    onVisiblePageChange={documentHandlers.onCurrentPageChange}\n                    renderPageOverlay={\n                      showPreview ? renderPageOverlay : undefined\n                    }\n                  />\n                </FileViewerViewport>\n              </FileViewerInset>\n            </FileViewerContent>\n          </PdfViewerProvider>\n        </FileViewer>\n      </FileViewerProvider>\n    );\n  }\n\n  if (showPreview) {\n    return (\n      <NoDocumentState message=\"Preview requires a PDF source document.\" />\n    );\n  }\n\n  return <EditFileViewerDocument source={source} />;\n}\n\nfunction FilledDocumentRenderer({\n  document,\n}: {\n  document: EditViewerDocumentSource;\n}) {\n  const source = useDocumentViewerSource(document);\n  if (!source) {\n    return <NoDocumentState message=\"Document preview is unavailable.\" />;\n  }\n\n  return <EditFileViewerDocument source={source} />;\n}\n\nfunction EditFileViewerDocument({\n  source,\n}: {\n  source: UrlViewerSource | BlobViewerSource;\n}) {\n  return (\n    <FileViewerProvider source={source}>\n      <FileViewer>\n        <FileViewerContent>\n          <FileViewerInset>\n            <FileViewerViewport>\n              <FileViewerDocument />\n            </FileViewerViewport>\n          </FileViewerInset>\n        </FileViewerContent>\n      </FileViewer>\n    </FileViewerProvider>\n  );\n}\n\nfunction useDocumentViewerSource(\n  document: EditViewerDocumentSource,\n): UrlViewerSource | BlobViewerSource | null {\n  return React.useMemo(() => {\n    const fileName = document.filename ?? \"document\";\n    if (document.src) {\n      return {\n        kind: \"url\",\n        url: document.src,\n        fileName,\n        mimeType: document.mimeType,\n      };\n    }\n    if (!document.buffer) return null;\n\n    return {\n      kind: \"blob\",\n      blob: new Blob([document.buffer], { type: document.mimeType }),\n      identityKey: editViewerBufferIdentityKey(document.buffer),\n      fileName,\n      mimeType: document.mimeType,\n    };\n  }, [document.buffer, document.filename, document.mimeType, document.src]);\n}\n\nconst editViewerBufferKeys = new WeakMap<ArrayBuffer, string>();\nlet nextEditViewerBufferKey = 0;\n\nfunction editViewerBufferIdentityKey(buffer: ArrayBuffer): string {\n  let key = editViewerBufferKeys.get(buffer);\n  if (!key) {\n    nextEditViewerBufferKey += 1;\n    key = `edit-viewer-buffer:${nextEditViewerBufferKey}`;\n    editViewerBufferKeys.set(buffer, key);\n  }\n  return key;\n}\n",
      "type": "registry:component",
      "target": "@components/viewers/edit/edit-viewer-document-pane.tsx"
    },
    {
      "path": "components/viewers/lib/edit-types.ts",
      "content": "// Edit (agent-edit) form-field shapes, mirrored from the Retab dashboard.\n\nexport interface BBox {\n  left: number;\n  top: number;\n  width: number;\n  height: number;\n  /** 1-based page index. */\n  page: number;\n}\n\nexport type EditFieldType = \"text\" | \"checkbox\";\n\nexport interface FormField {\n  bbox: BBox;\n  description: string;\n  type: EditFieldType;\n  key: string;\n  value?: string | null;\n  combing?: boolean;\n  max_length?: number;\n}\n",
      "type": "registry:lib",
      "target": "@components/viewers/lib/edit-types.ts"
    },
    {
      "path": "components/viewers/sample-data/edit.json",
      "content": "[\n  {\n    \"bbox\": {\n      \"height\": 0.023118317127227783,\n      \"left\": 0.22526241838932037,\n      \"page\": 1,\n      \"top\": 0.5017300844192505,\n      \"width\": 0.6918964236974716\n    },\n    \"description\": \"Name of the primary account owner\",\n    \"key\": \"name\",\n    \"type\": \"text\",\n    \"value\": \"David Alan Whitmore\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.023118317127227783,\n      \"left\": 0.2212209850549698,\n      \"page\": 1,\n      \"top\": 0.5357827544212341,\n      \"width\": 0.6959378570318222\n    },\n    \"description\": \"Name of the additional owner or authorized individual\",\n    \"key\": \"additional_owner/authorized_individual_name\",\n    \"type\": \"text\",\n    \"value\": \"Elena Marie Whitmore\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024680376052856445,\n      \"left\": 0.22182722389698029,\n      \"page\": 1,\n      \"top\": 0.6305991411209106,\n      \"width\": 0.19601033627986908\n    },\n    \"description\": \"First Fidelity Account Number\",\n    \"key\": \"fidelity_account_number\",\n    \"type\": \"text\",\n    \"value\": \"X41-872034\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024687528610229492,\n      \"left\": 0.43983685970306396,\n      \"page\": 1,\n      \"top\": 0.6307812333106995,\n      \"width\": 0.19727981090545654\n    },\n    \"description\": \"Second Fidelity Account Number\",\n    \"key\": \"fidelity_account_number_2\",\n    \"type\": \"text\",\n    \"value\": \"X41-872035\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024062514305114746,\n      \"left\": 0.6593510508537292,\n      \"page\": 1,\n      \"top\": 0.6310938000679016,\n      \"width\": 0.19647127389907837\n    },\n    \"description\": \"Third Fidelity Account Number\",\n    \"key\": \"fidelity_account_number_3\",\n    \"type\": \"text\",\n    \"value\": null\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024771258234977722,\n      \"left\": 0.22348324954509735,\n      \"page\": 2,\n      \"top\": 0.14343103766441345,\n      \"width\": 0.19443847239017487\n    },\n    \"description\": \"Bank Routing or ABA Number for the wire recipient\",\n    \"key\": \"recipient_routing_aba_number\",\n    \"type\": \"text\",\n    \"value\": \"121000248\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02391168475151062,\n      \"left\": 0.4227624833583832,\n      \"page\": 2,\n      \"top\": 0.14331382513046265,\n      \"width\": 0.49134036898612976\n    },\n    \"description\": \"Name of the recipient's bank\",\n    \"key\": \"recipient_bank_name\",\n    \"type\": \"text\",\n    \"value\": \"Wells Fargo Bank, N.A.\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024067968130111694,\n      \"left\": 0.22328156232833862,\n      \"page\": 2,\n      \"top\": 0.17847806215286255,\n      \"width\": 0.19403502345085144\n    },\n    \"description\": \"Account number of the wire recipient\",\n    \"key\": \"recipient_account_number\",\n    \"type\": \"text\",\n    \"value\": \"4872019365\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024067983031272888,\n      \"left\": 0.4223591089248657,\n      \"page\": 2,\n      \"top\": 0.17785291373729706,\n      \"width\": 0.4913403391838074\n    },\n    \"description\": \"Name(s) of the account owner\",\n    \"key\": \"recipient_account_owner_name\",\n    \"type\": \"text\",\n    \"value\": \"David Alan Whitmore & Elena Marie Whitmore\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.023130252957344055,\n      \"left\": 0.22429005801677704,\n      \"page\": 2,\n      \"top\": 0.2133297324180603,\n      \"width\": 0.689006045460701\n    },\n    \"description\": \"Full address of the wire recipient\",\n    \"key\": \"recipient_address\",\n    \"type\": \"text\",\n    \"value\": \"1450 Montgomery Street, Suite 700\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02422425150871277,\n      \"left\": 0.22429005801677704,\n      \"page\": 2,\n      \"top\": 0.24732184410095215,\n      \"width\": 0.21541519463062286\n    },\n    \"description\": \"City of the wire recipient\",\n    \"key\": \"recipient_city\",\n    \"type\": \"text\",\n    \"value\": \"San Francisco\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02391168475151062,\n      \"left\": 0.4449494481086731,\n      \"page\": 2,\n      \"top\": 0.24685296416282654,\n      \"width\": 0.11133825778961182\n    },\n    \"description\": \"State or Province of the wire recipient\",\n    \"key\": \"recipient_state_province\",\n    \"type\": \"text\",\n    \"value\": \"CA\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02391168475151062,\n      \"left\": 0.5607250928878784,\n      \"page\": 2,\n      \"top\": 0.2471655309200287,\n      \"width\": 0.11214500665664673\n    },\n    \"description\": \"ZIP or Postal Code of the wire recipient\",\n    \"key\": \"recipient_zip_postal_code\",\n    \"type\": \"text\",\n    \"value\": \"94104\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02422425150871277,\n      \"left\": 0.6769040822982788,\n      \"page\": 2,\n      \"top\": 0.24732184410095215,\n      \"width\": 0.23760223388671875\n    },\n    \"description\": \"Country of the wire recipient\",\n    \"key\": \"recipient_country\",\n    \"type\": \"text\",\n    \"value\": \"United States\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.022505104541778564,\n      \"left\": 0.22429005801677704,\n      \"page\": 2,\n      \"top\": 0.2831893563270569,\n      \"width\": 0.6898127943277359\n    },\n    \"description\": \"For Further Credit details\",\n    \"key\": \"recipient_for_further_credit\",\n    \"type\": \"text\",\n    \"value\": \"For Further Credit to Whitmore Family Trust, Acc #88210456\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.023442834615707397,\n      \"left\": 0.22590361535549164,\n      \"page\": 2,\n      \"top\": 0.3164781928062439,\n      \"width\": 0.689006045460701\n    },\n    \"description\": \"Additional details or instructions to be included with the wire transfer\",\n    \"key\": \"wire_additional_details\",\n    \"type\": \"text\",\n    \"value\": null\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.023755401372909546,\n      \"left\": 0.2240883708000183,\n      \"page\": 2,\n      \"top\": 0.37414753437042236,\n      \"width\": 0.1972622275352478\n    },\n    \"description\": \"Correspondent Bank Routing or ABA Number\",\n    \"key\": \"correspondent_routing_aba_number\",\n    \"type\": \"text\",\n    \"value\": \"N/A\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.024067968130111694,\n      \"left\": 0.42619138956069946,\n      \"page\": 2,\n      \"top\": 0.37367868423461914,\n      \"width\": 0.48851650953292847\n    },\n    \"description\": \"Name of the Correspondent Bank\",\n    \"key\": \"correspondent_bank_name\",\n    \"type\": \"text\",\n    \"value\": \"N/A\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.011559247970581055,\n      \"left\": 0.22135350108146667,\n      \"page\": 2,\n      \"top\": 0.41847583651542664,\n      \"width\": 0.015321388840675354\n    },\n    \"description\": \"Indicate if the recipient bank is outside the United States\",\n    \"key\": \"is_recipient_outside_us\",\n    \"type\": \"checkbox\",\n    \"value\": \"unchecked\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.023442834615707397,\n      \"left\": 0.22610536217689514,\n      \"page\": 2,\n      \"top\": 0.4465077221393585,\n      \"width\": 0.3497471511363983\n    },\n    \"description\": \"SWIFT Code for international wire recipient\",\n    \"key\": \"recipient_swift_code\",\n    \"type\": \"text\",\n    \"value\": \"N/A\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02375534176826477,\n      \"left\": 0.5812984108924866,\n      \"page\": 2,\n      \"top\": 0.44572630524635315,\n      \"width\": 0.33361124992370605\n    },\n    \"description\": \"Name of the country for the international bank\",\n    \"key\": \"recipient_bank_country\",\n    \"type\": \"text\",\n    \"value\": \"N/A\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.04469764232635498,\n      \"left\": 0.22731555998325348,\n      \"page\": 2,\n      \"top\": 0.5659098625183105,\n      \"width\": 0.6877957433462143\n    },\n    \"description\": \"Reason for standing instructions requested for a country different from the country of tax residence\",\n    \"key\": \"w8_standing_instructions_reason\",\n    \"type\": \"text\",\n    \"value\": \"N/A - domestic transfer to a U.S. financial institution matching country of tax residence\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.0234375,\n      \"left\": 0.10474555939435959,\n      \"page\": 3,\n      \"top\": 0.15593750774860382,\n      \"width\": 0.453628845512867\n    },\n    \"description\": \"Print Name of Owner/Authorized Individual\",\n    \"key\": \"print_owner_name_1\",\n    \"type\": \"text\",\n    \"value\": \"DAVID ALAN WHITMORE\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.12656249105930328,\n      \"left\": 0.5779135227203369,\n      \"page\": 3,\n      \"top\": 0.16343750059604645,\n      \"width\": 0.35895490646362305\n    },\n    \"description\": \"Medallion Signature Guarantee stamp area\",\n    \"key\": \"medallion_signature_guarantee\",\n    \"type\": \"text\",\n    \"value\": null\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.029069527983665466,\n      \"left\": 0.15744520723819733,\n      \"page\": 3,\n      \"top\": 0.20239263772964478,\n      \"width\": 0.39974115788936615\n    },\n    \"description\": \"Signature of Owner/Authorized Individual\",\n    \"key\": \"owner_signature_1\",\n    \"type\": \"text\",\n    \"value\": \"David Alan Whitmore\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.03609374165534973,\n      \"left\": 0.15319037437438965,\n      \"page\": 3,\n      \"top\": 0.2547656297683716,\n      \"width\": 0.4018603563308716\n    },\n    \"description\": \"Date of signature (MM/DD/YYYY)\",\n    \"key\": \"date_1\",\n    \"type\": \"text\",\n    \"value\": \"06/11/2026\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.02406829595565796,\n      \"left\": 0.1074775755405426,\n      \"page\": 3,\n      \"top\": 0.3105437457561493,\n      \"width\": 0.451320618391037\n    },\n    \"description\": \"Print Name of second Owner/Authorized Individual\",\n    \"key\": \"print_owner_name_2\",\n    \"type\": \"text\",\n    \"value\": \"ELENA MARIE WHITMORE\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.03282040357589722,\n      \"left\": 0.16066893935203552,\n      \"page\": 3,\n      \"top\": 0.35492947697639465,\n      \"width\": 0.397323340177536\n    },\n    \"description\": \"Signature of second Owner/Authorized Individual\",\n    \"key\": \"owner_signature_2\",\n    \"type\": \"text\",\n    \"value\": \"Elena Marie Whitmore\"\n  },\n  {\n    \"bbox\": {\n      \"height\": 0.03094497323036194,\n      \"left\": 0.15986301004886627,\n      \"page\": 3,\n      \"top\": 0.41275593638420105,\n      \"width\": 0.3965173810720444\n    },\n    \"description\": \"Date of second signature (MM/DD/YYYY)\",\n    \"key\": \"date_2\",\n    \"type\": \"text\",\n    \"value\": \"06/11/2026\"\n  }\n]",
      "type": "registry:file",
      "target": "@components/viewers/sample-data/edit.json"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:block"
}