@agent-native/core 0.134.0 → 0.134.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 (57) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/template-content-developers.mdx +1 -1
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
  6. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
  7. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
  8. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
  9. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
  10. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
  11. package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
  14. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
  15. package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
  16. package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
  17. package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
  18. package/corpus/templates/content/actions/_database-utils.ts +12 -3
  19. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
  20. package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
  21. package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
  22. package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
  23. package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
  24. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
  25. package/corpus/templates/content/actions/remove-database-items.ts +183 -0
  26. package/corpus/templates/content/actions/set-document-property.ts +57 -39
  27. package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
  28. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
  29. package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
  30. package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
  31. package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
  32. package/corpus/templates/content/app/i18n-data.ts +130 -0
  33. package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
  34. package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
  35. package/corpus/templates/content/parity/matrix.md +1 -1
  36. package/corpus/templates/content/parity/matrix.ts +4 -5
  37. package/corpus/templates/content/shared/api.ts +4 -0
  38. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
  39. package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
  40. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
  41. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
  42. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
  43. package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
  44. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
  45. package/corpus/templates/design/app/pages/Index.tsx +3 -1
  46. package/corpus/templates/design/app/pages/Present.tsx +2 -1
  47. package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
  48. package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
  49. package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
  50. package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
  51. package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
  52. package/corpus/templates/design/package.json +4 -0
  53. package/corpus/templates/design/shared/html-integrity.ts +802 -556
  54. package/dist/notifications/routes.d.ts +3 -3
  55. package/docs/content/template-content-developers.mdx +1 -1
  56. package/package.json +1 -1
  57. package/corpus/templates/content/actions/delete-database-items.ts +0 -89
@@ -91,6 +91,7 @@ import {
91
91
  organizationContentSpaceId,
92
92
  provisionContentSpaces,
93
93
  } from "./_content-spaces.js";
94
+ import { lockDatabaseMemberships } from "./_database-membership-lock.js";
94
95
  import { ensureFilesSystemPropertyDefinitions } from "./_files-system-properties.js";
