videobook-engine 4.2.0 → 5.3.2

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 (244) hide show
  1. package/README.md +84 -39
  2. package/dist/artifacts.d.ts +5 -10
  3. package/dist/artifacts.d.ts.map +1 -1
  4. package/dist/artifacts.js +79 -142
  5. package/dist/artifacts.js.map +1 -1
  6. package/dist/books.d.ts +1 -1
  7. package/dist/books.d.ts.map +1 -1
  8. package/dist/books.js +16 -9
  9. package/dist/books.js.map +1 -1
  10. package/dist/cas.d.ts +2 -1
  11. package/dist/cas.d.ts.map +1 -1
  12. package/dist/cas.js +33 -4
  13. package/dist/cas.js.map +1 -1
  14. package/dist/catalog-gc-state.d.ts +4 -0
  15. package/dist/catalog-gc-state.d.ts.map +1 -0
  16. package/dist/catalog-gc-state.js +49 -0
  17. package/dist/catalog-gc-state.js.map +1 -0
  18. package/dist/catalog-metadata.d.ts +2 -2
  19. package/dist/catalog-metadata.js +3 -3
  20. package/dist/catalog-metadata.js.map +1 -1
  21. package/dist/context.d.ts +5 -5
  22. package/dist/context.d.ts.map +1 -1
  23. package/dist/context.js +17 -49
  24. package/dist/context.js.map +1 -1
  25. package/dist/domain.d.ts +35 -2
  26. package/dist/domain.d.ts.map +1 -1
  27. package/dist/domain.js +131 -161
  28. package/dist/domain.js.map +1 -1
  29. package/dist/edits.js +2 -2
  30. package/dist/edits.js.map +1 -1
  31. package/dist/engine-types.d.ts +159 -39
  32. package/dist/engine-types.d.ts.map +1 -1
  33. package/dist/engine-types.js +2 -1
  34. package/dist/engine-types.js.map +1 -1
  35. package/dist/engine.d.ts +30 -20
  36. package/dist/engine.d.ts.map +1 -1
  37. package/dist/engine.js +46 -4
  38. package/dist/engine.js.map +1 -1
  39. package/dist/files.d.ts +10 -0
  40. package/dist/files.d.ts.map +1 -1
  41. package/dist/files.js +49 -2
  42. package/dist/files.js.map +1 -1
  43. package/dist/fork.d.ts +14 -30
  44. package/dist/fork.d.ts.map +1 -1
  45. package/dist/fork.js +18 -27
  46. package/dist/fork.js.map +1 -1
  47. package/dist/generations.d.ts +9 -0
  48. package/dist/generations.d.ts.map +1 -0
  49. package/dist/generations.js +175 -0
  50. package/dist/generations.js.map +1 -0
  51. package/dist/history.d.ts +2 -1
  52. package/dist/history.d.ts.map +1 -1
  53. package/dist/history.js +30 -57
  54. package/dist/history.js.map +1 -1
  55. package/dist/index.d.ts +8 -6
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +5 -5
  58. package/dist/index.js.map +1 -1
  59. package/dist/inline-temporal-models.d.ts +43 -0
  60. package/dist/inline-temporal-models.d.ts.map +1 -0
  61. package/dist/inline-temporal-models.js +236 -0
  62. package/dist/inline-temporal-models.js.map +1 -0
  63. package/dist/isolated-models.d.ts +8 -0
  64. package/dist/isolated-models.d.ts.map +1 -0
  65. package/dist/isolated-models.js +25 -0
  66. package/dist/isolated-models.js.map +1 -0
  67. package/dist/job-queue.d.ts +1 -1
  68. package/dist/job-queue.d.ts.map +1 -1
  69. package/dist/job-queue.js +20 -6
  70. package/dist/job-queue.js.map +1 -1
  71. package/dist/media-image.d.ts +8 -0
  72. package/dist/media-image.d.ts.map +1 -0
  73. package/dist/media-image.js +41 -0
  74. package/dist/media-image.js.map +1 -0
  75. package/dist/media-process.d.ts +13 -0
  76. package/dist/media-process.d.ts.map +1 -0
  77. package/dist/media-process.js +89 -0
  78. package/dist/media-process.js.map +1 -0
  79. package/dist/media.d.ts +3 -3
  80. package/dist/media.d.ts.map +1 -1
  81. package/dist/media.js +5 -5
  82. package/dist/media.js.map +1 -1
  83. package/dist/merge-policy.d.ts +16 -36
  84. package/dist/merge-policy.d.ts.map +1 -1
  85. package/dist/merge-policy.js +13 -125
  86. package/dist/merge-policy.js.map +1 -1
  87. package/dist/metadata.d.ts +1 -0
  88. package/dist/metadata.d.ts.map +1 -1
  89. package/dist/metadata.js +13 -3
  90. package/dist/metadata.js.map +1 -1
  91. package/dist/migrate-grid-text.d.ts +17 -0
  92. package/dist/migrate-grid-text.d.ts.map +1 -0
  93. package/dist/migrate-grid-text.js +117 -0
  94. package/dist/migrate-grid-text.js.map +1 -0
  95. package/dist/migrate-grid-v22.d.ts +12 -0
  96. package/dist/migrate-grid-v22.d.ts.map +1 -0
  97. package/dist/migrate-grid-v22.js +166 -0
  98. package/dist/migrate-grid-v22.js.map +1 -0
  99. package/dist/migrate-grid-v23.d.ts +10 -0
  100. package/dist/migrate-grid-v23.d.ts.map +1 -0
  101. package/dist/migrate-grid-v23.js +31 -0
  102. package/dist/migrate-grid-v23.js.map +1 -0
  103. package/dist/migration-notebooks.d.ts +25 -0
  104. package/dist/migration-notebooks.d.ts.map +1 -0
  105. package/dist/migration-notebooks.js +131 -0
  106. package/dist/migration-notebooks.js.map +1 -0
  107. package/dist/migration-source.d.ts +6 -0
  108. package/dist/migration-source.d.ts.map +1 -0
  109. package/dist/migration-source.js +37 -0
  110. package/dist/migration-source.js.map +1 -0
  111. package/dist/migration-timeline.d.ts +36 -0
  112. package/dist/migration-timeline.d.ts.map +1 -0
  113. package/dist/migration-timeline.js +187 -0
  114. package/dist/migration-timeline.js.map +1 -0
  115. package/dist/migration-validation.d.ts +12 -0
  116. package/dist/migration-validation.d.ts.map +1 -0
  117. package/dist/migration-validation.js +68 -0
  118. package/dist/migration-validation.js.map +1 -0
  119. package/dist/migration.d.ts.map +1 -1
  120. package/dist/migration.js +220 -119
  121. package/dist/migration.js.map +1 -1
  122. package/dist/model-cache-paths.d.ts +2 -0
  123. package/dist/model-cache-paths.d.ts.map +1 -0
  124. package/dist/model-cache-paths.js +5 -0
  125. package/dist/model-cache-paths.js.map +1 -0
  126. package/dist/model-checksums.json +338 -0
  127. package/dist/model-file-resolver.d.ts +30 -0
  128. package/dist/model-file-resolver.d.ts.map +1 -0
  129. package/dist/model-file-resolver.js +304 -0
  130. package/dist/model-file-resolver.js.map +1 -0
  131. package/dist/model-identity.d.ts +13 -0
  132. package/dist/model-identity.d.ts.map +1 -0
  133. package/dist/model-identity.js +44 -0
  134. package/dist/model-identity.js.map +1 -0
  135. package/dist/model-worker-errors.d.ts +3 -0
  136. package/dist/model-worker-errors.d.ts.map +1 -0
  137. package/dist/model-worker-errors.js +12 -0
  138. package/dist/model-worker-errors.js.map +1 -0
  139. package/dist/model-worker-pool.d.ts +32 -0
  140. package/dist/model-worker-pool.d.ts.map +1 -0
  141. package/dist/model-worker-pool.js +269 -0
  142. package/dist/model-worker-pool.js.map +1 -0
  143. package/dist/model-worker-protocol.d.ts +42 -0
  144. package/dist/model-worker-protocol.d.ts.map +1 -0
  145. package/dist/model-worker-protocol.js +2 -0
  146. package/dist/model-worker-protocol.js.map +1 -0
  147. package/dist/model-worker.d.ts +2 -0
  148. package/dist/model-worker.d.ts.map +1 -0
  149. package/dist/model-worker.js +91 -0
  150. package/dist/model-worker.js.map +1 -0
  151. package/dist/mvp-contract-fixtures.js +1 -1
  152. package/dist/mvp-contract-fixtures.js.map +1 -1
  153. package/dist/mvp-contracts.d.ts +32 -8
  154. package/dist/mvp-contracts.d.ts.map +1 -1
  155. package/dist/mvp-contracts.js +1 -1
  156. package/dist/mvp-contracts.js.map +1 -1
  157. package/dist/notebook/types.d.ts +1 -29
  158. package/dist/notebook/types.d.ts.map +1 -1
  159. package/dist/notebook-grid.d.ts +46 -0
  160. package/dist/notebook-grid.d.ts.map +1 -0
  161. package/dist/notebook-grid.js +187 -0
  162. package/dist/notebook-grid.js.map +1 -0
  163. package/dist/notebook-media-artifact.d.ts +23 -0
  164. package/dist/notebook-media-artifact.d.ts.map +1 -0
  165. package/dist/notebook-media-artifact.js +67 -0
  166. package/dist/notebook-media-artifact.js.map +1 -0
  167. package/dist/notebook-mentions.d.ts +20 -0
  168. package/dist/notebook-mentions.d.ts.map +1 -0
  169. package/dist/notebook-mentions.js +54 -0
  170. package/dist/notebook-mentions.js.map +1 -0
  171. package/dist/runtime-services.js +4 -9
  172. package/dist/runtime-services.js.map +1 -1
  173. package/dist/schema.d.ts +4 -19
  174. package/dist/schema.d.ts.map +1 -1
  175. package/dist/schema.js +66 -92
  176. package/dist/schema.js.map +1 -1
  177. package/dist/search-provider-access.d.ts +11 -0
  178. package/dist/search-provider-access.d.ts.map +1 -0
  179. package/dist/search-provider-access.js +42 -0
  180. package/dist/search-provider-access.js.map +1 -0
  181. package/dist/similarity.d.ts +60 -1
  182. package/dist/similarity.d.ts.map +1 -1
  183. package/dist/similarity.js +174 -154
  184. package/dist/similarity.js.map +1 -1
  185. package/dist/status.js +8 -10
  186. package/dist/status.js.map +1 -1
  187. package/dist/store.d.ts +16 -2
  188. package/dist/store.d.ts.map +1 -1
  189. package/dist/store.js +171 -17
  190. package/dist/store.js.map +1 -1
  191. package/dist/temporal-index-cache.d.ts +13 -0
  192. package/dist/temporal-index-cache.d.ts.map +1 -0
  193. package/dist/temporal-index-cache.js +59 -0
  194. package/dist/temporal-index-cache.js.map +1 -0
  195. package/dist/temporal-model-manifests.d.ts +17 -0
  196. package/dist/temporal-model-manifests.d.ts.map +1 -0
  197. package/dist/temporal-model-manifests.js +33 -0
  198. package/dist/temporal-model-manifests.js.map +1 -0
  199. package/dist/temporal-models.d.ts +20 -35
  200. package/dist/temporal-models.d.ts.map +1 -1
  201. package/dist/temporal-models.js +23 -257
  202. package/dist/temporal-models.js.map +1 -1
  203. package/dist/temporal-search.d.ts +6 -3
  204. package/dist/temporal-search.d.ts.map +1 -1
  205. package/dist/temporal-search.js +438 -122
  206. package/dist/temporal-search.js.map +1 -1
  207. package/dist/temporal-vector-index.d.ts +46 -0
  208. package/dist/temporal-vector-index.d.ts.map +1 -0
  209. package/dist/temporal-vector-index.js +220 -0
  210. package/dist/temporal-vector-index.js.map +1 -0
  211. package/dist/third-party/@huggingface-jinja.LICENSE +21 -0
  212. package/dist/third-party/@huggingface-tokenizers.LICENSE +201 -0
  213. package/dist/third-party/@huggingface-transformers.LICENSE +202 -0
  214. package/dist/third-party/onnxruntime-common.LICENSE +21 -0
  215. package/dist/third-party/versions.json +23 -0
  216. package/dist/transformers-runtime.d.ts +2 -0
  217. package/dist/transformers-runtime.d.ts.map +1 -0
  218. package/dist/transformers-runtime.js +33014 -0
  219. package/dist/transformers-runtime.js.LEGAL.txt +12 -0
  220. package/dist/transformers-runtime.js.map +7 -0
  221. package/docs/doltlite-staging.md +69 -0
  222. package/docs/edit-performance.md +83 -0
  223. package/docs/engine-layout.md +56 -50
  224. package/docs/media-limits.md +57 -0
  225. package/docs/model-download-policy.md +15 -0
  226. package/docs/model-integrity.md +72 -0
  227. package/docs/model-isolation.md +72 -0
  228. package/docs/model-revisions.md +54 -0
  229. package/docs/mvp-prd.md +2 -2
  230. package/docs/release-evidence.md +300 -0
  231. package/docs/search-provider-consent.md +67 -0
  232. package/docs/semantic-durability.md +55 -0
  233. package/docs/temporal-search-performance.md +260 -0
  234. package/docs/v4-migration.md +83 -0
  235. package/fixtures/v5/contract-fixtures.json +4 -4
  236. package/package.json +31 -13
  237. package/dist/artifact-names.d.ts +0 -6
  238. package/dist/artifact-names.d.ts.map +0 -1
  239. package/dist/artifact-names.js +0 -56
  240. package/dist/artifact-names.js.map +0 -1
  241. package/dist/resolver.d.ts +0 -15
  242. package/dist/resolver.d.ts.map +0 -1
  243. package/dist/resolver.js +0 -81
  244. package/dist/resolver.js.map +0 -1
