diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d14ff567..bdd4bb7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,9 @@ jobs: - name: Install dependencies if: steps.scope.outputs.package_any == 'true' run: npm ci --no-audit --no-fund - - name: Verify package and official extensions + - name: Verify v2 package if: steps.scope.outputs.package_full == 'true' - run: npm run typecheck && npm test && npm run build && npm run smoke:package && npm run extensions:verify && npm run docs:evaluate + run: npm run archive:check && npm run verify -w @interactive-os/json-document && npm run standard:check && npm run docs:evaluate - name: Verify package documentation if: steps.scope.outputs.package_full != 'true' && (steps.scope.outputs.package_docs == 'true' || steps.scope.outputs.package_smoke == 'true') run: | @@ -48,33 +48,18 @@ jobs: run: npm pack -w @interactive-os/json-document --dry-run lab-extensions: - name: Lab extensions + name: Archive isolation runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - with: - fetch-depth: 0 - uses: actions/setup-node@v6 with: node-version: "22" - - name: Detect changed scope - id: scope - env: - CI_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} - CI_HEAD_SHA: ${{ github.sha }} - run: node scripts/ci-scope.mjs - - name: Install dependencies - if: steps.scope.outputs.lab_extensions == 'true' - run: npm ci --no-audit --no-fund - - name: Verify changed lab extension dist imports - if: steps.scope.outputs.lab_extensions == 'true' - env: - LAB_EXTENSIONS_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} - LAB_EXTENSIONS_HEAD: ${{ github.sha }} - run: npm run labs:extensions:verify:changed + - name: Verify archived 1.x isolation + run: npm run archive:check playground-site: - name: Playgrounds and site + name: Site runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -90,14 +75,11 @@ jobs: CI_HEAD_SHA: ${{ github.sha }} run: node scripts/ci-scope.mjs - name: Install dependencies - if: steps.scope.outputs.site == 'true' || steps.scope.outputs.playground == 'true' || steps.scope.outputs.browser == 'true' + if: steps.scope.outputs.site == 'true' || steps.scope.outputs.browser == 'true' run: npm ci --no-audit --no-fund - - name: Verify playgrounds and site - if: steps.scope.outputs.playground == 'true' - run: npm run playground:typecheck && npm run playground:test && npm run playground:build - name: Verify site if: steps.scope.outputs.site == 'true' - run: npm run site:verify:pages + run: npm run typecheck -w @interactive-os/json-document-site && npm test -w @interactive-os/json-document-site && npm run site:verify:pages - name: Verify browser behavior if: steps.scope.outputs.browser == 'true' run: npm run browser:test diff --git a/.github/workflows/lab-extensions.yml b/.github/workflows/lab-extensions.yml index 2fe1059f..33e15a78 100644 --- a/.github/workflows/lab-extensions.yml +++ b/.github/workflows/lab-extensions.yml @@ -1,4 +1,4 @@ -name: Lab Extension Catalog +name: Archived 1.x Isolation on: workflow_dispatch: @@ -10,14 +10,12 @@ permissions: jobs: lab-extensions: - name: Full lab extension verification + name: Archive isolation runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: "22" - - name: Install dependencies - run: npm ci --no-audit --no-fund - - name: Verify all lab extension dist imports - run: npm run build && npm run labs:extensions:verify + - name: Verify archived code is outside the v2 release graph + run: npm run archive:check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 240e00db..7dd3ed20 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,6 @@ on: push: tags: - "json-document-v*-rc.*" - - "json-document-extensions-v*-rc.*" permissions: contents: read @@ -50,84 +49,9 @@ jobs: throw new Error("publishConfig.tag must remain next for prereleases"); } ' - - name: Verify extension prerelease identity - if: startsWith(github.ref_name, 'json-document-extensions-v') - env: - RELEASE_TAG: ${{ github.ref_name }} - run: | - node --input-type=module --eval ' - import { readFileSync } from "node:fs"; - const packagePaths = [ - "packages/grouping/package.json", - "packages/patch-preview/package.json", - "packages/search-replace/package.json", - ]; - const expectedNames = [ - "@interactive-os/json-document-grouping", - "@interactive-os/json-document-patch-preview", - "@interactive-os/json-document-search-replace", - ]; - const packages = packagePaths.map((path) => - JSON.parse(readFileSync(path, "utf8")) - ); - const versions = new Set(packages.map((pkg) => pkg.version)); - if (versions.size !== 1) { - throw new Error("extension prerelease versions must match"); - } - const [version] = versions; - const expectedTag = `json-document-extensions-v${version}`; - if (process.env.RELEASE_TAG !== expectedTag) { - throw new Error(`release tag ${process.env.RELEASE_TAG} does not match ${expectedTag}`); - } - if (!/^\d+\.\d+\.\d+-rc\.\d+$/.test(version)) { - throw new Error(`extension version is not an rc prerelease: ${version}`); - } - for (const [index, pkg] of packages.entries()) { - if (pkg.name !== expectedNames[index]) { - throw new Error(`${packagePaths[index]} has unexpected name ${pkg.name}`); - } - if (pkg.publishConfig?.tag !== "next") { - throw new Error(`${pkg.name} publishConfig.tag must remain next`); - } - if (pkg.publishConfig?.access !== "public" || pkg.publishConfig?.provenance !== true) { - throw new Error(`${pkg.name} must publish publicly with provenance`); - } - if (pkg.peerDependencies?.["@interactive-os/json-document"] !== "^1.0.1 || ^1.1.0-rc.0") { - throw new Error(`${pkg.name} does not accept the core integration RC`); - } - } - ' - name: Install dependencies run: npm ci --no-audit --no-fund - - name: Build core for extension verification - if: startsWith(github.ref_name, 'json-document-extensions-v') - run: npm run build -w @interactive-os/json-document - - name: Verify extension prereleases - if: startsWith(github.ref_name, 'json-document-extensions-v') - run: | - version=$(node -p 'require("./packages/grouping/package.json").version') - for package in \ - @interactive-os/json-document-grouping \ - @interactive-os/json-document-patch-preview \ - @interactive-os/json-document-search-replace - do - if npm view "$package@$version" version >/dev/null 2>&1; then - echo "::error::$package@$version is already published" - exit 1 - fi - npm run verify -w "$package" - npm pack -w "$package" --dry-run --cache "${RUNNER_TEMP}/npm-cache" - done + - name: Verify core prerelease + run: npm run release:check - name: Publish core prerelease - if: startsWith(github.ref_name, 'json-document-v') run: npm publish -w @interactive-os/json-document --tag next --provenance - - name: Publish extension prereleases - if: startsWith(github.ref_name, 'json-document-extensions-v') - run: | - for package in \ - @interactive-os/json-document-grouping \ - @interactive-os/json-document-patch-preview \ - @interactive-os/json-document-search-replace - do - npm publish -w "$package" --tag next --provenance - done diff --git a/README.md b/README.md index 9d6531b2..b4a1ab4c 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ v2 root는 JSON, JSON Pointer, JSONPath, JSON Patch만 전제로 하며 Zod, Rea selection, clipboard, history를 필수 계약에 넣지 않습니다. ```txt -Pure Protocol - |-> Document Projection -> host adapter - `-> Candidate Editing Session -> React / rich host adapter +Pure Protocol -> Document Projection -> host adapter ``` 공식 사이트: https://developer-1px.github.io/json-document/ @@ -21,42 +19,25 @@ Pure Protocol | 프로젝트 이해 | [docs/public/overview.md](docs/public/overview.md) | | 빠른 사용 예제 | [docs/public/quickstart.md](docs/public/quickstart.md) | | 공개 API | [docs/public/api.md](docs/public/api.md) | -| 공식 extension 사용법 | [docs/public/extensions.md](docs/public/extensions.md) | -| 제품별 feature 지도 | [docs/public/recipes.md](docs/public/recipes.md) | | 문서 구조 | [docs/README.md](docs/README.md) | | 변경 기록 | [docs/changelog.md](docs/changelog.md) | | v2 Projection 표준 | [docs/standard/v2-projection-profile.md](docs/standard/v2-projection-profile.md) | | v2 공개 표면 manifest | [docs/standard/v2-public-surface.json](docs/standard/v2-public-surface.json) | -| Candidate Session의 1.x 기준선 | [docs/standard/conformance-profile.md](docs/standard/conformance-profile.md) | +| 1.x 기록 | [archive/v1/docs](archive/v1/docs) | ## 코드 지도 | 위치 | 역할 | | --- | --- | -| [packages/json-document](packages/json-document) | v2 Kernel과 optional Candidate Session | -| [packages/collection](packages/collection) | ordered JSON array item 이동/복제/삭제 | -| [packages/clipboard-web](packages/clipboard-web) | browser clipboard bridge | -| [packages/contenteditable-web](packages/contenteditable-web) | `@interactive-os/json-document-contenteditable-web` DOM contenteditable text-surface adapter | -| [packages/contenteditable-react](packages/contenteditable-react) | `@interactive-os/json-document-contenteditable-react` React wrapper for contenteditable timing | -| [packages/schema-form](packages/schema-form) | schema-backed field descriptor | -| [packages/form-draft](packages/form-draft) | valid JSON commit 전 temporary invalid form input | -| [packages/protected-ranges](packages/protected-ranges) | protected JSON Pointer range edit guard | -| [packages/snippets](packages/snippets) | reusable JSON payload snippet insertion | -| [packages/dirty-state](packages/dirty-state) | clean baseline 대비 dirty state | -| [packages/bulk-edit](packages/bulk-edit) | JSONPath replace-all/delete-all | -| [packages/patch-log](packages/patch-log) | applied patch stream 기록/replay | -| [packages/persist-web](packages/persist-web) | browser storage-like persistence | -| [packages/id-resolver](packages/id-resolver) | stable id를 현재 JSON Pointer로 해석 | -| [packages/patch-preview](packages/patch-preview) | JSON Patch 적용 전 schema-safe dry-run | -| [packages/search-replace](packages/search-replace) | JSON string field 검색/치환 | -| [packages/grouping](packages/grouping) | sibling JSON item structural group/ungroup | -| [packages/proposed-changes](packages/proposed-changes) | JSON Patch 제안 accept/reject review model | -| [packages/comments](packages/comments) | JSON Pointer anchor 기반 review comment | -| [packages/outline](packages/outline) | document outline projection | -| [apps/site](apps/site) | public docs site와 workbench | -| [apps/outliner](apps/outliner) | outliner demo app | -| [apps/mobile-cms](apps/mobile-cms) | mobile CMS demo app | -| [labs/extensions](labs/extensions) | 아직 공식 package가 아닌 extension 실험 | +| [packages/json-document](packages/json-document) | 배포되는 v2 Kernel | +| [apps/site](apps/site) | v2 Core 공개 문서 사이트 | +| [archive/v1](archive/v1) | 배포·workspace·검증에서 분리된 1.x 기록 | + +이 저장소가 배포하는 package는 `@interactive-os/json-document` 하나입니다. +Selection, history, clipboard, persistence와 DOM lifecycle은 host adapter가 +여섯-member `JSONDocument` 위에서 조합합니다. DOM과 Input Events 정규화가 +필요한 제품은 별도 수명 주기를 가진 companion `@interactive-os/editable`을 +검토할 수 있으며, companion은 이 저장소의 배포물이나 v2 Core API가 아닙니다. ## 경계 @@ -68,12 +49,10 @@ v2 Kernel이 제공하는 최소 계약: - ordered atomic JSON Patch commit - canonical applied change publication -`@interactive-os/json-document/session`은 Zod 기반 schema 검증, selection, -clipboard, history와 고수준 편집 동사를 제공하는 optional Candidate 표면입니다. - 편집 툴이 계속 소유하는 것: - rendering, DOM focus, keyboard, drag/drop UI +- selection, clipboard, history, framework lifecycle - grid selection, TSV clipboard, formula engine - product command 이름, layout, route, remote protocol diff --git a/apps/site/package.json b/apps/site/package.json index 01b86834..ba7493f8 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -10,15 +10,11 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@interactive-os/json-document-mobile-cms": "*", - "@interactive-os/json-document-outliner": "*", "react": "^19.2.5", "react-dom": "^19.2.5", "react-markdown": "^10.1.0", "rehype-slug": "^6.0.0", - "remark-gfm": "^4.0.1", - "zod": "^4.0.0", - "@interactive-os/json-document": "*" + "remark-gfm": "^4.0.1" }, "devDependencies": { "@testing-library/react": "^16.3.2", @@ -33,5 +29,8 @@ "typescript": "^5.0.0", "vite": "^6.0.0", "vitest": "^4.1.7" + }, + "optionalDependencies": { + "@esbuild/linux-x64": "0.25.12" } } diff --git a/apps/site/src/App.tsx b/apps/site/src/App.tsx index 8d1e04c6..692445a1 100644 --- a/apps/site/src/App.tsx +++ b/apps/site/src/App.tsx @@ -7,7 +7,7 @@ type SiteRoute = { label: string; title: string; description: string; - group: "Start" | "Demos"; + group: "Start"; }; type Route = SiteRoute & { Component: ComponentType }; @@ -16,21 +16,11 @@ const SITE_URL = (import.meta.env.VITE_SITE_URL ?? "https://developer-1px.github const Docs = lazy(() => import("./routes/Docs").then((module) => ({ default: module.Docs }))); const DocsTutorial = lazy(() => import("./routes/Docs").then((module) => ({ default: module.DocsTutorial }))); const DocsApiReference = lazy(() => import("./routes/Docs").then((module) => ({ default: module.DocsApiReference }))); -const DocsExtensions = lazy(() => import("./routes/Docs").then((module) => ({ default: module.DocsExtensions }))); -const DocsRecipes = lazy(() => import("./routes/Docs").then((module) => ({ default: module.DocsRecipes }))); -const Playground = lazy(() => import("./routes/Playground").then((module) => ({ default: module.Playground }))); -const Outliner = lazy(() => import("@interactive-os/json-document-outliner").then((module) => ({ default: module.Outliner }))); -const MobileCms = lazy(() => import("@interactive-os/json-document-mobile-cms").then((module) => ({ default: module.App }))); const routeComponents: Record = { "/": Home, "/docs": Docs, "/docs/tutorial": DocsTutorial, "/docs/api": DocsApiReference, - "/docs/extensions": DocsExtensions, - "/docs/recipes": DocsRecipes, - "/playground": Playground, - "/playground/outliner": Outliner, - "/playground/mobile-cms": MobileCms, }; const ROUTES: Route[] = (siteRoutes as SiteRoute[]).map((route) => ({ ...route, @@ -148,7 +138,6 @@ export function App() { const Page = route.Component; const groupedRoutes = { Start: ROUTES.filter((item) => item.group === "Start"), - Demos: ROUTES.filter((item) => item.group === "Demos"), }; useEffect(() => { diff --git a/apps/site/src/generated/repo-catalog.ts b/apps/site/src/generated/repo-catalog.ts deleted file mode 100644 index 4e2c623f..00000000 --- a/apps/site/src/generated/repo-catalog.ts +++ /dev/null @@ -1,3261 +0,0 @@ -// Generated by scripts/generate-docs.mjs. Do not edit directly. -export const repoCatalog = { - "schemaVersion": 1, - "repo": { - "name": "@interactive-os/json-document-monorepo", - "private": true, - "summary": "json-document는 문서, 표, 슬라이드, 캔버스, 노트 편집기가 함께 쓸 수 있는\nprovider-neutral JSON 편집 protocol과 headless document projection입니다." - }, - "packages": [ - { - "path": "packages/bulk-edit", - "name": "@interactive-os/json-document-bulk-edit", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official JSONPath bulk editing extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless JSONPath bulk editing extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "apply JSONPath replace/delete operations to many document positions", - "notFor": "rendered text search UI or product workflow approval" - }, - "publicExports": [ - "BulkEdit", - "BulkEditCanReplaceAll", - "BulkEditChange", - "BulkEditChangeResult", - "BulkEditError", - "BulkEditErrorCode", - "BulkEditMatch", - "BulkEditReplaceAll", - "BulkEditResult", - "BulkEditValueMapper", - "canDeleteAll", - "canReplaceAll", - "createBulkEdit", - "deleteAll", - "replaceAll" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "bulk-edit", - "headless", - "json", - "jsonpath" - ] - }, - { - "path": "packages/clipboard-web", - "name": "@interactive-os/json-document-clipboard-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Web clipboard extension functions for json-document documents.", - "license": "MIT", - "summary": "Web clipboard extension functions for `@interactive-os/json-document`.", - "guidance": { - "useFor": "bridge json-document clipboard payloads to the browser clipboard", - "notFor": "TSV/CSV spreadsheet paste engines" - }, - "publicExports": [ - "CreateWebClipboardOptions", - "JSONCapabilityError", - "TextClipboardHost", - "WEB_CLIPBOARD_KIND", - "WEB_CLIPBOARD_VERSION", - "WebClipboard", - "WebClipboardCanPasteResult", - "WebClipboardCodec", - "WebClipboardCopyResult", - "WebClipboardCutResult", - "WebClipboardEnvelope", - "WebClipboardError", - "WebClipboardErrorCode", - "WebClipboardPasteResult", - "WebClipboardPayload", - "WebClipboardReadResult", - "WebClipboardWriteOk", - "WebClipboardWriteResult", - "createWebClipboard", - "defaultWebClipboardCodec" - ], - "publicExportCount": 20, - "keywords": [ - "@interactive-os/json-document", - "clipboard", - "headless", - "json", - "web" - ] - }, - { - "path": "packages/collection", - "name": "@interactive-os/json-document-collection", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official collection editing extension functions for ordered JSON array item commands in json-document documents.", - "license": "MIT", - "summary": "Official headless collection editing extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "edit ordered JSON arrays with item-level commands", - "notFor": "database collections or rendered list UI" - }, - "publicExports": [ - "Collection", - "CollectionCapabilityResult", - "CollectionDuplicateOptions", - "CollectionDuplicateResult", - "CollectionEditResult", - "CollectionError", - "CollectionErrorCode", - "CollectionSource", - "createCollection" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "collection", - "editing", - "headless", - "json" - ] - }, - { - "path": "packages/comments", - "name": "@interactive-os/json-document-comments", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official comments extension functions for review notes anchored to json-document documents.", - "license": "MIT", - "summary": "Official headless comments extension for review notes anchored to `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "anchor review comments to document structure", - "notFor": "comment UI, moderation, or author storage" - }, - "publicExports": [ - "Comment", - "CommentError", - "CommentErrorCode", - "CommentFilter", - "CommentInput", - "CommentListResult", - "CommentListener", - "CommentPointerFilter", - "CommentResult", - "CommentSnapshot", - "CommentStatus", - "CommentUpdate", - "Comments", - "createComments" - ], - "publicExportCount": 14, - "keywords": [ - "@interactive-os/json-document", - "comment", - "headless", - "review" - ] - }, - { - "path": "packages/contenteditable-react", - "name": "@interactive-os/json-document-contenteditable-react", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "React hook for the json-document contenteditable web adapter.", - "license": "MIT", - "summary": "Thin React hook for `@interactive-os/json-document-contenteditable-web`.", - "guidance": { - "useFor": "wrap the contenteditable web adapter with React render and selection restore timing", - "notFor": "rendering policy, editor commands, or non-React hosts" - }, - "publicExports": [ - "ContentEditableCommandPointerEvent", - "UseContentEditableOptions", - "UseContentEditableResult", - "useContentEditable" - ], - "publicExportCount": 4, - "keywords": [ - "@interactive-os/json-document", - "contenteditable", - "headless", - "json", - "react" - ] - }, - { - "path": "packages/contenteditable-web", - "name": "@interactive-os/json-document-contenteditable-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official contenteditable DOM adapter for json-document text surfaces.", - "license": "MIT", - "summary": "Official DOM adapter for using `@interactive-os/json-document` text surfaces with\nbrowser `contenteditable`.", - "guidance": { - "useFor": "bind text surfaces to DOM contenteditable selection, IME input, and clipboard events", - "notFor": "editor block semantics, toolbar commands, React rendering, or rich text schema policy" - }, - "publicExports": [ - "ContentEditableAdapter", - "ContentEditableAdapterOptions", - "ContentEditableCommand", - "ContentEditableCore", - "ContentEditableError", - "ContentEditableObservationReader", - "ContentEditableResult", - "JSON_ATOM_ATTRIBUTE", - "JSON_ATOM_REPLACEMENT", - "JSON_DOCUMENT_CONTENTEDITABLE_MIME", - "JSON_TEXT_ATTRIBUTE", - "TextSurfaceResolver", - "createContentEditableAdapter", - "createContentEditableCore" - ], - "publicExportCount": 14, - "keywords": [ - "@interactive-os/json-document", - "contenteditable", - "headless", - "json", - "web" - ] - }, - { - "path": "packages/dirty-state", - "name": "@interactive-os/json-document-dirty-state", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official dirty state tracking extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless dirty state tracking extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "compare a document to a clean baseline", - "notFor": "persistence or server save status" - }, - "publicExports": [ - "CreateDirtyStateOptions", - "DirtyState", - "DirtyStateComparator", - "DirtyStateDiscardOptions", - "DirtyStateListener", - "DirtyStateSnapshot", - "createDirtyState" - ], - "publicExportCount": 7, - "keywords": [ - "@interactive-os/json-document", - "dirty", - "editor", - "headless", - "json" - ] - }, - { - "path": "packages/form-draft", - "name": "@interactive-os/json-document-form-draft", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official form draft extension functions for temporary invalid input before committing valid json-document JSON.", - "license": "MIT", - "summary": "Official headless form draft extension for temporary input that is not ready to\nenter a schema-valid `@interactive-os/json-document` document.", - "guidance": { - "useFor": "hold temporary invalid form input before committing valid JSON", - "notFor": "rendered form components" - }, - "publicExports": [ - "CreateFormDraftOptions", - "FormDraftBatchChange", - "FormDraftBatchCommitResult", - "FormDraftBatchResult", - "FormDraftCommitResult", - "FormDraftError", - "FormDraftErrorCode", - "FormDraftListener", - "FormDraftParseContext", - "FormDraftParseResult", - "FormDraftParser", - "FormDraftSetResult", - "FormDraftSnapshot", - "FormDrafts", - "createFormDraft" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "draft", - "field", - "form", - "headless" - ] - }, - { - "path": "packages/grouping", - "name": "@interactive-os/json-document-grouping", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official structural group and ungroup extension functions for json-document documents.", - "license": "MIT", - "summary": "Official extension for structural `group` and `ungroup`.", - "guidance": { - "useFor": "group and ungroup selected sibling JSON items", - "notFor": "Airtable group-by views" - }, - "publicExports": [ - "Grouping", - "GroupingAdapter", - "GroupingApplyResult", - "GroupingChange", - "GroupingChangeResult", - "GroupingCreateContext", - "GroupingError", - "GroupingErrorCode", - "GroupingOperation", - "GroupingSource", - "canGroupSelection", - "canUngroupSelection", - "createGrouping", - "groupSelection", - "ungroupSelection" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "group", - "headless", - "selection", - "ungroup" - ] - }, - { - "path": "packages/id-resolver", - "name": "@interactive-os/json-document-id-resolver", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official stable id resolver extension functions for locating json-document document nodes by scope and id.", - "license": "MIT", - "summary": "Official headless stable id resolver extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "resolve scoped stable ids to current JSON Pointers", - "notFor": "id generation, relation graphs, routing, or server identity" - }, - "publicExports": [ - "IdResolver", - "IdResolverDiagnostic", - "IdResolverDiagnosticCode", - "IdResolverDocument", - "IdResolverEntry", - "IdResolverOptions", - "IdResolverScope", - "IdResolverSnapshot", - "ResolveIdErrorCode", - "ResolveIdResult", - "createIdResolver" - ], - "publicExportCount": 11, - "keywords": [ - "@interactive-os/json-document", - "editing", - "headless", - "id", - "json" - ] - }, - { - "path": "packages/json-document", - "name": "@interactive-os/json-document", - "status": "core", - "private": false, - "publishable": true, - "version": "2.0.0-rc.0", - "description": "Provider-neutral JSON editing protocol and headless document projection.", - "license": "MIT", - "summary": "문서, 표, 슬라이드, 캔버스, 노트 편집기가 함께 쓸 수 있는 provider-neutral\nJSON 편집 protocol과 headless document projection입니다.", - "guidance": null, - "publicExports": [ - "JSONAppliedChange", - "JSONCapabilityResult", - "JSONChangeMetadata", - "JSONDocument", - "JSONDocumentCommitOptions", - "JSONDocumentCommitResult", - "JSONPatchOperation", - "JSONPatchResult", - "JSONValue", - "Pointer", - "QueryResult", - "ReadResult", - "appendSegment", - "applyPatch", - "buildPointer", - "createJSONDocument", - "parentPointer", - "parsePointer", - "trackPointer", - "tryParsePointer" - ], - "publicExportCount": 20, - "keywords": [ - "document", - "editor", - "headless", - "json", - "json-patch", - "json-pointer", - "jsonpath", - "protocol" - ] - }, - { - "path": "packages/outline", - "name": "@interactive-os/json-document-outline", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official outline tree and structure editing extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless outline tree and structure editing extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "project and edit nested document outline structures", - "notFor": "Figma layer panels without a tree schema adapter" - }, - "publicExports": [ - "Outline", - "OutlineEditChange", - "OutlineEditChangeResult", - "OutlineEditError", - "OutlineEditErrorCode", - "OutlineEditResult", - "OutlineError", - "OutlineErrorCode", - "OutlineNode", - "OutlineResult", - "OutlineSource", - "OutlineStructureOptions", - "OutlineTreeOptions", - "canDemoteOutline", - "canPromoteOutline", - "createOutline", - "demoteOutline", - "promoteOutline", - "readOutline" - ], - "publicExportCount": 19, - "keywords": [ - "@interactive-os/json-document", - "headless", - "markdown", - "outline", - "outliner" - ] - }, - { - "path": "packages/patch-log", - "name": "@interactive-os/json-document-patch-log", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official patch log and replay extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless patch recording and replay extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "record and replay applied JSON Patch records", - "notFor": "product activity feeds or audit authorization" - }, - "publicExports": [ - "PatchLog", - "PatchLogEntry", - "PatchLogReplayCommitOptions", - "PatchLogReplayMetadataOption", - "PatchLogReplayMode", - "PatchLogReplayOptions", - "PatchLogReplayResult", - "PatchLogReplayStep", - "createPatchLog" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "log", - "patch", - "replay" - ] - }, - { - "path": "packages/patch-preview", - "name": "@interactive-os/json-document-patch-preview", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official patch preview extension functions for dry-running json-document document patches.", - "license": "MIT", - "summary": "Official headless patch preview extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "preview patch effects before confirmation", - "notFor": "visual diff rendering" - }, - "publicExports": [ - "PatchPreview", - "PatchPreviewError", - "PatchPreviewErrorCode", - "PatchPreviewOk", - "PatchPreviewOptions", - "PatchPreviewResult", - "createPatchPreview", - "previewPatch" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "dry-run", - "headless", - "patch", - "preview" - ] - }, - { - "path": "packages/persist-web", - "name": "@interactive-os/json-document-persist-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official web persistence extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless web persistence extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "save and restore documents in browser storage-like hosts", - "notFor": "server sync, auth, or conflict resolution" - }, - "publicExports": [ - "CreateDocumentPersistenceOptions", - "DocumentPersistence", - "DocumentPersistenceClearOk", - "DocumentPersistenceClearResult", - "DocumentPersistenceCodec", - "DocumentPersistenceEnvelope", - "DocumentPersistenceError", - "DocumentPersistenceErrorCode", - "DocumentPersistenceHost", - "DocumentPersistenceLoadError", - "DocumentPersistencePayload", - "DocumentPersistenceRestoreOk", - "DocumentPersistenceRestoreOptions", - "DocumentPersistenceRestoreResult", - "DocumentPersistenceSaveOk", - "DocumentPersistenceSaveResult", - "DocumentPersistenceWatchEvent", - "DocumentPersistenceWatchHandle", - "DocumentPersistenceWatchOptions", - "DocumentPersistenceWatchStatus", - "WEB_PERSISTENCE_KIND", - "WEB_PERSISTENCE_VERSION", - "createDocumentPersistence", - "defaultDocumentPersistenceCodec" - ], - "publicExportCount": 24, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "persistence", - "web" - ] - }, - { - "path": "packages/proposed-changes", - "name": "@interactive-os/json-document-proposed-changes", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official proposed document change review extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless proposed document change review extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "review, accept, or reject proposed document patches", - "notFor": "slash commands or mention autocomplete" - }, - "publicExports": [ - "ProposedChange", - "ProposedChangeAcceptResult", - "ProposedChangeAuditData", - "ProposedChangeError", - "ProposedChangeErrorCode", - "ProposedChangeFilter", - "ProposedChangeGuard", - "ProposedChangeInput", - "ProposedChangeListener", - "ProposedChangePlan", - "ProposedChangePlanResult", - "ProposedChangeResult", - "ProposedChangeSnapshot", - "ProposedChangeStatus", - "ProposedChanges", - "ProposedChangesOptions", - "canAcceptChange", - "canProposeChange", - "createProposedChanges" - ], - "publicExportCount": 19, - "keywords": [ - "@interactive-os/json-document", - "headless", - "patch", - "proposed-changes", - "review" - ] - }, - { - "path": "packages/protected-ranges", - "name": "@interactive-os/json-document-protected-ranges", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official protected range guard extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless protected range guard extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "guard edits to protected JSON Pointer ranges", - "notFor": "2D spreadsheet selection UI or server authorization" - }, - "publicExports": [ - "ProtectedRange", - "ProtectedRangeCapabilityResult", - "ProtectedRangeEditResult", - "ProtectedRangeError", - "ProtectedRangeErrorCode", - "ProtectedRangeOperation", - "ProtectedRangePasteResult", - "ProtectedRangeSummary", - "ProtectedRanges", - "canDeleteProtectedRange", - "canInsertProtectedRange", - "canMoveProtectedRange", - "canPasteProtectedRange", - "canPatchProtectedRanges", - "canReplaceProtectedRange", - "createProtectedRanges", - "deleteProtectedRange", - "insertProtectedRange", - "moveProtectedRange", - "pasteProtectedRange", - "patchProtectedRanges", - "replaceProtectedRange" - ], - "publicExportCount": 22, - "keywords": [ - "@interactive-os/json-document", - "headless", - "lock", - "protected", - "ranges" - ] - }, - { - "path": "packages/schema-form", - "name": "@interactive-os/json-document-schema-form", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official schema-backed field descriptor extension functions for json-document documents; no rendered form UI.", - "license": "MIT", - "summary": "Official headless schema-backed field descriptor extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "derive schema-backed field descriptors", - "notFor": "form rendering or input widgets" - }, - "publicExports": [ - "SchemaFormContainerKind", - "SchemaFormError", - "SchemaFormErrorCode", - "SchemaFormField", - "SchemaFormResult", - "SchemaFormTreeField", - "SchemaFormTreeResult", - "createSchemaForm", - "createSchemaFormTree" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "field", - "form", - "headless", - "schema" - ] - }, - { - "path": "packages/search-replace", - "name": "@interactive-os/json-document-search-replace", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official search and replace extension functions for text fields in json-document documents.", - "license": "MIT", - "summary": "Official headless search and replace extension for text fields in `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "find and replace text across document string fields", - "notFor": "regex engines, rendered text extraction, or search UI" - }, - "publicExports": [ - "SearchReplace", - "SearchReplaceApplyResult", - "SearchReplaceChange", - "SearchReplaceChangeResult", - "SearchReplaceError", - "SearchReplaceErrorCode", - "SearchReplaceMatch", - "SearchReplaceMatchApplyResult", - "SearchReplaceMatchChange", - "SearchReplaceMatchChangeResult", - "SearchReplaceMatchTarget", - "SearchReplaceOptions", - "SearchReplaceResult", - "SearchReplaceSnapshot", - "SearchReplaceTargetFilter", - "SearchReplaceTextTarget", - "TextMatchRange", - "canReplaceAllText", - "canReplaceTextMatch", - "createSearchReplace", - "findText", - "replaceAllText", - "replaceTextMatch" - ], - "publicExportCount": 23, - "keywords": [ - "@interactive-os/json-document", - "headless", - "replace", - "search", - "text" - ] - }, - { - "path": "packages/snippets", - "name": "@interactive-os/json-document-snippets", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official snippet insertion extension functions for reusable JSON payloads in json-document documents.", - "license": "MIT", - "summary": "Official headless snippet insertion extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "insert reusable JSON payloads with schema-safe paste checks", - "notFor": "slash palette UI or snippet storage" - }, - "publicExports": [ - "Snippet", - "SnippetError", - "SnippetErrorCode", - "SnippetInsertOptions", - "SnippetInsertResult", - "SnippetPlan", - "SnippetPlanResult", - "SnippetSummary", - "Snippets", - "canInsertSnippet", - "createSnippets", - "insertSnippet" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "headless", - "insert", - "snippets", - "template" - ] - } - ], - "officialExtensions": [ - { - "path": "packages/bulk-edit", - "name": "@interactive-os/json-document-bulk-edit", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official JSONPath bulk editing extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless JSONPath bulk editing extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "apply JSONPath replace/delete operations to many document positions", - "notFor": "rendered text search UI or product workflow approval" - }, - "publicExports": [ - "BulkEdit", - "BulkEditCanReplaceAll", - "BulkEditChange", - "BulkEditChangeResult", - "BulkEditError", - "BulkEditErrorCode", - "BulkEditMatch", - "BulkEditReplaceAll", - "BulkEditResult", - "BulkEditValueMapper", - "canDeleteAll", - "canReplaceAll", - "createBulkEdit", - "deleteAll", - "replaceAll" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "bulk-edit", - "headless", - "json", - "jsonpath" - ] - }, - { - "path": "packages/clipboard-web", - "name": "@interactive-os/json-document-clipboard-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Web clipboard extension functions for json-document documents.", - "license": "MIT", - "summary": "Web clipboard extension functions for `@interactive-os/json-document`.", - "guidance": { - "useFor": "bridge json-document clipboard payloads to the browser clipboard", - "notFor": "TSV/CSV spreadsheet paste engines" - }, - "publicExports": [ - "CreateWebClipboardOptions", - "JSONCapabilityError", - "TextClipboardHost", - "WEB_CLIPBOARD_KIND", - "WEB_CLIPBOARD_VERSION", - "WebClipboard", - "WebClipboardCanPasteResult", - "WebClipboardCodec", - "WebClipboardCopyResult", - "WebClipboardCutResult", - "WebClipboardEnvelope", - "WebClipboardError", - "WebClipboardErrorCode", - "WebClipboardPasteResult", - "WebClipboardPayload", - "WebClipboardReadResult", - "WebClipboardWriteOk", - "WebClipboardWriteResult", - "createWebClipboard", - "defaultWebClipboardCodec" - ], - "publicExportCount": 20, - "keywords": [ - "@interactive-os/json-document", - "clipboard", - "headless", - "json", - "web" - ] - }, - { - "path": "packages/collection", - "name": "@interactive-os/json-document-collection", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official collection editing extension functions for ordered JSON array item commands in json-document documents.", - "license": "MIT", - "summary": "Official headless collection editing extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "edit ordered JSON arrays with item-level commands", - "notFor": "database collections or rendered list UI" - }, - "publicExports": [ - "Collection", - "CollectionCapabilityResult", - "CollectionDuplicateOptions", - "CollectionDuplicateResult", - "CollectionEditResult", - "CollectionError", - "CollectionErrorCode", - "CollectionSource", - "createCollection" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "collection", - "editing", - "headless", - "json" - ] - }, - { - "path": "packages/comments", - "name": "@interactive-os/json-document-comments", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official comments extension functions for review notes anchored to json-document documents.", - "license": "MIT", - "summary": "Official headless comments extension for review notes anchored to `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "anchor review comments to document structure", - "notFor": "comment UI, moderation, or author storage" - }, - "publicExports": [ - "Comment", - "CommentError", - "CommentErrorCode", - "CommentFilter", - "CommentInput", - "CommentListResult", - "CommentListener", - "CommentPointerFilter", - "CommentResult", - "CommentSnapshot", - "CommentStatus", - "CommentUpdate", - "Comments", - "createComments" - ], - "publicExportCount": 14, - "keywords": [ - "@interactive-os/json-document", - "comment", - "headless", - "review" - ] - }, - { - "path": "packages/contenteditable-react", - "name": "@interactive-os/json-document-contenteditable-react", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "React hook for the json-document contenteditable web adapter.", - "license": "MIT", - "summary": "Thin React hook for `@interactive-os/json-document-contenteditable-web`.", - "guidance": { - "useFor": "wrap the contenteditable web adapter with React render and selection restore timing", - "notFor": "rendering policy, editor commands, or non-React hosts" - }, - "publicExports": [ - "ContentEditableCommandPointerEvent", - "UseContentEditableOptions", - "UseContentEditableResult", - "useContentEditable" - ], - "publicExportCount": 4, - "keywords": [ - "@interactive-os/json-document", - "contenteditable", - "headless", - "json", - "react" - ] - }, - { - "path": "packages/contenteditable-web", - "name": "@interactive-os/json-document-contenteditable-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official contenteditable DOM adapter for json-document text surfaces.", - "license": "MIT", - "summary": "Official DOM adapter for using `@interactive-os/json-document` text surfaces with\nbrowser `contenteditable`.", - "guidance": { - "useFor": "bind text surfaces to DOM contenteditable selection, IME input, and clipboard events", - "notFor": "editor block semantics, toolbar commands, React rendering, or rich text schema policy" - }, - "publicExports": [ - "ContentEditableAdapter", - "ContentEditableAdapterOptions", - "ContentEditableCommand", - "ContentEditableCore", - "ContentEditableError", - "ContentEditableObservationReader", - "ContentEditableResult", - "JSON_ATOM_ATTRIBUTE", - "JSON_ATOM_REPLACEMENT", - "JSON_DOCUMENT_CONTENTEDITABLE_MIME", - "JSON_TEXT_ATTRIBUTE", - "TextSurfaceResolver", - "createContentEditableAdapter", - "createContentEditableCore" - ], - "publicExportCount": 14, - "keywords": [ - "@interactive-os/json-document", - "contenteditable", - "headless", - "json", - "web" - ] - }, - { - "path": "packages/dirty-state", - "name": "@interactive-os/json-document-dirty-state", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official dirty state tracking extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless dirty state tracking extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "compare a document to a clean baseline", - "notFor": "persistence or server save status" - }, - "publicExports": [ - "CreateDirtyStateOptions", - "DirtyState", - "DirtyStateComparator", - "DirtyStateDiscardOptions", - "DirtyStateListener", - "DirtyStateSnapshot", - "createDirtyState" - ], - "publicExportCount": 7, - "keywords": [ - "@interactive-os/json-document", - "dirty", - "editor", - "headless", - "json" - ] - }, - { - "path": "packages/form-draft", - "name": "@interactive-os/json-document-form-draft", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official form draft extension functions for temporary invalid input before committing valid json-document JSON.", - "license": "MIT", - "summary": "Official headless form draft extension for temporary input that is not ready to\nenter a schema-valid `@interactive-os/json-document` document.", - "guidance": { - "useFor": "hold temporary invalid form input before committing valid JSON", - "notFor": "rendered form components" - }, - "publicExports": [ - "CreateFormDraftOptions", - "FormDraftBatchChange", - "FormDraftBatchCommitResult", - "FormDraftBatchResult", - "FormDraftCommitResult", - "FormDraftError", - "FormDraftErrorCode", - "FormDraftListener", - "FormDraftParseContext", - "FormDraftParseResult", - "FormDraftParser", - "FormDraftSetResult", - "FormDraftSnapshot", - "FormDrafts", - "createFormDraft" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "draft", - "field", - "form", - "headless" - ] - }, - { - "path": "packages/grouping", - "name": "@interactive-os/json-document-grouping", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official structural group and ungroup extension functions for json-document documents.", - "license": "MIT", - "summary": "Official extension for structural `group` and `ungroup`.", - "guidance": { - "useFor": "group and ungroup selected sibling JSON items", - "notFor": "Airtable group-by views" - }, - "publicExports": [ - "Grouping", - "GroupingAdapter", - "GroupingApplyResult", - "GroupingChange", - "GroupingChangeResult", - "GroupingCreateContext", - "GroupingError", - "GroupingErrorCode", - "GroupingOperation", - "GroupingSource", - "canGroupSelection", - "canUngroupSelection", - "createGrouping", - "groupSelection", - "ungroupSelection" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "group", - "headless", - "selection", - "ungroup" - ] - }, - { - "path": "packages/id-resolver", - "name": "@interactive-os/json-document-id-resolver", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official stable id resolver extension functions for locating json-document document nodes by scope and id.", - "license": "MIT", - "summary": "Official headless stable id resolver extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "resolve scoped stable ids to current JSON Pointers", - "notFor": "id generation, relation graphs, routing, or server identity" - }, - "publicExports": [ - "IdResolver", - "IdResolverDiagnostic", - "IdResolverDiagnosticCode", - "IdResolverDocument", - "IdResolverEntry", - "IdResolverOptions", - "IdResolverScope", - "IdResolverSnapshot", - "ResolveIdErrorCode", - "ResolveIdResult", - "createIdResolver" - ], - "publicExportCount": 11, - "keywords": [ - "@interactive-os/json-document", - "editing", - "headless", - "id", - "json" - ] - }, - { - "path": "packages/outline", - "name": "@interactive-os/json-document-outline", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official outline tree and structure editing extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless outline tree and structure editing extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "project and edit nested document outline structures", - "notFor": "Figma layer panels without a tree schema adapter" - }, - "publicExports": [ - "Outline", - "OutlineEditChange", - "OutlineEditChangeResult", - "OutlineEditError", - "OutlineEditErrorCode", - "OutlineEditResult", - "OutlineError", - "OutlineErrorCode", - "OutlineNode", - "OutlineResult", - "OutlineSource", - "OutlineStructureOptions", - "OutlineTreeOptions", - "canDemoteOutline", - "canPromoteOutline", - "createOutline", - "demoteOutline", - "promoteOutline", - "readOutline" - ], - "publicExportCount": 19, - "keywords": [ - "@interactive-os/json-document", - "headless", - "markdown", - "outline", - "outliner" - ] - }, - { - "path": "packages/patch-log", - "name": "@interactive-os/json-document-patch-log", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official patch log and replay extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless patch recording and replay extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "record and replay applied JSON Patch records", - "notFor": "product activity feeds or audit authorization" - }, - "publicExports": [ - "PatchLog", - "PatchLogEntry", - "PatchLogReplayCommitOptions", - "PatchLogReplayMetadataOption", - "PatchLogReplayMode", - "PatchLogReplayOptions", - "PatchLogReplayResult", - "PatchLogReplayStep", - "createPatchLog" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "log", - "patch", - "replay" - ] - }, - { - "path": "packages/patch-preview", - "name": "@interactive-os/json-document-patch-preview", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official patch preview extension functions for dry-running json-document document patches.", - "license": "MIT", - "summary": "Official headless patch preview extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "preview patch effects before confirmation", - "notFor": "visual diff rendering" - }, - "publicExports": [ - "PatchPreview", - "PatchPreviewError", - "PatchPreviewErrorCode", - "PatchPreviewOk", - "PatchPreviewOptions", - "PatchPreviewResult", - "createPatchPreview", - "previewPatch" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "dry-run", - "headless", - "patch", - "preview" - ] - }, - { - "path": "packages/persist-web", - "name": "@interactive-os/json-document-persist-web", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official web persistence extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless web persistence extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "save and restore documents in browser storage-like hosts", - "notFor": "server sync, auth, or conflict resolution" - }, - "publicExports": [ - "CreateDocumentPersistenceOptions", - "DocumentPersistence", - "DocumentPersistenceClearOk", - "DocumentPersistenceClearResult", - "DocumentPersistenceCodec", - "DocumentPersistenceEnvelope", - "DocumentPersistenceError", - "DocumentPersistenceErrorCode", - "DocumentPersistenceHost", - "DocumentPersistenceLoadError", - "DocumentPersistencePayload", - "DocumentPersistenceRestoreOk", - "DocumentPersistenceRestoreOptions", - "DocumentPersistenceRestoreResult", - "DocumentPersistenceSaveOk", - "DocumentPersistenceSaveResult", - "DocumentPersistenceWatchEvent", - "DocumentPersistenceWatchHandle", - "DocumentPersistenceWatchOptions", - "DocumentPersistenceWatchStatus", - "WEB_PERSISTENCE_KIND", - "WEB_PERSISTENCE_VERSION", - "createDocumentPersistence", - "defaultDocumentPersistenceCodec" - ], - "publicExportCount": 24, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "persistence", - "web" - ] - }, - { - "path": "packages/proposed-changes", - "name": "@interactive-os/json-document-proposed-changes", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official proposed document change review extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless proposed document change review extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "review, accept, or reject proposed document patches", - "notFor": "slash commands or mention autocomplete" - }, - "publicExports": [ - "ProposedChange", - "ProposedChangeAcceptResult", - "ProposedChangeAuditData", - "ProposedChangeError", - "ProposedChangeErrorCode", - "ProposedChangeFilter", - "ProposedChangeGuard", - "ProposedChangeInput", - "ProposedChangeListener", - "ProposedChangePlan", - "ProposedChangePlanResult", - "ProposedChangeResult", - "ProposedChangeSnapshot", - "ProposedChangeStatus", - "ProposedChanges", - "ProposedChangesOptions", - "canAcceptChange", - "canProposeChange", - "createProposedChanges" - ], - "publicExportCount": 19, - "keywords": [ - "@interactive-os/json-document", - "headless", - "patch", - "proposed-changes", - "review" - ] - }, - { - "path": "packages/protected-ranges", - "name": "@interactive-os/json-document-protected-ranges", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official protected range guard extension functions for json-document documents.", - "license": "MIT", - "summary": "Official headless protected range guard extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "guard edits to protected JSON Pointer ranges", - "notFor": "2D spreadsheet selection UI or server authorization" - }, - "publicExports": [ - "ProtectedRange", - "ProtectedRangeCapabilityResult", - "ProtectedRangeEditResult", - "ProtectedRangeError", - "ProtectedRangeErrorCode", - "ProtectedRangeOperation", - "ProtectedRangePasteResult", - "ProtectedRangeSummary", - "ProtectedRanges", - "canDeleteProtectedRange", - "canInsertProtectedRange", - "canMoveProtectedRange", - "canPasteProtectedRange", - "canPatchProtectedRanges", - "canReplaceProtectedRange", - "createProtectedRanges", - "deleteProtectedRange", - "insertProtectedRange", - "moveProtectedRange", - "pasteProtectedRange", - "patchProtectedRanges", - "replaceProtectedRange" - ], - "publicExportCount": 22, - "keywords": [ - "@interactive-os/json-document", - "headless", - "lock", - "protected", - "ranges" - ] - }, - { - "path": "packages/schema-form", - "name": "@interactive-os/json-document-schema-form", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official schema-backed field descriptor extension functions for json-document documents; no rendered form UI.", - "license": "MIT", - "summary": "Official headless schema-backed field descriptor extension for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "derive schema-backed field descriptors", - "notFor": "form rendering or input widgets" - }, - "publicExports": [ - "SchemaFormContainerKind", - "SchemaFormError", - "SchemaFormErrorCode", - "SchemaFormField", - "SchemaFormResult", - "SchemaFormTreeField", - "SchemaFormTreeResult", - "createSchemaForm", - "createSchemaFormTree" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "field", - "form", - "headless", - "schema" - ] - }, - { - "path": "packages/search-replace", - "name": "@interactive-os/json-document-search-replace", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.1-rc.1", - "description": "Official search and replace extension functions for text fields in json-document documents.", - "license": "MIT", - "summary": "Official headless search and replace extension for text fields in `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "find and replace text across document string fields", - "notFor": "regex engines, rendered text extraction, or search UI" - }, - "publicExports": [ - "SearchReplace", - "SearchReplaceApplyResult", - "SearchReplaceChange", - "SearchReplaceChangeResult", - "SearchReplaceError", - "SearchReplaceErrorCode", - "SearchReplaceMatch", - "SearchReplaceMatchApplyResult", - "SearchReplaceMatchChange", - "SearchReplaceMatchChangeResult", - "SearchReplaceMatchTarget", - "SearchReplaceOptions", - "SearchReplaceResult", - "SearchReplaceSnapshot", - "SearchReplaceTargetFilter", - "SearchReplaceTextTarget", - "TextMatchRange", - "canReplaceAllText", - "canReplaceTextMatch", - "createSearchReplace", - "findText", - "replaceAllText", - "replaceTextMatch" - ], - "publicExportCount": 23, - "keywords": [ - "@interactive-os/json-document", - "headless", - "replace", - "search", - "text" - ] - }, - { - "path": "packages/snippets", - "name": "@interactive-os/json-document-snippets", - "status": "official-extension", - "private": false, - "publishable": true, - "version": "0.1.0", - "description": "Official snippet insertion extension functions for reusable JSON payloads in json-document documents.", - "license": "MIT", - "summary": "Official headless snippet insertion extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "insert reusable JSON payloads with schema-safe paste checks", - "notFor": "slash palette UI or snippet storage" - }, - "publicExports": [ - "Snippet", - "SnippetError", - "SnippetErrorCode", - "SnippetInsertOptions", - "SnippetInsertResult", - "SnippetPlan", - "SnippetPlanResult", - "SnippetSummary", - "Snippets", - "canInsertSnippet", - "createSnippets", - "insertSnippet" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "headless", - "insert", - "snippets", - "template" - ] - } - ], - "labExtensions": [ - { - "path": "labs/extensions/apply-defaults", - "name": "@interactive-os/json-document-apply-defaults", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab apply-defaults (add missing object keys from defaults) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab apply-defaults extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "add missing object keys from a defaults map without overwriting existing ones", - "notFor": "filling existing empty values, removing unknown keys, or deep merge" - }, - "publicExports": [ - "ApplyDefaults", - "ApplyDefaultsChange", - "ApplyDefaultsError", - "ApplyDefaultsErrorCode", - "ApplyDefaultsResult", - "canEnsure", - "createApplyDefaults", - "ensure" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "defaults", - "ensure", - "headless", - "json", - "object" - ] - }, - { - "path": "labs/extensions/autosave", - "name": "@interactive-os/json-document-autosave", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab autosave orchestration extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab autosave extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "schedule host-owned saves after document changes", - "notFor": "retry queues, offline sync, or server conflict resolution" - }, - "publicExports": [ - "AutoSave", - "AutoSaveEvent", - "AutoSaveFlushResult", - "AutoSaveHost", - "AutoSaveHostResult", - "AutoSaveListener", - "AutoSaveOptions", - "AutoSaveReason", - "AutoSaveScheduler", - "AutoSaveSnapshot", - "AutoSaveState", - "createAutoSave" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "autosave", - "draft", - "headless" - ] - }, - { - "path": "labs/extensions/batch-update", - "name": "@interactive-os/json-document-batch-update", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab batch-update (set a field across selected items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab batch-update extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "set a field across a list of selected item pointers to a constant or computed value", - "notFor": "selecting which items to edit, or JSONPath query-driven replacement" - }, - "publicExports": [ - "BatchUpdate", - "BatchUpdateChange", - "BatchUpdateError", - "BatchUpdateErrorCode", - "BatchUpdateOptions", - "BatchUpdateResult", - "BatchUpdateValue", - "batchUpdate", - "canBatchUpdate", - "createBatchUpdate" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "batch", - "headless", - "json", - "selection", - "set" - ] - }, - { - "path": "labs/extensions/bookmarks", - "name": "@interactive-os/json-document-bookmarks", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab bookmark tracking extension functions for json-document documents.", - "license": "MIT", - "summary": "Headless bookmark tracking extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "keep named JSON Pointer locations stable across edits", - "notFor": "browser bookmarks or route state" - }, - "publicExports": [ - "Bookmark", - "BookmarkError", - "BookmarkErrorCode", - "BookmarkSetResult", - "Bookmarks", - "BookmarksListener", - "BookmarksSnapshot", - "createBookmarks" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "bookmark", - "headless", - "pointer", - "tracking" - ] - }, - { - "path": "labs/extensions/calculated-fields", - "name": "@interactive-os/json-document-calculated-fields", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab calculated-fields (sync host-calculated fields) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab calculated field extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "sync host-computed derived JSON fields", - "notFor": "formula languages or dependency runtimes" - }, - "publicExports": [ - "CalculatedFieldChange", - "CalculatedFieldContext", - "CalculatedFieldDefinition", - "CalculatedFieldError", - "CalculatedFieldErrorCode", - "CalculatedFields", - "CalculatedFieldsChange", - "CalculatedFieldsPlanResult", - "CalculatedFieldsSyncResult", - "createCalculatedFields", - "planCalculatedFields", - "syncCalculatedFields" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "computed", - "formula", - "headless" - ] - }, - { - "path": "labs/extensions/causal-patch-inbox", - "name": "@interactive-os/json-document-causal-patch-inbox", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab causal JSON Patch inbox for json-document documents.", - "license": "MIT", - "summary": "Lab module for admitting dependency-declared JSON Patch or authored edit\nenvelopes, holding out-of-order work, and committing each envelope when it\nbecomes causally ready on one `JSONDocument`.", - "guidance": { - "useFor": "queue dependency-declared envelopes and materialize delayed positional or stable-id edits when causally ready", - "notFor": "CRDT/OT convergence, transport, persistence, or automatic conflict resolution" - }, - "publicExports": [ - "CausalAuthoredIntent", - "CausalBaseRevisionFailure", - "CausalEnvelope", - "CausalHostPublication", - "CausalHostPublicationOwnership", - "CausalHostReadyRequest", - "CausalHostReadyResult", - "CausalIntentEnvelope", - "CausalMaterializationDiagnostic", - "CausalMaterializationPolicy", - "CausalPatchDocument", - "CausalPatchEnvelope", - "CausalPatchFailure", - "CausalPatchHost", - "CausalPatchInbox", - "CausalPatchInboxOptions", - "CausalPatchInboxSnapshot", - "CausalPatchIngestError", - "CausalPatchIngestErrorCode", - "CausalPatchIngestOk", - "CausalPatchIngestResult", - "CausalPatchPublicationDocument", - "CausalPositionalIntent", - "CausalPositionalMaterializationFailure", - "CausalStableIdReplaceIntent", - "FailedCausalMaterialization", - "FailedCausalPatch", - "FaultedCausalPatch", - "QueuedCausalPatch", - "createCausalPatchInbox" - ], - "publicExportCount": 30, - "keywords": [ - "@interactive-os/json-document", - "causal", - "headless", - "inbox", - "patch" - ] - }, - { - "path": "labs/extensions/change-case", - "name": "@interactive-os/json-document-change-case", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab change-case (case/whitespace transforms on a string field) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab change-case extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "apply case/whitespace transforms (upper, lower, trim, title) to a string field", - "notFor": "locale-aware casing, rich text formatting toolbars, or find/replace" - }, - "publicExports": [ - "CaseTransform", - "ChangeCase", - "ChangeCaseChange", - "ChangeCaseError", - "ChangeCaseErrorCode", - "ChangeCaseResult", - "applyTransform", - "canTransform", - "createChangeCase" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "case", - "headless", - "json", - "text", - "transform" - ] - }, - { - "path": "labs/extensions/checkpoints", - "name": "@interactive-os/json-document-checkpoints", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab checkpoint snapshot extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab checkpoint extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "name and restore document snapshots", - "notFor": "durable version graphs or cloud backup" - }, - "publicExports": [ - "CheckpointEntry", - "CheckpointError", - "CheckpointErrorCode", - "CheckpointReadResult", - "CheckpointRestoreOptions", - "CheckpointRestoreResult", - "CheckpointSaveOptions", - "CheckpointSaveResult", - "Checkpoints", - "CheckpointsListener", - "CheckpointsSnapshot", - "CreateCheckpointsOptions", - "createCheckpoints" - ], - "publicExportCount": 13, - "keywords": [ - "@interactive-os/json-document", - "checkpoint", - "headless", - "restore", - "snapshot" - ] - }, - { - "path": "labs/extensions/clear-contents", - "name": "@interactive-os/json-document-clear-contents", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab clear-contents (reset fields to schema-derived empties) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab clear-contents extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "reset selected fields to schema-derived empty values, keeping structure", - "notFor": "structural delete, caller-supplied bulk replace, or enum/object default policy" - }, - "publicExports": [ - "ClearContents", - "ClearContentsChange", - "ClearContentsError", - "ClearContentsErrorCode", - "ClearContentsOptions", - "ClearContentsResult", - "EmptyFor", - "canClearContents", - "clearContents", - "createClearContents" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "clear", - "empty", - "headless", - "json", - "reset" - ] - }, - { - "path": "labs/extensions/convert-block-type", - "name": "@interactive-os/json-document-convert-block-type", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab block type conversion extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab extension for converting a JSON block/object from one host-described type to\nanother.", - "guidance": { - "useFor": "convert selected nodes between host-described kinds", - "notFor": "schema migration systems" - }, - "publicExports": [ - "BlockTypeConversionDescriptor", - "BlockTypeConversionError", - "BlockTypeConversionErrorCode", - "BlockTypeConversionFactoryInput", - "BlockTypeConversionInput", - "BlockTypeConversionPlan", - "BlockTypeConversionPlanResult", - "BlockTypeConversionResult", - "BlockTypeConverter", - "canConvertBlockType", - "convertBlockType", - "createBlockTypeConverter" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "convert", - "headless", - "node-kind", - "structure" - ] - }, - { - "path": "labs/extensions/convert-type", - "name": "@interactive-os/json-document-convert-type", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab convert-type (convert a field type: string/number/boolean) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab convert-type extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "convert a field type (string/number/integer/boolean) where the schema permits it", - "notFor": "locale/format-aware parsing of currency or dates, or input masks" - }, - "publicExports": [ - "ConvertType", - "ConvertTypeChange", - "ConvertTypeError", - "ConvertTypeErrorCode", - "ConvertTypeResult", - "ConvertTypeTarget", - "canConvertType", - "convertType", - "createConvertType" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "cast", - "convert-type", - "headless", - "json", - "type" - ] - }, - { - "path": "labs/extensions/dedupe", - "name": "@interactive-os/json-document-dedupe", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab dedupe (remove duplicate array items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab dedupe extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "remove duplicate array items by whole value or a host key", - "notFor": "fuzzy matching, cross-array dedupe, or JSONPath match deletion" - }, - "publicExports": [ - "Dedupe", - "DedupeChange", - "DedupeError", - "DedupeErrorCode", - "DedupeKeyOf", - "DedupeOptions", - "DedupeResult", - "canDedupe", - "createDedupe", - "dedupe" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "array", - "dedupe", - "duplicate", - "headless", - "json" - ] - }, - { - "path": "labs/extensions/document-diff", - "name": "@interactive-os/json-document-document-diff", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab document diff and apply extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab document diff and apply extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "produce and apply patch changes toward a target document", - "notFor": "visual diff UI or merge conflict resolution" - }, - "publicExports": [ - "DocumentDiff", - "DocumentDiffApplyResult", - "DocumentDiffChange", - "DocumentDiffChangeResult", - "DocumentDiffError", - "DocumentDiffErrorCode", - "applyDocumentDiff", - "canApplyDocumentDiff", - "createDocumentDiff", - "diffDocument" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "diff", - "headless", - "json", - "patch" - ] - }, - { - "path": "labs/extensions/drag-drop", - "name": "@interactive-os/json-document-drag-drop", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab drag and drop extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab drag and drop extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "turn drag/drop intent into move or paste operations", - "notFor": "DOM drag/drop events, hit testing, or hover UI" - }, - "publicExports": [ - "DragDrop", - "DragDropError", - "DragDropErrorCode", - "DragDropInput", - "DragDropPayloadOptions", - "DragDropPerformError", - "DragDropPerformOk", - "DragDropPerformResult", - "DragDropPlan", - "DragDropPlanResult", - "DragDropSource", - "DragDropTarget", - "canDrop", - "createDragDrop", - "performDrop" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "copy", - "drag", - "drop", - "headless", - "paste" - ] - }, - { - "path": "labs/extensions/fill-blanks", - "name": "@interactive-os/json-document-fill-blanks", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab fill-blanks (fill only empty slots) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab fill-blanks extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "fill only empty slots across targets, preserving non-empty values", - "notFor": "adding missing fields, choosing targets, or unconditional batch set" - }, - "publicExports": [ - "FillBlanks", - "FillBlanksChange", - "FillBlanksError", - "FillBlanksErrorCode", - "FillBlanksOptions", - "FillBlanksResult", - "FillBlanksValue", - "canFillBlanks", - "createFillBlanks", - "fillBlanks" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "default", - "empty", - "fill", - "headless", - "json" - ] - }, - { - "path": "labs/extensions/fill-down", - "name": "@interactive-os/json-document-fill-down", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab fill-down (carry last non-empty value into blanks) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab fill-down extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "carry the last non-empty value into the empty slots that follow (ffill)", - "notFor": "constant fill, numeric series interpolation, or rendered grid UI" - }, - "publicExports": [ - "FillDown", - "FillDownChange", - "FillDownError", - "FillDownErrorCode", - "FillDownOptions", - "FillDownResult", - "canFillDown", - "createFillDown", - "fillDown" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "ffill", - "fill-down", - "headless", - "json", - "propagate" - ] - }, - { - "path": "labs/extensions/fill-series", - "name": "@interactive-os/json-document-fill-series", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab fill/series propagation extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab fill/series propagation extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "fill a value or linear series across a contiguous sibling range", - "notFor": "date/pattern series, 2D grid fill, or fill-handle drag UI" - }, - "publicExports": [ - "FillCell", - "FillGenerator", - "FillOptions", - "FillSeries", - "FillSeriesChange", - "FillSeriesError", - "FillSeriesErrorCode", - "FillSeriesResult", - "FillSource", - "canFill", - "createFillSeries", - "fill" - ], - "publicExportCount": 12, - "keywords": [ - "@interactive-os/json-document", - "autofill", - "fill", - "headless", - "json", - "series" - ] - }, - { - "path": "labs/extensions/generate-slug", - "name": "@interactive-os/json-document-generate-slug", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab generate-slug (derive a URL-safe slug from a string) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab generate-slug extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "derive a URL-safe slug from a string field (CMS title to slug)", - "notFor": "uniqueness/collision handling or non-Latin transliteration" - }, - "publicExports": [ - "GenerateSlug", - "GenerateSlugChange", - "GenerateSlugError", - "GenerateSlugErrorCode", - "GenerateSlugOptions", - "GenerateSlugResult", - "canGenerateSlug", - "createGenerateSlug", - "generateSlug" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "generate-slug", - "headless", - "json", - "slug", - "url" - ] - }, - { - "path": "labs/extensions/grid-range", - "name": "@interactive-os/json-document-grid-range", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab grid-range editing extension functions for sparse-record-backed json-document documents.", - "license": "MIT", - "summary": "Lab grid-range editing extension for sparse-record-backed `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "paste or fill rectangular grid ranges backed by sparse JSON records", - "notFor": "DOM grid selection, coordinate naming policy, formulas, or TSV/CSV parsing" - }, - "publicExports": [ - "GridRange", - "GridRangeBounds", - "GridRangeCellAction", - "GridRangeCellAddress", - "GridRangeCellIntent", - "GridRangeCellIntentKind", - "GridRangeChange", - "GridRangeDecision", - "GridRangeEqualityContext", - "GridRangeError", - "GridRangeErrorCode", - "GridRangeFillContext", - "GridRangeFillGenerator", - "GridRangeFillInput", - "GridRangeIntentContext", - "GridRangeKeyResolver", - "GridRangeOptions", - "GridRangePasteInput", - "GridRangeRect", - "GridRangeResolvedCell", - "GridRangeResult", - "GridRangeSourceCell", - "canFillGridRange", - "canPasteGridRange", - "createGridRange", - "fillGridRange", - "pasteGridRange" - ], - "publicExportCount": 27, - "keywords": [ - "@interactive-os/json-document", - "cell-range", - "fill", - "grid-range", - "headless", - "paste", - "sparse-record" - ] - }, - { - "path": "labs/extensions/increment-number", - "name": "@interactive-os/json-document-increment-number", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab increment-number (increment/decrement a numeric field) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab increment-number extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "increment, decrement, or step a numeric field with optional clamping", - "notFor": "rendered spinners, formatting, units, or currency" - }, - "publicExports": [ - "IncrementNumber", - "IncrementNumberChange", - "IncrementNumberError", - "IncrementNumberErrorCode", - "IncrementNumberOptions", - "IncrementNumberResult", - "canStep", - "createIncrementNumber", - "step" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "increment", - "json", - "number", - "stepper" - ] - }, - { - "path": "labs/extensions/join-text", - "name": "@interactive-os/json-document-join-text", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab join-text (join an array into a string field) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab join-text extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "join an array into a string field with a separator (inverse of split-text)", - "notFor": "locale list formatting, or reading the result without writing" - }, - "publicExports": [ - "JoinText", - "JoinTextChange", - "JoinTextError", - "JoinTextErrorCode", - "JoinTextOptions", - "JoinTextResult", - "canJoin", - "createJoinText", - "join" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "array", - "headless", - "join", - "json", - "text" - ] - }, - { - "path": "labs/extensions/layer-order", - "name": "@interactive-os/json-document-layer-order", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab layer ordering extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab layer ordering extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "reorder visual stack arrays with bring/send commands", - "notFor": "canvas rendering or z-index CSS management" - }, - "publicExports": [ - "LayerOrder", - "LayerOrderAction", - "LayerOrderApplyResult", - "LayerOrderChange", - "LayerOrderChangeResult", - "LayerOrderError", - "LayerOrderErrorCode", - "LayerOrderSource", - "canReorderLayers", - "createLayerOrder", - "reorderLayers" - ], - "publicExportCount": 11, - "keywords": [ - "@interactive-os/json-document", - "headless", - "layer", - "order", - "z-order" - ] - }, - { - "path": "labs/extensions/limit-items", - "name": "@interactive-os/json-document-limit-items", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab limit-items (cap an array to N items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab limit-items extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "cap a JSON array to at most N items, keeping the start or end", - "notFor": "choosing survivors beyond start/end, or auto-trimming on insert" - }, - "publicExports": [ - "LimitItems", - "LimitItemsChange", - "LimitItemsError", - "LimitItemsErrorCode", - "LimitItemsOptions", - "LimitItemsResult", - "canLimitItems", - "createLimitItems", - "limitItems" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "cap", - "headless", - "json", - "limit", - "slice" - ] - }, - { - "path": "labs/extensions/live-cursors", - "name": "@interactive-os/json-document-live-cursors", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab live-cursors (track remote collaborator cursors) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab package for remote collaborator cursor and selection presence.", - "guidance": { - "useFor": "track remote collaborator cursors and selections", - "notFor": "CRDT/OT or realtime transport" - }, - "publicExports": [ - "LiveCursors", - "PresenceCursor", - "PresenceCursorError", - "PresenceCursorErrorCode", - "PresenceCursorInput", - "PresenceCursorListener", - "PresenceCursorResult", - "PresenceCursorSnapshot", - "PresenceCursorUpdate", - "createLiveCursors" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "collaboration", - "cursor", - "headless", - "presence", - "selection" - ] - }, - { - "path": "labs/extensions/move-selected", - "name": "@interactive-os/json-document-move-selected", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab move-selected (move a contiguous selected block) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab move-selected extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "move a contiguous selection of sibling items to a new position", - "notFor": "single-item moves, drag/drop events, or cross-array moves" - }, - "publicExports": [ - "MoveSelected", - "MoveSelectedChange", - "MoveSelectedError", - "MoveSelectedErrorCode", - "MoveSelectedResult", - "MoveSelectedTarget", - "canMoveSelected", - "createMoveSelected", - "moveSelected" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "move", - "reorder", - "selection" - ] - }, - { - "path": "labs/extensions/pad-text", - "name": "@interactive-os/json-document-pad-text", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab pad-text (pad a string to a minimum length) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab pad-text extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "pad a string field to a minimum length (zero-padded codes/IDs)", - "notFor": "number formatting or display-time alignment" - }, - "publicExports": [ - "PadText", - "PadTextChange", - "PadTextError", - "PadTextErrorCode", - "PadTextOptions", - "PadTextResult", - "canPadText", - "createPadText", - "padText" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "format", - "headless", - "json", - "pad", - "string" - ] - }, - { - "path": "labs/extensions/paste-cells", - "name": "@interactive-os/json-document-paste-cells", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab paste-cells (2D matrix into rectangular cells) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab paste-cells extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "paste a 2D value matrix onto a rectangular array-of-records region", - "notFor": "TSV/CSV parsing, clipboard I/O, or auto-growing the array" - }, - "publicExports": [ - "PasteCells", - "PasteCellsChange", - "PasteCellsError", - "PasteCellsErrorCode", - "PasteCellsResult", - "PasteCellsTarget", - "canPasteGrid", - "createPasteCells", - "pasteGrid" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "grid", - "headless", - "json", - "paste", - "table" - ] - }, - { - "path": "labs/extensions/paste-special", - "name": "@interactive-os/json-document-paste-special", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab paste special extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab extension for paste special.", - "guidance": { - "useFor": "adapt external payloads before schema-safe paste", - "notFor": "browser clipboard I/O or autocomplete dropdowns" - }, - "publicExports": [ - "PasteSpecial", - "PasteSpecialAdaptedPayload", - "PasteSpecialAdapter", - "PasteSpecialAdapterError", - "PasteSpecialAdapterInput", - "PasteSpecialAdapterResult", - "PasteSpecialApplyResult", - "PasteSpecialDiagnostic", - "PasteSpecialError", - "PasteSpecialErrorCode", - "PasteSpecialInput", - "PasteSpecialOptions", - "PasteSpecialPlan", - "PasteSpecialPlanResult", - "canPasteSpecial", - "createPasteSpecial", - "pasteSpecial" - ], - "publicExportCount": 17, - "keywords": [ - "@interactive-os/json-document", - "clipboard", - "compatible", - "headless", - "paste" - ] - }, - { - "path": "labs/extensions/patch-rebase", - "name": "@interactive-os/json-document-patch-rebase", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab optimistic JSON Patch rebase planner for json-document documents.", - "license": "MIT", - "summary": "Lab optimistic JSON Patch rebase planner for `@interactive-os/json-document`\ndocuments.", - "guidance": { - "useFor": "plan conservative optimistic rebases over ordered applied JSON Patch batches", - "notFor": "CRDT/OT protocols, transport, durable sync queues, or merge UI" - }, - "publicExports": [ - "RebaseChangeInput", - "RebaseChangeResult", - "RebaseConflict", - "RebaseConflictCode", - "RebaseDiagnostic", - "RebaseDiagnosticCode", - "RebaseSchema", - "rebaseChange" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "conflict", - "headless", - "patch", - "rebase" - ] - }, - { - "path": "labs/extensions/references", - "name": "@interactive-os/json-document-references", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab stable reference and backlink extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab extension for stable references and backlinks over JSON documents.", - "guidance": { - "useFor": "track stable references and backlinks over JSON documents", - "notFor": "route state or rendered links" - }, - "publicExports": [ - "ReferenceBacklinksResult", - "ReferenceDiagnostic", - "ReferenceDiagnosticCode", - "ReferenceError", - "ReferenceErrorCode", - "ReferenceFieldDescriptor", - "ReferenceLink", - "ReferenceResolveResult", - "ReferenceSetInput", - "ReferenceSetPlan", - "ReferenceSetPlanResult", - "ReferenceSetResult", - "ReferenceSnapshot", - "ReferenceTarget", - "ReferenceTargetDescriptor", - "References", - "ReferencesDescriptor", - "canSetReference", - "createReferences", - "indexReferences", - "resolveReference" - ], - "publicExportCount": 21, - "keywords": [ - "@interactive-os/json-document", - "backlinks", - "headless", - "references", - "relations" - ] - }, - { - "path": "labs/extensions/renumber-items", - "name": "@interactive-os/json-document-renumber-items", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab renumber-items (sync an order field to array position) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab renumber-items extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "sync an order/position field to each item array position after a reorder", - "notFor": "reordering the array itself, or fractional/gap indexing" - }, - "publicExports": [ - "RenumberItems", - "RenumberItemsChange", - "RenumberItemsError", - "RenumberItemsErrorCode", - "RenumberItemsOptions", - "RenumberItemsResult", - "canRenumberItems", - "createRenumberItems", - "renumberItems" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "order", - "position", - "renumber-items" - ] - }, - { - "path": "labs/extensions/round", - "name": "@interactive-os/json-document-round", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab round (round/floor/ceil a number) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab round extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "round a number to a precision or nearest step (round/floor/ceil/trunc)", - "notFor": "currency/locale formatting, or increment/clamp (see increment-number)" - }, - "publicExports": [ - "Round", - "RoundChange", - "RoundError", - "RoundErrorCode", - "RoundMode", - "RoundOptions", - "RoundResult", - "canRound", - "createRound", - "round" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "number", - "precision", - "round" - ] - }, - { - "path": "labs/extensions/sort-items", - "name": "@interactive-os/json-document-sort-items", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab sort-items (sort/reverse array items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab collection sort extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "sort or reverse JSON array items", - "notFor": "query views, filters, or server sorting" - }, - "publicExports": [ - "SortItems", - "SortItemsChange", - "SortItemsChangeResult", - "SortItemsCompare", - "SortItemsError", - "SortItemsErrorCode", - "SortItemsItem", - "SortItemsResult", - "canReverse", - "canSort", - "createSortItems", - "reverse", - "sort" - ], - "publicExportCount": 13, - "keywords": [ - "@interactive-os/json-document", - "collection", - "headless", - "json", - "sort" - ] - }, - { - "path": "labs/extensions/sparse-record", - "name": "@interactive-os/json-document-sparse-record", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab sparse-record entry editing extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab sparse-record entry editing extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "set or remove keyed entries in sparse JSON records with add/replace/remove/no-op planning", - "notFor": "2D grid coordinate expansion, product key normalization, or rendered selection" - }, - "publicExports": [ - "SparseRecord", - "SparseRecordAction", - "SparseRecordChange", - "SparseRecordDecision", - "SparseRecordEdit", - "SparseRecordEqualityContext", - "SparseRecordError", - "SparseRecordErrorCode", - "SparseRecordIntent", - "SparseRecordOptions", - "SparseRecordResult", - "canEditSparseRecords", - "createSparseRecord", - "editSparseRecords" - ], - "publicExportCount": 14, - "keywords": [ - "@interactive-os/json-document", - "entries", - "headless", - "json", - "record", - "sparse-record" - ] - }, - { - "path": "labs/extensions/split-text", - "name": "@interactive-os/json-document-split-text", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab split-text (split a string into array items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab split-text extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "split a string into array items by a delimiter (tag input, paste-as-list)", - "notFor": "CSV/TSV quoting, split-to-columns, or clipboard access" - }, - "publicExports": [ - "SplitText", - "SplitTextChange", - "SplitTextError", - "SplitTextErrorCode", - "SplitTextOptions", - "SplitTextResult", - "canSplit", - "createSplitText", - "split" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "split", - "tags", - "text" - ] - }, - { - "path": "labs/extensions/stable-id-rebase", - "name": "@interactive-os/json-document-stable-id-rebase", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab stable-id anchored delayed change planner for json-document documents.", - "license": "MIT", - "summary": "Lab planner for materializing one delayed field replacement against the current\nPointer of a host-owned stable id.", - "guidance": { - "useFor": "materialize one delayed field replacement against a current stable-id target", - "notFor": "id policy, structural patches, CRDT/OT protocols, transport, or merge UI" - }, - "publicExports": [ - "StableIdRebaseDiagnostic", - "StableIdRebaseDocument", - "StableIdRebaseResult", - "StableIdReplaceInput", - "StableIdTarget", - "rebaseStableChange" - ], - "publicExportCount": 6, - "keywords": [ - "@interactive-os/json-document", - "conflict", - "headless", - "rebase", - "stable-id" - ] - }, - { - "path": "labs/extensions/swap-items", - "name": "@interactive-os/json-document-swap-items", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab swap-items (exchange two array items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab swap-items extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "exchange the positions of two items in the same array", - "notFor": "cross-array swaps or moving to an arbitrary index" - }, - "publicExports": [ - "SwapItems", - "SwapItemsChange", - "SwapItemsError", - "SwapItemsErrorCode", - "SwapItemsResult", - "canSwapItems", - "createSwapItems", - "swapItems" - ], - "publicExportCount": 8, - "keywords": [ - "@interactive-os/json-document", - "exchange", - "headless", - "json", - "reorder", - "swap-items" - ] - }, - { - "path": "labs/extensions/toggle-option", - "name": "@interactive-os/json-document-toggle-option", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab toggle-option (toggle a value in an array set) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab toggle-option extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "toggle, add, or remove a value's presence in a JSON array (tag/multi-select)", - "notFor": "ordered insertion position or deduping an existing array" - }, - "publicExports": [ - "MembershipAction", - "ToggleOption", - "ToggleOptionChange", - "ToggleOptionError", - "ToggleOptionErrorCode", - "ToggleOptionOptions", - "ToggleOptionResult", - "createToggleOption", - "plan" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "headless", - "json", - "membership", - "set", - "toggle" - ] - }, - { - "path": "labs/extensions/toggle-value", - "name": "@interactive-os/json-document-toggle-value", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab toggle-value (toggle/advance a field through values) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab toggle-value extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "toggle a boolean or advance an enum/value field (enum options come from the schema)", - "notFor": "rendered toggle controls or keyboard policy" - }, - "publicExports": [ - "ToggleValue", - "ToggleValueChange", - "ToggleValueDirection", - "ToggleValueError", - "ToggleValueErrorCode", - "ToggleValueOptions", - "ToggleValueResult", - "canToggleValue", - "createToggleValue", - "toggleValue" - ], - "publicExportCount": 10, - "keywords": [ - "@interactive-os/json-document", - "enum", - "headless", - "json", - "toggle", - "toggle-value" - ] - }, - { - "path": "labs/extensions/trim-text", - "name": "@interactive-os/json-document-trim-text", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab trim-text (cap a string to a max length) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab trim-text extension for `@interactive-os/json-document` documents.", - "guidance": { - "useFor": "cap a string field to a max length with optional ellipsis and word boundary", - "notFor": "display-time CSS truncation or grapheme/locale-aware length" - }, - "publicExports": [ - "TrimText", - "TrimTextChange", - "TrimTextError", - "TrimTextErrorCode", - "TrimTextOptions", - "TrimTextResult", - "canTrimText", - "createTrimText", - "trimText" - ], - "publicExportCount": 9, - "keywords": [ - "@interactive-os/json-document", - "excerpt", - "headless", - "json", - "text", - "trim-text" - ] - }, - { - "path": "labs/extensions/wrap-selection", - "name": "@interactive-os/json-document-wrap-selection", - "status": "lab-extension", - "private": true, - "publishable": false, - "version": "0.1.0", - "description": "Lab wrap-selection (wrap/unwrap selected items) extension functions for json-document documents.", - "license": "MIT", - "summary": "Lab extension for structural `wrap` and `unwrap`.", - "guidance": { - "useFor": "wrap sibling JSON items in host-defined containers", - "notFor": "visual grouping or layout containers" - }, - "publicExports": [ - "WrapCreateContext", - "WrapSelection", - "WrapSelectionAdapter", - "WrapSelectionApplyResult", - "WrapSelectionChange", - "WrapSelectionChangeResult", - "WrapSelectionError", - "WrapSelectionErrorCode", - "WrapSelectionOperation", - "WrapSource", - "canUnwrapSelection", - "canWrapSelection", - "createWrapSelection", - "unwrapSelection", - "wrapSelection" - ], - "publicExportCount": 15, - "keywords": [ - "@interactive-os/json-document", - "headless", - "structure", - "unwrap", - "wrap" - ] - } - ], - "apps": [ - { - "path": "apps/copy-review-lab", - "name": "@interactive-os/json-document-copy-review-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods `@interactive-os/json-document-search-replace` as a copy cleanup product surface.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/grouping-lab", - "name": "@interactive-os/json-document-grouping-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods `@interactive-os/json-document-grouping` as a small structural editor feature.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/mobile-cms", - "name": "@interactive-os/json-document-mobile-cms", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/outliner", - "name": "@interactive-os/json-document-outliner", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [ - "EMPTY_NODE", - "OutlineNode", - "OutlineSchema", - "Outliner", - "SAMPLE" - ], - "publicExportCount": 5, - "keywords": [] - }, - { - "path": "apps/outliner-blind-a", - "name": "@interactive-os/json-document-outliner-blind-a", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/outliner-blind-b", - "name": "@interactive-os/json-document-outliner-blind-b", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/proposed-changes-lab", - "name": "@interactive-os/json-document-proposed-changes-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods `@interactive-os/json-document-proposed-changes` as a small proposed document change review feature.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/protected-ranges-lab", - "name": "@interactive-os/json-document-protected-ranges-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods official `@interactive-os/json-document-protected-ranges` as a locked structured content editor.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/review-comments-lab", - "name": "@interactive-os/json-document-review-comments-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods `@interactive-os/json-document-comments` as an editorial review product surface.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/schema-form-lab", - "name": "@interactive-os/json-document-schema-form-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/site", - "name": "@interactive-os/json-document-site", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": null, - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - }, - { - "path": "apps/snippet-composer-lab", - "name": "@interactive-os/json-document-snippet-composer-lab", - "status": "app", - "private": true, - "publishable": false, - "version": null, - "description": null, - "license": null, - "summary": "Dogfoods official `@interactive-os/json-document-snippets` as a page block composer.", - "guidance": null, - "publicExports": [], - "publicExportCount": 0, - "keywords": [] - } - ], - "totals": { - "packages": 20, - "officialExtensions": 19, - "labExtensions": 41, - "apps": 12 - } -} as const; - -export type RepoCatalog = typeof repoCatalog; diff --git a/apps/site/src/playgrounds/InterfaceWorkbench.playground.tsx b/apps/site/src/playgrounds/InterfaceWorkbench.playground.tsx deleted file mode 100644 index 1ba675a2..00000000 --- a/apps/site/src/playgrounds/InterfaceWorkbench.playground.tsx +++ /dev/null @@ -1,1909 +0,0 @@ -import { useEffect, useMemo, useState, type ReactNode } from "react"; -import { z } from "zod"; -import { - appendSegment, - applyOperation, - applyPatch, - applyPatchToTrustedState, - buildPointer, - createJSONDocument, - escapeSegment, - JSONDocumentError, - lastSegment, - lastSegmentIndex, - parentPointer, - parsePointer, - PointerSyntaxError, - trackPointer, - tryParsePointer, - unescapeSegment, - withLastSegment, - type JSONPatchOperation, - type JSONCapabilityResult, - type Pointer, - type SelectionSnap, -} from "@interactive-os/json-document/session"; -import { useJSONDocument } from "@interactive-os/json-document/react"; - -const Card = z.object({ - id: z.string(), - title: z.string().min(1), - status: z.enum(["todo", "doing", "done"]), - points: z.number().int().min(0).max(13), - tags: z.array(z.string().min(1)), -}); - -const BoardSchema = z.object({ - title: z.string().min(1), - settings: z.object({ - archived: z.boolean(), - owner: z.string().min(1), - }), - lists: z.array(z.object({ - id: z.string(), - name: z.string().min(1), - cards: z.array(Card), - })), -}); - -type Board = z.infer; -type BenchResult = { - call: string; - value: unknown; - feature?: string; - bindings?: readonly string[]; - effect?: readonly string[]; -}; -type FeatureStageId = - | "board-setup" - | "card-intake" - | "card-edit" - | "flow-columns" - | "selection-bulk" - | "clipboard-reuse" - | "find-filter" - | "recovery-history" - | "integration"; - -type FeatureStage = { - id: FeatureStageId; - title: string; - apis: readonly string[]; -}; - -const featureStages: readonly FeatureStage[] = [ - { id: "board-setup", title: "Board setup", apis: ["useJSONDocument", "createJSONDocument", "doc.load", "doc.reset", "doc.subscribe"] }, - { id: "card-intake", title: "Card intake", apis: ["schema.accepts", "schema.describe", "canInsert", "insert"] }, - { id: "card-edit", title: "Card edit", apis: ["at", "exists", "schema.kind", "canReplace", "replace", "commit", "canDelete", "delete"] }, - { id: "flow-columns", title: "Flow across columns", apis: ["canMove", "move", "canDuplicate", "duplicate"] }, - { id: "selection-bulk", title: "Selection and bulk work", apis: ["selection.*", "canDelete", "delete"] }, - { id: "clipboard-reuse", title: "Reuse via clipboard", apis: ["clipboard.*", "canCopy", "canCut", "canPaste", "paste"] }, - { id: "find-filter", title: "Find and filter", apis: ["canFind", "find", "selection.selectRanges"] }, - { id: "recovery-history", title: "Recovery and history", apis: ["canUndo", "canRedo", "history.*"] }, - { id: "integration", title: "Integration and helpers", apis: ["applyOperation", "applyPatch", "applyPatchToTrustedState", "trackPointer", "pointer helpers"] }, -]; - -const initialBoard: Board = { - title: "Workbench board", - settings: { archived: false, owner: "core" }, - lists: [ - { - id: "todo", - name: "Todo", - cards: [ - { id: "c1", title: "Patch API", status: "todo", points: 2, tags: ["ops"] }, - { id: "c2", title: "Selection API", status: "todo", points: 3, tags: ["selection"] }, - ], - }, - { - id: "doing", - name: "Doing", - cards: [ - { id: "c3", title: "Clipboard API", status: "doing", points: 5, tags: ["clipboard"] }, - ], - }, - { - id: "done", - name: "Done", - cards: [ - { id: "c4", title: "History API", status: "done", points: 8, tags: ["history"] }, - ], - }, - ], -}; - -const sampleCard: Board["lists"][number]["cards"][number] = { - id: "new", - title: "Inserted card", - status: "todo", - points: 1, - tags: ["new"], -}; - -const invalidCard = { - id: "bad", - title: "", - status: "blocked", - points: -1, - tags: [], -}; - -const publicTypeExports = [ - "HistoryTransactionOptions", - "JSONCapabilityResult", - "JSONChangeMetadata", - "JSONDocument", - "JSONDocumentCommitOptions", - "JSONDocumentDuplicateError", - "JSONDocumentDuplicateOptions", - "JSONDocumentDuplicateResult", - "JSONDocumentHistory", - "JSONDocumentOptions", - "JSONDocumentPasteOptions", - "JSONDocumentPasteTarget", - "JSONPatchInput", - "JSONPatchOperation", - "SelectionPoint", - "JSONResult", - "Pointer", - "ClipboardCopyOptions", - "ClipboardCopyError", - "ClipboardCopyOk", - "ClipboardCopyResult", - "ClipboardCutError", - "ClipboardCutOk", - "ClipboardCutOptions", - "ClipboardCutResult", - "ClipboardEmpty", - "ClipboardMutationOk", - "ClipboardPasteDiscriminatorMismatch", - "ClipboardPasteError", - "ClipboardPasteResult", - "ClipboardReadOk", - "ClipboardReadOptions", - "ClipboardReadResult", - "ClipboardState", - "ClipboardWriteOptions", - "EntriesResult", - "EntryKind", - "QueryResult", - "ReadEntry", - "ReadResult", - "SchemaDescription", - "SchemaDescriptionResult", - "SchemaErrorCode", - "SchemaErrorResult", - "SchemaKind", - "SchemaKindResult", - "SchemaPathMode", - "SchemaQueryResult", - "SchemaState", - "SelectionOptions", - "SelectionPointObject", - "SelectionOrderedRange", - "SelectionOrderedRangeEntry", - "SelectionAffinity", - "SelectionContext", - "SelectionCursorDirection", - "SelectionCursorErrorCode", - "SelectionCursorOptions", - "SelectionCursorResult", - "SelectionCursorTarget", - "SelectionDirection", - "SelectionEdge", - "SelectionMode", - "SelectionOrderErrorCode", - "SelectionOrderOptions", - "SelectionPointOrderResult", - "SelectionPointerSpan", - "SelectionPointerSpansResult", - "SelectionRange", - "SelectionRangeInput", - "SelectionRangeOrderResult", - "SelectionRangesOrderResult", - "SelectionScopeErrorCode", - "SelectionScopeOptions", - "SelectionScopeResult", - "SelectionScopeTarget", - "SelectionSnap", - "SelectionSource", - "SelectionSpanOptions", - "SelectionState", - "SelectionType", - "DeleteSelectionTextResult", - "ReplaceSelectionTextResult", - "SelectionTextDeleteDirection", - "SelectionTextDeleteOptions", - "SelectionTextEdit", - "SelectionTextEditErrorCode", - "SelectionTextEditOptions", - "SelectionTextEditsResult", - "ClipboardSource", -]; - -const publicTypeGroups = [ - { - label: "Document and patch", - note: "used by Add, Edit, Move, Duplicate", - items: [ - "JSONDocument", - "JSONPatchInput", - "JSONPatchOperation", - "JSONResult", - "JSONCapabilityResult", - "JSONChangeMetadata", - "Pointer", - ], - }, - { - label: "History", - note: "used by Undo and redo", - items: [ - "JSONDocumentHistory", - "JSONDocumentCommitOptions", - "HistoryTransactionOptions", - ], - }, - { - label: "Selection", - note: "used by Find and Bulk cards", - items: publicTypeExports.filter((item) => item.includes("Selection") || item === "SelectionPoint" || item === "SelectionPointObject" || item === "SelectionOrderedRange" || item === "SelectionOrderedRangeEntry"), - }, - { - label: "Clipboard", - note: "used by Copy and paste", - items: publicTypeExports.filter((item) => item.includes("Clipboard") || item.includes("Copy") || item.includes("Cut") || item.includes("Paste") || item.includes("Duplicate")), - }, - { - label: "Schema and read", - note: "used by Read schema", - items: publicTypeExports.filter((item) => item.includes("Schema") || item.includes("Read") || item.includes("Entries") || item.includes("Query") || item === "EntryKind"), - }, -]; - -function cardPointer(listIndex: number, cardIndex: number): Pointer { - return `/lists/${listIndex}/cards/${cardIndex}` as Pointer; -} - -function collapsedSelection(pointer: Pointer): SelectionSnap { - return { - selectedPointers: [pointer], - selectionRanges: [{ anchor: pointer, focus: pointer }], - primaryIndex: 0, - anchor: pointer, - focus: pointer, - }; -} - -function stringify(value: unknown): string { - return JSON.stringify(value, null, 2); -} - -function parseJson(value: string): { ok: true; value: unknown } | { ok: false; message: string } { - try { - return { ok: true, value: JSON.parse(value) }; - } catch (error) { - return { ok: false, message: error instanceof Error ? error.message : "invalid JSON" }; - } -} - -function parseNumberPayload(value: string): unknown { - const trimmed = value.trim(); - if (trimmed.length === 0) return { __invalid_number: "empty" }; - const number = Number(trimmed); - return Number.isFinite(number) ? number : { __invalid_number: value }; -} - -function canDisabledReason(result: JSONCapabilityResult): string | undefined { - if (result.ok) return undefined; - const violation = result.violations?.[0]; - const detail = result.reason - ?? (violation ? `${violation.path}: ${violation.message}` : undefined) - ?? result.pointer; - return detail ? `can: ${result.code}: ${detail}` : `can: ${result.code}`; -} - -function capabilityStatus(result: JSONCapabilityResult): string { - return result.ok ? "ok" : result.code; -} - -function disabledMark(reason: string | undefined): string { - if (!reason) return "cannot"; - if (reason.startsWith("state: ")) return `cannot ${reason.slice("state: ".length)}`; - if (!reason.startsWith("can: ")) return "cannot"; - const code = reason.slice("can: ".length).split(":")[0]?.trim().split(" ")[0]; - return code ? `cannot ${code}` : "cannot"; -} - -function stateStatus(reason: string | undefined): string | undefined { - if (!reason) return undefined; - if (reason.startsWith("state: ")) return `state ${reason.slice("state: ".length)}`; - if (!reason.startsWith("can: ")) return reason; - const code = reason.slice("can: ".length).split(":")[0]?.trim().split(" ")[0]; - return code ? `can ${code}` : "can blocked"; -} - -function isEditableShortcutTarget(target: EventTarget | null): boolean { - if (!(target instanceof HTMLElement)) return false; - if (target.isContentEditable) return true; - return target instanceof HTMLInputElement - || target instanceof HTMLTextAreaElement - || target instanceof HTMLSelectElement; -} - -function resultSummary(value: unknown, call = ""): string[] { - if (value === null || typeof value !== "object") return [String(value)]; - const record = value as Record; - const lines: string[] = []; - if (typeof record.ok === "boolean") { - lines.push(record.ok ? "ok true" : `ok false ${String(record.code ?? "")}`.trim()); - } - if (Array.isArray(record.applied)) { - lines.push(...record.applied.slice(0, 3).map((operation) => { - const op = operation as { op?: unknown; path?: unknown; from?: unknown }; - return [op.op, op.from ? `from ${String(op.from)}` : null, op.path ? `path ${String(op.path)}` : null] - .filter(Boolean) - .join(" "); - })); - } - if (typeof record.duplicatedTo === "string") lines.push(`duplicatedTo ${record.duplicatedTo}`); - if (typeof record.pointer === "string") lines.push(`pointer ${record.pointer}`); - if (typeof record.kind === "string") lines.push(`kind ${record.kind}`); - if (typeof record.type === "string") lines.push(`type ${record.type}`); - if (typeof record.path === "string") lines.push(`path ${record.path}`); - if (Array.isArray(record.entries)) lines.push(`entries ${record.entries.length}`); - if (Array.isArray(record.pointers)) lines.push(`pointers ${record.pointers.length}`); - if (Array.isArray(record.selectedPointers)) lines.push(`selected ${record.selectedPointers.length}`); - if (Array.isArray(record.events)) lines.push(`events ${record.events.length}`); - if (Array.isArray(record.patch)) lines.push(`patch ${record.patch.length}`); - if (Array.isArray(record.parsePointer)) lines.push(`segments ${record.parsePointer.join("/")}`); - if (typeof record.buildPointer === "string") lines.push(`buildPointer ${record.buildPointer}`); - if (call.includes("schema.describe")) lines.push("schema insert"); - if (call.includes("clipboard.clear") && record.hasData === false) lines.push("clipboard empty"); - if (call.includes("selection?.textPatch")) lines.push("text patch"); - if (Array.isArray(record.violations) && record.violations.length > 0) { - const first = record.violations[0] as { path?: unknown; message?: unknown }; - lines.push(`violation ${String(first.path ?? "")} ${String(first.message ?? "")}`.trim()); - } - if ("value" in record) lines.push(...valueSummary(record.value)); - return lines.length > 0 ? lines : ["object returned"]; -} - -function valueSummary(value: unknown): string[] { - if (value === null) return ["value null"]; - if (Array.isArray(value)) return [`array ${value.length}`]; - if (typeof value !== "object") return [`value ${String(value)}`]; - const record = value as Record; - if (typeof record.title === "string" && typeof record.status === "string") return [`card "${record.title}"`]; - if (typeof record.title === "string") return [`title "${record.title}"`]; - if (typeof record.name === "string") return [`name "${record.name}"`]; - const keys = Object.keys(record); - return keys.length > 0 ? [`value keys ${keys.slice(0, 3).join(",")}`] : ["value object"]; -} - -function isBoard(value: unknown): value is Board { - return BoardSchema.safeParse(value).success; -} - -function resultBoard(value: unknown, fallback: Board): Board { - if (isBoard(value)) return value; - if (value !== null && typeof value === "object" && "value" in value) { - const next = (value as { value?: unknown }).value; - if (isBoard(next)) return next; - } - return fallback; -} - -function operationEffects(value: unknown): string[] { - if (value === null || typeof value !== "object") return []; - const record = value as Record; - const effects: string[] = []; - if (typeof record.duplicatedTo === "string") effects.push(`duplicated to ${record.duplicatedTo}`); - if (!Array.isArray(record.applied)) return effects; - - for (const operation of record.applied.slice(0, 4)) { - const op = operation as { op?: unknown; path?: unknown; from?: unknown }; - const path = typeof op.path === "string" ? op.path : ""; - const from = typeof op.from === "string" ? op.from : ""; - if (op.op === "add") effects.push(`added ${path}`); - else if (op.op === "remove") effects.push(`removed ${path}`); - else if (op.op === "replace") effects.push(`replaced ${path}`); - else if (op.op === "move") effects.push(`moved ${from} -> ${path}`); - else if (op.op === "copy") effects.push(`copied ${from} -> ${path}`); - else if (typeof op.op === "string") effects.push(`${op.op} ${path}`.trim()); - } - return effects; -} - -function boardEffect(before: Board, after: Board): string[] { - const effects: string[] = []; - const beforeCounts = before.lists.map((list) => list.cards.length).join("/"); - const afterCounts = after.lists.map((list) => list.cards.length).join("/"); - if (beforeCounts !== afterCounts) effects.push(`cards ${beforeCounts} -> ${afterCounts}`); - - const beforeCards = flattenCards(before); - const afterCards = flattenCards(after); - const beforeById = new Map(beforeCards.map((item) => [item.card.id, item])); - const afterById = new Map(afterCards.map((item) => [item.card.id, item])); - - if (afterCards.length < beforeCards.length) { - for (const item of beforeCards) { - if (!afterById.has(item.card.id)) { - effects.push(`removed ${item.card.title} ${item.pointer}`); - return effects; - } - } - const index = firstDifferentCardIndex(beforeCards, afterCards); - const removed = beforeCards[index] ?? beforeCards[beforeCards.length - 1]; - if (removed) { - effects.push(`removed ${removed.card.title} ${removed.pointer}`); - return effects; - } - } - - if (afterCards.length > beforeCards.length) { - for (const item of afterCards) { - if (!beforeById.has(item.card.id)) { - effects.push(`added ${item.card.title} ${item.pointer}`); - return effects; - } - } - const index = firstDifferentCardIndex(afterCards, beforeCards); - const added = afterCards[index] ?? afterCards[afterCards.length - 1]; - if (added) { - effects.push(`added ${added.card.title} ${added.pointer}`); - return effects; - } - } - - for (const item of beforeCards) { - const next = afterById.get(item.card.id); - if (next && next.pointer !== item.pointer) { - effects.push(`moved ${item.card.title} ${item.pointer} -> ${next.pointer}`); - return effects; - } - } - - for (let listIndex = 0; listIndex < Math.min(before.lists.length, after.lists.length); listIndex += 1) { - const beforeCards = before.lists[listIndex]?.cards ?? []; - const afterCards = after.lists[listIndex]?.cards ?? []; - for (let cardIndex = 0; cardIndex < Math.min(beforeCards.length, afterCards.length); cardIndex += 1) { - const beforeCard = beforeCards[cardIndex]; - const afterCard = afterCards[cardIndex]; - if (beforeCard && afterCard && beforeCard.id === afterCard.id && beforeCard.title !== afterCard.title) { - effects.push(`title ${cardPointer(listIndex, cardIndex)}: ${beforeCard.title} -> ${afterCard.title}`); - return effects; - } - } - } - return effects; -} - -function flattenCards(board: Board): Array<{ - pointer: Pointer; - card: Board["lists"][number]["cards"][number]; -}> { - return board.lists.flatMap((list, listIndex) => - list.cards.map((card, cardIndex) => ({ - pointer: cardPointer(listIndex, cardIndex), - card, - })), - ); -} - -function firstDifferentCardIndex( - left: Array<{ card: Board["lists"][number]["cards"][number] }>, - right: Array<{ card: Board["lists"][number]["cards"][number] }>, -): number { - const length = Math.max(left.length, right.length); - for (let index = 0; index < length; index += 1) { - const leftCard = left[index]?.card; - const rightCard = right[index]?.card; - if (!leftCard || !rightCard || leftCard.id !== rightCard.id || leftCard.title !== rightCard.title) { - return index; - } - } - return Math.max(0, left.length - 1); -} - -function cardTitleAt(board: Board, pointer: Pointer): string | null { - const segments = tryParsePointer(pointer); - if (!segments || segments.length !== 4 || segments[0] !== "lists" || segments[2] !== "cards") return null; - const listIndex = Number(segments[1]); - const cardIndex = Number(segments[3]); - return board.lists[listIndex]?.cards[cardIndex]?.title ?? null; -} - -function mutatesBoard(call: string): boolean { - return call.includes("doc.patch(") - || call.includes("doc.replace(") - || call.includes("doc.delete(") - || call.includes("doc.move(") - || call.includes("doc.commit(") - || call.includes("doc.duplicate(") - || call.includes("doc.cut(") - || call.includes("doc.paste(") - || call.includes("doc.undo(") - || call.includes("doc.redo(") - || call.includes("doc.history.transaction("); -} - -function selectedLabel(selected: readonly string[]): string { - return selected.length === 0 ? "none" : selected.join(", "); -} - -function cardRekey() { - return { fields: ["id"], strategy: "suffix" as const }; -} - -function columnClass(id: string): string { - if (id === "doing") return "border-amber-200 bg-amber-50/70"; - if (id === "done") return "border-emerald-200 bg-emerald-50/70"; - return "border-stone-200 bg-stone-50"; -} - -function statusClass(status: Board["lists"][number]["cards"][number]["status"]): string { - if (status === "doing") return "bg-amber-100 text-amber-800"; - if (status === "done") return "bg-emerald-100 text-emerald-800"; - return "bg-sky-100 text-sky-800"; -} - -function statusForInsertTarget(board: Board, target: Pointer): Board["lists"][number]["cards"][number]["status"] | null { - const segments = tryParsePointer(target); - if (!segments || segments[0] !== "lists" || segments[2] !== "cards") return null; - const listIndex = Number(segments[1]); - const listId = board.lists[listIndex]?.id; - if (listId === "todo" || listId === "doing" || listId === "done") return listId; - return null; -} - -function payloadWithStatus(payloadText: string, status: Board["lists"][number]["cards"][number]["status"]): string { - const parsed = parseJson(payloadText); - if (!parsed.ok || parsed.value === null || typeof parsed.value !== "object" || Array.isArray(parsed.value)) { - return payloadText; - } - return stringify({ ...parsed.value, status }); -} - -export function InterfaceWorkbench() { - const doc = useJSONDocument(BoardSchema, initialBoard, { - history: 100, - strict: false, - selection: { mode: "extended", initial: [cardPointer(0, 0)] }, - }); - const selectedPointers = doc.selection?.selectedPointers ?? []; - const primaryPointer = doc.selection?.primaryPointer ?? null; - const [valueTarget, setValueTarget] = useState(cardPointer(0, 0)); - const [insertTarget, setInsertTarget] = useState("/lists/0/cards/-" as Pointer); - const [query, setQuery] = useState("$..cards[?(@.status=='todo')]"); - const [payload, setPayload] = useState(stringify(sampleCard)); - const [textPayload, setTextPayload] = useState("Patch edit"); - const [pointsPayload, setPointsPayload] = useState("8"); - const [badPointsPayload, setBadPointsPayload] = useState("-5"); - const [result, setResult] = useState({ call: "ready", value: doc.value }); - const [featureResults, setFeatureResults] = useState>({}); - const [apiCoverageOpen, setApiCoverageOpen] = useState(false); - const [activeStageId, setActiveStageId] = useState("board-setup"); - - const pointers = useMemo( - () => doc.value.lists.flatMap((list, listIndex) => - list.cards.map((card, cardIndex) => ({ - pointer: cardPointer(listIndex, cardIndex), - card, - })), - ), - [doc.value], - ); - const insertPointers = useMemo( - () => doc.value.lists.map((list, listIndex) => ({ - pointer: `/lists/${listIndex}/cards/-` as Pointer, - label: `${list.name} /cards/-`, - })), - [doc.value], - ); - const clipboardSnapshot = doc.clipboard.read(); - const payloadValue = useMemo(() => { - const parsed = parseJson(payload); - if (!parsed.ok) return { __invalid_json: parsed.message }; - return parsed.value; - }, [payload]); - const pointsValue = useMemo(() => parseNumberPayload(pointsPayload), [pointsPayload]); - const badPointsValue = useMemo(() => parseNumberPayload(badPointsPayload), [badPointsPayload]); - const selectedSource = selectedPointers.length > 0 ? selectedPointers : valueTarget; - const primarySource = valueTarget; - const targetTitlePath = `${valueTarget}/title` as Pointer; - const targetPointsPath = `${valueTarget}/points` as Pointer; - const canInsertPayload = doc.canInsert(insertTarget, payloadValue); - const canPatchReplaceTitle = doc.canPatch([{ op: "replace", path: targetTitlePath, value: textPayload }]); - const canPatchReplacePoints = doc.canPatch([{ op: "replace", path: targetPointsPath, value: pointsValue }]); - const canPatchBadPoints = doc.canPatch([{ op: "replace", path: targetPointsPath, value: badPointsValue }]); - const canPatchDeleteTarget = doc.canPatch([{ op: "remove", path: valueTarget }]); - const canDuplicateTarget = doc.canDuplicate(valueTarget, { rekey: cardRekey() }); - const canMoveTarget = doc.canMove(valueTarget, insertTarget); - const canReplaceTargetTitle = doc.canReplace(targetTitlePath, textPayload); - const canDeleteSource = doc.canDelete(selectedSource); - const canCopySource = doc.canCopy(selectedSource); - const canCopyPrimary = doc.canCopy(primarySource); - const canCutSource = doc.canCut(selectedSource); - const canPasteClipboardAfterTarget = doc.canPaste({ after: valueTarget }); - const canPasteClipboardToInsertTarget = doc.canPaste(insertTarget, { spread: true, rekey: cardRekey() }); - const canInsertPayloadToInsertTarget = doc.canInsert(insertTarget, payloadValue, { rekey: cardRekey() }); - const canFindQuery = doc.canFind(query); - const canUndo = doc.canUndo(); - const canRedo = doc.canRedo(); - const selectedCount = selectedPointers.length; - const activeStage = featureStages.find((stage) => stage.id === activeStageId) ?? featureStages[0]!; - const selectedCardReason = selectedCount === 1 - ? undefined - : selectedCount === 0 - ? "state: select_one_card" - : "state: single_card_only"; - const bulkSelectionReason = selectedCount > 1 ? undefined : "state: select_multiple_cards"; - - const run = (call: string, action: () => unknown, feature?: string): void => { - const before = doc.value; - const bindings = feature ? featureBindings(feature, call, before) : []; - try { - const value = action(); - const output = value ?? doc.value; - const effects = operationEffects(output); - const next: BenchResult = { - call, - value: output, - feature, - bindings, - effect: effects.length > 0 - ? effects - : mutatesBoard(call) - ? boardEffect(before, resultBoard(output, doc.value)) - : [], - }; - setResult(next); - if (feature) { - setFeatureResults((current) => ( - call.includes("doc.load(") || call.includes("doc.reset(") - ? { [feature]: next } - : { ...current, [feature]: next } - )); - } - } catch (error) { - const next: BenchResult = { - call, - value: error instanceof Error ? error.message : error, - feature, - bindings, - effect: [], - }; - setResult(next); - if (feature) setFeatureResults((current) => ({ ...current, [feature]: next })); - } - }; - - const featureResult = (feature: string): BenchResult | undefined => ( - featureResults[feature] - ); - - const changeInsertTarget = (target: Pointer): void => { - setInsertTarget(target); - const status = statusForInsertTarget(doc.value, target); - if (status) setPayload((current) => payloadWithStatus(current, status)); - }; - - const selectNoCards = (): unknown => { - doc.selection?.empty(); - return doc.selection?.snapshot(); - }; - - const selectFirstCard = (): unknown => { - const pointer = cardPointer(0, 0); - setValueTarget(pointer); - doc.selection?.collapse(pointer); - return doc.selection?.snapshot(); - }; - - const featureBindings = (feature: string, call: string, board: Board): string[] => { - const title = cardTitleAt(board, valueTarget); - if (feature === "Insert card") { - const card = payloadValue as { title?: unknown; status?: unknown }; - return [ - `target ${insertTarget}`, - `payload ${card.title ?? "payload"}`, - `status ${card.status ?? "payload"}`, - ]; - } - if (feature === "Edit card") return [`path ${targetTitlePath}`, `value ${textPayload}`]; - if (feature === "Move card") return [`source ${valueTarget}`, `target ${insertTarget}`, title ? `card ${title}` : "card unknown"]; - if (feature === "Duplicate card") return [`source ${valueTarget}`, title ? `card ${title}` : "card unknown", "rekey id:suffix"]; - if (feature === "Find and select") return [`query ${query}`]; - if (feature === "Copy and paste") { - if (call.includes("canCopy") || call.includes("copy(")) return [`source ${primarySource}`, title ? `card ${title}` : "card unknown"]; - if (call.includes("after")) return [`target after ${valueTarget}`, title ? `after ${title}` : "after unknown"]; - return [`target ${insertTarget}`]; - } - if (feature === "Bulk cards") return [`source ${selectedLabel(Array.isArray(selectedSource) ? selectedSource : [selectedSource])}`]; - if (feature === "Selection set") return [`selected ${selectedCount}`]; - if (feature === "Undo and redo") return [`undo ${doc.history.undoDepth}`, `redo ${doc.history.redoDepth}`]; - if (feature === "Read schema") { - if (call.includes("schema.")) return [`schema target ${insertTarget}`]; - if (call.includes("entries")) return ['entries /lists/0/cards']; - return [`value ${valueTarget}`]; - } - if (feature === "Board plumbing") { - if (call.includes("doc.load(")) return ["fixture Loaded fixture"]; - if (call.includes("doc.reset(")) return ["initial board"]; - if (call.includes("doc.subscribe(")) return ["watch /settings/owner"]; - if (call.includes("applyPatch(")) return ["external patch"]; - if (call.includes("pointer helpers")) return ["path /lists/0/cards/0/title"]; - if (call.includes("trackPointer(")) return ["track /lists/0/cards/1/title"]; - if (call.includes("clipboard.write(")) return [`source ${valueTarget}`]; - if (call.includes("clipboard.clear(")) return ["clipboard buffer"]; - if (call.includes("selection?.textPatch(")) return [`text ${textPayload}`]; - if (call.includes("schema.kind(")) return [`value ${valueTarget}`]; - } - return []; - }; - - const parsedPayload = (): unknown => { - return payloadValue; - }; - - const insertCardToTodo = (): unknown => doc.insert(insertTarget, parsedPayload()); - - const copySelection = (): unknown => doc.copy(selectedSource); - - const copyPrimaryCard = (): unknown => doc.copy(primarySource); - - const pasteClipboardAfterTarget = (): unknown => doc.paste({ after: valueTarget }); - - const pasteClipboardToInsertTarget = (): unknown => doc.paste(insertTarget, { - spread: true, - rekey: cardRekey(), - }); - - const insertPayloadAfterTarget = (): unknown => doc.insert( - { after: valueTarget }, - parsedPayload(), - { rekey: cardRekey() }, - ); - - const insertPayloadToInsertTarget = (): unknown => doc.insert( - insertTarget, - parsedPayload(), - { rekey: cardRekey() }, - ); - - const duplicateTarget = (): unknown => { - const duplicated = doc.duplicate(valueTarget, { rekey: cardRekey() }); - if (duplicated.ok) { - setValueTarget(duplicated.duplicatedTo); - doc.selection?.collapse(duplicated.duplicatedTo); - } - return duplicated; - }; - - const selectTodoCards = (): unknown => { - const matches = doc.find(query); - if (!matches.ok) return matches; - doc.selection?.selectRanges(matches.pointers, undefined, undefined, Math.max(0, matches.pointers.length - 1)); - return matches; - }; - - const replaceTitleText = (): unknown => { - const selection = doc.selection?.snapshot(); - const hasTitleSelection = selection?.selectedPointers.includes("/title") ?? false; - if (!hasTitleSelection) doc.selection?.collapse({ path: "/title", offset: 0 }); - const planned = doc.selection?.textPatch(textPayload); - return planned?.ok - ? doc.commit(planned.patch, { mergeKey: "title-text", selectionAfter: planned.selection }) - : planned; - }; - - const commitReplaceTitle = (): unknown => { - return doc.commit( - [{ op: "replace", path: targetTitlePath, value: textPayload }], - { label: "commit", selectionAfter: collapsedSelection(valueTarget) }, - ); - }; - - const transactionRename = (): unknown => { - doc.history.transaction({ label: "rename-two" }, () => { - doc.patch({ op: "replace", path: "/lists/0/cards/0/title", value: "Batch A" }); - doc.patch({ op: "replace", path: "/lists/0/cards/1/title", value: "Batch B" }); - }); - return doc.value; - }; - - const queryPointers = (): unknown => doc.find(query); - - const inspectCreateJSONDocument = (): unknown => { - const headless = createJSONDocument(BoardSchema, initialBoard, { - history: 10, - selection: { mode: "extended", initial: [cardPointer(0, 0)] }, - }); - headless.patch({ op: "copy", from: cardPointer(0, 0), path: "/lists/0/cards/-" }); - headless.selection?.togglePointer(cardPointer(0, 1)); - headless.copy(headless.selection?.selectedPointers ?? []); - headless.paste("/lists/1/cards/-"); - - return { - title: headless.value.title, - cards: headless.value.lists.map((list) => list.cards.length), - selection: headless.selection?.selectedPointers, - canUndo: headless.history.canUndo, - }; - }; - - const inspectApplyOperation = (): unknown => applyOperation(BoardSchema, doc.value, { - op: "replace", - path: "/title", - value: "Single op", - }); - - const inspectApplyPatch = (): unknown => { - const patch: JSONPatchOperation[] = [ - { op: "add", path: "/lists/0/cards/0/tags/-", value: "patched" }, - { op: "move", from: "/lists/0/cards/1", path: "/lists/1/cards/1" }, - ]; - return applyPatch(BoardSchema, doc.value, patch); - }; - - const inspectApplyPatchToTrustedState = (): unknown => applyPatchToTrustedState(BoardSchema, doc.value, [ - { op: "replace", path: "/settings/owner", value: "trusted" }, - ]); - - const inspectBoundaryErrors = (): unknown => { - let pointerError = false; - try { - parsePointer("lists/0/cards/0" as Pointer); - } catch (error) { - pointerError = error instanceof PointerSyntaxError; - } - const crudError = new JSONDocumentError("patch", { - ok: false, - code: "path_not_found", - reason: "demo", - } as never); - return { - JSONDocumentError: { name: crudError.name, message: crudError.message }, - PointerSyntaxError: pointerError, - }; - }; - - const inspectPointerHelpers = (): unknown => { - return { - parsePointer: parsePointer("/lists/0/cards/0"), - tryParsePointer: tryParsePointer("/lists/0/cards/0"), - buildPointer: buildPointer(["lists", 0, "cards", 0]), - appendSegment: appendSegment("/lists/0/cards", 0), - parentPointer: parentPointer("/lists/0/cards/0/title"), - lastSegment: lastSegment("/lists/0/cards/0/title"), - lastSegmentIndex: lastSegmentIndex("/lists/0/cards/12"), - withLastSegment: withLastSegment("/lists/0/cards/0/title", "points"), - escapeSegment: escapeSegment("a/b~c"), - unescapeSegment: unescapeSegment("a~1b~0c"), - }; - }; - - const inspectTrackPointer = (): unknown => trackPointer("/lists/0/cards/1/title", [ - { op: "add", path: "/lists/0/cards/0/tags/-", value: "patched" }, - { op: "move", from: "/lists/0/cards/1", path: "/lists/1/cards/1" }, - ]); - - const inspectReactFacade = (): unknown => ({ - value: doc.value, - selection: doc.selection?.snapshot(), - history: { undo: doc.history.undoDepth, redo: doc.history.redoDepth }, - clipboard: doc.clipboard.read(), - schema: doc.schema.kind(valueTarget), - }); - - const inspectSubscribe = (): unknown => { - const events: unknown[] = []; - const unsubscribe = doc.subscribe((applied, metadata) => { - events.push({ applied, metadata }); - }); - const applied = doc.patch({ op: "replace", path: "/settings/owner", value: `sub-${doc.history.undoDepth}` }); - unsubscribe(); - return { applied, events }; - }; - - const inspectSelectionReads = (): unknown => ({ - selectedPointers: doc.selection?.selectedPointers, - selectionRanges: doc.selection?.selectionRanges, - primaryIndex: doc.selection?.primaryIndex, - rangeCount: doc.selection?.rangeCount, - selectedCount: doc.selection?.selectedCount, - hasSelection: doc.selection?.hasSelection, - isCollapsed: doc.selection?.isCollapsed, - type: doc.selection?.type, - primaryRange: doc.selection?.primaryRange, - anchorPointer: doc.selection?.anchorPointer, - focusPointer: doc.selection?.focusPointer, - selectedSource: doc.selection?.selectedSource, - primaryPointer: doc.selection?.primaryPointer, - caret: doc.selection?.caret, - caretPointer: doc.selection?.caretPointer, - context: doc.selection?.context, - anchor: doc.selection?.anchor, - focus: doc.selection?.focus, - }); - - const selectionRestoreRoundtrip = (): unknown => { - const snap = doc.selection?.snapshot(); - doc.selection?.empty(); - if (snap) doc.selection?.restore(snap); - return doc.selection?.snapshot(); - }; - - const selectionSubscribeOnce = (): unknown => { - const events: unknown[] = []; - const unsubscribe = doc.selection?.subscribe((snapshot, previous) => { - events.push({ snapshot, previous }); - }); - doc.selection?.collapse(valueTarget); - unsubscribe?.(); - return events; - }; - - const resetTargets = (): void => { - setValueTarget(cardPointer(0, 0)); - setInsertTarget("/lists/0/cards/-" as Pointer); - }; - - const loadFixture = (): unknown => { - resetTargets(); - return doc.load({ - ...initialBoard, - title: "Loaded fixture", - settings: { archived: true, owner: "fixture" }, - }); - }; - const resetBoard = (): unknown => { - resetTargets(); - return doc.reset(); - }; - const deleteTarget = (): unknown => doc.delete(valueTarget); - const deleteTargets = (): unknown => doc.delete(selectedSource); - - const executeInsertCard = (): void => { - const disabled = canDisabledReason(canInsertPayload); - if (disabled) return; - run(`doc.insert("${insertTarget}", payload)`, insertCardToTodo, "Insert card"); - }; - - const executeSelectNone = (): void => { - run("doc.selection?.empty()", selectNoCards, "Selection set"); - }; - - const executeSelectOne = (): void => { - run(`doc.selection?.collapse("${cardPointer(0, 0)}")`, selectFirstCard, "Selection set"); - }; - - const executeSelectMany = (): void => { - run("doc.selection?.selectRanges(queryPointers)", selectTodoCards, "Selection set"); - }; - - const executeSelectSearchResults = (): void => { - const disabled = canDisabledReason(canFindQuery); - if (disabled) return; - run("doc.selection?.selectRanges(queryPointers)", selectTodoCards, "Find and select"); - }; - - const executeUndo = (): void => { - const disabled = canDisabledReason(canUndo); - if (disabled) return; - run("doc.undo()", () => doc.undo(), "Undo and redo"); - }; - - const executeRedo = (): void => { - const disabled = canDisabledReason(canRedo); - if (disabled) return; - run("doc.redo()", () => doc.redo(), "Undo and redo"); - }; - - const executeRenameCard = (): void => { - const disabled = selectedCardReason ?? canDisabledReason(canPatchReplaceTitle); - if (disabled) return; - run(`doc.replace("${targetTitlePath}", textPayload)`, () => doc.replace(targetTitlePath, textPayload), "Edit card"); - }; - - const executeMoveCard = (): void => { - const disabled = selectedCardReason ?? canDisabledReason(canMoveTarget); - if (disabled) return; - run(`doc.move("${valueTarget}", "${insertTarget}")`, () => doc.move(valueTarget, insertTarget), "Move card"); - }; - - const executeDuplicateCard = (): void => { - const disabled = selectedCardReason ?? canDisabledReason(canDuplicateTarget); - if (disabled) return; - run(`doc.duplicate("${valueTarget}", { rekey })`, duplicateTarget, "Duplicate card"); - }; - - const executeCopyCommand = (): void => { - if (selectedCount > 1) { - const disabled = bulkSelectionReason ?? canDisabledReason(canCopySource); - if (disabled) return; - run("doc.copy(source)", copySelection, "Bulk cards"); - return; - } - const disabled = selectedCardReason ?? canDisabledReason(canCopyPrimary); - if (disabled) return; - run(`doc.copy("${primarySource}")`, copyPrimaryCard, "Copy and paste"); - }; - - const executeCutCommand = (): void => { - const disabled = selectedCount > 1 - ? bulkSelectionReason ?? canDisabledReason(canCutSource) - : selectedCardReason ?? canDisabledReason(canCutSource); - if (disabled) return; - run("doc.cut(source)", () => doc.cut(selectedSource), selectedCount > 1 ? "Bulk cards" : "Copy and paste"); - }; - - const executePasteCommand = (): void => { - if (selectedCount > 1) { - const disabled = bulkSelectionReason ?? canDisabledReason(canPasteClipboardToInsertTarget); - if (disabled) return; - run(`doc.paste("${insertTarget}", { spread: true, rekey })`, pasteClipboardToInsertTarget, "Bulk cards"); - return; - } - const disabled = selectedCardReason ?? canDisabledReason(canPasteClipboardAfterTarget); - if (disabled) return; - run(`doc.paste({ after: "${valueTarget}" })`, pasteClipboardAfterTarget, "Copy and paste"); - }; - - const executeDeleteCommand = (): void => { - if (selectedCount > 1) { - const disabled = bulkSelectionReason ?? canDisabledReason(canDeleteSource); - if (disabled) return; - run("doc.delete(source)", deleteTargets, "Bulk cards"); - return; - } - const disabled = selectedCardReason ?? canDisabledReason(canPatchDeleteTarget); - if (disabled) return; - run(`doc.delete("${valueTarget}")`, deleteTarget, "Delete card"); - }; - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent): void => { - if (event.defaultPrevented || event.repeat || isEditableShortcutTarget(event.target)) return; - - const key = event.key.toLowerCase(); - const mod = event.metaKey || event.ctrlKey; - if (mod && !event.altKey && key === "z") { - event.preventDefault(); - if (event.shiftKey) executeRedo(); - else executeUndo(); - return; - } - if (mod && !event.altKey && key === "y") { - event.preventDefault(); - executeRedo(); - return; - } - if (mod || event.altKey) return; - - if (key === "n") { - event.preventDefault(); - executeInsertCard(); - } else if (key === "f") { - event.preventDefault(); - executeSelectSearchResults(); - } else if (key === "e") { - event.preventDefault(); - executeRenameCard(); - } else if (key === "m") { - event.preventDefault(); - executeMoveCard(); - } else if (key === "d") { - event.preventDefault(); - executeDuplicateCard(); - } else if (key === "c") { - event.preventDefault(); - executeCopyCommand(); - } else if (key === "x") { - event.preventDefault(); - executeCutCommand(); - } else if (key === "v") { - event.preventDefault(); - executePasteCommand(); - } else if (key === "delete" || key === "backspace") { - event.preventDefault(); - executeDeleteCommand(); - } else if (key === "0") { - event.preventDefault(); - executeSelectNone(); - } else if (key === "1") { - event.preventDefault(); - executeSelectOne(); - } else if (key === "2") { - event.preventDefault(); - executeSelectMany(); - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }); - - const valueTargetInput = (label: string): ReactNode => ( - - - - ); - - const insertTargetInput = (label: string): ReactNode => ( - - - - ); - - const payloadInput = (label: string): ReactNode => ( - -