@neta-art/cohub 12.5.0 → 13.0.0

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 (76) hide show
  1. package/README.md +35 -13
  2. package/dist/board/editor/editor.d.ts +7 -5
  3. package/dist/board/editor/index.js +1 -1
  4. package/dist/board/editor/interaction.js +1 -1
  5. package/dist/board/export/index.d.ts +1 -6
  6. package/dist/board/export/index.js +16 -10
  7. package/dist/board/export/node/index.d.ts +2 -7
  8. package/dist/board/export/node/index.js +2 -5
  9. package/dist/board/export/scene.d.ts +3 -7
  10. package/dist/board/export/scene.js +22 -26
  11. package/dist/board/model/asset-key.d.ts +5 -2
  12. package/dist/board/model/asset-key.js +13 -5
  13. package/dist/board/model/index.d.ts +5 -4
  14. package/dist/board/model/index.js +4 -3
  15. package/dist/board/model/media-playback.d.ts +4 -6
  16. package/dist/board/model/media-playback.js +4 -4
  17. package/dist/board/model/read.d.ts +7 -0
  18. package/dist/board/model/read.js +18 -0
  19. package/dist/board/model/themes.d.ts +50 -0
  20. package/dist/board/model/themes.js +44 -6
  21. package/dist/board/render/backdrop-layer.d.ts +21 -0
  22. package/dist/board/render/backdrop-layer.js +227 -0
  23. package/dist/board/render/backdrop.d.ts +45 -0
  24. package/dist/board/render/backdrop.js +62 -0
  25. package/dist/board/render/index.d.ts +3 -2
  26. package/dist/board/render/index.js +7 -6
  27. package/dist/board/render/renderers/arrow-card-renderer.js +1 -1
  28. package/dist/board/render/renderers/audio-card-renderer.js +2 -2
  29. package/dist/board/render/renderers/file-card-renderer.js +1 -1
  30. package/dist/board/render/renderers/frame-card-renderer.js +1 -1
  31. package/dist/board/render/renderers/model-card-renderer.js +1 -1
  32. package/dist/board/render/renderers/shape-card-renderer.js +1 -1
  33. package/dist/board/render/renderers/task-card-renderer.js +2 -2
  34. package/dist/board/render/renderers/text-card-renderer.js +2 -2
  35. package/dist/board/render/renderers/video-card-renderer.js +1 -1
  36. package/dist/board/render/theme.d.ts +2 -0
  37. package/dist/board/render/theme.js +4 -2
  38. package/dist/board/replica/index.d.ts +3 -2
  39. package/dist/board/replica/index.js +34 -24
  40. package/dist/board/replica/indexeddb.js +14 -85
  41. package/dist/board/stage/assets.d.ts +4 -3
  42. package/dist/board/stage/assets.js +186 -83
  43. package/dist/board/stage/backdrop.d.ts +25 -0
  44. package/dist/board/stage/backdrop.js +104 -0
  45. package/dist/board/stage/index.d.ts +2 -1
  46. package/dist/board/stage/scene.js +12 -5
  47. package/dist/board/stage/stage.d.ts +7 -2
  48. package/dist/board/stage/stage.js +44 -24
  49. package/dist/board/stage/texture-pool.d.ts +1 -1
  50. package/dist/board/stage/texture-pool.js +4 -5
  51. package/dist/chunks/app-realtime.js +1 -1
  52. package/dist/chunks/environment.d.ts +34 -21
  53. package/dist/chunks/environment.js +4 -3
  54. package/dist/chunks/http-error.js +28 -5
  55. package/dist/chunks/http.d.ts +87 -18
  56. package/dist/chunks/http.js +1855 -59
  57. package/dist/chunks/websocket.d.ts +9 -1
  58. package/dist/http.d.ts +2 -2
  59. package/dist/http.js +1 -1
  60. package/dist/index.d.ts +9 -3
  61. package/dist/index.js +6 -1080
  62. package/dist/indexeddb.js +79 -0
  63. package/dist/media.d.ts +18 -32
  64. package/dist/media.js +81 -67
  65. package/dist/protocol/dist/board-model.d.ts +33 -26
  66. package/dist/protocol/dist/board-model.js +21 -18
  67. package/dist/protocol/dist/board.d.ts +21 -1
  68. package/dist/protocol/dist/board.js +5 -1
  69. package/dist/protocol/dist/core/media.d.ts +32 -9
  70. package/dist/protocol/dist/core/media.js +203 -48
  71. package/dist/protocol/dist/index.d.ts +3 -3
  72. package/dist/voice-input.js +1 -1
  73. package/dist/websocket.js +37 -5
  74. package/package.json +1 -1
  75. package/dist/board/render/board-background.d.ts +0 -18
  76. package/dist/board/render/board-background.js +0 -104
