{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "segment-sidebar",
  "title": "Segment Sidebar",
  "description": "Selectable list of segments with page ranges and confidence bars.",
  "registryDependencies": [
    "@retab/segments",
    "@retab/segment-interaction",
    "@retab/sidebar-list",
    "@retab/utils"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/segment-sidebar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  getSegmentInteractionState,\n  getSegmentSurfaceProps,\n  scopeSegmentInteraction,\n  type SegmentInteraction,\n} from \"@/lib/segment-interaction\";\nimport {\n  formatPageRanges,\n  segmentDisplayLabel,\n  segmentPageCount,\n} from \"@/lib/segments\";\nimport { cn } from \"@/lib/utils\";\n\nimport type { DocumentSegment } from \"./segmented-document-model\";\nimport {\n  SidebarListButton,\n  SidebarListContent,\n  SidebarListGroup,\n  SidebarListGroupContent,\n  SidebarListGroupLabel,\n  SidebarListMenu,\n  SidebarListMenuItem,\n  SidebarListRoot,\n} from \"./sidebar-list\";\n\nexport interface SegmentSidebarProps {\n  segments: DocumentSegment[];\n  /** Shared preview state. */\n  interaction?: SegmentInteraction;\n  /** Fired when a segment surface is clicked, after transient preview is cleared. */\n  onSelect?: (segment: DocumentSegment) => void;\n  /** 1-based current page; owning segments receive current-page state. */\n  currentPage?: number | null;\n  /** Noun for a row, e.g. \"chunk\" (partition) or \"subdocument\" (split). */\n  unitLabel?: string;\n  /** Show segments with zero pages too. */\n  showUnused?: boolean;\n  className?: string;\n}\n\n/**\n * A navigable list of segments — the \"sidebar\" surface. Each row shows the\n * color swatch, label, page ranges, page count, and (when present) a confidence\n * bar. Hovering previews the segment; clicking fires `onSelect` for host side\n * effects like document scrolling.\n */\nexport function SegmentSidebar({\n  segments,\n  interaction,\n  onSelect,\n  currentPage,\n  unitLabel = \"segment\",\n  showUnused = true,\n  className,\n}: SegmentSidebarProps) {\n  const visible = showUnused\n    ? segments\n    : segments.filter((s) => segmentPageCount(s.pages) > 0);\n  const scopedInteraction = React.useMemo(\n    () =>\n      scopeSegmentInteraction(\n        interaction,\n        visible.map((segment) => segment.id),\n      ),\n    [interaction, visible],\n  );\n  const interactionState = React.useMemo(\n    () =>\n      getSegmentInteractionState({\n        segments: visible,\n        currentPage,\n        interaction: scopedInteraction,\n      }),\n    [currentPage, scopedInteraction, visible],\n  );\n\n  return (\n    <div\n      data-slot=\"segment-sidebar\"\n      onMouseLeave={() => scopedInteraction?.clearPreview()}\n      className={cn(\"flex min-h-0 flex-col\", className)}\n    >\n      <SidebarListRoot className=\"w-full bg-transparent\">\n        <SidebarListContent className=\"gap-0\">\n          <SidebarListGroup className=\"min-h-0 flex-1 p-2\">\n            <SidebarListGroupLabel className=\"px-1\">\n              {visible.length} {unitLabel}\n              {visible.length === 1 ? \"\" : \"s\"}\n            </SidebarListGroupLabel>\n            <SidebarListGroupContent className=\"min-h-0 flex-1\">\n              <SidebarListMenu className=\"gap-0.5\">\n                {visible.map((segment, segmentPosition) => (\n                  <SegmentSidebarItem\n                    key={`${segment.id}-${segmentPosition}`}\n                    segment={segment}\n                    interaction={scopedInteraction ?? undefined}\n                    interactionState={interactionState}\n                    onSelect={onSelect}\n                  />\n                ))}\n              </SidebarListMenu>\n            </SidebarListGroupContent>\n          </SidebarListGroup>\n        </SidebarListContent>\n      </SidebarListRoot>\n    </div>\n  );\n}\n\nfunction SegmentSidebarItem({\n  segment,\n  interaction,\n  interactionState,\n  onSelect,\n}: {\n  segment: DocumentSegment;\n  interaction: SegmentInteraction | undefined;\n  interactionState: ReturnType<typeof getSegmentInteractionState>;\n  onSelect: ((segment: DocumentSegment) => void) | undefined;\n}) {\n  const { state, eventHandlers, dataProps } = getSegmentSurfaceProps({\n    segment,\n    interaction,\n    interactionState,\n    onSelect,\n  });\n  const label = segmentDisplayLabel(segment.label);\n  const pageCount = segmentPageCount(segment.pages);\n\n  return (\n    <SidebarListMenuItem>\n      <SidebarListButton\n        {...dataProps}\n        {...eventHandlers}\n        aria-current={state.isCurrent ? \"page\" : undefined}\n        className={cn(\n          \"h-auto items-start gap-2.5 rounded-md border px-2.5 py-2\",\n          \"data-[active=true]:border-sidebar-border\",\n          state.isHighlighted\n            ? \"border-sidebar-border bg-sidebar-accent text-sidebar-accent-foreground\"\n            : \"border-transparent\",\n          state.isDimmed && \"opacity-60\",\n        )}\n        isActive={state.isHighlighted}\n      >\n        <span\n          aria-hidden\n          className=\"mt-0.5 h-3.5 w-3.5 shrink-0 rounded-[3px] ring-1 ring-black/20\"\n          style={{ backgroundColor: segment.color }}\n        />\n        <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n          <span className=\"truncate text-sm font-medium\">\n            {segment.label.trim() ? (\n              label\n            ) : (\n              <span className=\"text-muted-foreground italic\">unnamed</span>\n            )}\n          </span>\n          <span className=\"text-muted-foreground font-mono text-xs\">\n            {pageCount === 0\n              ? \"no pages\"\n              : `${pageCount} page${\n                  pageCount === 1 ? \"\" : \"s\"\n                } · ${formatPageRanges(segment.pages)}`}\n          </span>\n          {segment.confidence != null ? (\n            <ConfidenceBar value={segment.confidence} />\n          ) : null}\n        </span>\n      </SidebarListButton>\n    </SidebarListMenuItem>\n  );\n}\n\nfunction ConfidenceBar({ value }: { value: number }) {\n  const safeValue = Number.isFinite(value) ? value : 0;\n  const pct = Math.round(Math.max(0, Math.min(1, safeValue)) * 100);\n  return (\n    <span className=\"mt-0.5 flex items-center gap-1.5\">\n      <span className=\"bg-muted-foreground/20 h-1 w-16 overflow-hidden rounded-full\">\n        <span\n          className=\"bg-foreground/60 block h-full rounded-full\"\n          style={{ width: `${pct}%` }}\n        />\n      </span>\n      <span className=\"text-muted-foreground text-[10px] tabular-nums\">\n        {pct}%\n      </span>\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/segment-sidebar.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/segmented-document-model.ts",
      "content": "import { segmentsPageCount, type Segment } from \"@/lib/segments\";\n\nexport type DocumentSegment = Segment & {\n  /** Stable domain id for the item that produced this segment. */\n  sourceId?: string;\n};\n\nexport type SegmentBounds = {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n};\n\nexport type SegmentAnchor = {\n  id: string;\n  segmentId: string;\n  /** 1-based page/frame number. */\n  pageNumber: number;\n  /** Normalized page-local rectangle. Omit for whole-page anchors. */\n  bounds?: SegmentBounds;\n};\n\nexport type SegmentedPage = {\n  pageNumber: number;\n  width?: number;\n  height?: number;\n};\n\nexport type SegmentRow = {\n  id: string;\n  label?: string;\n  /** Generic display grouping only; domain vote/output semantics stay outside. */\n  segments: DocumentSegment[];\n};\n\nexport type SegmentedDocumentModel = {\n  pages: SegmentedPage[];\n  /** Viewport/navigation projection used for page ownership and jumps. */\n  segments: DocumentSegment[];\n  /** Optional page-local targets for segment-level navigation and overlays. */\n  anchors?: SegmentAnchor[];\n  /** Optional generic row projection for visual ribbons or grouped legends. */\n  rows?: SegmentRow[];\n};\n\nexport function createSegmentedDocumentModel({\n  anchors,\n  pageCount,\n  pages,\n  rows,\n  segments,\n}: {\n  anchors?: SegmentAnchor[];\n  pageCount?: number;\n  pages?: SegmentedPage[];\n  rows?: SegmentRow[];\n  segments: DocumentSegment[];\n}): SegmentedDocumentModel {\n  return {\n    pages:\n      pages ?? createSegmentedPages(pageCount ?? segmentsPageCount(segments)),\n    segments,\n    ...(anchors ? { anchors } : null),\n    ...(rows ? { rows } : null),\n  };\n}\n\nexport function createSegmentedPages(pageCount: number): SegmentedPage[] {\n  const count =\n    Number.isFinite(pageCount) && pageCount > 0 ? Math.floor(pageCount) : 0;\n  return Array.from({ length: count }, (_, index) => ({\n    pageNumber: index + 1,\n  }));\n}\n",
      "type": "registry:ui",
      "target": "@ui/segmented-document-model.ts"
    }
  ],
  "type": "registry:ui"
}