@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
@@ -1 +1 @@
1
- {"version":3,"file":"useCollabReconcile.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,EACL,sCAAsC,EAGvC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sCAAsC,EAAE,CAAC;AAElD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD;AAqBD,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,mFAAmF;IACnF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACtD,IAAI,CAAC;IACV;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EACP,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,eAAe,GAAG,IAAI,CAAC,GAC3D,KAAK,CAAC;IACV;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,mBAAmB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AAoED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAW,EACX,YAAmB,EACnB,SAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,WAA+B,EAC/B,UAA8B,EAC9B,UAAU,EACV,cAAyB,EACzB,UAA8B,EAC9B,uBAAuB,GACxB,EAAE,yBAAyB,GAAG,wBAAwB,CAyVtD"}
1
+ {"version":3,"file":"useCollabReconcile.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,EACL,sCAAsC,EAGvC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,sCAAsC,EAAE,CAAC;AAElD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD;AAqBD,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,mFAAmF;IACnF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACtD,IAAI,CAAC;IACV;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EACP,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,eAAe,GAAG,IAAI,CAAC,GAC3D,KAAK,CAAC;IACV;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,mBAAmB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AAoED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAW,EACX,YAAmB,EACnB,SAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,WAA+B,EAC/B,UAA8B,EAC9B,UAAU,EACV,cAAyB,EACzB,UAA8B,EAC9B,uBAAuB,GACxB,EAAE,yBAAyB,GAAG,wBAAwB,CA8VtD"}
@@ -169,12 +169,14 @@ export function useCollabReconcile({ editor, ydoc = null, collabSynced = true, a
169
169
  // Seed only when the shared doc is genuinely empty — either the fragment has
170
170
  // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an
171
171
  // app's sentinel-empty filler via a custom `shouldSeed`).
172
- seededRef.current = true;
173
172
  if (!shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })) {
173
+ seededRef.current = true;
174
174
  return;
175
175
  }
176
176
  let cancelled = false;
