이 레포가 무엇을 하는 물건인가.
송장·계약서·은행 명세서 같은 문서를 다루는 제품을 만들 때, 매번 PDF 렌더러를 직접 붙이고, OCR 결과를 페이지 위에 겹쳐 그리고, "이 값이 문서 몇 페이지 어디에서 추출됐는지"를 보여주는 인용(citation) UI를 맨손으로 짜는 일은 지옥이다. Extend UI는 바로 그 14종의 '문서 전용 컴포넌트'를 검증된 상태로 묶어 준다.
중요한 점 하나 — 이건 npm 패키지가 아니다. shadcn 방식이라 npx shadcn add @extend/pdf-viewer를 하면 컴포넌트 소스코드 그 자체가 내 프로젝트에 복사된다. 블랙박스 의존성이 아니라, 내 손으로 고칠 수 있는 React/Tailwind 코드가 들어온다.
Extend UI는 document agents(문서를 다루는 AI 에이전트)·사용자용 문서 플로우·사내 도구를 위한 오픈소스 컴포넌트 모음이다. 저장소 package.json의 버전은 0.0.1로 표기돼 있어 공개 자체는 신생(early-stage)이지만, 코드는 신생의 느낌과 거리가 멀다 — bounding-box-citations.tsx 한 파일이 2,300줄, layout-blocks.tsx는 8,300줄에 달한다. README가 밝히듯 "Extend 본사에서 production으로 유지보수되며 수백만 페이지를 처리하는 워크플로에 쓰인다."
registry.json이라는 매니페스트에 "이 컴포넌트는 어떤 파일·어떤 의존성으로 이뤄진다"가 적혀 있고, CLI가 그걸 읽어 파일을 가져온다. 버전 잠금·블랙박스가 없어 자유롭게 수정 가능 — 대신 업데이트도 수동이다.트렌딩 이유 · 경쟁 대비 장점.
2026년은 "AI가 문서를 읽어 데이터를 뽑는다"는 흐름(document AI / IDP)이 폭발한 해다. 송장 자동 처리, 계약서 검토, 보험 청구 심사 — 전부 "AI가 뽑은 값을 사람이 문서 원본과 대조해 승인"하는 휴먼 리뷰 UI를 필요로 한다. 그런데 이걸 처음부터 만들면 PDF.js·OCR 좌표 매핑·가상 스크롤·그리드 편집 같은 난제가 한꺼번에 쏟아진다. Extend UI가 주목받는 이유는 세 가지다.
첫째, "문서 도메인 특화" — 범용 UI 키트(shadcn/ui, MUI)엔 없는 PDF/DOCX/XLSX 뷰어와 인용·OCR 블록을 한곳에 모았다. 둘째, production에서 검증됨 — 장난감이 아니라 실제 매출 제품의 내부 컴포넌트 계층을 그대로 공개했다. 셋째, shadcn 소스 복사 방식 + MIT라, 디자인 시스템 에이전트·프로토타이핑 도구와도 잘 맞고 자유롭게 개조할 수 있다.
| 비교 항목 | 범용 UI 키트 (shadcn/ui·MUI 류) | Extend UI |
|---|---|---|
| 제공 부품 | 버튼·폼·다이얼로그 등 일반 부품 | 일반 부품 + 문서 전용 14종 |
| PDF/문서 뷰어 | 없음 (직접 붙여야) | PDF·DOCX·XLSX·CSV 뷰어 내장 |
| AI 인용 UI | 없음 | 바운딩박스 인용·OCR 레이아웃 블록 |
| 배포 방식 | npm 또는 소스 복사(shadcn) | shadcn 레지스트리(소스 복사) |
| 검증 수준 | 커뮤니티 검증 | 수백만 페이지 production 운영 |
| 대상 | 일반 웹앱 | 문서 에이전트·IDP·리뷰 도구 |
브라우저에서 PDF를 제대로 렌더링하려면 pdf.js 워커 설정·텍스트 레이어·확대/축소·가상 스크롤을 직접 다뤄야 한다. 거기에 "AI가 뽑은 값의 위치를 페이지 위에 정확히 겹쳐 그리기"까지 더하면, 좌표계 변환·스크롤 동기화 지옥이 펼쳐진다. 범용 키트엔 이런 게 아예 없어 매번 바닥부터 짠다.
PDF 워커 자동 설정, OCR 블록과 PDF 오버레이의 좌표 동기화, 필드별 인용·JSON diff·편집 가능한 폼 컨트롤 — 이 모든 게 bounding-box-citations·layout-blocks 같은 단일 컴포넌트 안에 캡슐화돼 있다. 소스가 통째로 들어오니, 80%를 공짜로 얻고 나머지 20%만 내 도메인에 맞게 고친다.
모노레포 · Next.js 16 · React 19 · @embedpdf · glide-data-grid.
저장소는 pnpm 워크스페이스 + Turborepo 모노레포다. 실질 콘텐츠는 apps/v4 한 패키지에 들어 있고, 이것이 ① 문서 컴포넌트 레지스트리(배포되는 본체)이자 ② docs 사이트(extend.ai/ui)를 겸한다.
| 요소 | 버전 · 역할 |
|---|---|
| 모노레포 | pnpm 9 + Turborepo 1.x — workspaces: ["apps/*"], turbo run으로 build/lint/typecheck 파이프라인 |
| 프레임워크 | Next.js 16 (App Router, RSC, Turbopack dev) |
| 런타임 | React 19 + React DOM 19, Node v20.5.1 (.nvmrc) |
| 언어 | TypeScript 5 (tsc --noEmit typecheck) |
| 스타일 | Tailwind CSS v4 + tw-animate-css, CSS 변수 테마(new-york 스타일) |
| 문서 사이트 | fumadocs (MDX 기반 문서 엔진) — content/docs/**/*.mdx |
| 레지스트리 빌드 | shadcn build --output public/r → 컴포넌트별 .json 생성 |
turbo.json의 pipeline에 "build는 의존 패키지 build가 끝난 뒤"처럼 작업 그래프를 선언하면, Turbo가 바뀐 것만 다시 빌드한다.| 라이브러리 | 어디에 쓰이나 |
|---|---|
| @embedpdf/* (core, engines, models, 플러그인 14종) | PDF 렌더링 엔진. WASM 기반 EmbedPDF 프레임워크를 사용하며 플러그인으로 스크롤·줌·회전·검색·썸네일·선택 등을 조합 |
| @glideapps/glide-data-grid | 고성능 캔버스 기반 데이터 그리드. bounding-box-citations의 편집 가능한 필드 표·XLSX 편집 |
| @extend-ai/react-docx / react-xlsx | Extend 자체 DOCX·XLSX 렌더링 패키지(뷰어·에디터) |
| @tanstack/react-virtual | 가상 스크롤 — OCR 블록 목록(layout-blocks) 긴 리스트를 끊김 없이. PDF 페이지 가상 스크롤은 @embedpdf/plugin-scroll이 담당 |
| @dnd-kit/* | 드래그&드롭 — schema-builder·문서 분할(document-splits)의 재정렬 |
| @pierre/diffs · @pierre/trees | 코드/JSON diff 뷰 (인용 검토 시 기대값 vs 추출값 비교) |
| signature_pad | 전자서명(e-signature) 캔버스 입력 |
| pdf-lib | PDF 조작/생성 (서명 삽입 등) |
| papaparse | CSV/TSV 파싱 → csv-viewer |
| react-markdown · marked · shiki | OCR 마크다운 텍스트 렌더·코드 하이라이트 |
| 요소 | 역할 |
|---|---|
| @base-ui/react | 접근성 있는 헤드리스 프리미티브(accordion·dialog·popover 등의 기반) |
| class-variance-authority · clsx · tailwind-merge | variant 기반 클래스 조합 (cn() 헬퍼) |
| @hugeicons/react | 아이콘 세트(iconLibrary: "hugeicons") |
| jotai · nuqs | 원자 상태 관리 · URL 쿼리 동기화 상태 |
| cmdk · sonner | 커맨드 팔레트 · 토스트 알림 |
| zod | 스키마 검증 (schema-builder 등) |
Extend UI의 스택을 요리에 비유하면, Next.js/React/Tailwind는 주방과 조리도구, base-ui·CVA는 기본 양념이다. 하지만 이 식당의 시그니처 요리는 pdf.js·glide-data-grid·dnd-kit 같은 '특수 재료'로 만든다 — 이것들이 PDF 위에 인용 상자를 정확히 얹고, 수천 행을 부드럽게 그리고, 스키마 필드를 끌어 옮기는 '문서 제품다움'을 만들어낸다.
레지스트리 = 단일 진실원 · 3계층 컴포넌트 · 인용 좌표 동기화.
핵심은 registry.json이 모든 것의 단일 진실원이라는 점이다. 각 컴포넌트는 "이름·설명·의존성·구성 파일·다른 레지스트리 컴포넌트 의존(registryDependencies)"으로 선언되고, shadcn build가 이를 컴포넌트별 정적 JSON으로 굽는다. 사용자가 npx shadcn add @extend/pdf-viewer를 치면 CLI가 그 JSON을 받아 파일을 내 프로젝트에 풀어 놓는다.
Extend UI는 컴포넌트를 추상화 수준에 따라 세 층으로 나눈다. 이 구분이 "어떤 걸 가져다 쓰고 어떤 걸 조립할지"를 결정한다.
| 계층 | 타입(registry type) | 예시 · 성격 |
|---|---|---|
| 프리미티브 | registry:ui | button·dialog·select·tabs — base-ui 위에 얹은 저수준 부품. 내 앱에 이미 있으면 그걸 써도 됨. |
| 문서 컴포넌트 | registry:ui | pdf-viewer·bounding-box-citations·layout-blocks — 도메인 핵심. 단독으로 큰 기능. |
| 블록(예제) | registry:block | file-system-block·bounding-box-citations-block — 위 컴포넌트들을 조합해 "그대로 페이지에 붙이는" 완성형 예제. |
accordion은 ["utils"]에 의존. CLI가 이를 따라 의존 컴포넌트도 자동으로 함께 복사한다. npm의 dependencies(외부 패키지)와 구분되는, shadcn 특유의 내부 의존 그래프다.pdf-viewer.tsx는 브라우저 PDF 렌더링의 두 난제를 정공법으로 푼다. ① WASM 기반 @embedpdf 렌더 엔진(createPluginRegistration()으로 플러그인 조합)으로 고성능 PDF 렌더링을 구현하고, ② @embedpdf/plugin-scroll의 가상 페이지 스크롤(ScrollStrategy·VirtualItem)로 수백 페이지도 메모리 폭발 없이 렌더한다.
@embedpdf는 레고 블록 세트와 같다 — createPluginRegistration()이 "어떤 플러그인 블록을 끼울지" 선언하는 조립 설명서이고, 스크롤·줌·회전·검색 등 각 기능은 독립 플러그인 패키지로 분리돼 있다. 필요한 기능만 골라 끼우면 그 조합이 하나의 PDF 뷰어가 된다. PDF 페이지 가상 스크롤은 @embedpdf/plugin-scroll의 ScrollStrategy·VirtualItem이 담당하며, WASM 기반 렌더 엔진(@embedpdf/engines)이 무거운 렌더링을 처리한다.
bounding-box-citations(2,300여 줄)의 본질은 두 좌표계를 묶는 일이다. 한쪽은 PDF 페이지 좌표(예: {left:62, top:82, width:22, height:4}, 보통 0~100 비율), 다른 한쪽은 화면 픽셀 좌표다. 사용자가 폼에서 "송장 총액" 필드를 클릭하면, 그 필드의 location이 가리키는 페이지로 스크롤하고 해당 영역에 네모를 그려야 한다.
여기에 편집 가능한 폼 컨트롤(텍스트/숫자/JSON)과 기대값 대 추출값의 diff까지 얹혀, "AI가 뽑은 값을 사람이 원본과 대조하며 고치는" 휴먼 리뷰의 전 과정이 한 컴포넌트 안에 들어온다. 데이터 표는 캔버스 기반 glide-data-grid라 수천 필드도 빠르게 편집된다.
layout-blocks는 OCR/레이아웃 분석 결과(ParsedOcrOutput: chunk → block 트리, 각 block에 type·content·boundingBox·avgOcrConfidence)를 받아, 선택 가능한 블록 목록 + 신뢰도 + 마크다운 텍스트 + PDF 페이지 위 오버레이를 서로 연결해 보여준다. 한쪽에서 블록을 고르면 다른 쪽이 따라 하이라이트된다 — 추출 디버깅의 표준 패턴.
pnpm 워크스페이스 · apps/v4 단일 앱 안에 레지스트리·docs 공존.
① 루트 README.md로 무엇을 주는지 파악 → ② apps/v4/registry.json으로 "어떤 컴포넌트가 무슨 의존성으로 구성되는지" 통람 → ③ registry/new-york-v4/ui/pdf-viewer.tsx(상대적으로 읽을 만함)부터 → ④ 간판인 bounding-box-citations.tsx·layout-blocks.tsx는 거대하니 props 타입 정의와 좌표 변환 부분만 발췌 정독.
이 레포에서 무엇을 배우고, 무엇을 실습할까.
npm 패키지 대신 레지스트리 매니페스트(registry.json)로 컴포넌트를 배포하는 현대적 패턴. name/dependencies/registryDependencies/files 스키마와, shadcn build로 정적 JSON을 굽는 과정을 익히면, 나만의 사내 컴포넌트 레지스트리를 만들 수 있다. components.json의 registries 항목으로 외부 레지스트리(@extend)를 등록하는 법도 함께.
registry.json으로 선언하고 shadcn build로 구워, 다른 프로젝트에서 shadcn add로 가져와 보라.pdf.js 워커 동적 로드, react-pdf 텍스트 레이어, TanStack Virtual 가상 스크롤, 줌/회전/페이지 점프. "PDF를 멈춤 없이 빠르게 그리는" 실전 기법이 pdf-viewer.tsx에 압축돼 있다.
"문서 비율 좌표 ↔ 화면 픽셀"을 줌·회전·렌더 크기를 반영해 변환하고, 양방향 하이라이트를 거는 것은 문서 AI UI의 핵심 난제다. bounding-box-citations·layout-blocks가 교과서.
{left,top,width,height} 비율 박스를 절대 위치로 그리고, 줌 슬라이더에 맞춰 정확히 따라가게 만들어 보라.glide-data-grid로 수천 행을 캔버스에 그려 편집하고, dnd-kit으로 스키마 필드·문서 분할을 재정렬하는 법. 대량 데이터 UI와 인터랙션 설계를 동시에 배운다.
'컴포넌트만 쓰기'와 '레포 전체 띄우기'는 다르다.
| 목적 | 필요한 것 |
|---|---|
| 컴포넌트 한두 개 가져다 쓰기 | React 19 + Tailwind v4 프로젝트, shadcn CLI. npx shadcn add @extend/<name> |
| 프리미티브 정렬 | 내 앱의 button/select/dialog 등 프리미티브 경로를 components.json alias로 맞추기(없으면 함께 설치) |
| 레포 전체 개발(docs+레지스트리) | Node v20.5.1(.nvmrc), pnpm 9. pnpm install && pnpm v4:dev → localhost:4000 |
| 레지스트리 빌드/검증 | pnpm v4:registry:build / :validate (shadcn build) |
| 런타임 의존(브라우저) | PDF 워커·에셋을 unpkg CDN에서 로드 → 인터넷 연결 필요(오프라인이면 워커 self-host 필요) |
README가 명시한다 — Button·Select·Dialog 같은 공용 프리미티브는 내 앱이 이미 가진 것을 쓰는 것을 전제한다. 별칭(alias)이 다르면 가져온 컴포넌트의 @/components/ui/button 같은 import를 내 경로로 고쳐야 한다. 설치 전 components.json에 alias를 미리 맞춰 두면 생성 시점부터 내 구조에 맞게 들어온다.
난이도별 5단계 — 설치부터 인용 UI 구현까지.
새 Next.js + Tailwind v4 프로젝트에서 npx shadcn add @extend/pdf-viewer를 실행하고, 샘플 PDF를 <PDFViewer file="/sample.pdf" className="h-[720px]" />로 렌더한다. 들어온 소스가 @/components/ui/ 어디에 복사됐는지, 어떤 의존성이 같이 설치됐는지 확인.
apps/v4/registry.json을 열어 bounding-box-citations-block이 어떤 registryDependencies를 끌고 오는지 추적해 의존 트리를 그린다. registry:ui·registry:block·registry:lib·registry:hook 타입의 차이를 표로 정리.
@extend/bounding-box-citations-block을 설치하고, README의 ReviewField 예제(invoice_total, location.area)를 그대로 넣어 휴먼 리뷰 화면을 띄운다. 필드를 클릭했을 때 PDF가 해당 영역으로 스크롤·하이라이트되는 좌표 동기화 동작을 관찰.
react-pdf 위에, 비율 좌표 {left,top,width,height}(0~100)를 받아 절대 위치 div 박스를 그리는 작은 컴포넌트를 직접 만든다. 줌 슬라이더(0.5x~2x)와 페이지 회전에도 박스가 정확히 따라가게 좌표 변환을 처리할 것.
내 컴포넌트 3개를 registry.json 스키마로 선언하고 shadcn build로 public/r/*.json을 굽는다. 이를 정적 호스팅한 뒤, 다른 프로젝트의 components.json에 registries로 등록해 shadcn add @mine/<name>으로 가져와 본다.
6주 코스 — shadcn 레지스트리에서 문서 AI UI까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | shadcn 레지스트리 모델 · components.json · registry.json 스키마 | ui.shadcn.com/docs/registry · registry.json 통람 |
| 2주차 | Turborepo·pnpm 워크스페이스 모노레포 구조 | turbo.json pipeline · 빌드 캐시 실험 |
| 3주차 | pdf.js / react-pdf — 워커·텍스트레이어·가상 스크롤 | pdf-viewer.tsx · TanStack Virtual |
| 4주차 | 좌표계 변환 & 오버레이 동기화 (인용·OCR 블록) | bounding-box-citations · layout-blocks props |
| 5주차 | glide-data-grid 캔버스 그리드 · dnd-kit 재정렬 | schema-builder · document-splits |
| 6주차 | 문서 AI(IDP) 휴먼 리뷰 UX · 전자서명/문서 분할 통합 | e-signature · document-splits-block 조합 데모 |
본문·레포에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| Extend UI | 문서 에이전트·문서 플로우·사내 도구용 오픈소스 React 컴포넌트 14종 모음. MIT. |
| shadcn registry | 컴포넌트를 npm이 아니라 소스 복사로 배포하는 매니페스트 기반 방식. @extend/* 네임스페이스. |
| registry.json | 모든 컴포넌트의 단일 진실원. name·dependencies·registryDependencies·files로 구성. |
| registryDependencies | 같은 레지스트리 내 다른 컴포넌트 의존(예: accordion→utils). CLI가 따라 복사. |
| block | registry:block — 여러 컴포넌트를 조합한 "그대로 붙이는" 완성형 예제. |
| bounding box citation | AI 추출값이 문서 어느 좌표에서 나왔는지 페이지 위에 박스로 표시하는 검증 UI. |
| layout blocks | OCR/레이아웃 결과(블록·신뢰도·마크다운)를 PDF 오버레이와 연결해 보여주는 컴포넌트. |
| ParsedOcrOutput | OCR 출력 타입: chunks→blocks 트리, 각 block에 type·content·boundingBox·avgOcrConfidence. |
| ReviewField | 휴먼 리뷰 필드 타입: key·schema·actual·expected·location(page+area). |
| pdf.js worker | PDF 파싱/렌더를 별도 스레드에서 처리하는 Web Worker. 여기선 unpkg에서 버전 일치 로드. |
| glide-data-grid | 캔버스 기반 고성능 데이터 그리드. 수천 행 편집을 매끄럽게. |
| dnd-kit | 접근성 있는 드래그&드롭 라이브러리. schema-builder·document-splits 재정렬. |
| fumadocs | MDX 기반 문서 사이트 엔진. extend.ai/ui 문서를 구동. |
| IDP / document AI | 지능형 문서 처리 — 문서에서 데이터를 자동 추출하고 사람이 검토·승인하는 분야. |