package/dist/domain.js CHANGED
@@ -2,8 +2,9 @@ import { ok } from "./engine-types.js";
2
2
  import { normalizeSearchLocation } from "./mvp-time.js";
3
3
  import { resultOf, syncResultOf } from "./context.js";
4
4
  import { assertUuidV7, newUuidV7 } from "./ids.js";
5
- import { isValidNotebookCellSlug, NOTEBOOK_CELL_TYPES } from "./schema.js";
6
- import { canonicalJson, parseJson } from "./store.js";
5
+ import { LEGACY_NOTEBOOK_CELL_TYPE_ALIASES, NOTEBOOK_CELL_TYPES, } from "./schema.js";
6
+ import { firstEmptyNotebookGridSlots, isNotebookGridSlot, NOTEBOOK_GRID_ADDRESS_RANGE, } from "./notebook-grid.js";
7
+ import { canonicalJson, commitDateIso, parseCommitMessage, parseJson, } from "./store.js";
7
8
  import { EngineFault } from "./store.js";
8
9
  const NOTEBOOK_CELL_TYPE_SET = new Set(NOTEBOOK_CELL_TYPES);
9
10
  export function createEntitiesApi(context) {
@@ -28,7 +29,9 @@ export function createNotebooksApi(context) {
28
29
  }),