@@ -1,10 +1,10 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
3
- import { boardCardDetailed } from "../lod.js";
3
+ import { drawAudioWaveform } from "../audio-waveform.js";
4
4
  import { fitTextToLines, fitTextToWidth } from "../text-fit.js";
5
5
  import { syncTextResolution } from "../text-resolution.js";
6
6
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
7
- import { drawAudioWaveform } from "../audio-waveform.js";
7
+ import { boardCardDetailed } from "../lod.js";
8
8
  import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
9
9
  import { positionShell } from "./base-card-renderer.js";
10
10
  import { drawFarPlate, farPictureTone } from "./far-plate.js";
@@ -1,8 +1,8 @@
1
1
  import { estimateTextWidth, layoutBoardText } from "../../../protocol/dist/board-layout.js";
2
- import { itemAlpha, itemColor } from "../palette.js";
3
2
  import { boardTextStyle, canvasFont, cssBaselineShift, layoutTextLines } from "../text-measurement.js";
4
- import { boardTextSize } from "../lod.js";
5
3
  import { syncTextResolution } from "../text-resolution.js";
4
+ import { itemAlpha, itemColor } from "../palette.js";
5
+ import { boardTextSize } from "../lod.js";
6
6
  import { positionShell } from "./base-card-renderer.js";
7
7
  import { drawFarBars } from "./far-plate.js";
8
8
  import { Container, Graphics, Text } from "pixi.js";
@@ -1,6 +1,6 @@
1
1
  import { getMediaResourceTitle } from "../../model/media.js";
2
- import { boardCardDetailed } from "../lod.js";
3
2
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
3
+ import { boardCardDetailed } from "../lod.js";
4
4
  import { cardElevation, containRect, drawCardSurface, fillCardPicture } from "../surface.js";
5
5
  import { positionShell } from "./base-card-renderer.js";
6
6
  import { drawFarPlate, farPictureTone } from "./far-plate.js";
@@ -2,12 +2,14 @@ import { BoardShapeColors } from "../model/palette.js";
2
2
  import { BoardThemeId } from "../model/themes.js";
3
3
  import { BoardRenderStyle } from "./style.js";
4
4
  import { BoardRenderPalette } from "./renderers/board-renderer-registry.js";
5
+ import { BoardThemeBackdrop } from "./backdrop.js";
5
6
  //#region src/board/render/theme.d.ts
6
7
  type BoardRenderTheme = {
7
8
  colorScheme: "dark" | "light";
8
9
  palette: BoardRenderPalette;
9
10
  colors: BoardShapeColors;
10
11
  style: BoardRenderStyle;
12
+ backdrop?: BoardThemeBackdrop;
11
13
  };
