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
13 changes: 9 additions & 4 deletions apps/site/src/generated/repo-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ export const repoCatalog = {
"IdResolver",
"IdResolverDiagnostic",
"IdResolverDiagnosticCode",
"IdResolverDocument",
"IdResolverEntry",
"IdResolverOptions",
"IdResolverScope",
Expand All @@ -370,7 +371,7 @@ export const repoCatalog = {
"ResolveIdResult",
"createIdResolver"
],
"publicExportCount": 10,
"publicExportCount": 11,
"keywords": [
"@interactive-os/json-document",
"editing",
Expand Down Expand Up @@ -1271,6 +1272,7 @@ export const repoCatalog = {
"IdResolver",
"IdResolverDiagnostic",
"IdResolverDiagnosticCode",
"IdResolverDocument",
"IdResolverEntry",
"IdResolverOptions",
"IdResolverScope",
Expand All @@ -1279,7 +1281,7 @@ export const repoCatalog = {
"ResolveIdResult",
"createIdResolver"
],
"publicExportCount": 10,
"publicExportCount": 11,
"keywords": [
"@interactive-os/json-document",
"editing",
Expand Down Expand Up @@ -1860,6 +1862,7 @@ export const repoCatalog = {
"CausalIntentEnvelope",
"CausalMaterializationDiagnostic",
"CausalMaterializationPolicy",
"CausalPatchDocument",
"CausalPatchEnvelope",
"CausalPatchFailure",
"CausalPatchHost",
Expand All @@ -1870,6 +1873,7 @@ export const repoCatalog = {
"CausalPatchIngestErrorCode",
"CausalPatchIngestOk",
"CausalPatchIngestResult",
"CausalPatchPublicationDocument",
"CausalPositionalIntent",
"CausalPositionalMaterializationFailure",
"CausalStableIdReplaceIntent",
Expand All @@ -1879,7 +1883,7 @@ export const repoCatalog = {
"QueuedCausalPatch",
"createCausalPatchInbox"
],
"publicExportCount": 28,
"publicExportCount": 30,
"keywords": [
"@interactive-os/json-document",
"causal",
Expand Down Expand Up @@ -2982,12 +2986,13 @@ export const repoCatalog = {
},
"publicExports": [
"StableIdRebaseDiagnostic",
"StableIdRebaseDocument",
"StableIdRebaseResult",
"StableIdReplaceInput",
"StableIdTarget",
"rebaseStableChange"
],
"publicExportCount": 5,
"publicExportCount": 6,
"keywords": [
"@interactive-os/json-document",
"conflict",
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project are documented here.

## Unreleased

- Narrowed the id resolver, stable-id rebase, and causal inbox inputs to their
actual read/preflight/publication ports. Full `JSONDocument` values remain
compatible, while host adapters can guard a projection without exposing the
complete document command and history surface.

### Added

- Added lab-only ready-time materialization to the causal patch inbox for
Expand Down
6 changes: 3 additions & 3 deletions docs/generated/extensions-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Official extensions: 19
| `@interactive-os/json-document-dirty-state` | 7 | compare a document to a clean baseline | persistence or server save status | Official headless dirty state tracking extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-form-draft` | 15 | hold temporary invalid form input before committing valid JSON | rendered form components | Official headless form draft extension for temporary input that is not ready to enter a schema-valid `@interactive-os/json-document` document. |
| `@interactive-os/json-document-grouping` | 15 | group and ungroup selected sibling JSON items | Airtable group-by views | Official extension for structural `group` and `ungroup`. |
| `@interactive-os/json-document-id-resolver` | 10 | resolve scoped stable ids to current JSON Pointers | id generation, relation graphs, routing, or server identity | Official headless stable id resolver extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-id-resolver` | 11 | resolve scoped stable ids to current JSON Pointers | id generation, relation graphs, routing, or server identity | Official headless stable id resolver extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-outline` | 19 | project and edit nested document outline structures | Figma layer panels without a tree schema adapter | Official headless outline tree and structure editing extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-patch-log` | 9 | record and replay applied JSON Patch records | product activity feeds or audit authorization | Official headless patch recording and replay extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-patch-preview` | 8 | preview patch effects before confirmation | visual diff rendering | Official headless patch preview extension for `@interactive-os/json-document` documents. |
Expand All @@ -39,7 +39,7 @@ Lab extensions are private candidates. They are listed to show product pressure,
| `@interactive-os/json-document-batch-update` | lab-only | 10 | set a field across a list of selected item pointers to a constant or computed value | selecting which items to edit, or JSONPath query-driven replacement | Lab batch-update extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-bookmarks` | lab-only | 8 | keep named JSON Pointer locations stable across edits | browser bookmarks or route state | Headless bookmark tracking extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-calculated-fields` | lab-only | 12 | sync host-computed derived JSON fields | formula languages or dependency runtimes | Lab calculated field extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-causal-patch-inbox` | lab-only | 28 | queue dependency-declared envelopes and materialize delayed positional or stable-id edits when causally ready | CRDT/OT convergence, transport, persistence, or automatic conflict resolution | Lab module for admitting dependency-declared JSON Patch or authored edit envelopes, holding out-of-order work, and committing each envelope when it becomes causally ready on one `JSONDocument`. |
| `@interactive-os/json-document-causal-patch-inbox` | lab-only | 30 | queue dependency-declared envelopes and materialize delayed positional or stable-id edits when causally ready | CRDT/OT convergence, transport, persistence, or automatic conflict resolution | Lab module for admitting dependency-declared JSON Patch or authored edit envelopes, holding out-of-order work, and committing each envelope when it becomes causally ready on one `JSONDocument`. |
| `@interactive-os/json-document-change-case` | lab-only | 9 | apply case/whitespace transforms (upper, lower, trim, title) to a string field | locale-aware casing, rich text formatting toolbars, or find/replace | Lab change-case extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-checkpoints` | lab-only | 13 | name and restore document snapshots | durable version graphs or cloud backup | Lab checkpoint extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-clear-contents` | lab-only | 10 | reset selected fields to schema-derived empty values, keeping structure | structural delete, caller-supplied bulk replace, or enum/object default policy | Lab clear-contents extension for `@interactive-os/json-document` documents. |
Expand Down Expand Up @@ -69,7 +69,7 @@ Lab extensions are private candidates. They are listed to show product pressure,
| `@interactive-os/json-document-sort-items` | lab-only | 13 | sort or reverse JSON array items | query views, filters, or server sorting | Lab collection sort extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-sparse-record` | lab-only | 14 | set or remove keyed entries in sparse JSON records with add/replace/remove/no-op planning | 2D grid coordinate expansion, product key normalization, or rendered selection | Lab sparse-record entry editing extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-split-text` | lab-only | 9 | split a string into array items by a delimiter (tag input, paste-as-list) | CSV/TSV quoting, split-to-columns, or clipboard access | Lab split-text extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-stable-id-rebase` | lab-only | 5 | materialize one delayed field replacement against a current stable-id target | id policy, structural patches, CRDT/OT protocols, transport, or merge UI | Lab planner for materializing one delayed field replacement against the current Pointer of a host-owned stable id. |
| `@interactive-os/json-document-stable-id-rebase` | lab-only | 6 | materialize one delayed field replacement against a current stable-id target | id policy, structural patches, CRDT/OT protocols, transport, or merge UI | Lab planner for materializing one delayed field replacement against the current Pointer of a host-owned stable id. |
| `@interactive-os/json-document-swap-items` | lab-only | 8 | exchange the positions of two items in the same array | cross-array swaps or moving to an arbitrary index | Lab swap-items extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-toggle-option` | lab-only | 9 | toggle, add, or remove a value's presence in a JSON array (tag/multi-select) | ordered insertion position or deduping an existing array | Lab toggle-option extension for `@interactive-os/json-document` documents. |
| `@interactive-os/json-document-toggle-value` | lab-only | 10 | toggle a boolean or advance an enum/value field (enum options come from the schema) | rendered toggle controls or keyboard policy | Lab toggle-value extension for `@interactive-os/json-document` documents. |
Expand Down
13 changes: 9 additions & 4 deletions docs/generated/repo-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@
"IdResolver",
"IdResolverDiagnostic",
"IdResolverDiagnosticCode",
"IdResolverDocument",
"IdResolverEntry",
"IdResolverOptions",
"IdResolverScope",
Expand All @@ -369,7 +370,7 @@
"ResolveIdResult",
"createIdResolver"
],
"publicExportCount": 10,
"publicExportCount": 11,
"keywords": [
"@interactive-os/json-document",
"editing",
Expand Down Expand Up @@ -1270,6 +1271,7 @@
"IdResolver",
"IdResolverDiagnostic",
"IdResolverDiagnosticCode",
"IdResolverDocument",
"IdResolverEntry",
"IdResolverOptions",
"IdResolverScope",
Expand All @@ -1278,7 +1280,7 @@
"ResolveIdResult",
"createIdResolver"
],
"publicExportCount": 10,
"publicExportCount": 11,
"keywords": [
"@interactive-os/json-document",
"editing",
Expand Down Expand Up @@ -1859,6 +1861,7 @@
"CausalIntentEnvelope",
"CausalMaterializationDiagnostic",
"CausalMaterializationPolicy",
"CausalPatchDocument",
"CausalPatchEnvelope",
"CausalPatchFailure",
"CausalPatchHost",
Expand All @@ -1869,6 +1872,7 @@
"CausalPatchIngestErrorCode",
"CausalPatchIngestOk",
"CausalPatchIngestResult",
"CausalPatchPublicationDocument",
"CausalPositionalIntent",
"CausalPositionalMaterializationFailure",
"CausalStableIdReplaceIntent",
Expand All @@ -1878,7 +1882,7 @@
"QueuedCausalPatch",
"createCausalPatchInbox"
],
"publicExportCount": 28,
"publicExportCount": 30,
"keywords": [
"@interactive-os/json-document",
"causal",
Expand Down Expand Up @@ -2981,12 +2985,13 @@
},
"publicExports": [
"StableIdRebaseDiagnostic",
"StableIdRebaseDocument",
"StableIdRebaseResult",
"StableIdReplaceInput",
"StableIdTarget",
"rebaseStableChange"
],
"publicExportCount": 5,
"publicExportCount": 6,
"keywords": [
"@interactive-os/json-document",
"conflict",
Expand Down
9 changes: 9 additions & 0 deletions labs/extensions/causal-patch-inbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ observable. In-place mutation of an object reachable through `doc.value` emits
no subscription event and cannot be detected here; callers must treat document
values as owned projection state rather than a side channel for mutation.

Direct and positional configurations accept the narrow
`CausalPatchPublicationDocument` projection port: `value`, `commit`, and
`subscribe`. A stable-id configuration accepts `CausalPatchDocument`, which
adds `query`, `at`, and `canPatch`. The projection reference must change after
a concrete commit, and the matching subscription must run synchronously before
that commit returns. This lets a host validate external patches and hide
unrelated editing, history, clipboard, and schema APIs while a full
`JSONDocument` remains structurally compatible.

Typed patch failure is a clean seam: core keeps the JSON projection atomic, so
the inbox can leave its frontier unchanged. The first strict
`JSONDocumentError` still propagates but its structured result is retained as
Expand Down
50 changes: 45 additions & 5 deletions labs/extensions/causal-patch-inbox/src/create.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
JSONDocumentError,
type JSONDocument,
type JSONPatchOperation,
type SelectionPoint,
} from "@interactive-os/json-document";
Expand All @@ -22,6 +21,8 @@ import type {
CausalPatchInboxOptions,
CausalPatchInboxSnapshot,
CausalPatchIngestResult,
CausalPatchDocument,
CausalPatchPublicationDocument,
FailedCausalMaterialization,
FaultedCausalPatch,
} from "./types.js";
Expand Down Expand Up @@ -71,14 +72,32 @@ interface CommittedReadyEnvelope {
let nextInboxInstance = 0;

export function createCausalPatchInbox<TDocument>(
doc: JSONDocument<TDocument>,
options: CausalPatchInboxOptions<TDocument> = {},
doc: CausalPatchPublicationDocument<TDocument>,
options?: Omit<CausalPatchInboxOptions<TDocument>, "stableIdScopes"> & {
readonly stableIdScopes?: undefined;
},
): CausalPatchInbox<TDocument>;
// Keep the complete policy signature last for Parameters utility consumers.
export function createCausalPatchInbox<TDocument>(
doc: CausalPatchDocument<TDocument>,
options?: CausalPatchInboxOptions<TDocument>,
): CausalPatchInbox<TDocument>;
export function createCausalPatchInbox<TDocument>(
doc: CausalPatchPublicationDocument<TDocument>,
options: CausalPatchInboxOptions<TDocument> | (
Omit<CausalPatchInboxOptions<TDocument>, "stableIdScopes"> & {
readonly stableIdScopes?: undefined;
}
) = {},
): CausalPatchInbox<TDocument> {
const positionalSchema = options.positionalSchema;
const host = options.host;
const stableIdScopes = options.stableIdScopes === undefined
? undefined
: options.stableIdScopes.map((scope) => ({ ...scope }));
const stableIdDocument = stableIdScopes === undefined
? undefined
: requireStableIdDocument(doc);
const known = new Map<string, StoredEnvelope<TDocument>>();
const queued = new Map<string, PendingEnvelope<TDocument>>();
const dependents = new Map<string, Set<string>>();
Expand Down Expand Up @@ -343,6 +362,7 @@ export function createCausalPatchInbox<TDocument>(
materialized = materializeReadyEnvelope(
ready,
doc,
stableIdDocument,
positionalSchema,
stableIdScopes,
known,
Expand Down Expand Up @@ -729,7 +749,8 @@ export function createCausalPatchInbox<TDocument>(

function materializeReadyEnvelope<TDocument>(
ready: StoredEnvelope<TDocument>,
doc: JSONDocument<TDocument>,
doc: CausalPatchPublicationDocument<TDocument>,
stableIdDocument: CausalPatchDocument<TDocument> | undefined,
positionalSchema: CausalPatchInboxOptions<TDocument>["positionalSchema"],
stableIdScopes: CausalPatchInboxOptions<TDocument>["stableIdScopes"],
known: ReadonlyMap<string, StoredEnvelope<TDocument>>,
Expand Down Expand Up @@ -845,7 +866,10 @@ function materializeReadyEnvelope<TDocument>(
if (stableIdScopes === undefined) {
throw new Error("stable-id materialization policy was not configured");
}
const planned = rebaseStableChange(doc, {
if (stableIdDocument === undefined) {
throw new Error("stable-id materialization document port was not configured");
}
const planned = rebaseStableChange(stableIdDocument, {
scopes: stableIdScopes,
target: ready.intent.target,
relativePath: ready.intent.relativePath,
Expand Down Expand Up @@ -877,6 +901,22 @@ function materializeReadyEnvelope<TDocument>(
};
}

function requireStableIdDocument<TDocument>(
doc: CausalPatchPublicationDocument<TDocument>,
): CausalPatchDocument<TDocument> {
const candidate = doc as Partial<CausalPatchDocument<TDocument>>;
if (
typeof candidate.at !== "function"
|| typeof candidate.canPatch !== "function"
|| typeof candidate.query !== "function"
) {
throw new TypeError(
"stable-id materialization requires query, at, and canPatch document ports",
);
}
return candidate as CausalPatchDocument<TDocument>;
}

function collectCausalPast<TDocument>(
dependencies: ReadonlyArray<string>,
known: ReadonlyMap<string, StoredEnvelope<TDocument>>,
Expand Down
2 changes: 2 additions & 0 deletions labs/extensions/causal-patch-inbox/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type {
CausalMaterializationDiagnostic,
CausalMaterializationPolicy,
CausalPatchEnvelope,
CausalPatchDocument,
CausalPatchPublicationDocument,
CausalPatchFailure,
CausalPatchInbox,
CausalPatchInboxOptions,
Expand Down
21 changes: 21 additions & 0 deletions labs/extensions/causal-patch-inbox/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
JSONChangeMetadata,
JSONDocument,
JSONPatchOperation,
JSONResult,
Pointer,
Expand All @@ -11,6 +12,7 @@ import type {
RebaseSchema,
} from "@interactive-os/json-document-patch-rebase";
import type {
StableIdRebaseDocument,
StableIdRebaseDiagnostic,
StableIdRebaseResult,
StableIdReplaceInput,
Expand Down Expand Up @@ -67,6 +69,25 @@ export type CausalMaterializationPolicy =
| "positional"
| "stable-id-replace";

/**
* Publication port used by direct and positional inbox policies.
*
* `value` must return the same immutable reference while the projection is
* unchanged and a different reference after a concrete publication. `commit`
* must synchronously notify `subscribe` observers before returning, once for
* one concrete projection change and zero times for a successful
* no-op/test-only commit.
*/
export type CausalPatchPublicationDocument<TDocument> = Pick<
JSONDocument<TDocument>,
"commit" | "subscribe" | "value"
>;

/** Publication port plus the reads required by stable-id materialization. */
export type CausalPatchDocument<TDocument> =
CausalPatchPublicationDocument<TDocument>
& StableIdRebaseDocument<TDocument>;

export interface CausalHostPublication {
readonly operations: ReadonlyArray<JSONPatchOperation>;
readonly metadata?: JSONChangeMetadata;
Expand Down
Loading