29
30
  list: () => listNotebooks(context),
30
31
  read: (notebookId) => syncResultOf(() => requiredNotebook(context, notebookId)),
31
- write: (notebook) => writeNotebook(context, notebook),
32
+ write: (notebook, options) => writeNotebook(context, notebook, options),
33
+ cellHistory: (notebookId, cellId, options) => cellHistory(context, notebookId, cellId, options),
34
+ executionHistory: (notebookId, cellId, options) => executionHistory(context, notebookId, cellId, options),
32
35
  insertCell: (notebookId, cell) => insertNotebookCell(context, notebookId, cell),
33
36
  updateCell: (notebookId, cell) => updateNotebookCell(context, notebookId, cell),
34
37
  moveCell: (notebookId, cellId, slot) => moveNotebookCell(context, notebookId, cellId, slot),
@@ -149,13 +152,13 @@ function listNotebooks(context) {
149
152
  .all();
150
153
  return rows.map((row) => notebookFromRows(context, row));
151
154
  }
152
- async function writeNotebook(context, notebook) {
155
+ async function writeNotebook(context, notebook, options) {
153
156
  return resultOf(async () => {
154
157
  assertUuidV7(notebook.id, "Notebook ID");
155
158
  requiredNotebook(context, notebook.id);
156
159
  validateNotebook(context, notebook);
157
160
  const mutation = await context.store.semantic({
158
- operation: "write_notebook",
161
+ operation: options?.operation ?? "write_notebook",
159
162
  tables: [
160
163
  "notebooks",
161
164
  "notebook_fields",
@@ -163,13 +166,12 @@ async function writeNotebook(context, notebook) {
163
166
  "notebook_cell_executions",
164
167
  "notebook_generation_plans",
165
168
  "notebook_run_plans",
166
- "notebook_transcript_edits",
167
- "notebook_transcript_attachments",
168
169
  "edges",
169
170
  "cell_references",
170
171
  "pinned_search_results",
171
172
  ],
172
- details: { notebookId: notebook.id },
173
+ details: { notebookId: notebook.id, ...options?.details },
174
+ ...(options?.artifactId ? { artifactId: options.artifactId } : {}),
173
175
  writeSet: [
174
176
  `notebook:${notebook.id}`,
175
177
  ...notebook.cells.map((cell) => `cell:${notebook.id}:${cell.id}`),
@@ -185,6 +187,98 @@ async function writeNotebook(context, notebook) {
185
187
  return ok(revisionFor(context, mutation.revision), mutation.revision);
186
188
  });
187
189
  }
190
+ function cellHistory(context, notebookId, cellId, options) {
191
+ assertUuidV7(notebookId, "Notebook ID");
192
+ assertUuidV7(cellId, "Cell ID");
193
+ const limit = Math.max(1, Math.min(options?.limit ?? 50, 500));
194
+ const commitMeta = commitMetaByHash(context);
195
+ let rows;
196
+ try {
197
+ rows = context.store.db
198
+ .prepare(`SELECT notebook_id, cell_id, type, label, grid_row, grid_column,
199
+ output_entity_id, prompt, provider, model, operation, tool,
200
+ inputs_json, output_artifact_id, commit_hash, committer, commit_date
201
+ FROM dolt_history_cells
202
+ WHERE notebook_id=? AND cell_id=?
203
+ ORDER BY commit_date DESC
204
+ LIMIT ?`)
205
+ .all(notebookId, cellId, limit);
206
+ }
207
+ catch {
208
+ rows = context.store.db.doltHistoryOf("cells")
209
+ .filter((row) => row.notebook_id === notebookId && row.cell_id === cellId)
210
+ .sort((left, right) => right.commit_date.localeCompare(left.commit_date))
211
+ .slice(0, limit);
212
+ }
213
+ return rows.map((row) => {
214
+ const meta = commitMeta.get(row.commit_hash);
215
+ return {
216
+ commitHash: row.commit_hash,
217
+ commitDate: commitDateIso(row.commit_date),
218
+ committer: row.committer,
219
+ ...(meta?.operation ? { operation: meta.operation } : {}),
220
+ ...(meta?.operationId ? { operationId: meta.operationId } : {}),
221
+ ...(meta?.artifactId ? { artifactId: meta.artifactId } : {}),
222
+ ...(meta?.details ? { details: meta.details } : {}),
223
+ cell: rowToCell(row, [], []),
224
+ };
225
+ });
226
+ }
227
+ function executionHistory(context, notebookId, cellId, options) {
228
+ assertUuidV7(notebookId, "Notebook ID");
229
+ assertUuidV7(cellId, "Cell ID");
230
+ const limit = Math.max(1, Math.min(options?.limit ?? 50, 500));
231
+ const commitMeta = commitMetaByHash(context);
232
+ let rows;
233
+ try {
234
+ rows = context.store.db
235
+ .prepare(`SELECT notebook_id, cell_id, fingerprint, status, output_artifact_id,
236
+ provider_artifact_id, run_id, completed_at, started_at,
237
+ updated_at, tool, error, stale, fixture_baseline,
238
+ commit_hash, committer, commit_date
239
+ FROM dolt_history_notebook_cell_executions
240
+ WHERE notebook_id=? AND cell_id=?
241
+ ORDER BY commit_date DESC
242
+ LIMIT ?`)
243
+ .all(notebookId, cellId, limit);
244
+ }
245
+ catch {
246
+ rows = context.store.db.doltHistoryOf("notebook_cell_executions")
247
+ .filter((row) => row.notebook_id === notebookId && row.cell_id === cellId)
248
+ .sort((left, right) => right.commit_date.localeCompare(left.commit_date))
249
+ .slice(0, limit);
250
+ }
251
+ return rows.map((row) => {
252
+ const meta = commitMeta.get(row.commit_hash);
253
+ return {
254
+ commitHash: row.commit_hash,
255
+ commitDate: commitDateIso(row.commit_date),
256
+ committer: row.committer,
257
+ notebookId: row.notebook_id,
258
+ cellId: row.cell_id,
259
+ ...(meta?.operation ? { operation: meta.operation } : {}),
260
+ ...(meta?.operationId ? { operationId: meta.operationId } : {}),
261
+ ...(meta?.artifactId ? { artifactId: meta.artifactId } : {}),
262
+ ...(meta?.details ? { details: meta.details } : {}),
263
+ execution: executionFromRow(row),
264
+ };
265
+ });
266
+ }
267
+ function commitMetaByHash(context) {
268
+ const map = new Map();
269
+ for (const commit of context.store.db.doltLog()) {
270
+ const parsed = parseCommitMessage(commit.message);
271
+ if (!parsed)
272
+ continue;
273
+ map.set(commit.commit_hash, {
274
+ operation: parsed.operation,
275
+ operationId: parsed.operationId,
276
+ ...(parsed.artifactId ? { artifactId: parsed.artifactId } : {}),
277
+ details: parsed.details,
278
+ });
279
+ }
280
+ return map;
281
+ }
188
282
  async function insertNotebookCell(context, notebookId, cell) {
189
283
  return resultOf(async () => {
190
284
  assertUuidV7(notebookId, "Notebook ID");
@@ -194,7 +288,6 @@ async function insertNotebookCell(context, notebookId, cell) {
194
288
  }
195
289
  const prospective = { ...notebook, cells: [...notebook.cells, cell] };
196
290
  validateCellFields(context, prospective, cell);
197
- assertCellSlugFree(context, notebookId, cell);
198
291
  assertCellSlotFree(context, notebookId, cell);
199
292
  const mutation = await context.store.semantic({
200
293
  operation: "insert_cell",
@@ -223,7 +316,6 @@ async function updateNotebookCell(context, notebookId, cell) {
223
316
  cells: notebook.cells.map((existing) => existing.id === cell.id ? cell : existing),
224
317
  };
225
318
  validateCellFields(context, prospective, cell);
226
- assertCellSlugFree(context, notebookId, cell);
227
319
  assertCellSlotFree(context, notebookId, cell);
228
320
  const mutation = await context.store.semantic({
229
321
  operation: "update_cell",
@@ -270,6 +362,7 @@ async function removeNotebookCell(context, notebookId, cellId) {
270
362
  operation: "remove_cell",
271
363
  tables: [
272
364
  "cells",
365
+ "generations",
273
366
  "notebook_cell_executions",
274
367
  "notebook_generation_plans",
275
368
  "edges",
@@ -287,15 +380,6 @@ async function removeNotebookCell(context, notebookId, cellId) {
287
380
  return ok(revisionFor(context, mutation.revision), mutation.revision);
288
381
  });
289
382
  }
290
- function assertCellSlugFree(context, notebookId, cell) {
291
- const found = context.store.db
292
- .prepare(`SELECT cell_id FROM cells
293
- WHERE notebook_id=? AND slug=? AND cell_id<>?`)
294
- .get(notebookId, cell.slug, cell.id);
295
- if (found) {
296
- throw new Error(`Duplicate cell slug: ${cell.slug}`);
297
- }
298
- }
299
383
  function assertCellSlotFree(context, notebookId, cell) {
300
384
  const found = context.store.db
301
385
  .prepare(`SELECT cell_id FROM cells
@@ -305,13 +389,6 @@ function assertCellSlotFree(context, notebookId, cell) {
305
389
  throw new Error(`Cell slot is occupied: ${cell.slot.row}:${cell.slot.column}`);
306
390
  }
307
391
  }
308
- /**
309
- * Repairs grid-slot collisions left behind by a merge (the schema no longer
310
- * enforces slot uniqueness so two forks can land cells on the same slot).
311
- * Within each colliding slot the protected cell wins when present, otherwise
312
- * the lowest cell id; losers keep their column and move to fresh rows below
313
- * the current grid, in cell-id order.
314
- */
315
392
  function repairCellSlots(context, notebookId, protectedCellId) {
316
393
  const rows = context.store.db
317
394
  .prepare(`SELECT cell_id, grid_row, grid_column FROM cells
@@ -319,67 +396,46 @@ function repairCellSlots(context, notebookId, protectedCellId) {
319
396
  ORDER BY grid_row, grid_column, cell_id`)
320
397
  .all(notebookId);
321
398
  const slots = new Map();
322
- let maxRow = -1;
323
399
  for (const row of rows) {
324
- maxRow = Math.max(maxRow, row.grid_row);
325
400
  const key = `${row.grid_row}:${row.grid_column}`;
326
401
  const group = slots.get(key) ?? [];
327
- group.push({ cell_id: row.cell_id, grid_column: row.grid_column });
402
+ group.push(row);
328
403
  slots.set(key, group);
329
404
  }
330
- let nextRow = maxRow + 1;
331
- const move = context.store.db.prepare("UPDATE cells SET grid_row=? WHERE notebook_id=? AND cell_id=?");
405
+ const occupied = [];
406
+ const losers = [];
332
407
  for (const group of slots.values()) {
333
- if (group.length < 2)
334
- continue;
335
408
  const winner = protectedCellId && group.some((row) => row.cell_id === protectedCellId)
336
409
  ? protectedCellId
337
410
  : group[0].cell_id;
338
- for (const row of group) {
339
- if (row.cell_id === winner)
340
- continue;
341
- move.run(nextRow, notebookId, row.cell_id);
342
- nextRow += 1;
343
- }
411
+ const winnerRow = group.find((row) => row.cell_id === winner);
412
+ occupied.push({
413
+ row: winnerRow.grid_row,
414
+ column: winnerRow.grid_column,
415
+ });
416
+ losers.push(...group
417
+ .filter((row) => row.cell_id !== winner)
418
+ .map((row) => row.cell_id));
344
419
  }
345
- }
346
- /**
347
- * `UNIQUE(notebook_id, slug)` is immediate and the upsert loop writes cells
348
- * one at a time, so a document that validly swaps or rotates slugs between
349
- * surviving cells would collide mid-loop. Every surviving cell whose slug is
350
- * about to change is first parked on a unique temporary slug derived from
351
- * its current one — same type prefix, so the slug/type CHECK holds — and
352
- * the upsert pass then assigns the final slugs.
353
- */
354
- function evacuateChangedCellSlugs(context, notebook) {
355
- const existing = context.store.db
356
- .prepare("SELECT cell_id, slug FROM cells WHERE notebook_id=?")
357
- .all(notebook.id);
358
- if (existing.length === 0)
359
- return;
360
- const incomingSlugs = new Map(notebook.cells.map((cell) => [
361
- cell.id,
362
- normalizeCellForWrite(cell).slug ?? "",
363
- ]));
364
- const evacuate = context.store.db.prepare("UPDATE cells SET slug=? WHERE notebook_id=? AND cell_id=?");
365
- for (const row of existing) {
366
- const nextSlug = incomingSlugs.get(row.cell_id);
367
- if (nextSlug === undefined || nextSlug === row.slug)
368
- continue;
369
- evacuate.run(`${row.slug}-evac-${newUuidV7().replace(/-/g, "")}`, notebook.id, row.cell_id);
420
+ const destinations = firstEmptyNotebookGridSlots(occupied, losers.length);
421
+ const move = context.store.db.prepare(`UPDATE cells SET grid_row=?, grid_column=?
422
+ WHERE notebook_id=? AND cell_id=?`);
423
+ for (const [index, cellId] of losers.entries()) {
424
+ const slot = destinations[index];
425
+ move.run(slot.row, slot.column, notebookId, cellId);
370
426
  }
371
427
  }
372
428
  function upsertNotebookCell(context, notebookId, cell) {
373
429
  const normalized = normalizeCellForWrite(cell);
374
430
  context.store.db
375
431
  .prepare(`INSERT INTO cells(
376
- notebook_id, cell_id, type, slug, grid_row, grid_column,
432
+ notebook_id, cell_id, type, label, grid_row, grid_column,
377
433
  output_entity_id, prompt, provider, model, operation, tool,
378
434
  inputs_json, output_artifact_id
379
435
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
380
436
  ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
381
437
  type=excluded.type,
382
- slug=excluded.slug,
438
+ label=excluded.label,
383
439
  grid_row=excluded.grid_row,
384
440
  grid_column=excluded.grid_column,
385
441
  output_entity_id=excluded.output_entity_id,
@@ -390,7 +446,7 @@ function upsertNotebookCell(context, notebookId, cell) {
390
446
  tool=excluded.tool,
391
447
  inputs_json=excluded.inputs_json,
392
448
  output_artifact_id=excluded.output_artifact_id`)
393
- .run(notebookId, normalized.id, normalized.type, requiredText(normalized.slug, "Cell slug"), normalized.slot.row, normalized.slot.column, normalized.outputEntityId ?? null, normalized.prompt ?? null, normalized.provider ?? null, normalized.model ?? null, normalized.operation ?? null, normalized.tool ?? null, canonicalJson(normalized.inputs ?? {}), normalized.outputArtifactId ?? null);
449
+ .run(notebookId, normalized.id, normalized.type, normalized.label?.trim() || null, normalized.slot.row, normalized.slot.column, normalized.outputEntityId ?? null, normalized.prompt ?? null, normalized.provider ?? null, normalized.model ?? null, normalized.operation ?? null, normalized.tool ?? null, canonicalJson(normalized.inputs ?? {}), normalized.outputArtifactId ?? null);
394
450
  synchronizeCellReferences(context, notebookId, normalized);
395
451
  synchronizePinnedResults(context, notebookId, normalized);
396
452
  }
@@ -404,11 +460,10 @@ async function deleteNotebook(context, notebookId) {
404
460
  "notebooks",
405
461
  "notebook_fields",
406
462
  "cells",
463
+ "generations",
407
464
  "notebook_cell_executions",
408
465
  "notebook_generation_plans",
409
466
  "notebook_run_plans",
410
- "notebook_transcript_edits",
411
- "notebook_transcript_attachments",
412
467
  "edges",
413
468
  "runs",
414
469
  "cell_references",
@@ -485,7 +540,7 @@ function notebookFromRows(context, row) {
485
540
  ORDER BY field`)
486
541
  .all(row.notebook_id);
487
542
  const cells = context.store.db
488
- .prepare(`SELECT cell_id, type, slug, grid_row, grid_column, output_entity_id,
543
+ .prepare(`SELECT cell_id, type, label, grid_row, grid_column, output_entity_id,
489
544
  prompt, provider, model, operation, tool,
490
545
  inputs_json, output_artifact_id
491
546
  FROM cells WHERE notebook_id=?
@@ -529,16 +584,6 @@ function notebookFromRows(context, row) {
529
584
  FROM notebook_run_plans
530
585
  WHERE notebook_id=? ORDER BY created_at, plan_id`)
531
586
  .all(row.notebook_id);
532
- const transcriptEdits = context.store.db
533
- .prepare(`SELECT action_id, kind, restored, payload_json
534
- FROM notebook_transcript_edits
535
- WHERE notebook_id=? ORDER BY action_id`)
536
- .all(row.notebook_id);
537
- const transcriptAttachments = context.store.db
538
- .prepare(`SELECT attachment_id, payload_json
539
- FROM notebook_transcript_attachments
540
- WHERE notebook_id=? ORDER BY attachment_id`)
541
- .all(row.notebook_id);
542
587
  const field = new Map(fields.map((item) => [
543
588
  item.field,
544
589
  parseJson(item.value_json, null),
@@ -558,16 +603,6 @@ function notebookFromRows(context, row) {
558
603
  ...(typeof field.get("analysis_revision") === "string"
559
604
  ? { analysisRevision: field.get("analysis_revision") }
560
605
  : {}),
561
- ...(isRecord(field.get("audio_spine"))
562
- ? {
563
- audioSpine: field.get("audio_spine"),
564
- }
565
- : {}),
566
- ...(isRecord(field.get("current_selection"))
567
- ? {
568
- currentSelection: field.get("current_selection"),
569
- }
570
- : {}),
571
606
  ...(isRecord(field.get("fixture"))
572
607
  ? { fixture: field.get("fixture") }
573
608
  : {}),
@@ -577,8 +612,6 @@ function notebookFromRows(context, row) {
577
612
  ])),
578
613
  generationPlans: generationPlans.map(generationPlanFromRow),
579
614
  notebookRunPlans: runPlans.map(runPlanFromRow),
580
- transcriptEdits: transcriptEdits.map(transcriptEditFromRow),
581
- transcriptAttachments: transcriptAttachments.map(transcriptAttachmentFromRow),
582
615
  cells: cells.map((cell) => rowToCell(cell, references.filter((reference) => reference.cell_id === cell.cell_id), pinnedResults.filter((result) => result.cell_id === cell.cell_id))),
583
616
  edges: edges.map(rowToEdge),
584
617
  createdAt: new Date(row.created_at).toISOString(),
@@ -586,16 +619,11 @@ function notebookFromRows(context, row) {
586
619
  }
587
620
  function validateNotebook(context, notebook) {
588
621
  const cellIds = new Set();
589
- const cellSlugs = new Set();
590
622
  const occupiedSlots = new Set();
591
623
  for (const cell of notebook.cells) {
592
624
  if (cellIds.has(cell.id))
593
625
  throw new Error(`Duplicate cell ID: ${cell.id}`);
594
626
  cellIds.add(cell.id);
595
- if (cellSlugs.has(cell.slug)) {
596
- throw new Error(`Duplicate cell slug: ${cell.slug}`);
597
- }
598
- cellSlugs.add(cell.slug);
599
627
  const slot = `${cell.slot.row}:${cell.slot.column}`;
600
628
  if (occupiedSlots.has(slot))
601
629
  throw new Error(`Duplicate cell slot: ${slot}`);
@@ -625,14 +653,8 @@ function validateCellFields(context, notebook, cell) {
625
653
  if (!NOTEBOOK_CELL_TYPE_SET.has(cell.type)) {
626
654
  throw new Error(`Invalid cell type: ${cell.type}`);
627
655
  }
628
- const slug = requiredText(cell.slug, "Cell slug");
629
- if (slug !== cell.slug || !isValidNotebookCellSlug(cell.type, slug)) {
630
- throw new Error(`Invalid ${cell.type} cell slug: ${cell.slug}`);
631
- }
632
- if (!Number.isInteger(cell.slot.row) ||
633
- cell.slot.row < 0 ||
634
- !Number.isInteger(cell.slot.column)) {
635
- throw new Error(`Cell slot row must be nonnegative and column must be an integer: ${cell.id}`);
656
+ if (!isNotebookGridSlot(cell.slot)) {
657
+ throw new Error(`Cell slot must be within ${NOTEBOOK_GRID_ADDRESS_RANGE}: ${cell.id}`);
636
658
  }
637
659
  if (cell.outputEntityId) {
638
660
  assertUuidV7(cell.outputEntityId, "Cell output entity ID");
@@ -648,7 +670,6 @@ function validateCellFields(context, notebook, cell) {
648
670
  function synchronizeNotebookChildren(context, notebook) {
649
671
  deleteMissing(context, "edges", "edge_id", notebook.id, notebook.edges.map((edge) => edge.id));
650
672
  deleteMissing(context, "cells", "cell_id", notebook.id, notebook.cells.map((cell) => cell.id));
651
- evacuateChangedCellSlugs(context, notebook);
652
673
  for (const cell of notebook.cells) {
653
674
  upsertNotebookCell(context, notebook.id, cell);
654
675
  }
@@ -669,8 +690,6 @@ function synchronizeNotebookState(context, notebook) {
669
690
  synchronizeCellExecutions(context, notebook);
670
691
  synchronizeGenerationPlans(context, notebook);
671
692
  synchronizeRunPlans(context, notebook);
672
- synchronizeTranscriptEdits(context, notebook);
673
- synchronizeTranscriptAttachments(context, notebook);
674
693
  }
675
694
  function synchronizeNotebookFields(context, notebook) {
676
695
  const values = new Map();
@@ -689,12 +708,6 @@ function synchronizeNotebookFields(context, notebook) {
689
708
  if (notebook.analysisRevision !== undefined) {
690
709
  values.set("analysis_revision", notebook.analysisRevision);
691
710
  }
692
- if (notebook.audioSpine !== undefined) {
693
- values.set("audio_spine", notebook.audioSpine);
694
- }
695
- if (notebook.currentSelection !== undefined) {
696
- values.set("current_selection", notebook.currentSelection);
697
- }
698
711
  if (notebook.fixture !== undefined) {
699
712
  values.set("fixture", notebook.fixture);
700
713
  }
@@ -785,34 +798,6 @@ function synchronizeRunPlans(context, notebook) {
785
798
  upsert.run(notebook.id, requiredText(plan.planId, "Notebook run plan ID"), requiredText(plan.status, "Notebook run plan status"), canonicalJson(plan.plan), canonicalJson(plan.paidCellIds), canonicalJson(plan.cellDefinitionFingerprints), nonNegativeFinite(plan.knownCostUsd, "Notebook run plan known cost"), nonNegativeInteger(plan.unknownCostCount, "Notebook run plan unknown cost count"), requiredText(plan.createdAt, "Notebook run plan created time"), requiredText(plan.updatedAt, "Notebook run plan updated time"), plan.runId ?? null, plan.outputs === undefined ? null : canonicalJson(plan.outputs), plan.error ?? null);
786
799
  }
787
800
  }
788
- function synchronizeTranscriptEdits(context, notebook) {
789
- const edits = notebook.transcriptEdits ?? [];
790
- assertUnique(edits.map((edit) => edit.actionId), "transcript edit action ID");
791
- deleteMissingNotebookRows(context, "notebook_transcript_edits", "action_id", notebook.id, edits.map((edit) => edit.actionId));
792
- const upsert = context.store.db.prepare(`INSERT INTO notebook_transcript_edits(
793
- notebook_id, action_id, kind, restored, payload_json
794
- ) VALUES (?, ?, ?, ?, ?)
795
- ON CONFLICT(notebook_id, action_id) DO UPDATE SET
796
- kind=excluded.kind,
797
- restored=excluded.restored,
798
- payload_json=excluded.payload_json`);
799
- for (const edit of edits) {
800
- upsert.run(notebook.id, requiredText(edit.actionId, "Transcript edit action ID"), requiredText(edit.kind, "Transcript edit kind"), edit.restored === true ? 1 : 0, canonicalJson(edit));
801
- }
802
- }
803
- function synchronizeTranscriptAttachments(context, notebook) {
804
- const attachments = notebook.transcriptAttachments ?? [];
805
- assertUnique(attachments.map((attachment) => attachment.id), "transcript attachment ID");
806
- deleteMissingNotebookRows(context, "notebook_transcript_attachments", "attachment_id", notebook.id, attachments.map((attachment) => attachment.id));
807
- const upsert = context.store.db.prepare(`INSERT INTO notebook_transcript_attachments(
808
- notebook_id, attachment_id, payload_json
809
- ) VALUES (?, ?, ?)
810
- ON CONFLICT(notebook_id, attachment_id) DO UPDATE SET
811
- payload_json=excluded.payload_json`);
812
- for (const attachment of attachments) {
813
- upsert.run(notebook.id, requiredText(attachment.id, "Transcript attachment ID"), canonicalJson(attachment));
814
- }
815
- }
816
801
  function deleteMissingNotebookRows(context, table, idColumn, notebookId, ids) {
817
802
  if (ids.length === 0) {
818
803
  context.store.db
@@ -1015,7 +1000,7 @@ function rowToCell(row, references, pinnedResults) {
1015
1000
  return {
1016
1001
  id: row.cell_id,
1017
1002
  type: notebookCellType(row.type),
1018
- slug: row.slug,
1003
+ ...(row.label ? { label: row.label } : {}),
1019
1004
  slot: { row: row.grid_row, column: row.grid_column },
1020
1005
  ...(row.output_entity_id ? { outputEntityId: row.output_entity_id } : {}),
1021
1006
  ...(row.prompt ? { prompt: row.prompt } : {}),
@@ -1032,10 +1017,11 @@ function rowToCell(row, references, pinnedResults) {
1032
1017
  };
1033
1018
  }
1034
1019
  function notebookCellType(type) {
1035
- if (!NOTEBOOK_CELL_TYPE_SET.has(type)) {
1020
+ const resolved = LEGACY_NOTEBOOK_CELL_TYPE_ALIASES[type] ?? type;
1021
+ if (!NOTEBOOK_CELL_TYPE_SET.has(resolved)) {
1036
1022
  throw new Error(`Invalid cell type: ${type}`);
1037
1023
  }
1038
- return type;
1024
+ return resolved;
1039
1025
  }
1040
1026
  function normalizeCellForWrite(cell) {
1041
1027
  const inputs = { ...(cell.inputs ?? {}) };
@@ -1158,22 +1144,6 @@ function runPlanFromRow(row) {
1158
1144
  ...(row.error ? { error: row.error } : {}),
1159
1145
  };
1160
1146
  }
1161
- function transcriptEditFromRow(row) {
1162
- const payload = parseJson(row.payload_json, {});
1163
- return {
1164
- ...payload,
1165
- actionId: row.action_id,
1166
- kind: row.kind,
1167
- ...(row.restored === 1 ? { restored: true } : {}),
1168
- };
1169
- }
1170
- function transcriptAttachmentFromRow(row) {
1171
- const payload = parseJson(row.payload_json, {});
1172
- return {
1173
- ...payload,
1174
- id: row.attachment_id,
1175
- };
1176
- }
1177
1147
  function assertUnique(values, label) {
1178
1148
  const unique = new Set(values);
1179
1149
  if (unique.size !== values.length) {