12
14
  /** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
13
15
  declare function boardRenderTheme(id: BoardThemeId): BoardRenderTheme;
@@ -3,12 +3,14 @@ import { resolveBoardRenderStyle } from "./style.js";
3
3
  //#region src/board/render/theme.ts
4
4
  /** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
5
5
  function boardRenderTheme(id) {
6
- const { colorScheme, palette, colors, style } = BOARD_THEME_DATA[id];
6
+ const data = BOARD_THEME_DATA[id];
7
+ const { colorScheme, palette, colors, style } = data;
7
8
  return {
8
9
  colorScheme,
9
10
  palette: { ...palette },
10
11
  colors: Object.fromEntries(Object.entries(colors).map(([key, value]) => [key, { ...value }])),
11
- style: resolveBoardRenderStyle(colorScheme, style)
12
+ style: resolveBoardRenderStyle(colorScheme, style),
13
+ ..."backdrop" in data ? { backdrop: structuredClone(data.backdrop) } : {}
12
14
  };
13
15
  }
14
16
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { BoardDelta, BoardDocument, BoardPatch } from "../../protocol/dist/board-model.js";
2
- import { BoardApplyResult, BoardPlaybackSnapshot, BoardReadResult } from "../../protocol/dist/board.js";
2
+ import { BoardApplyResult, BoardPlaybackSnapshot, BoardReadInput, BoardReadResult } from "../../protocol/dist/board.js";
3
3
  import "../../protocol/dist/index.js";
4
4
  import { BoardReplicaDatabaseOptions, BoardReplicaScope, clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
5
5
  //#region src/board/replica/index.d.ts
@@ -14,7 +14,8 @@ type BoardChangedPayload = {
14
14
  after?: BoardDelta;
15
15
  };
16
16
  type BoardRemote = {
17
- get(): Promise<BoardReadResult>;
17
+ /** `since` asks only for the changes after that version; remotes may return the full Board. */
18
+ get(read?: Pick<BoardReadInput, "since">): Promise<BoardReadResult>;
18
19
  apply(patch: BoardPatch, options: {
19
20
  mutationId: string;
20
21
  clientId?: string;
@@ -1,6 +1,6 @@
1
1
  import { applyBoardDelta, applyBoardPatchToDocument, parseBoardDocument } from "../../protocol/dist/board-patch.js";
2
2
  import { createBoardEntityId } from "../model/items/id.js";
3
- import { shareBoardDocument } from "../model/document-share.js";
3
+ import { readBoardDocument } from "../model/read.js";
4
4
  import { clearBoardReplicaStorage, createIndexedDbBoardReplicaStorage } from "./indexeddb.js";
5
5
  //#region src/board/replica/index.ts
6
6
  function memoryStorage() {
@@ -15,6 +15,7 @@ function memoryStorage() {
15
15
  const RETRY_BASE_MS = 1e3;
16
16
  const RETRY_MAX_MS = 3e4;
17
17
  const PERSIST_INTERVAL_MS = 500;
18
+ const CACHE_WAIT_MS = 250;
18
19
  function isRejection(error) {
19
20
  const status = error.status;
20
21
  return typeof status === "number" && status >= 400 && status < 500 && ![
@@ -34,6 +35,7 @@ function createBoardReplica(options) {
34
35
  let unsubscribeRemote = null;
35
36
  let server = null;
36
37
  let version = 0;
38
+ let sent = [];
37
39
  let title = null;
38
40
  let playback = null;
39
41
  let playbackRevision = 0;
@@ -69,7 +71,7 @@ function createBoardReplica(options) {
69
71
  return;
70
72
  }
71
73
  let next = server;
72
- for (const entry of pending) {
74
+ for (const entry of [...sent, ...pending]) {
73
75
  const result = applyBoardPatchToDocument(next, entry.patch);
74
76
  if (result.ok) next = result.document;
75
77
  }
@@ -95,6 +97,7 @@ function createBoardReplica(options) {
95
97
  function adoptServer(next, nextVersion, persist) {
96
98
  server = next;
97
99
  version = nextVersion;
100
+ sent = sent.filter((entry) => entry.version > nextVersion);
98
101
  recompute();
99
102
  if (persist) schedulePersist(nextVersion, next);
100
103
  }
@@ -112,21 +115,16 @@ function createBoardReplica(options) {
112
115
  unpersisted = null;
113
116
  if (entry) storage.writeDocument(entry.version, entry.document).catch(() => void 0);
114
117
  }
115
- function adoptRead(result, readPlaybackRevision) {
116
- const parsed = parseBoardDocument({
117
- board: result.board,
118
- items: result.items ?? {},
119
- animations: result.animations ?? {}
120
- });
121
- if (!parsed.ok) throw new Error(`Board ${result.id} could not be read.`);
118
+ function adoptRead(result, base, readPlaybackRevision) {
119
+ const next = readBoardDocument(result, result.changes ? base : server);
122
120
  title = result.title;
123
121
  if (readPlaybackRevision === playbackRevision) playback = result.playback;
124
122
  loaded = true;
125
- const next = shareBoardDocument(server, parsed.document);
126
123
  if (next === server && result.version === version) return;
127
124
  adoptServer(next, result.version, true);
128
125
  }
129
126
  function refresh() {
127
+ if (disposed) return Promise.resolve();
130
128
  if (refreshing) {
131
129
  refreshAgain = true;
132
130
  return refreshing;
@@ -136,9 +134,10 @@ function createBoardReplica(options) {
136
134
  do {
137
135
  refreshAgain = false;
138
136
  const readPlaybackRevision = playbackRevision;
139
- const result = await remote.get();
137
+ const base = server;
138
+ const result = await remote.get(base ? { since: version } : void 0);
140
139
  if (disposed) return;
141
- if (result.version >= version || !loaded) adoptRead(result, readPlaybackRevision);
140
+ if (result.version >= version || !loaded) adoptRead(result, base, readPlaybackRevision);
142
141
  readError = null;
143
142
  emit();
144
143
  } while (refreshAgain && !disposed);
@@ -162,6 +161,7 @@ function createBoardReplica(options) {
162
161
  async function flush() {
163
162
  if (sending || disposed || !loaded || !restored) return;
164
163
  sending = true;
164
+ let latest = 0;
165
165
  try {
166
166
  while (pending.length && !disposed) {
167
167
  const entry = pending[0];
@@ -173,12 +173,12 @@ function createBoardReplica(options) {
173
173
  const stillPending = pending.some((item) => item.mutationId === entry.mutationId);
174
174
  pending = pending.filter((item) => item.mutationId !== entry.mutationId);
175
175
  storage.deletePending(entry.mutationId).catch(() => void 0);
176
- if (stillPending && server && !result.replayed && result.status === "applied" && result.version === version + 1) {
177
- const next = applyBoardPatchToDocument(server, entry.patch);
178
- if (next.ok) adoptServer(next.document, result.version, true);
179
- else refresh();
180
- } else if (result.version > version) refresh();
181
- else recompute();
176
+ if (stillPending && result.status === "applied" && result.version > version) sent = [...sent, {
177
+ version: result.version,
178
+ patch: entry.patch
179
+ }];
180
+ latest = Math.max(latest, result.version);
181
+ recompute();
182
182
  retryDelay = RETRY_BASE_MS;
183
183
  writeError = null;
184
184
  emit();
@@ -198,6 +198,7 @@ function createBoardReplica(options) {
198
198
  return;
199
199
  }
200
200
  }
201
+ if (latest > version && !disposed) refresh();
201
202
  } finally {
202
203
  sending = false;
203
204
  }
@@ -233,15 +234,19 @@ function createBoardReplica(options) {
233
234
  function onOnline() {
234
235
  if (pending.length || readError || writeError) retry();
235
236
  }
236
- async function restore() {
237
- const [cached, stored] = await Promise.all([storage.readDocument().catch(() => null), storage.listPending().catch(() => [])]);
237
+ async function restoreDocument() {
238
+ const cached = await storage.readDocument().catch(() => null);
239
+ if (disposed || server || !cached) return;
240
+ const parsed = parseBoardDocument(cached.document);
241
+ if (parsed.ok) adoptServer(parsed.document, cached.version, false);
242
+ }
243
+ async function restore(document) {
244
+ const [, stored] = await Promise.all([document, storage.listPending().catch(() => [])]);
238
245
  if (disposed) return;
239
246
  const known = new Set(stored.map((entry) => entry.mutationId));
240
247
  pending = [...stored, ...pending.filter((entry) => !known.has(entry.mutationId))];
241
248
  restored = true;
242
- const parsed = cached && !server ? parseBoardDocument(cached.document) : null;
243
- if (cached && parsed?.ok) adoptServer(parsed.document, cached.version, false);
244
- else recompute();
249
+ recompute();
245
250
  emit();
246
251
  }
247
252
  function receivePlayback(next) {
@@ -260,7 +265,12 @@ function createBoardReplica(options) {
260
265
  async start() {
261
266
  unsubscribeRemote ??= subscribeRemote();
262
267
  globalThis.addEventListener?.("online", onOnline);
263
- await Promise.all([restore(), refresh()]);
268
+ const document = restoreDocument();
269
+ const cacheWait = new Promise((resolve) => {
270
+ const timer = setTimeout(resolve, CACHE_WAIT_MS);
271
+ document.then(() => clearTimeout(timer));
272
+ });
273
+ await Promise.all([restore(document), Promise.race([document, cacheWait]).then(refresh)]);
264
274
  await flush();
265
275
  },
266
276
  apply(patch) {
@@ -1,102 +1,31 @@
1
+ import { deleteByIndex, indexedDb } from "../../indexeddb.js";
1
2
  //#region src/board/replica/indexeddb.ts
2
3
  const DATABASE = "cohub-board";
3
4
  const VERSION = 1;
4
- const OPEN_TIMEOUT_MS = 3e3;
5
- const connections = /* @__PURE__ */ new Map();
6
- function open(name) {
7
- const existing = connections.get(name);
8
- if (existing) return existing;
9
- const opening = new Promise((resolve, reject) => {
10
- const factory = globalThis.indexedDB;
11
- if (!factory) {
12
- reject(/* @__PURE__ */ new Error("IndexedDB is unavailable."));
13
- return;
14
- }
15
- let late = false;
16
- const timer = setTimeout(() => {
17
- late = true;
18
- reject(/* @__PURE__ */ new Error("IndexedDB did not open in time."));
19
- }, OPEN_TIMEOUT_MS);
20
- const request = factory.open(name, VERSION);
21
- request.onupgradeneeded = () => {
22
- const db = request.result;
23
- db.createObjectStore("documents", { keyPath: "board" }).createIndex("user", "userKey");
24
- const pending = db.createObjectStore("pending", { keyPath: ["board", "mutationId"] });
25
- pending.createIndex("board", "board");
26
- pending.createIndex("user", "userKey");
27
- };
28
- request.onsuccess = () => {
29
- clearTimeout(timer);
30
- const db = request.result;
31
- if (late) {
32
- db.close();
33
- return;
34
- }
35
- const forget = () => {
36
- if (connections.get(name) === opening) connections.delete(name);
37
- };
38
- db.onversionchange = () => {
39
- db.close();
40
- forget();
41
- };
42
- db.onclose = forget;
43
- resolve(db);
44
- };
45
- request.onerror = () => {
46
- clearTimeout(timer);
47
- reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB failed to open."));
48
- };
49
- });
50
- connections.set(name, opening);
51
- opening.catch(() => {
52
- if (connections.get(name) === opening) connections.delete(name);
53
- });
54
- return opening;
55
- }
56
- async function transact(name, stores, mode, work, retry = true) {
57
- const db = await open(name);
58
- let tx;
59
- try {
60
- tx = db.transaction(stores, mode);
61
- } catch (error) {
62
- if (!retry) throw error;
63
- db.close();
64
- connections.delete(name);
65
- return transact(name, stores, mode, work, false);
66
- }
67
- return new Promise((resolve, reject) => {
68
- const result = work(tx);
69
- tx.oncomplete = () => resolve(result());
70
- tx.onerror = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed."));
71
- tx.onabort = () => reject(tx.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted."));
72
- });
73
- }
74
- function deleteByIndex(store, index, key) {
75
- const request = store.index(index).openKeyCursor(IDBKeyRange.only(key));
76
- request.onsuccess = () => {
77
- const cursor = request.result;
78
- if (!cursor) return;
79
- store.delete(cursor.primaryKey);
80
- cursor.continue();
81
- };
5
+ function upgrade(db) {
6
+ db.createObjectStore("documents", { keyPath: "board" }).createIndex("user", "userKey");
7
+ const pending = db.createObjectStore("pending", { keyPath: ["board", "mutationId"] });
8
+ pending.createIndex("board", "board");
9
+ pending.createIndex("user", "userKey");
82
10
  }
11
+ const database = (name) => indexedDb(name, VERSION, upgrade);
83
12
  let lastOrder = 0;
84
13
  function createIndexedDbBoardReplicaStorage(scope, options = {}) {
85
- const name = options.database ?? DATABASE;
14
+ const { transact } = database(options.database ?? DATABASE);
86
15
  const board = JSON.stringify([
87
16
  scope.userKey,
88
17
  scope.spaceId,
89
18
  scope.boardId
90
19
  ]);
91
20
  return {
92
- listPending: () => transact(name, ["pending"], "readonly", (tx) => {
21
+ listPending: () => transact(["pending"], "readonly", (tx) => {
93
22
  const request = tx.objectStore("pending").index("board").getAll(IDBKeyRange.only(board));
94
23
  return () => request.result.sort((a, b) => a.order - b.order).map(({ mutationId, patch }) => ({
95
24
  mutationId,
96
25
  patch
97
26
  }));
98
27
  }),
99
- putPending: ({ mutationId, patch }) => transact(name, ["pending"], "readwrite", (tx) => {
28
+ putPending: ({ mutationId, patch }) => transact(["pending"], "readwrite", (tx) => {
100
29
  lastOrder = Math.max(Date.now(), lastOrder + 1);
101
30
  const record = {
102
31
  board,
@@ -108,11 +37,11 @@ function createIndexedDbBoardReplicaStorage(scope, options = {}) {
108
37
  tx.objectStore("pending").put(record);
109
38
  return () => void 0;
110
39
  }),
111
- deletePending: (mutationId) => transact(name, ["pending"], "readwrite", (tx) => {
40
+ deletePending: (mutationId) => transact(["pending"], "readwrite", (tx) => {
112
41
  tx.objectStore("pending").delete([board, mutationId]);
113
42
  return () => void 0;
114
43
  }),
115
- readDocument: () => transact(name, ["documents"], "readonly", (tx) => {
44
+ readDocument: () => transact(["documents"], "readonly", (tx) => {
116
45
  const request = tx.objectStore("documents").get(board);
117
46
  return () => {
118
47
  const record = request.result;
@@ -122,7 +51,7 @@ function createIndexedDbBoardReplicaStorage(scope, options = {}) {
122
51
  } : null;
123
52
  };
124
53
  }),
125
- writeDocument: (version, document) => transact(name, ["documents"], "readwrite", (tx) => {
54
+ writeDocument: (version, document) => transact(["documents"], "readwrite", (tx) => {
126
55
  const store = tx.objectStore("documents");
127
56
  const request = store.get(board);
128
57
  request.onsuccess = () => {
@@ -142,7 +71,7 @@ function createIndexedDbBoardReplicaStorage(scope, options = {}) {
142
71
  }
143
72
  async function clearBoardReplicaStorage(options = {}) {
144
73
  const { userKey } = options;
145
- await transact(options.database ?? DATABASE, ["documents", "pending"], "readwrite", (tx) => {
74
+ await database(options.database ?? DATABASE).transact(["documents", "pending"], "readwrite", (tx) => {
146
75
  for (const name of ["documents", "pending"]) {
147
76
  const store = tx.objectStore(name);
148
77
  if (userKey === void 0) store.clear();
@@ -1,7 +1,7 @@
1
1
  import { ModelFormat } from "../../protocol/dist/fs/mime.js";
2
2
  import { BoardSceneItem } from "../model/scene.js";
3
3
  import { BoardAssetMedia, BoardAssetRef } from "../model/asset-key.js";
4
- import { BoardAssetUrlOptions } from "../model/media-playback.js";
4
+ import { BoardAssetSource } from "../model/media-playback.js";
5
5
  import "../model/index.js";
6
6
  import { LruBudget } from "./lru.js";
7
7
  import { BoardImageTexturePool } from "./texture-pool.js";
@@ -25,7 +25,7 @@ type BoardModelPreviews = {
25
25
  type BoardAssetManager = {
26
26
  assetKey: (item: BoardSceneItem) => string | null;
27
27
  invalidatePath: (path: string) => void;
28
- invalidateItemAssets: () => void;
28
+ invalidateItemAssets: (ids?: Iterable<string>) => void;
29
29
  requestItem: (item: BoardSceneItem, edge?: number) => void;
30
30
  /** World point used to prioritize queued loads. */
31
31
  focus: (point: {
@@ -60,7 +60,8 @@ type BoardAssetPrefetch = {
60
60
  type BoardAssetChange = "texture" | "tone";
61
61
  type BoardAssetManagerOptions = {
62
62
  /** Resolve a Space file path to a URL a texture can load from; null when unavailable. */
63
- resolveFileUrl: (path: string, options?: BoardAssetUrlOptions) => Promise<string | null>;
63
+ resolveFile: BoardAssetSource["resolveFile"];
64
+ subscribe?: BoardAssetSource["subscribe"];
64
65
  /** undefined keeps the built-in rule, null shows none; cached until `invalidateItemAssets`. */
65
66
  itemAsset?: (item: BoardSceneItem) => BoardAssetRef | null | undefined;
66
67
  concurrency?: number;