@agent-native/core 0.90.1 → 0.90.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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -6,8 +6,10 @@ import type {
6
6
  BuilderCmsModelsResponse,
7
7
  ChangeContentDatabaseSourceRoleRequest,
8
8
  ContentDatabaseResponse,
9
+ ContentDatabaseSourceFieldMapping,
9
10
  CreateInlineDatabaseRequest,
10
11
  CreateInlineDatabaseResponse,
12
+ DocumentPropertyType,
11
13
  ListTrashedContentDatabasesResponse,
12
14
  ListContentDatabasesResponse,
13
15
  ContentDatabaseSourceFieldPropertyResponse,
@@ -33,17 +35,122 @@ import type {
33
35
  UpdateContentDatabaseViewRequest,
34
36
  ValidateBuilderSourceExecutionRequest,
35
37
  } from "@shared/api";
38
+ import type { Query, QueryClient } from "@tanstack/react-query";
36
39
  import { useQueryClient } from "@tanstack/react-query";
37
40
 
38
41
  export function contentDatabaseQueryKey(documentId: string) {
39
42
  return ["action", "get-content-database", { documentId }] as const;
40
43
  }
41
44
 
45
+ function isContentDatabaseQueryForDocument(
46
+ queryKey: readonly unknown[],
47
+ documentId: string,
48
+ ) {
49
+ if (
50
+ queryKey[0] !== "action" ||
51
+ queryKey[1] !== "get-content-database" ||
52
+ !queryKey[2] ||
53
+ typeof queryKey[2] !== "object"
54
+ ) {
55
+ return false;
56
+ }
57
+ const params = queryKey[2] as { documentId?: unknown };
58
+ return params.documentId === documentId;
59
+ }
60
+
61
+ function contentDatabaseQueryFilter(documentId: string) {
62
+ return {
63
+ queryKey: ["action", "get-content-database"],
64
+ predicate: (query: Query) =>
65
+ isContentDatabaseQueryForDocument(query.queryKey, documentId),
66
+ };
67
+ }
68
+
69
+ export function writeContentDatabaseResponseToCache(
70
+ queryClient: Pick<QueryClient, "setQueryData" | "setQueriesData">,
71
+ documentId: string,
72
+ data: ContentDatabaseResponse,
73
+ ) {
74
+ queryClient.setQueryData<ContentDatabaseResponse>(
75
+ contentDatabaseQueryKey(documentId),
76
+ data,
77
+ );
78
+ queryClient.setQueriesData<ContentDatabaseResponse>(
79
+ contentDatabaseQueryFilter(documentId),
80
+ data,
81
+ );
82
+ }
83
+
84
+ // `get-content-database` returns a union at runtime: the full response, or an
85
+ // unavailable payload (`{ available: false, reason: "deleted" | "not_found" }`)
86
+ // with no `database` field. Consumers typed against ContentDatabaseResponse
87
+ // must narrow with this guard before touching `data.database`.
88
+ export function isContentDatabaseUnavailable(
89
+ data: unknown,
90
+ ): data is { available: false; reason: string } {
91
+ return (
92
+ !!data &&
93
+ typeof data === "object" &&
94
+ (data as { available?: unknown }).available === false
95
+ );
96
+ }
97
+
98
+ export function readCachedContentDatabaseResponse(
99
+ queryClient: Pick<QueryClient, "getQueryData" | "getQueriesData">,
100
+ documentId: string,
101
+ ) {
102
+ const exact = queryClient.getQueryData<ContentDatabaseResponse>(
103
+ contentDatabaseQueryKey(documentId),
104
+ );
105
+ if (exact && !isContentDatabaseUnavailable(exact)) return exact;
106
+
107
+ const cached = queryClient
108
+ .getQueriesData<ContentDatabaseResponse>(
109
+ contentDatabaseQueryFilter(documentId),
110
+ )
111
+ .find(([, data]) => data && !isContentDatabaseUnavailable(data));
112
+ return cached?.[1];
113
+ }
114
+
115
+ export function clearDeletedContentDatabaseFromCache(
116
+ queryClient: Pick<QueryClient, "removeQueries" | "invalidateQueries">,
117
+ documentId: string,
118
+ ) {
119
+ queryClient.removeQueries(contentDatabaseQueryFilter(documentId));
120
+ queryClient.removeQueries({
121
+ queryKey: ["action", "get-document", { id: documentId }],
122
+ });
123
+ queryClient.invalidateQueries({
124
+ queryKey: ["action", "get-content-database"],
125
+ });
126
+ queryClient.invalidateQueries({
127
+ queryKey: ["action", "get-document"],
128
+ });
129
+ queryClient.invalidateQueries({
130
+ queryKey: ["action", "list-documents"],
131
+ });
132
+ queryClient.invalidateQueries({
133
+ queryKey: ["action", "list-trashed-content-databases"],
134
+ });
135
+ queryClient.invalidateQueries({
136
+ queryKey: ["action", "list-content-databases"],
137
+ });
138
+ }
139
+
42
140
  export function applySourceFieldPropertyToDatabaseResponse(
43
141
  current: ContentDatabaseResponse | undefined,
44
142
  patch: ContentDatabaseSourceFieldPropertyResponse,
45
143
  ): ContentDatabaseResponse | undefined {
46
144
  if (!current || current.database.id !== patch.databaseId) return current;
145
+ const patchSource = (source: ContentDatabaseResponse["source"]) =>
146
+ source
147
+ ? {
148
+ ...source,
149
+ fields: source.fields.map((field) =>
150
+ field.id === patch.sourceField.id ? patch.sourceField : field,
151
+ ),
152
+ }
153
+ : source;
47
154
 
48
155
  const hasProperty = current.properties.some(
49
156
  (property) => property.definition.id === patch.property.definition.id,
@@ -87,18 +194,141 @@ export function applySourceFieldPropertyToDatabaseResponse(
87
194
  : [...item.properties, nextProperty],
88
195
  };
89
196
  }),
90
- source: current.source
197
+ source: patchSource(current.source),
198
+ sources: current.sources?.map((source) => patchSource(source)!),
199
+ };
200
+ }
201
+
202
+ function propertyTypeForOptimisticSourceField(
203
+ sourceFieldType: string,
204
+ ): DocumentPropertyType {
205
+ if (sourceFieldType === "number") return "number";
206
+ if (sourceFieldType === "datetime" || sourceFieldType === "date") {
207
+ return "date";
208
+ }
209
+ if (sourceFieldType === "url") return "url";
210
+ if (sourceFieldType === "boolean" || sourceFieldType === "checkbox") {
211
+ return "checkbox";
212
+ }
213
+ return "text";
214
+ }
215
+
216
+ function optimisticSourceFieldPropertyId(sourceFieldId: string) {
217
+ return `optimistic-source-field-property:${sourceFieldId}`;
218
+ }
219
+
220
+ function findSourceFieldById(
221
+ current: ContentDatabaseResponse,
222
+ sourceFieldId: string,
223
+ ): ContentDatabaseSourceFieldMapping | null {
224
+ const sources = current.sources ?? (current.source ? [current.source] : []);
225
+ for (const source of sources) {
226
+ const field = source.fields.find(
227
+ (candidate) => candidate.id === sourceFieldId,
228
+ );
229
+ if (field) return field;
230
+ }
231
+ return null;
232
+ }
233
+
234
+ export function applyOptimisticSourceFieldPropertyToDatabaseResponse(
235
+ current: ContentDatabaseResponse | undefined,
236
+ variables: AddContentDatabaseSourceFieldPropertyRequest,
237
+ ): ContentDatabaseResponse | undefined {
238
+ if (
239
+ !current ||
240
+ (current.database.id !== variables.documentId &&
241
+ current.database.documentId !== variables.documentId)
242
+ ) {
243
+ return current;
244
+ }
245
+ const field = findSourceFieldById(current, variables.sourceFieldId);
246
+ if (!field || field.propertyId) return current;
247
+
248
+ const propertyId = optimisticSourceFieldPropertyId(field.id);
249
+ const now = new Date().toISOString();
250
+ const position =
251
+ Math.max(
252
+ -1,
253
+ ...current.properties.map((property) => property.definition.position),
254
+ ) + 1;
255
+ const property = {
256
+ definition: {
257
+ id: propertyId,
258
+ databaseId: current.database.id,
259
+ name: field.sourceFieldLabel,
260
+ type: propertyTypeForOptimisticSourceField(field.sourceFieldType),
261
+ visibility: "always_show" as const,
262
+ options: {},
263
+ position,
264
+ createdAt: now,
265
+ updatedAt: now,
266
+ },
267
+ value: null,
268
+ editable: false,
269
+ };
270
+ const sourceField = {
271
+ ...field,
272
+ propertyId,
273
+ propertyName: field.sourceFieldLabel,
274
+ localFieldKey: propertyId,
275
+ freshness: "unknown" as const,
276
+ };
277
+
278
+ return applySourceFieldPropertyToDatabaseResponse(current, {
279
+ databaseId: current.database.id,
280
+ documentId: variables.documentId,
281
+ property,
282
+ sourceField,
283
+ itemValues: current.items.map((item) => ({
284
+ itemId: item.id,
285
+ documentId: item.document.id,
286
+ value: null,
287
+ })),
288
+ });
289
+ }
290
+
291
+ function removeOptimisticSourceFieldProperty(
292
+ current: ContentDatabaseResponse | undefined,
293
+ sourceFieldId: string,
294
+ ): ContentDatabaseResponse | undefined {
295
+ if (!current) return current;
296
+ const propertyId = optimisticSourceFieldPropertyId(sourceFieldId);
297
+ const removeFromSource = (source: ContentDatabaseResponse["source"]) =>
298
+ source
91
299
  ? {
92
- ...current.source,
93
- fields: current.source.fields.map((field) =>
94
- field.id === patch.sourceField.id ? patch.sourceField : field,
300
+ ...source,
301
+ fields: source.fields.map((field) =>
302
+ field.id === sourceFieldId && field.propertyId === propertyId
303
+ ? {
304
+ ...field,
305
+ propertyId: null,
306
+ propertyName: null,
307
+ localFieldKey: field.sourceFieldKey,
308
+ freshness: "fresh" as const,
309
+ }
310
+ : field,
95
311
  ),
96
312
  }
97
- : current.source,
313
+ : source;
314
+ return {
315
+ ...current,
316
+ properties: current.properties.filter(
317
+ (property) => property.definition.id !== propertyId,
318
+ ),
319
+ items: current.items.map((item) => ({
320
+ ...item,
321
+ properties: item.properties.filter(
322
+ (property) => property.definition.id !== propertyId,
323
+ ),
324
+ })),
325
+ source: removeFromSource(current.source),
326
+ sources: current.sources?.map((source) => removeFromSource(source)!),
98
327
  };
99
328
  }
100
329
 
101
330
  export function useContentDatabase(documentId: string | null, limit?: number) {
331
+ const queryClient = useQueryClient();
102
332
  return useActionQuery<ContentDatabaseResponse>(
103
333
  "get-content-database",
104
334
  documentId ? { documentId, limit } : undefined,
@@ -106,6 +336,13 @@ export function useContentDatabase(documentId: string | null, limit?: number) {
106
336
  enabled: !!documentId,
107
337
  retry: false,
108
338
  placeholderData: (previous) => previous,
339
+ initialData: () =>
340
+ documentId
341
+ ? readCachedContentDatabaseResponse(queryClient, documentId)
342
+ : undefined,
343
+ // Cross-key seeds (e.g. a differently-paginated cached response) render
344
+ // instantly but must refetch immediately, not sit fresh for staleTime.
345
+ initialDataUpdatedAt: 0,
109
346
  },
110
347
  );
111
348
  }
@@ -171,22 +408,16 @@ export function useCreateInlineContentDatabase(hostDocumentId: string | null) {
171
408
  export function useDeleteContentDatabase() {
172
409
  const queryClient = useQueryClient();
173
410
  return useActionMutation<
174
- { success: boolean; databaseId: string; deletedAt: string },
411
+ {
412
+ success: boolean;
413
+ databaseId: string;
414
+ documentId: string;
415
+ deletedAt: string;
416
+ },
175
417
  { databaseId: string }
176
418
  >("delete-content-database", {
177
- onSuccess: () => {
178
- queryClient.invalidateQueries({
179
- queryKey: ["action", "get-content-database"],
180
- });
181
- queryClient.invalidateQueries({
182
- queryKey: ["action", "get-document"],
183
- });
184
- queryClient.invalidateQueries({
185
- queryKey: ["action", "list-documents"],
186
- });
187
- queryClient.invalidateQueries({
188
- queryKey: ["action", "list-trashed-content-databases"],
189
- });
419
+ onSuccess: (data) => {
420
+ clearDeletedContentDatabaseFromCache(queryClient, data.documentId);
190
421
  },
191
422
  });
192
423
  }
@@ -194,7 +425,12 @@ export function useDeleteContentDatabase() {
194
425
  export function useRestoreContentDatabase() {
195
426
  const queryClient = useQueryClient();
196
427
  return useActionMutation<
197
- { success: boolean; databaseId: string; deletedAt: null },
428
+ {
429
+ success: boolean;
430
+ databaseId: string;
431
+ documentId: string;
432
+ deletedAt: null;
433
+ },
198
434
  { databaseId: string }
199
435
  >("restore-content-database", {
200
436
  onSuccess: () => {
@@ -210,6 +446,9 @@ export function useRestoreContentDatabase() {
210
446
  queryClient.invalidateQueries({
211
447
  queryKey: ["action", "list-trashed-content-databases"],
212
448
  });
449
+ queryClient.invalidateQueries({
450
+ queryKey: ["action", "list-content-databases"],
451
+ });
213
452
  },
214
453
  });
215
454
  }
@@ -333,10 +572,14 @@ export function useAttachContentDatabaseSource(documentId: string) {
333
572
  ContentDatabaseResponse,
334
573
  AttachContentDatabaseSourceRequest
335
574
  >("attach-content-database-source", {
336
- onSuccess: () => {
575
+ onSuccess: (data) => {
576
+ writeContentDatabaseResponseToCache(queryClient, documentId, data);
337
577
  queryClient.invalidateQueries({
338
578
  queryKey: contentDatabaseQueryKey(documentId),
339
579
  });
580
+ queryClient.invalidateQueries({
581
+ queryKey: ["action", "get-content-database-source", { documentId }],
582
+ });
340
583
  },
341
584
  });
342
585
  }
@@ -364,10 +607,46 @@ export function useAddContentDatabaseSourceFieldProperty(documentId: string) {
364
607
  ContentDatabaseSourceFieldPropertyResponse,
365
608
  AddContentDatabaseSourceFieldPropertyRequest
366
609
  >("add-content-database-source-field-property", {
610
+ onMutate: async (variables) => {
611
+ await queryClient.cancelQueries({
612
+ queryKey: contentDatabaseQueryKey(documentId),
613
+ });
614
+ // Patch every cached response for this document, not just the exact
615
+ // unpaginated key — the rendered table observes a `{documentId, limit}`
616
+ // key, and setQueryData does not partial-match the way invalidate does.
617
+ const previous = queryClient.getQueriesData<ContentDatabaseResponse>(
618
+ contentDatabaseQueryFilter(documentId),
619
+ );
620
+ queryClient.setQueriesData<ContentDatabaseResponse>(
621
+ contentDatabaseQueryFilter(documentId),
622
+ (current) =>
623
+ applyOptimisticSourceFieldPropertyToDatabaseResponse(
624
+ current,
625
+ variables,
626
+ ),
627
+ );
628
+ return { previous };
629
+ },
630
+ onError: (_error, _variables, context) => {
631
+ const rollback = context as
632
+ | {
633
+ previous?: Array<
634
+ [readonly unknown[], ContentDatabaseResponse | undefined]
635
+ >;
636
+ }
637
+ | undefined;
638
+ for (const [queryKey, data] of rollback?.previous ?? []) {
639
+ queryClient.setQueryData(queryKey, data);
640
+ }
641
+ },
367
642
  onSuccess: (data) => {
368
- queryClient.setQueryData<ContentDatabaseResponse>(
369
- contentDatabaseQueryKey(documentId),
370
- (current) => applySourceFieldPropertyToDatabaseResponse(current, data),
643
+ queryClient.setQueriesData<ContentDatabaseResponse>(
644
+ contentDatabaseQueryFilter(documentId),
645
+ (current) =>
646
+ applySourceFieldPropertyToDatabaseResponse(
647
+ removeOptimisticSourceFieldProperty(current, data.sourceField.id),
648
+ data,
649
+ ),
371
650
  );
372
651
  queryClient.invalidateQueries({
373
652
  queryKey: contentDatabaseQueryKey(documentId),
@@ -395,12 +674,16 @@ export function useBuilderCmsModels(enabled: boolean) {
395
674
 
396
675
  export function useContentDatabases(args: {
397
676
  excludeDatabaseId?: string;
677
+ excludeDatabaseIds?: string[];
398
678
  enabled: boolean;
399
679
  }) {
400
680
  return useActionQuery<ListContentDatabasesResponse>(
401
681
  "list-content-databases",
402
682
  args.enabled
403
- ? { excludeDatabaseId: args.excludeDatabaseId ?? undefined }
683
+ ? {
684
+ excludeDatabaseId: args.excludeDatabaseId ?? undefined,
685
+ excludeDatabaseIds: args.excludeDatabaseIds ?? undefined,
686
+ }
404
687
  : undefined,
405
688
  { enabled: args.enabled, retry: false },
406
689
  );
@@ -434,39 +717,60 @@ export function useRefreshContentDatabaseSource(documentId: string) {
434
717
  ContentDatabaseSourceStatusResponse,
435
718
  RefreshContentDatabaseSourceRequest
436
719
  >("refresh-content-database-source", {
720
+ skipActionQueryInvalidation: true,
437
721
  onSuccess: () => {
438
- queryClient.invalidateQueries({
439
- queryKey: contentDatabaseQueryKey(documentId),
440
- });
441
- queryClient.invalidateQueries({
442
- queryKey: ["action", "get-content-database-source", { documentId }],
443
- });
722
+ invalidateContentDatabaseSourceRefreshQueries(queryClient, documentId);
444
723
  },
445
724
  });
446
725
  }
447
726
 
727
+ export function invalidateContentDatabaseSourceRefreshQueries(
728
+ queryClient: {
729
+ invalidateQueries: (filters: { queryKey: readonly unknown[] }) => unknown;
730
+ },
731
+ documentId: string,
732
+ ) {
733
+ queryClient.invalidateQueries({
734
+ queryKey: contentDatabaseQueryKey(documentId),
735
+ });
736
+ queryClient.invalidateQueries({
737
+ queryKey: ["action", "get-content-database-source", { documentId }],
738
+ });
739
+ }
740
+
448
741
  export function useProcessBuilderBodyHydration(documentId: string) {
449
742
  const queryClient = useQueryClient();
450
743
  return useActionMutation<
451
744
  ProcessBuilderBodyHydrationResponse,
452
745
  ProcessBuilderBodyHydrationRequest
453
746
  >("process-builder-body-hydration", {
747
+ skipActionQueryInvalidation: true,
454
748
  onSuccess: (_data, variables) => {
455
- queryClient.invalidateQueries({
456
- queryKey: contentDatabaseQueryKey(documentId),
457
- });
458
- queryClient.invalidateQueries({
459
- queryKey: ["action", "get-content-database-source", { documentId }],
460
- });
461
- if (variables?.documentId) {
462
- queryClient.invalidateQueries({
463
- queryKey: ["action", "get-document", { id: variables.documentId }],
464
- });
465
- }
749
+ invalidateBuilderBodyHydrationQueries(queryClient, documentId, variables);
466
750
  },
467
751
  });
468
752
  }
469
753
 
754
+ export function invalidateBuilderBodyHydrationQueries(
755
+ queryClient: {
756
+ invalidateQueries: (filters: { queryKey: readonly unknown[] }) => unknown;
757
+ },
758
+ documentId: string,
759
+ variables?: Pick<ProcessBuilderBodyHydrationRequest, "documentId"> | null,
760
+ ) {
761
+ queryClient.invalidateQueries({
762
+ queryKey: contentDatabaseQueryKey(documentId),
763
+ });
764
+ queryClient.invalidateQueries({
765
+ queryKey: ["action", "get-content-database-source", { documentId }],
766
+ });
767
+ if (variables?.documentId) {
768
+ queryClient.invalidateQueries({
769
+ queryKey: ["action", "get-document", { id: variables.documentId }],
770
+ });
771
+ }
772
+ }
773
+
470
774
  export function useDisconnectContentDatabaseSource(documentId: string) {
471
775
  const queryClient = useQueryClient();
472
776
  return useActionMutation<
@@ -6,6 +6,15 @@ export function useDbSync() {
6
6
 
7
7
  useCoreDbSync({
8
8
  queryClient,
9
+ // refresh-notion-sync-status is a POST behind an ["action"]-keyed query
10
+ // (useDocumentSyncStatus). Without suppression its own action-change event
11
+ // invalidates all action queries, which refetches the POST, which emits
12
+ // the next event — a self-sustaining refetch storm on every poll tick.
13
+ suppressActionInvalidationFor: [
14
+ "process-builder-body-hydration",
15
+ "refresh-content-database-source",
16
+ "refresh-notion-sync-status",
17
+ ],
9
18
  queryKeys: [
10
19
  "action",
11
20
  "document-sync",
@@ -1,15 +1,20 @@
1
1
  import { useActionQuery, useActionMutation } from "@agent-native/core/client";
2
2
  import type {
3
+ ContentDatabaseItem,
3
4
  Document,
4
5
  DocumentCreateRequest,
6
+ DocumentPropertiesResponse,
5
7
  DocumentUpdateRequest,
6
8
  DocumentUpdateResponse,
7
9
  DocumentMoveRequest,
8
10
  DocumentTreeNode,
9
11
  } from "@shared/api";
12
+ import type { QueryClient } from "@tanstack/react-query";
10
13
  import { useQueryClient } from "@tanstack/react-query";
11
14
  import { toast } from "sonner";
12
15
 
16
+ import { databaseItemBodyHydrationIsPending } from "@/components/editor/body-hydration";
17
+
13
18
  import type { DocumentUpdateConflictResponse } from "../../actions/update-document";
14
19
  import { useRestoreContentDatabase } from "./use-content-database";
15
20
 
@@ -17,6 +22,14 @@ export type { DocumentUpdateConflictResponse };
17
22
 
18
23
  const LIST_DOCUMENTS_QUERY_KEY = ["action", "list-documents", undefined];
19
24
 
25
+ export function documentQueryKey(documentId: string) {
26
+ return ["action", "get-document", { id: documentId }] as const;
27
+ }
28
+
29
+ export function documentPropertiesQueryKey(documentId: string) {
30
+ return ["action", "list-document-properties", { documentId }] as const;
31
+ }
32
+
20
33
  // Extends the shared request/response shapes with the optional
21
34
  // compare-and-swap fields the action supports but shared/api.ts does not
22
35
  // (yet) declare. See actions/update-document.ts for the CAS contract.
@@ -68,6 +81,40 @@ export function mergeDocumentIntoListDocumentsCache(
68
81
  return { ...(old as object), documents: nextDocuments };
69
82
  }
70
83
 
84
+ export function seedDatabaseItemDocumentCaches(
85
+ queryClient: Pick<QueryClient, "getQueryData" | "setQueryData">,
86
+ item: ContentDatabaseItem,
87
+ ) {
88
+ // Seed only cold caches. Overwriting an existing entry would bump its
89
+ // freshness with possibly older table-snapshot data (a background database
90
+ // refetch can lag a just-saved document edit) and suppress the correcting
91
+ // refetch for the whole staleTime window. Rows whose Builder body has not
92
+ // hydrated yet are never seeded: their empty table-snapshot `content` would
93
+ // render as an authoritative empty document.
94
+ if (
95
+ !databaseItemBodyHydrationIsPending(item) &&
96
+ queryClient.getQueryData(documentQueryKey(item.document.id)) === undefined
97
+ ) {
98
+ queryClient.setQueryData<Document>(documentQueryKey(item.document.id), {
99
+ ...item.document,
100
+ properties: item.properties,
101
+ });
102
+ }
103
+ if (
104
+ queryClient.getQueryData(documentPropertiesQueryKey(item.document.id)) ===
105
+ undefined
106
+ ) {
107
+ queryClient.setQueryData<DocumentPropertiesResponse>(
108
+ documentPropertiesQueryKey(item.document.id),
109
+ {
110
+ documentId: item.document.id,
111
+ databaseId: item.databaseId,
112
+ properties: item.properties,
113
+ },
114
+ );
115
+ }
116
+ }
117
+
71
118
  export function useDocuments() {
72
119
  return useActionQuery<Document[]>("list-documents", undefined, {
73
120
  select: (data: any) => {
@@ -73,10 +73,11 @@ export function documentSyncStatusQueryKey(
73
73
  documentId: string,
74
74
  options?: { autoSync?: boolean },
75
75
  ) {
76
+ const normalizedDocumentId = documentId.trim();
76
77
  return [
77
78
  "action",
78
79
  "refresh-notion-sync-status",
79
- { documentId, autoSync: !!options?.autoSync },
80
+ { documentId: normalizedDocumentId, autoSync: !!options?.autoSync },
80
81
  ] as const;
81
82
  }
82
83
 
@@ -124,19 +125,20 @@ export function useDocumentSyncStatus(
124
125
  ) {
125
126
  const queryClient = useQueryClient();
126
127
  const lastObservedSyncedAtRef = useRef<string | null>(null);
128
+ const normalizedDocumentId = documentId?.trim() || null;
127
129
  const autoSync = !!options?.autoSync;
128
130
  const query = useQuery<DocumentSyncStatus>({
129
- queryKey: documentId
130
- ? documentSyncStatusQueryKey(documentId, options)
131
+ queryKey: normalizedDocumentId
132
+ ? documentSyncStatusQueryKey(normalizedDocumentId, options)
131
133
  : ["action", "refresh-notion-sync-status", null],
132
134
  queryFn: () => {
133
- if (!documentId) throw new Error("documentId is required");
135
+ if (!normalizedDocumentId) throw new Error("documentId is required");
134
136
  return callAction<DocumentSyncStatus>("refresh-notion-sync-status", {
135
- documentId,
137
+ documentId: normalizedDocumentId,
136
138
  autoSync,
137
139
  });
138
140
  },
139
- enabled: !!documentId,
141
+ enabled: !!normalizedDocumentId,
140
142
  // Poll Notion aggressively when auto-sync is on so remote changes appear
141
143
  // within ~2s. Server throttles match (see REFRESH_THROTTLE_AUTO_SYNC_MS in
142
144
  // notion-sync.ts) so we make at most one real Notion request per 2s per doc.
@@ -148,7 +150,7 @@ export function useDocumentSyncStatus(
148
150
  });
149
151
 
150
152
  useEffect(() => {
151
- if (!documentId || !query.data?.lastSyncedAt) return;
153
+ if (!normalizedDocumentId || !query.data?.lastSyncedAt) return;
152
154
  if (lastObservedSyncedAtRef.current === query.data.lastSyncedAt) return;
153
155
 
154
156
  lastObservedSyncedAtRef.current = query.data.lastSyncedAt;
@@ -156,7 +158,7 @@ export function useDocumentSyncStatus(
156
158
  const cachedDocument = queryClient.getQueryData<Document>([
157
159
  "action",
158
160
  "get-document",
159
- { id: documentId },
161
+ { id: normalizedDocumentId },
160
162
  ]);
161
163
  const syncedLocalUpdatedAt = query.data.lastPushedLocalUpdatedAt;
162
164
 
@@ -166,12 +168,12 @@ export function useDocumentSyncStatus(
166
168
  syncedLocalUpdatedAt > cachedDocument.updatedAt
167
169
  ) {
168
170
  queryClient.invalidateQueries({
169
- queryKey: ["action", "get-document", { id: documentId }],
171
+ queryKey: ["action", "get-document", { id: normalizedDocumentId }],
170
172
  });
171
173
  queryClient.invalidateQueries({ queryKey: ["action", "list-documents"] });
172
174
  }
173
175
  }, [
174
- documentId,
176
+ normalizedDocumentId,
175
177
  query.data?.lastPushedLocalUpdatedAt,
176
178
  query.data?.lastSyncedAt,
177
179
  queryClient,