177
- queueMicrotask(() => {
177
+ // Defer via a timer task (NOT a microtask — microtasks can still run
178
+ // inside React's commit and trigger flushSync-from-lifecycle warnings).
179
+ const seedTimer = setTimeout(() => {
178
180
  if (cancelled || editor.isDestroyed)
179
181
  return;
180
182
  isSettingContentRef.current = true;
@@ -191,9 +193,11 @@ export function useCollabReconcile({ editor, ydoc = null, collabSynced = true, a
191
193
  lastAppliedSerializedRef.current = serialized;
192
194
  if (contentUpdatedAt)
193
195
  lastAppliedUpdatedAtRef.current = contentUpdatedAt;
194
- });
196
+ seededRef.current = true;
197
+ }, 0);
195
198
  return () => {
196
199
  cancelled = true;
200
+ clearTimeout(seedTimer);
197
201
  };
198
202
  }, [
199
203
  collab,
@@ -317,11 +321,11 @@ export function useCollabReconcile({ editor, ydoc = null, collabSynced = true, a
317
321
  retry = setTimeout(() => apply(true), PEER_SETTLE_MS);
318
322
  return;
319
323
  }
320
- queueMicrotask(() => {
324
+ const applyTimer = setTimeout(() => {
321
325
  if (cancelled || editor.isDestroyed)
322
326
  return;
323
327
  // Re-check doc-equivalence at apply time. Between the decision above and
324
- // this microtask a peer/Yjs edit (or our own prior apply) may have made
328
+ // this task a peer/Yjs edit (or our own prior apply) may have made
325
329
  // the editor already represent this value — re-applying would be a
326
330
  // wasted setContent that, for non-idempotent input, re-triggers the
327
331
  // loop. Skip when the editor's current serialization already matches the
@@ -370,7 +374,8 @@ export function useCollabReconcile({ editor, ydoc = null, collabSynced = true, a
370
374
  if (contentUpdatedAt) {
371
375
  lastAppliedUpdatedAtRef.current = contentUpdatedAt;
372
376
  }
373
- });
377
+ }, 0);
378
+ retry = applyTimer;
374
379
  };
375
380
  apply();
376
381
  return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"useCollabReconcile.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"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","sourcesContent":["import { isChangeOrigin } from \"@tiptap/extension-collaboration\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport type { Editor } from \"@tiptap/react\";\nimport { useEffect, useRef, useState, type MutableRefObject } from \"react\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport type { Doc as YDoc } from \"yjs\";\n\nimport { AGENT_CLIENT_ID } from \"../../collab/agent-identity.js\";\nimport { isReconcileLeadClient } from \"../../collab/client.js\";\nimport {\n RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION,\n applyDocSurgically,\n defaultParseValue,\n} from \"./surgical-apply.js\";\n\nexport { RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION };\n\n/** Reads the current markdown out of the tiptap-markdown storage. */\nexport function getEditorMarkdown(editor: Editor): string {\n const markdownStorage = editor.storage as unknown as {\n markdown?: { getMarkdown?: () => string };\n };\n return markdownStorage.markdown?.getMarkdown?.() ?? \"\";\n}\n\n/**\n * Push a value onto the bounded ring of recently-emitted markdown (most recent\n * last, deduped, capped). The reconcile uses this to recognize a stale-but-recent\n * echo of OUR OWN edits: a debounced autosave can persist a PARTIAL burst, and\n * the next poll re-supplies that partial value with a newer timestamp — applying\n * it would clobber the freshly-typed tail. An external change (agent/peer) never\n * byte-matches one of our own recent emissions, and if it somehow did the content\n * is identical, so skipping it is safe by construction.\n */\nconst EMITTED_RING_MAX = 16;\nfunction pushEmittedRing(ring: string[], value: string): void {\n if (!value) return;\n if (ring[ring.length - 1] === value) return;\n const dupe = ring.indexOf(value);\n if (dupe !== -1) ring.splice(dupe, 1);\n ring.push(value);\n if (ring.length > EMITTED_RING_MAX) ring.shift();\n}\n\nexport interface UseCollabReconcileOptions {\n /** The live editor, or null until it mounts. */\n editor: Editor | null;\n /** Shared Y.Doc when collaborating; null disables all collab paths. */\n ydoc?: YDoc | null;\n /** True after the collab provider has loaded the persisted initial Y.Doc state. */\n collabSynced?: boolean;\n /** Shared awareness; null keeps the sole-client lead path. */\n awareness?: Awareness | null;\n /** Authoritative markdown value (SQL source of truth). */\n value: string;\n /** Timestamp of the authoritative value; gates newer-than reconcile. */\n contentUpdatedAt?: string | null;\n /** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */\n editable: boolean;\n /**\n * Reads the current markdown from the editor. Injected so a dialect could\n * swap serializers; defaults to the tiptap-markdown storage reader. For an app\n * with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass\n * it here so the seed/reconcile equality checks compare like-for-like.\n */\n getMarkdown?: (editor: Editor) => string;\n /**\n * Applies the authoritative `value` into the editor. Defaults to passing the\n * raw markdown string to `editor.commands.setContent`. Apps whose serializer\n * is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)\n * override this so seed + reconcile write the correct content shape. The\n * supplied `options` carry the history/whitespace flags the default path uses;\n * a custom implementation should forward them when relevant.\n */\n setContent?: (\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n ) => void;\n /**\n * Parses the authoritative `value` into a full ProseMirror document for the\n * SURGICAL reconcile path: instead of a whole-document `setContent` (which\n * under Collaboration rewrites the entire Y.XmlFragment and tears down every\n * block NodeView), the reconcile diffs the parsed doc against the live doc\n * and replaces only the changed top-level run. Return null to skip the\n * surgical path for a given value (falls back to `setContent`).\n *\n * Defaults to a best-effort tiptap-markdown parse; apps with their own\n * serializers (Content's NFM, Plan's blocks[] doc) should supply the exact\n * doc their `setContent` would write. Pass `false` to disable surgical\n * application entirely.\n */\n parseValue?:\n | ((editor: Editor, value: string) => ProseMirrorNode | null)\n | false;\n /**\n * Normalizes the authoritative `value` to the canonical markdown the editor\n * would emit, so the \"already in sync / our own echo\" equality checks match a\n * serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to\n * identity (GFM already round-trips byte-stably).\n */\n normalizeValue?: (value: string) => string;\n /**\n * Decides whether the empty-doc seed should run for the current shared\n * fragment. Defaults to \"fragment has no nodes, or the editor holds no\n * semantic markdown\". Apps with sentinel-empty content (Content's\n * `<empty-block/>` filler) override this. Receives the live fragment length\n * and the editor's current markdown.\n */\n shouldSeed?: (info: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n }) => boolean;\n /**\n * The initial \"applied\" watermark. Default mirrors `contentUpdatedAt`, so a\n * fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`\n * to force the first reconcile pass to adopt authoritative SQL even at the\n * same timestamp — Content does this so a stale persisted Y.Doc (an agent that\n * edited the CLOSED doc) is corrected on open. The editor is keyed per\n * document upstream, so this only affects the first mount of each doc.\n */\n initialAppliedUpdatedAt?: string | null;\n}\n\nexport interface UseCollabReconcileResult {\n /** True when a Y.Doc is bound (collaborative editing active). */\n collab: boolean;\n /**\n * Set true around any programmatic `setContent` so the editor's `onUpdate`\n * can ignore the resulting transaction (it isn't a user edit).\n */\n isSettingContentRef: MutableRefObject<boolean>;\n /**\n * Call from `onUpdate` BEFORE serializing. Returns true when the update must\n * be ignored: editor not editable, mid-programmatic-setContent, or (in collab\n * mode) a remote-origin transaction. Also records the local typing time.\n */\n shouldIgnoreUpdate: (transaction: Transaction) => boolean;\n /**\n * Call from `onUpdate` AFTER computing the markdown to emit. Returns false\n * when the value must NOT be persisted yet (an empty collab doc before the\n * seed has run); records it as the last-emitted value otherwise.\n */\n registerEmitted: (markdown: string) => boolean;\n}\n\n/**\n * The subtle seed / reconcile / lead-client logic for the shared markdown\n * editor, extracted once so it can never be duplicated across embedders.\n *\n * Responsibilities (reproducing the Plan editor's behavior exactly):\n * - Track whether THIS client is the reconcile lead (sole client always leads;\n * otherwise elected via {@link isReconcileLeadClient}) and how many other\n * visible human peers are present.\n * - Seed an empty shared Y.Doc once from `value` — lead client only — so two\n * clients opening a brand-new block don't both insert the content.\n * - Reconcile authoritative external markdown (agent edit, source patch, peer\n * edit mirrored to SQL) into the editor: in collab mode only the lead client\n * applies it through `setContent` and Yjs propagates; in non-collab mode this\n * is the original controlled-value reconcile.\n * - Provide the `onUpdate` guards (`shouldIgnoreUpdate`, `registerEmitted`) so\n * the component never persists remote-origin or pre-seed empty content.\n */\n/** Default seed predicate: seed only when the shared doc is genuinely empty. */\nfunction defaultShouldSeed({\n currentMarkdown,\n fragmentLength,\n}: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n}): boolean {\n return fragmentLength === 0 || !currentMarkdown.trim();\n}\n\n/**\n * Default content writer: hand the raw markdown string to `setContent`, which\n * tiptap-markdown overrides to parse the markdown into a ProseMirror doc.\n *\n * IMPORTANT: do NOT pass `parseOptions: { preserveWhitespace: \"full\" }` here.\n * In tiptap v3 the core `setContent` command routes `preserveWhitespace: \"full\"`\n * through `insertContentAt`, which tiptap-markdown ALSO overrides to re-run its\n * markdown parser. That double-parse stringifies the already-parsed PM doc and\n * re-parses it as HTML, so a clean heading/list/code block comes back as the\n * escaped, non-idempotent `&lt;h1&gt;…` — which then escalates every reconcile\n * cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Letting the markdown\n * override parse the string directly (no `parseOptions`) round-trips byte-stably\n * for the GFM corpus, including code-block and empty-line whitespace. Content's\n * NFM path supplies its own `setContent` (it passes a pre-parsed PM doc) and is\n * unaffected by this default.\n */\nfunction defaultSetContent(\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n): void {\n if (options.addToHistory === false) {\n editor\n .chain()\n .command(({ tr }) => {\n // addToHistory:false so cmd+z (or Yjs undo) doesn't erase\n // externally-loaded content.\n tr.setMeta(\"addToHistory\", false);\n return true;\n })\n .setContent(value, { emitUpdate: options.emitUpdate })\n .run();\n return;\n }\n editor.commands.setContent(value);\n}\n\nexport function useCollabReconcile({\n editor,\n ydoc = null,\n collabSynced = true,\n awareness = null,\n value,\n contentUpdatedAt,\n editable,\n getMarkdown = getEditorMarkdown,\n setContent = defaultSetContent,\n parseValue,\n normalizeValue = (v) => v,\n shouldSeed = defaultShouldSeed,\n initialAppliedUpdatedAt,\n}: UseCollabReconcileOptions): UseCollabReconcileResult {\n const collab = !!ydoc;\n const isSettingContentRef = useRef(false);\n const lastEmittedRef = useRef(\"\");\n // Ring of recent local emissions (see pushEmittedRing). Lets the reconcile\n // recognize a stale-but-recent echo of our OWN (possibly partial, debounced)\n // save so a lagging poll never clobbers freshly-typed text.\n const recentEmittedRef = useRef<string[]>([]);\n const lastTypedAtRef = useRef(0);\n // The raw authoritative `value` string the reconcile last applied. When the\n // SAME raw string is re-fetched (a lagging poll, or a source-sync that keeps\n // re-supplying the same stored markdown), applying it again would only\n // reproduce the doc we already hold — and if `value` is NON-idempotent\n // (serialize(parse(value)) !== value) re-applying compounds the divergence\n // every cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Tracked so the\n // identical re-fetch is recognized and skipped.\n const lastAppliedValueRef = useRef<string | null>(null);\n // The editor's SERIALIZED output captured right AFTER the last reconcile/seed\n // apply (`getMarkdown(editor)` once the content settled). For non-idempotent\n // input this is what autosave actually persists, so the NEXT poll hands it\n // back as the new `value`. Comparing the incoming value against this lets the\n // reconcile recognize its own echo even when the raw string changed once, so\n // it never re-parses content the editor already represents. This is the\n // doc-equivalence guard that breaks the escalation loop.\n const lastAppliedSerializedRef = useRef<string | null>(null);\n const lastAppliedUpdatedAtRef = useRef<string | null>(\n initialAppliedUpdatedAt !== undefined\n ? initialAppliedUpdatedAt\n : (contentUpdatedAt ?? null),\n );\n\n // Whether THIS client is the one that seeds the empty shared doc / applies an\n // authoritative external snapshot into it. Exactly one client does, so the\n // content isn't inserted once per open editor. A sole client always leads.\n const [isLeadClient, setIsLeadClient] = useState(true);\n // Count of OTHER visible human collaborators. When >0, a peer's edit also\n // arrives via Yjs, so external markdown reconcile must defer (avoid applying\n // the same change through both Yjs and setContent).\n const peerCountRef = useRef(0);\n useEffect(() => {\n if (!collab || !awareness || !ydoc) {\n setIsLeadClient(true);\n peerCountRef.current = 0;\n return;\n }\n const update = () => {\n setIsLeadClient(isReconcileLeadClient(awareness, ydoc.clientID));\n let peers = 0;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc.clientID) return; // self\n if (clientId === AGENT_CLIENT_ID) return; // agent isn't a Yjs editor\n const s = state as { user?: unknown; visible?: boolean };\n if (s && s.user && s.visible !== false) peers += 1;\n });\n peerCountRef.current = peers;\n };\n update();\n awareness.on(\"change\", update);\n document.addEventListener(\"visibilitychange\", update);\n return () => {\n awareness.off(\"change\", update);\n document.removeEventListener(\"visibilitychange\", update);\n };\n }, [collab, awareness, ydoc]);\n\n // Collab seed: populate an empty shared Y.Doc from the markdown `value` once.\n // The Collaboration extension does NOT auto-seed; only the lead client does,\n // so two clients opening a brand-new block at once don't both seed (which\n // would duplicate the content via concurrent inserts at the same position).\n const seededRef = useRef(false);\n useEffect(() => {\n if (!collab || !editor || editor.isDestroyed || !ydoc) return;\n if (seededRef.current) return;\n if (!collabSynced) return;\n if (!isLeadClient) return;\n if (!value.trim()) return;\n const fragment = ydoc.getXmlFragment(\"default\");\n const currentMarkdown = getMarkdown(editor);\n // Seed only when the shared doc is genuinely empty — either the fragment has\n // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an\n // app's sentinel-empty filler via a custom `shouldSeed`).\n seededRef.current = true;\n if (\n !shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })\n ) {\n return;\n }\n\n let cancelled = false;\n queueMicrotask(() => {\n if (cancelled || editor.isDestroyed) return;\n isSettingContentRef.current = true;\n try {\n setContent(editor, value, {});\n } finally {\n isSettingContentRef.current = false;\n }\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n });\n return () => {\n cancelled = true;\n };\n }, [\n collab,\n collabSynced,\n editor,\n ydoc,\n value,\n isLeadClient,\n contentUpdatedAt,\n getMarkdown,\n setContent,\n shouldSeed,\n ]);\n\n // Reconcile authoritative external markdown (agent edit, source patch, or a\n // peer edit mirrored to SQL) into the live editor. In collab mode only the\n // lead client applies it through setContent; Yjs propagates the result to\n // every other client. In non-collab mode this is the original controlled-value\n // reconcile, unchanged.\n useEffect(() => {\n if (!editor || editor.isDestroyed) return;\n\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | null = null;\n // With peers present, a peer's edit also arrives via Yjs. Defer one poll\n // cycle (+margin) and re-check before applying via setContent so the same\n // change isn't inserted twice (Yjs + setContent → duplicated region).\n const PEER_SETTLE_MS = 2500;\n\n const apply = (deferred = false) => {\n if (cancelled || editor.isDestroyed) return;\n // In collab mode, defer all reconcile until the shared doc is seeded so we\n // never setContent over an unseeded fragment.\n if (collab && (!collabSynced || !seededRef.current)) {\n retry = setTimeout(() => apply(deferred), 300);\n return;\n }\n const currentMarkdown = getMarkdown(editor);\n // Compare against the canonical form the editor would emit so a serializer\n // that re-normalizes (Content's NFM) still recognizes \"already in sync\".\n const normalizedValue = normalizeValue(value);\n // Whether the editor still holds exactly what THIS hook last applied (the\n // user hasn't edited since). Only then are the round-trip echo guards\n // below safe: if the user has since edited away from the applied content,\n // an external snapshot equal to a previously-applied value is a real\n // revert and must NOT be swallowed as an echo.\n const editorUnchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n currentMarkdown === lastAppliedSerializedRef.current;\n\n // Doc-equivalence skip. Never re-apply content the editor already\n // represents — comparing by DOC EQUIVALENCE, not raw strings/timestamps:\n // 1. `currentMarkdown === normalizedValue` — the editor's CURRENT\n // serialized doc already equals the (normalized) incoming value.\n // 2. `value === lastEmittedRef.current` — the incoming value is our own\n // just-emitted markdown echoing back.\n // 3. `value === lastAppliedValueRef.current` — the SAME raw value we\n // already applied is being re-supplied (a lagging poll or a\n // source-sync re-handing the same stored markdown). Applying it again\n // would only reproduce the doc we hold; for NON-idempotent input it\n // would compound divergence. Guarded by `editorUnchangedSinceApply`\n // so a deliberate revert-to-previous after a local edit still lands.\n // 4. `normalizedValue === lastAppliedSerializedRef.current` — the\n // incoming value round-trips to the serialized output we last\n // produced (our own autosaved echo coming back from SQL). For\n // non-idempotent input the raw string differs from what we were\n // handed, but it is doc-equivalent to what the editor already shows,\n // so re-parsing it must be skipped. This is the guard that stops the\n // `<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` escalation.\n if (\n currentMarkdown === normalizedValue ||\n value === lastEmittedRef.current ||\n // A stale-but-recent echo of our own (possibly partial) save — applying\n // it would clobber the freshly-typed tail. External edits never match.\n recentEmittedRef.current.includes(value) ||\n recentEmittedRef.current.includes(normalizedValue) ||\n (editorUnchangedSinceApply &&\n (value === lastAppliedValueRef.current ||\n normalizedValue === lastAppliedSerializedRef.current))\n ) {\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n const externalNewer =\n !lastAppliedUpdatedAtRef.current ||\n !contentUpdatedAt ||\n contentUpdatedAt > lastAppliedUpdatedAtRef.current;\n\n // Only the lead client applies an authoritative snapshot into the shared\n // Y.Doc; peers receive it through Yjs sync.\n if (collab && !isLeadClient) {\n if (contentUpdatedAt && !externalNewer) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n // Never clobber an in-progress edit. While the user is actively typing\n // (focused and a keystroke landed within the window) defer and re-check —\n // applying external content now would yank text out from under them and,\n // for non-idempotent input, fight every keystroke. Newer external content\n // retries so it still lands once they pause; older-or-equal content is a\n // stale poll and is dropped outright while focused.\n const typingRecently =\n editor.isFocused && Date.now() - lastTypedAtRef.current < 1500;\n if (typingRecently) {\n if (externalNewer) {\n retry = setTimeout(() => apply(deferred), 700);\n }\n return;\n }\n // Older-or-equal content is a stale poll / lagging echo. Drop it while\n // focused (a peer/agent edit would be NEWER and retries above). In\n // NON-COLLAB mode there is no peer, so older-or-equal external content is\n // ALWAYS stale — dropping it regardless of focus stops a lagging\n // `get-visual-plan` poll from reverting a just-applied local structural\n // change (drag-to-columns) while the editor is blurred (the drag grips the\n // handle, not the prose, so `isFocused` is false at drop time). Gated on\n // `lastAppliedSerializedRef` so the very first seed (nothing applied yet,\n // also not-newer) still lands.\n const seeded = lastAppliedSerializedRef.current !== null;\n if (!externalNewer && (editor.isFocused || (!collab && seeded))) return;\n\n // Race guard: with peers present, let Yjs deliver a peer's edit first.\n // Defer once and re-check — a peer edit makes the equality check above\n // no-op next pass; an agent/source edit still differs and applies.\n if (collab && externalNewer && !deferred && peerCountRef.current > 0) {\n retry = setTimeout(() => apply(true), PEER_SETTLE_MS);\n return;\n }\n\n queueMicrotask(() => {\n if (cancelled || editor.isDestroyed) return;\n // Re-check doc-equivalence at apply time. Between the decision above and\n // this microtask a peer/Yjs edit (or our own prior apply) may have made\n // the editor already represent this value — re-applying would be a\n // wasted setContent that, for non-idempotent input, re-triggers the\n // loop. Skip when the editor's current serialization already matches the\n // normalized value, or the value round-trips to what we last produced.\n const beforeMarkdown = getMarkdown(editor);\n const normalized = normalizeValue(value);\n const unchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n beforeMarkdown === lastAppliedSerializedRef.current;\n if (\n beforeMarkdown === normalized ||\n (unchangedSinceApply &&\n normalized === lastAppliedSerializedRef.current)\n ) {\n lastAppliedValueRef.current = value;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n isSettingContentRef.current = true;\n // Surgical path first: replace only the changed top-level run so\n // unchanged block NodeViews are never torn down and (under\n // Collaboration) Yjs sees a minimal edit instead of a full-fragment\n // rewrite. Falls back to the classic whole-document setContent when no\n // parser is available or the targeted transaction cannot be applied.\n let appliedSurgically = false;\n if (parseValue !== false) {\n const parse = parseValue ?? defaultParseValue;\n const parsedDoc = parse(editor, value);\n if (parsedDoc) {\n const result = applyDocSurgically(editor, parsedDoc);\n appliedSurgically = result === \"applied\" || result === \"noop\";\n }\n }\n if (!appliedSurgically) {\n setContent(editor, value, { emitUpdate: false, addToHistory: false });\n }\n isSettingContentRef.current = false;\n // Capture the SERIALIZED result, not the raw value. For non-idempotent\n // input these differ; recording the serialized output is what lets the\n // next poll (which returns this serialized form) be recognized as our\n // own echo and skipped — stabilizing the doc after exactly one apply.\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n });\n };\n\n apply();\n return () => {\n cancelled = true;\n if (retry) clearTimeout(retry);\n };\n }, [\n contentUpdatedAt,\n editor,\n value,\n collab,\n collabSynced,\n isLeadClient,\n getMarkdown,\n setContent,\n parseValue,\n normalizeValue,\n ]);\n\n const shouldIgnoreUpdate = (transaction: Transaction): boolean => {\n if (!editable || isSettingContentRef.current) return true;\n if (transaction.getMeta(RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION)) {\n return true;\n }\n // In collab mode, never persist remote-originated changes (the initial Yjs\n // state load or a peer's edit arriving via sync). Each client saves only its\n // OWN local edits; a peer's edit is saved by that peer. Without this, a\n // lagging Y.Doc load would write stale markdown over newer SQL.\n if (collab && transaction && isChangeOrigin(transaction)) return true;\n lastTypedAtRef.current = Date.now();\n return false;\n };\n\n const registerEmitted = (markdown: string): boolean => {\n // Don't persist an empty doc before Collaboration has seeded — that would\n // clobber the saved block content with an empty string.\n if (collab && !markdown.trim()) return false;\n lastEmittedRef.current = markdown;\n pushEmittedRing(recentEmittedRef.current, markdown);\n return true;\n };\n\n return {\n collab,\n isSettingContentRef,\n shouldIgnoreUpdate,\n registerEmitted,\n };\n}\n"]}
1
+ {"version":3,"file":"useCollabReconcile.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"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","sourcesContent":["import { isChangeOrigin } from \"@tiptap/extension-collaboration\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport type { Editor } from \"@tiptap/react\";\nimport { useEffect, useRef, useState, type MutableRefObject } from \"react\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport type { Doc as YDoc } from \"yjs\";\n\nimport { AGENT_CLIENT_ID } from \"../../collab/agent-identity.js\";\nimport { isReconcileLeadClient } from \"../../collab/client.js\";\nimport {\n RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION,\n applyDocSurgically,\n defaultParseValue,\n} from \"./surgical-apply.js\";\n\nexport { RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION };\n\n/** Reads the current markdown out of the tiptap-markdown storage. */\nexport function getEditorMarkdown(editor: Editor): string {\n const markdownStorage = editor.storage as unknown as {\n markdown?: { getMarkdown?: () => string };\n };\n return markdownStorage.markdown?.getMarkdown?.() ?? \"\";\n}\n\n/**\n * Push a value onto the bounded ring of recently-emitted markdown (most recent\n * last, deduped, capped). The reconcile uses this to recognize a stale-but-recent\n * echo of OUR OWN edits: a debounced autosave can persist a PARTIAL burst, and\n * the next poll re-supplies that partial value with a newer timestamp — applying\n * it would clobber the freshly-typed tail. An external change (agent/peer) never\n * byte-matches one of our own recent emissions, and if it somehow did the content\n * is identical, so skipping it is safe by construction.\n */\nconst EMITTED_RING_MAX = 16;\nfunction pushEmittedRing(ring: string[], value: string): void {\n if (!value) return;\n if (ring[ring.length - 1] === value) return;\n const dupe = ring.indexOf(value);\n if (dupe !== -1) ring.splice(dupe, 1);\n ring.push(value);\n if (ring.length > EMITTED_RING_MAX) ring.shift();\n}\n\nexport interface UseCollabReconcileOptions {\n /** The live editor, or null until it mounts. */\n editor: Editor | null;\n /** Shared Y.Doc when collaborating; null disables all collab paths. */\n ydoc?: YDoc | null;\n /** True after the collab provider has loaded the persisted initial Y.Doc state. */\n collabSynced?: boolean;\n /** Shared awareness; null keeps the sole-client lead path. */\n awareness?: Awareness | null;\n /** Authoritative markdown value (SQL source of truth). */\n value: string;\n /** Timestamp of the authoritative value; gates newer-than reconcile. */\n contentUpdatedAt?: string | null;\n /** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */\n editable: boolean;\n /**\n * Reads the current markdown from the editor. Injected so a dialect could\n * swap serializers; defaults to the tiptap-markdown storage reader. For an app\n * with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass\n * it here so the seed/reconcile equality checks compare like-for-like.\n */\n getMarkdown?: (editor: Editor) => string;\n /**\n * Applies the authoritative `value` into the editor. Defaults to passing the\n * raw markdown string to `editor.commands.setContent`. Apps whose serializer\n * is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)\n * override this so seed + reconcile write the correct content shape. The\n * supplied `options` carry the history/whitespace flags the default path uses;\n * a custom implementation should forward them when relevant.\n */\n setContent?: (\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n ) => void;\n /**\n * Parses the authoritative `value` into a full ProseMirror document for the\n * SURGICAL reconcile path: instead of a whole-document `setContent` (which\n * under Collaboration rewrites the entire Y.XmlFragment and tears down every\n * block NodeView), the reconcile diffs the parsed doc against the live doc\n * and replaces only the changed top-level run. Return null to skip the\n * surgical path for a given value (falls back to `setContent`).\n *\n * Defaults to a best-effort tiptap-markdown parse; apps with their own\n * serializers (Content's NFM, Plan's blocks[] doc) should supply the exact\n * doc their `setContent` would write. Pass `false` to disable surgical\n * application entirely.\n */\n parseValue?:\n | ((editor: Editor, value: string) => ProseMirrorNode | null)\n | false;\n /**\n * Normalizes the authoritative `value` to the canonical markdown the editor\n * would emit, so the \"already in sync / our own echo\" equality checks match a\n * serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to\n * identity (GFM already round-trips byte-stably).\n */\n normalizeValue?: (value: string) => string;\n /**\n * Decides whether the empty-doc seed should run for the current shared\n * fragment. Defaults to \"fragment has no nodes, or the editor holds no\n * semantic markdown\". Apps with sentinel-empty content (Content's\n * `<empty-block/>` filler) override this. Receives the live fragment length\n * and the editor's current markdown.\n */\n shouldSeed?: (info: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n }) => boolean;\n /**\n * The initial \"applied\" watermark. Default mirrors `contentUpdatedAt`, so a\n * fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`\n * to force the first reconcile pass to adopt authoritative SQL even at the\n * same timestamp — Content does this so a stale persisted Y.Doc (an agent that\n * edited the CLOSED doc) is corrected on open. The editor is keyed per\n * document upstream, so this only affects the first mount of each doc.\n */\n initialAppliedUpdatedAt?: string | null;\n}\n\nexport interface UseCollabReconcileResult {\n /** True when a Y.Doc is bound (collaborative editing active). */\n collab: boolean;\n /**\n * Set true around any programmatic `setContent` so the editor's `onUpdate`\n * can ignore the resulting transaction (it isn't a user edit).\n */\n isSettingContentRef: MutableRefObject<boolean>;\n /**\n * Call from `onUpdate` BEFORE serializing. Returns true when the update must\n * be ignored: editor not editable, mid-programmatic-setContent, or (in collab\n * mode) a remote-origin transaction. Also records the local typing time.\n */\n shouldIgnoreUpdate: (transaction: Transaction) => boolean;\n /**\n * Call from `onUpdate` AFTER computing the markdown to emit. Returns false\n * when the value must NOT be persisted yet (an empty collab doc before the\n * seed has run); records it as the last-emitted value otherwise.\n */\n registerEmitted: (markdown: string) => boolean;\n}\n\n/**\n * The subtle seed / reconcile / lead-client logic for the shared markdown\n * editor, extracted once so it can never be duplicated across embedders.\n *\n * Responsibilities (reproducing the Plan editor's behavior exactly):\n * - Track whether THIS client is the reconcile lead (sole client always leads;\n * otherwise elected via {@link isReconcileLeadClient}) and how many other\n * visible human peers are present.\n * - Seed an empty shared Y.Doc once from `value` — lead client only — so two\n * clients opening a brand-new block don't both insert the content.\n * - Reconcile authoritative external markdown (agent edit, source patch, peer\n * edit mirrored to SQL) into the editor: in collab mode only the lead client\n * applies it through `setContent` and Yjs propagates; in non-collab mode this\n * is the original controlled-value reconcile.\n * - Provide the `onUpdate` guards (`shouldIgnoreUpdate`, `registerEmitted`) so\n * the component never persists remote-origin or pre-seed empty content.\n */\n/** Default seed predicate: seed only when the shared doc is genuinely empty. */\nfunction defaultShouldSeed({\n currentMarkdown,\n fragmentLength,\n}: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n}): boolean {\n return fragmentLength === 0 || !currentMarkdown.trim();\n}\n\n/**\n * Default content writer: hand the raw markdown string to `setContent`, which\n * tiptap-markdown overrides to parse the markdown into a ProseMirror doc.\n *\n * IMPORTANT: do NOT pass `parseOptions: { preserveWhitespace: \"full\" }` here.\n * In tiptap v3 the core `setContent` command routes `preserveWhitespace: \"full\"`\n * through `insertContentAt`, which tiptap-markdown ALSO overrides to re-run its\n * markdown parser. That double-parse stringifies the already-parsed PM doc and\n * re-parses it as HTML, so a clean heading/list/code block comes back as the\n * escaped, non-idempotent `&lt;h1&gt;…` — which then escalates every reconcile\n * cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Letting the markdown\n * override parse the string directly (no `parseOptions`) round-trips byte-stably\n * for the GFM corpus, including code-block and empty-line whitespace. Content's\n * NFM path supplies its own `setContent` (it passes a pre-parsed PM doc) and is\n * unaffected by this default.\n */\nfunction defaultSetContent(\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n): void {\n if (options.addToHistory === false) {\n editor\n .chain()\n .command(({ tr }) => {\n // addToHistory:false so cmd+z (or Yjs undo) doesn't erase\n // externally-loaded content.\n tr.setMeta(\"addToHistory\", false);\n return true;\n })\n .setContent(value, { emitUpdate: options.emitUpdate })\n .run();\n return;\n }\n editor.commands.setContent(value);\n}\n\nexport function useCollabReconcile({\n editor,\n ydoc = null,\n collabSynced = true,\n awareness = null,\n value,\n contentUpdatedAt,\n editable,\n getMarkdown = getEditorMarkdown,\n setContent = defaultSetContent,\n parseValue,\n normalizeValue = (v) => v,\n shouldSeed = defaultShouldSeed,\n initialAppliedUpdatedAt,\n}: UseCollabReconcileOptions): UseCollabReconcileResult {\n const collab = !!ydoc;\n const isSettingContentRef = useRef(false);\n const lastEmittedRef = useRef(\"\");\n // Ring of recent local emissions (see pushEmittedRing). Lets the reconcile\n // recognize a stale-but-recent echo of our OWN (possibly partial, debounced)\n // save so a lagging poll never clobbers freshly-typed text.\n const recentEmittedRef = useRef<string[]>([]);\n const lastTypedAtRef = useRef(0);\n // The raw authoritative `value` string the reconcile last applied. When the\n // SAME raw string is re-fetched (a lagging poll, or a source-sync that keeps\n // re-supplying the same stored markdown), applying it again would only\n // reproduce the doc we already hold — and if `value` is NON-idempotent\n // (serialize(parse(value)) !== value) re-applying compounds the divergence\n // every cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Tracked so the\n // identical re-fetch is recognized and skipped.\n const lastAppliedValueRef = useRef<string | null>(null);\n // The editor's SERIALIZED output captured right AFTER the last reconcile/seed\n // apply (`getMarkdown(editor)` once the content settled). For non-idempotent\n // input this is what autosave actually persists, so the NEXT poll hands it\n // back as the new `value`. Comparing the incoming value against this lets the\n // reconcile recognize its own echo even when the raw string changed once, so\n // it never re-parses content the editor already represents. This is the\n // doc-equivalence guard that breaks the escalation loop.\n const lastAppliedSerializedRef = useRef<string | null>(null);\n const lastAppliedUpdatedAtRef = useRef<string | null>(\n initialAppliedUpdatedAt !== undefined\n ? initialAppliedUpdatedAt\n : (contentUpdatedAt ?? null),\n );\n\n // Whether THIS client is the one that seeds the empty shared doc / applies an\n // authoritative external snapshot into it. Exactly one client does, so the\n // content isn't inserted once per open editor. A sole client always leads.\n const [isLeadClient, setIsLeadClient] = useState(true);\n // Count of OTHER visible human collaborators. When >0, a peer's edit also\n // arrives via Yjs, so external markdown reconcile must defer (avoid applying\n // the same change through both Yjs and setContent).\n const peerCountRef = useRef(0);\n useEffect(() => {\n if (!collab || !awareness || !ydoc) {\n setIsLeadClient(true);\n peerCountRef.current = 0;\n return;\n }\n const update = () => {\n setIsLeadClient(isReconcileLeadClient(awareness, ydoc.clientID));\n let peers = 0;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc.clientID) return; // self\n if (clientId === AGENT_CLIENT_ID) return; // agent isn't a Yjs editor\n const s = state as { user?: unknown; visible?: boolean };\n if (s && s.user && s.visible !== false) peers += 1;\n });\n peerCountRef.current = peers;\n };\n update();\n awareness.on(\"change\", update);\n document.addEventListener(\"visibilitychange\", update);\n return () => {\n awareness.off(\"change\", update);\n document.removeEventListener(\"visibilitychange\", update);\n };\n }, [collab, awareness, ydoc]);\n\n // Collab seed: populate an empty shared Y.Doc from the markdown `value` once.\n // The Collaboration extension does NOT auto-seed; only the lead client does,\n // so two clients opening a brand-new block at once don't both seed (which\n // would duplicate the content via concurrent inserts at the same position).\n const seededRef = useRef(false);\n useEffect(() => {\n if (!collab || !editor || editor.isDestroyed || !ydoc) return;\n if (seededRef.current) return;\n if (!collabSynced) return;\n if (!isLeadClient) return;\n if (!value.trim()) return;\n const fragment = ydoc.getXmlFragment(\"default\");\n const currentMarkdown = getMarkdown(editor);\n // Seed only when the shared doc is genuinely empty — either the fragment has\n // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an\n // app's sentinel-empty filler via a custom `shouldSeed`).\n if (\n !shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })\n ) {\n seededRef.current = true;\n return;\n }\n\n let cancelled = false;\n // Defer via a timer task (NOT a microtask — microtasks can still run\n // inside React's commit and trigger flushSync-from-lifecycle warnings).\n const seedTimer = setTimeout(() => {\n if (cancelled || editor.isDestroyed) return;\n isSettingContentRef.current = true;\n try {\n setContent(editor, value, {});\n } finally {\n isSettingContentRef.current = false;\n }\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n seededRef.current = true;\n }, 0);\n return () => {\n cancelled = true;\n clearTimeout(seedTimer);\n };\n }, [\n collab,\n collabSynced,\n editor,\n ydoc,\n value,\n isLeadClient,\n contentUpdatedAt,\n getMarkdown,\n setContent,\n shouldSeed,\n ]);\n\n // Reconcile authoritative external markdown (agent edit, source patch, or a\n // peer edit mirrored to SQL) into the live editor. In collab mode only the\n // lead client applies it through setContent; Yjs propagates the result to\n // every other client. In non-collab mode this is the original controlled-value\n // reconcile, unchanged.\n useEffect(() => {\n if (!editor || editor.isDestroyed) return;\n\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | null = null;\n // With peers present, a peer's edit also arrives via Yjs. Defer one poll\n // cycle (+margin) and re-check before applying via setContent so the same\n // change isn't inserted twice (Yjs + setContent → duplicated region).\n const PEER_SETTLE_MS = 2500;\n\n const apply = (deferred = false) => {\n if (cancelled || editor.isDestroyed) return;\n // In collab mode, defer all reconcile until the shared doc is seeded so we\n // never setContent over an unseeded fragment.\n if (collab && (!collabSynced || !seededRef.current)) {\n retry = setTimeout(() => apply(deferred), 300);\n return;\n }\n const currentMarkdown = getMarkdown(editor);\n // Compare against the canonical form the editor would emit so a serializer\n // that re-normalizes (Content's NFM) still recognizes \"already in sync\".\n const normalizedValue = normalizeValue(value);\n // Whether the editor still holds exactly what THIS hook last applied (the\n // user hasn't edited since). Only then are the round-trip echo guards\n // below safe: if the user has since edited away from the applied content,\n // an external snapshot equal to a previously-applied value is a real\n // revert and must NOT be swallowed as an echo.\n const editorUnchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n currentMarkdown === lastAppliedSerializedRef.current;\n\n // Doc-equivalence skip. Never re-apply content the editor already\n // represents — comparing by DOC EQUIVALENCE, not raw strings/timestamps:\n // 1. `currentMarkdown === normalizedValue` — the editor's CURRENT\n // serialized doc already equals the (normalized) incoming value.\n // 2. `value === lastEmittedRef.current` — the incoming value is our own\n // just-emitted markdown echoing back.\n // 3. `value === lastAppliedValueRef.current` — the SAME raw value we\n // already applied is being re-supplied (a lagging poll or a\n // source-sync re-handing the same stored markdown). Applying it again\n // would only reproduce the doc we hold; for NON-idempotent input it\n // would compound divergence. Guarded by `editorUnchangedSinceApply`\n // so a deliberate revert-to-previous after a local edit still lands.\n // 4. `normalizedValue === lastAppliedSerializedRef.current` — the\n // incoming value round-trips to the serialized output we last\n // produced (our own autosaved echo coming back from SQL). For\n // non-idempotent input the raw string differs from what we were\n // handed, but it is doc-equivalent to what the editor already shows,\n // so re-parsing it must be skipped. This is the guard that stops the\n // `<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` escalation.\n if (\n currentMarkdown === normalizedValue ||\n value === lastEmittedRef.current ||\n // A stale-but-recent echo of our own (possibly partial) save — applying\n // it would clobber the freshly-typed tail. External edits never match.\n recentEmittedRef.current.includes(value) ||\n recentEmittedRef.current.includes(normalizedValue) ||\n (editorUnchangedSinceApply &&\n (value === lastAppliedValueRef.current ||\n normalizedValue === lastAppliedSerializedRef.current))\n ) {\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n const externalNewer =\n !lastAppliedUpdatedAtRef.current ||\n !contentUpdatedAt ||\n contentUpdatedAt > lastAppliedUpdatedAtRef.current;\n\n // Only the lead client applies an authoritative snapshot into the shared\n // Y.Doc; peers receive it through Yjs sync.\n if (collab && !isLeadClient) {\n if (contentUpdatedAt && !externalNewer) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n // Never clobber an in-progress edit. While the user is actively typing\n // (focused and a keystroke landed within the window) defer and re-check —\n // applying external content now would yank text out from under them and,\n // for non-idempotent input, fight every keystroke. Newer external content\n // retries so it still lands once they pause; older-or-equal content is a\n // stale poll and is dropped outright while focused.\n const typingRecently =\n editor.isFocused && Date.now() - lastTypedAtRef.current < 1500;\n if (typingRecently) {\n if (externalNewer) {\n retry = setTimeout(() => apply(deferred), 700);\n }\n return;\n }\n // Older-or-equal content is a stale poll / lagging echo. Drop it while\n // focused (a peer/agent edit would be NEWER and retries above). In\n // NON-COLLAB mode there is no peer, so older-or-equal external content is\n // ALWAYS stale — dropping it regardless of focus stops a lagging\n // `get-visual-plan` poll from reverting a just-applied local structural\n // change (drag-to-columns) while the editor is blurred (the drag grips the\n // handle, not the prose, so `isFocused` is false at drop time). Gated on\n // `lastAppliedSerializedRef` so the very first seed (nothing applied yet,\n // also not-newer) still lands.\n const seeded = lastAppliedSerializedRef.current !== null;\n if (!externalNewer && (editor.isFocused || (!collab && seeded))) return;\n\n // Race guard: with peers present, let Yjs deliver a peer's edit first.\n // Defer once and re-check — a peer edit makes the equality check above\n // no-op next pass; an agent/source edit still differs and applies.\n if (collab && externalNewer && !deferred && peerCountRef.current > 0) {\n retry = setTimeout(() => apply(true), PEER_SETTLE_MS);\n return;\n }\n\n const applyTimer = setTimeout(() => {\n if (cancelled || editor.isDestroyed) return;\n // Re-check doc-equivalence at apply time. Between the decision above and\n // this task a peer/Yjs edit (or our own prior apply) may have made\n // the editor already represent this value — re-applying would be a\n // wasted setContent that, for non-idempotent input, re-triggers the\n // loop. Skip when the editor's current serialization already matches the\n // normalized value, or the value round-trips to what we last produced.\n const beforeMarkdown = getMarkdown(editor);\n const normalized = normalizeValue(value);\n const unchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n beforeMarkdown === lastAppliedSerializedRef.current;\n if (\n beforeMarkdown === normalized ||\n (unchangedSinceApply &&\n normalized === lastAppliedSerializedRef.current)\n ) {\n lastAppliedValueRef.current = value;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n isSettingContentRef.current = true;\n // Surgical path first: replace only the changed top-level run so\n // unchanged block NodeViews are never torn down and (under\n // Collaboration) Yjs sees a minimal edit instead of a full-fragment\n // rewrite. Falls back to the classic whole-document setContent when no\n // parser is available or the targeted transaction cannot be applied.\n let appliedSurgically = false;\n if (parseValue !== false) {\n const parse = parseValue ?? defaultParseValue;\n const parsedDoc = parse(editor, value);\n if (parsedDoc) {\n const result = applyDocSurgically(editor, parsedDoc);\n appliedSurgically = result === \"applied\" || result === \"noop\";\n }\n }\n if (!appliedSurgically) {\n setContent(editor, value, { emitUpdate: false, addToHistory: false });\n }\n isSettingContentRef.current = false;\n // Capture the SERIALIZED result, not the raw value. For non-idempotent\n // input these differ; recording the serialized output is what lets the\n // next poll (which returns this serialized form) be recognized as our\n // own echo and skipped — stabilizing the doc after exactly one apply.\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n }, 0);\n retry = applyTimer;\n };\n\n apply();\n return () => {\n cancelled = true;\n if (retry) clearTimeout(retry);\n };\n }, [\n contentUpdatedAt,\n editor,\n value,\n collab,\n collabSynced,\n isLeadClient,\n getMarkdown,\n setContent,\n parseValue,\n normalizeValue,\n ]);\n\n const shouldIgnoreUpdate = (transaction: Transaction): boolean => {\n if (!editable || isSettingContentRef.current) return true;\n if (transaction.getMeta(RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION)) {\n return true;\n }\n // In collab mode, never persist remote-originated changes (the initial Yjs\n // state load or a peer's edit arriving via sync). Each client saves only its\n // OWN local edits; a peer's edit is saved by that peer. Without this, a\n // lagging Y.Doc load would write stale markdown over newer SQL.\n if (collab && transaction && isChangeOrigin(transaction)) return true;\n lastTypedAtRef.current = Date.now();\n return false;\n };\n\n const registerEmitted = (markdown: string): boolean => {\n // Don't persist an empty doc before Collaboration has seeded — that would\n // clobber the saved block content with an empty string.\n if (collab && !markdown.trim()) return false;\n lastEmittedRef.current = markdown;\n pushEmittedRing(recentEmittedRef.current, markdown);\n return true;\n };\n\n return {\n collab,\n isSettingContentRef,\n shouldIgnoreUpdate,\n registerEmitted,\n };\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  import type { UseQueryOptions, UseMutationOptions } from "@tanstack/react-query";
2
2
  /** @internal exported for tests */
3
3
  export declare function defaultActionQueryRetry(failureCount: number, error: unknown): boolean;
4
+ /** @internal alias kept for existing specs. */
5
+ export declare const shouldRetryActionQueryForError: typeof defaultActionQueryRetry;
4
6
  /**
5
7
  * Default retry backoff for action queries. React Query's stock retryDelay
6
8
  * (1s → 2s → 4s) makes a failing query sit on a spinner for ~7s before the
@@ -87,6 +89,7 @@ export declare function useActionQuery<TResult = undefined, TName extends Action
87
89
  */
88
90
  export declare function useActionMutation<TData = undefined, TVariables = undefined, TName extends ActionName = ActionName>(actionName: TName, options?: Omit<UseMutationOptions<TData extends undefined ? ActionResult<TName> : TData, Error, TVariables extends undefined ? ActionParams<TName> : TVariables>, "mutationFn"> & {
89
91
  method?: "POST" | "PUT" | "DELETE";
92
+ skipActionQueryInvalidation?: boolean;
90
93
  }): import("@tanstack/react-query").UseMutationResult<TData extends undefined ? ActionResult<TName> : TData, Error, TVariables extends undefined ? ActionParams<TName> : TVariables, unknown>;
91
94
  export {};
92
95
  //# sourceMappingURL=use-action.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-action.d.ts","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-action.d.ts","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"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"}
@@ -53,8 +53,36 @@ export function defaultActionQueryRetry(failureCount, error) {
53
53
  // silently retrying would multiply that wait. Surface it instead.
54
54
  if (isActionTimeout(error))
55
55
  return false;
56
+ if (isBrowserResourceExhaustion(error))
57
+ return false;
58
+ // Network-level failures never carry an HTTP `status` (actionFetch only
59
+ // sets it after a response arrives). Chrome reports connection-pool
60
+ // exhaustion (net::ERR_INSUFFICIENT_RESOURCES) as a generic "Failed to
61
+ // fetch", indistinguishable from a transient blip — allow one retry, not
62
+ // three, so an exhausted tab cannot sustain its own fetch storm.
63
+ if (isNetworkLevelFailure(error))
64
+ return failureCount < 1;
56
65
  return failureCount < 3;
57
66
  }
67
+ /** @internal alias kept for existing specs. */
68
+ export const shouldRetryActionQueryForError = defaultActionQueryRetry;
69
+ function isBrowserResourceExhaustion(error) {
70
+ const message = error instanceof Error
71
+ ? error.message
72
+ : typeof error === "string"
73
+ ? error
74
+ : "";
75
+ return /ERR_INSUFFICIENT_RESOURCES|insufficient resources/i.test(message);
76
+ }
77
+ function isNetworkLevelFailure(error) {
78
+ // Match the exact shape actionFetch produces for fetch-level failures (its
79
+ // catch wraps the cause as "Action <name> failed: <cause>" and never sets a
80
+ // status). Other status-less errors (test doubles, transport internals)
81
+ // keep the standard three-retry policy.
82
+ return (error instanceof Error &&
83
+ error.status === undefined &&
84
+ /^Action .+ failed: /.test(error.message));
85
+ }
58
86
  /**
59
87
  * Default retry backoff for action queries. React Query's stock retryDelay
60
88
  * (1s → 2s → 4s) makes a failing query sit on a spinner for ~7s before the
@@ -309,14 +337,17 @@ export function useActionQuery(actionName, params, options) {
309
337
  */
310
338
  export function useActionMutation(actionName, options) {
311
339
  const queryClient = useQueryClient();
312
- const { method: methodOpt, onSuccess, ...restOptions } = options ?? {};
340
+ const { method: methodOpt, onSuccess, skipActionQueryInvalidation = false, ...restOptions } = options ?? {};
313
341
  const method = methodOpt ?? "POST";
314
342
  return useMutation({
315
343
  ...restOptions,
316
344
  mutationFn: (params) => actionFetch(actionName, method, params),
317
345
  onSuccess: (...args) => {
318
- // Invalidate related action queries
319
- queryClient.invalidateQueries({ queryKey: ["action"] });
346
+ // Most mutations change app data broadly. High-volume background
347
+ // mutations can opt out and perform narrower invalidation in onSuccess.
348
+ if (!skipActionQueryInvalidation) {
349
+ queryClient.invalidateQueries({ queryKey: ["action"] });
350
+ }
320
351
  onSuccess?.(...args);
321
352
  },
322
353
  });
@@ -1 +1 @@
1
- {"version":3,"file":"use-action.js","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Client helpers for calling actions through the framework transport.\n *\n * Components should prefer `useActionQuery` / `useActionMutation`; use\n * `callAction` for imperative cases such as debounced search, prefetching, or\n * event handlers that do not fit a hook.\n *\n * ## End-to-end type safety\n *\n * When the action type registry is generated (via the Vite plugin or CLI),\n * `useActionQuery` and `useActionMutation` automatically infer the correct\n * return type and parameter types from the action definitions — no manual\n * type annotations needed.\n *\n * ```ts\n * // Fully typed — return type and params inferred from the action's defineAction()\n * const { data } = useActionQuery(\"list-forms\", { status: \"published\" });\n * // ^? Form[] (inferred from the action's run() return type)\n * ```\n *\n * Without the registry, the hooks fall back to `any` types for backward\n * compatibility.\n */\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type {\n UseQueryOptions,\n UseMutationOptions,\n} from \"@tanstack/react-query\";\n\nimport { agentNativePath } from \"./api-path.js\";\nimport { ensureEmbedAuthFetchInterceptor } from \"./embed-auth.js\";\n\nconst ACTION_PREFIX = agentNativePath(\"/_agent-native/actions\");\n\n/**\n * Upper bound on how long a single action fetch may stay in flight (headers\n * AND body). Converts a hung server/proxy/connection into a visible, typed\n * failure instead of a UI that spins forever. Generous on purpose: it sits\n * above every server-side budget (serverless function limits, hosted run\n * wall-clock), so it only fires when something is genuinely stuck.\n */\nconst DEFAULT_ACTION_TIMEOUT_MS = 60_000;\n\nfunction isAuthFailure(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n ((error as { status?: unknown }).status === 401 ||\n (error as { status?: unknown }).status === 403)\n );\n}\n\nfunction isActionTimeout(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n (error as { timedOut?: unknown }).timedOut === true\n );\n}\n\n/** @internal exported for tests */\nexport function defaultActionQueryRetry(\n failureCount: number,\n error: unknown,\n): boolean {\n if (isAuthFailure(error)) return false;\n // A timeout already made the user wait the full timeout window once;\n // silently retrying would multiply that wait. Surface it instead.\n if (isActionTimeout(error)) return false;\n return failureCount < 3;\n}\n\n/**\n * Default retry backoff for action queries. React Query's stock retryDelay\n * (1s → 2s → 4s) makes a failing query sit on a spinner for ~7s before the\n * error surfaces; interactive data fetches want failures visible fast.\n *\n * @internal exported for tests\n */\nexport function defaultActionQueryRetryDelay(failureCount: number): number {\n return Math.min(500 * 2 ** failureCount, 2_000);\n}\n\n// ---------------------------------------------------------------------------\n// Action type registry — augmented by generated code\n// ---------------------------------------------------------------------------\n\n/**\n * Action type registry. This interface is empty by default and gets augmented\n * by the auto-generated `.generated/action-types.d.ts` file. When augmented,\n * it maps action names to their parameter and return types, enabling\n * end-to-end type safety for `useActionQuery` and `useActionMutation`.\n */\ndeclare global {\n interface AgentNativeActionRegistry {}\n}\n\nexport interface ActionRegistry extends AgentNativeActionRegistry {}\n\n/** Resolves to the union of registered action names, or `string` if no registry exists. */\ntype ActionName = keyof ActionRegistry extends never\n ? string\n : (keyof ActionRegistry & string) | (string & {});\n\n/** Resolves the return type of an action, or `any` if not in the registry. */\ntype ActionResult<T extends string> = T extends keyof ActionRegistry\n ? ActionRegistry[T] extends { result: infer R }\n ? R\n : any\n : any;\n\n/** Resolves the parameter type of an action, or `Record<string, any>` if not in the registry. */\ntype ActionParams<T extends string> = T extends keyof ActionRegistry\n ? ActionRegistry[T] extends { params: infer P }\n ? P\n : Record<string, any>\n : Record<string, any>;\n\nexport type ClientActionMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface ClientActionCallOptions {\n method?: ClientActionMethod;\n /** Abort signal for the underlying fetch. */\n signal?: AbortSignal;\n /** Override the default 60s fetch timeout for long-running actions. */\n timeoutMs?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Fetch helper\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the browser's IANA timezone (e.g. \"America/Los_Angeles\"). This is\n * sent on every action request as `x-user-timezone` so server-side defaults\n * like \"today\" honor the user's local day rather than the server's UTC clock.\n */\nfunction resolveUserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function serializeActionQueryParams(\n params: Record<string, any>,\n): string {\n const qs = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n appendActionQueryParam(qs, key, value);\n }\n return qs.toString();\n}\n\nfunction appendActionQueryParam(\n qs: URLSearchParams,\n key: string,\n value: unknown,\n) {\n if (value === null || value === undefined) return;\n if (Array.isArray(value)) {\n // Use bracket keys so a one-item array still arrives as an array after the\n // server parses URLSearchParams. Repeated bare keys lose that distinction.\n for (const item of value) {\n appendActionQueryParam(qs, `${key}[]`, item);\n }\n return;\n }\n qs.append(key, String(value));\n}\n\nexport interface ActionFetchOptions {\n /**\n * Abort signal from the caller (React Query passes one per queryFn\n * invocation so superseded requests — key change, unmount, refetch — cancel\n * the underlying network request instead of hogging a connection slot).\n */\n signal?: AbortSignal;\n /** Per-call override for the fetch timeout. */\n timeoutMs?: number;\n}\n\nasync function actionFetch<T>(\n name: string,\n method: string,\n params?: Record<string, any>,\n options?: ActionFetchOptions,\n): Promise<T> {\n ensureEmbedAuthFetchInterceptor();\n let url = `${ACTION_PREFIX}/${name}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n // Tag browser-originated action calls so the server can set\n // `ctx.caller = \"frontend\"` (vs a bare programmatic `\"http\"` POST).\n // Mirrors the X-Agent-Native-Tool-Bridge: 1 convention. The header is\n // safe to expose: CORS allows it (see action-routes.ts) and it carries\n // no auth weight — it only narrows the caller tag.\n \"X-Agent-Native-Frontend\": \"1\",\n };\n const tz = resolveUserTimezone();\n if (tz) headers[\"x-user-timezone\"] = tz;\n const init: RequestInit = {\n method,\n headers,\n cache: \"no-store\",\n };\n\n if (method === \"GET\" && params && Object.keys(params).length > 0) {\n // Skip null/undefined so optional filters don't turn into literal \"null\"\n // strings in the query string (e.g. `?folderId=null`).\n const qs = serializeActionQueryParams(params);\n if (qs) url += `?${qs}`;\n } else if (method !== \"GET\" && params) {\n init.body = JSON.stringify(params);\n }\n\n // One controller drives both cancellation sources: the caller's signal\n // (superseded query, unmount) and the timeout. The timer stays armed until\n // the BODY is fully read — headers arriving quickly while the body stalls\n // is exactly the hang this bounds.\n const outerSignal = options?.signal;\n const timeoutMs = options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS;\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const onOuterAbort = () => controller?.abort();\n if (outerSignal && controller) {\n if (outerSignal.aborted) controller.abort();\n else outerSignal.addEventListener(\"abort\", onOuterAbort, { once: true });\n }\n let timedOut = false;\n const timer = controller\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs)\n : null;\n if (controller) init.signal = controller.signal;\n\n const throwTimeout = (): never => {\n const error = new Error(\n `Action ${name} timed out after ${Math.round(timeoutMs / 1000)}s`,\n );\n (error as any).timedOut = true;\n (error as any).status = 408;\n throw error;\n };\n\n let res: Response;\n let raw = \"\";\n let readFailed = false;\n let readError: unknown;\n try {\n try {\n res = await fetch(url, init);\n } catch (err) {\n if (timedOut) throwTimeout();\n // Caller-initiated cancellation — rethrow untouched so React Query\n // recognizes it as a cancellation rather than a query failure.\n if (outerSignal?.aborted) throw err;\n // Network failures, CORS, server unreachable, etc. — give the caller a\n // useful message instead of the opaque \"Failed to fetch\".\n const cause = err instanceof Error ? err.message : String(err);\n throw new Error(`Action ${name} failed: ${cause}`);\n }\n\n // 204 No Content — nothing to parse.\n if (res.status === 204) return null as T;\n\n // Read the body as text first so we can:\n // - tolerate empty bodies (avoids \"Unexpected end of JSON input\")\n // - surface non-JSON error responses (HTML 401/404 pages, plain text, etc.)\n // - preserve the original HTTP status in the thrown error\n // Track read failures separately from \"no body\" — a stream interruption /\n // decode failure on a 2xx response should error rather than silently\n // succeed with `null`.\n try {\n raw = await res.text();\n } catch (err) {\n if (timedOut) throwTimeout();\n if (outerSignal?.aborted) throw err;\n readFailed = true;\n readError = err;\n }\n } finally {\n if (timer) clearTimeout(timer);\n if (outerSignal) outerSignal.removeEventListener(\"abort\", onOuterAbort);\n }\n\n let data: any = undefined;\n let parseFailed = false;\n if (raw.length > 0) {\n try {\n data = JSON.parse(raw);\n } catch {\n // Body wasn't JSON — keep `data` undefined and use the raw text below.\n parseFailed = true;\n }\n }\n\n if (!res.ok) {\n const message =\n (data && (data.error || data.message)) ||\n // Truncate non-JSON bodies so we don't dump entire HTML pages into the\n // console, but still give the developer a hint as to what came back.\n (raw && raw.slice(0, 200)) ||\n res.statusText ||\n `HTTP ${res.status}`;\n const error = new Error(`Action ${name} failed: ${message}`);\n (error as any).status = res.status;\n throw error;\n }\n\n // 2xx but the body couldn't even be read (mid-stream abort, decode failure,\n // etc.). Don't silently treat that as a `null` success.\n if (readFailed) {\n const cause =\n readError instanceof Error ? readError.message : String(readError);\n const error = new Error(\n `Action ${name} returned ${res.status} but the body could not be read: ${cause}`,\n );\n (error as any).status = res.status;\n throw error;\n }\n\n // 2xx with a non-empty, non-JSON body. Action callers expect typed data, so\n // returning `null` here would silently mask a real server bug (e.g. a proxy\n // returning HTML 200 instead of JSON). Throw instead — empty bodies (handled\n // above by the `raw.length > 0` guard and the 204 short-circuit) still\n // correctly resolve to `null`.\n if (parseFailed) {\n const error = new Error(\n `Action ${name} returned a non-JSON ${res.status} response: ${raw.slice(0, 200)}`,\n );\n (error as any).status = res.status;\n throw error;\n }\n\n return (data ?? (null as unknown)) as T;\n}\n\n/**\n * Imperatively call an action from browser/client code.\n *\n * Prefer `useActionQuery` / `useActionMutation` in React render flows. Use this\n * helper when a hook is not ergonomic; do not hand-write fetch calls to\n * `/_agent-native/actions/*` in components.\n */\nexport function callAction<\n TResult = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n params?: ActionParams<TName>,\n options: ClientActionCallOptions = {},\n): Promise<TResult extends undefined ? ActionResult<TName> : TResult> {\n type R = TResult extends undefined ? ActionResult<TName> : TResult;\n return actionFetch<R>(actionName, options.method ?? \"POST\", params, {\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Query hook\n// ---------------------------------------------------------------------------\n\n/**\n * Query an action exposed as GET.\n *\n * When the action type registry is generated, the return type and parameter\n * types are inferred automatically from the action's `defineAction()` call.\n *\n * ```ts\n * // Type-safe — no manual generic needed\n * const { data } = useActionQuery(\"list-meals\", { date: \"2025-01-01\" });\n *\n * // Manual override still works when needed\n * const { data } = useActionQuery<CustomType>(\"list-meals\");\n * ```\n */\nexport function useActionQuery<\n TResult = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n params?: ActionParams<TName>,\n options?: Omit<\n UseQueryOptions<TResult extends undefined ? ActionResult<TName> : TResult>,\n \"queryKey\" | \"queryFn\"\n >,\n) {\n type R = TResult extends undefined ? ActionResult<TName> : TResult;\n return useQuery<R>({\n queryKey: [\"action\", actionName, params],\n // Thread React Query's per-fetch AbortSignal into the network request so\n // superseded fetches (key change, unmount, rapid refetch) actually cancel\n // instead of holding a per-origin connection slot until they finish.\n queryFn: ({ signal }) =>\n actionFetch<R>(actionName, \"GET\", params, { signal }),\n retry: defaultActionQueryRetry,\n retryDelay: defaultActionQueryRetryDelay,\n ...options,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Mutation hook\n// ---------------------------------------------------------------------------\n\n/**\n * Mutate via an action exposed as POST (default), PUT, or DELETE.\n *\n * When the action type registry is generated, the return type and parameter\n * types are inferred automatically.\n *\n * ```ts\n * // Type-safe\n * const { mutate } = useActionMutation(\"log-meal\");\n * mutate({ name: \"Salad\", calories: 350 });\n * ```\n */\nexport function useActionMutation<\n TData = undefined,\n TVariables = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n options?: Omit<\n UseMutationOptions<\n TData extends undefined ? ActionResult<TName> : TData,\n Error,\n TVariables extends undefined ? ActionParams<TName> : TVariables\n >,\n \"mutationFn\"\n > & {\n method?: \"POST\" | \"PUT\" | \"DELETE\";\n },\n) {\n const queryClient = useQueryClient();\n const {\n method: methodOpt,\n onSuccess,\n ...restOptions\n } = options ?? ({} as any);\n const method = methodOpt ?? \"POST\";\n\n type D = TData extends undefined ? ActionResult<TName> : TData;\n type V = TVariables extends undefined ? ActionParams<TName> : TVariables;\n\n return useMutation<D, Error, V>({\n ...restOptions,\n mutationFn: (params) =>\n actionFetch<D>(actionName, method, params as Record<string, any>),\n onSuccess: (...args: [any, any, any]) => {\n // Invalidate related action queries\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n (onSuccess as Function)?.(...args);\n },\n });\n}\n"]}
1
+ {"version":3,"file":"use-action.js","sourceRoot":"","sources":["../../src/client/use-action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM9E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,aAAa,GAAG,eAAe,CAAC,wBAAwB,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEzC,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,CAAC,CAAC,KAAK;QACP,OAAO,KAAK,KAAK,QAAQ;QACzB,QAAQ,IAAI,KAAK;QACjB,CAAE,KAA8B,CAAC,MAAM,KAAK,GAAG;YAC5C,KAA8B,CAAC,MAAM,KAAK,GAAG,CAAC,CAClD,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,CACL,CAAC,CAAC,KAAK;QACP,OAAO,KAAK,KAAK,QAAQ;QACxB,KAAgC,CAAC,QAAQ,KAAK,IAAI,CACpD,CAAC;AACJ,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,uBAAuB,CACrC,YAAoB,EACpB,KAAc;IAEd,IAAI,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,qEAAqE;IACrE,kEAAkE;IAClE,IAAI,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,2BAA2B,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,wEAAwE;IACxE,oEAAoE;IACpE,uEAAuE;IACvE,yEAAyE;IACzE,iEAAiE;IACjE,IAAI,qBAAqB,CAAC,KAAK,CAAC;QAAE,OAAO,YAAY,GAAG,CAAC,CAAC;IAC1D,OAAO,YAAY,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,MAAM,8BAA8B,GAAG,uBAAuB,CAAC;AAEtE,SAAS,2BAA2B,CAAC,KAAc;IACjD,MAAM,OAAO,GACX,KAAK,YAAY,KAAK;QACpB,CAAC,CAAC,KAAK,CAAC,OAAO;QACf,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,EAAE,CAAC;IACX,OAAO,oDAAoD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IAC3C,2EAA2E;IAC3E,4EAA4E;IAC5E,wEAAwE;IACxE,wCAAwC;IACxC,OAAO,CACL,KAAK,YAAY,KAAK;QACrB,KAA8B,CAAC,MAAM,KAAK,SAAS;QACpD,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAC1C,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,YAAoB;IAC/D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,YAAY,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC;AA+CD,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E;;;;GAIG;AACH,SAAS,mBAAmB;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,IAAI,SAAS,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,MAA2B;IAE3B,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;AACvB,CAAC;AAED,SAAS,sBAAsB,CAC7B,EAAmB,EACnB,GAAW,EACX,KAAc;IAEd,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,2EAA2E;QAC3E,2EAA2E;QAC3E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,sBAAsB,CAAC,EAAE,EAAE,GAAG,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO;IACT,CAAC;IACD,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAChC,CAAC;AAaD,KAAK,UAAU,WAAW,CACxB,IAAY,EACZ,MAAc,EACd,MAA4B,EAC5B,OAA4B;IAE5B,+BAA+B,EAAE,CAAC;IAClC,IAAI,GAAG,GAAG,GAAG,aAAa,IAAI,IAAI,EAAE,CAAC;IACrC,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;QAClC,4DAA4D;QAC5D,oEAAoE;QACpE,sEAAsE;QACtE,uEAAuE;QACvE,mDAAmD;QACnD,yBAAyB,EAAE,GAAG;KAC/B,CAAC;IACF,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACjC,IAAI,EAAE;QAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC;IACxC,MAAM,IAAI,GAAgB;QACxB,MAAM;QACN,OAAO;QACP,KAAK,EAAE,UAAU;KAClB,CAAC;IAEF,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,yEAAyE;QACzE,uDAAuD;QACvD,MAAM,EAAE,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,EAAE;YAAE,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;IAC1B,CAAC;SAAM,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,uEAAuE;IACvE,2EAA2E;IAC3E,0EAA0E;IAC1E,mCAAmC;IACnC,MAAM,WAAW,GAAG,OAAO,EAAE,MAAM,CAAC;IACpC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,yBAAyB,CAAC;IAClE,MAAM,UAAU,GACd,OAAO,eAAe,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;IACxE,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC;IAC/C,IAAI,WAAW,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,WAAW,CAAC,OAAO;YAAE,UAAU,CAAC,KAAK,EAAE,CAAC;;YACvC,WAAW,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,KAAK,GAAG,UAAU;QACtB,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;YACd,QAAQ,GAAG,IAAI,CAAC;YAChB,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC,EAAE,SAAS,CAAC;QACf,CAAC,CAAC,IAAI,CAAC;IACT,IAAI,UAAU;QAAE,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAEhD,MAAM,YAAY,GAAG,GAAU,EAAE;QAC/B,MAAM,KAAK,GAAG,IAAI,KAAK,CACrB,UAAU,IAAI,oBAAoB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAClE,CAAC;QACD,KAAa,CAAC,QAAQ,GAAG,IAAI,CAAC;QAC9B,KAAa,CAAC,MAAM,GAAG,GAAG,CAAC;QAC5B,MAAM,KAAK,CAAC;IACd,CAAC,CAAC;IAEF,IAAI,GAAa,CAAC;IAClB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,SAAkB,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ;gBAAE,YAAY,EAAE,CAAC;YAC7B,mEAAmE;YACnE,+DAA+D;YAC/D,IAAI,WAAW,EAAE,OAAO;gBAAE,MAAM,GAAG,CAAC;YACpC,uEAAuE;YACvE,0DAA0D;YAC1D,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,YAAY,KAAK,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,qCAAqC;QACrC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAS,CAAC;QAEzC,yCAAyC;QACzC,oEAAoE;QACpE,8EAA8E;QAC9E,4DAA4D;QAC5D,0EAA0E;QAC1E,qEAAqE;QACrE,uBAAuB;QACvB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ;gBAAE,YAAY,EAAE,CAAC;YAC7B,IAAI,WAAW,EAAE,OAAO;gBAAE,MAAM,GAAG,CAAC;YACpC,UAAU,GAAG,IAAI,CAAC;YAClB,SAAS,GAAG,GAAG,CAAC;QAClB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,WAAW;YAAE,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,IAAI,GAAQ,SAAS,CAAC;IAC1B,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,OAAO,GACX,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;YACtC,uEAAuE;YACvE,qEAAqE;YACrE,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC1B,GAAG,CAAC,UAAU;YACd,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,UAAU,IAAI,YAAY,OAAO,EAAE,CAAC,CAAC;QAC5D,KAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACnC,MAAM,KAAK,CAAC;IACd,CAAC;IAED,4EAA4E;IAC5E,wDAAwD;IACxD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,KAAK,GACT,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,IAAI,KAAK,CACrB,UAAU,IAAI,aAAa,GAAG,CAAC,MAAM,oCAAoC,KAAK,EAAE,CACjF,CAAC;QACD,KAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACnC,MAAM,KAAK,CAAC;IACd,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,uEAAuE;IACvE,+BAA+B;IAC/B,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,IAAI,KAAK,CACrB,UAAU,IAAI,wBAAwB,GAAG,CAAC,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAClF,CAAC;QACD,KAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QACnC,MAAM,KAAK,CAAC;IACd,CAAC;IAED,OAAO,CAAC,IAAI,IAAK,IAAgB,CAAM,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAIxB,UAAiB,EACjB,MAA4B,EAC5B,OAAO,GAA4B,EAAE;IAGrC,OAAO,WAAW,CAAI,UAAU,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,EAAE,MAAM,EAAE;QAClE,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAI5B,UAAiB,EACjB,MAA4B,EAC5B,OAGC;IAGD,OAAO,QAAQ,CAAI;QACjB,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC;QACxC,yEAAyE;QACzE,0EAA0E;QAC1E,qEAAqE;QACrE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CACtB,WAAW,CAAI,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC;QACvD,KAAK,EAAE,uBAAuB;QAC9B,UAAU,EAAE,4BAA4B;QACxC,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAK/B,UAAiB,EACjB,OAUC;IAED,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,EACJ,MAAM,EAAE,SAAS,EACjB,SAAS,EACT,2BAA2B,GAAG,KAAK,EACnC,GAAG,WAAW,EACf,GAAG,OAAO,IAAK,EAAU,CAAC;IAC3B,MAAM,MAAM,GAAG,SAAS,IAAI,MAAM,CAAC;IAKnC,OAAO,WAAW,CAAc;QAC9B,GAAG,WAAW;QACd,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CACrB,WAAW,CAAI,UAAU,EAAE,MAAM,EAAE,MAA6B,CAAC;QACnE,SAAS,EAAE,CAAC,GAAG,IAAqB,EAAE,EAAE;YACtC,iEAAiE;YACjE,wEAAwE;YACxE,IAAI,CAAC,2BAA2B,EAAE,CAAC;gBACjC,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1D,CAAC;YACA,SAAsB,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Client helpers for calling actions through the framework transport.\n *\n * Components should prefer `useActionQuery` / `useActionMutation`; use\n * `callAction` for imperative cases such as debounced search, prefetching, or\n * event handlers that do not fit a hook.\n *\n * ## End-to-end type safety\n *\n * When the action type registry is generated (via the Vite plugin or CLI),\n * `useActionQuery` and `useActionMutation` automatically infer the correct\n * return type and parameter types from the action definitions — no manual\n * type annotations needed.\n *\n * ```ts\n * // Fully typed — return type and params inferred from the action's defineAction()\n * const { data } = useActionQuery(\"list-forms\", { status: \"published\" });\n * // ^? Form[] (inferred from the action's run() return type)\n * ```\n *\n * Without the registry, the hooks fall back to `any` types for backward\n * compatibility.\n */\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type {\n UseQueryOptions,\n UseMutationOptions,\n} from \"@tanstack/react-query\";\n\nimport { agentNativePath } from \"./api-path.js\";\nimport { ensureEmbedAuthFetchInterceptor } from \"./embed-auth.js\";\n\nconst ACTION_PREFIX = agentNativePath(\"/_agent-native/actions\");\n\n/**\n * Upper bound on how long a single action fetch may stay in flight (headers\n * AND body). Converts a hung server/proxy/connection into a visible, typed\n * failure instead of a UI that spins forever. Generous on purpose: it sits\n * above every server-side budget (serverless function limits, hosted run\n * wall-clock), so it only fires when something is genuinely stuck.\n */\nconst DEFAULT_ACTION_TIMEOUT_MS = 60_000;\n\nfunction isAuthFailure(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n \"status\" in error &&\n ((error as { status?: unknown }).status === 401 ||\n (error as { status?: unknown }).status === 403)\n );\n}\n\nfunction isActionTimeout(error: unknown): boolean {\n return (\n !!error &&\n typeof error === \"object\" &&\n (error as { timedOut?: unknown }).timedOut === true\n );\n}\n\n/** @internal exported for tests */\nexport function defaultActionQueryRetry(\n failureCount: number,\n error: unknown,\n): boolean {\n if (isAuthFailure(error)) return false;\n // A timeout already made the user wait the full timeout window once;\n // silently retrying would multiply that wait. Surface it instead.\n if (isActionTimeout(error)) return false;\n if (isBrowserResourceExhaustion(error)) return false;\n // Network-level failures never carry an HTTP `status` (actionFetch only\n // sets it after a response arrives). Chrome reports connection-pool\n // exhaustion (net::ERR_INSUFFICIENT_RESOURCES) as a generic \"Failed to\n // fetch\", indistinguishable from a transient blip — allow one retry, not\n // three, so an exhausted tab cannot sustain its own fetch storm.\n if (isNetworkLevelFailure(error)) return failureCount < 1;\n return failureCount < 3;\n}\n\n/** @internal alias kept for existing specs. */\nexport const shouldRetryActionQueryForError = defaultActionQueryRetry;\n\nfunction isBrowserResourceExhaustion(error: unknown): boolean {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"\";\n return /ERR_INSUFFICIENT_RESOURCES|insufficient resources/i.test(message);\n}\n\nfunction isNetworkLevelFailure(error: unknown): boolean {\n // Match the exact shape actionFetch produces for fetch-level failures (its\n // catch wraps the cause as \"Action <name> failed: <cause>\" and never sets a\n // status). Other status-less errors (test doubles, transport internals)\n // keep the standard three-retry policy.\n return (\n error instanceof Error &&\n (error as { status?: unknown }).status === undefined &&\n /^Action .+ failed: /.test(error.message)\n );\n}\n\n/**\n * Default retry backoff for action queries. React Query's stock retryDelay\n * (1s → 2s → 4s) makes a failing query sit on a spinner for ~7s before the\n * error surfaces; interactive data fetches want failures visible fast.\n *\n * @internal exported for tests\n */\nexport function defaultActionQueryRetryDelay(failureCount: number): number {\n return Math.min(500 * 2 ** failureCount, 2_000);\n}\n\n// ---------------------------------------------------------------------------\n// Action type registry — augmented by generated code\n// ---------------------------------------------------------------------------\n\n/**\n * Action type registry. This interface is empty by default and gets augmented\n * by the auto-generated `.generated/action-types.d.ts` file. When augmented,\n * it maps action names to their parameter and return types, enabling\n * end-to-end type safety for `useActionQuery` and `useActionMutation`.\n */\ndeclare global {\n interface AgentNativeActionRegistry {}\n}\n\nexport interface ActionRegistry extends AgentNativeActionRegistry {}\n\n/** Resolves to the union of registered action names, or `string` if no registry exists. */\ntype ActionName = keyof ActionRegistry extends never\n ? string\n : (keyof ActionRegistry & string) | (string & {});\n\n/** Resolves the return type of an action, or `any` if not in the registry. */\ntype ActionResult<T extends string> = T extends keyof ActionRegistry\n ? ActionRegistry[T] extends { result: infer R }\n ? R\n : any\n : any;\n\n/** Resolves the parameter type of an action, or `Record<string, any>` if not in the registry. */\ntype ActionParams<T extends string> = T extends keyof ActionRegistry\n ? ActionRegistry[T] extends { params: infer P }\n ? P\n : Record<string, any>\n : Record<string, any>;\n\nexport type ClientActionMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface ClientActionCallOptions {\n method?: ClientActionMethod;\n /** Abort signal for the underlying fetch. */\n signal?: AbortSignal;\n /** Override the default 60s fetch timeout for long-running actions. */\n timeoutMs?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Fetch helper\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the browser's IANA timezone (e.g. \"America/Los_Angeles\"). This is\n * sent on every action request as `x-user-timezone` so server-side defaults\n * like \"today\" honor the user's local day rather than the server's UTC clock.\n */\nfunction resolveUserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function serializeActionQueryParams(\n params: Record<string, any>,\n): string {\n const qs = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n appendActionQueryParam(qs, key, value);\n }\n return qs.toString();\n}\n\nfunction appendActionQueryParam(\n qs: URLSearchParams,\n key: string,\n value: unknown,\n) {\n if (value === null || value === undefined) return;\n if (Array.isArray(value)) {\n // Use bracket keys so a one-item array still arrives as an array after the\n // server parses URLSearchParams. Repeated bare keys lose that distinction.\n for (const item of value) {\n appendActionQueryParam(qs, `${key}[]`, item);\n }\n return;\n }\n qs.append(key, String(value));\n}\n\nexport interface ActionFetchOptions {\n /**\n * Abort signal from the caller (React Query passes one per queryFn\n * invocation so superseded requests — key change, unmount, refetch — cancel\n * the underlying network request instead of hogging a connection slot).\n */\n signal?: AbortSignal;\n /** Per-call override for the fetch timeout. */\n timeoutMs?: number;\n}\n\nasync function actionFetch<T>(\n name: string,\n method: string,\n params?: Record<string, any>,\n options?: ActionFetchOptions,\n): Promise<T> {\n ensureEmbedAuthFetchInterceptor();\n let url = `${ACTION_PREFIX}/${name}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n // Tag browser-originated action calls so the server can set\n // `ctx.caller = \"frontend\"` (vs a bare programmatic `\"http\"` POST).\n // Mirrors the X-Agent-Native-Tool-Bridge: 1 convention. The header is\n // safe to expose: CORS allows it (see action-routes.ts) and it carries\n // no auth weight — it only narrows the caller tag.\n \"X-Agent-Native-Frontend\": \"1\",\n };\n const tz = resolveUserTimezone();\n if (tz) headers[\"x-user-timezone\"] = tz;\n const init: RequestInit = {\n method,\n headers,\n cache: \"no-store\",\n };\n\n if (method === \"GET\" && params && Object.keys(params).length > 0) {\n // Skip null/undefined so optional filters don't turn into literal \"null\"\n // strings in the query string (e.g. `?folderId=null`).\n const qs = serializeActionQueryParams(params);\n if (qs) url += `?${qs}`;\n } else if (method !== \"GET\" && params) {\n init.body = JSON.stringify(params);\n }\n\n // One controller drives both cancellation sources: the caller's signal\n // (superseded query, unmount) and the timeout. The timer stays armed until\n // the BODY is fully read — headers arriving quickly while the body stalls\n // is exactly the hang this bounds.\n const outerSignal = options?.signal;\n const timeoutMs = options?.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS;\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const onOuterAbort = () => controller?.abort();\n if (outerSignal && controller) {\n if (outerSignal.aborted) controller.abort();\n else outerSignal.addEventListener(\"abort\", onOuterAbort, { once: true });\n }\n let timedOut = false;\n const timer = controller\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs)\n : null;\n if (controller) init.signal = controller.signal;\n\n const throwTimeout = (): never => {\n const error = new Error(\n `Action ${name} timed out after ${Math.round(timeoutMs / 1000)}s`,\n );\n (error as any).timedOut = true;\n (error as any).status = 408;\n throw error;\n };\n\n let res: Response;\n let raw = \"\";\n let readFailed = false;\n let readError: unknown;\n try {\n try {\n res = await fetch(url, init);\n } catch (err) {\n if (timedOut) throwTimeout();\n // Caller-initiated cancellation — rethrow untouched so React Query\n // recognizes it as a cancellation rather than a query failure.\n if (outerSignal?.aborted) throw err;\n // Network failures, CORS, server unreachable, etc. — give the caller a\n // useful message instead of the opaque \"Failed to fetch\".\n const cause = err instanceof Error ? err.message : String(err);\n throw new Error(`Action ${name} failed: ${cause}`);\n }\n\n // 204 No Content — nothing to parse.\n if (res.status === 204) return null as T;\n\n // Read the body as text first so we can:\n // - tolerate empty bodies (avoids \"Unexpected end of JSON input\")\n // - surface non-JSON error responses (HTML 401/404 pages, plain text, etc.)\n // - preserve the original HTTP status in the thrown error\n // Track read failures separately from \"no body\" — a stream interruption /\n // decode failure on a 2xx response should error rather than silently\n // succeed with `null`.\n try {\n raw = await res.text();\n } catch (err) {\n if (timedOut) throwTimeout();\n if (outerSignal?.aborted) throw err;\n readFailed = true;\n readError = err;\n }\n } finally {\n if (timer) clearTimeout(timer);\n if (outerSignal) outerSignal.removeEventListener(\"abort\", onOuterAbort);\n }\n\n let data: any = undefined;\n let parseFailed = false;\n if (raw.length > 0) {\n try {\n data = JSON.parse(raw);\n } catch {\n // Body wasn't JSON — keep `data` undefined and use the raw text below.\n parseFailed = true;\n }\n }\n\n if (!res.ok) {\n const message =\n (data && (data.error || data.message)) ||\n // Truncate non-JSON bodies so we don't dump entire HTML pages into the\n // console, but still give the developer a hint as to what came back.\n (raw && raw.slice(0, 200)) ||\n res.statusText ||\n `HTTP ${res.status}`;\n const error = new Error(`Action ${name} failed: ${message}`);\n (error as any).status = res.status;\n throw error;\n }\n\n // 2xx but the body couldn't even be read (mid-stream abort, decode failure,\n // etc.). Don't silently treat that as a `null` success.\n if (readFailed) {\n const cause =\n readError instanceof Error ? readError.message : String(readError);\n const error = new Error(\n `Action ${name} returned ${res.status} but the body could not be read: ${cause}`,\n );\n (error as any).status = res.status;\n throw error;\n }\n\n // 2xx with a non-empty, non-JSON body. Action callers expect typed data, so\n // returning `null` here would silently mask a real server bug (e.g. a proxy\n // returning HTML 200 instead of JSON). Throw instead — empty bodies (handled\n // above by the `raw.length > 0` guard and the 204 short-circuit) still\n // correctly resolve to `null`.\n if (parseFailed) {\n const error = new Error(\n `Action ${name} returned a non-JSON ${res.status} response: ${raw.slice(0, 200)}`,\n );\n (error as any).status = res.status;\n throw error;\n }\n\n return (data ?? (null as unknown)) as T;\n}\n\n/**\n * Imperatively call an action from browser/client code.\n *\n * Prefer `useActionQuery` / `useActionMutation` in React render flows. Use this\n * helper when a hook is not ergonomic; do not hand-write fetch calls to\n * `/_agent-native/actions/*` in components.\n */\nexport function callAction<\n TResult = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n params?: ActionParams<TName>,\n options: ClientActionCallOptions = {},\n): Promise<TResult extends undefined ? ActionResult<TName> : TResult> {\n type R = TResult extends undefined ? ActionResult<TName> : TResult;\n return actionFetch<R>(actionName, options.method ?? \"POST\", params, {\n signal: options.signal,\n timeoutMs: options.timeoutMs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Query hook\n// ---------------------------------------------------------------------------\n\n/**\n * Query an action exposed as GET.\n *\n * When the action type registry is generated, the return type and parameter\n * types are inferred automatically from the action's `defineAction()` call.\n *\n * ```ts\n * // Type-safe — no manual generic needed\n * const { data } = useActionQuery(\"list-meals\", { date: \"2025-01-01\" });\n *\n * // Manual override still works when needed\n * const { data } = useActionQuery<CustomType>(\"list-meals\");\n * ```\n */\nexport function useActionQuery<\n TResult = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n params?: ActionParams<TName>,\n options?: Omit<\n UseQueryOptions<TResult extends undefined ? ActionResult<TName> : TResult>,\n \"queryKey\" | \"queryFn\"\n >,\n) {\n type R = TResult extends undefined ? ActionResult<TName> : TResult;\n return useQuery<R>({\n queryKey: [\"action\", actionName, params],\n // Thread React Query's per-fetch AbortSignal into the network request so\n // superseded fetches (key change, unmount, rapid refetch) actually cancel\n // instead of holding a per-origin connection slot until they finish.\n queryFn: ({ signal }) =>\n actionFetch<R>(actionName, \"GET\", params, { signal }),\n retry: defaultActionQueryRetry,\n retryDelay: defaultActionQueryRetryDelay,\n ...options,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Mutation hook\n// ---------------------------------------------------------------------------\n\n/**\n * Mutate via an action exposed as POST (default), PUT, or DELETE.\n *\n * When the action type registry is generated, the return type and parameter\n * types are inferred automatically.\n *\n * ```ts\n * // Type-safe\n * const { mutate } = useActionMutation(\"log-meal\");\n * mutate({ name: \"Salad\", calories: 350 });\n * ```\n */\nexport function useActionMutation<\n TData = undefined,\n TVariables = undefined,\n TName extends ActionName = ActionName,\n>(\n actionName: TName,\n options?: Omit<\n UseMutationOptions<\n TData extends undefined ? ActionResult<TName> : TData,\n Error,\n TVariables extends undefined ? ActionParams<TName> : TVariables\n >,\n \"mutationFn\"\n > & {\n method?: \"POST\" | \"PUT\" | \"DELETE\";\n skipActionQueryInvalidation?: boolean;\n },\n) {\n const queryClient = useQueryClient();\n const {\n method: methodOpt,\n onSuccess,\n skipActionQueryInvalidation = false,\n ...restOptions\n } = options ?? ({} as any);\n const method = methodOpt ?? \"POST\";\n\n type D = TData extends undefined ? ActionResult<TName> : TData;\n type V = TVariables extends undefined ? ActionParams<TName> : TVariables;\n\n return useMutation<D, Error, V>({\n ...restOptions,\n mutationFn: (params) =>\n actionFetch<D>(actionName, method, params as Record<string, any>),\n onSuccess: (...args: [any, any, any]) => {\n // Most mutations change app data broadly. High-volume background\n // mutations can opt out and perform narrower invalidation in onSuccess.\n if (!skipActionQueryInvalidation) {\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n }\n (onSuccess as Function)?.(...args);\n },\n });\n}\n"]}
@@ -75,6 +75,9 @@ export declare function subscribeSyncEvents(options: SubscribeSyncEventsOptions)
75
75
  * compatibility invalidate triggered by `action` events. Use this to keep
76
76
  * expensive active queries on explicit-refresh semantics while still letting
77
77
  * normal source-versioned queries react through `useChangeVersion`.
78
+ * @param options.suppressActionInvalidationFor - Action names whose sync events
79
+ * should not invalidate all action queries. Use only for high-volume
80
+ * background actions that perform their own narrow client invalidation.
78
81
  */
79
82
  export declare function useDbSync(options?: {
80
83
  queryClient?: QueryClient;
@@ -89,6 +92,7 @@ export declare function useDbSync(options?: {
89
92
  pauseWhenHidden?: boolean;
90
93
  ignoreSource?: string;
91
94
  actionInvalidatePredicate?: (query: Query) => boolean;
95
+ suppressActionInvalidationFor?: string[];
92
96
  }): void;
93
97
  /** @deprecated Use useDbSync instead */
94
98
  export declare const useFileWatcher: typeof useDbSync;
@@ -1 +1 @@
1
- {"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"AAUA,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B;AAED,UAAU,WAAW;IACnB,iBAAiB,CAAC,IAAI,CAAC,EAAE;QACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;KACvC,GAAG,IAAI,CAAC;CACV;AAeD,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB,CAAC;AAqbF,gBAAgB;AAChB,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,MAAM,WAAW,0BAA0B;IACzC,gEAAgE;IAChE,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrE,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,MAAM,IAAI,CAkBZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS,CACvB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAClD,GACL,IAAI,CAgKN;AAED,wCAAwC;AACxC,eAAO,MAAM,cAAc,kBAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,MAAM,CAuDR"}
1
+ {"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"AAUA,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B;AAED,UAAU,WAAW;IACnB,iBAAiB,CAAC,IAAI,CAAC,EAAE;QACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;KACvC,GAAG,IAAI,CAAC;CACV;AAeD,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB,CAAC;AAqbF,gBAAgB;AAChB,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,MAAM,WAAW,0BAA0B;IACzC,gEAAgE;IAChE,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrE,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,MAAM,IAAI,CAkBZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,SAAS,CACvB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IACtD,6BAA6B,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC,GACL,IAAI,CAsLN;AAED,wCAAwC;AACxC,eAAO,MAAM,cAAc,kBAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,MAAM,CAuDR"}
@@ -423,6 +423,9 @@ export function subscribeSyncEvents(options) {
423
423
  * compatibility invalidate triggered by `action` events. Use this to keep
424
424
  * expensive active queries on explicit-refresh semantics while still letting
425
425
  * normal source-versioned queries react through `useChangeVersion`.
426
+ * @param options.suppressActionInvalidationFor - Action names whose sync events
427
+ * should not invalidate all action queries. Use only for high-volume
428
+ * background actions that perform their own narrow client invalidation.
426
429
  */
427
430
  export function useDbSync(options = {}) {
428
431
  const { queryClient, pollUrl = agentNativePath(options.eventsUrl ?? "/_agent-native/poll"), sseUrl = resolveSseUrl(options.sseUrl), interval = 2000, fallbackInterval = Math.max(options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS, interval), pauseWhenHidden = true, } = options;
@@ -432,6 +435,9 @@ export function useDbSync(options = {}) {
432
435
  ignoreSourceRef.current = options.ignoreSource;
433
436
  const actionInvalidatePredicateRef = useRef(options.actionInvalidatePredicate);
434
437
  actionInvalidatePredicateRef.current = options.actionInvalidatePredicate;
438
+ const suppressActionInvalidationForRef = useRef(options.suppressActionInvalidationFor);
439
+ suppressActionInvalidationForRef.current =
440
+ options.suppressActionInvalidationFor;
435
441
  useEffect(() => {
436
442
  const id = Symbol("useDbSync");
437
443
  // Per-subscriber version cursor: tracks which events have already been
@@ -448,6 +454,14 @@ export function useDbSync(options = {}) {
448
454
  const relevant = ignore
449
455
  ? events.filter((e) => e.requestSource !== ignore)
450
456
  : events;
457
+ const suppressedActions = new Set(suppressActionInvalidationForRef.current ?? []);
458
+ const isSuppressedActionEvent = (evt) => evt.source === "action" &&
459
+ typeof evt.key === "string" &&
460
+ suppressedActions.has(evt.key);
461
+ const nonAwareness = relevant.filter((e) => e.source !== "awareness");
462
+ const suppressesWholeBatch = nonAwareness.length > 0 &&
463
+ nonAwareness.every((evt) => evt.source === "action") &&
464
+ nonAwareness.every(isSuppressedActionEvent);
451
465
  // Bump per-source change counters. Components that read these via
452
466
  // `useChangeVersion(source)` and fold the value into a React Query
453
467
  // queryKey get a targeted refetch — no whole-cache invalidate, no
@@ -466,7 +480,7 @@ export function useDbSync(options = {}) {
466
480
  // still reach onEvent below for callers that render presence.
467
481
  const invalidating = relevant.filter((e) => e.source !== "awareness");
468
482
  if (invalidating.length > 0 && queryClient) {
469
- const hasActionEvent = invalidating.some((evt) => evt.source === "action");
483
+ const hasActionEvent = invalidating.some((evt) => evt.source === "action" && !isSuppressedActionEvent(evt));
470
484
  if (hasActionEvent) {
471
485
  // Custom apps frequently start with raw `useQuery` calls before
472
486
  // graduating to `useActionQuery` or source-versioned query keys.
@@ -483,23 +497,28 @@ export function useDbSync(options = {}) {
483
497
  // etc.). Templates' own data queries do NOT live here — they react
484
498
  // through `useChangeVersion(source)` in their query keys instead, so
485
499
  // a single change event doesn't fan out into "refetch everything".
486
- queryClient.invalidateQueries({ queryKey: ["action"] });
487
- queryClient.invalidateQueries({ queryKey: ["extension"] });
488
- queryClient.invalidateQueries({ queryKey: ["extensions"] });
489
- queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
490
- queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
491
- queryClient.invalidateQueries({ queryKey: ["slot-available"] });
492
- queryClient.invalidateQueries({ queryKey: ["tool"] });
493
- queryClient.invalidateQueries({ queryKey: ["tools"] });
494
- queryClient.invalidateQueries({ queryKey: ["app-state"] });
495
- if (hasAppStateEvent(invalidating, "navigate")) {
496
- queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
497
- }
498
- if (hasAppStateEvent(invalidating, "show-questions")) {
499
- queryClient.invalidateQueries({ queryKey: ["show-questions"] });
500
- }
501
- if (hasAppStateEvent(invalidating, "__set_url__")) {
502
- queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
500
+ // Suppressed-action-only batches skip this whole list (their
501
+ // mutations perform their own narrow invalidation) — but events must
502
+ // STILL reach the onEvent forwarding below, so guard, don't return.
503
+ if (!suppressesWholeBatch) {
504
+ queryClient.invalidateQueries({ queryKey: ["action"] });
505
+ queryClient.invalidateQueries({ queryKey: ["extension"] });
506
+ queryClient.invalidateQueries({ queryKey: ["extensions"] });
507
+ queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
508
+ queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
509
+ queryClient.invalidateQueries({ queryKey: ["slot-available"] });
510
+ queryClient.invalidateQueries({ queryKey: ["tool"] });
511
+ queryClient.invalidateQueries({ queryKey: ["tools"] });
512
+ queryClient.invalidateQueries({ queryKey: ["app-state"] });
513
+ if (hasAppStateEvent(invalidating, "navigate")) {
514
+ queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
515
+ }
516
+ if (hasAppStateEvent(invalidating, "show-questions")) {
517
+ queryClient.invalidateQueries({ queryKey: ["show-questions"] });
518
+ }
519
+ if (hasAppStateEvent(invalidating, "__set_url__")) {
520
+ queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
521
+ }
503
522
  }
504
523
  }
505
524
  // Always forward all events to onEvent — templates can layer surgical