95
96
  import {
96
97
  databaseItemsPositionScope,
@@ -1448,89 +1449,92 @@ async function enqueueBuilderBodyHydrations(
1448
1449
  const databaseItemIds = Array.from(
1449
1450
  new Set(uniqueRequests.map((request) => request.databaseItemId)),
1450
1451
  );
1451
- const existingRows: ContentDatabaseBodyHydrationQueueRowDb[] = [];
1452
- for (const idChunk of chunks(databaseItemIds, idChunkSize())) {
1453
- existingRows.push(
1454
- ...(await db
1455
- .select()
1456
- .from(schema.contentDatabaseBodyHydrationQueue)
1457
- .where(
1458
- inArray(
1459
- schema.contentDatabaseBodyHydrationQueue.databaseItemId,
1460
- idChunk,
1461
- ),
1462
- )),
1452
+ return db.transaction(async (tx) => {
1453
+ await lockDatabaseMemberships(tx, databaseItemIds);
1454
+ const existingRows: ContentDatabaseBodyHydrationQueueRowDb[] = [];
1455
+ for (const idChunk of chunks(databaseItemIds, idChunkSize())) {
1456
+ existingRows.push(
1457
+ ...(await tx
1458
+ .select()
1459
+ .from(schema.contentDatabaseBodyHydrationQueue)
1460
+ .where(
1461
+ inArray(
1462
+ schema.contentDatabaseBodyHydrationQueue.databaseItemId,
1463
+ idChunk,
1464
+ ),
1465
+ )),
1466
+ );
1467
+ }
1468
+ const existingByItemId = new Map(
1469
+ existingRows.map((row) => [row.databaseItemId, row]),
1463
1470
  );
1464
- }
1465
- const existingByItemId = new Map(
1466
- existingRows.map((row) => [row.databaseItemId, row]),
1467
- );
1468
- const queueRows: (typeof schema.contentDatabaseBodyHydrationQueue.$inferInsert)[] =
1469
- [];
1470
- for (const request of uniqueRequests) {
1471
- const existing = existingByItemId.get(request.databaseItemId);
1472
- const existingEntry = existing ? parseHydrationEntry(existing) : null;
1473
- const shouldPreserveExistingEntry =
1474
- builderEntryHasBodyContent(existingEntry) &&
1475
- !builderEntryHasBodyContent(request.entry);
1476
- const priority =
1477
- request.priority ??
1478
- builderBodyHydrationPriorityForRequest({ documentId: null });
1479
- queueRows.push({
1480
- id: existing?.id ?? crypto.randomUUID(),
1481
- ownerEmail: request.ownerEmail,
1482
- orgId: request.orgId,
1483
- sourceId: request.sourceId,
1484
- databaseItemId: request.databaseItemId,
1485
- documentId: request.documentId,
1486
- sourceRowId: request.entry.id,
1487
- sourceTable: request.sourceTable,
1488
- sourceEntryJson: shouldPreserveExistingEntry
1489
- ? existing!.sourceEntryJson
1490
- : JSON.stringify(request.entry),
1491
- priority: Math.min(existing?.priority ?? priority, priority),
1492
- attempts: existing?.attempts ?? 0,
1493
- lastAttemptedAt: existing?.lastAttemptedAt ?? null,
1494
- lastError: null,
1495
- createdAt: existing?.createdAt ?? request.now,
1496
- updatedAt: request.now,
1497
- });
1498
- }
1499
- const upsertedRows: ContentDatabaseBodyHydrationQueueRowDb[] = [];
1500
- for (const chunk of chunks(queueRows, bulkChunkSizeForColumnCount(15))) {
1501
- upsertedRows.push(
1502
- ...(await db
1503
- .insert(schema.contentDatabaseBodyHydrationQueue)
1504
- .values(chunk)
1505
- .onConflictDoUpdate({
1506
- target: schema.contentDatabaseBodyHydrationQueue.databaseItemId,
1507
- set: {
1508
- ownerEmail: sql`excluded.owner_email`,
1509
- orgId: sql`excluded.org_id`,
1510
- sourceId: sql`excluded.source_id`,
1511
- documentId: sql`excluded.document_id`,
1512
- sourceRowId: sql`excluded.source_row_id`,
1513
- sourceTable: sql`excluded.source_table`,
1514
- sourceEntryJson: sql`excluded.source_entry_json`,
1515
- priority: sql`excluded.priority`,
1516
- lastError: null,
1517
- updatedAt: sql`excluded.updated_at`,
1518
- },
1471
+ const queueRows: (typeof schema.contentDatabaseBodyHydrationQueue.$inferInsert)[] =
1472
+ [];
1473
+ for (const request of uniqueRequests) {
1474
+ const existing = existingByItemId.get(request.databaseItemId);
1475
+ const existingEntry = existing ? parseHydrationEntry(existing) : null;
1476
+ const shouldPreserveExistingEntry =
1477
+ builderEntryHasBodyContent(existingEntry) &&
1478
+ !builderEntryHasBodyContent(request.entry);
1479
+ const priority =
1480
+ request.priority ??
1481
+ builderBodyHydrationPriorityForRequest({ documentId: null });
1482
+ queueRows.push({
1483
+ id: existing?.id ?? crypto.randomUUID(),
1484
+ ownerEmail: request.ownerEmail,
1485
+ orgId: request.orgId,
1486
+ sourceId: request.sourceId,
1487
+ databaseItemId: request.databaseItemId,
1488
+ documentId: request.documentId,
1489
+ sourceRowId: request.entry.id,
1490
+ sourceTable: request.sourceTable,
1491
+ sourceEntryJson: shouldPreserveExistingEntry
1492
+ ? existing!.sourceEntryJson
1493
+ : JSON.stringify(request.entry),
1494
+ priority: Math.min(existing?.priority ?? priority, priority),
1495
+ attempts: existing?.attempts ?? 0,
1496
+ lastAttemptedAt: existing?.lastAttemptedAt ?? null,
1497
+ lastError: null,
1498
+ createdAt: existing?.createdAt ?? request.now,
1499
+ updatedAt: request.now,
1500
+ });
1501
+ }
1502
+ const upsertedRows: ContentDatabaseBodyHydrationQueueRowDb[] = [];
1503
+ for (const chunk of chunks(queueRows, bulkChunkSizeForColumnCount(15))) {
1504
+ upsertedRows.push(
1505
+ ...(await tx
1506
+ .insert(schema.contentDatabaseBodyHydrationQueue)
1507
+ .values(chunk)
1508
+ .onConflictDoUpdate({
1509
+ target: schema.contentDatabaseBodyHydrationQueue.databaseItemId,
1510
+ set: {
1511
+ ownerEmail: sql`excluded.owner_email`,
1512
+ orgId: sql`excluded.org_id`,
1513
+ sourceId: sql`excluded.source_id`,
1514
+ documentId: sql`excluded.document_id`,
1515
+ sourceRowId: sql`excluded.source_row_id`,
1516
+ sourceTable: sql`excluded.source_table`,
1517
+ sourceEntryJson: sql`excluded.source_entry_json`,
1518
+ priority: sql`excluded.priority`,
1519
+ lastError: null,
1520
+ updatedAt: sql`excluded.updated_at`,
1521
+ },
1522
+ })
1523
+ .returning()),
1524
+ );
1525
+ }
1526
+ for (const idChunk of chunks(databaseItemIds, idChunkSize())) {
1527
+ await tx
1528
+ .update(schema.contentDatabaseItems)
1529
+ .set({
1530
+ bodyHydrationStatus: "pending",
1531
+ bodyHydrationError: null,
1532
+ updatedAt: uniqueRequests[0]!.now,
1519
1533
  })
1520
- .returning()),
1521
- );
1522
- }
1523
- for (const idChunk of chunks(databaseItemIds, idChunkSize())) {
1524
- await db
1525
- .update(schema.contentDatabaseItems)
1526
- .set({
1527
- bodyHydrationStatus: "pending",
1528
- bodyHydrationError: null,
1529
- updatedAt: uniqueRequests[0]!.now,
1530
- })
1531
- .where(inArray(schema.contentDatabaseItems.id, idChunk));
1532
- }
1533
- return upsertedRows;
1534
+ .where(inArray(schema.contentDatabaseItems.id, idChunk));
1535
+ }
1536
+ return upsertedRows;
1537
+ });
1534
1538
  }
1535
1539
 
1536
1540
  export async function enqueueBuilderBodyHydrationForItems(args: {
@@ -5308,7 +5312,27 @@ export async function seedMockSourceRows(args: {
5308
5312
  }) {
5309
5313
  if (args.items.length === 0) return;
5310
5314
  const db = getDb();
5311
- const rows = args.items.map((item, index) => {
5315
+ const rows = mockSourceRowsForSeed(args);
5316
+ await db.transaction(async (tx) => {
5317
+ await lockDatabaseMemberships(
5318
+ tx,
5319
+ rows.map((row) => row.databaseItemId),
5320
+ );
5321
+ await insertMockSourceRows(tx, rows, args.now);
5322
+ });
5323
+ }
5324
+
5325
+ function mockSourceRowsForSeed(args: {
5326
+ sourceId: string;
5327
+ ownerEmail: string;
5328
+ sourceType: ContentDatabaseSourceType;
5329
+ sourceTable: string;
5330
+ items: ContentDatabaseItem[];
5331
+ now: string;
5332
+ existingBuilderRows?: Map<string, ExistingBuilderSourceRowIdentity>;
5333
+ builderEntriesByDocumentId?: Map<string, BuilderCmsSourceEntry>;
5334
+ }) {
5335
+ return args.items.map((item, index) => {
5312
5336
  const builderEntry = args.builderEntriesByDocumentId?.get(item.document.id);
5313
5337
  const existingBuilderRow = args.existingBuilderRows?.get(item.document.id);
5314
5338
  const builderIdentity =
@@ -5370,6 +5394,17 @@ export async function seedMockSourceRows(args: {
5370
5394
  updatedAt: args.now,
5371
5395
  };
5372
5396
  });
5397
+ }
5398
+
5399
+ async function insertMockSourceRows(
5400
+ db: any,
5401
+ rows: ReturnType<typeof mockSourceRowsForSeed>,
5402
+ now: string,
5403
+ ) {
5404
+ if (rows.length === 0) return;
5405
+ if (rows.some((row) => !row.sourceId)) {
5406
+ throw new Error("Source row writes require a source ID.");
5407
+ }
5373
5408
  await db
5374
5409
  .insert(schema.contentDatabaseSourceRows)
5375
5410
  .values(rows)
@@ -5383,15 +5418,53 @@ export async function seedMockSourceRows(args: {
5383
5418
  .update(schema.contentDatabaseItems)
5384
5419
  .set({
5385
5420
  bodyHydrationStatus: "unavailable",
5386
- bodyHydrationAttemptedAt: args.now,
5421
+ bodyHydrationAttemptedAt: now,
5387
5422
  bodyHydrationError: null,
5388
5423
  bodyHydrationVersion: null,
5389
- updatedAt: args.now,
5424
+ updatedAt: now,
5390
5425
  })
5391
5426
  .where(inArray(schema.contentDatabaseItems.id, idChunk));
5392
5427
  }
5393
5428
  }
5394
5429
 
5430
+ export async function replaceMockSourceRows(args: {
5431
+ sourceId: string;
5432
+ ownerEmail: string;
5433
+ sourceType: ContentDatabaseSourceType;
5434
+ sourceTable: string;
5435
+ items: ContentDatabaseItem[];
5436
+ now: string;
5437
+ existingBuilderRows?: Map<string, ExistingBuilderSourceRowIdentity>;
5438
+ builderEntriesByDocumentId?: Map<string, BuilderCmsSourceEntry>;
5439
+ documentIds?: string[];
5440
+ }) {
5441
+ const db = getDb();
5442
+ const rows = mockSourceRowsForSeed(args);
5443
+ await db.transaction(async (tx) => {
5444
+ const scope = args.documentIds?.length
5445
+ ? and(
5446
+ eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId),
5447
+ inArray(
5448
+ schema.contentDatabaseSourceRows.documentId,
5449
+ args.documentIds,
5450
+ ),
5451
+ )
5452
+ : eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId);
5453
+ const oldRows = await tx
5454
+ .select({
5455
+ databaseItemId: schema.contentDatabaseSourceRows.databaseItemId,
5456
+ })
5457
+ .from(schema.contentDatabaseSourceRows)
5458
+ .where(scope);
5459
+ await lockDatabaseMemberships(tx, [
5460
+ ...oldRows.map((row) => row.databaseItemId).filter(Boolean),
5461
+ ...rows.map((row) => row.databaseItemId),
5462
+ ]);
5463
+ await tx.delete(schema.contentDatabaseSourceRows).where(scope);
5464
+ await insertMockSourceRows(tx, rows, args.now);
5465
+ });
5466
+ }
5467
+
5395
5468
  export function sourceValuesForSeededSourceRow(args: {
5396
5469
  sourceType: ContentDatabaseSourceType;
5397
5470
  item: ContentDatabaseItem;
@@ -5862,10 +5935,6 @@ export async function resyncMockSourceSnapshot(args: {
5862
5935
  .where(
5863
5936
  eq(schema.contentDatabaseBodyHydrationQueue.sourceId, args.source.id),
5864
5937
  );
5865
- await db
5866
- .delete(schema.contentDatabaseSourceRows)
5867
- .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
5868
-
5869
5938
  await seedMockSourceFields({
5870
5939
  sourceId: args.source.id,
5871
5940
  ownerEmail: args.database.ownerEmail,
@@ -5873,7 +5942,7 @@ export async function resyncMockSourceSnapshot(args: {
5873
5942
  properties,
5874
5943
  now: args.now,
5875
5944
  });
5876
- await seedMockSourceRows({
5945
+ await replaceMockSourceRows({
5877
5946
  sourceId: args.source.id,
5878
5947
  ownerEmail: args.database.ownerEmail,
5879
5948
  sourceType: normalizeSourceType(args.source.sourceType),
@@ -6054,6 +6123,7 @@ export function builderCmsEntryAlreadyRepresented(args: {
6054
6123
 
6055
6124
  export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6056
6125
  database: ContentDatabaseRow;
6126
+ sourceId: string;
6057
6127
  entries: BuilderCmsSourceEntry[];
6058
6128
  now: string;
6059
6129
  sourceTable: string;
@@ -6159,8 +6229,8 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6159
6229
  .filter((row) => !representedDocumentIds.has(row.document.id))
6160
6230
  .map((row) => row.document.title.trim().toLowerCase()),
6161
6231
  );
6162
- const currentItemByDocumentId = new Map(
6163
- currentItems.map((row) => [row.document.id, row.item]),
6232
+ const currentRowByDocumentId = new Map(
6233
+ currentItems.map((row) => [row.document.id, row]),
6164
6234
  );
6165
6235
 
6166
6236
  // Reads MAX(position) for both `documents` and `content_database_items`
@@ -6195,7 +6265,6 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6195
6265
  const documentRows: (typeof schema.documents.$inferInsert)[] = [];
6196
6266
  const itemRows: (typeof schema.contentDatabaseItems.$inferInsert)[] =
6197
6267
  [];
6198
-
6199
6268
  for (const entry of args.entries) {
6200
6269
  if (
6201
6270
  builderCmsEntryAlreadyRepresented({
@@ -6213,9 +6282,9 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6213
6282
  sourceTable: args.sourceTable,
6214
6283
  entryId: entry.id,
6215
6284
  });
6216
- const existingDeterministicItem =
6217
- currentItemByDocumentId.get(documentId);
6218
- if (existingDeterministicItem?.id === itemId) {
6285
+ const existingDeterministicRow =
6286
+ currentRowByDocumentId.get(documentId);
6287
+ if (existingDeterministicRow?.item.id === itemId) {
6219
6288
  // A prior attach can commit the deterministic document/item and
6220
6289
  // fail before linking its source row. Treat that pair as the
6221
6290
  // same Builder identity so refresh repairs the missing link
@@ -6230,7 +6299,8 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6230
6299
  continue;
6231
6300
  }
6232
6301
 
6233
- documentRows.push({
6302
+ const documentPosition = nextDocPosition++;
6303
+ const documentRow = {
6234
6304
  id: documentId,
6235
6305
  spaceId: databaseSpaceId,
6236
6306
  ownerEmail: args.database.ownerEmail,
@@ -6239,20 +6309,22 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6239
6309
  title,
6240
6310
  content: "",
6241
6311
  icon: null,
6242
- position: nextDocPosition++,
6312
+ position: documentPosition,
6243
6313
  isFavorite: 0,
6244
6314
  hideFromSearch: databaseDocument?.hideFromSearch ?? 0,
6245
6315
  visibility: databaseDocument?.visibility ?? "private",
6246
6316
  createdAt: args.now,
6247
6317
  updatedAt: args.now,
6248
- });
6318
+ };
6319
+ const itemPosition = nextItemPosition++;
6320
+ documentRows.push(documentRow);
6249
6321
  itemRows.push({
6250
6322
  id: itemId,
6251
6323
  ownerEmail: args.database.ownerEmail,
6252
6324
  orgId: args.database.orgId,
6253
6325
  databaseId: args.database.id,
6254
6326
  documentId,
6255
- position: nextItemPosition++,
6327
+ position: itemPosition,
6256
6328
  bodyHydrationStatus: "pending",
6257
6329
  bodyHydrationError: null,
6258
6330
  createdAt: args.now,
@@ -6260,6 +6332,100 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6260
6332
  });
6261
6333
  importedEntriesByDocumentId.set(documentId, entry);
6262
6334
  }
6335
+ const candidateDocumentById = new Map(
6336
+ documentRows.map((document) => [document.id!, document]),
6337
+ );
6338
+ const sourceSeedItems: ContentDatabaseItem[] = [
6339
+ ...itemRows.map((item): ContentDatabaseItem => {
6340
+ const document = candidateDocumentById.get(item.documentId)!;
6341
+ return {
6342
+ id: item.id!,
6343
+ databaseId: args.database.id,
6344
+ document: {
6345
+ id: document.id!,
6346
+ parentId: document.parentId ?? null,
6347
+ title: document.title ?? "Untitled",
6348
+ content: "",
6349
+ icon: document.icon ?? null,
6350
+ position: document.position ?? 0,
6351
+ isFavorite: false,
6352
+ hideFromSearch: Boolean(document.hideFromSearch),
6353
+ visibility: document.visibility ?? "private",
6354
+ accessRole: "owner" as const,
6355
+ canView: true,
6356
+ canEdit: true,
6357
+ canManage: true,
6358
+ createdAt: document.createdAt!,
6359
+ updatedAt: document.updatedAt!,
6360
+ },
6361
+ position: item.position ?? 0,
6362
+ properties: [],
6363
+ bodyHydration: {
6364
+ status: "pending" as const,
6365
+ attemptedAt: null,
6366
+ error: null,
6367
+ version: null,
6368
+ },
6369
+ };
6370
+ }),
6371
+ ...[...importedEntriesByDocumentId.keys()].flatMap(
6372
+ (documentId): ContentDatabaseItem[] => {
6373
+ if (candidateDocumentById.has(documentId)) return [];
6374
+ const row = currentRowByDocumentId.get(documentId);
6375
+ if (!row) return [];
6376
+ return [
6377
+ {
6378
+ id: row.item.id,
6379
+ databaseId: args.database.id,
6380
+ document: {
6381
+ id: row.document.id,
6382
+ parentId: row.document.parentId,
6383
+ title: row.document.title,
6384
+ content: "",
6385
+ icon: row.document.icon,
6386
+ position: row.document.position,
6387
+ isFavorite: Boolean(row.document.isFavorite),
6388
+ hideFromSearch: Boolean(row.document.hideFromSearch),
6389
+ visibility: row.document.visibility,
6390
+ accessRole: "owner" as const,
6391
+ canView: true,
6392
+ canEdit: true,
6393
+ canManage: true,
6394
+ createdAt: row.document.createdAt,
6395
+ updatedAt: row.document.updatedAt,
6396
+ },
6397
+ position: row.item.position,
6398
+ properties: [],
6399
+ bodyHydration: {
6400
+ status:
6401
+ row.item.bodyHydrationStatus === "pending" ||
6402
+ row.item.bodyHydrationStatus === "hydrating" ||
6403
+ row.item.bodyHydrationStatus === "hydrated" ||
6404
+ row.item.bodyHydrationStatus === "unavailable" ||
6405
+ row.item.bodyHydrationStatus === "error"
6406
+ ? row.item.bodyHydrationStatus
6407
+ : "hydrated",
6408
+ attemptedAt: row.item.bodyHydrationAttemptedAt,
6409
+ error: row.item.bodyHydrationError,
6410
+ version: row.item.bodyHydrationVersion,
6411
+ },
6412
+ },
6413
+ ];
6414
+ },
6415
+ ),
6416
+ ];
6417
+ if (!args.sourceId) {
6418
+ throw new Error("Builder imports require a source ID.");
6419
+ }
6420
+ const importedSourceRows = mockSourceRowsForSeed({
6421
+ sourceId: args.sourceId,
6422
+ ownerEmail: args.database.ownerEmail,
6423
+ sourceType: "builder-cms",
6424
+ sourceTable: args.sourceTable,
6425
+ items: sourceSeedItems,
6426
+ now: args.now,
6427
+ builderEntriesByDocumentId: importedEntriesByDocumentId,
6428
+ });
6263
6429
  const insertedItemIds = new Set<string>();
6264
6430
  await db.transaction(async (tx) => {
6265
6431
  for (const chunk of chunks(
@@ -6284,6 +6450,11 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6284
6450
  insertedItemIds.add(item.id);
6285
6451
  }
6286
6452
  }
6453
+ await lockDatabaseMemberships(
6454
+ tx,
6455
+ importedSourceRows.map((row) => row.databaseItemId),
6456
+ );
6457
+ await insertMockSourceRows(tx, importedSourceRows, args.now);
6287
6458
  await ensureDocumentsFilesMembership(
6288
6459
  tx,
6289
6460
  documentRows.map((row) => row.id),
@@ -6292,9 +6463,6 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
6292
6463
  );
6293
6464
  });
6294
6465
 
6295
- const candidateDocumentById = new Map(
6296
- documentRows.map((document) => [document.id!, document]),
6297
- );
6298
6466
  const importedItems: ContentDatabaseItem[] = itemRows
6299
6467
  .filter((item) => insertedItemIds.has(item.id!))
6300
6468
  .map((item) => {
@@ -6474,6 +6642,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6474
6642
  if (builderRead.state === "live") {
6475
6643
  const importResult = await importBuilderCmsEntriesAsDatabaseItems({
6476
6644
  database: args.database,
6645
+ sourceId: args.source.id,
6477
6646
  entries: builderEntries,
6478
6647
  now: args.now,
6479
6648
  sourceTable: args.source.sourceTable,
@@ -6553,17 +6722,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6553
6722
  );
6554
6723
  const fetchedDocumentIds = itemsToLink.map((item) => item.document.id);
6555
6724
  if (fetchedDocumentIds.length > 0) {
6556
- for (const idChunk of chunks(fetchedDocumentIds, idChunkSize())) {
6557
- await db
6558
- .delete(schema.contentDatabaseSourceRows)
6559
- .where(
6560
- and(
6561
- eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
6562
- inArray(schema.contentDatabaseSourceRows.documentId, idChunk),
6563
- ),
6564
- );
6565
- }
6566
- await seedMockSourceRows({
6725
+ await replaceMockSourceRows({
6567
6726
  sourceId: args.source.id,
6568
6727
  ownerEmail: args.database.ownerEmail,
6569
6728
  sourceType: "builder-cms",
@@ -6572,6 +6731,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6572
6731
  now: args.now,
6573
6732
  existingBuilderRows,
6574
6733
  builderEntriesByDocumentId,
6734
+ documentIds: fetchedDocumentIds,
6575
6735
  });
6576
6736
  const refreshedFields = await db
6577
6737
  .select()
@@ -6601,18 +6761,25 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6601
6761
  .select({
6602
6762
  id: schema.contentDatabaseSourceRows.id,
6603
6763
  sourceRowId: schema.contentDatabaseSourceRows.sourceRowId,
6764
+ databaseItemId: schema.contentDatabaseSourceRows.databaseItemId,
6604
6765
  })
6605
6766
  .from(schema.contentDatabaseSourceRows)
6606
6767
  .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id))
6607
6768
  ).filter((row) => !activeSourceRowIds.has(row.sourceRowId));
6608
- for (const idChunk of chunks(
6609
- staleRows.map((row) => row.id),
6610
- idChunkSize(),
6611
- )) {
6612
- await db
6613
- .delete(schema.contentDatabaseSourceRows)
6614
- .where(inArray(schema.contentDatabaseSourceRows.id, idChunk));
6615
- }
6769
+ await db.transaction(async (tx) => {
6770
+ await lockDatabaseMemberships(
6771
+ tx,
6772
+ staleRows.map((row) => row.databaseItemId).filter(Boolean),
6773
+ );
6774
+ for (const idChunk of chunks(
6775
+ staleRows.map((row) => row.id),
6776
+ idChunkSize(),
6777
+ )) {
6778
+ await tx
6779
+ .delete(schema.contentDatabaseSourceRows)
6780
+ .where(inArray(schema.contentDatabaseSourceRows.id, idChunk));
6781
+ }
6782
+ });
6616
6783
  }
6617
6784
 
6618
6785
  await updateBuilderCmsSourceReadMetadata({
@@ -6644,10 +6811,6 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6644
6811
  .delete(schema.contentDatabaseSourceFields)
6645
6812
  .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
6646
6813
  }
6647
- await db
6648
- .delete(schema.contentDatabaseSourceRows)
6649
- .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
6650
-
6651
6814
  if (shouldReseedSourceFields) {
6652
6815
  await seedMockSourceFields({
6653
6816
  sourceId: args.source.id,
@@ -6682,7 +6845,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
6682
6845
  : existingBuilderRows.has(item.document.id),
6683
6846
  )
6684
6847
  : response.items;
6685
- await seedMockSourceRows({
6848
+ await replaceMockSourceRows({
6686
6849
  sourceId: args.source.id,
6687
6850
  ownerEmail: args.database.ownerEmail,
6688
6851
  sourceType: "builder-cms",
@@ -6827,9 +6990,6 @@ export async function replaceSourceMetadata(args: {
6827
6990
  await db
6828
6991
  .delete(schema.contentDatabaseSourceFields)
6829
6992
  .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
6830
- await db
6831
- .delete(schema.contentDatabaseSourceRows)
6832
- .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
6833
6993
  }
6834
6994
 
6835
6995
  if (args.source) {
@@ -191,11 +191,11 @@ async function contentDatabaseTableQueryMode(
191
191
  return usesSecondaryField ? "client-required" : "server";
192
192
  }
193
193
 
194
- function canManageRole(role: string) {
194
+ function canManageRole(role: string | undefined) {
195
195
  return role === "owner" || role === "admin";
196
196
  }
197
197
 
198
- function canEditRole(role: string) {
198
+ function canEditRole(role: string | undefined) {
199
199
  return role === "owner" || role === "admin" || role === "editor";
200
200
  }
201
201
 
@@ -399,7 +399,15 @@ function serializeDocument(
399
399
  const isOwner =
400
400
  doc.ownerEmail.trim().toLowerCase() ===
401
401
  getRequestUserEmail()?.trim().toLowerCase();
402
- const accessRole = isOwner ? ("owner" as const) : (shareRole ?? "viewer");
402
+ const hasVisibilityAccess =
403
+ doc.visibility === "public" ||
404
+ (doc.visibility === "org" &&
405
+ !!doc.orgId &&
406
+ doc.orgId === getRequestOrgId());
407
+ const canView = isOwner || hasVisibilityAccess || shareRole !== undefined;
408
+ const accessRole = isOwner
409
+ ? ("owner" as const)
410
+ : (shareRole ?? (hasVisibilityAccess ? ("viewer" as const) : undefined));
403
411
  return {
404
412
  id: doc.id,
405
413
  parentId: doc.parentId,
@@ -414,6 +422,7 @@ function serializeDocument(
414
422
  hideFromSearch: parseDocumentHideFromSearch(doc.hideFromSearch),
415
423
  visibility: doc.visibility,
416
424
  accessRole,
425
+ canView,
417
426
  canEdit: canEditRole(accessRole),
418
427
  canManage: canManageRole(accessRole),
419
428
  databaseMembership: membership