@agent-native/core 0.85.6 → 0.86.0

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 (1069) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +130 -1
  3. package/corpus/core/README.md +0 -11
  4. package/corpus/core/docs/content/cloneable-saas.mdx +0 -1
  5. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
  6. package/corpus/core/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
  7. package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +1 -2
  8. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
  9. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +23 -7
  10. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +1 -1
  11. package/corpus/core/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
  12. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
  13. package/corpus/core/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
  14. package/corpus/core/docs/content/locales/de-DE/sharing.mdx +1 -2
  15. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +2 -3
  16. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +23 -7
  17. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +1 -1
  18. package/corpus/core/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
  19. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
  20. package/corpus/core/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
  21. package/corpus/core/docs/content/locales/es-ES/sharing.mdx +1 -2
  22. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +2 -3
  23. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +23 -7
  24. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +1 -1
  25. package/corpus/core/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
  26. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
  27. package/corpus/core/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
  28. package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +1 -2
  29. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
  30. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +23 -7
  31. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +1 -1
  32. package/corpus/core/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
  33. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
  34. package/corpus/core/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
  35. package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +1 -2
  36. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
  37. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +23 -7
  38. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +1 -1
  39. package/corpus/core/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
  40. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
  41. package/corpus/core/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
  42. package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +1 -2
  43. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
  44. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +23 -7
  45. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +1 -1
  46. package/corpus/core/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
  47. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
  48. package/corpus/core/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
  49. package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +1 -2
  50. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
  51. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +23 -7
  52. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +1 -1
  53. package/corpus/core/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
  54. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
  55. package/corpus/core/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
  56. package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +1 -2
  57. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
  58. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +23 -7
  59. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +1 -1
  60. package/corpus/core/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
  61. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
  62. package/corpus/core/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
  63. package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +1 -2
  64. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
  65. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +23 -7
  66. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +1 -1
  67. package/corpus/core/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
  68. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
  69. package/corpus/core/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
  70. package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +1 -2
  71. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
  72. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +23 -7
  73. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +1 -1
  74. package/corpus/core/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
  75. package/corpus/core/docs/content/plan-plugin.mdx +12 -11
  76. package/corpus/core/docs/content/sharing.mdx +1 -2
  77. package/corpus/core/docs/content/skills-guide.mdx +4 -3
  78. package/corpus/core/docs/content/template-plan.mdx +37 -6
  79. package/corpus/core/docs/content/tracking.mdx +1 -1
  80. package/corpus/core/docs/content/workspace-connections.mdx +1 -1
  81. package/corpus/core/package.json +6 -6
  82. package/corpus/core/src/agent/run-store.ts +1 -1
  83. package/corpus/core/src/chat-threads/store.ts +73 -11
  84. package/corpus/core/src/checkpoints/store.ts +13 -2
  85. package/corpus/core/src/cli/create.ts +3 -4
  86. package/corpus/core/src/cli/design-connect.ts +469 -9
  87. package/corpus/core/src/cli/index.ts +13 -0
  88. package/corpus/core/src/cli/plan-local.ts +281 -24
  89. package/corpus/core/src/cli/skills.ts +177 -21
  90. package/corpus/core/src/cli/templates-meta.ts +0 -13
  91. package/corpus/core/src/cli/visualize-repo.ts +715 -0
  92. package/corpus/core/src/client/AssistantChat.tsx +126 -17
  93. package/corpus/core/src/client/agent-chat-adapter.ts +6 -5
  94. package/corpus/core/src/client/app-providers.tsx +2 -2
  95. package/corpus/core/src/client/collab/index.ts +31 -0
  96. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +16 -1
  97. package/corpus/core/src/client/components/PresenceBar.tsx +14 -1
  98. package/corpus/core/src/client/components/RecentEditHighlights.tsx +219 -0
  99. package/corpus/core/src/client/components/RemoteSelectionRings.tsx +40 -6
  100. package/corpus/core/src/client/index.ts +48 -0
  101. package/corpus/core/src/client/org/OrgSwitcher.tsx +0 -4
  102. package/corpus/core/src/client/rich-markdown-editor/SharedRichEditor.tsx +49 -32
  103. package/corpus/core/src/client/rich-markdown-editor/index.ts +6 -0
  104. package/corpus/core/src/client/rich-markdown-editor/surgical-apply.ts +192 -0
  105. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +59 -8
  106. package/corpus/core/src/client/session-replay.ts +181 -15
  107. package/corpus/core/src/client/sse-event-processor.ts +90 -1
  108. package/corpus/core/src/client/use-action.ts +123 -25
  109. package/corpus/core/src/client/use-agent-engine-configured.ts +7 -1
  110. package/corpus/core/src/client/use-chat-threads.ts +18 -7
  111. package/corpus/core/src/client/use-db-sync.ts +104 -19
  112. package/corpus/core/src/client/use-pinch-zoom.ts +76 -11
  113. package/corpus/core/src/client/visual-style-controls.tsx +475 -0
  114. package/corpus/core/src/collab/agent-presence.ts +191 -62
  115. package/corpus/core/src/collab/awareness.ts +83 -6
  116. package/corpus/core/src/collab/client.ts +89 -146
  117. package/corpus/core/src/collab/index.ts +34 -0
  118. package/corpus/core/src/collab/presence.ts +63 -3
  119. package/corpus/core/src/collab/recent-edits.ts +171 -0
  120. package/corpus/core/src/collab/undo.ts +432 -0
  121. package/corpus/core/src/collab/ydoc-manager.ts +68 -0
  122. package/corpus/core/src/connections/catalog.ts +2 -3
  123. package/corpus/core/src/db/create-get-db.ts +86 -1
  124. package/corpus/core/src/db/ensure-additive-columns.ts +430 -0
  125. package/corpus/core/src/db/index.ts +6 -0
  126. package/corpus/core/src/db/migrations.ts +236 -35
  127. package/corpus/core/src/jobs/scheduler.ts +17 -14
  128. package/corpus/core/src/observability/traces.ts +182 -1
  129. package/corpus/core/src/org/context.ts +79 -2
  130. package/corpus/core/src/server/agent-chat-plugin.ts +10 -6
  131. package/corpus/core/src/server/auth-marketing.ts +0 -11
  132. package/corpus/core/src/server/auth.ts +11 -4
  133. package/corpus/core/src/server/collab-plugin.ts +56 -13
  134. package/corpus/core/src/server/design-token-utils.ts +1 -1
  135. package/corpus/core/src/server/fusion-app.ts +447 -0
  136. package/corpus/core/src/server/index.ts +13 -0
  137. package/corpus/core/src/server/onboarding-html.ts +3 -4
  138. package/corpus/core/src/server/poll-events.ts +14 -2
  139. package/corpus/core/src/server/poll.ts +219 -12
  140. package/corpus/core/src/server/schema-prompt.ts +28 -9
  141. package/corpus/core/src/settings/store.ts +33 -2
  142. package/corpus/core/src/sharing/actions/set-resource-visibility.ts +2 -0
  143. package/corpus/core/src/sharing/actions/share-resource.ts +3 -0
  144. package/corpus/core/src/sharing/actions/unshare-resource.ts +2 -0
  145. package/corpus/core/src/styles/agent-native.css +0 -5
  146. package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  147. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  148. package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  149. package/corpus/core/src/templates/default/package.json +3 -3
  150. package/corpus/core/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  151. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  152. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  153. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  154. package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  155. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  156. package/corpus/core/src/tracking/providers.ts +22 -1
  157. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +15 -0
  158. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -5
  159. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +8 -0
  160. package/corpus/templates/analytics/AGENTS.md +33 -6
  161. package/corpus/templates/analytics/actions/navigate.ts +14 -4
  162. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  163. package/corpus/templates/analytics/actions/view-screen.ts +57 -0
  164. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +23 -1
  165. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  166. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -0
  167. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -0
  168. package/corpus/templates/analytics/app/i18n/zh-TW.ts +15 -1
  169. package/corpus/templates/analytics/app/i18n-data.ts +151 -10
  170. package/corpus/templates/analytics/app/pages/Agents.tsx +118 -0
  171. package/corpus/templates/analytics/app/pages/DataSources.tsx +11 -1
  172. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +76 -40
  173. package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +5 -0
  174. package/corpus/templates/analytics/app/routes/agents.tsx +10 -0
  175. package/corpus/templates/analytics/changelog/2026-07-03-agent-admin-tools-page.md +6 -0
  176. package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +6 -0
  177. package/corpus/templates/analytics/changelog/2026-07-03-session-recordings-now-include-response-bodies-for-5xx-error.md +6 -0
  178. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +34 -0
  179. package/corpus/templates/analytics/docs/schemas/tracked-events.md +6 -0
  180. package/corpus/templates/analytics/package.json +3 -3
  181. package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +173 -0
  182. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +23 -5
  183. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +58 -13
  184. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +23 -0
  185. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +5 -0
  186. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +120 -8
  187. package/corpus/templates/analytics/server/lib/session-replay.ts +3 -2
  188. package/corpus/templates/analytics/server/plugins/db.ts +93 -2
  189. package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +45 -1
  190. package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +10 -0
  191. package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +82 -19
  192. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +15 -0
  193. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +8 -0
  194. package/corpus/templates/assets/package.json +3 -3
  195. package/corpus/templates/assets/server/plugins/db.ts +63 -2
  196. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +15 -0
  197. package/corpus/templates/brain/package.json +3 -3
  198. package/corpus/templates/brain/server/plugins/db.ts +75 -2
  199. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +15 -0
  200. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +8 -0
  201. package/corpus/templates/calendar/package.json +3 -3
  202. package/corpus/templates/calendar/server/plugins/db.ts +64 -2
  203. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +15 -0
  204. package/corpus/templates/chat/package.json +3 -3
  205. package/corpus/templates/clips/actions/finalize-recording.ts +50 -40
  206. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
  207. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -3
  208. package/corpus/templates/clips/changelog/2026-07-03-mobile-recording-pages-keep-videos-in-a-16-9-player-with-act.md +6 -0
  209. package/corpus/templates/clips/desktop/src/lib/recorder.ts +245 -77
  210. package/corpus/templates/clips/package.json +3 -3
  211. package/corpus/templates/clips/server/lib/recording-upload-state.ts +56 -0
  212. package/corpus/templates/clips/server/plugins/db.ts +68 -1
  213. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +27 -3
  214. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +15 -0
  215. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +8 -0
  216. package/corpus/templates/content/actions/edit-document.ts +48 -1
  217. package/corpus/templates/content/actions/list-documents.ts +101 -94
  218. package/corpus/templates/content/actions/search-documents.ts +19 -3
  219. package/corpus/templates/content/actions/update-document.ts +54 -1
  220. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -7
  221. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +97 -2
  222. package/corpus/templates/content/changelog/2026-07-03-agent-document-edits-now-appear-as-a-live-collaborator-prese.md +6 -0
  223. package/corpus/templates/content/changelog/2026-07-03-document-search-and-list-views-load-faster-on-large-workspac.md +6 -0
  224. package/corpus/templates/content/package.json +3 -3
  225. package/corpus/templates/content/server/plugins/db.ts +57 -1
  226. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +190 -13
  227. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +16 -4
  228. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +46 -3
  229. package/corpus/templates/design/.agents/skills/full-app-build/SKILL.md +140 -0
  230. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +20 -0
  231. package/corpus/templates/design/AGENTS.md +83 -13
  232. package/corpus/templates/design/actions/add-fusion-screens.ts +91 -0
  233. package/corpus/templates/design/actions/apply-a11y-fix.ts +5 -1
  234. package/corpus/templates/design/actions/apply-component-prop-edit.ts +24 -69
  235. package/corpus/templates/design/actions/apply-fusion-edits.ts +161 -0
  236. package/corpus/templates/design/actions/apply-motion-edit.ts +29 -2
  237. package/corpus/templates/design/actions/apply-visual-edit.ts +38 -3
  238. package/corpus/templates/design/actions/connect-localhost.ts +25 -18
  239. package/corpus/templates/design/actions/create-component.ts +5 -1
  240. package/corpus/templates/design/actions/create-fusion-app.ts +202 -0
  241. package/corpus/templates/design/actions/deploy-fusion-app.ts +123 -0
  242. package/corpus/templates/design/actions/edit-design.ts +13 -16
  243. package/corpus/templates/design/actions/export-pdf.ts +8 -1
  244. package/corpus/templates/design/actions/export-zip.ts +10 -1
  245. package/corpus/templates/design/actions/get-fusion-deploy-status.ts +86 -0
  246. package/corpus/templates/design/actions/get-motion-timeline.ts +26 -20
  247. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +10 -0
  248. package/corpus/templates/design/actions/insert-asset.ts +190 -7
  249. package/corpus/templates/design/actions/list-fusion-edits.ts +96 -0
  250. package/corpus/templates/design/actions/list-local-files.ts +133 -0
  251. package/corpus/templates/design/actions/open-visual-edit.ts +27 -5
  252. package/corpus/templates/design/actions/present-design-variants.ts +2 -2
  253. package/corpus/templates/design/actions/push-fusion-app.ts +62 -0
  254. package/corpus/templates/design/actions/queue-fusion-edit.ts +112 -0
  255. package/corpus/templates/design/actions/read-local-file.ts +139 -0
  256. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +18 -14
  257. package/corpus/templates/design/actions/run-design-audit.ts +7 -4
  258. package/corpus/templates/design/actions/send-fusion-message.ts +69 -0
  259. package/corpus/templates/design/actions/sync-fusion-app.ts +196 -0
  260. package/corpus/templates/design/actions/write-local-file.ts +162 -36
  261. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +12 -0
  262. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +600 -66
  263. package/corpus/templates/design/app/components/design/EditPanel.tsx +793 -304
  264. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +328 -0
  265. package/corpus/templates/design/app/components/design/LayersPanel.tsx +671 -154
  266. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +1 -1
  267. package/corpus/templates/design/app/components/design/MotionDock.tsx +234 -46
  268. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2070 -420
  269. package/corpus/templates/design/app/components/design/StatesPanel.tsx +25 -2
  270. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -98
  271. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +640 -72
  272. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +605 -0
  273. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +50 -26
  274. package/corpus/templates/design/app/components/design/code-workbench/ActivityBar.tsx +85 -0
  275. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +466 -0
  276. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +53 -0
  277. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +339 -0
  278. package/corpus/templates/design/app/components/design/code-workbench/editor/Breadcrumbs.tsx +42 -0
  279. package/corpus/templates/design/app/components/design/code-workbench/editor/BufferSync.tsx +128 -0
  280. package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +363 -0
  281. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +268 -0
  282. package/corpus/templates/design/app/components/design/code-workbench/editor/StatusBar.tsx +208 -0
  283. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +61 -0
  284. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme-palette.ts +191 -0
  285. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme.ts +29 -0
  286. package/corpus/templates/design/app/components/design/code-workbench/editor/status-bar-lang.ts +21 -0
  287. package/corpus/templates/design/app/components/design/code-workbench/editor/tab-drag.ts +27 -0
  288. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +155 -0
  289. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +551 -0
  290. package/corpus/templates/design/app/components/design/code-workbench/explorer/file-icons.tsx +115 -0
  291. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +65 -0
  292. package/corpus/templates/design/app/components/design/code-workbench/format/prettier-format.ts +182 -0
  293. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +133 -0
  294. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +563 -0
  295. package/corpus/templates/design/app/components/design/code-workbench/quickinput/fuzzy.ts +170 -0
  296. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +508 -0
  297. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +280 -0
  298. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +734 -0
  299. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +39 -0
  300. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +196 -0
  301. package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +177 -0
  302. package/corpus/templates/design/app/components/design/code-workbench/workspace/tree.ts +110 -0
  303. package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +103 -0
  304. package/corpus/templates/design/app/components/design/code-workbench-theme.ts +68 -0
  305. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +16 -4
  306. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +217 -113
  307. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +111 -5
  308. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -2
  309. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +79 -16
  310. package/corpus/templates/design/app/components/design/inspector/index.ts +9 -0
  311. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +67 -2
  312. package/corpus/templates/design/app/components/design/types.ts +16 -0
  313. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +80 -1
  314. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +24 -6
  315. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +15 -1
  316. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +6 -16
  317. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +77 -0
  318. package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
  319. package/corpus/templates/design/app/i18n-data.ts +31 -0
  320. package/corpus/templates/design/app/lib/design-import.ts +95 -0
  321. package/corpus/templates/design/app/pages/DesignEditor.tsx +5586 -1112
  322. package/corpus/templates/design/app/pages/Index.tsx +90 -17
  323. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +6 -0
  324. package/corpus/templates/design/changelog/2026-07-03-inspector-fixes-mixed-selections-show-mixed-instead-of-wrong.md +6 -0
  325. package/corpus/templates/design/changelog/2026-07-03-keyframe-timeline-works-reliably-drag-keyframes-smoothly-pic.md +6 -0
  326. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +6 -0
  327. package/corpus/templates/design/changelog/2026-07-03-much-faster-editor-smooth-dragging-zooming-and-panel-updates.md +6 -0
  328. package/corpus/templates/design/changelog/2026-07-03-pen-tool-refinements-click-the-first-point-to-close-with-a-d.md +6 -0
  329. package/corpus/templates/design/changelog/2026-07-03-text-editing-enter-adds-a-line-break-international-ime-typin.md +6 -0
  330. package/corpus/templates/design/changelog/2026-07-03-the-ai-now-edits-like-a-collaborator-on-canvas-its-cursor-gl.md +6 -0
  331. package/corpus/templates/design/changelog/2026-07-03-the-code-panel-is-now-a-full-vs-code-style-editor-with-a-fil.md +6 -0
  332. package/corpus/templates/design/changelog/2026-07-03-undo-and-redo-are-dependable-across-agent-edits-screen-switc.md +6 -0
  333. package/corpus/templates/design/package.json +4 -3
  334. package/corpus/templates/design/server/db/schema.ts +31 -0
  335. package/corpus/templates/design/server/lib/design-export.ts +43 -1
  336. package/corpus/templates/design/server/lib/fusion-screens.ts +246 -0
  337. package/corpus/templates/design/server/plugins/db.ts +89 -2
  338. package/corpus/templates/design/shared/board-file.ts +25 -5
  339. package/corpus/templates/design/shared/canvas-math.ts +754 -9
  340. package/corpus/templates/design/shared/code-layer.ts +304 -26
  341. package/corpus/templates/design/shared/collab-selection.ts +52 -0
  342. package/corpus/templates/design/shared/color-utils.ts +84 -0
  343. package/corpus/templates/design/shared/design-source-capabilities.ts +16 -7
  344. package/corpus/templates/design/shared/full-app.ts +126 -0
  345. package/corpus/templates/design/shared/motion-compiler.ts +75 -31
  346. package/corpus/templates/design/shared/motion-timeline.ts +335 -0
  347. package/corpus/templates/design/shared/pen-path.ts +496 -23
  348. package/corpus/templates/dispatch/package.json +3 -3
  349. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +15 -0
  350. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +8 -0
  351. package/corpus/templates/forms/package.json +3 -3
  352. package/corpus/templates/forms/server/plugins/db.ts +62 -2
  353. package/corpus/templates/macros/package.json +3 -3
  354. package/corpus/templates/macros/server/plugins/db.ts +63 -2
  355. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +15 -0
  356. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +8 -0
  357. package/corpus/templates/mail/package.json +3 -3
  358. package/corpus/templates/mail/server/lib/contact-frequency.ts +42 -15
  359. package/corpus/templates/mail/server/plugins/db.ts +63 -2
  360. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +15 -0
  361. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +9 -3
  362. package/corpus/templates/plan/.agents/skills/visualize-repo/SKILL.md +92 -0
  363. package/corpus/templates/plan/actions/export-visual-plan.ts +5 -0
  364. package/corpus/templates/plan/actions/get-local-plan-folder.ts +4 -22
  365. package/corpus/templates/plan/actions/get-plan-version.ts +6 -16
  366. package/corpus/templates/plan/actions/list-plan-versions.ts +48 -20
  367. package/corpus/templates/plan/actions/patch-visual-plan-source.ts +41 -1
  368. package/corpus/templates/plan/actions/promote-local-plan-folder.ts +4 -22
  369. package/corpus/templates/plan/actions/update-local-plan-comments.ts +4 -2
  370. package/corpus/templates/plan/actions/update-local-plan-folder.ts +4 -22
  371. package/corpus/templates/plan/actions/update-visual-plan.ts +60 -1
  372. package/corpus/templates/plan/agent-native.app-skill.json +10 -0
  373. package/corpus/templates/plan/app/components/editor/PlanDocumentEditor.tsx +138 -39
  374. package/corpus/templates/plan/app/components/editor/usePlanUndoStack.ts +165 -23
  375. package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +80 -4
  376. package/corpus/templates/plan/app/global.css +11 -9
  377. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +10 -1
  378. package/corpus/templates/plan/app/hooks/use-plan-presence.ts +66 -0
  379. package/corpus/templates/plan/app/pages/PlansPage.tsx +215 -63
  380. package/corpus/templates/plan/changelog/2026-07-03-local-visual-plans-and-recaps-can-save-comments-without-sign.md +6 -0
  381. package/corpus/templates/plan/changelog/2026-07-03-plan-tabs-render-nested-text-instead-of-raw-json.md +6 -0
  382. package/corpus/templates/plan/changelog/2026-07-03-plans-are-now-fully-real-time-collaborative-multiple-people-.md +6 -0
  383. package/corpus/templates/plan/changelog/2026-07-03-plans-open-and-refresh-faster-especially-long-lived-plans-wi.md +6 -0
  384. package/corpus/templates/plan/changelog/2026-07-03-undo-now-survives-agent-edits-cmd-z-keeps-working-on-your-ow.md +6 -0
  385. package/corpus/templates/plan/package.json +5 -3
  386. package/corpus/templates/plan/server/db/schema.ts +12 -0
  387. package/corpus/templates/plan/server/lib/local-plan-kind.ts +25 -0
  388. package/corpus/templates/plan/server/lib/plan-versions.ts +83 -1
  389. package/corpus/templates/plan/server/lib/public-action-paths.ts +0 -2
  390. package/corpus/templates/plan/server/plans.ts +26 -3
  391. package/corpus/templates/plan/server/plugins/db.ts +105 -2
  392. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +8 -4
  393. package/corpus/templates/slides/AGENTS.md +4 -0
  394. package/corpus/templates/slides/actions/_agent-presence.ts +48 -0
  395. package/corpus/templates/slides/actions/add-slide.ts +16 -18
  396. package/corpus/templates/slides/actions/patch-deck.ts +4 -2
  397. package/corpus/templates/slides/actions/update-slide.ts +114 -77
  398. package/corpus/templates/slides/actions/view-screen.ts +39 -0
  399. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +47 -3
  400. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +32 -35
  401. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +625 -199
  402. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +342 -0
  403. package/corpus/templates/slides/app/components/editor/TweaksPanel.tsx +46 -96
  404. package/corpus/templates/slides/app/components/visual-editor/SaveStatusIndicator.tsx +8 -1
  405. package/corpus/templates/slides/app/context/DeckContext.tsx +589 -176
  406. package/corpus/templates/slides/app/hooks/use-deck-presence.ts +84 -42
  407. package/corpus/templates/slides/app/i18n/ar-SA.ts +46 -0
  408. package/corpus/templates/slides/app/i18n/de-DE.ts +46 -0
  409. package/corpus/templates/slides/app/i18n/en-US.ts +46 -0
  410. package/corpus/templates/slides/app/i18n/es-ES.ts +46 -0
  411. package/corpus/templates/slides/app/i18n/fr-FR.ts +46 -0
  412. package/corpus/templates/slides/app/i18n/hi-IN.ts +46 -0
  413. package/corpus/templates/slides/app/i18n/ja-JP.ts +46 -0
  414. package/corpus/templates/slides/app/i18n/ko-KR.ts +46 -0
  415. package/corpus/templates/slides/app/i18n/pt-BR.ts +46 -0
  416. package/corpus/templates/slides/app/i18n/zh-CN.ts +46 -0
  417. package/corpus/templates/slides/app/i18n/zh-TW.ts +46 -0
  418. package/corpus/templates/slides/app/pages/DeckEditor.tsx +25 -13
  419. package/corpus/templates/slides/changelog/2026-07-03-figma-style-slide-editing.md +6 -0
  420. package/corpus/templates/slides/changelog/2026-07-03-see-the-ai-working-with-you-when-the-agent-edits-a-slide-it-.md +6 -0
  421. package/corpus/templates/slides/changelog/2026-07-03-undo-redo-is-now-precise-and-safe-with-collaborators-it-only.md +6 -0
  422. package/corpus/templates/slides/package.json +3 -3
  423. package/corpus/templates/slides/server/handlers/decks.ts +31 -3
  424. package/corpus/templates/slides/server/plugins/db.ts +65 -2
  425. package/corpus/templates/videos/netlify.toml +1 -12
  426. package/dist/agent/run-store.js +1 -1
  427. package/dist/agent/run-store.js.map +1 -1
  428. package/dist/chat-threads/store.d.ts +9 -1
  429. package/dist/chat-threads/store.d.ts.map +1 -1
  430. package/dist/chat-threads/store.js +63 -11
  431. package/dist/chat-threads/store.js.map +1 -1
  432. package/dist/checkpoints/store.d.ts.map +1 -1
  433. package/dist/checkpoints/store.js +10 -2
  434. package/dist/checkpoints/store.js.map +1 -1
  435. package/dist/cli/create.d.ts.map +1 -1
  436. package/dist/cli/create.js +3 -5
  437. package/dist/cli/create.js.map +1 -1
  438. package/dist/cli/design-connect.d.ts +49 -1
  439. package/dist/cli/design-connect.d.ts.map +1 -1
  440. package/dist/cli/design-connect.js +414 -9
  441. package/dist/cli/design-connect.js.map +1 -1
  442. package/dist/cli/index.js +12 -0
  443. package/dist/cli/index.js.map +1 -1
  444. package/dist/cli/plan-local.d.ts +24 -0
  445. package/dist/cli/plan-local.d.ts.map +1 -1
  446. package/dist/cli/plan-local.js +188 -20
  447. package/dist/cli/plan-local.js.map +1 -1
  448. package/dist/cli/skills.d.ts +3 -1
  449. package/dist/cli/skills.d.ts.map +1 -1
  450. package/dist/cli/skills.js +173 -21
  451. package/dist/cli/skills.js.map +1 -1
  452. package/dist/cli/templates-meta.d.ts.map +1 -1
  453. package/dist/cli/templates-meta.js +0 -14
  454. package/dist/cli/templates-meta.js.map +1 -1
  455. package/dist/cli/visualize-repo.d.ts +49 -0
  456. package/dist/cli/visualize-repo.d.ts.map +1 -0
  457. package/dist/cli/visualize-repo.js +561 -0
  458. package/dist/cli/visualize-repo.js.map +1 -0
  459. package/dist/client/AssistantChat.d.ts.map +1 -1
  460. package/dist/client/AssistantChat.js +120 -15
  461. package/dist/client/AssistantChat.js.map +1 -1
  462. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  463. package/dist/client/agent-chat-adapter.js +6 -5
  464. package/dist/client/agent-chat-adapter.js.map +1 -1
  465. package/dist/client/app-providers.d.ts +2 -2
  466. package/dist/client/app-providers.js.map +1 -1
  467. package/dist/client/collab/index.d.ts +4 -1
  468. package/dist/client/collab/index.d.ts.map +1 -1
  469. package/dist/client/collab/index.js +3 -0
  470. package/dist/client/collab/index.js.map +1 -1
  471. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  472. package/dist/client/components/LiveCursorOverlay.js +11 -3
  473. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  474. package/dist/client/components/PresenceBar.d.ts.map +1 -1
  475. package/dist/client/components/PresenceBar.js +6 -1
  476. package/dist/client/components/PresenceBar.js.map +1 -1
  477. package/dist/client/components/RecentEditHighlights.d.ts +43 -0
  478. package/dist/client/components/RecentEditHighlights.d.ts.map +1 -0
  479. package/dist/client/components/RecentEditHighlights.js +146 -0
  480. package/dist/client/components/RecentEditHighlights.js.map +1 -0
  481. package/dist/client/components/RemoteSelectionRings.d.ts +9 -0
  482. package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -1
  483. package/dist/client/components/RemoteSelectionRings.js +22 -8
  484. package/dist/client/components/RemoteSelectionRings.js.map +1 -1
  485. package/dist/client/index.d.ts +6 -1
  486. package/dist/client/index.d.ts.map +1 -1
  487. package/dist/client/index.js +7 -0
  488. package/dist/client/index.js.map +1 -1
  489. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  490. package/dist/client/org/OrgSwitcher.js +1 -3
  491. package/dist/client/org/OrgSwitcher.js.map +1 -1
  492. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
  493. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  494. package/dist/client/rich-markdown-editor/SharedRichEditor.js +8 -2
  495. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  496. package/dist/client/rich-markdown-editor/index.d.ts +1 -0
  497. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  498. package/dist/client/rich-markdown-editor/index.js +1 -0
  499. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  500. package/dist/client/rich-markdown-editor/surgical-apply.d.ts +66 -0
  501. package/dist/client/rich-markdown-editor/surgical-apply.d.ts.map +1 -0
  502. package/dist/client/rich-markdown-editor/surgical-apply.js +147 -0
  503. package/dist/client/rich-markdown-editor/surgical-apply.js.map +1 -0
  504. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +18 -2
  505. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  506. package/dist/client/rich-markdown-editor/useCollabReconcile.js +39 -8
  507. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  508. package/dist/client/session-replay.d.ts +10 -1
  509. package/dist/client/session-replay.d.ts.map +1 -1
  510. package/dist/client/session-replay.js +134 -7
  511. package/dist/client/session-replay.js.map +1 -1
  512. package/dist/client/sse-event-processor.d.ts.map +1 -1
  513. package/dist/client/sse-event-processor.js +81 -1
  514. package/dist/client/sse-event-processor.js.map +1 -1
  515. package/dist/client/use-action.d.ts +24 -0
  516. package/dist/client/use-action.d.ts.map +1 -1
  517. package/dist/client/use-action.js +109 -27
  518. package/dist/client/use-action.js.map +1 -1
  519. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  520. package/dist/client/use-agent-engine-configured.js +7 -1
  521. package/dist/client/use-agent-engine-configured.js.map +1 -1
  522. package/dist/client/use-chat-threads.d.ts.map +1 -1
  523. package/dist/client/use-chat-threads.js +11 -3
  524. package/dist/client/use-chat-threads.js.map +1 -1
  525. package/dist/client/use-db-sync.d.ts +36 -0
  526. package/dist/client/use-db-sync.d.ts.map +1 -1
  527. package/dist/client/use-db-sync.js +68 -12
  528. package/dist/client/use-db-sync.js.map +1 -1
  529. package/dist/client/use-pinch-zoom.d.ts.map +1 -1
  530. package/dist/client/use-pinch-zoom.js +76 -11
  531. package/dist/client/use-pinch-zoom.js.map +1 -1
  532. package/dist/client/visual-style-controls.d.ts +79 -0
  533. package/dist/client/visual-style-controls.d.ts.map +1 -0
  534. package/dist/client/visual-style-controls.js +150 -0
  535. package/dist/client/visual-style-controls.js.map +1 -0
  536. package/dist/collab/agent-presence.d.ts +51 -5
  537. package/dist/collab/agent-presence.d.ts.map +1 -1
  538. package/dist/collab/agent-presence.js +144 -58
  539. package/dist/collab/agent-presence.js.map +1 -1
  540. package/dist/collab/awareness-store.d.ts +32 -0
  541. package/dist/collab/awareness-store.d.ts.map +1 -0
  542. package/dist/collab/awareness-store.js +0 -0
  543. package/dist/collab/awareness-store.js.map +1 -0
  544. package/dist/collab/awareness.d.ts +14 -3
  545. package/dist/collab/awareness.d.ts.map +1 -1
  546. package/dist/collab/awareness.js +53 -5
  547. package/dist/collab/awareness.js.map +1 -1
  548. package/dist/collab/client.d.ts +2 -0
  549. package/dist/collab/client.d.ts.map +1 -1
  550. package/dist/collab/client.js +79 -119
  551. package/dist/collab/client.js.map +1 -1
  552. package/dist/collab/index.d.ts +3 -1
  553. package/dist/collab/index.d.ts.map +1 -1
  554. package/dist/collab/index.js +5 -1
  555. package/dist/collab/index.js.map +1 -1
  556. package/dist/collab/presence.d.ts.map +1 -1
  557. package/dist/collab/presence.js +54 -3
  558. package/dist/collab/presence.js.map +1 -1
  559. package/dist/collab/recent-edits.d.ts +87 -0
  560. package/dist/collab/recent-edits.d.ts.map +1 -0
  561. package/dist/collab/recent-edits.js +116 -0
  562. package/dist/collab/recent-edits.js.map +1 -0
  563. package/dist/collab/undo.d.ts +121 -0
  564. package/dist/collab/undo.d.ts.map +1 -0
  565. package/dist/collab/undo.js +244 -0
  566. package/dist/collab/undo.js.map +1 -0
  567. package/dist/collab/ydoc-manager.d.ts +10 -0
  568. package/dist/collab/ydoc-manager.d.ts.map +1 -1
  569. package/dist/collab/ydoc-manager.js +57 -0
  570. package/dist/collab/ydoc-manager.js.map +1 -1
  571. package/dist/connections/catalog.d.ts +2 -2
  572. package/dist/connections/catalog.d.ts.map +1 -1
  573. package/dist/connections/catalog.js +1 -1
  574. package/dist/connections/catalog.js.map +1 -1
  575. package/dist/db/create-get-db.d.ts +22 -0
  576. package/dist/db/create-get-db.d.ts.map +1 -1
  577. package/dist/db/create-get-db.js +73 -1
  578. package/dist/db/create-get-db.js.map +1 -1
  579. package/dist/db/ensure-additive-columns.d.ts +93 -0
  580. package/dist/db/ensure-additive-columns.d.ts.map +1 -0
  581. package/dist/db/ensure-additive-columns.js +355 -0
  582. package/dist/db/ensure-additive-columns.js.map +1 -0
  583. package/dist/db/index.d.ts +1 -0
  584. package/dist/db/index.d.ts.map +1 -1
  585. package/dist/db/index.js +1 -0
  586. package/dist/db/index.js.map +1 -1
  587. package/dist/db/migrations.d.ts +70 -0
  588. package/dist/db/migrations.d.ts.map +1 -1
  589. package/dist/db/migrations.js +196 -33
  590. package/dist/db/migrations.js.map +1 -1
  591. package/dist/jobs/scheduler.js +17 -10
  592. package/dist/jobs/scheduler.js.map +1 -1
  593. package/dist/notifications/routes.d.ts +3 -3
  594. package/dist/observability/routes.d.ts +5 -5
  595. package/dist/observability/traces.d.ts.map +1 -1
  596. package/dist/observability/traces.js +155 -1
  597. package/dist/observability/traces.js.map +1 -1
  598. package/dist/org/context.d.ts +7 -0
  599. package/dist/org/context.d.ts.map +1 -1
  600. package/dist/org/context.js +54 -2
  601. package/dist/org/context.js.map +1 -1
  602. package/dist/progress/routes.d.ts +1 -1
  603. package/dist/resources/handlers.d.ts +3 -3
  604. package/dist/secrets/routes.d.ts +6 -6
  605. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  606. package/dist/server/agent-chat-plugin.js +10 -6
  607. package/dist/server/agent-chat-plugin.js.map +1 -1
  608. package/dist/server/auth-marketing.d.ts.map +1 -1
  609. package/dist/server/auth-marketing.js +0 -10
  610. package/dist/server/auth-marketing.js.map +1 -1
  611. package/dist/server/auth.d.ts.map +1 -1
  612. package/dist/server/auth.js +6 -4
  613. package/dist/server/auth.js.map +1 -1
  614. package/dist/server/collab-plugin.d.ts.map +1 -1
  615. package/dist/server/collab-plugin.js +45 -13
  616. package/dist/server/collab-plugin.js.map +1 -1
  617. package/dist/server/design-token-utils.d.ts +1 -1
  618. package/dist/server/design-token-utils.js +1 -1
  619. package/dist/server/design-token-utils.js.map +1 -1
  620. package/dist/server/fusion-app.d.ts +107 -0
  621. package/dist/server/fusion-app.d.ts.map +1 -0
  622. package/dist/server/fusion-app.js +371 -0
  623. package/dist/server/fusion-app.js.map +1 -0
  624. package/dist/server/index.d.ts +1 -0
  625. package/dist/server/index.d.ts.map +1 -1
  626. package/dist/server/index.js +1 -0
  627. package/dist/server/index.js.map +1 -1
  628. package/dist/server/onboarding-html.d.ts.map +1 -1
  629. package/dist/server/onboarding-html.js +3 -4
  630. package/dist/server/onboarding-html.js.map +1 -1
  631. package/dist/server/poll-events.d.ts +2 -0
  632. package/dist/server/poll-events.d.ts.map +1 -1
  633. package/dist/server/poll-events.js +7 -2
  634. package/dist/server/poll-events.js.map +1 -1
  635. package/dist/server/poll.d.ts +47 -3
  636. package/dist/server/poll.d.ts.map +1 -1
  637. package/dist/server/poll.js +176 -10
  638. package/dist/server/poll.js.map +1 -1
  639. package/dist/server/schema-prompt.d.ts.map +1 -1
  640. package/dist/server/schema-prompt.js +24 -7
  641. package/dist/server/schema-prompt.js.map +1 -1
  642. package/dist/settings/store.d.ts.map +1 -1
  643. package/dist/settings/store.js +32 -3
  644. package/dist/settings/store.js.map +1 -1
  645. package/dist/sharing/actions/set-resource-visibility.js +2 -0
  646. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  647. package/dist/sharing/actions/share-resource.d.ts.map +1 -1
  648. package/dist/sharing/actions/share-resource.js +3 -0
  649. package/dist/sharing/actions/share-resource.js.map +1 -1
  650. package/dist/sharing/actions/unshare-resource.js +2 -0
  651. package/dist/sharing/actions/unshare-resource.js.map +1 -1
  652. package/dist/styles/agent-native.css +0 -5
  653. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  654. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  655. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  656. package/dist/templates/default/package.json +3 -3
  657. package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  658. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  659. package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  660. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  661. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  662. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  663. package/dist/tracking/providers.d.ts.map +1 -1
  664. package/dist/tracking/providers.js +17 -1
  665. package/dist/tracking/providers.js.map +1 -1
  666. package/docs/content/cloneable-saas.mdx +0 -1
  667. package/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
  668. package/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
  669. package/docs/content/locales/ar-SA/sharing.mdx +1 -2
  670. package/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
  671. package/docs/content/locales/ar-SA/template-plan.mdx +23 -7
  672. package/docs/content/locales/ar-SA/tracking.mdx +1 -1
  673. package/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
  674. package/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
  675. package/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
  676. package/docs/content/locales/de-DE/sharing.mdx +1 -2
  677. package/docs/content/locales/de-DE/skills-guide.mdx +2 -3
  678. package/docs/content/locales/de-DE/template-plan.mdx +23 -7
  679. package/docs/content/locales/de-DE/tracking.mdx +1 -1
  680. package/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
  681. package/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
  682. package/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
  683. package/docs/content/locales/es-ES/sharing.mdx +1 -2
  684. package/docs/content/locales/es-ES/skills-guide.mdx +2 -3
  685. package/docs/content/locales/es-ES/template-plan.mdx +23 -7
  686. package/docs/content/locales/es-ES/tracking.mdx +1 -1
  687. package/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
  688. package/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
  689. package/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
  690. package/docs/content/locales/fr-FR/sharing.mdx +1 -2
  691. package/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
  692. package/docs/content/locales/fr-FR/template-plan.mdx +23 -7
  693. package/docs/content/locales/fr-FR/tracking.mdx +1 -1
  694. package/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
  695. package/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
  696. package/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
  697. package/docs/content/locales/hi-IN/sharing.mdx +1 -2
  698. package/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
  699. package/docs/content/locales/hi-IN/template-plan.mdx +23 -7
  700. package/docs/content/locales/hi-IN/tracking.mdx +1 -1
  701. package/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
  702. package/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
  703. package/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
  704. package/docs/content/locales/ja-JP/sharing.mdx +1 -2
  705. package/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
  706. package/docs/content/locales/ja-JP/template-plan.mdx +23 -7
  707. package/docs/content/locales/ja-JP/tracking.mdx +1 -1
  708. package/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
  709. package/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
  710. package/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
  711. package/docs/content/locales/ko-KR/sharing.mdx +1 -2
  712. package/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
  713. package/docs/content/locales/ko-KR/template-plan.mdx +23 -7
  714. package/docs/content/locales/ko-KR/tracking.mdx +1 -1
  715. package/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
  716. package/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
  717. package/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
  718. package/docs/content/locales/pt-BR/sharing.mdx +1 -2
  719. package/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
  720. package/docs/content/locales/pt-BR/template-plan.mdx +23 -7
  721. package/docs/content/locales/pt-BR/tracking.mdx +1 -1
  722. package/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
  723. package/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
  724. package/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
  725. package/docs/content/locales/zh-CN/sharing.mdx +1 -2
  726. package/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
  727. package/docs/content/locales/zh-CN/template-plan.mdx +23 -7
  728. package/docs/content/locales/zh-CN/tracking.mdx +1 -1
  729. package/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
  730. package/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
  731. package/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
  732. package/docs/content/locales/zh-TW/sharing.mdx +1 -2
  733. package/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
  734. package/docs/content/locales/zh-TW/template-plan.mdx +23 -7
  735. package/docs/content/locales/zh-TW/tracking.mdx +1 -1
  736. package/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
  737. package/docs/content/plan-plugin.mdx +12 -11
  738. package/docs/content/sharing.mdx +1 -2
  739. package/docs/content/skills-guide.mdx +4 -3
  740. package/docs/content/template-plan.mdx +37 -6
  741. package/docs/content/tracking.mdx +1 -1
  742. package/docs/content/workspace-connections.mdx +1 -1
  743. package/package.json +6 -6
  744. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  745. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  746. package/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  747. package/src/templates/default/package.json +3 -3
  748. package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  749. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  750. package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  751. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  752. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  753. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  754. package/corpus/core/docs/content/locales/ar-SA/template-videos.mdx +0 -336
  755. package/corpus/core/docs/content/locales/de-DE/template-videos.mdx +0 -336
  756. package/corpus/core/docs/content/locales/es-ES/template-videos.mdx +0 -336
  757. package/corpus/core/docs/content/locales/fr-FR/template-videos.mdx +0 -336
  758. package/corpus/core/docs/content/locales/hi-IN/template-videos.mdx +0 -336
  759. package/corpus/core/docs/content/locales/ja-JP/template-videos.mdx +0 -336
  760. package/corpus/core/docs/content/locales/ko-KR/template-videos.mdx +0 -336
  761. package/corpus/core/docs/content/locales/pt-BR/template-videos.mdx +0 -336
  762. package/corpus/core/docs/content/locales/zh-CN/template-videos.mdx +0 -334
  763. package/corpus/core/docs/content/locales/zh-TW/template-videos.mdx +0 -334
  764. package/corpus/core/docs/content/template-videos.mdx +0 -332
  765. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +0 -697
  766. package/corpus/templates/slides/app/components/editor/CodeEditor.tsx +0 -49
  767. package/corpus/templates/videos/.agents/skills/actions/SKILL.md +0 -432
  768. package/corpus/templates/videos/.agents/skills/animation-tracks/SKILL.md +0 -99
  769. package/corpus/templates/videos/.agents/skills/capture-learnings/SKILL.md +0 -76
  770. package/corpus/templates/videos/.agents/skills/composition-management/SKILL.md +0 -106
  771. package/corpus/templates/videos/.agents/skills/create-skill/SKILL.md +0 -221
  772. package/corpus/templates/videos/.agents/skills/delegate-to-agent/SKILL.md +0 -263
  773. package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +0 -112
  774. package/corpus/templates/videos/.agents/skills/security/SKILL.md +0 -263
  775. package/corpus/templates/videos/.agents/skills/self-modifying-code/SKILL.md +0 -88
  776. package/corpus/templates/videos/.agents/skills/shadcn-ui/SKILL.md +0 -95
  777. package/corpus/templates/videos/.builder/ANIMATED_PROPERTIES.md +0 -335
  778. package/corpus/templates/videos/.builder/KEYFRAME_SYNC.md +0 -166
  779. package/corpus/templates/videos/.builder/SEAMLESS_SYNC.md +0 -211
  780. package/corpus/templates/videos/.builder/rules/deploy-app.mdc +0 -22
  781. package/corpus/templates/videos/.builder/rules/organize-ui.mdc +0 -9
  782. package/corpus/templates/videos/.builder/skills/remotion/SKILL.md +0 -29
  783. package/corpus/templates/videos/.builder/skills/remotion/rules/animations.md +0 -27
  784. package/corpus/templates/videos/.builder/skills/remotion/rules/assets.md +0 -43
  785. package/corpus/templates/videos/.builder/skills/remotion/rules/audio.md +0 -58
  786. package/corpus/templates/videos/.builder/skills/remotion/rules/compositions.md +0 -129
  787. package/corpus/templates/videos/.builder/skills/remotion/rules/fonts.md +0 -47
  788. package/corpus/templates/videos/.builder/skills/remotion/rules/images.md +0 -41
  789. package/corpus/templates/videos/.builder/skills/remotion/rules/parameters.md +0 -59
  790. package/corpus/templates/videos/.builder/skills/remotion/rules/sequencing.md +0 -54
  791. package/corpus/templates/videos/.builder/skills/remotion/rules/tailwind.md +0 -11
  792. package/corpus/templates/videos/.builder/skills/remotion/rules/text-animations.md +0 -18
  793. package/corpus/templates/videos/.builder/skills/remotion/rules/timing.md +0 -103
  794. package/corpus/templates/videos/.builder/skills/remotion/rules/transparent-videos.md +0 -35
  795. package/corpus/templates/videos/.builder/skills/remotion/rules/trimming.md +0 -38
  796. package/corpus/templates/videos/.builder/skills/remotion/rules/videos.md +0 -54
  797. package/corpus/templates/videos/.dockerignore +0 -62
  798. package/corpus/templates/videos/.env.example +0 -4
  799. package/corpus/templates/videos/.oxfmtrc.json +0 -8
  800. package/corpus/templates/videos/AGENTS.md +0 -46
  801. package/corpus/templates/videos/CHANGELOG.md +0 -10
  802. package/corpus/templates/videos/DEVELOPING.md +0 -143
  803. package/corpus/templates/videos/README.md +0 -33
  804. package/corpus/templates/videos/actions/analyze-brand-assets.ts +0 -151
  805. package/corpus/templates/videos/actions/apply-design-system.ts +0 -35
  806. package/corpus/templates/videos/actions/create-design-system.ts +0 -84
  807. package/corpus/templates/videos/actions/create-folder.ts +0 -48
  808. package/corpus/templates/videos/actions/db-connect.ts +0 -45
  809. package/corpus/templates/videos/actions/db-health.ts +0 -24
  810. package/corpus/templates/videos/actions/db-status.ts +0 -28
  811. package/corpus/templates/videos/actions/delete-composition.ts +0 -272
  812. package/corpus/templates/videos/actions/delete-folder.ts +0 -27
  813. package/corpus/templates/videos/actions/generate-animated-component.ts +0 -470
  814. package/corpus/templates/videos/actions/get-composition.ts +0 -36
  815. package/corpus/templates/videos/actions/get-design-system.ts +0 -35
  816. package/corpus/templates/videos/actions/import-code.ts +0 -67
  817. package/corpus/templates/videos/actions/import-design-project.ts +0 -42
  818. package/corpus/templates/videos/actions/import-document.ts +0 -125
  819. package/corpus/templates/videos/actions/import-from-url.ts +0 -18
  820. package/corpus/templates/videos/actions/import-github.ts +0 -182
  821. package/corpus/templates/videos/actions/list-compositions.ts +0 -32
  822. package/corpus/templates/videos/actions/list-design-systems.ts +0 -66
  823. package/corpus/templates/videos/actions/list-folders.ts +0 -61
  824. package/corpus/templates/videos/actions/move-composition-to-folder.ts +0 -59
  825. package/corpus/templates/videos/actions/navigate.ts +0 -40
  826. package/corpus/templates/videos/actions/rename-folder.ts +0 -41
  827. package/corpus/templates/videos/actions/run.ts +0 -2
  828. package/corpus/templates/videos/actions/save-composition.ts +0 -114
  829. package/corpus/templates/videos/actions/set-default-design-system.ts +0 -40
  830. package/corpus/templates/videos/actions/update-composition.ts +0 -81
  831. package/corpus/templates/videos/actions/update-design-system.ts +0 -68
  832. package/corpus/templates/videos/actions/validate-compositions.ts +0 -502
  833. package/corpus/templates/videos/actions/view-screen.ts +0 -48
  834. package/corpus/templates/videos/app/components/CameraControls.tsx +0 -442
  835. package/corpus/templates/videos/app/components/CameraToolbar.tsx +0 -437
  836. package/corpus/templates/videos/app/components/CloudUpgrade.tsx +0 -287
  837. package/corpus/templates/videos/app/components/CollabPresenceBar.tsx +0 -81
  838. package/corpus/templates/videos/app/components/CompSettingsEditor.tsx +0 -107
  839. package/corpus/templates/videos/app/components/ComponentLibrarySidebar.tsx +0 -457
  840. package/corpus/templates/videos/app/components/ComponentPropsPanel.tsx +0 -227
  841. package/corpus/templates/videos/app/components/ComponentsSidebar.tsx +0 -70
  842. package/corpus/templates/videos/app/components/CompositionCard.tsx +0 -147
  843. package/corpus/templates/videos/app/components/CurrentElementPanel.tsx +0 -835
  844. package/corpus/templates/videos/app/components/CursorControls.tsx +0 -664
  845. package/corpus/templates/videos/app/components/CursorPositioningOverlay.tsx +0 -238
  846. package/corpus/templates/videos/app/components/LibraryFolderRow.tsx +0 -235
  847. package/corpus/templates/videos/app/components/MotionCurveSelect.tsx +0 -61
  848. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +0 -192
  849. package/corpus/templates/videos/app/components/PropsEditor.tsx +0 -210
  850. package/corpus/templates/videos/app/components/QuestionFlow.tsx +0 -40
  851. package/corpus/templates/videos/app/components/Sidebar.tsx +0 -609
  852. package/corpus/templates/videos/app/components/StudioHeader.tsx +0 -109
  853. package/corpus/templates/videos/app/components/Timeline.tsx +0 -2067
  854. package/corpus/templates/videos/app/components/TrackPropertiesPanel.tsx +0 -922
  855. package/corpus/templates/videos/app/components/TweaksPanel.tsx +0 -277
  856. package/corpus/templates/videos/app/components/VideoPlayer.tsx +0 -487
  857. package/corpus/templates/videos/app/components/design-system/DesignSystemCard.tsx +0 -134
  858. package/corpus/templates/videos/app/components/design-system/DesignSystemPreview.tsx +0 -176
  859. package/corpus/templates/videos/app/components/design-system/DesignSystemSetup.tsx +0 -707
  860. package/corpus/templates/videos/app/components/keyframes/KeyframeActionButtons.tsx +0 -78
  861. package/corpus/templates/videos/app/components/keyframes/KeyframeNavigation.tsx +0 -59
  862. package/corpus/templates/videos/app/components/keyframes/PropertyNumberInput.tsx +0 -56
  863. package/corpus/templates/videos/app/components/layout/Header.tsx +0 -64
  864. package/corpus/templates/videos/app/components/layout/HeaderActions.tsx +0 -84
  865. package/corpus/templates/videos/app/components/layout/Layout.tsx +0 -132
  866. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -180
  867. package/corpus/templates/videos/app/components/ui/accordion.tsx +0 -56
  868. package/corpus/templates/videos/app/components/ui/alert-dialog.tsx +0 -139
  869. package/corpus/templates/videos/app/components/ui/alert.tsx +0 -59
  870. package/corpus/templates/videos/app/components/ui/aspect-ratio.tsx +0 -5
  871. package/corpus/templates/videos/app/components/ui/avatar.tsx +0 -48
  872. package/corpus/templates/videos/app/components/ui/badge.tsx +0 -37
  873. package/corpus/templates/videos/app/components/ui/breadcrumb.tsx +0 -115
  874. package/corpus/templates/videos/app/components/ui/button.tsx +0 -57
  875. package/corpus/templates/videos/app/components/ui/calendar.tsx +0 -68
  876. package/corpus/templates/videos/app/components/ui/card.tsx +0 -86
  877. package/corpus/templates/videos/app/components/ui/carousel.tsx +0 -260
  878. package/corpus/templates/videos/app/components/ui/chart.tsx +0 -375
  879. package/corpus/templates/videos/app/components/ui/checkbox.tsx +0 -28
  880. package/corpus/templates/videos/app/components/ui/collapsible.tsx +0 -9
  881. package/corpus/templates/videos/app/components/ui/command.tsx +0 -154
  882. package/corpus/templates/videos/app/components/ui/context-menu.tsx +0 -202
  883. package/corpus/templates/videos/app/components/ui/dialog.tsx +0 -130
  884. package/corpus/templates/videos/app/components/ui/drawer.tsx +0 -116
  885. package/corpus/templates/videos/app/components/ui/dropdown-menu.tsx +0 -212
  886. package/corpus/templates/videos/app/components/ui/form.tsx +0 -177
  887. package/corpus/templates/videos/app/components/ui/hover-card.tsx +0 -27
  888. package/corpus/templates/videos/app/components/ui/input-otp.tsx +0 -69
  889. package/corpus/templates/videos/app/components/ui/input.tsx +0 -22
  890. package/corpus/templates/videos/app/components/ui/label.tsx +0 -24
  891. package/corpus/templates/videos/app/components/ui/menubar.tsx +0 -234
  892. package/corpus/templates/videos/app/components/ui/navigation-menu.tsx +0 -128
  893. package/corpus/templates/videos/app/components/ui/pagination.tsx +0 -121
  894. package/corpus/templates/videos/app/components/ui/popover.tsx +0 -44
  895. package/corpus/templates/videos/app/components/ui/progress.tsx +0 -26
  896. package/corpus/templates/videos/app/components/ui/radio-group.tsx +0 -42
  897. package/corpus/templates/videos/app/components/ui/resizable.tsx +0 -43
  898. package/corpus/templates/videos/app/components/ui/scroll-area.tsx +0 -46
  899. package/corpus/templates/videos/app/components/ui/select.tsx +0 -158
  900. package/corpus/templates/videos/app/components/ui/separator.tsx +0 -29
  901. package/corpus/templates/videos/app/components/ui/sheet.tsx +0 -160
  902. package/corpus/templates/videos/app/components/ui/sidebar.tsx +0 -782
  903. package/corpus/templates/videos/app/components/ui/skeleton.tsx +0 -15
  904. package/corpus/templates/videos/app/components/ui/slider.tsx +0 -26
  905. package/corpus/templates/videos/app/components/ui/sonner.tsx +0 -58
  906. package/corpus/templates/videos/app/components/ui/spinner.tsx +0 -17
  907. package/corpus/templates/videos/app/components/ui/switch.tsx +0 -27
  908. package/corpus/templates/videos/app/components/ui/table.tsx +0 -117
  909. package/corpus/templates/videos/app/components/ui/tabs.tsx +0 -53
  910. package/corpus/templates/videos/app/components/ui/textarea.tsx +0 -23
  911. package/corpus/templates/videos/app/components/ui/toast.tsx +0 -127
  912. package/corpus/templates/videos/app/components/ui/toaster.tsx +0 -33
  913. package/corpus/templates/videos/app/components/ui/toggle-group.tsx +0 -59
  914. package/corpus/templates/videos/app/components/ui/toggle.tsx +0 -43
  915. package/corpus/templates/videos/app/components/ui/tooltip.tsx +0 -30
  916. package/corpus/templates/videos/app/components/ui/use-toast.ts +0 -3
  917. package/corpus/templates/videos/app/config/constants.ts +0 -204
  918. package/corpus/templates/videos/app/contexts/CompositionContext.tsx +0 -437
  919. package/corpus/templates/videos/app/contexts/CurrentElementContext.tsx +0 -247
  920. package/corpus/templates/videos/app/contexts/PlaybackContext.tsx +0 -65
  921. package/corpus/templates/videos/app/contexts/TimelineContext.tsx +0 -433
  922. package/corpus/templates/videos/app/entry.client.tsx +0 -19
  923. package/corpus/templates/videos/app/entry.server.tsx +0 -10
  924. package/corpus/templates/videos/app/global.css +0 -171
  925. package/corpus/templates/videos/app/hooks/use-composition-collab.ts +0 -269
  926. package/corpus/templates/videos/app/hooks/use-composition-design-system.ts +0 -47
  927. package/corpus/templates/videos/app/hooks/use-database-compositions.ts +0 -144
  928. package/corpus/templates/videos/app/hooks/use-db-status.ts +0 -30
  929. package/corpus/templates/videos/app/hooks/use-design-systems.ts +0 -21
  930. package/corpus/templates/videos/app/hooks/use-folders.ts +0 -199
  931. package/corpus/templates/videos/app/hooks/use-mobile.tsx +0 -21
  932. package/corpus/templates/videos/app/hooks/use-navigation-state.ts +0 -52
  933. package/corpus/templates/videos/app/hooks/use-question-flow.ts +0 -14
  934. package/corpus/templates/videos/app/hooks/use-toast.ts +0 -188
  935. package/corpus/templates/videos/app/hooks/useUnsavedChanges.ts +0 -48
  936. package/corpus/templates/videos/app/i18n/ar-SA.ts +0 -446
  937. package/corpus/templates/videos/app/i18n/de-DE.ts +0 -470
  938. package/corpus/templates/videos/app/i18n/en-US.ts +0 -452
  939. package/corpus/templates/videos/app/i18n/es-ES.ts +0 -468
  940. package/corpus/templates/videos/app/i18n/fr-FR.ts +0 -469
  941. package/corpus/templates/videos/app/i18n/hi-IN.ts +0 -446
  942. package/corpus/templates/videos/app/i18n/index.ts +0 -34
  943. package/corpus/templates/videos/app/i18n/ja-JP.ts +0 -460
  944. package/corpus/templates/videos/app/i18n/ko-KR.ts +0 -453
  945. package/corpus/templates/videos/app/i18n/pt-BR.ts +0 -461
  946. package/corpus/templates/videos/app/i18n/zh-CN.ts +0 -437
  947. package/corpus/templates/videos/app/i18n/zh-TW.ts +0 -437
  948. package/corpus/templates/videos/app/lib/database-compositions.ts +0 -67
  949. package/corpus/templates/videos/app/lib/design-systems.ts +0 -234
  950. package/corpus/templates/videos/app/lib/tab-id.ts +0 -1
  951. package/corpus/templates/videos/app/lib/utils.ts +0 -1
  952. package/corpus/templates/videos/app/pages/ComponentLibrary.tsx +0 -134
  953. package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +0 -362
  954. package/corpus/templates/videos/app/pages/CompositionView.tsx +0 -715
  955. package/corpus/templates/videos/app/pages/DesignSystems.tsx +0 -180
  956. package/corpus/templates/videos/app/pages/Index.tsx +0 -258
  957. package/corpus/templates/videos/app/pages/NewComposition.tsx +0 -95
  958. package/corpus/templates/videos/app/pages/NotFound.tsx +0 -36
  959. package/corpus/templates/videos/app/remotion/CameraHost.tsx +0 -223
  960. package/corpus/templates/videos/app/remotion/animations/useElementAnimations.ts +0 -231
  961. package/corpus/templates/videos/app/remotion/componentRegistry.ts +0 -473
  962. package/corpus/templates/videos/app/remotion/components/AnimatedElement.tsx +0 -436
  963. package/corpus/templates/videos/app/remotion/components/InteractiveElement.tsx +0 -159
  964. package/corpus/templates/videos/app/remotion/components/InteractiveZoneEditor.tsx +0 -273
  965. package/corpus/templates/videos/app/remotion/compositions/BlankComposition.tsx +0 -204
  966. package/corpus/templates/videos/app/remotion/compositions/index.ts +0 -2
  967. package/corpus/templates/videos/app/remotion/createComponentCursorTrack.ts +0 -132
  968. package/corpus/templates/videos/app/remotion/docs/CURSOR_TRACKS.md +0 -285
  969. package/corpus/templates/videos/app/remotion/easingFunctions.ts +0 -255
  970. package/corpus/templates/videos/app/remotion/hooks/createInteractiveComposition.tsx +0 -283
  971. package/corpus/templates/videos/app/remotion/hooks/useCursorHistory.ts +0 -72
  972. package/corpus/templates/videos/app/remotion/hooks/useCursorTypeFromHover.ts +0 -35
  973. package/corpus/templates/videos/app/remotion/hooks/useEditableZones.tsx +0 -267
  974. package/corpus/templates/videos/app/remotion/hooks/useHoverAnimation.ts +0 -90
  975. package/corpus/templates/videos/app/remotion/hooks/useHoverAnimationSmooth.ts +0 -146
  976. package/corpus/templates/videos/app/remotion/hooks/useInteractiveComponent.ts +0 -711
  977. package/corpus/templates/videos/app/remotion/hooks/useRegisterInteractiveElement.ts +0 -56
  978. package/corpus/templates/videos/app/remotion/library-components/Button.tsx +0 -252
  979. package/corpus/templates/videos/app/remotion/library-components/Card.tsx +0 -241
  980. package/corpus/templates/videos/app/remotion/library-components/CodePanel.tsx +0 -1005
  981. package/corpus/templates/videos/app/remotion/library-components/FileItem.tsx +0 -196
  982. package/corpus/templates/videos/app/remotion/library-components/FolderItem.tsx +0 -199
  983. package/corpus/templates/videos/app/remotion/library-components/PrimaryButton.tsx +0 -177
  984. package/corpus/templates/videos/app/remotion/library-components/SecondaryButton.tsx +0 -167
  985. package/corpus/templates/videos/app/remotion/library-components/SectionHeader.tsx +0 -108
  986. package/corpus/templates/videos/app/remotion/library-components/TEMPLATE.tsx +0 -211
  987. package/corpus/templates/videos/app/remotion/library-components/index.ts +0 -10
  988. package/corpus/templates/videos/app/remotion/registry.ts +0 -91
  989. package/corpus/templates/videos/app/remotion/testing/animationTestUtils.ts +0 -366
  990. package/corpus/templates/videos/app/remotion/trackAnimation.ts +0 -179
  991. package/corpus/templates/videos/app/remotion/trackHelpers.ts +0 -110
  992. package/corpus/templates/videos/app/remotion/ui-components/Cursor.tsx +0 -182
  993. package/corpus/templates/videos/app/remotion/ui-components/InteractiveButton.tsx +0 -116
  994. package/corpus/templates/videos/app/remotion/ui-components/InteractiveCard.tsx +0 -126
  995. package/corpus/templates/videos/app/remotion/ui-components/index.ts +0 -16
  996. package/corpus/templates/videos/app/remotion/utils/animationHelpers.ts +0 -444
  997. package/corpus/templates/videos/app/remotion/utils/interactiveElements.ts +0 -235
  998. package/corpus/templates/videos/app/remotion/utils/springAnimation.ts +0 -76
  999. package/corpus/templates/videos/app/root.tsx +0 -165
  1000. package/corpus/templates/videos/app/routes/$.tsx +0 -10
  1001. package/corpus/templates/videos/app/routes/_index.tsx +0 -25
  1002. package/corpus/templates/videos/app/routes/c.$compositionId.tsx +0 -10
  1003. package/corpus/templates/videos/app/routes/components.tsx +0 -10
  1004. package/corpus/templates/videos/app/routes/design-systems.tsx +0 -10
  1005. package/corpus/templates/videos/app/routes/extensions.$id.$slug.tsx +0 -2
  1006. package/corpus/templates/videos/app/routes/extensions.$id.tsx +0 -5
  1007. package/corpus/templates/videos/app/routes/extensions._index.tsx +0 -5
  1008. package/corpus/templates/videos/app/routes/extensions.tsx +0 -5
  1009. package/corpus/templates/videos/app/routes/settings.tsx +0 -88
  1010. package/corpus/templates/videos/app/routes/team.tsx +0 -11
  1011. package/corpus/templates/videos/app/routes.ts +0 -4
  1012. package/corpus/templates/videos/app/state.ts +0 -77
  1013. package/corpus/templates/videos/app/types/elementAnimations.ts +0 -121
  1014. package/corpus/templates/videos/app/types.ts +0 -200
  1015. package/corpus/templates/videos/app/utils/compositionHelpers.ts +0 -445
  1016. package/corpus/templates/videos/app/utils/debug.ts +0 -63
  1017. package/corpus/templates/videos/app/utils/keyframeUtils.ts +0 -331
  1018. package/corpus/templates/videos/app/utils/timelineCoordinates.ts +0 -79
  1019. package/corpus/templates/videos/app/vite-env.d.ts +0 -6
  1020. package/corpus/templates/videos/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +0 -6
  1021. package/corpus/templates/videos/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +0 -6
  1022. package/corpus/templates/videos/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +0 -6
  1023. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +0 -6
  1024. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +0 -6
  1025. package/corpus/templates/videos/components.json +0 -20
  1026. package/corpus/templates/videos/data/sync-config.json +0 -3
  1027. package/corpus/templates/videos/docs/examples/create-composition.example.ts +0 -128
  1028. package/corpus/templates/videos/drizzle.config.ts +0 -3
  1029. package/corpus/templates/videos/learnings.defaults.md +0 -5
  1030. package/corpus/templates/videos/package.json +0 -114
  1031. package/corpus/templates/videos/public/agent-native-icon-dark.svg +0 -10
  1032. package/corpus/templates/videos/public/agent-native-icon-light.svg +0 -10
  1033. package/corpus/templates/videos/public/agent-native-logo-dark.svg +0 -21
  1034. package/corpus/templates/videos/public/agent-native-logo-light.svg +0 -21
  1035. package/corpus/templates/videos/public/favicon.svg +0 -1
  1036. package/corpus/templates/videos/public/icon-180.svg +0 -1
  1037. package/corpus/templates/videos/public/icon-192.svg +0 -1
  1038. package/corpus/templates/videos/public/icon-512.svg +0 -1
  1039. package/corpus/templates/videos/public/manifest.json +0 -21
  1040. package/corpus/templates/videos/public/placeholder.svg +0 -1
  1041. package/corpus/templates/videos/public/robots.txt +0 -14
  1042. package/corpus/templates/videos/react-router.config.ts +0 -7
  1043. package/corpus/templates/videos/server/db/index.ts +0 -37
  1044. package/corpus/templates/videos/server/db/schema.ts +0 -55
  1045. package/corpus/templates/videos/server/middleware/auth.ts +0 -15
  1046. package/corpus/templates/videos/server/plugins/agent-chat.ts +0 -29
  1047. package/corpus/templates/videos/server/plugins/auth.ts +0 -14
  1048. package/corpus/templates/videos/server/plugins/collab.ts +0 -11
  1049. package/corpus/templates/videos/server/plugins/core-routes.ts +0 -22
  1050. package/corpus/templates/videos/server/plugins/db.ts +0 -160
  1051. package/corpus/templates/videos/server/routes/[...page].get.ts +0 -5
  1052. package/corpus/templates/videos/server/routes/[...page].head.ts +0 -1
  1053. package/corpus/templates/videos/server/routes/api/save-composition-defaults.post.ts +0 -274
  1054. package/corpus/templates/videos/shared/api.ts +0 -75
  1055. package/corpus/templates/videos/ssr-entry.ts +0 -15
  1056. package/corpus/templates/videos/tsconfig.json +0 -21
  1057. package/corpus/templates/videos/vite.config.ts +0 -19
  1058. package/corpus/templates/videos/vitest.config.ts +0 -16
  1059. package/docs/content/locales/ar-SA/template-videos.mdx +0 -336
  1060. package/docs/content/locales/de-DE/template-videos.mdx +0 -336
  1061. package/docs/content/locales/es-ES/template-videos.mdx +0 -336
  1062. package/docs/content/locales/fr-FR/template-videos.mdx +0 -336
  1063. package/docs/content/locales/hi-IN/template-videos.mdx +0 -336
  1064. package/docs/content/locales/ja-JP/template-videos.mdx +0 -336
  1065. package/docs/content/locales/ko-KR/template-videos.mdx +0 -336
  1066. package/docs/content/locales/pt-BR/template-videos.mdx +0 -336
  1067. package/docs/content/locales/zh-CN/template-videos.mdx +0 -334
  1068. package/docs/content/locales/zh-TW/template-videos.mdx +0 -334
  1069. package/docs/content/template-videos.mdx +0 -332
