@agent-native/core 0.90.4 → 0.90.7

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 (189) hide show
  1. package/README.md +34 -7
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +21 -0
  4. package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
  5. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  6. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  8. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  9. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  10. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  11. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  12. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  13. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  14. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  15. package/corpus/core/docs/content/toolkit-agent-ux.mdx +1 -1
  16. package/corpus/core/docs/content/toolkit-app-adapters.mdx +1 -1
  17. package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +1 -1
  18. package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
  19. package/corpus/core/docs/content/toolkit-command-navigation.mdx +2 -2
  20. package/corpus/core/docs/content/toolkit-comments-review.mdx +1 -1
  21. package/corpus/core/docs/content/toolkit-history.mdx +1 -1
  22. package/corpus/core/docs/content/toolkit-observability.mdx +1 -1
  23. package/corpus/core/docs/content/toolkit-org-team.mdx +1 -1
  24. package/corpus/core/docs/content/toolkit-resources.mdx +1 -1
  25. package/corpus/core/docs/content/toolkit-settings.mdx +1 -1
  26. package/corpus/core/docs/content/toolkit-setup-connections.mdx +1 -1
  27. package/corpus/core/docs/content/toolkit-sharing-ui.mdx +1 -1
  28. package/corpus/core/docs/content/toolkit-sharing.mdx +1 -1
  29. package/corpus/core/docs/content/toolkit-shell-hooks.mdx +1 -1
  30. package/corpus/core/docs/content/toolkit-ui.mdx +1 -1
  31. package/corpus/core/package.json +1 -1
  32. package/corpus/core/src/agent/durable-background.ts +18 -18
  33. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
  34. package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
  35. package/corpus/core/src/agent/production-agent.ts +22 -11
  36. package/corpus/core/src/agent/run-manager.ts +17 -6
  37. package/corpus/core/src/agent/run-store.ts +16 -11
  38. package/corpus/core/src/cli/design-connect.ts +16 -2
  39. package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
  40. package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
  41. package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
  42. package/corpus/core/src/client/use-db-sync.ts +99 -1
  43. package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
  44. package/corpus/core/src/collab/client.ts +49 -4
  45. package/corpus/core/src/deploy/build.ts +103 -0
  46. package/corpus/core/src/server/core-routes-plugin.ts +137 -8
  47. package/corpus/templates/clips/changelog/2026-07-07-faster-local-whisper-dictation-start.md +6 -0
  48. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +311 -31
  49. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +2 -0
  50. package/corpus/templates/content/AGENTS.md +1 -0
  51. package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
  52. package/corpus/templates/content/actions/_database-utils.ts +2 -1
  53. package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
  54. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
  55. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
  56. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
  57. package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
  58. package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
  59. package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
  60. package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
  61. package/corpus/templates/content/app/i18n-data.ts +31 -0
  62. package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
  63. package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
  64. package/corpus/templates/content/parity/matrix.md +24 -24
  65. package/corpus/templates/content/parity/matrix.ts +2 -0
  66. package/corpus/templates/content/shared/api.ts +59 -4
  67. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +9 -0
  68. package/corpus/templates/design/actions/connect-localhost.ts +26 -3
  69. package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
  70. package/corpus/templates/design/actions/read-source-file.ts +28 -0
  71. package/corpus/templates/design/actions/update-file.ts +76 -7
  72. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +207 -11
  73. package/corpus/templates/design/app/components/design/EditPanel.tsx +4 -0
  74. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +387 -25
  75. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1110 -88
  76. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +368 -16
  77. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +24 -0
  78. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +27 -6
  79. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +18 -0
  80. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +5 -4
  81. package/corpus/templates/design/app/components/design/multi-screen/handle-hit-zones.ts +124 -0
  82. package/corpus/templates/design/app/components/design/multi-screen/types.ts +23 -0
  83. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  84. package/corpus/templates/design/app/i18n-data.ts +19 -0
  85. package/corpus/templates/design/changelog/2026-07-07-dragging-a-free-floating-element-into-a-layout-column-or-row.md +6 -0
  86. package/corpus/templates/design/changelog/2026-07-07-dragging-a-repeated-list-item-like-a-to-do-card-generated-fr.md +6 -0
  87. package/corpus/templates/design/changelog/2026-07-07-dragging-an-element-onto-a-button-or-chip-s-middle-no-longer.md +6 -0
  88. package/corpus/templates/design/changelog/2026-07-07-elements-dropped-into-another-board-shape-now-stay-exactly-w.md +6 -0
  89. package/corpus/templates/design/changelog/2026-07-07-fixed-drag-reordering-elements-within-a-screen-no-longer-sil.md +6 -0
  90. package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-an-element-from-a-screen-onto-the-canvas-now-.md +6 -0
  91. package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-elements-between-screens-into-ai-generated-co.md +6 -0
  92. package/corpus/templates/design/changelog/2026-07-07-fixed-moving-elements-between-screens-no-longer-bakes-intern.md +6 -0
  93. package/corpus/templates/design/changelog/2026-07-07-fixed-the-cross-screen-drop-indicator-now-points-at-the-corr.md +6 -0
  94. package/corpus/templates/design/changelog/2026-07-07-fixed-the-zoom-menu-no-longer-leaves-the-canvas-unresponsive.md +6 -0
  95. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
  96. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
  97. package/corpus/templates/design/changelog/2026-07-07-text-drawn-on-the-canvas-is-now-readable-white-instead-of-in.md +6 -0
  98. package/corpus/templates/design/server/source-workspace.ts +27 -3
  99. package/corpus/templates/design/shared/board-file.ts +359 -0
  100. package/corpus/templates/design/shared/breakpoint-media.ts +21 -3
  101. package/corpus/templates/design/shared/code-layer.ts +145 -10
  102. package/dist/agent/durable-background.d.ts +12 -12
  103. package/dist/agent/durable-background.js +18 -18
  104. package/dist/agent/durable-background.js.map +1 -1
  105. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  106. package/dist/agent/engine/ai-sdk-engine.js +8 -2
  107. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  108. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  109. package/dist/agent/engine/anthropic-engine.js +11 -1
  110. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  111. package/dist/agent/production-agent.d.ts +2 -2
  112. package/dist/agent/production-agent.d.ts.map +1 -1
  113. package/dist/agent/production-agent.js +18 -11
  114. package/dist/agent/production-agent.js.map +1 -1
  115. package/dist/agent/run-manager.d.ts +7 -1
  116. package/dist/agent/run-manager.d.ts.map +1 -1
  117. package/dist/agent/run-manager.js +6 -1
  118. package/dist/agent/run-manager.js.map +1 -1
  119. package/dist/agent/run-store.d.ts +1 -1
  120. package/dist/agent/run-store.d.ts.map +1 -1
  121. package/dist/agent/run-store.js +15 -10
  122. package/dist/agent/run-store.js.map +1 -1
  123. package/dist/cli/design-connect.d.ts.map +1 -1
  124. package/dist/cli/design-connect.js +12 -2
  125. package/dist/cli/design-connect.js.map +1 -1
  126. package/dist/client/KeepTabOpenNotice.d.ts +9 -10
  127. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
  128. package/dist/client/KeepTabOpenNotice.js +8 -4
  129. package/dist/client/KeepTabOpenNotice.js.map +1 -1
  130. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  131. package/dist/client/RunStuckBanner.js +14 -13
  132. package/dist/client/RunStuckBanner.js.map +1 -1
  133. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  134. package/dist/client/agent-chat-adapter.js +25 -17
  135. package/dist/client/agent-chat-adapter.js.map +1 -1
  136. package/dist/client/use-db-sync.d.ts +18 -0
  137. package/dist/client/use-db-sync.d.ts.map +1 -1
  138. package/dist/client/use-db-sync.js +88 -1
  139. package/dist/client/use-db-sync.js.map +1 -1
  140. package/dist/client/use-run-stuck-detection.d.ts +1 -1
  141. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  142. package/dist/client/use-run-stuck-detection.js +6 -4
  143. package/dist/client/use-run-stuck-detection.js.map +1 -1
  144. package/dist/collab/client.d.ts.map +1 -1
  145. package/dist/collab/client.js +43 -4
  146. package/dist/collab/client.js.map +1 -1
  147. package/dist/collab/routes.d.ts +1 -1
  148. package/dist/collab/struct-routes.d.ts +1 -1
  149. package/dist/deploy/build.d.ts +1 -0
  150. package/dist/deploy/build.d.ts.map +1 -1
  151. package/dist/deploy/build.js +57 -0
  152. package/dist/deploy/build.js.map +1 -1
  153. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  154. package/dist/notifications/routes.d.ts +2 -2
  155. package/dist/resources/handlers.d.ts +3 -3
  156. package/dist/secrets/routes.d.ts +9 -9
  157. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  158. package/dist/server/core-routes-plugin.d.ts +10 -0
  159. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  160. package/dist/server/core-routes-plugin.js +103 -9
  161. package/dist/server/core-routes-plugin.js.map +1 -1
  162. package/docs/content/agent-native-toolkit.mdx +1 -1
  163. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  164. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  165. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  166. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  167. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  168. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  169. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  170. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  171. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  172. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  173. package/docs/content/toolkit-agent-ux.mdx +1 -1
  174. package/docs/content/toolkit-app-adapters.mdx +1 -1
  175. package/docs/content/toolkit-collaboration-ui.mdx +1 -1
  176. package/docs/content/toolkit-collaboration.mdx +1 -1
  177. package/docs/content/toolkit-command-navigation.mdx +2 -2
  178. package/docs/content/toolkit-comments-review.mdx +1 -1
  179. package/docs/content/toolkit-history.mdx +1 -1
  180. package/docs/content/toolkit-observability.mdx +1 -1
  181. package/docs/content/toolkit-org-team.mdx +1 -1
  182. package/docs/content/toolkit-resources.mdx +1 -1
  183. package/docs/content/toolkit-settings.mdx +1 -1
  184. package/docs/content/toolkit-setup-connections.mdx +1 -1
  185. package/docs/content/toolkit-sharing-ui.mdx +1 -1
  186. package/docs/content/toolkit-sharing.mdx +1 -1
  187. package/docs/content/toolkit-shell-hooks.mdx +1 -1
  188. package/docs/content/toolkit-ui.mdx +1 -1
  189. package/package.json +1 -1
