diff --git a/README.md b/README.md index 7caab997..265ee1d1 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) | diff --git a/audits/README.md b/audits/README.md index fbd4d729..a759c92b 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 38af2d65..d3086d36 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,31 +25,22 @@ docs 복제한 탐색 정본이 아니다. ```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은 같은 diff --git a/docs/public/architecture.md b/docs/public/architecture.md index 07951c7e..55802720 100644 --- a/docs/public/architecture.md +++ b/docs/public/architecture.md @@ -6,13 +6,14 @@ 사이트 등록에서 문서 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 @@ -112,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)에 있습니다. diff --git a/docs/public/design.md b/docs/public/design.md new file mode 100644 index 00000000..e2d7ce13 --- /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/hands-support.md b/docs/public/hands-support.md new file mode 100644 index 00000000..93e41c37 --- /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 7ffbdd97..3de962f6 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 b2625347..b667df70 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 3e747bf9..b2fccc7e 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 00000000..092daaf6 --- /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 25baa871..b62ee47e 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 00000000..35244aa0 --- /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 00000000..cdb34995 --- /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/packages/json-document-calendar/docs/api.md b/packages/json-document-calendar/docs/api.md new file mode 100644 index 00000000..5a5e23cc --- /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/site/scripts/check-document-type-audits.mjs b/site/scripts/check-document-type-audits.mjs index 9909013c..bbfb1e4a 100644 --- a/site/scripts/check-document-type-audits.mjs +++ b/site/scripts/check-document-type-audits.mjs @@ -6,7 +6,7 @@ const root = new URL("../..", import.meta.url).pathname; const ledger = JSON.parse(readFileSync(join(root, "audits/document-types.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/route-checks.mjs b/site/scripts/route-checks.mjs index 3e2fb636..f662117d 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", diff --git a/site/site-routes.json b/site/site-routes.json index e2ca6096..e638515f 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", @@ -118,7 +128,7 @@ }, { "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 책임과 아직 확정하지 않은 후보를 정리합니다.", @@ -132,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", @@ -140,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", @@ -148,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", @@ -156,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", @@ -164,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", @@ -172,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", @@ -180,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", @@ -188,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", @@ -196,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를 설명합니다.", @@ -219,7 +238,8 @@ "usagePaths": [ "/demo" ] - } + }, + "parentPath": "/docs/api" }, { "path": "/docs/api/selection", @@ -232,7 +252,8 @@ "usagePaths": [ "/demo/selection" ] - } + }, + "parentPath": "/docs/editing" }, { "path": "/docs/api/editing", @@ -252,7 +273,8 @@ "/demo/clipboard", "/demo/markdown-caret" ] - } + }, + "parentPath": "/docs/editing" }, { "path": "/docs/api/rich-text", @@ -265,7 +287,8 @@ "usagePaths": [ "/editing/rich-text" ] - } + }, + "parentPath": "/docs/editing" }, { "path": "/docs/api/file-intake", @@ -279,7 +302,8 @@ "/demo/composer", "/demo/annotation" ] - } + }, + "parentPath": "/docs/document-types" }, { "path": "/docs/api/rich-text-mention", @@ -318,7 +342,8 @@ "usagePaths": [ "/demo/composer" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/rich-text-mention-react", @@ -331,7 +356,8 @@ "usagePaths": [ "/demo/composer" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/composer", @@ -357,7 +383,8 @@ "usagePaths": [ "/demo/composer" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/web", @@ -377,7 +404,8 @@ "/adapters/clipboard", "/demo/viewport" ] - } + }, + "parentPath": "/docs/adapters" }, { "path": "/docs/api/contenteditable", @@ -394,7 +422,8 @@ "/adapters/contenteditable", "/demo/markdown-caret" ] - } + }, + "parentPath": "/docs/adapters" }, { "path": "/docs/api/rich-text-web", @@ -407,7 +436,8 @@ "usagePaths": [ "/editing/rich-text" ] - } + }, + "parentPath": "/docs/adapters" }, { "path": "/docs/api/react", @@ -420,7 +450,8 @@ "usagePaths": [ "/connectors/react" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/react-hook-form", @@ -433,7 +464,8 @@ "usagePaths": [ "/connectors/react-hook-form" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/ajv", @@ -446,7 +478,8 @@ "usagePaths": [ "/connectors/ajv" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/a2ui", @@ -459,7 +492,8 @@ "usagePaths": [ "/connectors/a2ui" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/connector-a2ui", @@ -468,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", @@ -482,7 +517,8 @@ "/connectors/zod", "/connectors/zod/validate" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/tanstack-table", @@ -495,7 +531,8 @@ "usagePaths": [ "/connectors/tanstack-table" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/rich-text-react", @@ -508,7 +545,8 @@ "usagePaths": [ "/editing/rich-text" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/api/affordance", @@ -526,7 +564,8 @@ "/affordances/handles", "/demo/viewport" ] - } + }, + "parentPath": "/docs/affordance" }, { "path": "/docs/api/ui-primitives-react", @@ -542,7 +581,8 @@ "usagePaths": [ "/demo/ui-primitives" ] - } + }, + "parentPath": "/docs/ui-primitives" }, { "path": "/docs/api/animation-react", @@ -555,7 +595,8 @@ "usagePaths": [ "/demo/animation" ] - } + }, + "parentPath": "/docs/ui-primitives" }, { "path": "/docs/api/markdown", @@ -571,7 +612,8 @@ "usagePaths": [ "/demo/markdown-caret" ] - } + }, + "parentPath": "/docs/document-types" }, { "path": "/docs/api/markdown-web", @@ -587,7 +629,8 @@ "usagePaths": [ "/demo/markdown-caret" ] - } + }, + "parentPath": "/docs/adapters" }, { "path": "/demo/markdown-caret", @@ -596,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", @@ -613,7 +657,8 @@ "/demo/markdown-caret", "/demo/markdown" ] - } + }, + "parentPath": "/docs/connectors" }, { "path": "/demo/markdown", @@ -622,7 +667,8 @@ "heading": "Streaming Markdown", "description": "불완전한 Markdown 스트림과 커스텀 component renderer를 정본 패키지로 검증합니다.", "language": "ko", - "navigationGroup": "Connector" + "navigationGroup": "Connector", + "parentPath": "/docs/connectors" }, { "path": "/demo/ui-primitives", @@ -674,7 +720,8 @@ "usagePaths": [ "/demo/canvas" ] - } + }, + "parentPath": "/docs/document-types" }, { "path": "/docs/api/canvas", @@ -706,7 +753,8 @@ "usagePaths": [ "/editors" ] - } + }, + "parentPath": "/docs/document-types" }, { "path": "/docs/api/calendar", @@ -720,7 +768,10 @@ "/editors", "/demo/date-controls" ] - } + }, + "documentIncludes": [ + "packages/json-document-calendar/docs/api.md" + ] }, { "path": "/docs/api/collaboration", @@ -733,7 +784,8 @@ "usagePaths": [ "/docs/collaboration" ] - } + }, + "parentPath": "/docs/collaboration" }, { "path": "/docs/api/contenteditable-collaboration", @@ -746,11 +798,12 @@ "usagePaths": [ "/docs/collaboration" ] - } + }, + "parentPath": "/docs/adapters" }, { "path": "/docs/collaboration", - "label": "Overview", + "label": "Collaboration", "title": "Collaboration - json-document", "heading": "Collaboration", "description": "같은 JSON Document 계약을 여러 참여자의 인과 변경으로 수렴시킵니다.", @@ -766,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", @@ -776,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", @@ -786,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", @@ -796,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", @@ -816,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", @@ -826,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", @@ -837,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", @@ -857,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", @@ -877,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", @@ -897,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", @@ -915,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 손을 고릅니다.", @@ -927,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", @@ -1072,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", @@ -1145,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 계약을 설명합니다.", @@ -1165,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", @@ -1175,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", @@ -1185,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", @@ -1195,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", @@ -1205,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", @@ -1215,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", @@ -1223,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", @@ -1235,7 +1345,8 @@ "integration": { "kind": "adapter", "packageName": "@interactive-os/json-document-web" - } + }, + "parentPath": "/docs/adapters" }, { "path": "/adapters/clipboard", @@ -1247,7 +1358,8 @@ "integration": { "kind": "adapter", "packageName": "@interactive-os/json-document-web" - } + }, + "parentPath": "/docs/adapters" }, { "path": "/adapters/contenteditable", @@ -1259,7 +1371,8 @@ "integration": { "kind": "adapter", "packageName": "@interactive-os/json-document-contenteditable" - } + }, + "parentPath": "/docs/adapters" }, { "path": "/adapters/virtual-selection", @@ -1271,7 +1384,8 @@ "integration": { "kind": "adapter", "packageName": "@interactive-os/json-document-web" - } + }, + "parentPath": "/docs/adapters" }, { "path": "/connectors", @@ -1279,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 연결 계약을 설명합니다.", @@ -1299,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", @@ -1321,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", @@ -1331,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", @@ -1341,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", @@ -1361,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", @@ -1373,7 +1493,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-react" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/connectors/react-hook-form", @@ -1385,7 +1506,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-react-hook-form" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/connectors/ajv", @@ -1397,7 +1519,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-ajv" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/connectors/a2ui", @@ -1409,7 +1532,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-a2ui" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/connectors/zod", @@ -1421,7 +1545,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-zod" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/connectors/zod/validate", @@ -1429,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", @@ -1441,7 +1567,8 @@ "integration": { "kind": "connector", "packageName": "@interactive-os/json-document-tanstack-table" - } + }, + "parentPath": "/docs/connectors" }, { "path": "/docs/ui-primitives", @@ -1453,7 +1580,6 @@ "navigationGroup": "UI Primitives", "relatedDemoPath": "/demo/ui-primitives", "relatedDemoLabel": "Design system", - "sidebar": false, "documentSource": "docs/public/ui-primitives.md" }, { @@ -1464,7 +1590,6 @@ "description": "제품이 json-document를 만지는 최전선의 키보드·마우스·커서 손을 API와 사용법으로 설명합니다.", "language": "ko", "navigationGroup": "Affordance", - "sidebar": false, "documentSource": "docs/public/affordance.md" }, { @@ -1844,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 6f866c6a..7457ceeb 100644 --- a/site/src/app/documentation-map.ts +++ b/site/src/app/documentation-map.ts @@ -40,12 +40,15 @@ 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 { diff --git a/site/src/app/navigation-layer-icon.tsx b/site/src/app/navigation-layer-icon.tsx index 0c2c3129..0357e2c9 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 dcd6f1eb..a8493485 100644 --- a/site/src/app/page-descriptors.ts +++ b/site/src/app/page-descriptors.ts @@ -2,6 +2,7 @@ import { siteRoutes } from "../../route-registry.mjs"; export { legacyPageRedirects } from "./legacy-page-redirects"; export type SiteNavigationGroup = + | "Design" | "Introduction" | "JSON Document" | "Document Types" diff --git a/site/src/app/routeTree.gen.ts b/site/src/app/routeTree.gen.ts index c0188329..6fc17c11 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 f4363238..88a6f9a1 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/docs/design.tsx b/site/src/app/routes/_page/docs/design.tsx new file mode 100644 index 00000000..1e030f0b --- /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 00000000..dc45d672 --- /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 00000000..25450ca6 --- /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 00000000..bf941baf --- /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 00000000..dfe72815 --- /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 24c7e0ef..d9d6b99e 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 = { + 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 7fcafcff..29f99993 100644 --- a/site/src/routes/docs/doc-pages.ts +++ b/site/src/routes/docs/doc-pages.ts @@ -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 078c599c..ef3d969e 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/tests/browser/site-map.spec.ts b/site/tests/browser/site-map.spec.ts new file mode 100644 index 00000000..5cd464ca --- /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 e1ce8441..19f9528e 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 5a7fa492..00658ba2 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 4dab2e6c..61397a75 100644 --- a/site/tests/unit/breadcrumb.test.ts +++ b/site/tests/unit/breadcrumb.test.ts @@ -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 5210d015..a9c26d9c 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/site-map.test.ts b/site/tests/unit/site-map.test.ts new file mode 100644 index 00000000..bb440b55 --- /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; + } + } + }); +});