@@ -1,7 +1,11 @@
1
1
  import {
2
+ agentNativePath,
2
3
  appBasePath,
3
4
  callAction,
5
+ createLocalOpUndoController,
4
6
  isEmbedAuthActive,
7
+ type LocalOpUndoController,
8
+ type LocalOpUndoEntry,
5
9
  } from "@agent-native/core/client";
6
10
  import { nanoid } from "nanoid";
7
11
  import {
@@ -28,7 +32,7 @@ type GranularOp =
28
32
  slideId: string;
29
33
  fields: Partial<Omit<Slide, "id">>;
30
34
  }
31
- | { op: "delete-slide"; slideId: string }
35
+ | { op: "delete-slide"; slideId: string; allowEmpty?: boolean }
32
36
  | { op: "reorder-slides"; orderedIds: string[] }
33
37
  | {
34
38
  op: "add-slide";
@@ -50,7 +54,19 @@ type GranularOp =
50
54
  /** Sentinel: discard all accumulated ops and do a full PUT instead. */
51
55
  | { op: "full-replace"; deck: Deck };
52
56
 
53
- type PatchDeckOp = Exclude<GranularOp, { op: "full-replace" }>;
57
+ export type PatchDeckOp = Exclude<GranularOp, { op: "full-replace" }>;
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Inverse-op undo
61
+ // ---------------------------------------------------------------------------
62
+ // Undo/redo is per-user and is granular for ordinary slide/deck-field edits.
63
+ // Deck lifecycle and generated/imported full replacements use explicit
64
+ // deck-level ops because those user actions are whole-resource mutations.
65
+ export type DeckUndoOp =
66
+ | ({ deckId: string } & PatchDeckOp)
67
+ | { op: "delete-deck"; deckId: string }
68
+ | { op: "restore-deck"; deckId: string; deck: Deck; index?: number }
69
+ | { op: "replace-deck"; deckId: string; deck: Deck };
54
70
 
55
71
  export type SlideLayout =
56
72
  | "title"
@@ -115,12 +131,6 @@ export interface Deck {
115
131
  aspectRatio?: AspectRatio;
116
132
  }
117
133
 
118
- export interface HistoryEntry {
119
- timestamp: number;
120
- label: string;
121
- decks: Deck[];
122
- }
123
-
124
134
  interface DeckContextType {
125
135
  decks: Deck[];
126
136
  loading: boolean;
@@ -170,19 +180,15 @@ interface DeckContextType {
170
180
  * in-progress edit.
171
181
  */
172
182
  markDeckDirty: (deckId: string) => void;
173
- // Undo/Redo
183
+ // Undo/Redo — per-user inverse-op undo (see DeckUndoOp above).
174
184
  undo: () => void;
175
185
  redo: () => void;
176
186
  canUndo: boolean;
177
187
  canRedo: boolean;
178
- history: HistoryEntry[];
179
- historyIndex: number;
180
- restoreFromHistory: (index: number) => void;
181
188
  }
182
189
 
183
190
  const DeckContext = createContext<DeckContextType | null>(null);
184
191
 
185
- const MAX_HISTORY = 50;
186
192
  const OPEN_DECK_FALLBACK_POLL_MS = 5_000;
187
193
  const DECK_LIST_FALLBACK_POLL_MS = 15_000;
188
194
 
@@ -268,12 +274,15 @@ export function getSaveSnapshot(): { saving: boolean } {
268
274
  *
269
275
  * When a `full-replace` op is enqueued, all previously-queued ops for that
270
276
  * deck are discarded because the full replace already captures the authoritative
271
- * state (used by undo/redo and bulk generation which produce a known good
272
- * snapshot).
277
+ * state at that moment (used by undo/redo and bulk generation which produce a
278
+ * known good snapshot). Later granular edits inside the same debounce window
279
+ * must still be appended after that snapshot so quick follow-up user edits are
280
+ * not dropped on reload.
273
281
  *
274
282
  * The debounce fires after 500 ms of quiet, draining the queue via the
275
- * granular `patch-deck` action. If the queue contains a `full-replace` op,
276
- * a direct PUT to `/api/decks/:id` is used instead (backwards-compatible).
283
+ * granular `patch-deck` action. If the queue starts with a `full-replace` op,
284
+ * a direct PUT to `/api/decks/:id` is used first, then any trailing granular
285
+ * ops are sent through `patch-deck`.
277
286
  */
278
287
  function enqueueDeckOp(deckId: string, op: GranularOp) {
279
288
  // Clear any pending save timer — we're about to reset it
@@ -285,17 +294,8 @@ function enqueueDeckOp(deckId: string, op: GranularOp) {
285
294
  pendingOpsQueue.set(deckId, [op]);
286
295
  } else {
287
296
  const queue = pendingOpsQueue.get(deckId) ?? [];
288
- // If there's already a full-replace queued, leave it alone — it dominates
289
- if (queue.length > 0 && queue[0].op === "full-replace") {
290
- // Replace the stored deck snapshot with the latest state by replacing
291
- // the full-replace op rather than appending more granular ops on top.
292
- // (The op already carries the full deck; newer state comes via the
293
- // dirty-deck save effect which will enqueue a fresh full-replace when
294
- // it runs.)
295
- } else {
296
- queue.push(op);
297
- pendingOpsQueue.set(deckId, queue);
298
- }
297
+ queue.push(op);
298
+ pendingOpsQueue.set(deckId, queue);
299
299
  }
300
300
 
301
301
  // Arm the debounce
@@ -318,6 +318,13 @@ function enqueueDeckOp(deckId: string, op: GranularOp) {
318
318
  headers: { "Content-Type": "application/json" },
319
319
  body: JSON.stringify(deck),
320
320
  });
321
+ const trailingOps = ops.slice(1) as PatchDeckOp[];
322
+ if (trailingOps.length > 0) {
323
+ await callAction("patch-deck", {
324
+ deckId,
325
+ operations: trailingOps,
326
+ });
327
+ }
321
328
  } else {
322
329
  // Granular patch — concurrent-safe
323
330
  await callAction("patch-deck", {
@@ -346,6 +353,291 @@ function saveDeckToAPI(deck: Deck) {
346
353
  enqueueDeckOp(deck.id, { op: "full-replace", deck });
347
354
  }
348
355
 
356
+ /**
357
+ * Synchronously flush every pending (debounced) deck op queue using
358
+ * `fetch(..., { keepalive: true })` so in-flight edits survive a tab
359
+ * close / navigation. Called from a `pagehide` / `visibilitychange(hidden)`
360
+ * handler — without it there is a ~500ms window (the debounce) where the
361
+ * user's most recent edits are only in memory and are lost on tab close.
362
+ *
363
+ * keepalive requests are best-effort and capped (~64KB by the browser), which
364
+ * is fine: granular ops are small, and if a full-replace payload is too large
365
+ * to send keepalive the normal debounce/poll path still catches up on reopen.
366
+ */
367
+ function flushPendingSaves() {
368
+ const actionUrl = `${agentNativePath("/_agent-native/actions")}/patch-deck`;
369
+ for (const [deckId, timer] of pendingSaves) {
370
+ clearTimeout(timer);
371
+ const ops = pendingOpsQueue.get(deckId);
372
+ pendingOpsQueue.delete(deckId);
373
+ if (!ops || ops.length === 0) continue;
374
+ try {
375
+ if (ops[0].op === "full-replace") {
376
+ const deck = ops[0].deck;
377
+ void fetch(`${appBasePath()}/api/decks/${deckId}`, {
378
+ method: "PUT",
379
+ headers: { "Content-Type": "application/json" },
380
+ body: JSON.stringify(deck),
381
+ keepalive: true,
382
+ });
383
+ const trailingOps = ops.slice(1) as PatchDeckOp[];
384
+ if (trailingOps.length === 0) continue;
385
+ void fetch(actionUrl, {
386
+ method: "POST",
387
+ headers: {
388
+ "Content-Type": "application/json",
389
+ "X-Agent-Native-Frontend": "1",
390
+ },
391
+ body: JSON.stringify({
392
+ deckId,
393
+ operations: trailingOps,
394
+ }),
395
+ keepalive: true,
396
+ });
397
+ } else {
398
+ void fetch(actionUrl, {
399
+ method: "POST",
400
+ headers: {
401
+ "Content-Type": "application/json",
402
+ "X-Agent-Native-Frontend": "1",
403
+ },
404
+ body: JSON.stringify({
405
+ deckId,
406
+ operations: ops as PatchDeckOp[],
407
+ }),
408
+ keepalive: true,
409
+ });
410
+ }
411
+ } catch {
412
+ // Best-effort — nothing more we can do as the page is unloading.
413
+ }
414
+ }
415
+ pendingSaves.clear();
416
+ notifySaveListeners();
417
+ }
418
+
419
+ function discardPendingDeckOps(deckId: string) {
420
+ const timer = pendingSaves.get(deckId);
421
+ if (timer) clearTimeout(timer);
422
+ pendingSaves.delete(deckId);
423
+ pendingOpsQueue.delete(deckId);
424
+ notifySaveListeners();
425
+ }
426
+
427
+ // ---------------------------------------------------------------------------
428
+ // Local op application + inverse derivation (for inverse-op undo)
429
+ // ---------------------------------------------------------------------------
430
+ // These mirror the server-side merge in actions/patch-deck.ts but operate on
431
+ // the in-memory Deck[] so undo/redo can apply optimistically. They are pure:
432
+ // they return a new slides array / deck rather than mutating in place.
433
+
434
+ /** Fields carried by a `patch-deck-fields` op. */
435
+ type PatchDeckFields = Extract<
436
+ PatchDeckOp,
437
+ { op: "patch-deck-fields" }
438
+ >["fields"];
439
+
440
+ /**
441
+ * Apply a single granular op to a deck's slides/fields, returning the updated
442
+ * Deck. Unknown/no-op cases (slide already gone, etc.) return the deck
443
+ * unchanged so undo entries that no longer apply fail soft instead of
444
+ * corrupting state.
445
+ */
446
+ export function applyOpToDeck(deck: Deck, op: PatchDeckOp): Deck {
447
+ switch (op.op) {
448
+ case "patch-slide": {
449
+ let changed = false;
450
+ const slides = deck.slides.map((s) => {
451
+ if (s.id !== op.slideId) return s;
452
+ changed = true;
453
+ return { ...s, ...op.fields };
454
+ });
455
+ if (!changed) return deck; // slide concurrently deleted — skip
456
+ return { ...deck, slides, updatedAt: new Date().toISOString() };
457
+ }
458
+ case "delete-slide": {
459
+ const slides = deck.slides.filter((s) => s.id !== op.slideId);
460
+ if (slides.length === deck.slides.length) return deck; // already gone
461
+ // NOTE: unlike the user-facing `deleteSlide` handler and the server merge,
462
+ // undo/redo application does NOT inject a fallback blank slide when the
463
+ // deck empties out. Undo must restore the EXACT prior state — if the deck
464
+ // was legitimately empty before an add-slide (e.g. a freshly reloaded
465
+ // empty deck), undoing that add must return it to empty, not to a
466
+ // spurious blank slide.
467
+ return { ...deck, slides, updatedAt: new Date().toISOString() };
468
+ }
469
+ case "reorder-slides": {
470
+ const byId = new Map(deck.slides.map((s) => [s.id, s]));
471
+ const reordered: Slide[] = [];
472
+ for (const id of op.orderedIds) {
473
+ const slide = byId.get(id);
474
+ if (slide) reordered.push(slide);
475
+ }
476
+ // Preserve slides not named in orderedIds (concurrent adds) at the end.
477
+ const named = new Set(op.orderedIds);
478
+ for (const s of deck.slides) {
479
+ if (!named.has(s.id)) reordered.push(s);
480
+ }
481
+ return {
482
+ ...deck,
483
+ slides: reordered,
484
+ updatedAt: new Date().toISOString(),
485
+ };
486
+ }
487
+ case "add-slide": {
488
+ if (deck.slides.some((s) => s.id === op.slideId)) return deck; // idempotent
489
+ const newSlide: Slide = {
490
+ id: op.slideId,
491
+ content: op.fields.content,
492
+ notes: op.fields.notes ?? "",
493
+ layout: (op.fields.layout as SlideLayout) ?? "content",
494
+ ...(op.fields.background !== undefined
495
+ ? { background: op.fields.background }
496
+ : {}),
497
+ };
498
+ const slides = [...deck.slides];
499
+ const afterIdx = op.afterSlideId
500
+ ? slides.findIndex((s) => s.id === op.afterSlideId)
501
+ : -1;
502
+ if (afterIdx !== -1) slides.splice(afterIdx + 1, 0, newSlide);
503
+ else slides.push(newSlide);
504
+ return { ...deck, slides, updatedAt: new Date().toISOString() };
505
+ }
506
+ case "patch-deck-fields": {
507
+ return {
508
+ ...deck,
509
+ ...op.fields,
510
+ updatedAt: new Date().toISOString(),
511
+ } as Deck;
512
+ }
513
+ }
514
+ }
515
+
516
+ export function applyUndoOpToDecks(decks: Deck[], op: DeckUndoOp): Deck[] {
517
+ switch (op.op) {
518
+ case "delete-deck":
519
+ return decks.filter((deck) => deck.id !== op.deckId);
520
+ case "restore-deck": {
521
+ const nextDeck = op.deck;
522
+ const existingIndex = decks.findIndex((deck) => deck.id === op.deckId);
523
+ if (existingIndex >= 0) {
524
+ const next = [...decks];
525
+ next[existingIndex] = nextDeck;
526
+ return next;
527
+ }
528
+ const next = [...decks];
529
+ const index =
530
+ typeof op.index === "number" && op.index >= 0
531
+ ? Math.min(op.index, next.length)
532
+ : next.length;
533
+ next.splice(index, 0, nextDeck);
534
+ return next;
535
+ }
536
+ case "replace-deck": {
537
+ const existingIndex = decks.findIndex((deck) => deck.id === op.deckId);
538
+ if (existingIndex < 0) return [...decks, op.deck];
539
+ const next = [...decks];
540
+ next[existingIndex] = op.deck;
541
+ return next;
542
+ }
543
+ default: {
544
+ const idx = decks.findIndex((deck) => deck.id === op.deckId);
545
+ if (idx < 0) return decks;
546
+ const { deckId: _deckId, ...granular } = op;
547
+ void _deckId;
548
+ const updated = applyOpToDeck(decks[idx], granular);
549
+ if (updated === decks[idx]) return decks;
550
+ const next = [...decks];
551
+ next[idx] = updated;
552
+ return next;
553
+ }
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Build the inverse of a granular op given the deck state BEFORE the op was
559
+ * applied. Returns an array of ops to apply (usually one, occasionally two) or
560
+ * `null` when the op has no meaningful inverse (e.g. a no-op patch) so the
561
+ * caller skips pushing an undo entry.
562
+ */
563
+ export function deriveInverseOp(
564
+ before: Deck,
565
+ op: PatchDeckOp,
566
+ ): PatchDeckOp[] | null {
567
+ switch (op.op) {
568
+ case "patch-slide": {
569
+ const prior = before.slides.find((s) => s.id === op.slideId);
570
+ if (!prior) return null; // slide didn't exist before — nothing to restore
571
+ const priorFields: Partial<Omit<Slide, "id">> = {};
572
+ for (const key of Object.keys(op.fields) as (keyof Omit<Slide, "id">)[]) {
573
+ // Capture the prior value for every field this op touches, so undo
574
+ // restores exactly what changed (including clearing fields back to
575
+ // undefined).
576
+ (priorFields as Record<string, unknown>)[key] = prior[key];
577
+ }
578
+ return [{ op: "patch-slide", slideId: op.slideId, fields: priorFields }];
579
+ }
580
+ case "delete-slide": {
581
+ const prior = before.slides.find((s) => s.id === op.slideId);
582
+ if (!prior) return null;
583
+ const idx = before.slides.findIndex((s) => s.id === op.slideId);
584
+ const afterSlideId = idx > 0 ? before.slides[idx - 1]?.id : undefined;
585
+ // Re-add the deleted slide with its full prior content, then reorder to
586
+ // the exact prior order. The add-slide op alone can only express "after
587
+ // slide X" or "append", so it cannot restore a slide to the HEAD of the
588
+ // deck; the follow-up reorder guarantees exact position regardless.
589
+ return [
590
+ {
591
+ op: "add-slide",
592
+ slideId: prior.id,
593
+ afterSlideId,
594
+ fields: {
595
+ content: prior.content,
596
+ notes: prior.notes,
597
+ layout: prior.layout,
598
+ ...(prior.background !== undefined
599
+ ? { background: prior.background }
600
+ : {}),
601
+ },
602
+ },
603
+ {
604
+ op: "reorder-slides",
605
+ orderedIds: before.slides.map((s) => s.id),
606
+ },
607
+ ];
608
+ }
609
+ case "add-slide": {
610
+ // Inverse of adding a slide is deleting it.
611
+ return [
612
+ {
613
+ op: "delete-slide",
614
+ slideId: op.slideId,
615
+ ...(before.slides.length === 0 ? { allowEmpty: true } : {}),
616
+ },
617
+ ];
618
+ }
619
+ case "reorder-slides": {
620
+ // Inverse reorder = the order the slides were in before.
621
+ return [
622
+ { op: "reorder-slides", orderedIds: before.slides.map((s) => s.id) },
623
+ ];
624
+ }
625
+ case "patch-deck-fields": {
626
+ const priorFields: Record<string, unknown> = {};
627
+ const beforeRecord = before as unknown as Record<string, unknown>;
628
+ for (const key of Object.keys(op.fields)) {
629
+ priorFields[key] = beforeRecord[key];
630
+ }
631
+ return [
632
+ {
633
+ op: "patch-deck-fields",
634
+ fields: priorFields as PatchDeckFields,
635
+ },
636
+ ];
637
+ }
638
+ }
639
+ }
640
+
349
641
  /**
350
642
  * Fetch the deck list. Returns `null` on any failure (network error, non-2xx
351
643
  * response) so callers can distinguish "authoritative empty list" from
@@ -532,10 +824,14 @@ export function DeckProvider({ children }: { children: ReactNode }) {
532
824
  const [loading, setLoading] = useState(true);
533
825
  const decksRef = useRef<Deck[]>([]);
534
826
 
535
- // History for undo/redo
536
- const [history, setHistory] = useState<HistoryEntry[]>([]);
537
- const [historyIndex, setHistoryIndex] = useState(-1);
538
- const skipHistoryRef = useRef(false);
827
+ // Per-user inverse-op undo/redo. `canUndo`/`canRedo` are React state kept in
828
+ // sync with the controller via its onChange callback. The controller and its
829
+ // apply path are wired below once `decks`/enqueue are in scope.
830
+ const [canUndo, setCanUndo] = useState(false);
831
+ const [canRedo, setCanRedo] = useState(false);
832
+ const undoControllerRef = useRef<LocalOpUndoController<DeckUndoOp> | null>(
833
+ null,
834
+ );
539
835
  // Track when external (SSE) updates happen so the save effect doesn't echo them back
540
836
  const lastExternalUpdateRef = useRef(0);
541
837
  // Track client-created decks that haven't been confirmed on the server yet.
@@ -553,28 +849,103 @@ export function DeckProvider({ children }: { children: ReactNode }) {
553
849
  dirtyDeckIdsRef.current.add(deckId);
554
850
  }, []);
555
851
 
556
- const markChangedDecksDirty = useCallback((nextDecks: Deck[]) => {
557
- const ids = changedDeckIds(decksRef.current, nextDecks);
558
- if (ids.length === 0) return;
559
- lastExternalUpdateRef.current = 0;
560
- for (const id of ids) dirtyDeckIdsRef.current.add(id);
852
+ const deleteDeckAfterPendingCreate = useCallback((deckId: string) => {
853
+ const pendingCreate = pendingCreatePromisesRef.current.get(deckId);
854
+ if (!pendingCreate) {
855
+ void deleteDeckFromAPI(deckId);
856
+ return;
857
+ }
858
+
859
+ void pendingCreate
860
+ .then(() => deleteDeckFromAPI(deckId))
861
+ .catch(() => {
862
+ // The create/duplicate failed, so there is nothing on the server to delete.
863
+ });
864
+ }, []);
865
+
866
+ // Plain local decks update. Undo entries are recorded explicitly by each
867
+ // mutation via `recordUndo` (inverse ops), so this no longer snapshots the
868
+ // whole decks array the way the old `setDecksWithHistory` did.
869
+ const setDecksLocal = useCallback((updater: (prev: Deck[]) => Deck[]) => {
870
+ setDecks(updater);
561
871
  }, []);
562
872
 
563
873
  useEffect(() => {
564
874
  decksRef.current = decks;
565
875
  }, [decks]);
566
876
 
877
+ // ── Inverse-op undo controller ────────────────────────────────────────────
878
+ // Applying an undo/redo entry runs each tagged op through the SAME optimistic
879
+ // local update + granular persist path as a normal edit. Because we only ever
880
+ // send granular ops (never full-replace), undo/redo can never clobber a
881
+ // concurrent edit to a different slide by another human or the agent. Entries
882
+ // that no longer apply (e.g. the slide was deleted remotely) fail soft:
883
+ // applyOpToDeck returns the deck unchanged and the granular server merge
884
+ // ignores the missing target.
885
+ if (!undoControllerRef.current) {
886
+ undoControllerRef.current = createLocalOpUndoController<DeckUndoOp>({
887
+ apply: (ops) => {
888
+ // Apply all ops to local state in one pass, then persist each.
889
+ setDecks((prev) => {
890
+ let next = prev;
891
+ for (const op of ops) {
892
+ next = applyUndoOpToDecks(next, op);
893
+ }
894
+ return next;
895
+ });
896
+ for (const op of ops) {
897
+ markDeckDirty(op.deckId);
898
+ if (op.op === "delete-deck") {
899
+ discardPendingDeckOps(op.deckId);
900
+ deleteDeckAfterPendingCreate(op.deckId);
901
+ } else if (op.op === "restore-deck" || op.op === "replace-deck") {
902
+ enqueueDeckOp(op.deckId, { op: "full-replace", deck: op.deck });
903
+ } else {
904
+ const { deckId, ...granular } = op;
905
+ enqueueDeckOp(deckId, granular);
906
+ }
907
+ }
908
+ },
909
+ onChange: () => {
910
+ const c = undoControllerRef.current;
911
+ setCanUndo(c ? c.canUndo() : false);
912
+ setCanRedo(c ? c.canRedo() : false);
913
+ },
914
+ });
915
+ }
916
+
917
+ /**
918
+ * Record an undo entry for a just-applied local mutation. `before` is the
919
+ * deck state prior to the mutation (for inverse derivation); `redoOp` is the
920
+ * forward op that was applied. Same `coalesceKey` within the controller's
921
+ * window merges bursts (e.g. rapid text edits to one slide).
922
+ */
923
+ const recordUndo = useCallback(
924
+ (
925
+ before: Deck,
926
+ redoOp: PatchDeckOp,
927
+ opts?: { label?: string; coalesceKey?: string },
928
+ ) => {
929
+ const inverseOps = deriveInverseOp(before, redoOp);
930
+ if (!inverseOps || inverseOps.length === 0) return;
931
+ const entry: LocalOpUndoEntry<DeckUndoOp> = {
932
+ undo: inverseOps.map((o) => ({ deckId: before.id, ...o })),
933
+ redo: [{ deckId: before.id, ...redoOp }],
934
+ label: opts?.label,
935
+ coalesceKey: opts?.coalesceKey,
936
+ };
937
+ undoControllerRef.current?.push(entry);
938
+ },
939
+ [],
940
+ );
941
+
567
942
  const resetDeckBaseline = useCallback((nextDecks: Deck[]) => {
568
- skipHistoryRef.current = false;
569
943
  setDecks(nextDecks);
570
- setHistory([
571
- {
572
- timestamp: Date.now(),
573
- label: "Initial state",
574
- decks: JSON.parse(JSON.stringify(nextDecks)),
575
- },
576
- ]);
577
- setHistoryIndex(0);
944
+ // A baseline reset (initial mount, route change, or access reload) starts a
945
+ // fresh undo timeline. Note: this is NOT the SSE/poll "remote update" path —
946
+ // those call setDecks directly and intentionally leave the undo stack
947
+ // intact so a collaborator's edit doesn't wipe your local undo history.
948
+ undoControllerRef.current?.clear();
578
949
  }, []);
579
950
 
580
951
  const reloadDecks = useCallback(async () => {
@@ -816,92 +1187,31 @@ export function DeckProvider({ children }: { children: ReactNode }) {
816
1187
  return () => evtSource.close();
817
1188
  }, []);
818
1189
 
819
- const pushHistory = useCallback(
820
- (label: string, newDecks: Deck[]) => {
821
- setHistory((prev) => {
822
- const truncated = prev.slice(0, historyIndex + 1);
823
- const newHistory = [
824
- ...truncated,
825
- {
826
- timestamp: Date.now(),
827
- label,
828
- decks: JSON.parse(JSON.stringify(newDecks)),
829
- },
830
- ];
831
- if (newHistory.length > MAX_HISTORY) {
832
- newHistory.shift();
833
- return newHistory;
834
- }
835
- return newHistory;
836
- });
837
- setHistoryIndex((prev) => {
838
- const truncatedLen = Math.min(prev + 1, history.length);
839
- return Math.min(truncatedLen, MAX_HISTORY - 1);
840
- });
841
- },
842
- [historyIndex, history.length],
843
- );
844
-
845
- const setDecksWithHistory = useCallback(
846
- (label: string, updater: (prev: Deck[]) => Deck[]) => {
847
- setDecks((prev) => {
848
- const next = updater(prev);
849
- // Push to history after state update
850
- setTimeout(() => {
851
- if (!skipHistoryRef.current) {
852
- pushHistory(label, next);
853
- }
854
- skipHistoryRef.current = false;
855
- }, 0);
856
- return next;
857
- });
858
- },
859
- [pushHistory],
860
- );
1190
+ // Flush pending (debounced) saves before the tab is hidden or unloaded so the
1191
+ // last ~500ms of edits aren't lost on close/navigation. `pagehide` is the
1192
+ // reliable unload signal on modern browsers (incl. bfcache); we also flush on
1193
+ // `visibilitychange(hidden)` which fires on mobile tab-switch / app-background
1194
+ // where `pagehide` may not.
1195
+ useEffect(() => {
1196
+ const onHidden = () => {
1197
+ if (document.visibilityState === "hidden") flushPendingSaves();
1198
+ };
1199
+ const onPageHide = () => flushPendingSaves();
1200
+ document.addEventListener("visibilitychange", onHidden);
1201
+ window.addEventListener("pagehide", onPageHide);
1202
+ return () => {
1203
+ document.removeEventListener("visibilitychange", onHidden);
1204
+ window.removeEventListener("pagehide", onPageHide);
1205
+ };
1206
+ }, []);
861
1207
 
862
1208
  const undo = useCallback(() => {
863
- if (historyIndex <= 0) return;
864
- const newIndex = historyIndex - 1;
865
- const nextDecks = JSON.parse(JSON.stringify(history[newIndex].decks));
866
- // Undo restores a complete known-good snapshot — use full-replace so
867
- // we don't try to infer granular ops from the diff.
868
- for (const deck of nextDecks) {
869
- enqueueDeckOp(deck.id, { op: "full-replace", deck });
870
- }
871
- markChangedDecksDirty(nextDecks);
872
- setHistoryIndex(newIndex);
873
- skipHistoryRef.current = true;
874
- setDecks(nextDecks);
875
- }, [historyIndex, history, markChangedDecksDirty]);
1209
+ void undoControllerRef.current?.undo();
1210
+ }, []);
876
1211
 
877
1212
  const redo = useCallback(() => {
878
- if (historyIndex >= history.length - 1) return;
879
- const newIndex = historyIndex + 1;
880
- const nextDecks = JSON.parse(JSON.stringify(history[newIndex].decks));
881
- // Same as undo — restore from a complete snapshot.
882
- for (const deck of nextDecks) {
883
- enqueueDeckOp(deck.id, { op: "full-replace", deck });
884
- }
885
- markChangedDecksDirty(nextDecks);
886
- setHistoryIndex(newIndex);
887
- skipHistoryRef.current = true;
888
- setDecks(nextDecks);
889
- }, [historyIndex, history, markChangedDecksDirty]);
890
-
891
- const restoreFromHistory = useCallback(
892
- (index: number) => {
893
- if (index < 0 || index >= history.length) return;
894
- const nextDecks = JSON.parse(JSON.stringify(history[index].decks));
895
- for (const deck of nextDecks) {
896
- enqueueDeckOp(deck.id, { op: "full-replace", deck });
897
- }
898
- markChangedDecksDirty(nextDecks);
899
- setHistoryIndex(index);
900
- skipHistoryRef.current = true;
901
- setDecks(nextDecks);
902
- },
903
- [history, markChangedDecksDirty],
904
- );
1213
+ void undoControllerRef.current?.redo();
1214
+ }, []);
905
1215
 
906
1216
  // Keyboard shortcuts for undo/redo
907
1217
  useEffect(() => {
@@ -937,6 +1247,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
937
1247
  title?: string,
938
1248
  options?: { noDefaultSlides?: boolean; designSystemId?: string | null },
939
1249
  ): Deck => {
1250
+ const insertIndex = decksRef.current.length;
940
1251
  const newDeck: Deck = {
941
1252
  id: nanoid(10),
942
1253
  title: title || "Untitled Deck",
@@ -980,10 +1291,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
980
1291
  pendingCreatePromisesRef.current.delete(newDeck.id);
981
1292
  }
982
1293
  });
983
- setDecksWithHistory("Create deck", (prev) => [...prev, newDeck]);
1294
+ setDecksLocal((prev) => [...prev, newDeck]);
1295
+ undoControllerRef.current?.push({
1296
+ undo: [{ op: "delete-deck", deckId: newDeck.id }],
1297
+ redo: [
1298
+ {
1299
+ op: "restore-deck",
1300
+ deckId: newDeck.id,
1301
+ deck: newDeck,
1302
+ index: insertIndex,
1303
+ },
1304
+ ],
1305
+ label: "Create deck",
1306
+ });
984
1307
  return newDeck;
985
1308
  },
986
- [setDecksWithHistory],
1309
+ [setDecksLocal],
987
1310
  );
988
1311
 
989
1312
  const ensureDeckPersisted = useCallback(async (id: string) => {
@@ -1008,6 +1331,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1008
1331
 
1009
1332
  const now = new Date().toISOString();
1010
1333
  const newTitle = title || `Copy of ${source.title}`;
1334
+ const insertIndex = decksRef.current.length;
1011
1335
  // Re-id slides so optimistic edits to the copy don't collide with the
1012
1336
  // original. The server does the same thing — these client ids will be
1013
1337
  // replaced by server-generated ones once the duplicate action lands and
@@ -1035,11 +1359,16 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1035
1359
  pendingDuplicateSourceIdsRef.current.add(sourceDeckId);
1036
1360
 
1037
1361
  // Fire the action in the background. On error, roll back.
1038
- callAction<DuplicateDeckActionResult>("duplicate-deck", {
1039
- deckId: sourceDeckId,
1040
- newId,
1041
- title,
1042
- })
1362
+ const duplicatePromise = callAction<DuplicateDeckActionResult>(
1363
+ "duplicate-deck",
1364
+ {
1365
+ deckId: sourceDeckId,
1366
+ newId,
1367
+ title,
1368
+ },
1369
+ ).then(() => undefined);
1370
+ pendingCreatePromisesRef.current.set(newId, duplicatePromise);
1371
+ duplicatePromise
1043
1372
  .catch((err) => {
1044
1373
  console.error("Duplicate failed:", err);
1045
1374
  // Roll back: drop the optimistic deck from local state.
@@ -1047,23 +1376,55 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1047
1376
  })
1048
1377
  .finally(() => {
1049
1378
  pendingCreateIdsRef.current.delete(newId);
1379
+ if (
1380
+ pendingCreatePromisesRef.current.get(newId) === duplicatePromise
1381
+ ) {
1382
+ pendingCreatePromisesRef.current.delete(newId);
1383
+ }
1050
1384
  pendingDuplicateSourceIdsRef.current.delete(sourceDeckId);
1051
1385
  });
1052
1386
 
1053
- setDecksWithHistory("Duplicate deck", (prev) => [...prev, optimistic]);
1387
+ setDecksLocal((prev) => [...prev, optimistic]);
1388
+ undoControllerRef.current?.push({
1389
+ undo: [{ op: "delete-deck", deckId: optimistic.id }],
1390
+ redo: [
1391
+ {
1392
+ op: "restore-deck",
1393
+ deckId: optimistic.id,
1394
+ deck: optimistic,
1395
+ index: insertIndex,
1396
+ },
1397
+ ],
1398
+ label: "Duplicate deck",
1399
+ });
1054
1400
  return optimistic;
1055
1401
  },
1056
- [decks, setDecksWithHistory],
1402
+ [decks, setDecksLocal],
1057
1403
  );
1058
1404
 
1059
1405
  const deleteDeck = useCallback(
1060
1406
  (id: string) => {
1061
- deleteDeckFromAPI(id);
1062
- setDecksWithHistory("Delete deck", (prev) =>
1063
- prev.filter((d) => d.id !== id),
1064
- );
1407
+ const beforeDeck = decksRef.current.find((deck) => deck.id === id);
1408
+ const beforeIndex = decksRef.current.findIndex((deck) => deck.id === id);
1409
+ discardPendingDeckOps(id);
1410
+ deleteDeckAfterPendingCreate(id);
1411
+ setDecksLocal((prev) => prev.filter((d) => d.id !== id));
1412
+ if (beforeDeck) {
1413
+ undoControllerRef.current?.push({
1414
+ undo: [
1415
+ {
1416
+ op: "restore-deck",
1417
+ deckId: id,
1418
+ deck: beforeDeck,
1419
+ index: beforeIndex,
1420
+ },
1421
+ ],
1422
+ redo: [{ op: "delete-deck", deckId: id }],
1423
+ label: "Delete deck",
1424
+ });
1425
+ }
1065
1426
  },
1066
- [setDecksWithHistory],
1427
+ [deleteDeckAfterPendingCreate, setDecksLocal],
1067
1428
  );
1068
1429
 
1069
1430
  const updateDeck = useCallback(
@@ -1071,6 +1432,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1071
1432
  // Clear the external-update suppression window so a rename/update that
1072
1433
  // happens within 2s of page load (or an SSE event) is not silently dropped.
1073
1434
  markDeckDirty(id);
1435
+ const before = decksRef.current.find((d) => d.id === id);
1074
1436
  setDecks((prev) =>
1075
1437
  prev.map((d) =>
1076
1438
  d.id === id
@@ -1093,13 +1455,24 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1093
1455
  void _slides;
1094
1456
  _ca;
1095
1457
  if (Object.keys(persistableUpdates).length > 0) {
1096
- enqueueDeckOp(id, {
1458
+ const op: PatchDeckOp = {
1097
1459
  op: "patch-deck-fields",
1098
- fields: persistableUpdates,
1099
- });
1460
+ fields: persistableUpdates as PatchDeckFields,
1461
+ };
1462
+ enqueueDeckOp(id, op);
1463
+ if (before) {
1464
+ // Coalesce rapid deck-field edits (e.g. title typing, tweak sliders)
1465
+ // per field-set so a burst becomes one undo step.
1466
+ recordUndo(before, op, {
1467
+ label: "Update deck",
1468
+ coalesceKey: `${id}:deck-fields:${Object.keys(persistableUpdates)
1469
+ .sort()
1470
+ .join(",")}`,
1471
+ });
1472
+ }
1100
1473
  }
1101
1474
  },
1102
- [markDeckDirty],
1475
+ [markDeckDirty, recordUndo],
1103
1476
  );
1104
1477
 
1105
1478
  const getDeck = useCallback(
@@ -1118,8 +1491,9 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1118
1491
  background: "bg-[#000000]",
1119
1492
  };
1120
1493
 
1494
+ const before = decksRef.current.find((d) => d.id === deckId);
1121
1495
  let afterSlideId: string | undefined;
1122
- setDecksWithHistory("Add slide", (prev) =>
1496
+ setDecksLocal((prev) =>
1123
1497
  prev.map((d) => {
1124
1498
  if (d.id !== deckId) return d;
1125
1499
  const slides = [...d.slides];
@@ -1134,7 +1508,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1134
1508
 
1135
1509
  // Granular op — the server splices in only this slide, preserving any
1136
1510
  // concurrent changes to other slides.
1137
- enqueueDeckOp(deckId, {
1511
+ const op: PatchDeckOp = {
1138
1512
  op: "add-slide",
1139
1513
  slideId: newSlide.id,
1140
1514
  afterSlideId,
@@ -1144,11 +1518,13 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1144
1518
  layout: newSlide.layout,
1145
1519
  background: newSlide.background,
1146
1520
  },
1147
- });
1521
+ };
1522
+ enqueueDeckOp(deckId, op);
1523
+ if (before) recordUndo(before, op, { label: "Add slide" });
1148
1524
 
1149
1525
  return newSlide.id;
1150
1526
  },
1151
- [markDeckDirty, setDecksWithHistory],
1527
+ [markDeckDirty, recordUndo, setDecksLocal],
1152
1528
  );
1153
1529
 
1154
1530
  const updateSlide = useCallback(
@@ -1161,7 +1537,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1161
1537
  : updates.content
1162
1538
  ? "Update content"
1163
1539
  : "Edit slide";
1164
- setDecksWithHistory(label, (prev: Deck[]) =>
1540
+ const before = decksRef.current.find((d) => d.id === deckId);
1541
+ setDecksLocal((prev: Deck[]) =>
1165
1542
  prev.map((d) => {
1166
1543
  if (d.id !== deckId) return d;
1167
1544
  return {
@@ -1174,15 +1551,28 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1174
1551
  }),
1175
1552
  );
1176
1553
  // Granular op — only this slide's changed fields reach the server.
1177
- enqueueDeckOp(deckId, { op: "patch-slide", slideId, fields: updates });
1554
+ const op: PatchDeckOp = { op: "patch-slide", slideId, fields: updates };
1555
+ enqueueDeckOp(deckId, op);
1556
+ if (before) {
1557
+ // Coalesce a burst of edits to the SAME slide's SAME field-set into one
1558
+ // undo step (e.g. typing characters into inline text). Distinct
1559
+ // field-sets (content vs background vs layout) get distinct undo steps.
1560
+ recordUndo(before, op, {
1561
+ label,
1562
+ coalesceKey: `${deckId}:${slideId}:${Object.keys(updates)
1563
+ .sort()
1564
+ .join(",")}`,
1565
+ });
1566
+ }
1178
1567
  },
1179
- [markDeckDirty, setDecksWithHistory],
1568
+ [markDeckDirty, recordUndo, setDecksLocal],
1180
1569
  );
1181
1570
 
1182
1571
  const deleteSlide = useCallback(
1183
1572
  (deckId: string, slideId: string) => {
1184
1573
  markDeckDirty(deckId);
1185
- setDecksWithHistory("Delete slide", (prev) =>
1574
+ const before = decksRef.current.find((d) => d.id === deckId);
1575
+ setDecksLocal((prev) =>
1186
1576
  prev.map((d) => {
1187
1577
  if (d.id !== deckId) return d;
1188
1578
  const slides = d.slides.filter((s) => s.id !== slideId);
@@ -1198,16 +1588,22 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1198
1588
  }),
1199
1589
  );
1200
1590
  // Granular op — server deletes only this slide from the blob.
1201
- enqueueDeckOp(deckId, { op: "delete-slide", slideId });
1591
+ const op: PatchDeckOp = { op: "delete-slide", slideId };
1592
+ enqueueDeckOp(deckId, op);
1593
+ // Inverse re-adds the full prior slide at its old position, so undo
1594
+ // restores content/notes/layout/background exactly. (This is the case
1595
+ // behind the "Undo delete" toast in DeckEditor.)
1596
+ if (before) recordUndo(before, op, { label: "Delete slide" });
1202
1597
  },
1203
- [markDeckDirty, setDecksWithHistory],
1598
+ [markDeckDirty, recordUndo, setDecksLocal],
1204
1599
  );
1205
1600
 
1206
1601
  const duplicateSlide = useCallback(
1207
1602
  (deckId: string, slideId: string) => {
1208
1603
  markDeckDirty(deckId);
1604
+ const before = decksRef.current.find((d) => d.id === deckId);
1209
1605
  let copiedSlide: Slide | undefined;
1210
- setDecksWithHistory("Duplicate slide", (prev) =>
1606
+ setDecksLocal((prev) =>
1211
1607
  prev.map((d) => {
1212
1608
  if (d.id !== deckId) return d;
1213
1609
  const idx = d.slides.findIndex((s) => s.id === slideId);
@@ -1223,7 +1619,7 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1223
1619
  if (copiedSlide) {
1224
1620
  // Granular add-slide op — inserts the copy after the original.
1225
1621
  const { id: newSlideId, ...rest } = copiedSlide;
1226
- enqueueDeckOp(deckId, {
1622
+ const op: PatchDeckOp = {
1227
1623
  op: "add-slide",
1228
1624
  slideId: newSlideId,
1229
1625
  afterSlideId: slideId,
@@ -1233,17 +1629,20 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1233
1629
  layout: rest.layout,
1234
1630
  background: rest.background,
1235
1631
  },
1236
- });
1632
+ };
1633
+ enqueueDeckOp(deckId, op);
1634
+ if (before) recordUndo(before, op, { label: "Duplicate slide" });
1237
1635
  }
1238
1636
  },
1239
- [markDeckDirty, setDecksWithHistory],
1637
+ [markDeckDirty, recordUndo, setDecksLocal],
1240
1638
  );
1241
1639
 
1242
1640
  const reorderSlides = useCallback(
1243
1641
  (deckId: string, oldIndex: number, newIndex: number) => {
1244
1642
  markDeckDirty(deckId);
1643
+ const before = decksRef.current.find((d) => d.id === deckId);
1245
1644
  let orderedIds: string[] | undefined;
1246
- setDecksWithHistory("Reorder slides", (prev) =>
1645
+ setDecksLocal((prev) =>
1247
1646
  prev.map((d) => {
1248
1647
  if (d.id !== deckId) return d;
1249
1648
  const slides = [...d.slides];
@@ -1256,28 +1655,45 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1256
1655
  if (orderedIds) {
1257
1656
  // Granular op — server reorders by slide ID rather than by index,
1258
1657
  // so concurrent adds from other writers don't get dropped.
1259
- enqueueDeckOp(deckId, { op: "reorder-slides", orderedIds });
1658
+ const op: PatchDeckOp = { op: "reorder-slides", orderedIds };
1659
+ enqueueDeckOp(deckId, op);
1660
+ if (before) recordUndo(before, op, { label: "Reorder slides" });
1260
1661
  }
1261
1662
  },
1262
- [markDeckDirty, setDecksWithHistory],
1663
+ [markDeckDirty, recordUndo, setDecksLocal],
1263
1664
  );
1264
1665
 
1265
1666
  const setDeckSlides = useCallback(
1266
1667
  (deckId: string, slides: Slide[]) => {
1267
1668
  markDeckDirty(deckId);
1268
- setDecksWithHistory("Generate slides", (prev) =>
1669
+ const before = decksRef.current.find((deck) => deck.id === deckId);
1670
+ const after = before
1671
+ ? { ...before, slides, updatedAt: new Date().toISOString() }
1672
+ : null;
1673
+ // setDeckSlides replaces ALL slides wholesale (used by AI generation and
1674
+ // imports), so its undo entry is a deck-level full replacement instead of
1675
+ // a fine-grained slide patch.
1676
+ setDecksLocal((prev) =>
1269
1677
  prev.map((d) => {
1270
1678
  if (d.id !== deckId) return d;
1271
- const updated = { ...d, slides, updatedAt: new Date().toISOString() };
1272
- // setDeckSlides replaces ALL slides wholesale (used by AI generation
1273
- // and imports). Use a full-replace so the server state always exactly
1274
- // matches the generated result, regardless of any concurrent changes.
1275
- enqueueDeckOp(deckId, { op: "full-replace", deck: updated });
1276
- return updated;
1679
+ const next = after ?? {
1680
+ ...d,
1681
+ slides,
1682
+ updatedAt: new Date().toISOString(),
1683
+ };
1684
+ enqueueDeckOp(deckId, { op: "full-replace", deck: next });
1685
+ return next;
1277
1686
  }),
1278
1687
  );
1688
+ if (before && after) {
1689
+ undoControllerRef.current?.push({
1690
+ undo: [{ op: "replace-deck", deckId, deck: before }],
1691
+ redo: [{ op: "replace-deck", deckId, deck: after }],
1692
+ label: "Replace slides",
1693
+ });
1694
+ }
1279
1695
  },
1280
- [markDeckDirty, setDecksWithHistory],
1696
+ [markDeckDirty, setDecksLocal],
1281
1697
  );
1282
1698
 
1283
1699
  return (
@@ -1301,11 +1717,8 @@ export function DeckProvider({ children }: { children: ReactNode }) {
1301
1717
  markDeckDirty,
1302
1718
  undo,
1303
1719
  redo,
1304
- canUndo: historyIndex > 0,
1305
- canRedo: historyIndex < history.length - 1,
1306
- history,
1307
- historyIndex,
1308
- restoreFromHistory,
1720
+ canUndo,
1721
+ canRedo,
1309
1722
  }}
1310
1723
  >
1311
1724
  {children}