@@ -1523,11 +1523,13 @@ function PropertyOptionSettingsRow({
1523
1523
  export function PropertyValuePopover({
1524
1524
  property,
1525
1525
  documentId,
1526
+ databaseDocumentId = documentId,
1526
1527
  children,
1527
1528
  portalled = true,
1528
1529
  }: {
1529
1530
  property: DocumentProperty;
1530
1531
  documentId: string;
1532
+ databaseDocumentId?: string;
1531
1533
  children: React.ReactNode;
1532
1534
  portalled?: boolean;
1533
1535
  }) {
@@ -1551,6 +1553,7 @@ export function PropertyValuePopover({
1551
1553
  <PropertyValueEditor
1552
1554
  property={property}
1553
1555
  documentId={documentId}
1556
+ databaseDocumentId={databaseDocumentId}
1554
1557
  onDone={() => setOpen(false)}
1555
1558
  />
1556
1559
  </PopoverContent>
@@ -1561,10 +1564,12 @@ export function PropertyValuePopover({
1561
1564
  function PropertyValueEditor({
1562
1565
  property,
1563
1566
  documentId,
1567
+ databaseDocumentId,
1564
1568
  onDone,
1565
1569
  }: {
1566
1570
  property: DocumentProperty;
1567
1571
  documentId: string;
1572
+ databaseDocumentId: string;
1568
1573
  onDone: () => void;
1569
1574
  }) {
1570
1575
  const type = property.definition.type;
@@ -1573,6 +1578,7 @@ function PropertyValueEditor({
1573
1578
  <OptionValueEditor
1574
1579
  property={property}
1575
1580
  documentId={documentId}
1581
+ databaseDocumentId={databaseDocumentId}
1576
1582
  onDone={onDone}
1577
1583
  />
1578
1584
  );
@@ -1583,6 +1589,7 @@ function PropertyValueEditor({
1583
1589
  <CheckboxValueEditor
1584
1590
  property={property}
1585
1591
  documentId={documentId}
1592
+ databaseDocumentId={databaseDocumentId}
1586
1593
  onDone={onDone}
1587
1594
  />
1588
1595
  );
@@ -1593,6 +1600,7 @@ function PropertyValueEditor({
1593
1600
  <DateValueEditor
1594
1601
  property={property}
1595
1602
  documentId={documentId}
1603
+ databaseDocumentId={databaseDocumentId}
1596
1604
  onDone={onDone}
1597
1605
  />
1598
1606
  );
@@ -1603,6 +1611,7 @@ function PropertyValueEditor({
1603
1611
  <PersonValueEditor
1604
1612
  property={property}
1605
1613
  documentId={documentId}
1614
+ databaseDocumentId={databaseDocumentId}
1606
1615
  onDone={onDone}
1607
1616
  />
1608
1617
  );
@@ -1613,6 +1622,7 @@ function PropertyValueEditor({
1613
1622
  <FilesMediaValueEditor
1614
1623
  property={property}
1615
1624
  documentId={documentId}
1625
+ databaseDocumentId={databaseDocumentId}
1616
1626
  onDone={onDone}
1617
1627
  />
1618
1628
  );
@@ -1622,6 +1632,7 @@ function PropertyValueEditor({
1622
1632
  <ScalarValueEditor
1623
1633
  property={property}
1624
1634
  documentId={documentId}
1635
+ databaseDocumentId={databaseDocumentId}
1625
1636
  onDone={onDone}
1626
1637
  />
1627
1638
  );
@@ -1630,14 +1641,16 @@ function PropertyValueEditor({
1630
1641
  function PersonValueEditor({
1631
1642
  property,
1632
1643
  documentId,
1644
+ databaseDocumentId,
1633
1645
  onDone,
1634
1646
  }: {
1635
1647
  property: DocumentProperty;
1636
1648
  documentId: string;
1649
+ databaseDocumentId: string;
1637
1650
  onDone: () => void;
1638
1651
  }) {
1639
1652
  const t = useT();
1640
- const mutation = useSetDocumentProperty(documentId);
1653
+ const mutation = useSetDocumentProperty(documentId, databaseDocumentId);
1641
1654
  const { session } = useSession();
1642
1655
  const [people, setPeople] = useState(() => personItems(property.value));
1643
1656
  const [query, setQuery] = useState("");
@@ -1829,14 +1842,16 @@ function PersonValueEditor({
1829
1842
  function FilesMediaValueEditor({
1830
1843
  property,
1831
1844
  documentId,
1845
+ databaseDocumentId,
1832
1846
  onDone,
1833
1847
  }: {
1834
1848
  property: DocumentProperty;
1835
1849
  documentId: string;
1850
+ databaseDocumentId: string;
1836
1851
  onDone: () => void;
1837
1852
  }) {
1838
1853
  const t = useT();
1839
- const mutation = useSetDocumentProperty(documentId);
1854
+ const mutation = useSetDocumentProperty(documentId, databaseDocumentId);
1840
1855
  const [items, setItems] = useState(() => filesMediaItems(property.value));
1841
1856
  const [linkValue, setLinkValue] = useState("");
1842
1857
  const [uploading, setUploading] = useState(false);
@@ -2036,14 +2051,16 @@ function FilesMediaValueEditor({
2036
2051
  function DateValueEditor({
2037
2052
  property,
2038
2053
  documentId,
2054
+ databaseDocumentId,
2039
2055
  onDone,
2040
2056
  }: {
2041
2057
  property: DocumentProperty;
2042
2058
  documentId: string;
2059
+ databaseDocumentId: string;
2043
2060
  onDone: () => void;
2044
2061
  }) {
2045
2062
  const t = useT();
2046
- const mutation = useSetDocumentProperty(documentId);
2063
+ const mutation = useSetDocumentProperty(documentId, databaseDocumentId);
2047
2064
  const [includeTime, setIncludeTime] = useState(
2048
2065
  documentPropertyDateIncludesTime(property.value),
2049
2066
  );
@@ -2249,14 +2266,16 @@ function DateValueEditor({
2249
2266
  function ScalarValueEditor({
2250
2267
  property,
2251
2268
  documentId,
2269
+ databaseDocumentId,
2252
2270
  onDone,
2253
2271
  }: {
2254
2272
  property: DocumentProperty;
2255
2273
  documentId: string;
2274
+ databaseDocumentId: string;
2256
2275
  onDone: () => void;
2257
2276
  }) {
2258
2277
  const t = useT();
2259
- const mutation = useSetDocumentProperty(documentId);
2278
+ const mutation = useSetDocumentProperty(documentId, databaseDocumentId);
2260
2279
  const type = property.definition.type;
2261
2280
  const inputType =
2262
2281
  type === "number"
@@ -2357,14 +2376,16 @@ function ScalarValueEditor({
2357
2376
  function CheckboxValueEditor({
2358
2377
  property,
2359
2378
  documentId,
2379
+ databaseDocumentId,
2360
2380
  onDone,
2361
2381
  }: {
2362
2382
  property: DocumentProperty;
2363
2383
  documentId: string;
2384
+ databaseDocumentId: string;
2364
2385
  onDone: () => void;
2365
2386
  }) {
2366
2387
  const t = useT();
2367
- const mutation = useSetDocumentProperty(documentId);
2388
+ const mutation = useSetDocumentProperty(documentId, databaseDocumentId);
2368
2389
  const checked = Boolean(property.value);
2369
2390
 
2370
2391
  return (
@@ -2396,15 +2417,20 @@ function CheckboxValueEditor({
2396
2417
  function OptionValueEditor({
2397
2418
  property,
2398
2419
  documentId,
2420
+ databaseDocumentId,
2399
2421
  onDone,
2400
2422
  }: {
2401
2423
  property: DocumentProperty;
2402
2424
  documentId: string;
2425
+ databaseDocumentId: string;
2403
2426
  onDone: () => void;
2404
2427
  }) {
2405
2428
  const t = useT();
2406
- const setValue = useSetDocumentProperty(documentId);
2407
- const configure = useConfigureDocumentProperty(documentId);
2429
+ const setValue = useSetDocumentProperty(documentId, databaseDocumentId);
2430
+ const configure = useConfigureDocumentProperty(
2431
+ documentId,
2432
+ databaseDocumentId,
2433
+ );
2408
2434
  const options = property.definition.options.options ?? [];
2409
2435
  const [optionQuery, setOptionQuery] = useState("");
2410
2436
  const filteredOptions = filterPropertyOptions(options, optionQuery);
@@ -1223,9 +1223,15 @@ function DatabaseTable({
1223
1223
  ) {
1224
1224
  setBuilderReviewResult(null);
1225
1225
  setBuilderReviewCheckedAt(null);
1226
+ const scopedChangeSetIds =
1227
+ activeBuilderReview && activeBuilderReview.rows.length > 0
1228
+ ? activeBuilderReview.rows.map((row) => row.changeSetId)
1229
+ : undefined;
1226
1230
  try {
1227
1231
  const prepared = await prepareBuilderReview.mutateAsync({
1228
1232
  documentId: document.id,
1233
+ sourceId: source?.id,
1234
+ changeSetIds: scopedChangeSetIds,
1229
1235
  pushModeConfirmation: "autosave",
1230
1236
  });
1231
1237
  let nextReview = prepared.review;
@@ -2243,6 +2249,25 @@ export function databaseBulkEditableProperties(properties: DocumentProperty[]) {
2243
2249
  );
2244
2250
  }
2245
2251
 
2252
+ export function databaseBuilderBulkUpdateSource(
2253
+ sources: ContentDatabaseSource[],
2254
+ primarySource: ContentDatabaseSource | null,
2255
+ selectedItems: ContentDatabaseItem[],
2256
+ ) {
2257
+ if (selectedItems.length === 0) return null;
2258
+ const candidates =
2259
+ sources.length > 0 ? sources : primarySource ? [primarySource] : [];
2260
+ return (
2261
+ candidates.find(
2262
+ (candidate) =>
2263
+ candidate.sourceType === "builder-cms" &&
2264
+ selectedItems.every((item) =>
2265
+ candidate.rows.some((row) => row.documentId === item.document.id),
2266
+ ),
2267
+ ) ?? null
2268
+ );
2269
+ }
2270
+
2246
2271
  export function databaseBulkScalarInputState(
2247
2272
  type: DocumentPropertyType,
2248
2273
  input: string,
@@ -6068,6 +6093,30 @@ function BuilderSpaceModelsView({
6068
6093
  const [query, setQuery] = useState("");
6069
6094
 
6070
6095
  if (modelsQuery.isLoading) {
6096
+ if (attachedModelName) {
6097
+ return (
6098
+ <div className="grid min-w-0 gap-2">
6099
+ <div className="grid min-w-0 gap-1.5">
6100
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
6101
+ {dbText("alreadyAttached")}
6102
+ </div>
6103
+ <div className="flex min-w-0 items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm">
6104
+ <span className="flex min-w-0 items-center gap-2">
6105
+ <IconLayoutGrid className="size-4 shrink-0 text-muted-foreground" />
6106
+ <span className="min-w-0 truncate">{attachedModelName}</span>
6107
+ </span>
6108
+ <span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground">
6109
+ attached
6110
+ </span>
6111
+ </div>
6112
+ </div>
6113
+ <div className="flex min-w-0 items-center gap-2 px-2 text-xs text-muted-foreground">
6114
+ <Spinner className="size-3.5" />
6115
+ {dbText("loadingBuilderModels")}
6116
+ </div>
6117
+ </div>
6118
+ );
6119
+ }
6071
6120
  return (
6072
6121
  <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
6073
6122
  <Spinner className="size-3.5" />
@@ -12096,7 +12145,7 @@ function DatabaseBulkEditPopover({
12096
12145
  disabled={disabled}
12097
12146
  >
12098
12147
  <IconPencil className="size-3.5" />
12099
- Set
12148
+ Edit
12100
12149
  </Button>
12101
12150
  </PopoverTrigger>
12102
12151
  <PopoverContent align="start" className="w-[28rem] p-2">
@@ -12308,7 +12357,7 @@ function DatabaseBulkScalarValueEditor({
12308
12357
  autoFocus
12309
12358
  value={value}
12310
12359
  type={inputType}
12311
- aria-label={`Set ${property.definition.name} for selected rows`}
12360
+ aria-label={`Edit ${property.definition.name} for selected rows`}
12312
12361
  placeholder="Value"
12313
12362
  onChange={(event) => setValue(event.target.value)}
12314
12363
  onKeyDown={(event) => {
@@ -12370,7 +12419,7 @@ function DatabaseBulkFilesValueEditor({
12370
12419
  >
12371
12420
  <textarea
12372
12421
  autoFocus
12373
- aria-label="Set files for selected rows"
12422
+ aria-label={dbText("editFilesForSelectedRows")}
12374
12423
  value={value}
12375
12424
  placeholder={dbText("oneFileOrMediaLinkPerLine")}
12376
12425
  rows={4}
@@ -14508,7 +14557,6 @@ function DatabaseTableRow({
14508
14557
  item.properties.find(
14509
14558
  (candidate) => candidate.definition.id === property.definition.id,
14510
14559
  ) ?? property;
14511
-
14512
14560
  const value = (
14513
14561
  <div
14514
14562
  className={cn(
@@ -14555,6 +14603,7 @@ function DatabaseTableRow({
14555
14603
  <PropertyValuePopover
14556
14604
  property={itemProperty}
14557
14605
  documentId={item.document.id}
14606
+ databaseDocumentId={databaseDocumentId}
14558
14607
  >
14559
14608
  {value}
14560
14609
  </PropertyValuePopover>
@@ -2108,6 +2108,30 @@ function BuilderSpaceModelsView({
2108
2108
  const [query, setQuery] = useState("");
2109
2109
 
2110
2110
  if (modelsQuery.isLoading) {
2111
+ if (attachedModelName) {
2112
+ return (
2113
+ <div className="grid min-w-0 gap-2">
2114
+ <div className="grid min-w-0 gap-1.5">
2115
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
2116
+ {dbText("alreadyAttached")}
2117
+ </div>
2118
+ <div className="flex min-w-0 items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm">
2119
+ <span className="flex min-w-0 items-center gap-2">
2120
+ <IconLayoutGrid className="size-4 shrink-0 text-muted-foreground" />
2121
+ <span className="min-w-0 truncate">{attachedModelName}</span>
2122
+ </span>
2123
+ <span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground">
2124
+ attached
2125
+ </span>
2126
+ </div>
2127
+ </div>
2128
+ <div className="flex min-w-0 items-center gap-2 px-2 text-xs text-muted-foreground">
2129
+ <Spinner className="size-3.5" />
2130
+ {dbText("loadingBuilderModels")}
2131
+ </div>
2132
+ </div>
2133
+ );
2134
+ }
2111
2135
  return (
2112
2136
  <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
2113
2137
  <Spinner className="size-3.5" />
@@ -30,6 +30,8 @@ import type {
30
30
  RefreshContentDatabaseSourceRequest,
31
31
  ReviewContentDatabaseSourceChangeSetRequest,
32
32
  SetContentDatabaseSourceWriteModeRequest,
33
+ StageBuilderSourceBulkUpdateRequest,
34
+ StageBuilderSourceBulkUpdateResponse,
33
35
  StageBuilderRevisionRequest,
34
36
  SuggestSourceJoinKeyResponse,
35
37
  UpdateContentDatabaseViewRequest,
@@ -58,7 +60,7 @@ function isContentDatabaseQueryForDocument(
58
60
  return params.documentId === documentId;
59
61
  }
60
62
 
61
- function contentDatabaseQueryFilter(documentId: string) {
63
+ export function contentDatabaseQueryFilter(documentId: string) {
62
64
  return {
63
65
  queryKey: ["action", "get-content-database"],
64
66
  predicate: (query: Query) =>
@@ -81,6 +83,52 @@ export function writeContentDatabaseResponseToCache(
81
83
  );
82
84
  }
83
85
 
86
+ export function applyDocumentPropertyValueToDatabaseResponse(
87
+ current: ContentDatabaseResponse | undefined,
88
+ patch: {
89
+ documentId: string;
90
+ propertyId: string;
91
+ value: ContentDatabaseResponse["properties"][number]["value"];
92
+ },
93
+ ): ContentDatabaseResponse | undefined {
94
+ if (!current) return current;
95
+ const databaseProperty = current.properties.find(
96
+ (property) => property.definition.id === patch.propertyId,
97
+ );
98
+ if (!databaseProperty) return current;
99
+
100
+ let changed = false;
101
+ const items = current.items.map((item) => {
102
+ if (item.document.id !== patch.documentId) return item;
103
+
104
+ const existingIndex = item.properties.findIndex(
105
+ (property) => property.definition.id === patch.propertyId,
106
+ );
107
+ if (existingIndex >= 0) {
108
+ const properties = item.properties.map((property, index) =>
109
+ index === existingIndex
110
+ ? { ...property, value: patch.value }
111
+ : property,
112
+ );
113
+ changed = true;
114
+ return { ...item, properties };
115
+ }
116
+
117
+ changed = true;
118
+ return {
119
+ ...item,
120
+ properties: [
121
+ ...item.properties,
122
+ { ...databaseProperty, value: patch.value },
123
+ ]
124
+ .slice()
125
+ .sort((a, b) => a.definition.position - b.definition.position),
126
+ };
127
+ });
128
+
129
+ return changed ? { ...current, items } : current;
130
+ }
131
+
84
132
  // `get-content-database` returns a union at runtime: the full response, or an
85
133
  // unavailable payload (`{ available: false, reason: "deleted" | "not_found" }`)
86
134
  // with no `database` field. Consumers typed against ContentDatabaseResponse
@@ -668,6 +716,10 @@ export function useBuilderCmsModels(enabled: boolean) {
668
716
  {
669
717
  enabled,
670
718
  retry: false,
719
+ placeholderData: (previous) => previous,
720
+ staleTime: 5 * 60 * 1000,
721
+ gcTime: 30 * 60 * 1000,
722
+ refetchOnWindowFocus: false,
671
723
  },
672
724
  );
673
725
  }
@@ -923,3 +975,20 @@ export function usePrepareBuilderSourceReview(documentId: string) {
923
975
  },
924
976
  });
925
977
  }
978
+
979
+ export function useStageBuilderSourceBulkUpdate(documentId: string) {
980
+ const queryClient = useQueryClient();
981
+ return useActionMutation<
982
+ StageBuilderSourceBulkUpdateResponse,
983
+ StageBuilderSourceBulkUpdateRequest
984
+ >("stage-builder-source-bulk-update", {
985
+ onSuccess: () => {
986
+ queryClient.invalidateQueries({
987
+ queryKey: contentDatabaseQueryKey(documentId),
988
+ });
989
+ queryClient.invalidateQueries({
990
+ queryKey: ["action", "get-content-database-source", { documentId }],
991
+ });
992
+ },
993
+ });
994
+ }
@@ -1,15 +1,21 @@
1
1
  import { useActionMutation, useActionQuery } from "@agent-native/core/client";
2
2
  import type {
3
3
  ConfigureDocumentPropertyRequest,
4
+ ContentDatabaseResponse,
4
5
  DeleteDocumentPropertyRequest,
5
6
  DocumentPropertiesResponse,
7
+ DocumentPropertyValue,
6
8
  DuplicateDocumentPropertyRequest,
7
9
  ReorderDocumentPropertyRequest,
8
10
  SetDocumentPropertyRequest,
9
11
  } from "@shared/api";
10
12
  import { useQueryClient } from "@tanstack/react-query";
11
13
 
12
- import { contentDatabaseQueryKey } from "./use-content-database";
14
+ import {
15
+ applyDocumentPropertyValueToDatabaseResponse,
16
+ contentDatabaseQueryFilter,
17
+ contentDatabaseQueryKey,
18
+ } from "./use-content-database";
13
19
 
14
20
  export function useDocumentProperties(documentId: string | null) {
15
21
  return useActionQuery<DocumentPropertiesResponse>(
@@ -54,15 +60,67 @@ export function useSetDocumentProperty(
54
60
  DocumentPropertiesResponse,
55
61
  SetDocumentPropertyRequest
56
62
  >("set-document-property", {
57
- onSuccess: () => {
63
+ onMutate: async (variables) => {
64
+ await queryClient.cancelQueries(
65
+ contentDatabaseQueryFilter(databaseDocumentId),
66
+ );
67
+ const previous = queryClient.getQueriesData<ContentDatabaseResponse>(
68
+ contentDatabaseQueryFilter(databaseDocumentId),
69
+ );
70
+ queryClient.setQueriesData<ContentDatabaseResponse>(
71
+ contentDatabaseQueryFilter(databaseDocumentId),
72
+ (current) =>
73
+ applyDocumentPropertyValueToDatabaseResponse(current, {
74
+ documentId: variables.documentId,
75
+ propertyId: variables.propertyId,
76
+ value: variables.value,
77
+ }),
78
+ );
79
+ return { previous };
80
+ },
81
+ onError: (_error, _variables, context) => {
82
+ const rollback = context as
83
+ | {
84
+ previous?: Array<[readonly unknown[], unknown]>;
85
+ }
86
+ | undefined;
87
+ for (const [queryKey, data] of rollback?.previous ?? []) {
88
+ queryClient.setQueryData(queryKey, data);
89
+ }
90
+ },
91
+ onSuccess: (data, variables) => {
92
+ const savedValue =
93
+ data.properties.find(
94
+ (property) => property.definition.id === variables.propertyId,
95
+ )?.value ?? variables.value;
96
+ queryClient.setQueriesData<ContentDatabaseResponse>(
97
+ contentDatabaseQueryFilter(databaseDocumentId),
98
+ (current) =>
99
+ applyDocumentPropertyValueToDatabaseResponse(current, {
100
+ documentId: variables.documentId,
101
+ propertyId: variables.propertyId,
102
+ value: savedValue as DocumentPropertyValue,
103
+ }),
104
+ );
58
105
  queryClient.invalidateQueries({
59
- queryKey: ["action", "list-document-properties", { documentId }],
106
+ queryKey: [
107
+ "action",
108
+ "list-document-properties",
109
+ { documentId: variables.documentId },
110
+ ],
60
111
  });
61
112
  queryClient.invalidateQueries({
62
- queryKey: ["action", "get-document", { id: documentId }],
113
+ queryKey: ["action", "get-document", { id: variables.documentId }],
63
114
  });
64
115
  queryClient.invalidateQueries({
65
- queryKey: contentDatabaseQueryKey(databaseDocumentId),
116
+ ...contentDatabaseQueryFilter(databaseDocumentId),
117
+ });
118
+ queryClient.invalidateQueries({
119
+ queryKey: [
120
+ "action",
121
+ "get-content-database-source",
122
+ { documentId: databaseDocumentId },
123
+ ],
66
124
  });
67
125
  },
68
126
  });
@@ -607,6 +607,8 @@ const messages = {
607
607
  addProperty: "新增屬性",
608
608
  back: "返回",
609
609
  builderSpace: "Builder 空間",
610
+ builderBulkUpdateNotStaged: "Builder 批次更新未暫存",
611
+ builderBulkUpdateStaged: "Builder 批次更新已暫存",
610
612
  builderUpdateFailed: "Builder 更新失敗",
611
613
  builderWriteModeWasNotChanged: "Builder 寫入模式未更改",
612
614
  builderBodiesReadyLocally: "Builder 文章正文已在本機準備好。",
@@ -687,6 +689,7 @@ const messages = {
687
689
  nextMonth: "下個月",
688
690
  nextTimelineRange: "下一個時間線範圍",
689
691
  noRowsMatchThisView: "沒有行匹配此檢視",
692
+ editFilesForSelectedRows: "編輯所選行的檔案",
690
693
  oneFileOrMediaLinkPerLine: "每行一個檔案或媒體連結",
691
694
  previewPageTitle: "預覽頁面標題",
692
695
  previousDatabasePage: "上一頁面資料庫頁面",