@neta-art/cohub 8.11.0 → 8.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/board/animation.js +14 -2
  2. package/dist/board/core/file-preview.d.ts +2 -2
  3. package/dist/board/core/file-preview.js +2 -2
  4. package/dist/board/index.d.ts +2 -1
  5. package/dist/board/index.js +2 -1
  6. package/dist/board/mutation.js +2 -1
  7. package/dist/board/render/board-background.d.ts +16 -0
  8. package/dist/board/render/{themes/clean-theme.js → board-background.js} +28 -35
  9. package/dist/board/render/index.d.ts +3 -3
  10. package/dist/board/render/index.js +2 -2
  11. package/dist/board/render/renderers/file-card-renderer.js +3 -32
  12. package/dist/board/replay.d.ts +52 -0
  13. package/dist/board/replay.js +261 -0
  14. package/dist/board/semantic-document.d.ts +9 -2
  15. package/dist/board/semantic-document.js +1 -3
  16. package/dist/chunks/environment.d.ts +296 -9
  17. package/dist/chunks/environment.js +1 -0
  18. package/dist/chunks/http.d.ts +54 -4
  19. package/dist/chunks/http.js +67 -12
  20. package/dist/chunks/websocket.d.ts +1 -1
  21. package/dist/http.d.ts +3 -3
  22. package/dist/index.d.ts +11 -4
  23. package/dist/index.js +201 -51
  24. package/dist/protocol/dist/board-animation.d.ts +1 -0
  25. package/dist/protocol/dist/board-animation.js +34 -0
  26. package/dist/protocol/dist/board-authoring.d.ts +2 -1
  27. package/dist/protocol/dist/board-capability-registry.js +53 -3
  28. package/dist/protocol/dist/board-codec.js +149 -2
  29. package/dist/protocol/dist/board-constants.js +29 -7
  30. package/dist/protocol/dist/board-document.d.ts +30 -16
  31. package/dist/protocol/dist/board-document.js +15 -12
  32. package/dist/protocol/dist/board-effect.d.ts +4 -2
  33. package/dist/protocol/dist/board-effect.js +3 -2
  34. package/dist/protocol/dist/board.d.ts +148 -3
  35. package/dist/protocol/dist/board.js +7 -0
  36. package/dist/protocol/dist/index.d.ts +4 -2
  37. package/dist/protocol/dist/provenance.d.ts +21 -0
  38. package/dist/types.d.ts +1 -1
  39. package/docs/app-runtime-guide.md +7 -4
  40. package/package.json +1 -1
  41. package/dist/board/render/themes/board-theme-registry.d.ts +0 -22
  42. package/dist/board/render/themes/board-theme-registry.js +0 -13
  43. package/dist/board/render/themes/clean-theme.d.ts +0 -5
