diff --git a/README.md b/README.md
index ebb2b9be3..265ee1d19 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,9 @@ stateless JSON Patch
| 목적 | 위치 |
| --- | --- |
-| 빠른 사용 예제 | [Intent guide](docs/public/intent-guide.md) |
+| 빠른 시작 | [설치·변경·구독](docs/public/quick-start.md) |
+| 모듈 찾기 | [책임별 API·Usage](docs/public/modules.md) |
+| 설계와 진행 상태 | [목표·프로토타입·소유권 감사](docs/public/design.md) |
| 목표 구조와 TBD | [Architecture](docs/public/architecture.md), [Foundation](docs/public/foundation.md) |
| JSON Document 개념 | [docs/public/overview.md](docs/public/overview.md) |
| JSON Document Protocol | [docs/public/api.md](docs/public/api.md) |
@@ -131,6 +133,7 @@ npm run dev
```sh
npm run docs:evaluate
+npm run check:architecture -- --evidence
npm test -w @interactive-os/json-document
npm run typecheck -w @interactive-os/json-document
npm run build -w @interactive-os/json-document
diff --git a/architecture/README.md b/architecture/README.md
new file mode 100644
index 000000000..37490f160
--- /dev/null
+++ b/architecture/README.md
@@ -0,0 +1,48 @@
+# 아키텍처 등록과 drift
+
+`modules.json`은 실제 패키지의 책임·아키텍처 위치·public source entrypoint·API
+문서 소유 위치를 등록한다. URL·메뉴 순서·Usage는 소유하지 않는다. 사이트는
+`site/route-registry.mjs`에서 이 등록과 `site/site-routes.json`을 합성한다.
+같은 책임을 사이트에서 다시 정의하면 실패한다. API 문서는 owning package의
+`docs/api-reference.md`에 생성한다. `docs/api-reference/packages.mjs`는 생성기에
+이 등록을 연결하며 별도 책임 목록을 유지하지 않는다.
+
+```text
+package.json exports + 실제 TypeScript source
+ │ 대조
+ ▼
+ architecture/modules.json
+ │
+ ┌─────────┴───────────┐
+ ▼ ▼
+packages/*/docs/ 사이트 문서·Usage 등록
+api-reference.md │
+ Architecture·메뉴·API 연결
+```
+
+## #779 조사와 대응
+
+| 관찰한 drift | 대응 | 남은 한계 |
+| --- | --- | --- |
+| 책임·위치가 사이트 라우트에 종속 | 저장소 아키텍처 등록으로 이동; 사이트 재정의 금지 | 분류 의미의 타당성은 소유권 감사 대상 |
+| 생성 API 본문 35개가 패키지 밖에 위치 | 모든 본문을 owning package docs로 이동 | 수동 계약 설명은 기존 owner docs에서 유지 |
+| API 생성기의 source·책임 목록이 별도 존재 | 아키텍처 등록 소비; manifest의 types export와 실제 source 대조 | API 동작은 적합성·단위·브라우저 검사 대상 |
+| Application 연결이 유효 URL인지 확인할 뿐 소비 증거 없음 | route source에서 정적 import 경로 추적; 미도달 모듈 거절 | 대표 조합만 검증; 전체 의존성 목록 아님 |
+| 소스 표시 import를 API 소비로 오인할 수 있음 | `?raw`·`?url`은 소비 경로에서 제외 | 동적 계산 specifier·runtime 호출은 증명하지 않음 |
+| 사이트 경계 검사와 현재 Host·문서 조합이 불일치 | Applications 위치를 명시하고 shared의 app 역참조·카탈로그 중간 라우트를 제거 | 제품의 책임 준수는 별도 소유권 감사 대상 |
+| Editing·Rich Text 혼합 책임, Hands·Artifact 미완료 | 기존 statusNote와 Document Type 감사 상태 유지 | 이 변경은 패키지 책임 재배치나 Profile 완료가 아님 |
+
+## 검사
+
+- `npm run check:architecture`: workspace 분모, package 이름·exports/source, API
+ 소유 위치·사이트 연결, Application의 대표 모듈 도달 경로를 검사한다.
+- `npm run check:architecture -- --evidence`: 제품별 등록 연결의 import 경로를 출력한다.
+- `npm run test:architecture`: 소유자 누락·잘못된 public source·사이트 재정의·
+ 잘못된 소비 관계·private subpath·raw 소스의 오인에 대한 회귀 검사다.
+- `npm run docs:evaluate`: API 생성물과 위 검사, 문서 링크를 함께 검증한다.
+- 사이트 `check:canonical-modules`: 위 검사와 기존 Usage·Source 정본 검사를 실행한다.
+
+정적 그래프에는 type import와 barrel re-export가 포함된다. API의 실제 실행,
+사용한 symbol의 최소 집합 또는 번들 크기를 뜻하지 않는다. Application의 목록을
+늘릴 때에는 `applicationSource`에서 실제 연결되는지 확인하고 제품 의미를 별도로
+검토한다. Document Type 상태의 정본은 기존 `audits/document-types.json`이다.
diff --git a/architecture/modules.json b/architecture/modules.json
new file mode 100644
index 000000000..56c052a3c
--- /dev/null
+++ b/architecture/modules.json
@@ -0,0 +1,414 @@
+[
+ {
+ "packageName": "@interactive-os/json-document",
+ "sourceDirectory": "packages/json-document",
+ "positions": [
+ "JSON Document"
+ ],
+ "responsibility": "JSON 값·주소·검증·원자적 변경·구독",
+ "entrypoint": "packages/json-document/src/application/document/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-selection",
+ "sourceDirectory": "packages/json-document-selection",
+ "positions": [
+ "Editing"
+ ],
+ "responsibility": "범위·키·기하 선택과 선택 전이",
+ "entrypoint": "packages/json-document-selection/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-selection/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-editing",
+ "sourceDirectory": "packages/json-document-editing",
+ "positions": [
+ "Editing",
+ "Document Types"
+ ],
+ "responsibility": "Intent·변경 계획·선택·Clipboard·History",
+ "statusNote": "일부 장르의 문서 모델도 현재 Editing에 있습니다. Document Types의 후보별 소유권 감사를 따릅니다.",
+ "entrypoint": "packages/json-document-editing/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-editing/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text",
+ "sourceDirectory": "packages/json-document-rich-text",
+ "positions": [
+ "Editing",
+ "Document Types"
+ ],
+ "responsibility": "Rich Text 모델·의미 연산과 텍스트 편집",
+ "statusNote": "문서 모델과 Editing 계약이 한 패키지에 있습니다. Document Type 재배치 완료를 뜻하지 않습니다.",
+ "entrypoint": "packages/json-document-rich-text/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-file-intake",
+ "sourceDirectory": "packages/json-document-file-intake",
+ "positions": [
+ "Document Types"
+ ],
+ "responsibility": "직렬화 가능한 파일 후보·이미지 내용과 수용 검증",
+ "statusNote": "파일·이미지 내용 계약을 이 위치에서 안내합니다. 독립 Document Type Profile의 완료를 선언하지 않습니다.",
+ "entrypoint": "packages/json-document-file-intake/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-file-intake/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-mention",
+ "sourceDirectory": "packages/json-document-rich-text-mention",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "안정된 entity reference를 삽입하는 Mention 계약",
+ "entrypoint": "packages/json-document-rich-text-mention/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-mention/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-suggestion",
+ "sourceDirectory": "packages/json-document-rich-text-suggestion",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "입력 문맥에서 suggestion을 찾고 적용하는 계약",
+ "entrypoint": "packages/json-document-rich-text-suggestion/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-suggestion/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-suggestion-react",
+ "sourceDirectory": "packages/json-document-rich-text-suggestion-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Suggestion 관찰·수명을 React에 연결",
+ "entrypoint": "packages/json-document-rich-text-suggestion-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-suggestion-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-mention-react",
+ "sourceDirectory": "packages/json-document-rich-text-mention-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Mention 관찰·수명을 React에 연결",
+ "entrypoint": "packages/json-document-rich-text-mention-react/src/index.tsx",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-mention-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-composer",
+ "sourceDirectory": "packages/json-document-composer",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "텍스트와 구조화된 문맥을 한 입력으로 구성",
+ "entrypoint": "packages/json-document-composer/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-composer/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-composer-react",
+ "sourceDirectory": "packages/json-document-composer-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Composer 편집 관찰과 참조 atom을 React에 연결",
+ "entrypoint": "packages/json-document-composer-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-composer-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-web",
+ "sourceDirectory": "packages/json-document-web",
+ "positions": [
+ "Adapter"
+ ],
+ "responsibility": "키보드·포인터·Clipboard·DOM 관찰·입력 기록",
+ "entrypoint": "packages/json-document-web/src/index.ts",
+ "subpaths": [
+ {
+ "packageName": "@interactive-os/json-document-web/interaction-recording",
+ "entrypoint": "packages/json-document-web/src/interaction-recording-index.ts"
+ }
+ ],
+ "referencePath": "packages/json-document-web/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-contenteditable",
+ "sourceDirectory": "packages/json-document-contenteditable",
+ "positions": [
+ "Adapter"
+ ],
+ "responsibility": "DOM 입력·IME·선택 복원·기호 투영·화면 줄 탐색",
+ "entrypoint": "packages/json-document-contenteditable/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-contenteditable/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-web",
+ "sourceDirectory": "packages/json-document-rich-text-web",
+ "positions": [
+ "Adapter"
+ ],
+ "responsibility": "Rich Text와 DOM·Clipboard·native 입력의 대응",
+ "entrypoint": "packages/json-document-rich-text-web/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-web/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-react",
+ "sourceDirectory": "packages/json-document-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "문서·편집 관찰과 React 수명 연결",
+ "entrypoint": "packages/json-document-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-react-hook-form",
+ "sourceDirectory": "packages/json-document-react-hook-form",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "폼 draft·검증 결과를 문서 변경에 연결",
+ "entrypoint": "packages/json-document-react-hook-form/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-react-hook-form/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-ajv",
+ "sourceDirectory": "packages/json-document-ajv",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Ajv 검증 결과를 문서 진단으로 연결",
+ "entrypoint": "packages/json-document-ajv/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-ajv/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-a2ui",
+ "sourceDirectory": "packages/json-document-a2ui",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "A2UI 메시지와 문서·UI projection 연결",
+ "entrypoint": "packages/json-document-a2ui/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-a2ui/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-zod",
+ "sourceDirectory": "packages/json-document-zod",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Zod schema·오류를 문서 계약에 연결",
+ "entrypoint": "packages/json-document-zod/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-zod/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-tanstack-table",
+ "sourceDirectory": "packages/json-document-tanstack-table",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "TanStack Table의 표시 모델을 편집 topology에 연결",
+ "entrypoint": "packages/json-document-tanstack-table/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-tanstack-table/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-rich-text-react",
+ "sourceDirectory": "packages/json-document-rich-text-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Rich Text 편집 관찰·DOM 표면과 React 수명 연결",
+ "entrypoint": "packages/json-document-rich-text-react/src/index.tsx",
+ "subpaths": [],
+ "referencePath": "packages/json-document-rich-text-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-affordance",
+ "sourceDirectory": "packages/json-document-affordance",
+ "positions": [
+ "Affordance"
+ ],
+ "responsibility": "선택·이동·취소 등 입력 독립 조작 의미와 수명",
+ "entrypoint": "packages/json-document-affordance/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-affordance/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-ui-primitives-react",
+ "sourceDirectory": "packages/json-document-ui-primitives-react",
+ "positions": [
+ "UI Primitives"
+ ],
+ "responsibility": "표준 제어·포커스·overlay·접근성 행동",
+ "entrypoint": "packages/json-document-ui-primitives-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-ui-primitives-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-animation-react",
+ "sourceDirectory": "packages/json-document-animation-react",
+ "positions": [
+ "UI Primitives"
+ ],
+ "responsibility": "진행·대기·전환 상태의 반복 시각 행동",
+ "entrypoint": "packages/json-document-animation-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-animation-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-markdown",
+ "sourceDirectory": "packages/json-document-markdown",
+ "positions": [
+ "Document Types"
+ ],
+ "responsibility": "Markdown 원문 구문·좌표·체크·문단·목록 연산",
+ "statusNote": "원문 기반 편집 실험입니다. Rich Text 모델과 별개이며 Official Hands Profile 완료를 뜻하지 않습니다.",
+ "entrypoint": "packages/json-document-markdown/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-markdown/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-markdown-web",
+ "sourceDirectory": "packages/json-document-markdown-web",
+ "positions": [
+ "Adapter"
+ ],
+ "responsibility": "Markdown 원문과 편집 DOM·입력 binding의 대응",
+ "entrypoint": "packages/json-document-markdown-web/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-markdown-web/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-markdown-react",
+ "sourceDirectory": "packages/json-document-markdown-react",
+ "positions": [
+ "Connector"
+ ],
+ "responsibility": "Markdown 렌더링·편집 표면과 React 수명 연결",
+ "entrypoint": "packages/json-document-markdown-react/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-markdown-react/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-database",
+ "sourceDirectory": "packages/json-document-database",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "Database record와 view를 편집 UI로 조합",
+ "entrypoint": "packages/json-document-database/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-database/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-annotation",
+ "sourceDirectory": "packages/json-document-annotation",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "Raster annotation 도구·선택·제스처·UI 조합",
+ "entrypoint": "packages/json-document-annotation/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-annotation/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-object-document",
+ "sourceDirectory": "packages/json-document-object-document",
+ "positions": [
+ "Document Types"
+ ],
+ "responsibility": "Object·Canvas 모델·검증·의미 연산·projection",
+ "statusNote": "Object 소유권 확정 · RC. 모든 wire 계약의 Stable 승격을 뜻하지 않습니다.",
+ "entrypoint": "packages/json-document-object-document/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-object-document/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-canvas",
+ "sourceDirectory": "packages/json-document-canvas",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "Canvas 선택·변형·입력·UI 조합",
+ "entrypoint": "packages/json-document-canvas/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-canvas/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-calendar-document",
+ "sourceDirectory": "packages/json-document-calendar-document",
+ "positions": [
+ "Document Types"
+ ],
+ "responsibility": "Calendar 이벤트·반복·구간·발생분 projection",
+ "statusNote": "Calendar 소유권 확정 · RC. 모든 wire 계약의 Stable 승격을 뜻하지 않습니다.",
+ "entrypoint": "packages/json-document-calendar-document/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-calendar-document/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-calendar",
+ "sourceDirectory": "packages/json-document-calendar",
+ "positions": [
+ "Hands"
+ ],
+ "responsibility": "Calendar 입력·선택·시간 표시·제어 UI 조합",
+ "entrypoint": "packages/json-document-calendar/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/json-document-calendar/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-collaboration",
+ "sourceDirectory": "packages/json-document-collaboration",
+ "positions": [
+ "Collaboration"
+ ],
+ "responsibility": "동일 JSONDocument 계약의 복제·병합·협업 History",
+ "entrypoint": "packages/json-document-collaboration/src/index.ts",
+ "subpaths": [
+ {
+ "packageName": "@interactive-os/json-document-collaboration/history",
+ "entrypoint": "packages/json-document-collaboration/src/history-index.ts"
+ },
+ {
+ "packageName": "@interactive-os/json-document-collaboration/text",
+ "entrypoint": "packages/json-document-collaboration/src/text-index.ts"
+ },
+ {
+ "packageName": "@interactive-os/json-document-collaboration/editing",
+ "entrypoint": "packages/json-document-collaboration/src/editing-index.ts"
+ }
+ ],
+ "referencePath": "packages/json-document-collaboration/docs/api-reference.md"
+ },
+ {
+ "packageName": "@interactive-os/json-document-contenteditable-collaboration",
+ "sourceDirectory": "packages/contenteditable-collaboration",
+ "positions": [
+ "Adapter",
+ "Collaboration"
+ ],
+ "responsibility": "협업 문자열과 native DOM 입력 lease 연결",
+ "entrypoint": "packages/contenteditable-collaboration/src/index.ts",
+ "subpaths": [],
+ "referencePath": "packages/contenteditable-collaboration/docs/api-reference.md"
+ }
+]
diff --git a/audits/README.md b/audits/README.md
index fbd4d729f..a759c92b4 100644
--- a/audits/README.md
+++ b/audits/README.md
@@ -4,7 +4,7 @@
ledger를 소유합니다. 사이트는 결과를 표시할 수 있지만 감사 계약과 판정은
site-owned가 아닙니다.
-`document-types.json`의 후보 분모는 `site/site-routes.json`의 `Document Types`
+`document-types.json`의 후보 분모는 `site/site-routes.json`의 `/docs/document-types/*`
서브 메뉴입니다. 모든 후보는 `candidateProfiles`에 필요성, 역할, 현재 관찰된
schema와 근거 source symbol을 기록합니다. 이 schema는 감사 입력이며 정본 계약을
미리 확정하지 않습니다. 후보를 감사할 때는 다음 순서로 닫습니다.
diff --git a/docs/README.md b/docs/README.md
index 176945d58..d3086d363 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,12 +1,12 @@
# 문서 구조
-이 디렉터리는 외부 사용자에게 공개할 문서 원천과 생성된 owner API reference를
-보관한다. 릴리스 과정·검토 루프·과거 판단은 Git issue와 version history에 남긴다.
+이 디렉터리는 외부 사용자에게 공개할 공통 문서 원천과 문서 검사 도구를
+보관한다. 생성 API reference는 각 `packages/*/docs/api-reference.md`가 소유한다. 릴리스 과정·검토 루프·과거 판단은 Git issue와 version history에 남긴다.
```text
docs
├─ public/ # 한국어 개념·계약·사용법, llms.txt
-├─ api-reference/ # owner package별 생성 reference와 등록표
+├─ api-reference/ # API 생성기의 아키텍처 등록 연결과 검사
├─ changelog.md # 사용자 영향 중심 변경 기록
├─ evaluate.mjs # 문서·등록·증거 연결 검사
└─ public-contract-checks.mjs # 원천·Pages·live의 공통 공개 계약 검사
@@ -14,38 +14,33 @@ docs
## 사이트의 읽기 구조
-탐색 섹션은 `site/src/app/site-layers.ts`, 페이지 제목·URL·문서 원천의 연결은
-`site/site-routes.json`의 `documentSource`가 소유한다. `doc-pages.ts`는 그 원천을
+탐색 섹션은 `site/src/app/site-layers.ts`, 페이지 제목·URL은
+`site/site-routes.json`이 소유한다. 문서 원천은 일반 문서의 경우
+`site/site-routes.json`의 `documentSource`, 패키지 API는
+`architecture/modules.json`의 `referencePath`가 소유한다. `site/route-registry.mjs`가
+책임 등록과 페이지 탐색 정보를 합성한다. `doc-pages.ts`는 그 원천을
읽고, Markdown 링크도 같은 등록표에서 사이트 URL을 찾는다. 별도 파일명→URL
-카탈로그를 유지하지 않는다. 아래는 개념 수준의 지도이며 leaf 페이지 목록을
+카탈로그를 유지하지 않는다. [아키텍처 등록과 drift 검사](../architecture/README.md)에서
+정본의 방향과 남은 한계를 확인한다. 아래는 개념 수준의 지도이며 leaf 페이지 목록을
복제한 탐색 정본이 아니다.
```text
-Introduction
-├─ Why
-├─ Architecture
-└─ How We Build
-Foundation
-├─ Overview
-├─ JSON Document Protocol
-├─ Document Types · TBD
-│ └─ 후보별 관찰된 schema·목표 owner·완료 증거
-├─ Editing Protocol
-│ └─ Intent · Topology · Selection · Clipboard · History
-└─ Collaboration
- └─ Replica · Lifecycle · History · Text
-Building Blocks
-├─ Overview
-├─ Adapter
-├─ Connector
-├─ Affordance
-└─ UI Primitives
-Hands
-└─ 현재 Usage와 Official Hands Profile · TBD
-Artifact
-└─ Content Prototype · TBD
+시작하기
+├─ 소개 · 빠른 시작
+└─ Architecture
+모듈
+├─ JSON Document · Document Types · Editing · Collaboration
+└─ Adapter · Connector · Affordance · UI Primitives
+편집 조합 · Hands
+├─ 장르별 예제와 API
+└─ 지원 범위
Applications
-└─ 제품 조합과 제품에서 발견한 책임
+└─ Bear · Calendar · AI Agent
+설계와 진행 상태
+├─ Artifact · Prototype
+├─ Official Hands 목표
+├─ 소유권 감사
+└─ 개발 원칙
```
읽기 순서는 필수 package dependency chain이 아니다. Collaboration은 같은
@@ -89,8 +84,9 @@ owner의 행동 테스트에 연결한다. 이 확정은 전체 Hands의 Stable
| --- | --- |
| `public/` | 외부 사용자와 사이트 방문자의 개념·계약·Usage |
| `public/llms.txt` | 같은 목표와 현재 계약을 요약한 기계 판독 문서 |
-| `api-reference/packages.mjs` | owner package의 source entrypoint와 사이트 탐색 분류 |
-| `api-reference/*.md` | package root와 공개 subpath에서 생성한 API reference |
+| `../architecture/modules.json` | 패키지 책임·위치·공개 source entrypoint의 정본 |
+| `api-reference/packages.mjs` | 아키텍처 등록을 API 생성기에 연결 |
+| `../packages/*/docs/api-reference.md` | owning package의 공개 API에서 생성한 reference |
| `changelog.md` | 사용자 영향 중심 변경 기록 |
`package.json#exports`의 TypeScript 진입점과 API 등록을 비교해 subpath 누락을
diff --git a/docs/api-reference/packages.mjs b/docs/api-reference/packages.mjs
index 4596d2327..52aceeb18 100644
--- a/docs/api-reference/packages.mjs
+++ b/docs/api-reference/packages.mjs
@@ -1,62 +1,14 @@
-import { readFileSync } from "node:fs";
+import modules from "../../architecture/modules.json" with { type: "json" };
+import { siteRoutes } from "../../site/route-registry.mjs";
-const siteRoutes = JSON.parse(readFileSync(new URL("../../site/site-routes.json", import.meta.url), "utf8"));
-
-export const apiReferencePackages = [
- ["markdown", "@interactive-os/json-document-markdown", "packages/json-document-markdown/src/index.ts", "원문 문자열이 정본인 Markdown 문법과 source 위치 projection"],
- ["markdown-web", "@interactive-os/json-document-markdown-web", "packages/json-document-markdown-web/src/index.ts", "Markdown source 위치와 caret에 따른 DOM projection"],
- ["object-document", "@interactive-os/json-document-object-document", "packages/json-document-object-document/src/index.ts", "Object 문서와 Canvas 프로파일의 모델·검증·연산·projection"],
- ["canvas", "@interactive-os/json-document-canvas", "packages/json-document-canvas/src/index.ts", "한 장짜리 Canvas의 입력·preview·UI 조합"],
- ["calendar-document", "@interactive-os/json-document-calendar-document", "packages/json-document-calendar-document/src/index.ts", "Calendar 문서 모델·검증·의미 연산·projection 계약"],
- ["a2ui", "@interactive-os/json-document-a2ui", "packages/json-document-a2ui/src/index.ts", "A2UI streaming document connector"],
- ["json-document", "@interactive-os/json-document", "packages/json-document/src/application/document/index.ts", "Core document 값·주소·patch 계약"],
- ["selection", "@interactive-os/json-document-selection", "packages/json-document-selection/src/index.ts", "구조적 selection과 topology 계약"],
- ["editing", "@interactive-os/json-document-editing", "packages/json-document-editing/src/index.ts", "intent, editor, history 편집 계약"],
- ["react", "@interactive-os/json-document-react", "packages/json-document-react/src/index.ts", "React lifecycle connector"],
- ["react-hook-form", "@interactive-os/json-document-react-hook-form", "packages/json-document-react-hook-form/src/index.ts", "React Hook Form connector"],
- ["ajv", "@interactive-os/json-document-ajv", "packages/json-document-ajv/src/index.ts", "Ajv validation connector"],
- ["zod", "@interactive-os/json-document-zod", "packages/json-document-zod/src/index.ts", "Zod schema connector"],
- ["tanstack-table", "@interactive-os/json-document-tanstack-table", "packages/json-document-tanstack-table/src/index.ts", "TanStack Table connector"],
- ["affordance", "@interactive-os/json-document-affordance", "packages/json-document-affordance/src/index.ts", "입력 문법과 interaction session"],
- ["ui-primitives-react", "@interactive-os/json-document-ui-primitives-react", "packages/json-document-ui-primitives-react/src/index.ts", "표준 React UI primitive"],
- ["animation-react", "@interactive-os/json-document-animation-react", "packages/json-document-animation-react/src/index.ts", "생성 대기 시각 언어"],
- ["markdown-react", "@interactive-os/json-document-markdown-react", "packages/json-document-markdown-react/src/index.ts", "스트리밍 Markdown 투영과 렌더링"],
- ["database", "@interactive-os/json-document-database", "packages/json-document-database/src/index.ts", "Database 문서 모델·연산·saved-view projection"],
- ["annotation", "@interactive-os/json-document-annotation", "packages/json-document-annotation/src/index.ts", "Annotation interaction과 SVG projection"],
- ["calendar", "@interactive-os/json-document-calendar", "packages/json-document-calendar/src/index.ts", "Calendar React lifecycle와 occurrence interaction 계약 ([시간·반복·거절 계약: Editing의 Calendar protocol profile](/docs/api/editing#calendar-protocol-profile-rc))"],
- ["web", "@interactive-os/json-document-web", "packages/json-document-web/src/index.ts", "Web platform adapter"],
- ["contenteditable", "@interactive-os/json-document-contenteditable", "packages/json-document-contenteditable/src/index.ts", "contenteditable platform adapter"],
- ["rich-text", "@interactive-os/json-document-rich-text", "packages/json-document-rich-text/src/index.ts", "Rich Text 문서 의미와 editing 계약"],
- ["file-intake", "@interactive-os/json-document-file-intake", "packages/json-document-file-intake/src/index.ts", "플랫폼 독립 파일 후보와 수용 정책"],
- ["rich-text-suggestion", "@interactive-os/json-document-rich-text-suggestion", "packages/json-document-rich-text-suggestion/src/index.ts", "Rich Text suggestion trigger와 상태 계약"],
- ["rich-text-suggestion-react", "@interactive-os/json-document-rich-text-suggestion-react", "packages/json-document-rich-text-suggestion-react/src/index.ts", "Rich Text suggestion React interaction binding"],
- ["rich-text-mention", "@interactive-os/json-document-rich-text-mention", "packages/json-document-rich-text-mention/src/index.ts", "Rich Text entity mention schema와 삽입 계약"],
- ["rich-text-mention-react", "@interactive-os/json-document-rich-text-mention-react", "packages/json-document-rich-text-mention-react/src/index.tsx", "Rich Text mention React projection"],
- ["composer", "@interactive-os/json-document-composer", "packages/json-document-composer/src/index.ts", "Composer draft와 reference/trigger command 계약"],
- ["composer-react", "@interactive-os/json-document-composer-react", "packages/json-document-composer-react/src/index.ts", "Composer React interaction과 reference projection"],
- ["rich-text-web", "@interactive-os/json-document-rich-text-web", "packages/json-document-rich-text-web/src/index.ts", "Rich Text DOM adapter"],
- ["rich-text-react", "@interactive-os/json-document-rich-text-react", "packages/json-document-rich-text-react/src/index.tsx", "Rich Text React connector"],
- ["collaboration", "@interactive-os/json-document-collaboration", "packages/json-document-collaboration/src/index.ts", "replica, history, text collaboration runtime"],
- ["contenteditable-collaboration", "@interactive-os/json-document-contenteditable-collaboration", "packages/contenteditable-collaboration/src/index.ts", "collaborative contenteditable lease"],
-].map(([slug, packageName, entrypoint, responsibility]) => {
- const route = siteRoutes.find(route => route.path === `/docs/api/${slug}`);
- if (!route?.module || !entrypoint.startsWith(`${route.module.sourceDirectory}/`)) throw new Error(`API owner registration missing: ${packageName}`);
- return ({
- slug, packageName, entrypoint, navigationGroup: route.navigationGroup, responsibility,
- subpaths: slug === "collaboration" ? [{
- packageName: "@interactive-os/json-document-collaboration/history",
- entrypoint: "packages/json-document-collaboration/src/history-index.ts",
- }, {
- packageName: "@interactive-os/json-document-collaboration/text",
- entrypoint: "packages/json-document-collaboration/src/text-index.ts",
- }, {
- packageName: "@interactive-os/json-document-collaboration/editing",
- entrypoint: "packages/json-document-collaboration/src/editing-index.ts",
- }] : slug === "web" ? [{
- packageName: "@interactive-os/json-document-web/interaction-recording",
- entrypoint: "packages/json-document-web/src/interaction-recording-index.ts",
- }] : [],
-});
+export const apiReferencePackages = modules.map(owner => {
+ const route = siteRoutes.find(route => route.module?.packageName === owner.packageName);
+ if (!route) throw new Error(`API route missing: ${owner.packageName}`);
+ return {
+ ...owner,
+ slug: route.path.slice("/docs/api/".length),
+ navigationGroup: owner.positions[0],
+ };
});
export function apiReferenceCoverageErrors(manifests, references = apiReferencePackages) {
diff --git a/docs/evaluate.mjs b/docs/evaluate.mjs
index f3356dbd6..569b99fb4 100644
--- a/docs/evaluate.mjs
+++ b/docs/evaluate.mjs
@@ -1,3 +1,4 @@
+import { siteRoutes } from "../site/route-registry.mjs";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -8,7 +9,8 @@ const root = dirname(dirname(fileURLToPath(import.meta.url)));
for (const args of [
["scripts/generate-api-reference.mjs", "--check"],
- ["--test", "docs/api-reference/packages.test.mjs", "site/scripts/route-checks.test.mjs"],
+ ["scripts/check-architecture-drift.mjs"],
+ ["--test", "docs/api-reference/packages.test.mjs", "site/scripts/route-checks.test.mjs", "scripts/architecture-drift.test.mjs"],
]) {
const result = spawnSync(process.execPath, args, { cwd: root, encoding: "utf8" });
if (result.status !== 0) {
@@ -148,7 +150,7 @@ const implementationShape = read("standards/repository-implementation-shape.md")
const domEditingLifecycle = read("standards/dom-editing-lifecycle.md");
const editingSession = read("standards/editing-session.md");
-const documentRoutes = readJson("site/site-routes.json").filter((route) => route.documentSource !== undefined);
+const documentRoutes = siteRoutes.filter((route) => route.documentSource !== undefined);
for (const route of documentRoutes) {
for (const source of route.documentIncludes ?? []) {
if (!/^packages\/[^/]+\/docs\/[^/]+\.md$/.test(source) || !existsSync(join(root, source))) fail(`Invalid owner documentation inclusion: ${source}`);
@@ -156,7 +158,7 @@ for (const route of documentRoutes) {
}
const registeredSources = documentRoutes.map((route) => route.documentSource);
for (const source of registeredSources) {
- if (!/^docs\/(?:public|api-reference)\/[^/]+\.md$/.test(source) || !existsSync(join(root, source))) {
+ if (!/^(?:docs\/public\/[^/]+|packages\/[^/]+\/docs\/[^/]+)\.md$/.test(source) || !existsSync(join(root, source))) {
fail(`Invalid documentation source registration: ${source}`);
}
}
diff --git a/docs/public/api.md b/docs/public/api.md
index ec899b97d..7bcfd1c39 100644
--- a/docs/public/api.md
+++ b/docs/public/api.md
@@ -3,7 +3,7 @@
`JSONDocument`는 값·주소·검증·원자적 변경·관찰의 여섯 member를 갖는 공통
계약입니다. 로컬 구현과 [Collaboration](collaboration.md)이 같은 계약을
제공합니다. Core v3의 Stable 계약과 현재 TypeScript API를 작업별로 정리합니다.
-package 전체 시그니처는 [JSON Document API](../api-reference/json-document.md)에 있습니다.
+package 전체 시그니처는 [JSON Document API](../../packages/json-document/docs/api-reference.md)에 있습니다.
## 문서 만들기
diff --git a/docs/public/architecture.md b/docs/public/architecture.md
index 8c0150ba9..558027207 100644
--- a/docs/public/architecture.md
+++ b/docs/public/architecture.md
@@ -2,16 +2,18 @@
문서는 세 관계를 구분합니다. **책임 위치**는 무엇을 소유하는지, **공개 API**는
저장소가 실제로 제공하는 계약, **Usage와 Application**은 그 계약을 사용하는
-증거입니다. 위의 현재 저장소 지도는 사이트 등록에서 API·Usage·제품 관계를
-읽습니다. 문서 목록을 별도로 복제하지 않습니다.
+증거입니다. 위의 현재 저장소 지도는 `architecture/modules.json`에서 책임·위치·공개 계약을,
+사이트 등록에서 문서 URL·Usage·제품 관계를 읽습니다. 생성 API 본문은 각
+`packages/*/docs/api-reference.md`에 있습니다. 문서 목록을 별도로 복제하지 않습니다.
-- Foundation과 Building Blocks의 책임별 개요에서 모듈 API와 Usage를 찾습니다.
+- [모듈](modules.md)의 책임별 개요에서 API와 Usage를 찾습니다.
- API 문서의 책임과 사용 경로에서 혼합 책임·이행 상태와 소비 제품을 확인합니다.
- Usage의 Source에서 구현을 확인하고, 제품은 확인된 모듈 조합으로 돌아갑니다.
- 패키지 공개 여부, Document Type 소유권 확정, Hand Profile 완료는 다른 상태입니다.
목표는 같은 역할과 책임이 하나의 정본 모듈을 갖고, Application이 그 공개 API를
-조합하는 구조입니다. 아래는 읽기 순서와 책임 지도입니다. 모든 package가 차례로
+조합하는 구조입니다. 아래는 책임 지도입니다. 사이트의 읽기 순서는 시작하기·모듈·Hands·Applications·
+설계와 진행 상태이며, 책임 지도를 탐색 목적에 맞춰 투영합니다. 모든 package가 차례로
의존하는 직렬 계층은 아닙니다.
```text
@@ -111,7 +113,7 @@ URL과 navigation, 권한·copy·fixture·layout과 concrete runtime 연결을
각 canonical module의 책임입니다. 한 제품에서만 쓰여도 이 경계는 같습니다.
[Calendar와 AI Agent](applications.md)는 현재 제품에서 드러난 조합을 보여 줍니다.
-읽기 순서는 Foundation에서 Application으로 가지만, 책임을 발견하는 작업은
+책임 지도는 기반 계약부터 제품 조합까지 설명하지만, 책임을 발견하는 작업은
Application에서 시작해 정본 API를 만들고 제품이 다시 소비하는 순환입니다.
이 과정은 [How We Build](how-we-build.md)에 있습니다.
@@ -121,3 +123,15 @@ Rich Text는 새 최상위 계층이 아니라 이 책임 지도를 적용한
문서 의미와 Editing 위에 Web Adapter, React Connector, 장르별 UI를 조합합니다.
현재 profile·적합성·browser 증거는 다른 Hands가 경계를 판단할 때 참고할 수 있지만
모든 Document Type과 Hands의 완료를 대신하지는 않습니다.
+
+## 저장소와 문서 사이의 drift 검사
+
+`npm run check:architecture`는 실제 workspace·package exports·source entrypoint·
+패키지 소유 API 문서·사이트 등록을 대조합니다. 제품에 등록된 대표 모듈은 해당
+route source에서 import와 re-export를 따라 도달할 수 있어야 합니다.
+`npm run check:architecture -- --evidence`로 그 경로를 출력합니다.
+
+이 검사는 type import와 barrel re-export를 포함하는 정적 소스 관계입니다.
+실행 시 호출 여부, tree shaking 후 번들, 전체 제품 의존성이나 책임 분류의 의미적
+정당성을 증명하지 않습니다. 소스 표시를 위한 `?raw` import는 소비 증거에서 제외합니다.
+실제 Usage 동작은 브라우저 검사로, 혼합 책임과 Profile 완료는 소유권 감사로 확인합니다.
diff --git a/docs/public/connector-a2ui.md b/docs/public/connector-a2ui.md
index ada3e61ff..348789807 100644
--- a/docs/public/connector-a2ui.md
+++ b/docs/public/connector-a2ui.md
@@ -22,4 +22,4 @@ Connector는 SDK envelope 검증, JSONL buffer, 메시지→JSON Patch와 문서
## Reference
-[전체 public API](../api-reference/a2ui.md) · [패키지 README](../../packages/json-document-a2ui/README.md)
+[전체 public API](../../packages/json-document-a2ui/docs/api-reference.md) · [패키지 README](../../packages/json-document-a2ui/README.md)
diff --git a/docs/public/design.md b/docs/public/design.md
new file mode 100644
index 000000000..e2d7ce132
--- /dev/null
+++ b/docs/public/design.md
@@ -0,0 +1,17 @@
+# 설계와 진행 상태
+
+현재 제공하는 모듈과 별개로, 아직 완성되지 않은 목표·프로토타입·소유권 경계를
+확인하는 곳입니다. API가 공개됐다는 사실과 Profile의 완료를 구분합니다.
+
+## 설계 목표
+
+- [Artifact · Prototype](/viewer): 여러 콘텐츠 surface를 조합한 시각적 실험입니다. 문서·Hands 계약 연결과 파일 호환성은 미완료입니다.
+- [Official Hands 목표](official-hands.md): 기본 편집을 완성된 SDK로 제공하기 위한 Profile 조건입니다.
+- [소유권 감사](ownership.md): Document Type 후보별 정본 소유자와 남은 경계를 확인합니다.
+- [개발 원칙](how-we-build.md): 제품에서 책임을 발견하고 정본 모듈로 반영하는 과정입니다.
+
+## 현재 제공하는 것
+
+바로 사용할 계약은 [모듈](modules.md), 장르별 실행 예제는 [Hands](hands.md),
+구체 제품 조합은 [Applications](applications.md)에서 찾습니다.
+목표 책임의 전체 관계는 [Architecture](architecture.md)에서 설명합니다.
diff --git a/docs/public/editing.md b/docs/public/editing.md
index aa224fce1..954247c3b 100644
--- a/docs/public/editing.md
+++ b/docs/public/editing.md
@@ -39,7 +39,7 @@ interface로 통합하거나 새로운 wire protocol을 정의하지 않습니
다음 작업이 실행돼도 반환 결과는 자신의 전이에 속합니다. `revision`은 문서
commit 횟수가 아니라 편집 상태의 전이입니다.
-구체 시그니처는 [Editing API](../api-reference/editing.md), 사용법은
+구체 시그니처는 [Editing API](../../packages/json-document-editing/docs/api-reference.md), 사용법은
[Intent guide](intent-guide.md)와 [Intent](intent.md)에서 봅니다.
## 값을 바꾸지 않는 경로
diff --git a/docs/public/hands-support.md b/docs/public/hands-support.md
new file mode 100644
index 000000000..93e41c376
--- /dev/null
+++ b/docs/public/hands-support.md
@@ -0,0 +1,21 @@
+# 지원 범위
+
+Hands는 장르의 문서·편집·입력·UI를 조합한 편집 경험입니다.
+사용 가능한 예제와 API가 있어도 모든 입력·실패·History·호환성 조건을
+검증한 Official Hands Profile의 완료를 뜻하지 않습니다.
+
+## 확인하는 순서
+
+1. [장르별 예제](hands.md)에서 실제 편집 동작을 실행합니다.
+2. 해당 owner API에서 지원 입력과 연산, 제약을 확인합니다.
+3. Usage의 Source에서 정본 구현과 연결 방식을 확인합니다.
+4. [소유권 감사](ownership.md)와 [Official Hands 목표](official-hands.md)에서 남은 경계를 확인합니다.
+
+## 현재 근거
+
+- [Calendar](../../packages/json-document-calendar/docs/api-reference.md): 공개 Hand 계약과 [Calendar 예제](hands.md#calendar-editor)를 확인할 수 있습니다.
+- [Annotation](../../packages/json-document-annotation/docs/api-reference.md): 공개 Hand 계약과 [Annotation 예제](hands.md#annotation-editor)를 확인할 수 있습니다.
+- [Rich Text](/editing/rich-text): 문서·편집·Web·React를 연결한 대표 편집 조합입니다.
+- 다른 장르는 [Hands](hands.md)의 예제와 모듈 API에서 각각 지원 범위를 확인합니다.
+
+특정 장르의 검증 결과를 모든 Hands의 지원 보장으로 확대하지 않습니다.
diff --git a/docs/public/hands.md b/docs/public/hands.md
index 7ffbdd972..3de962f68 100644
--- a/docs/public/hands.md
+++ b/docs/public/hands.md
@@ -1,5 +1,8 @@
# Hands
+장르별 편집 예제와 공개 API를 확인합니다. [지원 범위](hands-support.md)에서
+현재 증거와 Profile 완료 조건을 구분하고, 설계 목표는 [설계 현황](design.md)에서 봅니다.
+
## Annotation editor
Raster Annotation의 persistent model과 editing session은
diff --git a/docs/public/how-we-build.md b/docs/public/how-we-build.md
index b26253479..b667df707 100644
--- a/docs/public/how-we-build.md
+++ b/docs/public/how-we-build.md
@@ -20,7 +20,7 @@ Application이 정본 API를 다시 소비한다
읽기 순서와 책임을 발견하는 방향을 구별합니다.
```text
-읽기 순서: Foundation → Building Blocks → Hands → Artifact → Application
+읽기 순서: 시작하기 → 모듈 → Hands → Applications → 설계와 진행 상태
책임 발견: Application → 책임 발견 → Canonical Module → Application
```
diff --git a/docs/public/llms.txt b/docs/public/llms.txt
index 3e747bf98..b2fccc7e7 100644
--- a/docs/public/llms.txt
+++ b/docs/public/llms.txt
@@ -11,7 +11,8 @@ reference·독립·collaboration binding에서 검증한다.
## 목표 구조와 현재 상태
-읽기 순서는 Foundation → Building Blocks → Hands → Artifact → Applications다.
+사이트 탐색은 시작하기 → 모듈 → 편집 조합 · Hands → Applications → 설계와 진행 상태다.
+Artifact·Official Hands 목표·소유권 감사는 설계 영역에 둔다.
필수 package dependency chain이 아니다. Foundation은 JSON Document, Document
Types, Editing, Collaboration을, Building Blocks는 Adapter, Connector,
Affordance, UI Primitives를 묶는다. Adapter와 Connector는 독립적으로 선택한다.
diff --git a/docs/public/modules.md b/docs/public/modules.md
new file mode 100644
index 000000000..092daaf6b
--- /dev/null
+++ b/docs/public/modules.md
@@ -0,0 +1,15 @@
+# 모듈
+
+필요한 책임에서 시작해 설명과 공개 API, 실행 가능한 Usage를 찾습니다.
+각 API의 Usage 링크에서 동작을 확인하고 Source 탭으로 구현을 따라갈 수 있습니다.
+
+## 선택 기준
+
+- 값·주소·변경·구독은 [JSON Document](api.md)를 사용합니다.
+- 문서의 의미와 유효성은 [Document Types](document-types.md), 편집 상태와 작업은 [Editing](editing.md)이 맡습니다.
+- 여러 참여자의 변경은 [Collaboration](collaboration.md), 플랫폼 입출력은 [Adapter](adapters.md)에서 찾습니다.
+- 외부 프레임워크·라이브러리는 [Connector](connectors.md), 조작 의미는 [Affordance](affordance.md), 반복 UI는 [UI Primitives](ui-primitives.md)가 맡습니다.
+
+장르별로 묶인 API와 예제는 [편집 조합 · Hands](hands.md)에 있습니다.
+전체 관계는 [Architecture](architecture.md), 미완료 목표와 소유권 감사는
+[설계와 진행 상태](design.md)에서 확인합니다. 메뉴 순서는 패키지 의존 순서가 아닙니다.
diff --git a/docs/public/overview.md b/docs/public/overview.md
index 25baa871b..b62ee47e7 100644
--- a/docs/public/overview.md
+++ b/docs/public/overview.md
@@ -1,5 +1,8 @@
# 왜 이 기반을 만드는가
+처음 사용한다면 [빠른 시작](quick-start.md), 필요한 계약은 [모듈](modules.md),
+목표와 미완료 상태는 [설계와 진행 상태](design.md)에서 찾을 수 있습니다.
+
에이전트는 이제 답변만 생성하지 않습니다. 문서와 표, 발표 자료 같은
artifact를 만들고, 사람은 그 결과를 곧바로 고쳐서 일을 이어 갑니다.
생성될 artifact마다 예전 방식의 제품 FE를 따로 설계하고 구현할 수는 없지만,
diff --git a/docs/public/ownership.md b/docs/public/ownership.md
new file mode 100644
index 000000000..35244aa0b
--- /dev/null
+++ b/docs/public/ownership.md
@@ -0,0 +1,13 @@
+# 소유권 감사
+
+아래 후보 상태는 기존 Document Type 감사 기록에서 읽습니다.
+소유권 확정은 전체 Profile이나 wire 계약의 Stable을 뜻하지 않습니다.
+
+## 상태 읽기
+
+- **소유권 확정 · RC**: 감사에서 해당 책임의 정본 소유자와 소비 경계를 확인했습니다.
+- **후보 · TBD**: 목표 계약과 현재 소유자가 아직 수렴하지 않았습니다.
+
+후보 이름을 눌러 현재 owner, 목표 경계와 감사 근거를 확인하세요.
+현재 제공하는 계약은 [Document Types](document-types.md),
+설계 목표 전체는 [설계와 진행 상태](design.md)에 있습니다.
diff --git a/docs/public/quick-start.md b/docs/public/quick-start.md
new file mode 100644
index 000000000..cdb349952
--- /dev/null
+++ b/docs/public/quick-start.md
@@ -0,0 +1,43 @@
+# 빠른 시작
+
+JSON Document는 값을 읽고 JSON Patch로 변경하는 공통 계약입니다.
+Core부터 사용하고, 편집 UI와 플랫폼 연결은 필요한 모듈을 추가합니다.
+
+## 설치
+
+```sh
+npm install @interactive-os/json-document
+```
+
+## 값 변경과 구독
+
+```ts
+import { createJSONDocument } from "@interactive-os/json-document";
+
+const document = createJSONDocument({ title: "첫 문서" });
+const unsubscribe = document.subscribe(() => {
+ console.log(document.value);
+});
+
+document.commit([{ op: "replace", path: "/title", value: "수정한 문서" }]);
+console.log(document.at("/title"));
+unsubscribe();
+```
+
+읽기·검증·변경·구독의 세부 계약은 [JSON Document](api.md),
+공개 symbol은 [Core API](../../packages/json-document/docs/api-reference.md)에서 확인합니다.
+
+## 직접 실행하기
+
+아래 Usage에서 값을 바꾸고 Source 탭에서 정본 구현을 확인하세요.
+
+```live-demo
+/demo
+```
+
+## 다음 선택
+
+- 입력·선택·실행 취소를 붙이려면 [Editing](editing.md)을 봅니다.
+- React나 스키마 라이브러리를 연결하려면 [Connector](connectors.md)를 봅니다.
+- 장르별 편집 경험은 [Hands](hands.md), 제품 조합은 [Applications](applications.md)에서 확인합니다.
+- 전체 책임은 [모듈](modules.md), 관계와 경계는 [Architecture](architecture.md)에서 찾습니다.
diff --git a/package.json b/package.json
index a428f3b6d..c81ccbfbb 100644
--- a/package.json
+++ b/package.json
@@ -77,7 +77,9 @@
"site:evaluate:pages": "SITE_BASE=/json-document/ SITE_URL=https://developer-1px.github.io/json-document npm run site:evaluate",
"site:smoke:pages": "SITE_BASE=/json-document/ SITE_URL=https://developer-1px.github.io/json-document node site/scripts/evaluate-http.mjs",
"site:verify:pages": "npm run site:build:pages && npm run site:evaluate:pages && npm run site:smoke:pages",
- "site:evaluate:live": "node site/scripts/evaluate-live.mjs"
+ "site:evaluate:live": "node site/scripts/evaluate-live.mjs",
+ "check:architecture": "node scripts/check-architecture-drift.mjs",
+ "test:architecture": "node --test scripts/architecture-drift.test.mjs"
},
"devDependencies": {
"jsonpath-js": "0.3.1",
diff --git a/docs/api-reference/contenteditable-collaboration.md b/packages/contenteditable-collaboration/docs/api-reference.md
similarity index 89%
rename from docs/api-reference/contenteditable-collaboration.md
rename to packages/contenteditable-collaboration/docs/api-reference.md
index 570309c06..e65affb33 100644
--- a/docs/api-reference/contenteditable-collaboration.md
+++ b/packages/contenteditable-collaboration/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Adapter
-collaborative contenteditable lease의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+협업 문자열과 native DOM 입력 lease 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/contenteditable-collaboration/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/a2ui.md b/packages/json-document-a2ui/docs/api-reference.md
similarity index 83%
rename from docs/api-reference/a2ui.md
rename to packages/json-document-a2ui/docs/api-reference.md
index b7a753a8a..180716fc6 100644
--- a/docs/api-reference/a2ui.md
+++ b/packages/json-document-a2ui/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-A2UI streaming document connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+A2UI 메시지와 문서·UI projection 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-a2ui/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/affordance.md b/packages/json-document-affordance/docs/api-reference.md
similarity index 98%
rename from docs/api-reference/affordance.md
rename to packages/json-document-affordance/docs/api-reference.md
index ac1aa6da8..37cb9b461 100644
--- a/docs/api-reference/affordance.md
+++ b/packages/json-document-affordance/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Affordance
-입력 문법과 interaction session의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+선택·이동·취소 등 입력 독립 조작 의미와 수명의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-affordance/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/ajv.md b/packages/json-document-ajv/docs/api-reference.md
similarity index 69%
rename from docs/api-reference/ajv.md
rename to packages/json-document-ajv/docs/api-reference.md
index 2a4af2713..36137f110 100644
--- a/docs/api-reference/ajv.md
+++ b/packages/json-document-ajv/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Ajv validation connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Ajv 검증 결과를 문서 진단으로 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-ajv/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/animation-react.md b/packages/json-document-animation-react/docs/api-reference.md
similarity index 90%
rename from docs/api-reference/animation-react.md
rename to packages/json-document-animation-react/docs/api-reference.md
index 5073bcd16..b0035bd32 100644
--- a/docs/api-reference/animation-react.md
+++ b/packages/json-document-animation-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** UI Primitives
-생성 대기 시각 언어의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+진행·대기·전환 상태의 반복 시각 행동의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-animation-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/annotation.md b/packages/json-document-annotation/docs/api-reference.md
similarity index 89%
rename from docs/api-reference/annotation.md
rename to packages/json-document-annotation/docs/api-reference.md
index 4e8cfed6c..f8d565888 100644
--- a/docs/api-reference/annotation.md
+++ b/packages/json-document-annotation/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Annotation interaction과 SVG projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Raster annotation 도구·선택·제스처·UI 조합의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-annotation/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/calendar-document.md b/packages/json-document-calendar-document/docs/api-reference.md
similarity index 96%
rename from docs/api-reference/calendar-document.md
rename to packages/json-document-calendar-document/docs/api-reference.md
index 08fad9e21..a1ba9ce8e 100644
--- a/docs/api-reference/calendar-document.md
+++ b/packages/json-document-calendar-document/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Document Types
-Calendar 문서 모델·검증·의미 연산·projection 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Calendar 이벤트·반복·구간·발생분 projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-calendar-document/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/calendar.md b/packages/json-document-calendar/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/calendar.md
rename to packages/json-document-calendar/docs/api-reference.md
index af3f9cd6a..4dcb0fa2c 100644
--- a/docs/api-reference/calendar.md
+++ b/packages/json-document-calendar/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Calendar React lifecycle와 occurrence interaction 계약 ([시간·반복·거절 계약: Editing의 Calendar protocol profile](/docs/api/editing#calendar-protocol-profile-rc))의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Calendar 입력·선택·시간 표시·제어 UI 조합의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-calendar/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/packages/json-document-calendar/docs/api.md b/packages/json-document-calendar/docs/api.md
new file mode 100644
index 000000000..5a5e23cca
--- /dev/null
+++ b/packages/json-document-calendar/docs/api.md
@@ -0,0 +1,7 @@
+## Calendar Hand와 편집 계약
+
+Calendar Hand는 React lifecycle과 viewport·입력·UI 조합을 제공합니다.
+시간·반복·거절의 의미는 UI에서 다시 정의하지 않고 정본 Editing 계약을 소비합니다.
+
+[시간·반복·거절 계약: Editing의 Calendar protocol profile](/docs/api/editing#calendar-protocol-profile-rc)에서
+지원 범위와 실패 시 문서·선택·History 보존 조건을 확인하세요.
diff --git a/docs/api-reference/canvas.md b/packages/json-document-canvas/docs/api-reference.md
similarity index 86%
rename from docs/api-reference/canvas.md
rename to packages/json-document-canvas/docs/api-reference.md
index 8c8d842c4..0ae2f5e8b 100644
--- a/docs/api-reference/canvas.md
+++ b/packages/json-document-canvas/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-한 장짜리 Canvas의 입력·preview·UI 조합의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Canvas 선택·변형·입력·UI 조합의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-canvas/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/packages/json-document-collaboration/README.md b/packages/json-document-collaboration/README.md
index f77473653..f61ef24a5 100644
--- a/packages/json-document-collaboration/README.md
+++ b/packages/json-document-collaboration/README.md
@@ -32,7 +32,7 @@ One causal commit is one undo step. Editing's local `historyGroup` does not grou
causal changes, and an external-history Editing plan cannot opt out of recording.
History remains local unless this connection is explicitly configured.
See [Collaborative History](../../docs/public/collaboration-history.md) and the
-owner [API reference](../../docs/api-reference/collaboration.md).
+owner [API reference](../../packages/json-document-collaboration/docs/api-reference.md).
Successful `runtime.history.undo/redo` results include their own `change`
(`JSONAppliedChange | null`) and immutable resulting `status`, including
diff --git a/docs/api-reference/collaboration.md b/packages/json-document-collaboration/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/collaboration.md
rename to packages/json-document-collaboration/docs/api-reference.md
index 09f7b84a6..9276ac83b 100644
--- a/docs/api-reference/collaboration.md
+++ b/packages/json-document-collaboration/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Collaboration
-replica, history, text collaboration runtime의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+동일 JSONDocument 계약의 복제·병합·협업 History의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-collaboration/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/composer-react.md b/packages/json-document-composer-react/docs/api-reference.md
similarity index 89%
rename from docs/api-reference/composer-react.md
rename to packages/json-document-composer-react/docs/api-reference.md
index 3b5724afc..2e916aa97 100644
--- a/docs/api-reference/composer-react.md
+++ b/packages/json-document-composer-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Composer React interaction과 reference projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Composer 편집 관찰과 참조 atom을 React에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-composer-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/composer.md b/packages/json-document-composer/docs/api-reference.md
similarity index 95%
rename from docs/api-reference/composer.md
rename to packages/json-document-composer/docs/api-reference.md
index a93049302..c93a93211 100644
--- a/docs/api-reference/composer.md
+++ b/packages/json-document-composer/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Composer draft와 reference/trigger command 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+텍스트와 구조화된 문맥을 한 입력으로 구성의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-composer/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/contenteditable.md b/packages/json-document-contenteditable/docs/api-reference.md
similarity index 93%
rename from docs/api-reference/contenteditable.md
rename to packages/json-document-contenteditable/docs/api-reference.md
index 8c6439f81..25992456b 100644
--- a/docs/api-reference/contenteditable.md
+++ b/packages/json-document-contenteditable/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Adapter
-contenteditable platform adapter의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+DOM 입력·IME·선택 복원·기호 투영·화면 줄 탐색의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-contenteditable/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/database.md b/packages/json-document-database/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/database.md
rename to packages/json-document-database/docs/api-reference.md
index eb127e3de..3d276f115 100644
--- a/docs/api-reference/database.md
+++ b/packages/json-document-database/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Database 문서 모델·연산·saved-view projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Database record와 view를 편집 UI로 조합의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-database/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/editing.md b/packages/json-document-editing/docs/api-reference.md
similarity index 99%
rename from docs/api-reference/editing.md
rename to packages/json-document-editing/docs/api-reference.md
index 342aac180..bd77e56b6 100644
--- a/docs/api-reference/editing.md
+++ b/packages/json-document-editing/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Editing
-intent, editor, history 편집 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Intent·변경 계획·선택·Clipboard·History의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-editing/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/file-intake.md b/packages/json-document-file-intake/docs/api-reference.md
similarity index 83%
rename from docs/api-reference/file-intake.md
rename to packages/json-document-file-intake/docs/api-reference.md
index b2c5595f6..467b2c133 100644
--- a/docs/api-reference/file-intake.md
+++ b/packages/json-document-file-intake/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Document Types
-플랫폼 독립 파일 후보와 수용 정책의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+직렬화 가능한 파일 후보·이미지 내용과 수용 검증의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-file-intake/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/markdown-react.md b/packages/json-document-markdown-react/docs/api-reference.md
similarity index 81%
rename from docs/api-reference/markdown-react.md
rename to packages/json-document-markdown-react/docs/api-reference.md
index cde247432..e35f6e410 100644
--- a/docs/api-reference/markdown-react.md
+++ b/packages/json-document-markdown-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-스트리밍 Markdown 투영과 렌더링의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Markdown 렌더링·편집 표면과 React 수명 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-markdown-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/markdown-web.md b/packages/json-document-markdown-web/docs/api-reference.md
similarity index 71%
rename from docs/api-reference/markdown-web.md
rename to packages/json-document-markdown-web/docs/api-reference.md
index 4afb97968..754800412 100644
--- a/docs/api-reference/markdown-web.md
+++ b/packages/json-document-markdown-web/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Adapter
-Markdown source 위치와 caret에 따른 DOM projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Markdown 원문과 편집 DOM·입력 binding의 대응의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-markdown-web/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/markdown.md b/packages/json-document-markdown/docs/api-reference.md
similarity index 91%
rename from docs/api-reference/markdown.md
rename to packages/json-document-markdown/docs/api-reference.md
index 5ac4dd1c2..d08f91c15 100644
--- a/docs/api-reference/markdown.md
+++ b/packages/json-document-markdown/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Document Types
-원문 문자열이 정본인 Markdown 문법과 source 위치 projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Markdown 원문 구문·좌표·체크·문단·목록 연산의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-markdown/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/object-document.md b/packages/json-document-object-document/docs/api-reference.md
similarity index 94%
rename from docs/api-reference/object-document.md
rename to packages/json-document-object-document/docs/api-reference.md
index 580539d9a..8f4b0a541 100644
--- a/docs/api-reference/object-document.md
+++ b/packages/json-document-object-document/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Document Types
-Object 문서와 Canvas 프로파일의 모델·검증·연산·projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Object·Canvas 모델·검증·의미 연산·projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-object-document/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/react-hook-form.md b/packages/json-document-react-hook-form/docs/api-reference.md
similarity index 85%
rename from docs/api-reference/react-hook-form.md
rename to packages/json-document-react-hook-form/docs/api-reference.md
index b7db9efd7..cbbb031b8 100644
--- a/docs/api-reference/react-hook-form.md
+++ b/packages/json-document-react-hook-form/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-React Hook Form connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+폼 draft·검증 결과를 문서 변경에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-react-hook-form/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/react.md b/packages/json-document-react/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/react.md
rename to packages/json-document-react/docs/api-reference.md
index d84fb355a..bb95e25fa 100644
--- a/docs/api-reference/react.md
+++ b/packages/json-document-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-React lifecycle connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+문서·편집 관찰과 React 수명 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-mention-react.md b/packages/json-document-rich-text-mention-react/docs/api-reference.md
similarity index 84%
rename from docs/api-reference/rich-text-mention-react.md
rename to packages/json-document-rich-text-mention-react/docs/api-reference.md
index 2f49240d9..085de6db4 100644
--- a/docs/api-reference/rich-text-mention-react.md
+++ b/packages/json-document-rich-text-mention-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Rich Text mention React projection의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Mention 관찰·수명을 React에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-mention-react/src/index.tsx`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-mention.md b/packages/json-document-rich-text-mention/docs/api-reference.md
similarity index 86%
rename from docs/api-reference/rich-text-mention.md
rename to packages/json-document-rich-text-mention/docs/api-reference.md
index 349a46524..5936fc387 100644
--- a/docs/api-reference/rich-text-mention.md
+++ b/packages/json-document-rich-text-mention/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Rich Text entity mention schema와 삽입 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+안정된 entity reference를 삽입하는 Mention 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-mention/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-react.md b/packages/json-document-rich-text-react/docs/api-reference.md
similarity index 90%
rename from docs/api-reference/rich-text-react.md
rename to packages/json-document-rich-text-react/docs/api-reference.md
index 9f06ea9a6..20fab2d9d 100644
--- a/docs/api-reference/rich-text-react.md
+++ b/packages/json-document-rich-text-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Rich Text React connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Rich Text 편집 관찰·DOM 표면과 React 수명 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-react/src/index.tsx`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-suggestion-react.md b/packages/json-document-rich-text-suggestion-react/docs/api-reference.md
similarity index 84%
rename from docs/api-reference/rich-text-suggestion-react.md
rename to packages/json-document-rich-text-suggestion-react/docs/api-reference.md
index 2f2c6f297..b38910634 100644
--- a/docs/api-reference/rich-text-suggestion-react.md
+++ b/packages/json-document-rich-text-suggestion-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Rich Text suggestion React interaction binding의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Suggestion 관찰·수명을 React에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-suggestion-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-suggestion.md b/packages/json-document-rich-text-suggestion/docs/api-reference.md
similarity index 88%
rename from docs/api-reference/rich-text-suggestion.md
rename to packages/json-document-rich-text-suggestion/docs/api-reference.md
index b70154e5d..11ce832e0 100644
--- a/docs/api-reference/rich-text-suggestion.md
+++ b/packages/json-document-rich-text-suggestion/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Hands
-Rich Text suggestion trigger와 상태 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+입력 문맥에서 suggestion을 찾고 적용하는 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-suggestion/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text-web.md b/packages/json-document-rich-text-web/docs/api-reference.md
similarity index 84%
rename from docs/api-reference/rich-text-web.md
rename to packages/json-document-rich-text-web/docs/api-reference.md
index 9311c44f0..926eb80a8 100644
--- a/docs/api-reference/rich-text-web.md
+++ b/packages/json-document-rich-text-web/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Adapter
-Rich Text DOM adapter의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Rich Text와 DOM·Clipboard·native 입력의 대응의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text-web/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/rich-text.md b/packages/json-document-rich-text/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/rich-text.md
rename to packages/json-document-rich-text/docs/api-reference.md
index 6f648e3a6..faecb30b0 100644
--- a/docs/api-reference/rich-text.md
+++ b/packages/json-document-rich-text/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Editing
-Rich Text 문서 의미와 editing 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Rich Text 모델·의미 연산과 텍스트 편집의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-rich-text/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/selection.md b/packages/json-document-selection/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/selection.md
rename to packages/json-document-selection/docs/api-reference.md
index 05dad5d39..c8f136d37 100644
--- a/docs/api-reference/selection.md
+++ b/packages/json-document-selection/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Editing
-구조적 selection과 topology 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+범위·키·기하 선택과 선택 전이의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-selection/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/tanstack-table.md b/packages/json-document-tanstack-table/docs/api-reference.md
similarity index 85%
rename from docs/api-reference/tanstack-table.md
rename to packages/json-document-tanstack-table/docs/api-reference.md
index ee676c51c..46463dc9f 100644
--- a/docs/api-reference/tanstack-table.md
+++ b/packages/json-document-tanstack-table/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-TanStack Table connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+TanStack Table의 표시 모델을 편집 topology에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-tanstack-table/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/ui-primitives-react.md b/packages/json-document-ui-primitives-react/docs/api-reference.md
similarity index 97%
rename from docs/api-reference/ui-primitives-react.md
rename to packages/json-document-ui-primitives-react/docs/api-reference.md
index 7b2079b8f..f9efde10a 100644
--- a/docs/api-reference/ui-primitives-react.md
+++ b/packages/json-document-ui-primitives-react/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** UI Primitives
-표준 React UI primitive의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+표준 제어·포커스·overlay·접근성 행동의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-ui-primitives-react/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/web.md b/packages/json-document-web/docs/api-reference.md
similarity index 99%
rename from docs/api-reference/web.md
rename to packages/json-document-web/docs/api-reference.md
index ea0d57e86..b661b693b 100644
--- a/docs/api-reference/web.md
+++ b/packages/json-document-web/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Adapter
-Web platform adapter의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+키보드·포인터·Clipboard·DOM 관찰·입력 기록의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-web/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/zod.md b/packages/json-document-zod/docs/api-reference.md
similarity index 75%
rename from docs/api-reference/zod.md
rename to packages/json-document-zod/docs/api-reference.md
index efa6b5603..622203ad6 100644
--- a/docs/api-reference/zod.md
+++ b/packages/json-document-zod/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** Connector
-Zod schema connector의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+Zod schema·오류를 문서 계약에 연결의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document-zod/src/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/docs/api-reference/json-document.md b/packages/json-document/docs/api-reference.md
similarity index 93%
rename from docs/api-reference/json-document.md
rename to packages/json-document/docs/api-reference.md
index 64b44fa0d..1964f9ac1 100644
--- a/docs/api-reference/json-document.md
+++ b/packages/json-document/docs/api-reference.md
@@ -2,7 +2,7 @@
**탐색 분류:** JSON Document
-Core document 값·주소·patch 계약의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
+JSON 값·주소·검증·원자적 변경·구독의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.
> 이 문서는 `packages/json-document/src/application/document/index.ts`에서 생성됩니다. API를 변경한 뒤 `npm run docs:api`를 실행하세요.
diff --git a/scripts/architecture-drift.mjs b/scripts/architecture-drift.mjs
new file mode 100644
index 000000000..52174a3f8
--- /dev/null
+++ b/scripts/architecture-drift.mjs
@@ -0,0 +1,92 @@
+import { existsSync, readFileSync } from "node:fs";
+import { join, relative, resolve } from "node:path";
+import ts from "typescript";
+
+/** Static source reachability, including type imports and re-exports; not runtime calls. */
+export function sourceImportGraph(root, entrypoint, modules) {
+ const byName = new Map(modules.map(module => [module.packageName, module]));
+ const paths = Object.fromEntries(modules.flatMap(module => [module, ...module.subpaths]
+ .map(entry => [entry.packageName, [entry.entrypoint]])));
+ const options = { baseUrl: root, paths, moduleResolution: ts.ModuleResolutionKind.Bundler, allowJs: true, jsx: ts.JsxEmit.ReactJSX };
+ const queue = [[entrypoint]];
+ const visited = new Set();
+ const packages = new Map();
+ const errors = [];
+ while (queue.length) {
+ const chain = queue.shift();
+ const file = chain.at(-1);
+ if (visited.has(file)) continue;
+ visited.add(file);
+ if (!existsSync(join(root, file))) { errors.push(`Missing source: ${file}`); continue; }
+ const source = readFileSync(join(root, file), "utf8");
+ for (const imported of ts.preProcessFile(source, true, true).importedFiles) {
+ const specifier = imported.fileName;
+ // Vite raw/url imports display source text; they do not consume its APIs.
+ if (/[?](?:raw|url)(?:&|$)/.test(specifier)) continue;
+ const packageName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
+ const owner = byName.get(packageName);
+ if (owner) {
+ const manifest = JSON.parse(readFileSync(join(root, owner.sourceDirectory, "package.json"), "utf8"));
+ const key = specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
+ if (!Object.hasOwn(manifest.exports ?? {}, key)) errors.push(`Non-public import: ${file} -> ${specifier}`);
+ if (!packages.has(packageName)) packages.set(packageName, [...chain, specifier]);
+ }
+ if (!owner && !specifier.startsWith(".")) continue;
+ if (/\.(?:css|json|svg|png)(?:\?|$)/.test(specifier)) continue;
+ const resolved = ts.resolveModuleName(specifier, resolve(root, file), options, ts.sys).resolvedModule?.resolvedFileName;
+ if (!resolved) { errors.push(`Unresolved source import: ${file} -> ${specifier}`); continue; }
+ const path = relative(root, resolved);
+ if (path.startsWith("../") || path.includes("node_modules/")) continue;
+ queue.push([...chain, path]);
+ }
+ }
+ return { packages, errors };
+}
+
+export function moduleRegistrationErrors(root, modules, routes) {
+ const errors = [];
+ const workspaces = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).workspaces.filter(path => path.startsWith("packages/"));
+ for (const directory of workspaces) {
+ if (modules.filter(module => module.sourceDirectory === directory).length !== 1) errors.push(`Architecture owner denominator: ${directory}`);
+ }
+ for (const owner of modules) {
+ if (!workspaces.includes(owner.sourceDirectory)) { errors.push(`Unknown package directory: ${owner.sourceDirectory}`); continue; }
+ const manifest = JSON.parse(readFileSync(join(root, owner.sourceDirectory, "package.json"), "utf8"));
+ if (manifest.name !== owner.packageName) errors.push(`Package name drift: ${owner.sourceDirectory}`);
+ if (!owner.responsibility?.trim() || !owner.positions?.length || new Set(owner.positions).size !== owner.positions.length) errors.push(`Invalid responsibility: ${owner.packageName}`);
+ const page = routes.filter(route => route.module?.packageName === owner.packageName);
+ if (page.length !== 1 || page[0].documentSource !== owner.referencePath) errors.push(`API owner route drift: ${owner.packageName}`);
+ if (owner.referencePath !== `${owner.sourceDirectory}/docs/api-reference.md` || !existsSync(join(root, owner.referencePath))) errors.push(`Package-owned reference missing: ${owner.packageName}`);
+ const config = ts.readConfigFile(join(root, owner.sourceDirectory, "tsconfig.json"), ts.sys.readFile);
+ const compiler = ts.parseJsonConfigFileContent(config.config, ts.sys, join(root, owner.sourceDirectory)).options;
+ for (const entry of [owner, ...owner.subpaths]) {
+ const subpath = entry.packageName === owner.packageName ? "." : `.${entry.packageName.slice(owner.packageName.length)}`;
+ const target = manifest.exports?.[subpath];
+ const types = typeTarget(target);
+ const expectedSource = types && relative(root, resolve(compiler.rootDir, relative(compiler.outDir, resolve(root, owner.sourceDirectory, types)))).replace(/\.d\.ts$/, "");
+ if (!entry.entrypoint.startsWith(`${owner.sourceDirectory}/`) || !existsSync(join(root, entry.entrypoint)) || !expectedSource || ![`${expectedSource}.ts`, `${expectedSource}.tsx`].includes(entry.entrypoint)) {
+ errors.push(`Public export source drift: ${entry.packageName}`);
+ }
+ }
+ }
+ for (const app of routes.filter(route => route.modulePaths)) {
+ const file = app.applicationSource && join(root, app.applicationSource);
+ const declared = file && existsSync(file)
+ ? /createFileRoute\(\s*["']([^"']+)["']/.exec(readFileSync(file, "utf8"))?.[1]?.replace(/^\/_page(?=\/)/, "")
+ : undefined;
+ if (declared !== app.path) errors.push(`Application route source drift: ${app.path}`);
+ }
+ return errors;
+}
+
+function typeTarget(target) {
+ if (!target || typeof target !== "object") return undefined;
+ return typeof target.types === "string" ? target.types : Object.values(target).map(typeTarget).find(Boolean);
+}
+
+export function applicationRelationErrors(route, graph, routes) {
+ return (route.modulePaths ?? []).flatMap(path => {
+ const owner = routes.find(page => page.path === path)?.module?.packageName;
+ return owner && graph.packages.has(owner) ? [] : [`Application import evidence missing: ${route.path} -> ${path}`];
+ });
+}
diff --git a/scripts/architecture-drift.test.mjs b/scripts/architecture-drift.test.mjs
new file mode 100644
index 000000000..9143bc3a0
--- /dev/null
+++ b/scripts/architecture-drift.test.mjs
@@ -0,0 +1,66 @@
+import assert from "node:assert/strict";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { test } from "node:test";
+import modules from "../architecture/modules.json" with { type: "json" };
+import { siteRoutes, resolveSiteRoutes } from "../site/route-registry.mjs";
+import { applicationRelationErrors, moduleRegistrationErrors, sourceImportGraph } from "./architecture-drift.mjs";
+
+const root = fileURLToPath(new URL("..", import.meta.url));
+
+test("actual workspace exports and references belong to their registered package", () => {
+ assert.deepEqual(moduleRegistrationErrors(root, modules, siteRoutes), []);
+});
+
+test("rejects a missing owner, wrong package name, detached docs and a stale export source", () => {
+ assert.ok(moduleRegistrationErrors(root, modules.slice(1), siteRoutes).some(error => error.includes("denominator")));
+ for (const [change, message] of [
+ [{ packageName: "wrong" }, "Package name drift"],
+ [{ referencePath: "docs/public/architecture.md" }, "Package-owned reference"],
+ [{ entrypoint: modules[1].entrypoint }, "Public export source drift"],
+ ]) {
+ const changed = [{ ...modules[0], ...change }, ...modules.slice(1)];
+ assert.ok(moduleRegistrationErrors(root, changed, siteRoutes).some(error => error.includes(message)));
+ }
+});
+
+test("application evidence starts at its actual route, not another product", () => {
+ const applications = siteRoutes.filter(route => route.modulePaths);
+ const changed = siteRoutes.map(route => route === applications[0] ? { ...route, applicationSource: applications[1].applicationSource } : route);
+ assert.ok(moduleRegistrationErrors(root, modules, changed).some(error => error.includes("Application route source drift")));
+});
+
+test("site cannot redefine a module responsibility or navigation position", () => {
+ const page = { path: "/api", module: { packageName: modules[0].packageName, usagePaths: ["/usage"] } };
+ assert.equal(resolveSiteRoutes([page], modules)[0].navigationGroup, modules[0].positions[0]);
+ assert.throws(() => resolveSiteRoutes([{ ...page, documentSource: "docs/public/architecture.md" }], modules), /duplicates architecture/);
+ assert.throws(() => resolveSiteRoutes([{ ...page, navigationGroup: "Connector" }], modules), /duplicates architecture/);
+ assert.throws(() => resolveSiteRoutes([{ ...page, module: { ...page.module, responsibility: "override" } }], modules), /duplicates architecture/);
+ assert.throws(() => resolveSiteRoutes([{ ...page, module: { ...page.module, packageName: "missing" } }], modules), /Unknown architecture/);
+});
+
+test("consumer evidence follows local .js imports and cycles, excludes raw text, and rejects private subpaths", () => {
+ const fixture = mkdtempSync(join(tmpdir(), "architecture-imports-"));
+ const write = (path, source) => { mkdirSync(dirname(join(fixture, path)), { recursive: true }); writeFileSync(join(fixture, path), source); };
+ try {
+ const owners = ["used", "unused"].map(name => ({ packageName: `@test/${name}`, sourceDirectory: `packages/${name}`, entrypoint: `packages/${name}/src/index.ts`, subpaths: [] }));
+ for (const owner of owners) {
+ write(`${owner.sourceDirectory}/package.json`, JSON.stringify({ name: owner.packageName, exports: { ".": { types: "./dist/index.d.ts" } } }));
+ write(owner.entrypoint, "export const value = 1;");
+ }
+ write("app.ts", 'import "./local.js"; import source from "./raw.ts?raw"; const example = "import x from \'@test/unused\'";');
+ write("local.ts", 'export { value } from "@test/used"; import "./app.js";');
+ write("raw.ts", 'import "@test/unused";');
+ const graph = sourceImportGraph(fixture, "app.ts", owners);
+ assert.deepEqual(graph.errors, []);
+ assert.deepEqual([...graph.packages.keys()], ["@test/used"]);
+ assert.deepEqual(graph.packages.get("@test/used"), ["app.ts", "local.ts", "@test/used"]);
+ const routes = owners.map(owner => ({ path: `/${owner.packageName}`, module: owner }));
+ assert.deepEqual(applicationRelationErrors({ path: "/app", modulePaths: ["/@test/used"] }, graph, routes), []);
+ assert.match(applicationRelationErrors({ path: "/app", modulePaths: ["/@test/unused"] }, graph, routes)[0], /evidence missing/);
+ write("app.ts", 'import "@test/used/private";');
+ assert.ok(sourceImportGraph(fixture, "app.ts", owners).errors.some(error => error.includes("Non-public import")));
+ } finally { rmSync(fixture, { recursive: true, force: true }); }
+});
diff --git a/scripts/check-architecture-drift.mjs b/scripts/check-architecture-drift.mjs
new file mode 100644
index 000000000..389da9078
--- /dev/null
+++ b/scripts/check-architecture-drift.mjs
@@ -0,0 +1,22 @@
+import { fileURLToPath } from "node:url";
+import modules from "../architecture/modules.json" with { type: "json" };
+import { siteRoutes } from "../site/route-registry.mjs";
+import { applicationRelationErrors, moduleRegistrationErrors, sourceImportGraph } from "./architecture-drift.mjs";
+
+const root = fileURLToPath(new URL("..", import.meta.url));
+const errors = moduleRegistrationErrors(root, modules, siteRoutes);
+for (const app of siteRoutes.filter(route => route.modulePaths)) {
+ if (!app.applicationSource) { errors.push(`Application source missing: ${app.path}`); continue; }
+ const graph = sourceImportGraph(root, app.applicationSource, modules);
+ errors.push(...graph.errors, ...applicationRelationErrors(app, graph, siteRoutes));
+ if (process.argv.includes("--evidence")) {
+ for (const path of app.modulePaths) {
+ const owner = siteRoutes.find(page => page.path === path).module.packageName;
+ console.log(`${app.path} -> ${owner}\n ${graph.packages.get(owner)?.join(" -> ") ?? "MISSING"}`);
+ }
+ }
+}
+if (errors.length) {
+ console.error([...new Set(errors)].join("\n"));
+ process.exitCode = 1;
+} else console.log(`Architecture drift checks passed: ${modules.length} package owners; ${siteRoutes.filter(route => route.modulePaths).length} application source graphs. Semantic ownership and runtime behavior require separate audits.`);
diff --git a/scripts/generate-api-reference.mjs b/scripts/generate-api-reference.mjs
index b91ab3a73..7e065dd36 100644
--- a/scripts/generate-api-reference.mjs
+++ b/scripts/generate-api-reference.mjs
@@ -1,3 +1,4 @@
+import { siteRoutes } from "../site/route-registry.mjs";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -25,7 +26,6 @@ const manifests = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).w
.map((workspace) => JSON.parse(readFileSync(join(root, workspace, "package.json"), "utf8")));
const failures = apiReferenceCoverageErrors(manifests);
let exportCount = 0;
-const siteRoutes = JSON.parse(readFileSync(join(root, "site/site-routes.json"), "utf8"));
function display(symbol, entry) {
const target = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
@@ -66,7 +66,7 @@ for (const descriptor of apiReferencePackages) {
const referenceRoutes = siteRoutes.filter((route) =>
route.path === referencePath
&& route.navigationGroup === descriptor.navigationGroup
- && route.documentSource === `docs/api-reference/${descriptor.slug}.md`);
+ && route.documentSource === descriptor.referencePath);
if (referenceRoutes.length !== 1) failures.push(`${descriptor.packageName} owner reference route`);
const entry = program.getSourceFile(join(root, descriptor.entrypoint));
if (!entry) throw new Error(`public entrypoint를 찾을 수 없습니다: ${descriptor.entrypoint}`);
@@ -97,14 +97,14 @@ for (const descriptor of apiReferencePackages) {
"",
`**탐색 분류:** ${descriptor.navigationGroup}`,
"",
- `${descriptor.responsibility}의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 사이트에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.`,
+ `${descriptor.responsibility}의 public entrypoint입니다. API의 owner는 이 package이며 탐색 분류는 저장소의 아키텍처 등록에서 읽는 위치입니다. 별도 subpath 표시가 없는 항목은 package root에서 import합니다. internal 경로는 계약이 아닙니다.`,
"",
`> 이 문서는 \`${descriptor.entrypoint}\`에서 생성됩니다. API를 변경한 뒤 \`npm run docs:api\`를 실행하세요.`,
"",
...sections,
"",
].join("\n");
- const path = join(root, "docs/api-reference", `${descriptor.slug}.md`);
+ const path = join(root, descriptor.referencePath);
let current = "";
try { current = readFileSync(path, "utf8"); } catch {}
if (check && current !== output) failures.push(descriptor.packageName);
diff --git a/site/package.json b/site/package.json
index 134216d43..87327fcd0 100644
--- a/site/package.json
+++ b/site/package.json
@@ -14,7 +14,7 @@
"check:primitives": "node scripts/check-live-demo-primitives.mjs",
"check:ui-roles": "node scripts/check-ui-role-primitives.mjs",
"check:choice-id": "node scripts/check-choice-id.mjs",
- "check:canonical-modules": "node scripts/check-canonical-module-closure.mjs && node scripts/check-document-type-audits.mjs && node scripts/check-documentation-page.mjs",
+ "check:canonical-modules": "node ../scripts/check-architecture-drift.mjs && node scripts/check-canonical-module-closure.mjs && node scripts/check-document-type-audits.mjs && node scripts/check-documentation-page.mjs",
"check:interaction-handles": "node scripts/check-interaction-handles.mjs",
"check:contextual-affordance": "node scripts/check-contextual-affordance.mjs",
"check:content-interaction": "node scripts/check-content-interaction-grammar.mjs",
diff --git a/site/route-registry.d.mts b/site/route-registry.d.mts
new file mode 100644
index 000000000..962361ad1
--- /dev/null
+++ b/site/route-registry.d.mts
@@ -0,0 +1,2 @@
+import type { SiteRoute } from "./src/app/page-descriptors";
+export const siteRoutes: ReadonlyArray;
diff --git a/site/route-registry.mjs b/site/route-registry.mjs
new file mode 100644
index 000000000..d2b1669ce
--- /dev/null
+++ b/site/route-registry.mjs
@@ -0,0 +1,29 @@
+import routes from "./site-routes.json" with { type: "json" };
+import modules from "../architecture/modules.json" with { type: "json" };
+
+/** Project repository responsibilities into site navigation, never the reverse. */
+export function resolveSiteRoutes(pages, owners) {
+ return pages.map(page => {
+ if (!page.module) return page;
+ const owner = owners.find(module => module.packageName === page.module.packageName);
+ if (!owner) throw new Error(`Unknown architecture module: ${page.module.packageName}`);
+ if (page.navigationGroup !== undefined || page.documentSource !== undefined || Object.keys(page.module).some(key => !["packageName", "usagePaths"].includes(key))) {
+ throw new Error(`Site route duplicates architecture ownership: ${page.path}`);
+ }
+ return {
+ ...page,
+ navigationGroup: owner.positions[0],
+ documentSource: owner.referencePath,
+ module: {
+ packageName: owner.packageName,
+ sourceDirectory: owner.sourceDirectory,
+ responsibility: owner.responsibility,
+ alsoIn: owner.positions.slice(1),
+ usagePaths: page.module.usagePaths,
+ ...(owner.statusNote ? { statusNote: owner.statusNote } : {}),
+ },
+ };
+ });
+}
+
+export const siteRoutes = resolveSiteRoutes(routes, modules);
diff --git a/site/scripts/check-canonical-module-closure.mjs b/site/scripts/check-canonical-module-closure.mjs
index 78d9ccfa8..3be3aaef1 100644
--- a/site/scripts/check-canonical-module-closure.mjs
+++ b/site/scripts/check-canonical-module-closure.mjs
@@ -94,9 +94,10 @@ const linkedPackageDirectories = new Set([...visited]
const missingSources = packageDirectories.filter((directory) => !linkedPackageDirectories.has(directory));
if (missingSources.length > 0) throw new Error(`canonical source registration missing:\n${missingSources.join("\n")}`);
-const apiReferences = readdirSync(join(repositoryRoot, "docs/api-reference")).filter((name) => name.endsWith(".md"));
-if (apiReferences.length !== packageDirectories.length) {
- throw new Error(`API Reference denominator mismatch: packages=${packageDirectories.length}, references=${apiReferences.length}`);
+for (const directory of packageDirectories) {
+ if (!existsSync(join(repositoryRoot, directory, "docs/api-reference.md"))) {
+ throw new Error(`Package-owned API reference missing: ${directory}`);
+ }
}
console.log(`Canonical module closure ok; packages=${packageDirectories.length}; live demos=${entries.length}; linked package sources=${linkedPackageDirectories.size}.`);
diff --git a/site/scripts/check-document-type-audits.mjs b/site/scripts/check-document-type-audits.mjs
index 6bf107109..bbfb1e4af 100644
--- a/site/scripts/check-document-type-audits.mjs
+++ b/site/scripts/check-document-type-audits.mjs
@@ -1,12 +1,12 @@
+import { siteRoutes } from "../route-registry.mjs";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
const root = new URL("../..", import.meta.url).pathname;
const ledger = JSON.parse(readFileSync(join(root, "audits/document-types.json"), "utf8"));
-const siteRoutes = JSON.parse(readFileSync(join(root, "site/site-routes.json"), "utf8"));
const allowed = new Set(["canonical consumer", "Host composition", "duplicate implementation", "canonical API gap", "missing canonical module", "mislocated module", "out of scope", "unverified"]);
const expectedCandidates = siteRoutes
- .filter((route) => route.navigationGroup === "Document Types" && route.path.startsWith("/docs/document-types/"))
+ .filter((route) => route.path.startsWith("/docs/document-types/"))
.map((route) => route.path.slice("/docs/document-types/".length));
if (JSON.stringify(ledger.candidates) !== JSON.stringify(expectedCandidates)) throw new Error("Document Type audit candidates do not match the TBD navigation denominator");
diff --git a/site/scripts/evaluate-http.mjs b/site/scripts/evaluate-http.mjs
index 31bf9184a..dabd4deb4 100644
--- a/site/scripts/evaluate-http.mjs
+++ b/site/scripts/evaluate-http.mjs
@@ -1,3 +1,4 @@
+import { siteRoutes as routes } from "../route-registry.mjs";
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
import { createServer } from "node:http";
import { extname, join, normalize, sep } from "node:path";
@@ -8,7 +9,6 @@ const siteRoot = new URL("..", import.meta.url).pathname;
const dist = join(siteRoot, "dist");
const expectedBase = normalizeBase(process.env.SITE_BASE ?? "/json-document/");
const expectedSiteUrl = (process.env.SITE_URL ?? "https://developer-1px.github.io/json-document").replace(/\/$/, "");
-const routes = JSON.parse(readFileSync(join(siteRoot, "site-routes.json"), "utf8"));
validateSiteRoutes(routes, fail);
const seenAssets = new Set();
diff --git a/site/scripts/evaluate-live.mjs b/site/scripts/evaluate-live.mjs
index 19ddb4788..4bbfeb71f 100644
--- a/site/scripts/evaluate-live.mjs
+++ b/site/scripts/evaluate-live.mjs
@@ -1,3 +1,4 @@
+import { siteRoutes as routes } from "../route-registry.mjs";
import { readFileSync } from "node:fs";
import { validateSiteRoutes } from "./route-checks.mjs";
import { validateLlmsContract } from "../../docs/public-contract-checks.mjs";
@@ -5,7 +6,6 @@ import { validateLlmsContract } from "../../docs/public-contract-checks.mjs";
const siteUrl = (process.env.SITE_URL ?? "https://developer-1px.github.io/json-document").replace(/\/$/, "");
const attempts = Number(process.env.SITE_LIVE_ATTEMPTS ?? "18");
const delayMs = Number(process.env.SITE_LIVE_DELAY_MS ?? "10000");
-const routes = JSON.parse(readFileSync(new URL("../site-routes.json", import.meta.url), "utf8"));
validateSiteRoutes(routes, fail);
const rootRoute = routes.find((route) => route.path === "/");
if (rootRoute === undefined) fail("live site routes are missing the root route.");
diff --git a/site/scripts/evaluate.mjs b/site/scripts/evaluate.mjs
index 145d14dc1..ecfbc9465 100644
--- a/site/scripts/evaluate.mjs
+++ b/site/scripts/evaluate.mjs
@@ -1,3 +1,4 @@
+import { siteRoutes } from "../route-registry.mjs";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { routeFile, validateSiteRoutes } from "./route-checks.mjs";
@@ -7,7 +8,6 @@ const siteRoot = new URL("..", import.meta.url).pathname;
const dist = join(siteRoot, "dist");
const expectedBase = normalizeBase(process.env.SITE_BASE ?? "/");
const expectedSiteUrl = (process.env.SITE_URL ?? "https://developer-1px.github.io/json-document").replace(/\/$/, "");
-const siteRoutes = JSON.parse(readFileSync(join(siteRoot, "site-routes.json"), "utf8"));
validateSiteRoutes(siteRoutes, fail);
validateSourceBoundaries();
const routes = siteRoutes.map((route) => ({ ...route, file: routeFile(route.path) }));
@@ -33,8 +33,8 @@ function validateSourceBoundaries() {
.map((entry) => entry.name)
.sort();
- if (JSON.stringify(rootEntries) !== JSON.stringify(["app", "main.tsx", "routes", "shared"])) {
- fail(`site src root must contain only app, main.tsx, route owners, and shared UI: ${rootEntries.join(", ")}.`);
+ if (JSON.stringify(rootEntries) !== JSON.stringify(["app", "applications", "main.tsx", "routes", "shared"])) {
+ fail(`site src root must contain only app, applications, main.tsx, route owners, and shared UI: ${rootEntries.join(", ")}.`);
}
const flatRouteFiles = readdirSync(routesRoot, { withFileTypes: true })
@@ -67,8 +67,8 @@ function validateSourceBoundaries() {
const specifier = match[1];
if (!specifier?.startsWith(".")) continue;
const target = resolve(dirname(file), specifier);
- if (relative(routesRoot, target).startsWith("..") === false || relative(join(src, "app"), target).startsWith("..") === false) {
- fail(`site shared UI must not import app or route owners: ${relative(siteRoot, file)}.`);
+ if (relative(routesRoot, target).startsWith("..") === false || relative(join(src, "app"), target).startsWith("..") === false || relative(join(src, "applications"), target).startsWith("..") === false) {
+ fail(`site shared UI must not import app, application or route owners: ${relative(siteRoot, file)}.`);
}
}
}
diff --git a/site/scripts/route-checks.mjs b/site/scripts/route-checks.mjs
index de2f63529..f662117d3 100644
--- a/site/scripts/route-checks.mjs
+++ b/site/scripts/route-checks.mjs
@@ -1,6 +1,7 @@
const routePathPattern = /^\/(?:[a-z0-9]+(?:-[a-z0-9]+)*\/?)*$/;
const navigationGroups = new Set([
"Introduction",
+ "Design",
"JSON Document",
"Document Types",
"Collaboration",
@@ -53,7 +54,7 @@ export function validateSiteRoutes(routes, fail) {
if (route.heading !== undefined && (typeof route.heading !== "string" || route.heading.trim() === "")) {
fail(`site route ${route.path} has an invalid heading.`);
}
- if (route.documentSource !== undefined && !/^docs\/(?:public|api-reference)\/[^/]+\.md$/.test(route.documentSource)) {
+ if (route.documentSource !== undefined && !/^(?:docs\/public\/[^/]+|packages\/[^/]+\/docs\/[^/]+)\.md$/.test(route.documentSource)) {
fail(`site route ${route.path} has an invalid documentation source.`);
}
if (route.documentIncludes !== undefined && (!Array.isArray(route.documentIncludes) || route.documentIncludes.some((source) => typeof source !== "string" || !/^packages\/[^/]+\/docs\/[^/]+\.md$/.test(source)))) {
diff --git a/site/scripts/route-checks.test.mjs b/site/scripts/route-checks.test.mjs
index 373531791..6048b0b34 100644
--- a/site/scripts/route-checks.test.mjs
+++ b/site/scripts/route-checks.test.mjs
@@ -1,4 +1,4 @@
-import { readFileSync } from "node:fs";
+import { siteRoutes } from "../route-registry.mjs";
import assert from "node:assert/strict";
import { test } from "node:test";
import { validateSiteRoutes } from "./route-checks.mjs";
@@ -12,7 +12,7 @@ const errors = (routes) => {
};
test("validates the real site registry and separate section landings", () => {
- assert.deepEqual(errors(JSON.parse(readFileSync(new URL("../site-routes.json", import.meta.url), "utf8"))), []);
+ assert.deepEqual(errors(siteRoutes), []);
assert.deepEqual(errors([root, route("/docs/foundation", { sidebar: false }), route("/docs/building-blocks", { sidebar: false })]), []);
});
@@ -32,7 +32,7 @@ test("validates package-owned documentation includes", () => {
test("rejects missing Usage and product-to-module links", () => {
const module = { sourceDirectory: "packages/example", responsibility: "Example", alsoIn: [], usagePaths: ["/missing"] };
- const failures = errors([root, route("/docs/api/example", {navigationGroup:"Adapter", documentSource:"docs/api-reference/example.md", module}), route("/applications/example", {modulePaths:["/missing"]})]);
+ const failures = errors([root, route("/docs/api/example", {navigationGroup:"Adapter", documentSource:"packages/example/docs/api-reference.md", module}), route("/applications/example", {modulePaths:["/missing"]})]);
assert.ok(failures.some(message => message.includes("known Usage")));
assert.ok(failures.some(message => message.includes("unknown module")));
});
diff --git a/site/site-routes.json b/site/site-routes.json
index 7048b16a9..e638515f6 100644
--- a/site/site-routes.json
+++ b/site/site-routes.json
@@ -7,7 +7,7 @@
},
{
"path": "/viewer",
- "label": "Content Prototype · TBD",
+ "label": "Artifact · Prototype",
"title": "Artifact · TBD - json-document",
"heading": "Artifact · TBD",
"description": "Application 안에서 MD·PPT·Sheet를 다루는 visual prototype과 아직 연결하지 않은 문서·Hands·호환성 계약을 구분합니다.",
@@ -16,7 +16,7 @@
},
{
"path": "/docs",
- "label": "Why",
+ "label": "소개",
"title": "json-document Docs - json-document",
"heading": "왜 json-document인가",
"description": "문서·표·보드가 JSON Document 계약을 공유하고, 독립적인 편집·플랫폼·생태계 책임을 조합하는 이유를 설명합니다.",
@@ -24,15 +24,35 @@
"navigationGroup": "Introduction",
"documentSource": "docs/public/overview.md"
},
+ {
+ "path": "/docs/modules",
+ "label": "전체 모듈",
+ "title": "전체 모듈 - json-document",
+ "heading": "전체 모듈",
+ "description": "현재 제공하는 계약을 여덟 책임별 API와 Usage에서 찾습니다.",
+ "documentSource": "docs/public/modules.md",
+ "sidebar": false
+ },
{
"path": "/docs/foundation",
- "label": "Overview",
+ "label": "Foundation",
"title": "Foundation - json-document",
"heading": "Foundation",
"description": "JSON Document, Document Types, Editing과 Collaboration이 공유하는 기반 계약을 설명합니다.",
"language": "ko",
"documentSource": "docs/public/foundation.md",
- "sidebar": false
+ "sidebar": false,
+ "navigationGroup": "Introduction",
+ "parentPath": "/docs/architecture"
+ },
+ {
+ "path": "/docs/quick-start",
+ "label": "빠른 시작",
+ "title": "빠른 시작 - json-document",
+ "heading": "빠른 시작",
+ "description": "JSON Document를 설치하고 값 변경과 구독을 직접 시작합니다.",
+ "documentSource": "docs/public/quick-start.md",
+ "navigationGroup": "Introduction"
},
{
"path": "/docs/architecture",
@@ -43,16 +63,6 @@
"navigationGroup": "Introduction",
"documentSource": "docs/public/architecture.md"
},
- {
- "path": "/docs/how-we-build",
- "label": "How We Build",
- "title": "How We Build - json-document",
- "heading": "제품에서 정본 모듈을 발견하는 방법",
- "description": "Application을 먼저 만들고 실제 제품 책임을 canonical module로 추출한 뒤 제품이 다시 소비하는 개발 순환을 설명합니다.",
- "language": "ko",
- "navigationGroup": "Introduction",
- "documentSource": "docs/public/how-we-build.md"
- },
{
"path": "/applications",
"label": "Overview",
@@ -79,7 +89,8 @@
"/docs/api/markdown-react",
"/docs/api/contenteditable",
"/docs/api/web"
- ]
+ ],
+ "applicationSource": "site/src/app/routes/applications/bear.tsx"
},
{
"path": "/applications/calendar",
@@ -97,7 +108,8 @@
"/docs/api/affordance",
"/docs/api/web",
"/docs/api/ui-primitives-react"
- ]
+ ],
+ "applicationSource": "site/src/app/routes/_page/applications/calendar.tsx"
},
{
"path": "/applications/ai-agent",
@@ -111,11 +123,12 @@
"/docs/api/a2ui",
"/docs/api/markdown-react",
"/docs/api/ui-primitives-react"
- ]
+ ],
+ "applicationSource": "site/src/app/routes/_page/applications/ai-agent.tsx"
},
{
"path": "/docs/document-types",
- "label": "Overview · TBD",
+ "label": "Document Types",
"title": "Document Types · TBD - json-document",
"heading": "Document Types · TBD",
"description": "JSON Document의 의미 모델을 소유하는 Document Type 책임과 아직 확정하지 않은 후보를 정리합니다.",
@@ -129,7 +142,8 @@
"title": "Rich Text Document Type · TBD - json-document",
"description": "Rich Text의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/order",
@@ -137,7 +151,8 @@
"title": "Order Document Type · TBD - json-document",
"description": "Order의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/object",
@@ -145,7 +160,8 @@
"title": "Object Document Type · RC - json-document",
"description": "Object와 Canvas 프로파일의 공개 소유자와 책임 감사 증거를 설명합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/tree",
@@ -153,7 +169,8 @@
"title": "Tree Document Type · TBD - json-document",
"description": "Tree의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/database",
@@ -161,7 +178,8 @@
"title": "Database Document Type · TBD - json-document",
"description": "Database의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/calendar",
@@ -169,7 +187,8 @@
"title": "Calendar Document Type · RC - json-document",
"description": "Calendar 문서 모델·검증·의미 연산·projection의 공개 소유자와 RC 계약을 설명합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/sheet",
@@ -177,7 +196,8 @@
"title": "Sheet Document Type · TBD - json-document",
"description": "Sheet의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/kanban",
@@ -185,7 +205,8 @@
"title": "Kanban Document Type · TBD - json-document",
"description": "Kanban의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/document-types/annotation",
@@ -193,11 +214,12 @@
"title": "Annotation Document Type · TBD - json-document",
"description": "Annotation의 Document Type 소유권을 확정하기 전 후보 상태와 필요한 증거를 정리합니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Design",
+ "parentPath": "/docs/ownership"
},
{
"path": "/docs/api",
- "label": "JSON Document Protocol",
+ "label": "JSON Document",
"title": "JSON Document Protocol - json-document",
"heading": "JSON Document Protocol",
"description": "로컬·협업 구현이 공유하는 여섯 member의 JSONDocument 계약과 JSON 표준 연산 API를 설명합니다.",
@@ -211,16 +233,13 @@
"title": "JSON Document public API - json-document",
"description": "@interactive-os/json-document의 전체 public export와 signature입니다.",
"language": "ko",
- "navigationGroup": "JSON Document",
- "documentSource": "docs/api-reference/json-document.md",
"module": {
- "sourceDirectory": "packages/json-document",
- "responsibility": "JSON 값·주소·검증·원자적 변경·구독",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document",
"usagePaths": [
"/demo"
]
- }
+ },
+ "parentPath": "/docs/api"
},
{
"path": "/docs/api/selection",
@@ -228,16 +247,13 @@
"title": "Selection API - json-document",
"description": "@interactive-os/json-document-selection의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Editing",
- "documentSource": "docs/api-reference/selection.md",
"module": {
- "sourceDirectory": "packages/json-document-selection",
- "responsibility": "범위·키·기하 선택과 선택 전이",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-selection",
"usagePaths": [
"/demo/selection"
]
- }
+ },
+ "parentPath": "/docs/editing"
},
{
"path": "/docs/api/editing",
@@ -245,26 +261,20 @@
"title": "Editing API - json-document",
"description": "@interactive-os/json-document-editing의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Editing",
- "documentSource": "docs/api-reference/editing.md",
"documentIncludes": [
"packages/json-document-editing/docs/object-selection.md",
"packages/json-document-editing/docs/calendar-profile.md",
"packages/json-document-editing/docs/text.md"
],
"module": {
- "sourceDirectory": "packages/json-document-editing",
- "responsibility": "Intent·변경 계획·선택·Clipboard·History",
- "alsoIn": [
- "Document Types"
- ],
+ "packageName": "@interactive-os/json-document-editing",
"usagePaths": [
"/demo/history",
"/demo/clipboard",
"/demo/markdown-caret"
- ],
- "statusNote": "일부 장르의 문서 모델도 현재 Editing에 있습니다. Document Types의 후보별 소유권 감사를 따릅니다."
- }
+ ]
+ },
+ "parentPath": "/docs/editing"
},
{
"path": "/docs/api/rich-text",
@@ -272,19 +282,13 @@
"title": "Rich Text API - json-document",
"description": "@interactive-os/json-document-rich-text의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Editing",
- "documentSource": "docs/api-reference/rich-text.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text",
- "responsibility": "Rich Text 모델·의미 연산과 텍스트 편집",
- "alsoIn": [
- "Document Types"
- ],
+ "packageName": "@interactive-os/json-document-rich-text",
"usagePaths": [
"/editing/rich-text"
- ],
- "statusNote": "문서 모델과 Editing 계약이 한 패키지에 있습니다. Document Type 재배치 완료를 뜻하지 않습니다."
- }
+ ]
+ },
+ "parentPath": "/docs/editing"
},
{
"path": "/docs/api/file-intake",
@@ -292,18 +296,14 @@
"title": "File Intake API - json-document",
"description": "@interactive-os/json-document-file-intake의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Document Types",
- "documentSource": "docs/api-reference/file-intake.md",
"module": {
- "sourceDirectory": "packages/json-document-file-intake",
- "responsibility": "직렬화 가능한 파일 후보·이미지 내용과 수용 검증",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-file-intake",
"usagePaths": [
"/demo/composer",
"/demo/annotation"
- ],
- "statusNote": "파일·이미지 내용 계약을 이 위치에서 안내합니다. 독립 Document Type Profile의 완료를 선언하지 않습니다."
- }
+ ]
+ },
+ "parentPath": "/docs/document-types"
},
{
"path": "/docs/api/rich-text-mention",
@@ -311,12 +311,8 @@
"title": "Rich Text Mention API - json-document",
"description": "@interactive-os/json-document-rich-text-mention의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/rich-text-mention.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-mention",
- "responsibility": "안정된 entity reference를 삽입하는 Mention 계약",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-mention",
"usagePaths": [
"/demo/composer"
]
@@ -328,12 +324,8 @@
"title": "Rich Text Suggestion API - json-document",
"description": "@interactive-os/json-document-rich-text-suggestion의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/rich-text-suggestion.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-suggestion",
- "responsibility": "입력 문맥에서 suggestion을 찾고 적용하는 계약",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-suggestion",
"usagePaths": [
"/demo/composer"
]
@@ -345,16 +337,13 @@
"title": "Rich Text Suggestion React API - json-document",
"description": "@interactive-os/json-document-rich-text-suggestion-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/rich-text-suggestion-react.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-suggestion-react",
- "responsibility": "Suggestion 관찰·수명을 React에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-suggestion-react",
"usagePaths": [
"/demo/composer"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/rich-text-mention-react",
@@ -362,16 +351,13 @@
"title": "Rich Text Mention React API - json-document",
"description": "@interactive-os/json-document-rich-text-mention-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/rich-text-mention-react.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-mention-react",
- "responsibility": "Mention 관찰·수명을 React에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-mention-react",
"usagePaths": [
"/demo/composer"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/composer",
@@ -379,12 +365,8 @@
"title": "Composer API - json-document",
"description": "@interactive-os/json-document-composer의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/composer.md",
"module": {
- "sourceDirectory": "packages/json-document-composer",
- "responsibility": "텍스트와 구조화된 문맥을 한 입력으로 구성",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-composer",
"usagePaths": [
"/demo/composer"
]
@@ -396,16 +378,13 @@
"title": "Composer React API - json-document",
"description": "@interactive-os/json-document-composer-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/composer-react.md",
"module": {
- "sourceDirectory": "packages/json-document-composer-react",
- "responsibility": "Composer 편집 관찰과 참조 atom을 React에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-composer-react",
"usagePaths": [
"/demo/composer"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/web",
@@ -413,23 +392,20 @@
"title": "Web API - json-document",
"description": "@interactive-os/json-document-web의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Adapter",
- "documentSource": "docs/api-reference/web.md",
"documentIncludes": [
"packages/json-document-web/docs/clipboard.md",
"packages/json-document-web/docs/text-clipboard.md",
"packages/json-document-web/docs/interaction-recording.md"
],
"module": {
- "sourceDirectory": "packages/json-document-web",
- "responsibility": "키보드·포인터·Clipboard·DOM 관찰·입력 기록",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-web",
"usagePaths": [
"/adapters/keyboard",
"/adapters/clipboard",
"/demo/viewport"
]
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/api/contenteditable",
@@ -437,20 +413,17 @@
"title": "Contenteditable API - json-document",
"description": "@interactive-os/json-document-contenteditable의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Adapter",
- "documentSource": "docs/api-reference/contenteditable.md",
"documentIncludes": [
"packages/json-document-contenteditable/docs/editing.md"
],
"module": {
- "sourceDirectory": "packages/json-document-contenteditable",
- "responsibility": "DOM 입력·IME·선택 복원·기호 투영·화면 줄 탐색",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-contenteditable",
"usagePaths": [
"/adapters/contenteditable",
"/demo/markdown-caret"
]
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/api/rich-text-web",
@@ -458,16 +431,13 @@
"title": "Rich Text Web API - json-document",
"description": "@interactive-os/json-document-rich-text-web의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Adapter",
- "documentSource": "docs/api-reference/rich-text-web.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-web",
- "responsibility": "Rich Text와 DOM·Clipboard·native 입력의 대응",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-web",
"usagePaths": [
"/editing/rich-text"
]
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/api/react",
@@ -475,16 +445,13 @@
"title": "React API - json-document",
"description": "@interactive-os/json-document-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/react.md",
"module": {
- "sourceDirectory": "packages/json-document-react",
- "responsibility": "문서·편집 관찰과 React 수명 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-react",
"usagePaths": [
"/connectors/react"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/react-hook-form",
@@ -492,16 +459,13 @@
"title": "React Hook Form API - json-document",
"description": "@interactive-os/json-document-react-hook-form의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/react-hook-form.md",
"module": {
- "sourceDirectory": "packages/json-document-react-hook-form",
- "responsibility": "폼 draft·검증 결과를 문서 변경에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-react-hook-form",
"usagePaths": [
"/connectors/react-hook-form"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/ajv",
@@ -509,16 +473,13 @@
"title": "Ajv API - json-document",
"description": "@interactive-os/json-document-ajv의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/ajv.md",
"module": {
- "sourceDirectory": "packages/json-document-ajv",
- "responsibility": "Ajv 검증 결과를 문서 진단으로 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-ajv",
"usagePaths": [
"/connectors/ajv"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/a2ui",
@@ -526,16 +487,13 @@
"title": "A2UI API - json-document",
"description": "@interactive-os/json-document-a2ui의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/a2ui.md",
"module": {
- "sourceDirectory": "packages/json-document-a2ui",
- "responsibility": "A2UI 메시지와 문서·UI projection 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-a2ui",
"usagePaths": [
"/connectors/a2ui"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/connector-a2ui",
@@ -544,7 +502,8 @@
"description": "A2UI 메시지와 JSONL을 JSONDocument로 연결합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-a2ui.md"
+ "documentSource": "docs/public/connector-a2ui.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/zod",
@@ -552,17 +511,14 @@
"title": "Zod API - json-document",
"description": "@interactive-os/json-document-zod의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/zod.md",
"module": {
- "sourceDirectory": "packages/json-document-zod",
- "responsibility": "Zod schema·오류를 문서 계약에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-zod",
"usagePaths": [
"/connectors/zod",
"/connectors/zod/validate"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/tanstack-table",
@@ -570,16 +526,13 @@
"title": "TanStack Table API - json-document",
"description": "@interactive-os/json-document-tanstack-table의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/tanstack-table.md",
"module": {
- "sourceDirectory": "packages/json-document-tanstack-table",
- "responsibility": "TanStack Table의 표시 모델을 편집 topology에 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-tanstack-table",
"usagePaths": [
"/connectors/tanstack-table"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/rich-text-react",
@@ -587,16 +540,13 @@
"title": "Rich Text React API - json-document",
"description": "@interactive-os/json-document-rich-text-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/rich-text-react.md",
"module": {
- "sourceDirectory": "packages/json-document-rich-text-react",
- "responsibility": "Rich Text 편집 관찰·DOM 표면과 React 수명 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-rich-text-react",
"usagePaths": [
"/editing/rich-text"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/api/affordance",
@@ -604,21 +554,18 @@
"title": "Affordance API - json-document",
"description": "@interactive-os/json-document-affordance의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Affordance",
- "documentSource": "docs/api-reference/affordance.md",
"documentIncludes": [
"packages/json-document-affordance/docs/plane-select.md",
"packages/json-document-affordance/docs/resize.md"
],
"module": {
- "sourceDirectory": "packages/json-document-affordance",
- "responsibility": "선택·이동·취소 등 입력 독립 조작 의미와 수명",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-affordance",
"usagePaths": [
"/affordances/handles",
"/demo/viewport"
]
- }
+ },
+ "parentPath": "/docs/affordance"
},
{
"path": "/docs/api/ui-primitives-react",
@@ -626,19 +573,16 @@
"title": "UI Primitives API - json-document",
"description": "@interactive-os/json-document-ui-primitives-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "UI Primitives",
- "documentSource": "docs/api-reference/ui-primitives-react.md",
"documentIncludes": [
"packages/json-document-ui-primitives-react/docs/popover.md"
],
"module": {
- "sourceDirectory": "packages/json-document-ui-primitives-react",
- "responsibility": "표준 제어·포커스·overlay·접근성 행동",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-ui-primitives-react",
"usagePaths": [
"/demo/ui-primitives"
]
- }
+ },
+ "parentPath": "/docs/ui-primitives"
},
{
"path": "/docs/api/animation-react",
@@ -646,16 +590,13 @@
"title": "Animation API - json-document",
"description": "@interactive-os/json-document-animation-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "UI Primitives",
- "documentSource": "docs/api-reference/animation-react.md",
"module": {
- "sourceDirectory": "packages/json-document-animation-react",
- "responsibility": "진행·대기·전환 상태의 반복 시각 행동",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-animation-react",
"usagePaths": [
"/demo/animation"
]
- }
+ },
+ "parentPath": "/docs/ui-primitives"
},
{
"path": "/docs/api/markdown",
@@ -663,20 +604,16 @@
"title": "Markdown API - json-document",
"description": "@interactive-os/json-document-markdown의 원문 보존 편집 계약입니다.",
"language": "ko",
- "navigationGroup": "Document Types",
- "documentSource": "docs/api-reference/markdown.md",
"documentIncludes": [
"packages/json-document-markdown/docs/api.md"
],
"module": {
- "sourceDirectory": "packages/json-document-markdown",
- "responsibility": "Markdown 원문 구문·좌표·체크·문단·목록 연산",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-markdown",
"usagePaths": [
"/demo/markdown-caret"
- ],
- "statusNote": "원문 기반 편집 실험입니다. Rich Text 모델과 별개이며 Official Hands Profile 완료를 뜻하지 않습니다."
- }
+ ]
+ },
+ "parentPath": "/docs/document-types"
},
{
"path": "/docs/api/markdown-web",
@@ -684,19 +621,16 @@
"title": "Markdown Web API - json-document",
"description": "@interactive-os/json-document-markdown-web의 원문 보존 편집 계약입니다.",
"language": "ko",
- "navigationGroup": "Adapter",
- "documentSource": "docs/api-reference/markdown-web.md",
"documentIncludes": [
"packages/json-document-markdown-web/docs/api.md"
],
"module": {
- "sourceDirectory": "packages/json-document-markdown-web",
- "responsibility": "Markdown 원문과 편집 DOM·입력 binding의 대응",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-markdown-web",
"usagePaths": [
"/demo/markdown-caret"
]
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/demo/markdown-caret",
@@ -705,7 +639,8 @@
"heading": "Markdown caret",
"description": "Markdown 원문이 정본인 caret·delimiter·입력·clipboard·history 실험입니다.",
"language": "ko",
- "navigationGroup": "Document Types"
+ "navigationGroup": "Document Types",
+ "parentPath": "/docs/document-types"
},
{
"path": "/docs/api/markdown-react",
@@ -713,20 +648,17 @@
"title": "Markdown React API - json-document",
"description": "@interactive-os/json-document-markdown-react의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Connector",
- "documentSource": "docs/api-reference/markdown-react.md",
"documentIncludes": [
"packages/json-document-markdown-react/docs/editing.md"
],
"module": {
- "sourceDirectory": "packages/json-document-markdown-react",
- "responsibility": "Markdown 렌더링·편집 표면과 React 수명 연결",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-markdown-react",
"usagePaths": [
"/demo/markdown-caret",
"/demo/markdown"
]
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/demo/markdown",
@@ -735,7 +667,8 @@
"heading": "Streaming Markdown",
"description": "불완전한 Markdown 스트림과 커스텀 component renderer를 정본 패키지로 검증합니다.",
"language": "ko",
- "navigationGroup": "Connector"
+ "navigationGroup": "Connector",
+ "parentPath": "/docs/connectors"
},
{
"path": "/demo/ui-primitives",
@@ -753,12 +686,8 @@
"title": "Database API - json-document",
"description": "@interactive-os/json-document-database의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/database.md",
"module": {
- "sourceDirectory": "packages/json-document-database",
- "responsibility": "Database record와 view를 편집 UI로 조합",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-database",
"usagePaths": [
"/demo/database"
]
@@ -770,12 +699,8 @@
"title": "Annotation API - json-document",
"description": "@interactive-os/json-document-annotation의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/annotation.md",
"module": {
- "sourceDirectory": "packages/json-document-annotation",
- "responsibility": "Raster annotation 도구·선택·제스처·UI 조합",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-annotation",
"usagePaths": [
"/demo/annotation"
]
@@ -787,20 +712,16 @@
"title": "Object Document Type API - json-document",
"description": "Object와 Canvas의 문서 계약과 public API입니다.",
"language": "ko",
- "navigationGroup": "Document Types",
- "documentSource": "docs/api-reference/object-document.md",
"documentIncludes": [
"packages/json-document-object-document/docs/api.md"
],
"module": {
- "sourceDirectory": "packages/json-document-object-document",
- "responsibility": "Object·Canvas 모델·검증·의미 연산·projection",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-object-document",
"usagePaths": [
"/demo/canvas"
- ],
- "statusNote": "Object 소유권 확정 · RC. 모든 wire 계약의 Stable 승격을 뜻하지 않습니다."
- }
+ ]
+ },
+ "parentPath": "/docs/document-types"
},
{
"path": "/docs/api/canvas",
@@ -808,15 +729,11 @@
"title": "Canvas Hand API - json-document",
"description": "Canvas 입력·preview·UI 조합의 API와 Usage입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/canvas.md",
"documentIncludes": [
"packages/json-document-canvas/docs/api.md"
],
"module": {
- "sourceDirectory": "packages/json-document-canvas",
- "responsibility": "Canvas 선택·변형·입력·UI 조합",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-canvas",
"usagePaths": [
"/demo/canvas"
]
@@ -828,20 +745,16 @@
"title": "Calendar Document Type API - json-document",
"description": "@interactive-os/json-document-calendar-document의 문서 계약과 public API입니다.",
"language": "ko",
- "navigationGroup": "Document Types",
- "documentSource": "docs/api-reference/calendar-document.md",
"documentIncludes": [
"packages/json-document-calendar-document/docs/api.md"
],
"module": {
- "sourceDirectory": "packages/json-document-calendar-document",
- "responsibility": "Calendar 이벤트·반복·구간·발생분 projection",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-calendar-document",
"usagePaths": [
"/editors"
- ],
- "statusNote": "Calendar 소유권 확정 · RC. 모든 wire 계약의 Stable 승격을 뜻하지 않습니다."
- }
+ ]
+ },
+ "parentPath": "/docs/document-types"
},
{
"path": "/docs/api/calendar",
@@ -849,17 +762,16 @@
"title": "Calendar API - json-document",
"description": "@interactive-os/json-document-calendar의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Hands",
- "documentSource": "docs/api-reference/calendar.md",
"module": {
- "sourceDirectory": "packages/json-document-calendar",
- "responsibility": "Calendar 입력·선택·시간 표시·제어 UI 조합",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-calendar",
"usagePaths": [
"/editors",
"/demo/date-controls"
]
- }
+ },
+ "documentIncludes": [
+ "packages/json-document-calendar/docs/api.md"
+ ]
},
{
"path": "/docs/api/collaboration",
@@ -867,16 +779,13 @@
"title": "Collaboration API - json-document",
"description": "@interactive-os/json-document-collaboration의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Collaboration",
- "documentSource": "docs/api-reference/collaboration.md",
"module": {
- "sourceDirectory": "packages/json-document-collaboration",
- "responsibility": "동일 JSONDocument 계약의 복제·병합·협업 History",
- "alsoIn": [],
+ "packageName": "@interactive-os/json-document-collaboration",
"usagePaths": [
"/docs/collaboration"
]
- }
+ },
+ "parentPath": "/docs/collaboration"
},
{
"path": "/docs/api/contenteditable-collaboration",
@@ -884,22 +793,17 @@
"title": "Contenteditable Collaboration API - json-document",
"description": "@interactive-os/json-document-contenteditable-collaboration의 전체 public API입니다.",
"language": "ko",
- "navigationGroup": "Adapter",
- "documentSource": "docs/api-reference/contenteditable-collaboration.md",
"module": {
- "sourceDirectory": "packages/contenteditable-collaboration",
- "responsibility": "협업 문자열과 native DOM 입력 lease 연결",
- "alsoIn": [
- "Collaboration"
- ],
+ "packageName": "@interactive-os/json-document-contenteditable-collaboration",
"usagePaths": [
"/docs/collaboration"
]
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/collaboration",
- "label": "Overview",
+ "label": "Collaboration",
"title": "Collaboration - json-document",
"heading": "Collaboration",
"description": "같은 JSON Document 계약을 여러 참여자의 인과 변경으로 수렴시킵니다.",
@@ -915,7 +819,8 @@
"description": "한 참여자가 소유한 인과 상태와 아직 적용하지 못한 변경을 설명합니다.",
"language": "ko",
"navigationGroup": "Collaboration",
- "documentSource": "docs/public/collaboration-replica.md"
+ "documentSource": "docs/public/collaboration-replica.md",
+ "parentPath": "/docs/collaboration"
},
{
"path": "/docs/collaboration/lifecycle",
@@ -925,7 +830,8 @@
"description": "epoch, checkpoint, restore, compaction이 협업 세대를 접고 되돌리는 방식을 설명합니다.",
"language": "ko",
"navigationGroup": "Collaboration",
- "documentSource": "docs/public/collaboration-lifecycle.md"
+ "documentSource": "docs/public/collaboration-lifecycle.md",
+ "parentPath": "/docs/collaboration"
},
{
"path": "/docs/collaboration/history",
@@ -935,7 +841,8 @@
"description": "다른 참여자를 덮어쓰지 않고 현재 참여자의 인과 기여를 끄거나 켭니다.",
"language": "ko",
"navigationGroup": "Collaboration",
- "documentSource": "docs/public/collaboration-history.md"
+ "documentSource": "docs/public/collaboration-history.md",
+ "parentPath": "/docs/collaboration"
},
{
"path": "/docs/collaboration/text",
@@ -945,7 +852,8 @@
"description": "같이 쓰는 문자열의 안정된 글자 단위와 상대 위치를 설명합니다.",
"language": "ko",
"navigationGroup": "Collaboration",
- "documentSource": "docs/public/collaboration-text.md"
+ "documentSource": "docs/public/collaboration-text.md",
+ "parentPath": "/docs/collaboration"
},
{
"path": "/docs/collaboration/text/lease",
@@ -965,7 +873,8 @@
"description": "Document editor를 만들고 요청을 EditingIntent로 표현해 EditingResult를 처리합니다.",
"language": "ko",
"navigationGroup": "Editing",
- "documentSource": "docs/public/intent-guide.md"
+ "documentSource": "docs/public/intent-guide.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/docs/intent",
@@ -975,7 +884,8 @@
"description": "EditingIntent, dispatch, EditingResult와 editor별 Intent의 공개 시그니처를 정리합니다.",
"language": "ko",
"navigationGroup": "Editing",
- "documentSource": "docs/public/intent.md"
+ "documentSource": "docs/public/intent.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/docs/topology",
@@ -986,7 +896,8 @@
"language": "ko",
"navigationGroup": "Editing",
"relatedDemoPath": "/demo/topology",
- "documentSource": "docs/public/topology.md"
+ "documentSource": "docs/public/topology.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/demo/topology",
@@ -1006,7 +917,8 @@
"language": "ko",
"navigationGroup": "Editing",
"relatedDemoPath": "/demo/selection",
- "documentSource": "docs/public/selection.md"
+ "documentSource": "docs/public/selection.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/demo/selection",
@@ -1026,7 +938,8 @@
"language": "ko",
"navigationGroup": "Editing",
"relatedDemoPath": "/demo/clipboard",
- "documentSource": "docs/public/clipboard.md"
+ "documentSource": "docs/public/clipboard.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/demo/clipboard",
@@ -1046,7 +959,8 @@
"language": "ko",
"navigationGroup": "Editing",
"relatedDemoPath": "/demo/history",
- "documentSource": "docs/public/history.md"
+ "documentSource": "docs/public/history.md",
+ "parentPath": "/docs/editing"
},
{
"path": "/demo/history",
@@ -1064,11 +978,12 @@
"description": "versioned Rich Text domain이 JSON Document, Selection, Editing, Web와 React integration을 통과하는 reference vertical입니다.",
"language": "ko",
"navigationGroup": "Editing",
- "sidebar": true
+ "sidebar": true,
+ "parentPath": "/docs/editing"
},
{
"path": "/editors",
- "label": "Overview",
+ "label": "장르별 예제",
"title": "Hands - json-document",
"heading": "Hands",
"description": "닫힌 장르의 손과 TBD 손을 고릅니다.",
@@ -1076,16 +991,53 @@
"navigationGroup": "Hands",
"documentSource": "docs/public/hands.md"
},
+ {
+ "path": "/docs/hands-support",
+ "label": "지원 범위",
+ "title": "지원 범위 - json-document",
+ "heading": "지원 범위",
+ "description": "편집 조합의 현재 증거와 Profile 완료 조건을 구분합니다.",
+ "documentSource": "docs/public/hands-support.md",
+ "navigationGroup": "Hands"
+ },
+ {
+ "path": "/docs/design",
+ "label": "설계 현황",
+ "title": "설계 현황 - json-document",
+ "heading": "설계 현황",
+ "description": "프로토타입과 목표 계약, 소유권 감사의 진행 상태를 확인합니다.",
+ "documentSource": "docs/public/design.md",
+ "sidebar": false
+ },
+ {
+ "path": "/docs/ownership",
+ "label": "소유권 감사",
+ "title": "소유권 감사 - json-document",
+ "heading": "소유권 감사",
+ "description": "Document Type 후보의 실제 소유권 감사와 남은 경계를 확인합니다.",
+ "documentSource": "docs/public/ownership.md",
+ "navigationGroup": "Design"
+ },
{
"path": "/docs/official-hands",
- "label": "Official Hands · TBD",
+ "label": "Official Hands 목표",
"title": "Official Hands · TBD - json-document",
"heading": "Official Hands · TBD",
"description": "디자인과 제품 데이터는 자유롭게 유지하면서 수렴된 편집 기능을 완성된 SDK로 제공하는 방향을 설명합니다.",
"language": "ko",
- "navigationGroup": "Hands",
+ "navigationGroup": "Design",
"documentSource": "docs/public/official-hands.md"
},
+ {
+ "path": "/docs/how-we-build",
+ "label": "개발 원칙",
+ "title": "How We Build - json-document",
+ "heading": "제품에서 정본 모듈을 발견하는 방법",
+ "description": "Application을 먼저 만들고 실제 제품 책임을 canonical module로 추출한 뒤 제품이 다시 소비하는 개발 순환을 설명합니다.",
+ "language": "ko",
+ "navigationGroup": "Design",
+ "documentSource": "docs/public/how-we-build.md"
+ },
{
"path": "/demo",
"label": "Document",
@@ -1221,7 +1173,8 @@
"navigationGroup": "UI Primitives",
"relatedDemoPath": "/demo/animation",
"relatedDemoLabel": "Animation",
- "documentSource": "docs/public/animation.md"
+ "documentSource": "docs/public/animation.md",
+ "parentPath": "/docs/ui-primitives"
},
{
"path": "/demo/animation",
@@ -1294,11 +1247,12 @@
"title": "Adapters - json-document",
"description": "Official json-document platform adapters with live keyboard, clipboard, and contenteditable demos.",
"navigationGroup": "Adapter",
- "sidebar": false
+ "sidebar": false,
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapters",
- "label": "Overview",
+ "label": "Adapter",
"title": "Adapter Docs - json-document",
"heading": "json-document Adapters",
"description": "Keyboard, Clipboard, Contenteditable 공식 adapter 계약을 설명합니다.",
@@ -1314,7 +1268,8 @@
"description": "Keyboard, Press와 ARIA platform contract를 Editing 입력으로 번역합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-keyboard.md"
+ "documentSource": "docs/public/adapter-keyboard.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapter-grid-cell",
@@ -1324,7 +1279,8 @@
"description": "GridPoint를 안정된 DOM cell 주소와 focus lookup에 연결합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-grid-cell.md"
+ "documentSource": "docs/public/adapter-grid-cell.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapter-interaction",
@@ -1334,7 +1290,8 @@
"description": "Pointer capture와 HTML Drag and Drop session을 preview, commit, cancel lifecycle에 연결합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-interaction.md"
+ "documentSource": "docs/public/adapter-interaction.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapter-clipboard",
@@ -1344,7 +1301,8 @@
"description": "ClipboardEvent를 구조화된 copy, cut, paste 계약에 연결합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-clipboard.md"
+ "documentSource": "docs/public/adapter-clipboard.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapter-contenteditable",
@@ -1354,7 +1312,8 @@
"description": "문자열 pointer와 native-input DOM lifecycle을 연결합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-contenteditable.md"
+ "documentSource": "docs/public/adapter-contenteditable.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/docs/adapter-virtual-selection",
@@ -1364,7 +1323,8 @@
"description": "부분 마운트된 DOM의 Native Selection과 전체 model-backed plain-text 복사를 연결합니다.",
"language": "ko",
"navigationGroup": "Adapter",
- "documentSource": "docs/public/adapter-virtual-selection.md"
+ "documentSource": "docs/public/adapter-virtual-selection.md",
+ "parentPath": "/docs/adapters"
},
{
"path": "/affordances/handles",
@@ -1372,7 +1332,8 @@
"title": "Interaction Handles Live Demo - json-document",
"description": "DragHandle, ResizeHandle, ControlHandle의 정본 descriptor, cursor, lifecycle Usage입니다.",
"navigationGroup": "Affordance",
- "sidebar": false
+ "sidebar": false,
+ "parentPath": "/docs/affordance"
},
{
"path": "/adapters/keyboard",
@@ -1384,7 +1345,8 @@
"integration": {
"kind": "adapter",
"packageName": "@interactive-os/json-document-web"
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/adapters/clipboard",
@@ -1396,7 +1358,8 @@
"integration": {
"kind": "adapter",
"packageName": "@interactive-os/json-document-web"
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/adapters/contenteditable",
@@ -1408,7 +1371,8 @@
"integration": {
"kind": "adapter",
"packageName": "@interactive-os/json-document-contenteditable"
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/adapters/virtual-selection",
@@ -1420,7 +1384,8 @@
"integration": {
"kind": "adapter",
"packageName": "@interactive-os/json-document-web"
- }
+ },
+ "parentPath": "/docs/adapters"
},
{
"path": "/connectors",
@@ -1428,11 +1393,12 @@
"title": "Connectors - json-document",
"description": "Official json-document Connector catalog with live integration demos.",
"navigationGroup": "Connector",
- "sidebar": false
+ "sidebar": false,
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/connectors",
- "label": "Overview",
+ "label": "Connector",
"title": "Connector Docs - json-document",
"heading": "json-document Connectors",
"description": "React 구독과 선택·커서 질의, React Hook Form, Ajv, Zod, TanStack Table 연결 계약을 설명합니다.",
@@ -1448,7 +1414,8 @@
"description": "document와 editor의 변경을 React 구독과 선택 질의로 연결합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-react.md"
+ "documentSource": "docs/public/connector-react.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/react-editing",
@@ -1470,7 +1437,8 @@
"description": "form draft와 canonical document commit을 연결합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-react-hook-form.md"
+ "documentSource": "docs/public/connector-react-hook-form.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/connector-ajv",
@@ -1480,7 +1448,8 @@
"description": "Ajv validator와 JSON Document validation을 연결합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-ajv.md"
+ "documentSource": "docs/public/connector-ajv.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/connector-zod",
@@ -1490,7 +1459,8 @@
"description": "Zod schema와 Database document 변환을 설명합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-zod.md"
+ "documentSource": "docs/public/connector-zod.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/connector-zod-validate",
@@ -1510,7 +1480,8 @@
"description": "visible row와 column 순서를 SheetTopology에 연결합니다.",
"language": "ko",
"navigationGroup": "Connector",
- "documentSource": "docs/public/connector-tanstack-table.md"
+ "documentSource": "docs/public/connector-tanstack-table.md",
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/react",
@@ -1522,7 +1493,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-react"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/react-hook-form",
@@ -1534,7 +1506,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-react-hook-form"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/ajv",
@@ -1546,7 +1519,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-ajv"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/a2ui",
@@ -1558,7 +1532,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-a2ui"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/zod",
@@ -1570,7 +1545,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-zod"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/connectors/zod/validate",
@@ -1578,7 +1554,8 @@
"relatedDemoLabel": "Validate commits",
"title": "Zod Validate Live Demo - json-document",
"description": "Live Zod validation with JSON Pointer diagnostics and canonical JSON preservation.",
- "parentPath": "/connectors/zod"
+ "parentPath": "/connectors/zod",
+ "sidebar": false
},
{
"path": "/connectors/tanstack-table",
@@ -1590,7 +1567,8 @@
"integration": {
"kind": "connector",
"packageName": "@interactive-os/json-document-tanstack-table"
- }
+ },
+ "parentPath": "/docs/connectors"
},
{
"path": "/docs/ui-primitives",
@@ -1602,7 +1580,6 @@
"navigationGroup": "UI Primitives",
"relatedDemoPath": "/demo/ui-primitives",
"relatedDemoLabel": "Design system",
- "sidebar": false,
"documentSource": "docs/public/ui-primitives.md"
},
{
@@ -1613,7 +1590,6 @@
"description": "제품이 json-document를 만지는 최전선의 키보드·마우스·커서 손을 API와 사용법으로 설명합니다.",
"language": "ko",
"navigationGroup": "Affordance",
- "sidebar": false,
"documentSource": "docs/public/affordance.md"
},
{
@@ -1993,17 +1969,19 @@
},
{
"path": "/docs/building-blocks",
- "label": "Overview",
+ "label": "Building Blocks",
"title": "Building Blocks - json-document",
"heading": "Building Blocks",
"description": "Adapter·Connector·Affordance·UI Primitives의 독립적인 책임과 Foundation·Hands를 연결하는 계약을 설명합니다.",
"language": "ko",
"sidebar": false,
- "documentSource": "docs/public/building-blocks.md"
+ "documentSource": "docs/public/building-blocks.md",
+ "navigationGroup": "Introduction",
+ "parentPath": "/docs/architecture"
},
{
"path": "/docs/editing",
- "label": "Editing Protocol",
+ "label": "Editing",
"title": "Editing Protocol - json-document",
"heading": "Editing Protocol",
"description": "Intent, EditingPlan, EditingSession과 EditingSnapshot의 현재 계약 및 아직 동결하지 않은 Hands Profile을 설명합니다.",
diff --git a/site/src/app/documentation-map.ts b/site/src/app/documentation-map.ts
index 30be0998f..7457ceebd 100644
--- a/site/src/app/documentation-map.ts
+++ b/site/src/app/documentation-map.ts
@@ -40,16 +40,19 @@ export function documentationMap(page: SiteRoute, pages: readonly SiteRoute[] =
}).filter(Boolean).join("\n\n")}`).join("\n\n")}\n\n${applicationMap(pages)}`;
}
if (page.path === "/applications") return applicationMap(pages);
- const section = siteSections.find(section => section.path === page.path && ["foundation", "building-blocks"].includes(section.id));
+ const section = siteSections.find(section => section.path === page.path && section.id === "modules");
const group = Object.entries(groupLandings).find(([, landing]) => landing.path === page.path)?.[0] as SiteNavigationGroup | undefined;
+ if (section?.id === "modules") {
+ return section.groups.map(group => `## ${group}\n\n[설명](${groupLandings[group].path})\n\n${table(modulesAt([group], pages), pages)}`).join("\n\n");
+ }
const groups = section?.groups ?? (group ? [group] : []);
const modules = modulesAt(groups, pages);
- const candidates = page.path === "/docs/document-types" ? documentTypeMap(pages) : "";
- return modules.length ? `## 현재 제공 모듈\n\n역할별 공개 계약과 실제 Usage입니다. 패키지 내부의 혼합 책임과 이행 상태는 각 API 문서에서 확인합니다.\n\n${table(modules, pages)}\n\n${candidates}` : "";
+ const candidates = ["/docs/document-types", "/docs/ownership"].includes(page.path) ? documentTypeMap(pages) : "";
+ return modules.length ? `## 현재 제공 모듈\n\n역할별 공개 계약과 실제 Usage입니다. 패키지 내부의 혼합 책임과 이행 상태는 각 API 문서에서 확인합니다.\n\n${table(modules, pages)}\n\n${candidates}` : candidates;
}
function applicationMap(pages: readonly SiteRoute[]): string {
- return `## 제품에서 모듈로\n\n직접 호출과 그 편집 경로에서 사용하는 정본 모듈을 연결합니다. 아래는 확인한 대표 조합이며 전체 의존성 목록이 아닙니다. 제품의 존재가 모든 Profile의 완료를 뜻하지 않습니다.\n\n| Application | 확인된 모듈 조합 |\n| --- | --- |\n${pages.filter(page => page.modulePaths).map(page => `| ${link(page)} | ${links(page.modulePaths!, pages)} |`).join("\n")}`;
+ return `## 제품에서 모듈로\n\n제품 route의 정적 import 경로에서 확인되는 대표 정본 모듈을 연결합니다. type import와 re-export를 포함하며 실행 시 호출을 보장하지 않습니다. 아래는 확인한 대표 조합이며 전체 의존성 목록이 아닙니다. 제품의 존재가 모든 Profile의 완료를 뜻하지 않습니다.\n\n| Application | 확인된 모듈 조합 |\n| --- | --- |\n${pages.filter(page => page.modulePaths).map(page => `| ${link(page)} | ${links(page.modulePaths!, pages)} |`).join("\n")}`;
}
function documentTypeMap(pages: readonly SiteRoute[]): string {
diff --git a/site/src/app/navigation-layer-icon.tsx b/site/src/app/navigation-layer-icon.tsx
index 0c2c31296..0357e2c94 100644
--- a/site/src/app/navigation-layer-icon.tsx
+++ b/site/src/app/navigation-layer-icon.tsx
@@ -1,7 +1,6 @@
import {
Blocks,
BookOpen,
- Braces,
Files,
Hand,
PanelsTopLeft,
@@ -15,11 +14,10 @@ type LayerIcon = {
};
const layerIcons: Readonly> = {
- introduction: { icon: BookOpen, size: 18 },
- foundation: { icon: Braces, size: 19 },
- "building-blocks": { icon: Blocks, size: 18 },
+ "getting-started": { icon: BookOpen, size: 18 },
+ modules: { icon: Blocks, size: 18 },
hands: { icon: Hand, size: 19 },
- artifact: { icon: Files, size: 18 },
+ design: { icon: Files, size: 18 },
applications: { icon: PanelsTopLeft, size: 19 },
};
diff --git a/site/src/app/page-descriptors.ts b/site/src/app/page-descriptors.ts
index 9b675bf54..a84934856 100644
--- a/site/src/app/page-descriptors.ts
+++ b/site/src/app/page-descriptors.ts
@@ -1,7 +1,8 @@
-import siteRoutesJson from "../../site-routes.json";
+import { siteRoutes } from "../../route-registry.mjs";
export { legacyPageRedirects } from "./legacy-page-redirects";
export type SiteNavigationGroup =
+ | "Design"
| "Introduction"
| "JSON Document"
| "Document Types"
@@ -32,12 +33,14 @@ export type SiteRoute = {
readonly relatedDemoPath?: string;
readonly relatedDemoLabel?: string;
readonly module?: {
+ readonly packageName: string;
readonly sourceDirectory: string;
readonly responsibility: string;
readonly alsoIn: readonly SiteNavigationGroup[];
readonly usagePaths: readonly string[];
readonly statusNote?: string;
};
+ readonly applicationSource?: string;
readonly modulePaths?: readonly string[];
readonly integration?: {
readonly kind: IntegrationKind;
@@ -45,7 +48,7 @@ export type SiteRoute = {
};
};
-export const pageDescriptors = siteRoutesJson as ReadonlyArray;
+export const pageDescriptors = siteRoutes;
export function pageDescriptor(path: string): SiteRoute {
const descriptor = pageDescriptors.find((route) => route.path === path);
diff --git a/site/src/app/routeTree.gen.ts b/site/src/app/routeTree.gen.ts
index c0188329e..6fc17c11b 100644
--- a/site/src/app/routeTree.gen.ts
+++ b/site/src/app/routeTree.gen.ts
@@ -76,16 +76,21 @@ import { Route as PageDocsConnectorZodRouteImport } from "./routes/_page/docs/co
import { Route as PageDocsConnectorZodValidateRouteImport } from "./routes/_page/docs/connector-zod-validate";
import { Route as PageDocsConnectorsRouteImport } from "./routes/_page/docs/connectors";
import { Route as PageDocsDatabaseRouteImport } from "./routes/_page/docs/database";
+import { Route as PageDocsDesignRouteImport } from "./routes/_page/docs/design";
import { Route as PageDocsEditingRouteImport } from "./routes/_page/docs/editing";
import { Route as PageDocsFoundationRouteImport } from "./routes/_page/docs/foundation";
+import { Route as PageDocsHandsSupportRouteImport } from "./routes/_page/docs/hands-support";
import { Route as PageDocsHistoryRouteImport } from "./routes/_page/docs/history";
import { Route as PageDocsHowWeBuildRouteImport } from "./routes/_page/docs/how-we-build";
import { Route as PageDocsIntentRouteImport } from "./routes/_page/docs/intent";
import { Route as PageDocsIntentGuideRouteImport } from "./routes/_page/docs/intent-guide";
import { Route as PageDocsMentionRouteImport } from "./routes/_page/docs/mention";
+import { Route as PageDocsModulesRouteImport } from "./routes/_page/docs/modules";
import { Route as PageDocsObjectRouteImport } from "./routes/_page/docs/object";
import { Route as PageDocsOfficialHandsRouteImport } from "./routes/_page/docs/official-hands";
import { Route as PageDocsOrderRouteImport } from "./routes/_page/docs/order";
+import { Route as PageDocsOwnershipRouteImport } from "./routes/_page/docs/ownership";
+import { Route as PageDocsQuickStartRouteImport } from "./routes/_page/docs/quick-start";
import { Route as PageDocsReactEditingRouteImport } from "./routes/_page/docs/react-editing";
import { Route as PageDocsSelectionRouteImport } from "./routes/_page/docs/selection";
import { Route as PageDocsTopologyRouteImport } from "./routes/_page/docs/topology";
@@ -523,6 +528,11 @@ const PageDocsDatabaseRoute = PageDocsDatabaseRouteImport.update({
path: "/docs/database",
getParentRoute: () => PageRoute,
} as any);
+const PageDocsDesignRoute = PageDocsDesignRouteImport.update({
+ id: "/docs/design",
+ path: "/docs/design",
+ getParentRoute: () => PageRoute,
+} as any);
const PageDocsEditingRoute = PageDocsEditingRouteImport.update({
id: "/docs/editing",
path: "/docs/editing",
@@ -533,6 +543,11 @@ const PageDocsFoundationRoute = PageDocsFoundationRouteImport.update({
path: "/docs/foundation",
getParentRoute: () => PageRoute,
} as any);
+const PageDocsHandsSupportRoute = PageDocsHandsSupportRouteImport.update({
+ id: "/docs/hands-support",
+ path: "/docs/hands-support",
+ getParentRoute: () => PageRoute,
+} as any);
const PageDocsHistoryRoute = PageDocsHistoryRouteImport.update({
id: "/docs/history",
path: "/docs/history",
@@ -558,6 +573,11 @@ const PageDocsMentionRoute = PageDocsMentionRouteImport.update({
path: "/docs/mention",
getParentRoute: () => PageRoute,
} as any);
+const PageDocsModulesRoute = PageDocsModulesRouteImport.update({
+ id: "/docs/modules",
+ path: "/docs/modules",
+ getParentRoute: () => PageRoute,
+} as any);
const PageDocsObjectRoute = PageDocsObjectRouteImport.update({
id: "/docs/object",
path: "/docs/object",
@@ -573,6 +593,16 @@ const PageDocsOrderRoute = PageDocsOrderRouteImport.update({
path: "/docs/order",
getParentRoute: () => PageRoute,
} as any);
+const PageDocsOwnershipRoute = PageDocsOwnershipRouteImport.update({
+ id: "/docs/ownership",
+ path: "/docs/ownership",
+ getParentRoute: () => PageRoute,
+} as any);
+const PageDocsQuickStartRoute = PageDocsQuickStartRouteImport.update({
+ id: "/docs/quick-start",
+ path: "/docs/quick-start",
+ getParentRoute: () => PageRoute,
+} as any);
const PageDocsReactEditingRoute = PageDocsReactEditingRouteImport.update({
id: "/docs/react-editing",
path: "/docs/react-editing",
@@ -1123,16 +1153,21 @@ export interface FileRoutesByFullPath {
"/docs/connector-zod-validate": typeof PageDocsConnectorZodValidateRoute;
"/docs/connectors": typeof PageDocsConnectorsRoute;
"/docs/database": typeof PageDocsDatabaseRoute;
+ "/docs/design": typeof PageDocsDesignRoute;
"/docs/editing": typeof PageDocsEditingRoute;
"/docs/foundation": typeof PageDocsFoundationRoute;
+ "/docs/hands-support": typeof PageDocsHandsSupportRoute;
"/docs/history": typeof PageDocsHistoryRoute;
"/docs/how-we-build": typeof PageDocsHowWeBuildRoute;
"/docs/intent": typeof PageDocsIntentRoute;
"/docs/intent-guide": typeof PageDocsIntentGuideRoute;
"/docs/mention": typeof PageDocsMentionRoute;
+ "/docs/modules": typeof PageDocsModulesRoute;
"/docs/object": typeof PageDocsObjectRoute;
"/docs/official-hands": typeof PageDocsOfficialHandsRoute;
"/docs/order": typeof PageDocsOrderRoute;
+ "/docs/ownership": typeof PageDocsOwnershipRoute;
+ "/docs/quick-start": typeof PageDocsQuickStartRoute;
"/docs/react-editing": typeof PageDocsReactEditingRoute;
"/docs/selection": typeof PageDocsSelectionRoute;
"/docs/topology": typeof PageDocsTopologyRoute;
@@ -1290,16 +1325,21 @@ export interface FileRoutesByTo {
"/docs/connector-zod-validate": typeof PageDocsConnectorZodValidateRoute;
"/docs/connectors": typeof PageDocsConnectorsRoute;
"/docs/database": typeof PageDocsDatabaseRoute;
+ "/docs/design": typeof PageDocsDesignRoute;
"/docs/editing": typeof PageDocsEditingRoute;
"/docs/foundation": typeof PageDocsFoundationRoute;
+ "/docs/hands-support": typeof PageDocsHandsSupportRoute;
"/docs/history": typeof PageDocsHistoryRoute;
"/docs/how-we-build": typeof PageDocsHowWeBuildRoute;
"/docs/intent": typeof PageDocsIntentRoute;
"/docs/intent-guide": typeof PageDocsIntentGuideRoute;
"/docs/mention": typeof PageDocsMentionRoute;
+ "/docs/modules": typeof PageDocsModulesRoute;
"/docs/object": typeof PageDocsObjectRoute;
"/docs/official-hands": typeof PageDocsOfficialHandsRoute;
"/docs/order": typeof PageDocsOrderRoute;
+ "/docs/ownership": typeof PageDocsOwnershipRoute;
+ "/docs/quick-start": typeof PageDocsQuickStartRoute;
"/docs/react-editing": typeof PageDocsReactEditingRoute;
"/docs/selection": typeof PageDocsSelectionRoute;
"/docs/topology": typeof PageDocsTopologyRoute;
@@ -1459,16 +1499,21 @@ export interface FileRoutesById {
"/_page/docs/connector-zod-validate": typeof PageDocsConnectorZodValidateRoute;
"/_page/docs/connectors": typeof PageDocsConnectorsRoute;
"/_page/docs/database": typeof PageDocsDatabaseRoute;
+ "/_page/docs/design": typeof PageDocsDesignRoute;
"/_page/docs/editing": typeof PageDocsEditingRoute;
"/_page/docs/foundation": typeof PageDocsFoundationRoute;
+ "/_page/docs/hands-support": typeof PageDocsHandsSupportRoute;
"/_page/docs/history": typeof PageDocsHistoryRoute;
"/_page/docs/how-we-build": typeof PageDocsHowWeBuildRoute;
"/_page/docs/intent": typeof PageDocsIntentRoute;
"/_page/docs/intent-guide": typeof PageDocsIntentGuideRoute;
"/_page/docs/mention": typeof PageDocsMentionRoute;
+ "/_page/docs/modules": typeof PageDocsModulesRoute;
"/_page/docs/object": typeof PageDocsObjectRoute;
"/_page/docs/official-hands": typeof PageDocsOfficialHandsRoute;
"/_page/docs/order": typeof PageDocsOrderRoute;
+ "/_page/docs/ownership": typeof PageDocsOwnershipRoute;
+ "/_page/docs/quick-start": typeof PageDocsQuickStartRoute;
"/_page/docs/react-editing": typeof PageDocsReactEditingRoute;
"/_page/docs/selection": typeof PageDocsSelectionRoute;
"/_page/docs/topology": typeof PageDocsTopologyRoute;
@@ -1628,16 +1673,21 @@ export interface FileRouteTypes {
| "/docs/connector-zod-validate"
| "/docs/connectors"
| "/docs/database"
+ | "/docs/design"
| "/docs/editing"
| "/docs/foundation"
+ | "/docs/hands-support"
| "/docs/history"
| "/docs/how-we-build"
| "/docs/intent"
| "/docs/intent-guide"
| "/docs/mention"
+ | "/docs/modules"
| "/docs/object"
| "/docs/official-hands"
| "/docs/order"
+ | "/docs/ownership"
+ | "/docs/quick-start"
| "/docs/react-editing"
| "/docs/selection"
| "/docs/topology"
@@ -1795,16 +1845,21 @@ export interface FileRouteTypes {
| "/docs/connector-zod-validate"
| "/docs/connectors"
| "/docs/database"
+ | "/docs/design"
| "/docs/editing"
| "/docs/foundation"
+ | "/docs/hands-support"
| "/docs/history"
| "/docs/how-we-build"
| "/docs/intent"
| "/docs/intent-guide"
| "/docs/mention"
+ | "/docs/modules"
| "/docs/object"
| "/docs/official-hands"
| "/docs/order"
+ | "/docs/ownership"
+ | "/docs/quick-start"
| "/docs/react-editing"
| "/docs/selection"
| "/docs/topology"
@@ -1963,16 +2018,21 @@ export interface FileRouteTypes {
| "/_page/docs/connector-zod-validate"
| "/_page/docs/connectors"
| "/_page/docs/database"
+ | "/_page/docs/design"
| "/_page/docs/editing"
| "/_page/docs/foundation"
+ | "/_page/docs/hands-support"
| "/_page/docs/history"
| "/_page/docs/how-we-build"
| "/_page/docs/intent"
| "/_page/docs/intent-guide"
| "/_page/docs/mention"
+ | "/_page/docs/modules"
| "/_page/docs/object"
| "/_page/docs/official-hands"
| "/_page/docs/order"
+ | "/_page/docs/ownership"
+ | "/_page/docs/quick-start"
| "/_page/docs/react-editing"
| "/_page/docs/selection"
| "/_page/docs/topology"
@@ -2546,6 +2606,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof PageDocsDatabaseRouteImport;
parentRoute: typeof PageRoute;
};
+ "/_page/docs/design": {
+ id: "/_page/docs/design";
+ path: "/docs/design";
+ fullPath: "/docs/design";
+ preLoaderRoute: typeof PageDocsDesignRouteImport;
+ parentRoute: typeof PageRoute;
+ };
"/_page/docs/editing": {
id: "/_page/docs/editing";
path: "/docs/editing";
@@ -2560,6 +2627,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof PageDocsFoundationRouteImport;
parentRoute: typeof PageRoute;
};
+ "/_page/docs/hands-support": {
+ id: "/_page/docs/hands-support";
+ path: "/docs/hands-support";
+ fullPath: "/docs/hands-support";
+ preLoaderRoute: typeof PageDocsHandsSupportRouteImport;
+ parentRoute: typeof PageRoute;
+ };
"/_page/docs/history": {
id: "/_page/docs/history";
path: "/docs/history";
@@ -2595,6 +2669,13 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof PageDocsMentionRouteImport;
parentRoute: typeof PageRoute;
};
+ "/_page/docs/modules": {
+ id: "/_page/docs/modules";
+ path: "/docs/modules";
+ fullPath: "/docs/modules";
+ preLoaderRoute: typeof PageDocsModulesRouteImport;
+ parentRoute: typeof PageRoute;
+ };
"/_page/docs/object": {
id: "/_page/docs/object";
path: "/docs/object";
@@ -2616,6 +2697,20 @@ declare module "@tanstack/react-router" {
preLoaderRoute: typeof PageDocsOrderRouteImport;
parentRoute: typeof PageRoute;
};
+ "/_page/docs/ownership": {
+ id: "/_page/docs/ownership";
+ path: "/docs/ownership";
+ fullPath: "/docs/ownership";
+ preLoaderRoute: typeof PageDocsOwnershipRouteImport;
+ parentRoute: typeof PageRoute;
+ };
+ "/_page/docs/quick-start": {
+ id: "/_page/docs/quick-start";
+ path: "/docs/quick-start";
+ fullPath: "/docs/quick-start";
+ preLoaderRoute: typeof PageDocsQuickStartRouteImport;
+ parentRoute: typeof PageRoute;
+ };
"/_page/docs/react-editing": {
id: "/_page/docs/react-editing";
path: "/docs/react-editing";
@@ -3302,16 +3397,21 @@ interface PageRouteChildren {
PageDocsConnectorZodValidateRoute: typeof PageDocsConnectorZodValidateRoute;
PageDocsConnectorsRoute: typeof PageDocsConnectorsRoute;
PageDocsDatabaseRoute: typeof PageDocsDatabaseRoute;
+ PageDocsDesignRoute: typeof PageDocsDesignRoute;
PageDocsEditingRoute: typeof PageDocsEditingRoute;
PageDocsFoundationRoute: typeof PageDocsFoundationRoute;
+ PageDocsHandsSupportRoute: typeof PageDocsHandsSupportRoute;
PageDocsHistoryRoute: typeof PageDocsHistoryRoute;
PageDocsHowWeBuildRoute: typeof PageDocsHowWeBuildRoute;
PageDocsIntentRoute: typeof PageDocsIntentRoute;
PageDocsIntentGuideRoute: typeof PageDocsIntentGuideRoute;
PageDocsMentionRoute: typeof PageDocsMentionRoute;
+ PageDocsModulesRoute: typeof PageDocsModulesRoute;
PageDocsObjectRoute: typeof PageDocsObjectRoute;
PageDocsOfficialHandsRoute: typeof PageDocsOfficialHandsRoute;
PageDocsOrderRoute: typeof PageDocsOrderRoute;
+ PageDocsOwnershipRoute: typeof PageDocsOwnershipRoute;
+ PageDocsQuickStartRoute: typeof PageDocsQuickStartRoute;
PageDocsReactEditingRoute: typeof PageDocsReactEditingRoute;
PageDocsSelectionRoute: typeof PageDocsSelectionRoute;
PageDocsTopologyRoute: typeof PageDocsTopologyRoute;
@@ -3468,16 +3568,21 @@ const PageRouteChildren: PageRouteChildren = {
PageDocsConnectorZodValidateRoute: PageDocsConnectorZodValidateRoute,
PageDocsConnectorsRoute: PageDocsConnectorsRoute,
PageDocsDatabaseRoute: PageDocsDatabaseRoute,
+ PageDocsDesignRoute: PageDocsDesignRoute,
PageDocsEditingRoute: PageDocsEditingRoute,
PageDocsFoundationRoute: PageDocsFoundationRoute,
+ PageDocsHandsSupportRoute: PageDocsHandsSupportRoute,
PageDocsHistoryRoute: PageDocsHistoryRoute,
PageDocsHowWeBuildRoute: PageDocsHowWeBuildRoute,
PageDocsIntentRoute: PageDocsIntentRoute,
PageDocsIntentGuideRoute: PageDocsIntentGuideRoute,
PageDocsMentionRoute: PageDocsMentionRoute,
+ PageDocsModulesRoute: PageDocsModulesRoute,
PageDocsObjectRoute: PageDocsObjectRoute,
PageDocsOfficialHandsRoute: PageDocsOfficialHandsRoute,
PageDocsOrderRoute: PageDocsOrderRoute,
+ PageDocsOwnershipRoute: PageDocsOwnershipRoute,
+ PageDocsQuickStartRoute: PageDocsQuickStartRoute,
PageDocsReactEditingRoute: PageDocsReactEditingRoute,
PageDocsSelectionRoute: PageDocsSelectionRoute,
PageDocsTopologyRoute: PageDocsTopologyRoute,
diff --git a/site/src/app/routes/__root.tsx b/site/src/app/routes/__root.tsx
index f4363238f..88a6f9a11 100644
--- a/site/src/app/routes/__root.tsx
+++ b/site/src/app/routes/__root.tsx
@@ -141,7 +141,7 @@ function AppShell() {
>
{open && landingRoute ? : null}
{open
- ? section.groups.map((group) => {
+ ? section.groups.flatMap((group) => {
const groupRoutes = sectionRoutes.filter((item) => {
if (item.navigationGroup !== group) return false;
const parent = item.parentPath
@@ -149,17 +149,9 @@ function AppShell() {
: undefined;
return parent?.navigationGroup !== group;
});
- if (groupRoutes.length === 0) return null;
- return (
-
- {section.groups.length > 1 ? {group} : null}
-
- {groupRoutes.map((item) => (
-
- ))}
-
-
- );
+ return groupRoutes.map((item) => (
+
+ ));
})
: null}
diff --git a/site/src/app/routes/_page/adapters/index.tsx b/site/src/app/routes/_page/adapters/index.tsx
index fc2bbeb01..5b2a32c43 100644
--- a/site/src/app/routes/_page/adapters/index.tsx
+++ b/site/src/app/routes/_page/adapters/index.tsx
@@ -1,6 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
-import { AdapterCatalogRoute } from "../../../../routes/adapters/AdapterCatalogRoute";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
export const Route = createFileRoute("/_page/adapters/")({
- component: AdapterCatalogRoute,
+ component: () => ,
});
diff --git a/site/src/app/routes/_page/connectors/index.tsx b/site/src/app/routes/_page/connectors/index.tsx
index d639afbc7..361eb1d94 100644
--- a/site/src/app/routes/_page/connectors/index.tsx
+++ b/site/src/app/routes/_page/connectors/index.tsx
@@ -1,6 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
-import { ConnectorCatalogRoute } from "../../../../routes/connectors/ConnectorCatalogRoute";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
export const Route = createFileRoute("/_page/connectors/")({
- component: ConnectorCatalogRoute,
+ component: () => ,
});
diff --git a/site/src/app/routes/_page/docs/design.tsx b/site/src/app/routes/_page/docs/design.tsx
new file mode 100644
index 000000000..1e030f0bd
--- /dev/null
+++ b/site/src/app/routes/_page/docs/design.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
+
+export const Route = createFileRoute("/_page/docs/design")({
+ component: () => ,
+});
diff --git a/site/src/app/routes/_page/docs/hands-support.tsx b/site/src/app/routes/_page/docs/hands-support.tsx
new file mode 100644
index 000000000..dc45d6728
--- /dev/null
+++ b/site/src/app/routes/_page/docs/hands-support.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
+
+export const Route = createFileRoute("/_page/docs/hands-support")({
+ component: () => ,
+});
diff --git a/site/src/app/routes/_page/docs/modules.tsx b/site/src/app/routes/_page/docs/modules.tsx
new file mode 100644
index 000000000..25450ca62
--- /dev/null
+++ b/site/src/app/routes/_page/docs/modules.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
+
+export const Route = createFileRoute("/_page/docs/modules")({
+ component: () => ,
+});
diff --git a/site/src/app/routes/_page/docs/ownership.tsx b/site/src/app/routes/_page/docs/ownership.tsx
new file mode 100644
index 000000000..bf941bafc
--- /dev/null
+++ b/site/src/app/routes/_page/docs/ownership.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
+
+export const Route = createFileRoute("/_page/docs/ownership")({
+ component: () => ,
+});
diff --git a/site/src/app/routes/_page/docs/quick-start.tsx b/site/src/app/routes/_page/docs/quick-start.tsx
new file mode 100644
index 000000000..dfe728155
--- /dev/null
+++ b/site/src/app/routes/_page/docs/quick-start.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { DocsRoute } from "../../../../routes/docs/DocsRoute";
+
+export const Route = createFileRoute("/_page/docs/quick-start")({
+ component: () => ,
+});
diff --git a/site/src/app/site-layers.ts b/site/src/app/site-layers.ts
index 24c7e0efe..d9d6b99ef 100644
--- a/site/src/app/site-layers.ts
+++ b/site/src/app/site-layers.ts
@@ -1,7 +1,7 @@
import { type SiteNavigationGroup } from "./page-descriptors";
export type SiteSection = {
- readonly id: "introduction" | "foundation" | "building-blocks" | "hands" | "artifact" | "applications";
+ readonly id: "getting-started" | "modules" | "hands" | "applications" | "design";
readonly path: string;
readonly label: string;
readonly blurb: string;
@@ -9,48 +9,11 @@ export type SiteSection = {
};
export const siteSections: ReadonlyArray = [
- {
- id: "introduction",
- path: "/docs",
- label: "Introduction",
- blurb: "Why, concepts, and how we build",
- groups: ["Introduction"],
- },
- {
- id: "foundation",
- path: "/docs/foundation",
- label: "Foundation",
- blurb: "Values, meaning, editing, and collaboration",
- groups: ["JSON Document", "Document Types", "Editing", "Collaboration"],
- },
- {
- id: "building-blocks",
- path: "/docs/building-blocks",
- label: "Building Blocks",
- blurb: "Platform, ecosystem, interaction, and UI",
- groups: ["Adapter", "Connector", "Affordance", "UI Primitives"],
- },
- {
- id: "hands",
- path: "/editors",
- label: "Hands",
- blurb: "Tools that close an editing loop",
- groups: ["Hands"],
- },
- {
- id: "artifact",
- path: "/viewer",
- label: "Artifact",
- blurb: "Editable content inside applications",
- groups: ["Artifact"],
- },
- {
- id: "applications",
- path: "/applications",
- label: "Applications",
- blurb: "Products that reveal reusable modules",
- groups: ["Applications"],
- },
+ { id: "getting-started", path: "/docs", label: "시작하기", blurb: "소개, 빠른 시작과 아키텍처", groups: ["Introduction"] },
+ { id: "modules", path: "/docs/modules", label: "모듈", blurb: "책임별 설명, API와 Usage", groups: ["JSON Document", "Document Types", "Editing", "Collaboration", "Adapter", "Connector", "Affordance", "UI Primitives"] },
+ { id: "hands", path: "/editors", label: "편집 조합 · Hands", blurb: "장르별 편집 예제와 지원 범위", groups: ["Hands"] },
+ { id: "applications", path: "/applications", label: "Applications", blurb: "Bear, Calendar와 AI Agent", groups: ["Applications"] },
+ { id: "design", path: "/docs/design", label: "설계와 진행 상태", blurb: "설계 목표, 프로토타입과 소유권 감사", groups: ["Design", "Artifact"] },
];
export type SiteSectionId = (typeof siteSections)[number]["id"];
@@ -72,6 +35,7 @@ export const groupLandings: Record;
-}
diff --git a/site/src/routes/connectors/ConnectorCatalogRoute.tsx b/site/src/routes/connectors/ConnectorCatalogRoute.tsx
deleted file mode 100644
index d4b692598..000000000
--- a/site/src/routes/connectors/ConnectorCatalogRoute.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { DocsRoute } from "../docs/DocsRoute";
-
-export function ConnectorCatalogRoute() {
- return ;
-}
diff --git a/site/src/routes/docs/DocsRoute.tsx b/site/src/routes/docs/DocsRoute.tsx
index bdfefb2ac..f277763e6 100644
--- a/site/src/routes/docs/DocsRoute.tsx
+++ b/site/src/routes/docs/DocsRoute.tsx
@@ -3,6 +3,11 @@ import { docPages, type DocPageId } from "./doc-pages";
import { DocumentationPage } from "./DocumentationPage";
const docIllustrations: Record = {
+ quickStart: "terminal",
+ modules: "braces",
+ handsSupport: "cursor",
+ design: "branch",
+ ownership: "branch",
overview: "package",
architecture: "sleep",
foundation: "braces",
diff --git a/site/src/routes/docs/doc-pages.ts b/site/src/routes/docs/doc-pages.ts
index 8a6dea88c..29f999932 100644
--- a/site/src/routes/docs/doc-pages.ts
+++ b/site/src/routes/docs/doc-pages.ts
@@ -2,7 +2,7 @@ import { documentationMap } from "../../app/documentation-map";
import { pageDescriptor } from "../../app/page-descriptors";
const documentSources = import.meta.glob(
- ["../../../../docs/public/*.md", "../../../../docs/api-reference/*.md", "../../../../packages/*/docs/*.md"],
+ ["../../../../docs/public/*.md", "../../../../packages/*/docs/*.md"],
{ query: "?raw", import: "default", eager: true },
);
@@ -19,6 +19,11 @@ function docPage(path: string) {
}
export const docPages = {
+ quickStart: docPage("/docs/quick-start"),
+ modules: docPage("/docs/modules"),
+ handsSupport: docPage("/docs/hands-support"),
+ design: docPage("/docs/design"),
+ ownership: docPage("/docs/ownership"),
markdownApi: docPage("/docs/api/markdown"),
markdownWebApi: docPage("/docs/api/markdown-web"),
canvasApi: docPage("/docs/api/canvas"),
diff --git a/site/src/routes/home/HomeRoute.tsx b/site/src/routes/home/HomeRoute.tsx
index 078c599c5..ef3d969eb 100644
--- a/site/src/routes/home/HomeRoute.tsx
+++ b/site/src/routes/home/HomeRoute.tsx
@@ -46,7 +46,7 @@ export function HomeRoute() {
제품을 먼저 만들고, 그 안에서 발견한 책임을 모듈과 부품으로 되돌립니다.
-
Introduction부터 읽기
+
시작하기
Applications 보기
이야기를 따라 내려가기
@@ -63,7 +63,7 @@ export function HomeRoute() {
JSON 값, 의미, 변경, 협업의 계약을 먼저 공유하면 사람과 agent가 같은 문서를 안전하게 다룰 수 있습니다.
-
Foundation 살펴보기
+
모듈 살펴보기
Architecture
diff --git a/site/src/shared/demo-workbench/demo-sources.ts b/site/src/shared/demo-workbench/demo-sources.ts
index b2d781c02..fab3046b3 100644
--- a/site/src/shared/demo-workbench/demo-sources.ts
+++ b/site/src/shared/demo-workbench/demo-sources.ts
@@ -1,4 +1,4 @@
-import { pageDescriptors } from "../../app/page-descriptors";
+import { siteRoutes } from "../../../route-registry.mjs";
import markdownSourceEditSource from "../../../../packages/json-document-markdown/src/source-edit.ts?raw";
import markdownListEditingSource from "../../../../packages/json-document-markdown/src/list-editing.ts?raw";
import markdownBindingSource from "../../../../packages/json-document-markdown-web/src/editing-binding.ts?raw";
@@ -191,7 +191,7 @@ export type DemoSourceFile = {
readonly load: () => Promise;
};
-const packageReferencePaths = new Map(pageDescriptors.flatMap(page => page.module ? [[`${page.module.sourceDirectory}/`, page.path] as const] : []));
+const packageReferencePaths = new Map(siteRoutes.flatMap(page => page.module ? [[`${page.module.sourceDirectory}/`, page.path] as const] : []));
const sourceModules = import.meta.glob(
[
diff --git a/site/tests/browser/site-map.spec.ts b/site/tests/browser/site-map.spec.ts
new file mode 100644
index 000000000..5cd464ca9
--- /dev/null
+++ b/site/tests/browser/site-map.spec.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "@playwright/test";
+
+test("menu moves from quick start to a module and separates design status", async ({ page }, testInfo) => {
+ await page.goto("/docs/quick-start");
+ await expect(page.getByRole("heading", { level: 1, name: "빠른 시작" })).toBeVisible();
+ await expect(page.locator('[data-live-demo="/demo"]')).toBeVisible();
+ const nav = page.getByRole("navigation", { name: "Site navigation" });
+ await nav.getByRole("button", { name: "모듈", exact: true }).click();
+ await nav.getByRole("link", { name: "Document Types", exact: true }).click();
+ await expect(nav.getByRole("link", { name: "API · Markdown", exact: true })).toBeVisible();
+ await expect(nav.getByRole("group", { name: "모듈", exact: true }).getByRole("link", { name: "Tree · TBD", exact: true })).toHaveCount(0);
+ await nav.getByRole("link", { name: "API · Markdown", exact: true }).click();
+ await page.locator('[data-doc-content]').getByRole("link", { name: "Markdown caret", exact: true }).first().click();
+ await expect(page.getByRole("textbox", { name: "Markdown 편집" })).toBeVisible();
+ await expect(page.getByRole("tablist", { name: "Demo and source files" })).toBeVisible();
+ await nav.getByRole("button", { name: "설계와 진행 상태" }).click();
+ await nav.getByRole("link", { name: "소유권 감사", exact: true }).click();
+ await expect(page.getByRole("heading", { level: 2, name: "현재 후보별 상태" })).toBeVisible();
+ await nav.getByRole("link", { name: "Tree · TBD", exact: true }).click();
+ await expect(page.getByRole("navigation", { name: "Breadcrumb" }).getByRole("link", { name: "설계와 진행 상태" })).toBeVisible();
+ await nav.getByRole("button", { name: "편집 조합 · Hands" }).click();
+ await nav.getByRole("link", { name: "지원 범위", exact: true }).click();
+ await expect(page.getByRole("heading", { level: 1, name: "지원 범위" })).toBeVisible();
+ await page.setViewportSize({ width: 390, height: 844 });
+ await page.goto("/docs/modules");
+ await expect(page.getByRole("heading", { level: 1, name: "전체 모듈" })).toBeVisible();
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
+ await page.setViewportSize({ width: 1440, height: 1000 });
+ await page.getByRole("navigation", { name: "Site navigation" }).getByRole("button", { name: "시작하기" }).click();
+ await page.getByRole("navigation", { name: "Site navigation" }).getByRole("button", { name: "설계와 진행 상태" }).click();
+ await page.screenshot({ path: testInfo.outputPath("site-map.png") });
+});
diff --git a/site/tests/browser/site-shell.spec.ts b/site/tests/browser/site-shell.spec.ts
index e1ce8441e..19f9528e4 100644
--- a/site/tests/browser/site-shell.spec.ts
+++ b/site/tests/browser/site-shell.spec.ts
@@ -28,42 +28,25 @@ test("official overview exposes the product hierarchy", async ({ page }) => {
await expect.poll(() => image.evaluate((element) => (element as HTMLImageElement).naturalWidth > 0)).toBe(true);
}
expect(await page.getByRole("main").evaluate((element) => getComputedStyle(element).scrollSnapType)).toBe("y mandatory");
- await expect(navigation.getByRole("link", { name: "Why" })).toHaveCount(0);
+ await expect(navigation.getByRole("link", { name: "소개" })).toHaveCount(0);
await expect(navigation.getByRole("link", { name: "Replica" })).toHaveCount(0);
- await navigation.getByRole("button", { name: "Introduction" }).click();
- await expect(navigation.getByRole("group", { name: "Introduction" }).getByRole("link")).toHaveText([
- "Why",
- "Architecture",
- "How We Build",
+ await navigation.getByRole("button", { name: "시작하기" }).click();
+ await expect(navigation.getByRole("group", { name: "시작하기" }).getByRole("link")).toHaveText(["소개", "빠른 시작", "Architecture"]);
+ await navigation.getByRole("button", { name: "모듈" }).click();
+ await expect(navigation.getByRole("group", { name: "모듈" }).getByRole("link")).toHaveText([
+ "전체 모듈", "JSON Document", "Document Types", "Editing", "Collaboration", "Adapter", "Connector", "Affordance", "UI Primitives",
]);
- await navigation.getByRole("button", { name: "Foundation" }).click();
- await expect(navigation.getByRole("link", { name: "API · Markdown", exact: true })).toBeVisible();
- await expect(navigation.getByRole("group", { name: "Foundation" }).getByRole("link", { name: "Overview", exact: true }).first()).toBeVisible();
- await navigation.getByRole("button", { name: "Building Blocks" }).click();
- await expect(navigation.getByRole("link", { name: "API · Markdown React", exact: true })).toBeVisible();
- await navigation.getByRole("button", { name: "Hands" }).click();
- for (const label of ["Official Hands · TBD", "Order", "Object", "Tree", "Database", "Composer", "Mention", "API · Calendar"]) {
- await expect(navigation.getByRole("group", { name: "Hands" }).getByRole("link", {name: label, exact:true})).toBeVisible();
+ await navigation.getByRole("button", { name: "편집 조합 · Hands" }).click();
+ for (const label of ["지원 범위", "Order", "Object", "Tree", "Database", "Composer", "Mention", "API · Calendar"]) {
+ await expect(navigation.getByRole("group", { name: "편집 조합 · Hands" }).getByRole("link", {name: label, exact:true})).toBeVisible();
}
- await navigation.getByRole("button", { name: "Artifact" }).click();
- await expect(navigation.getByRole("group", { name: "Artifact" }).getByRole("link")).toHaveText([
- "Content Prototype · TBD",
- ]);
+ await expect(navigation.getByRole("group", { name: "편집 조합 · Hands" }).getByRole("link", {name: "Official Hands 목표"})).toHaveCount(0);
+ await navigation.getByRole("button", { name: "설계와 진행 상태" }).click();
+ await expect(navigation.getByRole("group", { name: "설계와 진행 상태" }).getByRole("link", {name: "Artifact · Prototype"})).toBeVisible();
await navigation.getByRole("button", { name: "Applications" }).click();
- await expect(navigation.getByRole("group", { name: "Applications" }).getByRole("link")).toHaveText([
- "Overview",
- "Bear",
- "Calendar",
- "AI Agent",
- ]);
- await expect(navigation.getByRole("link", { name: "JSON Document Protocol", exact: true })).toHaveAttribute("href", "/docs/api");
+ await expect(navigation.getByRole("group", { name: "Applications" }).getByRole("link")).toHaveText(["Overview", "Bear", "Calendar", "AI Agent"]);
expect(await navigation.getByRole("group").evaluateAll((nodes) => nodes.map((node) => node.getAttribute("aria-label")))).toEqual([
- "Introduction",
- "Foundation",
- "Building Blocks",
- "Hands",
- "Artifact",
- "Applications",
+ "시작하기", "모듈", "편집 조합 · Hands", "Applications", "설계와 진행 상태",
]);
await expect(navigation.getByRole("link", { name: "Extensions" })).toHaveCount(0);
expect(requests.some(isLegacyRequest)).toBe(false);
@@ -77,12 +60,12 @@ test("mobile navigation preserves the product groups without duplicating documen
await expect(page.locator("[data-home-scene]")).toHaveCount(5);
const siteNavigation = page.getByRole("navigation", { name: "Site navigation" });
- await expect(siteNavigation.getByRole("group", { name: "Introduction" })).toBeVisible();
- await expect(siteNavigation.getByRole("group", { name: "Foundation" })).toBeVisible();
+ await expect(siteNavigation.getByRole("group", { name: "시작하기" })).toBeVisible();
+ await expect(siteNavigation.getByRole("group", { name: "모듈" })).toBeVisible();
await expect(siteNavigation.getByRole("group", { name: "Core" })).toHaveCount(0);
- await expect(siteNavigation.getByRole("group", { name: "Hands" })).toBeVisible();
- await expect(siteNavigation.getByRole("group", { name: "Artifact" })).toBeVisible();
- await expect(siteNavigation.getByRole("group", { name: "Building Blocks" })).toBeVisible();
+ await expect(siteNavigation.getByRole("group", { name: "편집 조합 · Hands" })).toBeVisible();
+ await expect(siteNavigation.getByRole("group", { name: "설계와 진행 상태" })).toBeVisible();
+ await expect(siteNavigation.getByRole("group", { name: "모듈" })).toBeVisible();
await expect(siteNavigation.getByRole("group", { name: "Demos" })).toHaveCount(0);
await expect(siteNavigation.getByRole("group", { name: "Applications" })).toBeVisible();
@@ -121,8 +104,8 @@ test("Document Types publishes a TBD responsibility boundary", async ({ page })
await expect(page.getByRole("heading", { level: 1, name: "Document Types · TBD" })).toBeVisible();
await expect(page.getByRole("heading", { level: 2, name: "책임", exact: true })).toBeVisible();
await expect(page.getByRole("heading", { level: 2, name: "현재 소유자와 후보" })).toBeVisible();
- await expect(page.getByRole("navigation", { name: "Breadcrumb" }).getByRole("link", { name: "Foundation" })).toHaveAttribute("href", "/docs/foundation");
- await expect(page.getByRole("navigation", { name: "Breadcrumb" }).getByText("Overview · TBD")).toBeVisible();
+ await expect(page.getByRole("navigation", { name: "Breadcrumb" }).getByRole("link", { name: "모듈" })).toHaveAttribute("href", "/docs/modules");
+ await expect(page.getByRole("navigation", { name: "Breadcrumb" }).getByText("Document Types")).toBeVisible();
});
test("Calendar Document Type exposes its RC owner while unrelated candidates remain TBD", async ({ page }) => {
@@ -160,15 +143,14 @@ test("official docs routes render with route metadata in a real browser", async
await expect(page.getByRole("heading", { level: 3, name: "값을 다루는 하나의 계약" })).toHaveCount(0);
await expect(page.getByRole("navigation", { name: "Documentation pages" })).toHaveCount(0);
await expect(page.getByRole("navigation", { name: "On this page" })).toBeVisible();
- await expect(siteNavigation.getByRole("group", { name: "Introduction" }).getByRole("link", { name: "Why" })).toHaveAttribute("aria-current", "page");
- await siteNavigation.getByRole("button", { name: "Building Blocks" }).click();
- await siteNavigation.getByRole("group", { name: "Building Blocks" }).getByRole("link", { name: "Overview", exact: true }).nth(2).click();
+ await expect(siteNavigation.getByRole("group", { name: "시작하기" }).getByRole("link", { name: "소개" })).toHaveAttribute("aria-current", "page");
+ await siteNavigation.getByRole("button", { name: "모듈" }).click();
+ await siteNavigation.getByRole("group", { name: "모듈" }).getByRole("link", { name: "Connector", exact: true }).click();
await expect(page).toHaveTitle("Connector Docs - json-document");
await expect(page.getByRole("heading", { level: 1, name: "json-document Connectors" })).toBeVisible();
await expect(page.locator("[data-live-demo]")).toHaveCount(0);
- await siteNavigation.getByRole("button", { name: "Foundation" }).click();
- await siteNavigation.getByRole("link", { name: "JSON Document Protocol", exact: true }).click();
+ await siteNavigation.getByRole("link", { name: "JSON Document", exact: true }).click();
await expect(page).toHaveTitle("JSON Document Protocol - json-document");
await expect(page.getByRole("heading", { level: 1, name: "JSON Document Protocol" })).toBeVisible();
});
@@ -216,14 +198,14 @@ test("Connector pages declare the connection before the live demo", async ({ pag
test("Adapter and Connector menus expose contract docs while demos stay embedded", async ({ page }) => {
await page.goto("/docs/adapter-keyboard");
const navigation = page.getByRole("navigation", { name: "Site navigation" });
- await expect(navigation.getByRole("group", { name: "Building Blocks" }).getByRole("link", { name: "Keyboard" })).toBeVisible();
- await expect(navigation.getByRole("link", { name: "API · Markdown React", exact: true })).toBeVisible();
+ await expect(navigation.getByRole("group", { name: "모듈" }).getByRole("link", { name: "Keyboard" })).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: "Keyboard Adapter" })).toBeVisible();
await expect(page.locator("[data-live-demo]")).toHaveCount(1);
await page.goto("/docs/connector-zod-validate");
- await expect(navigation.getByRole("group", { name: "Building Blocks" }).getByRole("link", { name: "Zod", exact: true })).toBeVisible();
- await expect(navigation.getByRole("group", { name: "Building Blocks" }).getByRole("link", { name: "Validate" })).toBeVisible();
+ await expect(navigation.getByRole("link", { name: "API · Markdown React", exact: true })).toBeVisible();
+ await expect(navigation.getByRole("group", { name: "모듈" }).getByRole("link", { name: "Zod", exact: true })).toBeVisible();
+ await expect(navigation.getByRole("group", { name: "모듈" }).getByRole("link", { name: "Validate" })).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: "Zod Validate" })).toBeVisible();
await expect(page.locator("[data-live-demo]")).toHaveCount(1);
});
@@ -402,7 +384,7 @@ test("docs chrome groups with paper and type instead of rest-state borders", asy
expect(await navigation.getByRole("link", { name: "json-document" }).evaluate((element) => (
getComputedStyle(element).borderBottomWidth
))).toBe("0px");
- expect(await navigation.getByRole("button", { name: "Foundation" }).evaluate((element) => (
+ expect(await navigation.getByRole("button", { name: "모듈" }).evaluate((element) => (
getComputedStyle(element).borderLeftColor
))).toContain("222, 109, 85");
const currentCat = navigation.getByRole("link", { name: "Selection", exact: true }).locator("svg");
@@ -514,7 +496,7 @@ test("cat palette gives impact to interaction states and keeps code ink-led", as
expect((await titleInput.evaluate(controlSnapshot)).boxShadow).toContain("rgba(222, 109, 85, 0.25)");
const siteNavigation = page.getByRole("navigation", { name: "Site navigation" });
- const currentLink = siteNavigation.getByRole("group", { name: "Building Blocks" })
+ const currentLink = siteNavigation.getByRole("group", { name: "모듈" })
.getByRole("link", { name: "React Reference", exact: true });
expect(await currentLink.locator("svg").evaluate((element) => getComputedStyle(element).color)).toContain("222, 109, 85");
diff --git a/site/tests/unit/app-shell.test.tsx b/site/tests/unit/app-shell.test.tsx
index 5a7fa4925..00658ba2f 100644
--- a/site/tests/unit/app-shell.test.tsx
+++ b/site/tests/unit/app-shell.test.tsx
@@ -45,43 +45,29 @@ describe("official site shell", () => {
expect(nav.queryByRole("group", { name: "Start" })).toBeNull();
expect(nav.queryByRole("group", { name: "Core" })).toBeNull();
- expect(nav.queryByRole("link", { name: "Why" })).toBeNull();
+ expect(nav.queryByRole("link", { name: "소개" })).toBeNull();
expect(nav.queryByRole("link", { name: "Replica" })).toBeNull();
expect(screen.queryByRole("navigation", { name: "Dependency map" })).toBeNull();
- await user.click(nav.getByRole("button", { name: "Introduction" }));
- expect(groupLinks(nav, "Introduction")).toEqual([
- "Why",
- "Architecture",
- "How We Build",
+ await user.click(nav.getByRole("button", { name: "시작하기" }));
+ expect(groupLinks(nav, "시작하기")).toEqual(["소개", "빠른 시작", "Architecture"]);
+ await user.click(nav.getByRole("button", { name: "모듈" }));
+ expect(groupLinks(nav, "모듈")).toEqual([
+ "전체 모듈", "JSON Document", "Document Types", "Editing", "Collaboration", "Adapter", "Connector", "Affordance", "UI Primitives",
]);
- await user.click(nav.getByRole("button", { name: "Foundation" }));
- expect(groupLinks(nav, "Foundation")).toEqual(expect.arrayContaining([
- "Overview",
- "Replica",
- "Intent guide",
+ await user.click(nav.getByRole("button", { name: "편집 조합 · Hands" }));
+ expect(groupLinks(nav, "편집 조합 · Hands")).toEqual(expect.arrayContaining([
+ "장르별 예제", "지원 범위", "Order", "Object", "Tree", "Database", "Composer", "Mention",
]));
- await user.click(nav.getByRole("button", { name: "Hands" }));
- expect(groupLinks(nav, "Hands")).toEqual(expect.arrayContaining([
- "Overview", "Official Hands · TBD",
- "Order",
- "Object",
- "Tree",
- "Database",
- "Composer",
- "Mention",
+ expect(groupLinks(nav, "편집 조합 · Hands")).not.toContain("Official Hands 목표");
+ await user.click(nav.getByRole("button", { name: "설계와 진행 상태" }));
+ expect(groupLinks(nav, "설계와 진행 상태")).toEqual(expect.arrayContaining([
+ "설계 현황", "Official Hands 목표", "소유권 감사", "Artifact · Prototype", "개발 원칙",
]));
- await user.click(nav.getByRole("button", { name: "Artifact" }));
- expect(groupLinks(nav, "Artifact")).toEqual(["Content Prototype · TBD"]);
await user.click(nav.getByRole("button", { name: "Applications" }));
expect(groupLinks(nav, "Applications")).toEqual(["Overview", "Bear", "Calendar", "AI Agent"]);
- expect(nav.getByRole("link", { name: "JSON Document Protocol" }).getAttribute("href")).toBe("/docs/api");
+ expect(nav.getByRole("link", { name: "JSON Document", exact: true }).getAttribute("href")).toBe("/docs/api");
expect(nav.getAllByRole("group").map((group) => group.getAttribute("aria-label"))).toEqual([
- "Introduction",
- "Foundation",
- "Building Blocks",
- "Hands",
- "Artifact",
- "Applications",
+ "시작하기", "모듈", "편집 조합 · Hands", "Applications", "설계와 진행 상태",
]);
expect(nav.queryByRole("link", { name: "Extensions" })).toBeNull();
@@ -95,13 +81,13 @@ describe("official site shell", () => {
expect(catalog.getByRole("link", {name:"Rich Text React",exact:true}).getAttribute("href")).toBe("/docs/api/rich-text-react");
expect(catalog.getByRole("link", {name:"Validate commits",exact:true}).getAttribute("href")).toBe("/connectors/zod/validate");
- await user.click(within(nav.getByRole("group", { name: "Hands" })).getByRole("link", { name: "Database", exact: true }));
+ await user.click(within(nav.getByRole("group", { name: "편집 조합 · Hands" })).getByRole("link", { name: "Database", exact: true }));
const databaseCrumb = within(await screen.findByRole("navigation", { name: "Breadcrumb" }));
expect(databaseCrumb.getByRole("link", { name: "Overview" }).getAttribute("href")).toBe("/");
- expect(databaseCrumb.getByRole("link", { name: "Hands" }).getAttribute("href")).toBe("/editors");
+ expect(databaseCrumb.getByRole("link", { name: "편집 조합 · Hands" }).getAttribute("href")).toBe("/editors");
expect(databaseCrumb.getByText("Database")).toBeTruthy();
- await user.click(within(nav.getByRole("group", { name: "Building Blocks" })).getAllByRole("link", { name: "Overview", exact: true })[2]!);
+ await user.click(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "Connector", exact: true }));
expect(await screen.findByRole(
"heading",
{ level: 1, name: "json-document Connectors" },
@@ -118,8 +104,8 @@ describe("official site shell", () => {
expect(screen.getByText(/Calendar와 Object는 공개 소유자와 소비 경계를 확정했고/)).toBeTruthy();
expect(screen.getByRole("heading", { level: 2, name: "현재 소유자와 후보" })).toBeTruthy();
const breadcrumb = within(screen.getByRole("navigation", { name: "Breadcrumb" }));
- expect(breadcrumb.getByRole("link", { name: "Foundation" }).getAttribute("href")).toBe("/docs/foundation");
- expect(breadcrumb.getByText("Overview · TBD")).toBeTruthy();
+ expect(breadcrumb.getByRole("link", { name: "모듈" }).getAttribute("href")).toBe("/docs/modules");
+ expect(breadcrumb.getByText("Document Types")).toBeTruthy();
});
test("renders the Calendar owner API reference and package-owned contract", async () => {
@@ -137,8 +123,8 @@ describe("official site shell", () => {
const brand = screen.getByRole("link", { name: "json-document" });
const siteNav = screen.getByRole("navigation", { name: "Site navigation" });
- await user.click(nav.getByRole("button", { name: "Introduction" }));
- await user.click(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Why" }));
+ await user.click(nav.getByRole("button", { name: "시작하기" }));
+ await user.click(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "소개" }));
await waitFor(() => expect(document.documentElement.lang).toBe("ko"));
const frame = await waitFor(() => {
const node = document.querySelector("[data-page-frame]");
@@ -146,10 +132,10 @@ describe("official site shell", () => {
return node;
});
- await user.click(nav.getByRole("button", { name: "Foundation" }));
- await user.click(nav.getByRole("link", { name: "JSON Document Protocol" }));
+ await user.click(nav.getByRole("button", { name: "모듈" }));
+ await user.click(nav.getByRole("link", { name: "JSON Document" }));
const crumb = within(await screen.findByRole("navigation", { name: "Breadcrumb" }));
- await waitFor(() => expect(crumb.getByText("JSON Document Protocol")).toBeTruthy());
+ await waitFor(() => expect(crumb.getByText("JSON Document")).toBeTruthy());
expect(screen.getByRole("link", { name: "json-document" })).toBe(brand);
expect(screen.getByRole("navigation", { name: "Site navigation" })).toBe(siteNav);
expect(document.querySelector("[data-page-frame]")).toBe(frame);
@@ -157,8 +143,8 @@ describe("official site shell", () => {
expect(header?.contains(screen.getByRole("navigation", { name: "Breadcrumb" }))).toBe(true);
expect(header?.querySelector("h1")).toBeTruthy();
- await user.click(nav.getByRole("button", { name: "Hands" }));
- await user.click(within(nav.getByRole("group", { name: "Hands" })).getByRole("link", { name: "Overview", exact: true }));
+ await user.click(nav.getByRole("button", { name: "편집 조합 · Hands" }));
+ await user.click(within(nav.getByRole("group", { name: "편집 조합 · Hands" })).getByRole("link", { name: "장르별 예제", exact: true }));
await waitFor(() => expect(document.documentElement.lang).toBe("ko"));
});
});
diff --git a/site/tests/unit/breadcrumb.test.ts b/site/tests/unit/breadcrumb.test.ts
index 7de21effd..61397a756 100644
--- a/site/tests/unit/breadcrumb.test.ts
+++ b/site/tests/unit/breadcrumb.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, test } from "vitest";
-import siteRoutes from "../../site-routes.json";
+import { siteRoutes } from "../../route-registry.mjs";
import { breadcrumbTrail, rootNavRoutes, visibleNavChildren } from "../../src/app/breadcrumb";
import type { SiteRoute } from "../../src/app/router";
@@ -13,21 +13,21 @@ function trail(path: string) {
describe("breadcrumbTrail", () => {
test("places owner groups below the new IA sections", () => {
- expect(trail("/docs")).toEqual(["Overview:/", "Introduction:/docs"]);
- expect(trail("/docs/architecture")).toEqual(["Overview:/", "Introduction:/docs", "Architecture:/docs/architecture"]);
- expect(trail("/docs/foundation")).toEqual(["Overview:/", "Foundation:/docs/foundation"]);
+ expect(trail("/docs")).toEqual(["Overview:/", "시작하기:/docs"]);
+ expect(trail("/docs/architecture")).toEqual(["Overview:/", "시작하기:/docs", "Architecture:/docs/architecture"]);
+ expect(trail("/docs/modules")).toEqual(["Overview:/", "모듈:/docs/modules"]);
expect(trail("/docs/collaboration/replica")).toEqual([
- "Overview:/", "Foundation:/docs/foundation", "Collaboration:/docs/collaboration", "Replica:/docs/collaboration/replica",
+ "Overview:/", "모듈:/docs/modules", "Collaboration:/docs/collaboration", "Replica:/docs/collaboration/replica",
]);
expect(trail("/docs/selection")).toEqual([
- "Overview:/", "Foundation:/docs/foundation", "Editing:/docs/editing", "Selection:/docs/selection",
+ "Overview:/", "모듈:/docs/modules", "Editing:/docs/editing", "Selection:/docs/selection",
]);
expect(trail("/docs/connector-zod-validate")).toEqual([
- "Overview:/", "Building Blocks:/docs/building-blocks", "Connector:/docs/connectors",
+ "Overview:/", "모듈:/docs/modules", "Connector:/docs/connectors",
"Zod:/docs/connector-zod", "Validate:/docs/connector-zod-validate",
]);
- expect(trail("/docs/order")).toEqual(["Overview:/", "Hands:/editors", "Order:/docs/order"]);
- expect(trail("/viewer")).toEqual(["Overview:/", "Artifact:/viewer"]);
+ expect(trail("/docs/order")).toEqual(["Overview:/", "편집 조합 · Hands:/editors", "Order:/docs/order"]);
+ expect(trail("/viewer")).toEqual(["Overview:/", "설계와 진행 상태:/docs/design", "Artifact · Prototype:/viewer"]);
expect(trail("/applications/calendar")).toEqual([
"Overview:/", "Applications:/applications", "Calendar:/applications/calendar",
]);
diff --git a/site/tests/unit/docs-route.test.tsx b/site/tests/unit/docs-route.test.tsx
index 5210d0156..a9c26d9ce 100644
--- a/site/tests/unit/docs-route.test.tsx
+++ b/site/tests/unit/docs-route.test.tsx
@@ -46,40 +46,40 @@ describe("documentation routes", () => {
const user = userEvent.setup();
const nav = within(await screen.findByRole("navigation", { name: "Site navigation" }));
- await user.click(nav.getByRole("button", { name: "Introduction" }));
- await user.click(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Why" }));
+ await user.click(nav.getByRole("button", { name: "시작하기" }));
+ await user.click(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "소개" }));
await waitFor(() => expect(document.title).toBe("json-document Docs - json-document"));
expect(document.head.querySelector('link[rel="canonical"]')?.getAttribute("href")).toBe("https://developer-1px.github.io/json-document/docs");
expect(await screen.findByRole("heading", { level: 1 }, { timeout: 10000 })).toBeTruthy();
- expect(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Why" }).getAttribute("aria-current")).toBe("page");
+ expect(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "소개" }).getAttribute("aria-current")).toBe("page");
- await user.click(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Architecture" }));
+ await user.click(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "Architecture" }));
await waitFor(() => expect(document.title).toBe("Architecture - json-document"));
expect(await screen.findByRole("heading", { level: 1, name: "Architecture" }, { timeout: 10000 })).toBeTruthy();
expect(screen.queryByRole("navigation", { name: "Documentation pages" })).toBeNull();
- await user.click(nav.getByRole("button", { name: "Building Blocks" }));
- await user.click(within(nav.getByRole("group", { name: "Building Blocks" })).getAllByRole("link", { name: "Overview", exact: true })[2]!);
+ await user.click(nav.getByRole("button", { name: "모듈" }));
+ await user.click(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "Connector", exact: true }));
await waitFor(() => expect(document.title).toBe("Connector Docs - json-document"), { timeout: 10000 });
expect(await screen.findByRole("heading", { level: 1, name: "json-document Connectors" }, { timeout: 10000 })).toBeTruthy();
- await user.click(within(nav.getByRole("group", { name: "Building Blocks" })).getByRole("link", { name: "React Reference" }));
+ await user.click(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "React Reference" }));
expect(await screen.findByRole("heading", { level: 1, name: "React Connector" }, { timeout: 10000 })).toBeTruthy();
expect(screen.getByRole("heading", { level: 2, name: "useEditingObservation" })).toBeTruthy();
- expect(within(nav.getByRole("group", { name: "Building Blocks" })).getByRole("link", { name: "Editing guide" })).toBeTruthy();
+ expect(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "Editing guide" })).toBeTruthy();
- await user.click(nav.getByRole("button", { name: "Foundation" }));
- await user.click(within(nav.getByRole("group", { name: "Foundation" })).getByRole("link", { name: "Topology" }));
+ await user.click(nav.getByRole("link", { name: "Editing", exact: true }));
+ await user.click(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "Topology" }));
await waitFor(() => expect(document.title).toBe("Topology - json-document"));
expect(await screen.findByRole("heading", { level: 1, name: "Topology" }, { timeout: 10000 })).toBeTruthy();
- await user.click(nav.getByRole("link", { name: "JSON Document Protocol" }));
+ await user.click(nav.getByRole("link", { name: "JSON Document" }));
await waitFor(() => expect(document.title).toBe("JSON Document Protocol - json-document"));
expect(document.head.querySelector('meta[name="description"]')?.getAttribute("content")).toBe("로컬·협업 구현이 공유하는 여섯 member의 JSONDocument 계약과 JSON 표준 연산 API를 설명합니다.");
expect(await screen.findByRole("heading", { level: 1, name: "JSON Document Protocol" }, { timeout: 10000 })).toBeTruthy();
- expect(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Why" }).getAttribute("aria-current")).toBeNull();
- expect(nav.getByRole("link", { name: "JSON Document Protocol" }).getAttribute("aria-current")).toBe("page");
+ expect(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "소개" }).getAttribute("aria-current")).toBeNull();
+ expect(nav.getByRole("link", { name: "JSON Document" }).getAttribute("aria-current")).toBe("page");
const mobileSections = within(screen.getByRole("navigation", { name: "Documentation sections" }));
expect(mobileSections.getByRole("link", { name: "작업별 진입점" }).getAttribute("href")).toBe("#작업별-진입점");
});
@@ -91,19 +91,19 @@ describe("documentation routes", () => {
await waitFor(() => expect(document.title).toBe("json-document Docs - json-document"));
expect(await screen.findByRole("heading", { level: 1 }, { timeout: 10000 })).toBeTruthy();
- expect(within(nav.getByRole("group", { name: "Introduction" })).getByRole("link", { name: "Why" }).getAttribute("aria-current")).toBe("page");
+ expect(within(nav.getByRole("group", { name: "시작하기" })).getByRole("link", { name: "소개" }).getAttribute("aria-current")).toBe("page");
window.history.pushState(null, "", "/docs/topology/");
window.dispatchEvent(new Event("popstate"));
await waitFor(() => expect(document.title).toBe("Topology - json-document"));
expect(await screen.findByRole("heading", { level: 1, name: "Topology" })).toBeTruthy();
- expect(within(nav.getByRole("group", { name: "Foundation" })).getByRole("link", { name: "Topology" }).getAttribute("aria-current")).toBe("page");
+ expect(within(nav.getByRole("group", { name: "모듈" })).getByRole("link", { name: "Topology" }).getAttribute("aria-current")).toBe("page");
window.history.pushState(null, "", "/docs/api/");
window.dispatchEvent(new Event("popstate"));
await waitFor(() => expect(document.title).toBe("JSON Document Protocol - json-document"));
expect(await screen.findByRole("heading", { level: 1, name: "JSON Document Protocol" })).toBeTruthy();
- expect(nav.getByRole("link", { name: "JSON Document Protocol" }).getAttribute("aria-current")).toBe("page");
+ expect(nav.getByRole("link", { name: "JSON Document" }).getAttribute("aria-current")).toBe("page");
});
test("exposes the Interaction Handles ecosystem document", async () => {
diff --git a/site/tests/unit/documentation-page.test.tsx b/site/tests/unit/documentation-page.test.tsx
index 01d96957b..44b71611e 100644
--- a/site/tests/unit/documentation-page.test.tsx
+++ b/site/tests/unit/documentation-page.test.tsx
@@ -60,8 +60,8 @@ describe("canonical documentation projection", () => {
["docs/public/ui-primitives.md", "animation.md", "/docs/animation"],
["docs/public/connectors.md", "connector-a2ui.md", "/docs/connector-a2ui"],
["docs/public/hands.md", "official-hands.md", "/docs/official-hands"],
- ["docs/public/connector-a2ui.md", "../api-reference/a2ui.md", "/docs/api/a2ui"],
- ["docs/api-reference/editing.md", "../public/editing.md?mode=read#입력에서-관찰까지", "/docs/editing?mode=read#입력에서-관찰까지"],
+ ["docs/public/connector-a2ui.md", "../../packages/json-document-a2ui/docs/api-reference.md", "/docs/api/a2ui"],
+ ["packages/json-document-editing/docs/api-reference.md", "../../../docs/public/editing.md?mode=read#입력에서-관찰까지", "/docs/editing?mode=read#입력에서-관찰까지"],
["docs/public/connector-a2ui.md", "../../packages/json-document-a2ui/README.md", "https://github.com/developer-1px/json-document/blob/main/packages/json-document-a2ui/README.md"],
["docs/public/api.md", "#commit", "#commit"],
["docs/public/api.md", "https://example.com/api.md", "https://example.com/api.md"],
diff --git a/site/tests/unit/public-document-contract.test.ts b/site/tests/unit/public-document-contract.test.ts
index ded713e74..1fbb714f6 100644
--- a/site/tests/unit/public-document-contract.test.ts
+++ b/site/tests/unit/public-document-contract.test.ts
@@ -21,7 +21,7 @@ describe("public documentation contract", () => {
packageName: "@interactive-os/json-document-collaboration/history",
entrypoint: "packages/json-document-collaboration/src/history-index.ts",
});
- const reference = readFileSync(new URL("../../../docs/api-reference/collaboration.md", import.meta.url), "utf8");
+ const reference = readFileSync(new URL("../../../packages/json-document-collaboration/docs/api-reference.md", import.meta.url), "utf8");
const history = reference.split("## `@interactive-os/json-document-collaboration/history`")[1]?.split("## `@interactive-os/json-document-collaboration/editing`")[0];
expect(history).toContain("### `HistoryResult`");
expect(history).toContain("readonly change: JSONAppliedChange | null");
diff --git a/site/tests/unit/site-map.test.ts b/site/tests/unit/site-map.test.ts
new file mode 100644
index 000000000..bb440b552
--- /dev/null
+++ b/site/tests/unit/site-map.test.ts
@@ -0,0 +1,48 @@
+import { describe, expect, test } from "vitest";
+import { pageDescriptor, pageDescriptors } from "../../src/app/page-descriptors";
+import { breadcrumbTrail, routeSection, visibleNavChildren } from "../../src/app/breadcrumb";
+import { siteSections, groupLandings } from "../../src/app/site-layers";
+import { documentationMap } from "../../src/app/documentation-map";
+
+describe("site map separates available contracts from design status", () => {
+ test("provides the agreed five entrances and eight module responsibilities", () => {
+ expect(siteSections.map(section => section.label)).toEqual([
+ "시작하기", "모듈", "편집 조합 · Hands", "Applications", "설계와 진행 상태",
+ ]);
+ const modules = siteSections.find(section => section.id === "modules")!;
+ expect(modules.groups).toEqual(["JSON Document", "Document Types", "Editing", "Collaboration", "Adapter", "Connector", "Affordance", "UI Primitives"]);
+ for (const group of modules.groups) {
+ const landing = pageDescriptor(groupLandings[group].path);
+ expect(landing.sidebar).not.toBe(false);
+ expect(documentationMap(pageDescriptor("/docs/modules"))).toContain(`](${landing.path})`);
+ }
+ });
+
+ test("keeps goals and all ownership candidates in design while public APIs keep their roles", () => {
+ for (const path of ["/viewer", "/docs/official-hands", "/docs/ownership", ...pageDescriptors.filter(page => page.path.startsWith("/docs/document-types/")).map(page => page.path)]) {
+ expect(routeSection(pageDescriptor(path), pageDescriptors)?.id).toBe("design");
+ }
+ expect(routeSection(pageDescriptor("/docs/api/calendar-document"), pageDescriptors)?.id).toBe("modules");
+ expect(routeSection(pageDescriptor("/docs/hands-support"), pageDescriptors)?.id).toBe("hands");
+ expect(documentationMap(pageDescriptor("/docs/ownership"))).toContain("소유권 확정 · RC");
+ expect(breadcrumbTrail(pageDescriptor("/docs/document-types/tree"), pageDescriptors).map(crumb => crumb.path)).toEqual([
+ "/", "/docs/design", "/docs/ownership", "/docs/document-types/tree",
+ ]);
+ });
+
+ test("each visible page has a known section and no hidden or cyclic navigation ancestors", () => {
+ for (const page of pageDescriptors.filter(page => page.path !== "/" && page.sidebar !== false)) {
+ expect(routeSection(page, pageDescriptors), page.path).toBeDefined();
+ const seen = new Set([page.path]);
+ let child = page;
+ while (child.parentPath) {
+ const parent = pageDescriptor(child.parentPath);
+ expect(seen.has(parent.path), page.path).toBe(false);
+ expect(parent.sidebar, page.path).not.toBe(false);
+ expect(visibleNavChildren(parent.path, page.path, pageDescriptors).some(route => route.path === child.path), page.path).toBe(true);
+ seen.add(parent.path);
+ child = parent;
+ }
+ }
+ });
+});
diff --git a/site/vite.config.ts b/site/vite.config.ts
index 442d7172c..b2603c3cf 100644
--- a/site/vite.config.ts
+++ b/site/vite.config.ts
@@ -7,7 +7,7 @@ import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { jsonDocumentSourceAliases } from "./config/json-document-source-aliases.ts";
import { codexAppServer } from "./config/codex-app-server.ts";
-import siteRoutes from "./site-routes.json";
+import { siteRoutes } from "./route-registry.mjs";
function rootLlmsTxt(): Plugin {
const path = fileURLToPath(new URL("../docs/public/llms.txt", import.meta.url));
diff --git a/standards/editing-session.md b/standards/editing-session.md
index 7a37568ad..6e1a3de27 100644
--- a/standards/editing-session.md
+++ b/standards/editing-session.md
@@ -152,7 +152,7 @@ DOM lifecycle의 브라우저 증거도 해당 정본에서 관리한다.
- [Editing package](../packages/json-document-editing/README.md)와
[public entrypoint](../packages/json-document-editing/src/index.ts)
-- [Editing API reference](../docs/api-reference/editing.md)와
+- [Editing API reference](../packages/json-document-editing/docs/api-reference.md)와
[History 사용법](../docs/public/history.md)
- [Editing Usage](../site/src/routes/editing-demos/HistoryDemoRoute.tsx)와
[source registration](../site/src/shared/demo-workbench/demo-sources.ts)