Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Publish v2 prerelease package
name: Publish v2 package

on:
push:
tags:
- "json-document-v*-rc.*"
- "json-document-collaboration-v*-rc.*"
- "json-document-contenteditable-collaboration-v*-rc.*"
- "json-document-v*"
- "json-document-collaboration-v*"
- "json-document-contenteditable-collaboration-v*"

permissions:
contents: read
Expand All @@ -32,7 +32,7 @@ jobs:
node-version: "24"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- name: Resolve prerelease package
- name: Resolve release package
id: release
env:
RELEASE_TAG: ${{ github.ref_name }}
Expand Down Expand Up @@ -68,19 +68,24 @@ jobs:
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(pkg.version)) {
throw new Error(`package version is not an rc prerelease: ${pkg.version}`);
const prerelease = /^\d+\.\d+\.\d+-rc\.\d+$/.test(pkg.version);
const stable = /^\d+\.\d+\.\d+$/.test(pkg.version);
if (!prerelease && !stable) {
throw new Error(`package version is not a supported stable or rc version: ${pkg.version}`);
}
if (pkg.publishConfig?.tag !== "next") {
throw new Error("publishConfig.tag must remain next for prereleases");
const distTag = prerelease ? "next" : "latest";
if (pkg.publishConfig?.tag !== distTag) {
throw new Error(`publishConfig.tag must be ${distTag} for ${pkg.version}`);
}
appendFileSync(process.env.GITHUB_OUTPUT, `workspace=${release.workspace}\n`);
appendFileSync(process.env.GITHUB_OUTPUT, `dist_tag=${distTag}\n`);
'
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Verify v2 prerelease graph
- name: Verify v2 release graph
run: npm run release:check
- name: Publish prerelease package
- name: Publish package
env:
RELEASE_WORKSPACE: ${{ steps.release.outputs.workspace }}
run: npm publish -w "$RELEASE_WORKSPACE" --tag next --access public --provenance
RELEASE_DIST_TAG: ${{ steps.release.outputs.dist_tag }}
run: npm publish -w "$RELEASE_WORKSPACE" --tag "$RELEASE_DIST_TAG" --access public --provenance
2 changes: 1 addition & 1 deletion apps/site/src/routes/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function Home() {

<div className="rounded border border-stone-200 bg-stone-950 p-3 text-stone-100">
<div className="mb-2 text-xs font-medium text-stone-400">Install</div>
<pre className="m-0 overflow-x-auto text-sm leading-6"><code>npm install @interactive-os/json-document@2.0.0-rc.0</code></pre>
<pre className="m-0 overflow-x-auto text-sm leading-6"><code>npm install @interactive-os/json-document@2.0.0</code></pre>
<div className="mt-4 border-t border-stone-800 pt-3 text-xs font-medium text-stone-400">Start</div>
<pre className="m-0 mt-2 overflow-x-auto text-sm leading-6"><code>{`import { createJSONDocument } from "@interactive-os/json-document";`}</code></pre>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/site/tests/docs-consistency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ describe("public docs consistency", () => {
expect(docs.quickstart).toMatch(/튜토리얼: 작은 카드 편집기 만들기/);
expect(docs.api).toMatch(/## 작업별 진입점/);
expect(docs.api).toMatch(/ReadResult/);
expect(docs.readme).toMatch(/npm install @interactive-os\/json-document@2\.0\.0-rc\.0/);
expect(docs.readme).toMatch(/npm install @interactive-os\/json-document@2\.0\.0/);
expect(docs.readme).toMatch(/provider-neutral/);
expect(docs.llms).toMatch(/2\.0\.0-rc\.0.*Candidate/);
expect(docs.llms).toMatch(/2\.0\.0.*Stable/);
});

test("keeps JSONPath scoped to search and JSON Pointer scoped to mutation", () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/site/tests/site-shell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("official site shell", () => {
expect(screen.getByRole("link", { name: "Skip to content" }).getAttribute("href")).toBe("#main-content");
expect(screen.getByRole("heading", { level: 1, name: "json-document" })).toBeTruthy();
expect(screen.getByText(/Provider-neutral JSON editing/)).toBeTruthy();
expect(screen.getByText("npm install @interactive-os/json-document@2.0.0-rc.0")).toBeTruthy();
expect(screen.getByText("npm install @interactive-os/json-document@2.0.0")).toBeTruthy();
expect(screen.getByText('import { createJSONDocument } from "@interactive-os/json-document";')).toBeTruthy();
expect(screen.getByRole("link", { name: "npm" }).getAttribute("href")).toBe("https://www.npmjs.com/package/@interactive-os/json-document");
expect(screen.getByRole("link", { name: "GitHub" }).getAttribute("href")).toBe("https://github.com/developer-1px/json-document");
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to the active v2 packages are documented here. The complete

## Unreleased

- Declared the `2.0.0` Projection Profile Stable after the same black-box suite
passed the public reference binding and a reference-free independent
implementation across form, table/data-grid, outliner/tree, rich text, and
storage/collaboration pressure vectors.
- Converged Core JSON equality and canonical array-index parsing to one
primitive each, locked collaboration package-local equivalents to shared
vectors, and recorded the intentional validation/owning-clone/trusted-clone
responsibility split.
- Added independent prerelease tag publishing for the Core and both optional
collaboration packages, and kept the live-site archive gate aware of the two
supported v2 companions.
Expand Down
6 changes: 3 additions & 3 deletions docs/generated/repo-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"status": "core",
"private": false,
"publishable": true,
"version": "2.0.0-rc.0",
"version": "2.0.0",
"description": "Provider-neutral JSON editing protocol and headless document projection.",
"license": "MIT",
"summary": "문서, 표, 슬라이드, 캔버스, 노트 편집기가 함께 쓸 수 있는 provider-neutral\nJSON 편집 protocol과 headless document projection입니다.",
Expand Down Expand Up @@ -59,7 +59,7 @@
"status": "companion",
"private": false,
"publishable": true,
"version": "0.1.0-rc.0",
"version": "0.1.0-rc.1",
"description": "Transport-free causal collaboration provider for @interactive-os/json-document.",
"license": "MIT",
"summary": "Transport-free causal collaboration provider for the six-member\n`@interactive-os/json-document` Projection contract.",
Expand Down Expand Up @@ -119,7 +119,7 @@
"status": "companion",
"private": false,
"publishable": true,
"version": "0.1.0-rc.0",
"version": "0.1.0-rc.1",
"description": "IME-safe contenteditable publication lease for json-document collaboration text.",
"license": "MIT",
"summary": "IME-safe DOM publication lease for\n`@interactive-os/json-document-collaboration/text`.",
Expand Down
36 changes: 30 additions & 6 deletions docs/standard/v2-projection-profile.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# json-document v2 Projection Profile

상태: Candidate (`2.0.0-rc.0`). TypeScript root binding과 reference
implementation은 구현됐지만 stable gate는 아직 통과하지 않았다.
상태: Stable (`2.0.0`). TypeScript root binding, reference implementation,
독립 test implementation, 다섯 pressure vertical이 stable gate를 통과했다.

이 profile은 문서, 표, 슬라이드, 캔버스, 노트 편집기가 공통으로 의존할
수 있는 최소 JSON 편집 계약을 정의한다. 구현체는 더 많은 기능을 제공할 수
Expand Down Expand Up @@ -159,17 +159,41 @@ conformance corpus의 public-root binding을 서로 분리한다.
| `packages/json-document/tests/public/v2-rfc6902-standard-conformance.test.ts` | public root `applyPatch`의 전체 RFC 6902 corpus binding |
| `packages/json-document/tests/conformance/v2/jsonpath-suite.ts` | vendored RFC 9535 CTS를 `query`와 `at`으로 검증하는 runner |
| `packages/json-document/tests/public/v2-jsonpath-standard-conformance.test.ts` | public root Projection의 전체 RFC 9535 CTS binding |
| `packages/json-document/tests/conformance/v2/foundation-vectors.json` | Core와 collaboration package-local primitive의 array index·equality parity 및 JSON boundary fixture |
| `packages/json-document/tests/conformance/v2/pressure-vectors.json` | form, table/data-grid, outliner/tree, rich text, storage/collaboration 시나리오 |
| `packages/json-document/tests/conformance/v2/pressure-suite.ts` | 여섯 member만으로 다섯 vertical을 실행하는 injected runner |
| `packages/json-document/tests/independent/v2-projection.ts` | reference runtime을 import하지 않는 독립 6-member test implementation |
| `packages/json-document/tests/independent/v2-projection-independent-conformance.test.ts` | 독립 구현에 Projection과 pressure suite를 함께 주입하는 binding |
| `packages/json-document-collaboration/tests/projection-conformance.test.ts` | collaboration public root에 같은 두 suite를 주입하는 추가 binding |

suite가 export하는 structural type은 test harness 내부 계약이며 package public
export가 아니다. Vendored RFC 6902 corpus 112건 중 110건을 실행한다. 남은 2건은
원본 JSON에 중복 `op` member가 있지만 JSON module parsing 단계에서 마지막
member만 남아 public operation object로는 그 입력을 표현할 수 없어, 각 fixture에
명시적인 `disabledReason`을 기록한다.

21개 요구사항의 현재 증거 상태는 runtime 13개, static 6개, deferred 2개다.
schema-free `applyPatch`는 구현됐다. transform identity의 two-provider 검증,
독립 구현과 다섯 pressure vertical 통과는 아직 deferred이므로 Candidate를
stable이라고 선언할 수 없다.
21개 요구사항의 현재 증거 상태는 runtime 15개, static 6개, deferred 0개다.
schema-free `applyPatch`와 acceptance transform identity는 reference와 독립
구현에서 같은 vector로 검증한다. 같은 Projection suite와 pressure suite는
reference와 독립 구현을 모두 통과하며, collaboration public binding도 같은
다섯 vertical을 통과한다. collaboration 구현은 Core protocol을 조합하므로
독립 구현 수에는 포함하지 않는다.

## Durability primitive boundary

Core의 JSON equality는 하나의 equality leaf가 소유한다. canonical array index
`[0]|[1-9][0-9]*`와 JavaScript safe-integer 경계도 하나의 Pointer leaf가
소유하며, RFC 6902 append marker `-`는 write 문맥에서만 별도로 처리한다.
collaboration package는 Core private path를 deep import하지 않고 package-local
두 primitive를 유지하며 `foundation-vectors.json`을 함께 실행해 의미 차이를
드러낸다.

JSON validation, owning clone, trusted clone은 하나의 traversal abstraction으로
합치지 않는다. validation은 오류 위치를 설명하고, owning clone은 검증과
reference 격리를 함께 수행하며, trusted clone은 이미 검증된 값만 받는
precondition을 가진다. validation과 owning clone이 공유해야 하는
array-property 분류만 공통 leaf에 두고, parity test가 untrusted boundary의
동일한 성공·실패와 trusted clone의 소유권 분리를 고정한다.

## Package binding

Expand Down
12 changes: 9 additions & 3 deletions docs/standard/v2-public-surface.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"formatVersion": 1,
"status": "candidate",
"status": "stable",
"sourceContract": "packages/json-document/public-contract.json#root",
"package": {
"name": "@interactive-os/json-document",
"version": "2.0.0-rc.0",
"version": "2.0.0",
"entrypoint": ".",
"excludedEntrypoints": [
"./session",
Expand All @@ -26,7 +26,13 @@
"rfc6902Suite": "packages/json-document/tests/conformance/v2/rfc6902-suite.ts",
"rfc6902Binding": "packages/json-document/tests/public/v2-rfc6902-standard-conformance.test.ts",
"jsonPathSuite": "packages/json-document/tests/conformance/v2/jsonpath-suite.ts",
"jsonPathBinding": "packages/json-document/tests/public/v2-jsonpath-standard-conformance.test.ts"
"jsonPathBinding": "packages/json-document/tests/public/v2-jsonpath-standard-conformance.test.ts",
"foundationVectors": "packages/json-document/tests/conformance/v2/foundation-vectors.json",
"pressureVectors": "packages/json-document/tests/conformance/v2/pressure-vectors.json",
"pressureSuite": "packages/json-document/tests/conformance/v2/pressure-suite.ts",
"independentProjectionImplementation": "packages/json-document/tests/independent/v2-projection.ts",
"independentProjectionBinding": "packages/json-document/tests/independent/v2-projection-independent-conformance.test.ts",
"collaborationProjectionBinding": "packages/json-document-collaboration/tests/projection-conformance.test.ts"
},
"requirements": [
"JD2-GOV-001",
Expand Down
6 changes: 3 additions & 3 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
json-document는 문서, 표, 슬라이드, 캔버스, 노트 편집기가 공통으로 사용할 수
있는 provider-neutral JSON 편집 Kernel이다. UI component library가 아니다.

현재 package version과 표준 상태는 `2.0.0-rc.0` Candidate다. Root binding은
구현됐지만 독립 구현과 다섯 pressure vertical의 conformance gate가 남아 있으므로
stable이라고 부르지 않는다.
현재 package version과 표준 상태는 `2.0.0` Stable이다. Reference와 독립
implementation이 같은 conformance suite를 통과하고, 다섯 pressure vertical을
reference·독립·collaboration binding에서 검증한다.

공식 사이트: https://developer-1px.github.io/json-document/

Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/contenteditable-collaboration/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@interactive-os/json-document-contenteditable-collaboration",
"version": "0.1.0-rc.0",
"version": "0.1.0-rc.1",
"description": "IME-safe contenteditable publication lease for json-document collaboration text.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -46,8 +46,8 @@
"verify": "npm run typecheck && npm test && npm run build"
},
"peerDependencies": {
"@interactive-os/json-document": "^2.0.0-rc.0",
"@interactive-os/json-document-collaboration": "^0.1.0-rc.0"
"@interactive-os/json-document": "^2.0.0",
"@interactive-os/json-document-collaboration": "^0.1.0-rc.1"
},
"devDependencies": {
"@interactive-os/json-document": "*",
Expand Down
4 changes: 2 additions & 2 deletions packages/json-document-collaboration/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@interactive-os/json-document-collaboration",
"version": "0.1.0-rc.0",
"version": "0.1.0-rc.1",
"description": "Transport-free causal collaboration provider for @interactive-os/json-document.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -46,7 +46,7 @@
"verify": "npm run typecheck && npm test && npm run build"
},
"peerDependencies": {
"@interactive-os/json-document": "^2.0.0-rc.0"
"@interactive-os/json-document": "^2.0.0"
},
"devDependencies": {
"@interactive-os/json-document": "*",
Expand Down
5 changes: 5 additions & 0 deletions packages/json-document-collaboration/src/arrayIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function parseArrayIndex(segment: string): number | null {
if (!/^(0|[1-9]\d*)$/.test(segment)) return null;
const index = Number(segment);
return Number.isSafeInteger(index) ? index : null;
}
5 changes: 1 addition & 4 deletions packages/json-document-collaboration/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
prepareGraph,
type PreparedGraph,
} from "./change.js";
import { jsonEqual } from "./jsonEqual.js";
import { createCheckpoint } from "./checkpoint.js";
import {
acceptCandidate,
Expand Down Expand Up @@ -1455,10 +1456,6 @@ function textFailure(
return Object.freeze({ ok: false, code, reason });
}

function jsonEqual(left: JSONValue, right: JSONValue): boolean {
return canonicalStringify(left) === canonicalStringify(right);
}

function failure(
code: string,
reason?: string,
Expand Down
7 changes: 7 additions & 0 deletions packages/json-document-collaboration/src/jsonEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { JSONValue } from "@interactive-os/json-document";

import { canonicalStringify } from "./change.js";

export function jsonEqual(left: JSONValue, right: JSONValue): boolean {
return canonicalStringify(left) === canonicalStringify(right);
}
7 changes: 1 addition & 6 deletions packages/json-document-collaboration/src/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from "@interactive-os/json-document";

import { changeIdKey } from "./change.js";
import { parseArrayIndex } from "./arrayIndex.js";
import {
applyAuthoredOperation,
arrayMembers,
Expand Down Expand Up @@ -404,12 +405,6 @@ function authoredMemberId(
return `member:${changeIdKey(changeId)}:${opIndex}`;
}

function parseArrayIndex(segment: string): number | null {
if (!/^(0|[1-9]\d*)$/.test(segment)) return null;
const index = Number(segment);
return Number.isSafeInteger(index) ? index : null;
}

function failure(code: string, reason: string): TreeFailure {
return { ok: false, code, reason };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { JSONValue } from "@interactive-os/json-document";
import { describe, expect, test } from "vitest";

import { parseArrayIndex } from "../src/arrayIndex.js";
import { jsonEqual } from "../src/jsonEqual.js";
import rawVectors from "../../json-document/tests/conformance/v2/foundation-vectors.json" with { type: "json" };

interface FoundationVectors {
readonly arrayIndexes: ReadonlyArray<{
readonly segment: string;
readonly value: number | null;
}>;
readonly equalities: ReadonlyArray<{
readonly left: JSONValue;
readonly right: JSONValue;
readonly equal: boolean;
}>;
}

const vectors = rawVectors as FoundationVectors;

describe("collaboration durability primitive parity", () => {
test("package-local array indexes match the shared Core vectors", () => {
for (const vector of vectors.arrayIndexes) {
expect(parseArrayIndex(vector.segment), vector.segment)
.toBe(vector.value);
}
});

test("package-local equality matches the shared Core vectors", () => {
for (const vector of vectors.equalities) {
expect(jsonEqual(vector.left, vector.right))
.toBe(vector.equal);
}
});
});
Loading