@@ -0,0 +1,261 @@
1
+ import { BOARD_DOCUMENT_KIND } from "../protocol/dist/board.js";
2
+ import { BoardAppearanceSchema, parseBoardDocument } from "../protocol/dist/board-document.js";
3
+ import { boardNodeToAuthoringItem } from "../protocol/dist/board-codec.js";
4
+ import { DEFAULT_BOARD_APPEARANCE, boardAuthoringItemToDocumentItem } from "./semantic-document.js";
5
+ //#region src/board/replay.ts
6
+ function boardReplayActorKind(source) {
7
+ if (!source) return "human";
8
+ if (source.toolCallId) return "agent";
9
+ return source.via === "cli" ? "cli" : "human";
10
+ }
11
+ function isRecord(value) {
12
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ /** Apply one stored operation (forward payload or inverse) to the mutable state. */
15
+ function applyOperation(state, type, payload) {
16
+ switch (type) {
17
+ case "board.patch": {
18
+ const patch = isRecord(payload.patch) ? payload.patch : {};
19
+ if (isRecord(patch.metadata)) state.metadata = patch.metadata;
20
+ if (isRecord(patch.metadataPatch)) state.metadata = {
21
+ ...state.metadata,
22
+ ...patch.metadataPatch
23
+ };
24
+ return;
25
+ }
26
+ case "node.create": {
27
+ const node = payload.node;
28
+ if (node) state.nodes.set(node.nodeId, node);
29
+ return;
30
+ }
31
+ case "node.patch": {
32
+ const nodeId = payload.nodeId;
33
+ const current = state.nodes.get(nodeId);
34
+ if (current && isRecord(payload.patch)) state.nodes.set(nodeId, {
35
+ ...current,
36
+ ...payload.patch,
37
+ nodeId
38
+ });
39
+ return;
40
+ }
41
+ case "node.delete":
42
+ state.nodes.delete(payload.nodeId);
43
+ return;
44
+ case "connection.create": {
45
+ const connection = payload.connection;
46
+ if (connection) state.connections.set(connection.id, connection);
47
+ return;
48
+ }
49
+ case "connection.patch": {
50
+ const connectionId = payload.connectionId;
51
+ const current = state.connections.get(connectionId);
52
+ if (current && isRecord(payload.patch)) state.connections.set(connectionId, {
53
+ ...current,
54
+ ...payload.patch,
55
+ id: connectionId
56
+ });
57
+ return;
58
+ }
59
+ case "connection.delete":
60
+ state.connections.delete(payload.connectionId);
61
+ return;
62
+ default: return;
63
+ }
64
+ }
65
+ /**
66
+ * Inverses are stored either as a full operation (`{ type, payload }`) or, for
67
+ * `board.patch`, as a bare `{ patch }`. Normalise both to `(type, payload)`.
68
+ */
69
+ function inverseOf(operation) {
70
+ const inverse = operation.inverse;
71
+ if (!inverse) return null;
72
+ if (typeof inverse.type === "string" && isRecord(inverse.payload)) return {
73
+ type: inverse.type,
74
+ payload: inverse.payload
75
+ };
76
+ if (operation.type === "board.patch" && isRecord(inverse.patch)) return {
77
+ type: "board.patch",
78
+ payload: { patch: inverse.patch }
79
+ };
80
+ return null;
81
+ }
82
+ function operationItemIds(operation) {
83
+ const payload = operation.payload;
84
+ switch (operation.type) {
85
+ case "node.create": return isRecord(payload.node) && typeof payload.node.nodeId === "string" ? [payload.node.nodeId] : [];
86
+ case "node.patch":
87
+ case "node.delete": return typeof payload.nodeId === "string" ? [payload.nodeId] : [];
88
+ default: return [];
89
+ }
90
+ }
91
+ const VISUAL_OPERATION_TYPES = /* @__PURE__ */ new Set([
92
+ "board.patch",
93
+ "node.create",
94
+ "node.patch",
95
+ "node.delete",
96
+ "connection.create",
97
+ "connection.patch",
98
+ "connection.delete"
99
+ ]);
100
+ function entryOf(transaction) {
101
+ return {
102
+ version: transaction.version,
103
+ actorId: transaction.actorId,
104
+ kind: boardReplayActorKind(transaction.source),
105
+ at: Date.parse(transaction.createdAt),
106
+ visual: transaction.operations.some((operation) => VISUAL_OPERATION_TYPES.has(operation.type))
107
+ };
108
+ }
109
+ function appearanceOf(metadata) {
110
+ const parsed = BoardAppearanceSchema.safeParse(metadata.appearance);
111
+ return parsed.success ? parsed.data : DEFAULT_BOARD_APPEARANCE;
112
+ }
113
+ function project(state) {
114
+ const nodes = [...state.nodes.values()].sort((a, b) => (a.orderKey ?? "").localeCompare(b.orderKey ?? ""));
115
+ return parseBoardDocument({
116
+ kind: BOARD_DOCUMENT_KIND,
117
+ version: 1,
118
+ appearance: appearanceOf(state.metadata),
119
+ viewport: {
120
+ x: 0,
121
+ y: 0,
122
+ zoom: 1
123
+ },
124
+ items: nodes.map((node) => boardAuthoringItemToDocumentItem(boardNodeToAuthoringItem(node))),
125
+ connections: [...state.connections.values()]
126
+ });
127
+ }
128
+ function stateFromSnapshot(snapshot) {
129
+ return {
130
+ nodes: new Map(snapshot.nodes.map(({ boardId: _boardId, version: _version, createdAt: _c, updatedAt: _u, ...node }) => [node.nodeId, node])),
131
+ connections: new Map(snapshot.connections.map(({ boardId: _boardId, revision: _revision, createdAt: _c, updatedAt: _u, ...connection }) => [connection.id, connection])),
132
+ metadata: snapshot.board.metadata
133
+ };
134
+ }
135
+ /**
136
+ * Create a player positioned at the live version described by the first page.
137
+ *
138
+ * `transactions` are newest-first as served; the player keeps them oldest-first.
139
+ * Older pages can be added with `prepend`, and newer transactions that arrive
140
+ * live can be added with `append`, so the timeline grows in both directions
141
+ * without a reset.
142
+ */
143
+ function createBoardReplayPlayer(page) {
144
+ if (!page.snapshot) throw new Error("Board replay needs the first transactions page (with snapshot).");
145
+ const liveVersion = page.board.version;
146
+ const newest = page.transactions.reduce((max, transaction) => Math.max(max, transaction.version), 0);
147
+ if (page.transactions.length > 0 && newest !== liveVersion) throw new Error(`Board replay page is inconsistent: board is at v${liveVersion}, newest transaction is v${newest}.`);
148
+ let transactions = [...page.transactions].sort((a, b) => a.version - b.version);
149
+ let entries = transactions.map(entryOf);
150
+ const state = stateFromSnapshot(page.snapshot);
151
+ let cursor = liveVersion;
152
+ const documents = /* @__PURE__ */ new Map();
153
+ /** Oldest version reachable with the transactions loaded so far. */
154
+ let floor = transactions[0]?.baseVersion ?? liveVersion;
155
+ /**
156
+ * Index of the first transaction whose version is `>= version`. Versions are
157
+ * strictly increasing but not dense (a no-op mutation leaves a gap), so
158
+ * binary-search by value rather than indexing by offset.
159
+ */
160
+ function lowerBound(version) {
161
+ let low = 0;
162
+ let high = transactions.length;
163
+ while (low < high) {
164
+ const mid = low + high >> 1;
165
+ if (transactions[mid].version < version) low = mid + 1;
166
+ else high = mid;
167
+ }
168
+ return low;
169
+ }
170
+ function transactionProducing(version) {
171
+ const candidate = transactions[lowerBound(version)];
172
+ return candidate?.version === version ? candidate : void 0;
173
+ }
174
+ /** Undo the transaction that produced `cursor`. */
175
+ function stepBackward() {
176
+ const transaction = transactionProducing(cursor);
177
+ if (!transaction) return false;
178
+ for (let index = transaction.operations.length - 1; index >= 0; index -= 1) {
179
+ const inverse = inverseOf(transaction.operations[index]);
180
+ if (inverse) applyOperation(state, inverse.type, inverse.payload);
181
+ }
182
+ cursor = transaction.baseVersion;
183
+ return true;
184
+ }
185
+ function stepForward(next) {
186
+ for (const operation of next.operations) applyOperation(state, operation.type, operation.payload);
187
+ cursor = next.version;
188
+ }
189
+ function head() {
190
+ return transactions.at(-1)?.version ?? liveVersion;
191
+ }
192
+ /**
193
+ * Move to `version`, clamped to the loaded range. Returns the version reached,
194
+ * which is the nearest loaded version at or below the request.
195
+ */
196
+ function seek(version) {
197
+ const target = Math.max(floor, Math.min(head(), version));
198
+ while (cursor > target && stepBackward());
199
+ for (let next = transactions[lowerBound(cursor + 1)]; next && next.version <= target; next = transactions[lowerBound(cursor + 1)]) stepForward(next);
200
+ return cursor;
201
+ }
202
+ function documentAt(version) {
203
+ const reached = seek(version);
204
+ const cached = documents.get(reached);
205
+ if (cached) return cached;
206
+ const document = project(state);
207
+ documents.set(reached, document);
208
+ return document;
209
+ }
210
+ return {
211
+ /** Oldest version the loaded transactions can rewind to. */
212
+ get floor() {
213
+ return floor;
214
+ },
215
+ /** Newest version known to the player. */
216
+ get head() {
217
+ return head();
218
+ },
219
+ get entries() {
220
+ return entries;
221
+ },
222
+ /** Current position. */
223
+ get version() {
224
+ return cursor;
225
+ },
226
+ seek,
227
+ documentAt,
228
+ /** Item ids touched by the transaction that produced `version`, for camera follow. */
229
+ changedItemIds(version) {
230
+ return [...new Set(transactionProducing(version)?.operations.flatMap(operationItemIds))];
231
+ },
232
+ /** Add an older page (as served, newest-first). */
233
+ prepend(older) {
234
+ const current = floor;
235
+ const fresh = older.transactions.filter((transaction) => transaction.version <= current).sort((a, b) => a.version - b.version);
236
+ if (fresh.length === 0) return;
237
+ transactions = [...fresh, ...transactions];
238
+ entries = [...fresh.map(entryOf), ...entries];
239
+ floor = fresh[0].baseVersion;
240
+ },
241
+ /**
242
+ * Add transactions that landed after the current head. Forward payloads are
243
+ * enough to extend the timeline; the scrub position is untouched.
244
+ *
245
+ * Returns false when the page does not reach back to the head: the chain
246
+ * would have a hole, so nothing is appended and the caller must fetch an
247
+ * older page (`before` = the page's oldest version) and try again.
248
+ */
249
+ append(latest) {
250
+ const current = head();
251
+ const fresh = latest.transactions.filter((transaction) => transaction.version > current).sort((a, b) => a.version - b.version);
252
+ if (fresh.length === 0) return true;
253
+ if (fresh[0].baseVersion > current) return false;
254
+ transactions = [...transactions, ...fresh];
255
+ entries = [...entries, ...fresh.map(entryOf)];
256
+ return true;
257
+ }
258
+ };
259
+ }
260
+ //#endregion
261
+ export { boardReplayActorKind, createBoardReplayPlayer };
@@ -4,7 +4,15 @@ import { BoardDocument, BoardItem } from "../protocol/dist/board-document.js";
4
4
  import "../protocol/dist/index.js";
5
5
  //#region src/board/semantic-document.d.ts
6
6
  declare const DEFAULT_BOARD_APPEARANCE: {
7
- theme: string;
7
+ theme?: string | undefined;
8
+ motion?: {
9
+ enter?: {
10
+ kind: string;
11
+ kindVersion: number;
12
+ params: Record<string, unknown>;
13
+ } | undefined;
14
+ } | undefined;
15
+ mood?: "arcane" | "clean" | "cyber" | "natural" | "playful" | undefined;
8
16
  background: {
9
17
  kind: "custom" | "dots" | "grid" | "image" | "shader" | "solid";
10
18
  color?: string | undefined;
@@ -18,7 +26,6 @@ declare const DEFAULT_BOARD_APPEARANCE: {
18
26
  size: number;
19
27
  opacity: number;
20
28
  };
21
- mood: "arcane" | "clean" | "cyber" | "natural" | "playful";
22
29
  };
23
30
  /** Convert a public authoring Item to the renderer/editor document shape. */
24
31
  declare function boardAuthoringItemToDocumentItem(item: BoardAuthoringItem): BoardItem;
@@ -5,14 +5,12 @@ import { boardDrawPointsToWorld } from "../protocol/dist/board-geometry.js";
5
5
  import { boardAuthoringItemToNode } from "../protocol/dist/board-codec.js";
6
6
  //#region src/board/semantic-document.ts
7
7
  const DEFAULT_BOARD_APPEARANCE = BoardAppearanceSchema.parse({
8
- theme: "clean",
9
8
  background: { kind: "solid" },
10
9
  grid: {
11
10
  visible: false,
12
11
  size: 24,
13
12
  opacity: .12
14
- },
15
- mood: "clean"
13
+ }
16
14
  });
17
15
  /** Convert a public authoring Item to the renderer/editor document shape. */
18
16
  function boardAuthoringItemToDocumentItem(item) {