@agent-native/core 0.169.1 → 0.171.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 (463) hide show
  1. package/agent-native.eject.json +4 -1
  2. package/bin/agent-native.js +23 -5
  3. package/corpus/README.md +1 -1
  4. package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
  5. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  6. package/corpus/templates/analytics/app/i18n-data.ts +28 -0
  7. package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
  8. package/corpus/templates/analytics/app/routes/$.tsx +10 -0
  9. package/corpus/templates/analytics/app/routes.ts +3 -1
  10. package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
  12. package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
  14. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
  15. package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
  16. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
  17. package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
  18. package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
  20. package/corpus/templates/analytics/server/lib/slack.ts +4 -2
  21. package/corpus/templates/analytics/vitest.config.ts +6 -1
  22. package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
  23. package/corpus/templates/assets/app/routes/library.tsx +5 -3
  24. package/corpus/templates/assets/app/routes.ts +3 -1
  25. package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
  26. package/corpus/templates/assets/vitest.config.ts +6 -1
  27. package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
  28. package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
  29. package/corpus/templates/brain/app/routes/sources.tsx +8 -3
  30. package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
  31. package/corpus/templates/brain/vitest.config.ts +1 -0
  32. package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
  33. package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
  34. package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
  35. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
  36. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
  37. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
  38. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
  39. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
  40. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
  41. package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
  42. package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
  43. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
  44. package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
  45. package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
  46. package/corpus/templates/calendar/app/routes.ts +3 -1
  47. package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
  48. package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
  49. package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
  50. package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
  51. package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
  52. package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
  53. package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
  54. package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
  55. package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
  56. package/corpus/templates/calendar/server/onboarding.ts +85 -0
  57. package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
  58. package/corpus/templates/calendar/shared/api.ts +9 -1
  59. package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  60. package/corpus/templates/chat/app/routes.ts +3 -1
  61. package/corpus/templates/chat/vitest.config.ts +8 -0
  62. package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
  63. package/corpus/templates/clips/actions/get-meeting.ts +9 -4
  64. package/corpus/templates/clips/actions/list-recordings.ts +16 -3
  65. package/corpus/templates/clips/actions/search-meetings.ts +1 -0
  66. package/corpus/templates/clips/actions/view-screen.ts +8 -1
  67. package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
  68. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
  69. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
  70. package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
  71. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
  72. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
  73. package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
  74. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
  75. package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
  76. package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
  80. package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
  81. package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
  82. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
  83. package/corpus/templates/clips/desktop/src/app.tsx +53 -24
  84. package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
  86. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
  87. package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
  88. package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
  89. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
  90. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
  91. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
  92. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
  93. package/corpus/templates/clips/server/lib/emails.ts +17 -33
  94. package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
  95. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
  96. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
  97. package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
  98. package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
  99. package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
  100. package/corpus/templates/content/app/i18n-data.ts +25 -0
  101. package/corpus/templates/content/app/routes.ts +3 -1
  102. package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
  103. package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
  104. package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
  105. package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
  106. package/corpus/templates/content/vitest.config.ts +7 -1
  107. package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
  108. package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
  109. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
  110. package/corpus/templates/design/actions/generate-design.ts +98 -47
  111. package/corpus/templates/design/actions/get-design-system.ts +6 -1
  112. package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
  114. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
  115. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
  116. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
  117. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
  118. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
  119. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
  120. package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
  121. package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
  122. package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
  123. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
  124. package/corpus/templates/design/app/i18n-data.ts +1 -0
  125. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
  126. package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
  127. package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
  128. package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
  129. package/corpus/templates/design/app/pages/Index.tsx +2 -0
  130. package/corpus/templates/design/app/pages/Templates.tsx +0 -10
  131. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
  132. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
  134. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
  135. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
  136. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
  137. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
  138. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
  139. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
  140. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
  141. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
  142. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
  143. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
  144. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
  145. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
  146. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
  147. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
  148. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
  149. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
  150. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
  151. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
  152. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
  153. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
  154. package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
  155. package/corpus/templates/design/app/routes.ts +3 -1
  156. package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
  157. package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
  158. package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
  159. package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
  160. package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
  161. package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
  162. package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
  163. package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
  164. package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
  165. package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
  166. package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
  167. package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
  168. package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
  169. package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
  170. package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
  171. package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
  172. package/corpus/templates/design/package.json +2 -0
  173. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
  174. package/corpus/templates/design/shared/canvas-math.ts +612 -206
  175. package/corpus/templates/design/shared/pen-path.ts +37 -0
  176. package/corpus/templates/design/vitest.config.ts +7 -1
  177. package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
  178. package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
  179. package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
  180. package/corpus/templates/dispatch/app/routes.ts +3 -1
  181. package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
  182. package/corpus/templates/factory/.env.example +2 -7
  183. package/corpus/templates/factory/AGENTS.md +3 -0
  184. package/corpus/templates/factory/README.md +5 -3
  185. package/corpus/templates/factory/actions/create-factory.ts +16 -3
  186. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
  187. package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
  188. package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
  189. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
  190. package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
  191. package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
  192. package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
  193. package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
  194. package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
  195. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
  196. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
  197. package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
  198. package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
  199. package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
  200. package/corpus/templates/factory/app/routes/factory.tsx +84 -11
  201. package/corpus/templates/factory/app/routes.ts +3 -1
  202. package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
  203. package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
  204. package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
  205. package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
  206. package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
  207. package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
  208. package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
  209. package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
  210. package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
  211. package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
  212. package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
  213. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
  214. package/corpus/templates/factory/vitest.config.ts +17 -2
  215. package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
  216. package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
  217. package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  218. package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
  219. package/corpus/templates/forms/vitest.config.ts +1 -0
  220. package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
  221. package/corpus/templates/macros/app/routes.ts +3 -1
  222. package/corpus/templates/macros/vitest.config.ts +6 -1
  223. package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
  224. package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
  225. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
  226. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
  227. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
  228. package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
  229. package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
  230. package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
  231. package/corpus/templates/mail/app/routes/$.tsx +9 -0
  232. package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
  233. package/corpus/templates/mail/app/routes/$view.tsx +47 -1
  234. package/corpus/templates/mail/app/routes.ts +3 -1
  235. package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
  236. package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
  237. package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
  238. package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
  239. package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
  240. package/corpus/templates/mail/server/onboarding.ts +66 -18
  241. package/corpus/templates/mail/vitest.config.ts +6 -1
  242. package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
  243. package/corpus/templates/plan/app/routes/settings.tsx +4 -1
  244. package/corpus/templates/plan/app/routes.ts +3 -1
  245. package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  246. package/corpus/templates/plan/netlify.toml +1 -1
  247. package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
  248. package/corpus/templates/slides/actions/patch-deck.ts +79 -10
  249. package/corpus/templates/slides/actions/update-slide.ts +11 -1
  250. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
  251. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
  252. package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
  253. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
  254. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
  255. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
  256. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
  257. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
  258. package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
  259. package/corpus/templates/slides/app/routes.ts +3 -1
  260. package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
  261. package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
  262. package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
  263. package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
  264. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
  265. package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
  266. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
  267. package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
  268. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
  269. package/corpus/templates/slides/tsconfig.json +5 -0
  270. package/corpus/templates/slides/vitest.config.ts +6 -1
  271. package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
  272. package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
  273. package/corpus/templates/tasks/app/routes.ts +3 -1
  274. package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  275. package/corpus/templates/tasks/vitest.config.ts +7 -1
  276. package/dist/action.d.ts +91 -0
  277. package/dist/action.js +50 -0
  278. package/dist/agent/abort-reasons.d.ts +38 -0
  279. package/dist/agent/abort-reasons.js +49 -0
  280. package/dist/agent/engine/ai-sdk-engine.js +8 -5
  281. package/dist/agent/engine/anthropic-engine.js +7 -5
  282. package/dist/agent/engine/first-event-timeout.d.ts +42 -17
  283. package/dist/agent/engine/first-event-timeout.js +67 -25
  284. package/dist/agent/production-agent.d.ts +23 -0
  285. package/dist/agent/production-agent.js +107 -102
  286. package/dist/agent/run-loop-with-resume.d.ts +2 -28
  287. package/dist/agent/run-loop-with-resume.js +25 -40
  288. package/dist/agent/run-manager.js +5 -4
  289. package/dist/agent/run-store.d.ts +55 -0
  290. package/dist/agent/run-store.js +124 -1
  291. package/dist/agent/tool-search.js +2 -1
  292. package/dist/agent/types.d.ts +9 -4
  293. package/dist/app-config/auth.d.ts +1 -0
  294. package/dist/app-config/auth.js +7 -0
  295. package/dist/app-config/configuration-error.d.ts +11 -0
  296. package/dist/app-config/configuration-error.js +14 -0
  297. package/dist/app-config/index.d.ts +1 -0
  298. package/dist/app-config/index.js +1 -0
  299. package/dist/app-config/integrations.d.ts +1 -0
  300. package/dist/app-config/integrations.js +10 -0
  301. package/dist/app-config/plugins.d.ts +34 -0
  302. package/dist/app-config/plugins.js +39 -0
  303. package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
  304. package/dist/app-config/run-lifecycle-invariants.js +18 -25
  305. package/dist/app-config/schema.d.ts +17 -0
  306. package/dist/app-config/schema.js +2 -0
  307. package/dist/chat-threads/schema-migrations.js +20 -0
  308. package/dist/chat-threads/store.js +21 -4
  309. package/dist/cli/native-dependencies.d.ts +4 -0
  310. package/dist/cli/native-dependencies.js +15 -0
  311. package/dist/client/EnvironmentBadge.d.ts +1 -1
  312. package/dist/client/EnvironmentBadge.js +40 -5
  313. package/dist/client/MultiTabAssistantChat.d.ts +1 -1
  314. package/dist/client/MultiTabAssistantChat.js +35 -14
  315. package/dist/client/agent-chat/index.d.ts +1 -1
  316. package/dist/client/agent-chat/index.js +1 -1
  317. package/dist/client/agent-page/use-jobs.d.ts +2 -1
  318. package/dist/client/agent-page/use-jobs.js +3 -5
  319. package/dist/client/agent-sidebar-state.js +3 -1
  320. package/dist/client/blocks/library/code-tabs.js +5 -0
  321. package/dist/client/blocks/library/code.js +1 -0
  322. package/dist/client/chat-first/apps-rail.js +2 -2
  323. package/dist/client/chat-model-groups.d.ts +12 -0
  324. package/dist/client/chat-model-groups.js +3 -2
  325. package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
  326. package/dist/client/integrations/GoogleProductLogo.js +45 -0
  327. package/dist/client/integrations/index.d.ts +2 -0
  328. package/dist/client/integrations/index.js +2 -0
  329. package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
  330. package/dist/client/integrations/workspace-provider-oauth.js +13 -0
  331. package/dist/client/resources/mcp-integration-catalog.js +0 -29
  332. package/dist/client/settings/SettingsTabsPage.js +10 -6
  333. package/dist/client/sharing/ShareButton.js +11 -2
  334. package/dist/client/use-chat-models.d.ts +6 -0
  335. package/dist/client/use-chat-models.js +10 -0
  336. package/dist/client/use-db-sync.js +27 -9
  337. package/dist/collab/routes.d.ts +1 -1
  338. package/dist/collab/struct-routes.d.ts +1 -1
  339. package/dist/connections/catalog.d.ts +111 -9
  340. package/dist/connections/catalog.js +86 -30
  341. package/dist/db/create-get-db.js +18 -0
  342. package/dist/db/ddl-guard.d.ts +0 -4
  343. package/dist/db/ddl-guard.js +46 -0
  344. package/dist/deploy/build.d.ts +19 -0
  345. package/dist/deploy/build.js +190 -16
  346. package/dist/deploy/function-bundle.d.ts +45 -0
  347. package/dist/deploy/function-bundle.js +337 -0
  348. package/dist/deploy/workspace-deploy.js +19 -1
  349. package/dist/eject/workspace-connections.js +4 -0
  350. package/dist/email-catalog/registry.d.ts +4 -0
  351. package/dist/email-catalog/registry.js +79 -14
  352. package/dist/extensions/content-patch.js +14 -2
  353. package/dist/integrations/plugin.d.ts +10 -0
  354. package/dist/integrations/plugin.js +40 -6
  355. package/dist/jobs/actions/run-automation-now.d.ts +2 -1
  356. package/dist/jobs/actions/run-automation-now.js +11 -5
  357. package/dist/jobs/frontmatter.js +53 -2
  358. package/dist/jobs/run-now.d.ts +11 -1
  359. package/dist/jobs/run-now.js +42 -7
  360. package/dist/jobs/scheduler.d.ts +1 -0
  361. package/dist/jobs/scheduler.js +2 -1
  362. package/dist/localization/core-messages/ar-SA.js +3 -0
  363. package/dist/localization/core-messages/de-DE.js +3 -0
  364. package/dist/localization/core-messages/en-US.d.ts +3 -0
  365. package/dist/localization/core-messages/en-US.js +3 -0
  366. package/dist/localization/core-messages/es-ES.js +3 -0
  367. package/dist/localization/core-messages/fr-FR.js +3 -0
  368. package/dist/localization/core-messages/hi-IN.js +3 -0
  369. package/dist/localization/core-messages/ja-JP.js +3 -0
  370. package/dist/localization/core-messages/ko-KR.js +3 -0
  371. package/dist/localization/core-messages/pt-BR.js +3 -0
  372. package/dist/localization/core-messages/zh-CN.js +3 -0
  373. package/dist/localization/core-messages/zh-TW.js +3 -0
  374. package/dist/localization/core-messages.d.ts +3 -0
  375. package/dist/localization/default-messages.d.ts +2 -5
  376. package/dist/localization/default-messages.js +2 -5
  377. package/dist/mcp/build-server.d.ts +9 -0
  378. package/dist/mcp/build-server.js +37 -8
  379. package/dist/mcp-client/oauth-client.d.ts +7 -0
  380. package/dist/mcp-client/oauth-client.js +12 -1
  381. package/dist/oauth-tokens/index.d.ts +1 -1
  382. package/dist/oauth-tokens/index.js +1 -1
  383. package/dist/oauth-tokens/lifecycle.d.ts +11 -0
  384. package/dist/oauth-tokens/lifecycle.js +17 -0
  385. package/dist/oauth-tokens/store.js +7 -0
  386. package/dist/observability/posthog-ai.d.ts +15 -1
  387. package/dist/observability/posthog-ai.js +24 -12
  388. package/dist/observability/traces.js +158 -28
  389. package/dist/onboarding/app-profile.js +2 -2
  390. package/dist/onboarding/plugin.js +10 -2
  391. package/dist/onboarding/types.d.ts +2 -0
  392. package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
  393. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  394. package/dist/provider-api/index.js +2 -0
  395. package/dist/resources/handlers.d.ts +2 -2
  396. package/dist/resources/store.js +13 -4
  397. package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
  398. package/dist/secrets/register-framework-secrets.js +1 -1
  399. package/dist/server/action-discovery.js +5 -0
  400. package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
  401. package/dist/server/agent-chat/action-filters-a2a.js +48 -9
  402. package/dist/server/agent-chat-plugin.js +36 -9
  403. package/dist/server/agent-discovery.js +43 -20
  404. package/dist/server/auth.js +18 -16
  405. package/dist/server/beta-opt-out-html.js +45 -4
  406. package/dist/server/better-auth-instance.d.ts +2 -1
  407. package/dist/server/better-auth-instance.js +37 -7
  408. package/dist/server/builder-design-systems.d.ts +4 -1
  409. package/dist/server/builder-design-systems.js +87 -6
  410. package/dist/server/builder-oauth.d.ts +1 -7
  411. package/dist/server/builder-oauth.js +80 -163
  412. package/dist/server/core-routes-plugin.d.ts +29 -0
  413. package/dist/server/core-routes-plugin.js +100 -20
  414. package/dist/server/default-plugins.d.ts +18 -0
  415. package/dist/server/default-plugins.js +22 -0
  416. package/dist/server/edge.d.ts +1 -0
  417. package/dist/server/edge.js +1 -0
  418. package/dist/server/framework-request-handler.js +31 -3
  419. package/dist/server/google-oauth.d.ts +3 -0
  420. package/dist/server/google-oauth.js +3 -0
  421. package/dist/server/index.d.ts +4 -4
  422. package/dist/server/index.js +3 -3
  423. package/dist/server/onboarding-html.js +37 -3
  424. package/dist/server/realtime-token.d.ts +1 -1
  425. package/dist/server/workspace-provider-oauth.d.ts +6 -1
  426. package/dist/server/workspace-provider-oauth.js +72 -20
  427. package/dist/shared/agent-sidebar-url.d.ts +1 -0
  428. package/dist/shared/agent-sidebar-url.js +1 -0
  429. package/dist/shared/environment-lanes.d.ts +2 -0
  430. package/dist/shared/environment-lanes.js +3 -1
  431. package/dist/shared/index.d.ts +3 -1
  432. package/dist/shared/index.js +3 -1
  433. package/dist/shared/surface-visibility.d.ts +31 -0
  434. package/dist/shared/surface-visibility.js +57 -0
  435. package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  436. package/dist/templates/chat/app/routes.ts +3 -1
  437. package/dist/templates/chat/vitest.config.ts +8 -0
  438. package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
  439. package/dist/templates/default/app/routes.ts +3 -1
  440. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
  441. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
  442. package/dist/tracking/registry.d.ts +11 -0
  443. package/dist/tracking/registry.js +5 -2
  444. package/dist/triggers/actions.js +10 -3
  445. package/dist/usage/store.js +23 -3
  446. package/dist/user-profile/actions/get-auth-methods.js +18 -5
  447. package/dist/vite/client.js +33 -7
  448. package/docs/content/actions-access-control.mdx +43 -4
  449. package/docs/content/actions-defining.mdx +16 -0
  450. package/docs/content/authentication.mdx +40 -21
  451. package/docs/content/environment-variables.mdx +1 -0
  452. package/docs/content/template-clips-capture-everywhere.mdx +29 -30
  453. package/docs/content/template-clips-developers.mdx +4 -4
  454. package/docs/content/template-clips.mdx +3 -1
  455. package/package.json +2 -2
  456. package/src/eject/workspace-connections.ts +4 -0
  457. package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  458. package/src/templates/chat/app/routes.ts +3 -1
  459. package/src/templates/chat/vitest.config.ts +8 -0
  460. package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
  461. package/src/templates/default/app/routes.ts +3 -1
  462. package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
  463. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
@@ -8,9 +8,11 @@ import {
8
8
  DEFAULT_CANVAS_MIN_ZOOM,
9
9
  DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
10
10
  canvasToScreenPoint,
11
- computeEqualGapGuides,
11
+ computeDragSnap,
12
12
  computeMoveSnap,
13
13
  computeResizeSnap,
14
+ type DistanceGuideBand,
15
+ type ProximityMeasurement,
14
16
  type EqualGapGuide,
15
17
  getCameraForBounds,
16
18
  getFrameGroupBounds,
@@ -31,7 +33,8 @@ import {
31
33
  closePenPath,
32
34
  constrainPointTo45Degrees,
33
35
  createCornerNode,
34
- createSmoothNode,
36
+ createPenCuspLatch,
37
+ createPenDragNode,
35
38
  getPenPathGeometry,
36
39
  hitTestPenAnchor,
37
40
  hitTestPenHandle,
@@ -142,9 +145,6 @@ const SCREEN_CARD_HEIGHT = SCREEN_HEIGHT + 26;
142
145
  const SCREEN_GAP = 56;
143
146
  const DUPLICATE_DRAG_THRESHOLD = 6;
144
147
  const DRAG_THRESHOLD = 3;
145
- /** How close two gaps must be (in screen px, converted to canvas px at the
146
- * live zoom) to count as "equal" for the smart-spacing guides (CV11). */
147
- const EQUAL_GAP_TOLERANCE_SCREEN_PX = 2;
148
148
  const FRAME_LABEL_HEIGHT = 28;
149
149
  const FRAME_HEADER_BUTTON_COMPACT_WIDTH = 260;
150
150
  const FRAME_HEADER_BUTTON_RESERVE = 116;
@@ -284,6 +284,7 @@ import {
284
284
  altHoverMeasurementEqual,
285
285
  computeAltHoverMeasurement,
286
286
  equalGapGuidesEqual,
287
+ proximityMeasurementsEqual,
287
288
  } from "./multi-screen/alt-hover-measurement";
288
289
  import {
289
290
  boardPointToScreenLocalPoint,
@@ -306,6 +307,8 @@ import {
306
307
  computeBoundedScreenCullState,
307
308
  getScreenContentCullState,
308
309
  getOverscannedViewportCanvasBounds,
310
+ isFrameWithinOverscannedViewport,
311
+ type OverscannedViewportBounds,
309
312
  type ScreenCullTier,
310
313
  } from "./multi-screen/culling";
311
314
  import {
@@ -835,6 +838,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
835
838
  [],
836
839
  );
837
840
  const [equalGapGuides, setEqualGapGuidesRaw] = useState<EqualGapGuide[]>([]);
841
+ const [proximityMeasurements, setProximityMeasurementsRaw] = useState<
842
+ ProximityMeasurement[]
843
+ >([]);
838
844
  // Guides are recomputed into a brand-new array on every rAF-coalesced
839
845
  // mousemove during a drag; without a value-equality bail, React commits a
840
846
  // state update (and a re-render) every frame even when the guides drawn on
@@ -844,6 +850,14 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
844
850
  alignmentGuidesEqual(current, next) ? current : next,
845
851
  );
846
852
  }, []);
853
+ const setProximityMeasurements = useCallback(
854
+ (next: ProximityMeasurement[]) => {
855
+ setProximityMeasurementsRaw((current) =>
856
+ proximityMeasurementsEqual(current, next) ? current : next,
857
+ );
858
+ },
859
+ [],
860
+ );
847
861
  const setEqualGapGuides = useCallback((next: EqualGapGuide[]) => {
848
862
  setEqualGapGuidesRaw((current) =>
849
863
  equalGapGuidesEqual(current, next) ? current : next,
@@ -1752,6 +1766,47 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
1752
1766
  [getCurrentDraftEntries, getCurrentFrameEntries],
1753
1767
  );
1754
1768
 
1769
+ /** Snap candidates are the entries on screen right now, matching Figma and
1770
+ * tldraw: a frame parked off in the corner of an infinite canvas is not
1771
+ * something the user is aligning to, and a guide drawn to it would stretch
1772
+ * across empty space. Falls back to every entry before first layout. */
1773
+ const snapViewportRef = useRef<OverscannedViewportBounds | null>(null);
1774
+
1775
+ /** Reads the surface box once per gesture. Called per rAF it would force a
1776
+ * synchronous layout of the host document — which holds every live screen
1777
+ * iframe — on the hottest path there is. Pan/zoom cannot change mid-drag. */
1778
+ const beginSnapGesture = useCallback(() => {
1779
+ const size = surfaceSizeRef.current;
1780
+ snapViewportRef.current = size
1781
+ ? getOverscannedViewportCanvasBounds(
1782
+ size,
1783
+ panRef.current,
1784
+ zoomRef.current,
1785
+ 0,
1786
+ )
1787
+ : null;
1788
+ }, []);
1789
+
1790
+ /** Wheel pan/zoom is not blocked during a drag, so the cached viewport can
1791
+ * go stale mid-gesture; drop it and fall back to every candidate rather
1792
+ * than filter against a rect that no longer describes the screen. */
1793
+ const invalidateSnapGesture = useCallback(() => {
1794
+ snapViewportRef.current = null;
1795
+ }, []);
1796
+
1797
+ const getSnapCandidateEntries = useCallback(
1798
+ (excludeIds: string[], entries = getCurrentCanvasEntries()) => {
1799
+ const viewport = snapViewportRef.current;
1800
+ return entries.filter(
1801
+ (entry) =>
1802
+ !excludeIds.includes(entry.id) &&
1803
+ (!viewport ||
1804
+ isFrameWithinOverscannedViewport(entry.geometry, viewport)),
1805
+ );
1806
+ },
1807
+ [getCurrentCanvasEntries],
1808
+ );
1809
+
1755
1810
  const getFrameEntryAtPoint = useCallback(
1756
1811
  (point: Point) =>
1757
1812
  findTopFrameEntryAtPoint(getSelectableFrameEntries(), point, {
@@ -3070,6 +3125,53 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3070
3125
  ],
3071
3126
  );
3072
3127
 
3128
+ /** Selects the layer under a client point inside one screen. Shared by the
3129
+ * double-click drill-in and by a click on an already-selected frame's body,
3130
+ * so both resolve the same candidate and keep the same repeat-click walk. */
3131
+ const drillIntoScreenAtPoint = useCallback(
3132
+ (id: string, clientX: number, clientY: number) => {
3133
+ const point = getCanvasPoint(clientX, clientY);
3134
+ const previousKey =
3135
+ drillInTargetRef.current?.screenId === id
3136
+ ? drillInTargetRef.current.key
3137
+ : null;
3138
+ const requestId = drillInRequestRef.current + 1;
3139
+ drillInRequestRef.current = requestId;
3140
+ void collectLayerMarqueeCandidates(new Set([id])).then((candidates) => {
3141
+ if (drillInRequestRef.current !== requestId) return;
3142
+ const target = resolveDrillInTarget({
3143
+ candidates,
3144
+ screenId: id,
3145
+ point,
3146
+ previousKey,
3147
+ });
3148
+ if (!target) {
3149
+ // Nothing selectable under the pointer (e.g. an empty frame). Leave
3150
+ // the frame itself selected rather than falling back to Interact.
3151
+ drillInTargetRef.current = null;
3152
+ return;
3153
+ }
3154
+ drillInTargetRef.current = {
3155
+ screenId: id,
3156
+ key: drillInCandidateKey(target),
3157
+ };
3158
+ updateSelectedDraftIds(() => []);
3159
+ updateSelectedIds(() => []);
3160
+ onLayerMarqueeSelectionChange?.(
3161
+ [{ screenId: target.screenId, info: target.info }],
3162
+ { source: "pointer" },
3163
+ );
3164
+ });
3165
+ },
3166
+ [
3167
+ collectLayerMarqueeCandidates,
3168
+ getCanvasPoint,
3169
+ onLayerMarqueeSelectionChange,
3170
+ updateSelectedDraftIds,
3171
+ updateSelectedIds,
3172
+ ],
3173
+ );
3174
+
3073
3175
  const scheduleFeedbackClear = useCallback(() => {
3074
3176
  if (feedbackTimerRef.current !== null) {
3075
3177
  window.clearTimeout(feedbackTimerRef.current);
@@ -3232,6 +3334,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3232
3334
  setCreationPreview(null);
3233
3335
  setAlignmentGuides([]);
3234
3336
  setEqualGapGuides([]);
3337
+ setProximityMeasurements([]);
3235
3338
  setTransformBadge(null);
3236
3339
  setDragCursor(null);
3237
3340
  primitiveDropTargetRef.current = null;
@@ -3762,6 +3865,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3762
3865
  (
3763
3866
  draft: DraftPrimitive,
3764
3867
  preferredFrameId?: string,
3868
+ options?: { nextTool?: "move" | "pen" },
3765
3869
  ): PersistedDraftPrimitive | null => {
3766
3870
  const targetFrame = getTargetFrameForDraft(draft, preferredFrameId);
3767
3871
 
@@ -3779,7 +3883,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3779
3883
  width: 1,
3780
3884
  height: 1,
3781
3885
  });
3782
- const persisted = handler(boardPrimitive);
3886
+ const persisted = handler(boardPrimitive, options);
3783
3887
  if (!persisted) return null;
3784
3888
  // Return the board file id so the caller can run the same selection
3785
3889
  // and text-edit activation path used by regular screen primitives.
@@ -3856,7 +3960,11 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3856
3960
  preferredFrameId?: string,
3857
3961
  options?: { nextTool?: "move" | "pen" },
3858
3962
  ) => {
3859
- const persisted = persistDraftPrimitive(nextDraft, preferredFrameId);
3963
+ const persisted = persistDraftPrimitive(
3964
+ nextDraft,
3965
+ preferredFrameId,
3966
+ options,
3967
+ );
3860
3968
  if (persisted) {
3861
3969
  updateDraftPrimitives((current) =>
3862
3970
  current.filter((draft) => draft.id !== nextDraft.id),
@@ -3947,10 +4055,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3947
4055
 
3948
4056
  const finishPenPath = useCallback(
3949
4057
  (path = activePenPathRef.current) => {
3950
- if (!path || path.nodes.length < 2) {
3951
- clearActivePenPath();
3952
- return;
3953
- }
4058
+ // Clear before committing: the commit flushes React synchronously, and
4059
+ // an effect it wakes can re-enter here and commit the same path twice.
4060
+ clearActivePenPath();
4061
+ if (!path || path.nodes.length < 2) return;
3954
4062
 
3955
4063
  commitDraftPrimitive(
3956
4064
  createPenDraftPrimitive(path, {
@@ -3966,7 +4074,6 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3966
4074
  // asynchronous selection reconciliation can otherwise paint Move for a
3967
4075
  // frame. Drive the controlled tool explicitly at the commit boundary.
3968
4076
  onActiveToolChange?.("pen");
3969
- clearActivePenPath();
3970
4077
  },
3971
4078
  [clearActivePenPath, commitDraftPrimitive, onActiveToolChange, toolProps],
3972
4079
  );
@@ -4096,6 +4203,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4096
4203
  pathBefore: pathSnapshot,
4097
4204
  hasMoved: false,
4098
4205
  closing,
4206
+ cuspLatch: createPenCuspLatch(),
4099
4207
  };
4100
4208
  const initialPath = closing
4101
4209
  ? (pathSnapshot as PenPath)
@@ -4142,13 +4250,12 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4142
4250
  }
4143
4251
 
4144
4252
  const node = state.hasMoved
4145
- ? createSmoothNode(state.anchor, handleOut, {
4146
- // Alt/Option while dragging a new anchor's handle breaks
4147
- // symmetry into a cusp (P8): read the live event's altKey on
4148
- // every move so toggling Alt mid-drag updates immediately,
4149
- // rather than latching whatever it was when the drag started.
4150
- breakSymmetry: ev.altKey,
4151
- })
4253
+ ? createPenDragNode(
4254
+ state.anchor,
4255
+ handleOut,
4256
+ state.cuspLatch,
4257
+ ev.altKey,
4258
+ )
4152
4259
  : createCornerNode(state.anchor);
4153
4260
  const nextPath = appendPenNode(state.pathBefore, node);
4154
4261
  activePenPathRef.current = nextPath;
@@ -4182,9 +4289,12 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4182
4289
  }
4183
4290
 
4184
4291
  const node: PenNode = state.hasMoved
4185
- ? createSmoothNode(state.anchor, handleOut, {
4186
- breakSymmetry: ev.altKey,
4187
- })
4292
+ ? createPenDragNode(
4293
+ state.anchor,
4294
+ handleOut,
4295
+ state.cuspLatch,
4296
+ ev.altKey,
4297
+ )
4188
4298
  : createCornerNode(state.anchor);
4189
4299
  const nextPath = appendPenNode(state.pathBefore, node);
4190
4300
  activePenPathRef.current = nextPath;
@@ -4331,6 +4441,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4331
4441
  which,
4332
4442
  pathBefore,
4333
4443
  hasMoved: false,
4444
+ symmetryBroken: false,
4334
4445
  };
4335
4446
  setIsDragging(true);
4336
4447
  setDragCursor("crosshair");
@@ -4354,15 +4465,13 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4354
4465
  canvasPoint,
4355
4466
  active.originCanvas,
4356
4467
  );
4357
- // Alt/Option held mid-drag breaks handle symmetry into a cusp,
4358
- // matching the pen tool's own alt behavior (read live on every move
4359
- // so toggling Alt mid-drag updates immediately).
4468
+ if (ev.altKey) state.symmetryBroken = true;
4360
4469
  const nextPath = movePenHandle(
4361
4470
  state.pathBefore,
4362
4471
  state.nodeIndex,
4363
4472
  state.which,
4364
4473
  localPoint,
4365
- { breakSymmetry: ev.altKey },
4474
+ { breakSymmetry: state.symmetryBroken },
4366
4475
  );
4367
4476
  active.onChange(nextPath, "preview");
4368
4477
  };
@@ -4383,13 +4492,14 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4383
4492
  canvasPoint,
4384
4493
  active.originCanvas,
4385
4494
  );
4495
+ if (ev.altKey) state.symmetryBroken = true;
4386
4496
  const nextPath = state.hasMoved
4387
4497
  ? movePenHandle(
4388
4498
  state.pathBefore,
4389
4499
  state.nodeIndex,
4390
4500
  state.which,
4391
4501
  localPoint,
4392
- { breakSymmetry: ev.altKey },
4502
+ { breakSymmetry: state.symmetryBroken },
4393
4503
  )
4394
4504
  : state.pathBefore;
4395
4505
  active.onChange(nextPath, "commit");
@@ -4655,6 +4765,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4655
4765
  current.includes(id) ? current : [id],
4656
4766
  );
4657
4767
 
4768
+ beginSnapGesture();
4658
4769
  dragState.current = {
4659
4770
  type: "draft-move",
4660
4771
  originClient: { x: e.clientX, y: e.clientY },
@@ -4730,13 +4841,13 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4730
4841
  },
4731
4842
  };
4732
4843
  });
4733
- const stationaryEntries = getCurrentCanvasEntries().filter(
4734
- (entry) => !state.targetIds.includes(entry.id),
4735
- );
4736
- const snap = computeMoveSnap(movingEntries, stationaryEntries, {
4844
+ const stationaryEntries = getSnapCandidateEntries(state.targetIds);
4845
+ const snap = computeDragSnap(movingEntries, stationaryEntries, {
4737
4846
  thresholdScreenPx: DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
4738
4847
  zoom: zoomRef.current,
4739
4848
  bypass: ev.metaKey || ev.ctrlKey,
4849
+ pixelGrid: true,
4850
+ lockedAxes: ev.shiftKey ? { x: dx === 0, y: dy === 0 } : undefined,
4740
4851
  });
4741
4852
 
4742
4853
  // PERF9: ref-only geometry write (no setDraftPrimitives) + direct DOM
@@ -4793,6 +4904,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4793
4904
  }
4794
4905
  }
4795
4906
  setAlignmentGuides(snap.guides);
4907
+ setEqualGapGuides(snap.spacingGuides);
4908
+ setProximityMeasurements(snap.measurements);
4796
4909
 
4797
4910
  // Primitive drop-into-container detection: check if the dragged draft
4798
4911
  // is hovering over a committed container primitive on any screen.
@@ -5411,6 +5524,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5411
5524
  const targetIds = currentSelectedIds.includes(id)
5412
5525
  ? currentSelectedIds
5413
5526
  : [id];
5527
+ // Figma parity: this surface covers the frame body, so a press that
5528
+ // never becomes a drag has to hand the click back to the content
5529
+ // underneath — otherwise selecting a screen makes it unclickable.
5530
+ const wasAlreadySelected = currentSelectedIds.includes(id);
5414
5531
  if (activeId !== id) {
5415
5532
  onPick(id);
5416
5533
  }
@@ -5427,6 +5544,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5427
5544
  ) as FrameGeometryById;
5428
5545
  if (!originFrames[id]) return;
5429
5546
 
5547
+ beginSnapGesture();
5430
5548
  dragState.current = {
5431
5549
  type: "move",
5432
5550
  originClient: { x: e.clientX, y: e.clientY },
@@ -5528,13 +5646,18 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5528
5646
  y: state.originFrames[targetId].y + dy,
5529
5647
  },
5530
5648
  }));
5531
- const stationaryEntries = getCurrentFrameEntries().filter(
5532
- (entry) => !state.targetIds.includes(entry.id),
5649
+ // Frames align to frames: a draft primitive is transient content and
5650
+ // must not anchor a persisted screen move.
5651
+ const stationaryEntries = getSnapCandidateEntries(
5652
+ state.targetIds,
5653
+ getCurrentFrameEntries(),
5533
5654
  );
5534
- const snap = computeMoveSnap(movingEntries, stationaryEntries, {
5655
+ const snap = computeDragSnap(movingEntries, stationaryEntries, {
5535
5656
  thresholdScreenPx: DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
5536
5657
  zoom: zoomRef.current,
5537
5658
  bypass: ev.metaKey || ev.ctrlKey,
5659
+ pixelGrid: true,
5660
+ lockedAxes: ev.shiftKey ? { x: dx === 0, y: dy === 0 } : undefined,
5538
5661
  });
5539
5662
 
5540
5663
  // PERF9: mutate the dragged frame(s)' DOM position directly (ref-only
@@ -5624,37 +5747,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5624
5747
  }
5625
5748
  }
5626
5749
  setAlignmentGuides(snap.guides);
5627
-
5628
- // Smart-spacing guides (CV11) — only meaningful for a single moving
5629
- // frame (matches Figma, which only shows equal-gap guides while
5630
- // dragging one object, not a multi-select group).
5631
- if (state.targetIds.length === 1) {
5632
- const primaryId = state.targetIds[0];
5633
- const movedGeometry = movingEntries.find(
5634
- (entry) => entry.id === primaryId,
5635
- )?.geometry;
5636
- if (movedGeometry) {
5637
- // Same screen-px-to-canvas-px conversion computeMoveSnap uses
5638
- // for its own threshold, so the equal-gap tolerance also stays
5639
- // a constant few screen pixels regardless of zoom level.
5640
- const tolerance =
5641
- EQUAL_GAP_TOLERANCE_SCREEN_PX /
5642
- Math.max(0.01, zoomRef.current / 100);
5643
- setEqualGapGuides(
5644
- computeEqualGapGuides(
5645
- {
5646
- ...movedGeometry,
5647
- x: movedGeometry.x + snap.dx,
5648
- y: movedGeometry.y + snap.dy,
5649
- },
5650
- stationaryEntries,
5651
- { toleranceCanvasPx: tolerance },
5652
- ),
5653
- );
5654
- }
5655
- } else {
5656
- setEqualGapGuides([]);
5657
- }
5750
+ setEqualGapGuides(snap.spacingGuides);
5751
+ setProximityMeasurements(snap.measurements);
5658
5752
 
5659
5753
  // Resize shows a W x H badge and rotate shows a degrees badge — move
5660
5754
  // was the one transform with no live feedback at all. Show the
@@ -5687,17 +5781,19 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5687
5781
  }
5688
5782
  };
5689
5783
 
5690
- const handleMouseUp = () => {
5784
+ const handleMouseUp = (ev: MouseEvent) => {
5691
5785
  const state = dragState.current;
5692
5786
  const dropTarget = primitiveDropTargetRef.current;
5693
5787
  if (state?.type === "move" && !state.hasMoved) {
5694
- // Belt-and-braces: the live transform above already skips committing
5695
- // until hasMoved, but restore origin here too in case any geometry
5696
- // slipped through (e.g. a future code path that writes frameGeometry
5697
- // directly) so a below-threshold click never leaves a phantom nudge.
5788
+ // Belt-and-braces against a below-threshold click leaving a phantom
5789
+ // nudge, in case any geometry slipped past the live transform's own
5790
+ // hasMoved guard.
5698
5791
  updateFrameGeometry((current) =>
5699
5792
  frameGeometryWithOverrides(current, state.originFrames),
5700
5793
  );
5794
+ if (wasAlreadySelected) {
5795
+ drillIntoScreenAtPoint(id, ev.clientX, ev.clientY);
5796
+ }
5701
5797
  }
5702
5798
  if (state?.type === "move" && state.hasMoved) {
5703
5799
  // If all dragged ids are committed primitive nodeIds (not screen
@@ -5751,6 +5847,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
5751
5847
  [
5752
5848
  activeId,
5753
5849
  beginDuplicateGesture,
5850
+ drillIntoScreenAtPoint,
5754
5851
  findPrimitiveDropTarget,
5755
5852
  finishDrag,
5756
5853
  getCanvasPoint,
@@ -6562,48 +6659,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
6562
6659
  onEdit?.(id);
6563
6660
  return;
6564
6661
  }
6565
- const point = getCanvasPoint(e.clientX, e.clientY);
6566
- const previousKey =
6567
- drillInTargetRef.current?.screenId === id
6568
- ? drillInTargetRef.current.key
6569
- : null;
6570
- const requestId = drillInRequestRef.current + 1;
6571
- drillInRequestRef.current = requestId;
6572
- void collectLayerMarqueeCandidates(new Set([id])).then((candidates) => {
6573
- if (drillInRequestRef.current !== requestId) return;
6574
- const target = resolveDrillInTarget({
6575
- candidates,
6576
- screenId: id,
6577
- point,
6578
- previousKey,
6579
- });
6580
- if (!target) {
6581
- // Nothing selectable under the pointer (e.g. an empty frame). Leave
6582
- // the frame itself selected rather than falling back to Interact.
6583
- drillInTargetRef.current = null;
6584
- return;
6585
- }
6586
- drillInTargetRef.current = {
6587
- screenId: id,
6588
- key: drillInCandidateKey(target),
6589
- };
6590
- updateSelectedDraftIds(() => []);
6591
- updateSelectedIds(() => []);
6592
- onLayerMarqueeSelectionChange?.(
6593
- [{ screenId: target.screenId, info: target.info }],
6594
- { source: "pointer" },
6595
- );
6596
- });
6662
+ drillIntoScreenAtPoint(id, e.clientX, e.clientY);
6597
6663
  },
6598
- [
6599
- collectLayerMarqueeCandidates,
6600
- getCanvasPoint,
6601
- lockedScreenIdSet,
6602
- onEdit,
6603
- onLayerMarqueeSelectionChange,
6604
- updateSelectedDraftIds,
6605
- updateSelectedIds,
6606
- ],
6664
+ [drillIntoScreenAtPoint, lockedScreenIdSet, onEdit],
6607
6665
  );
6608
6666
 
6609
6667
  const handleMouseDown = useCallback(
@@ -7229,6 +7287,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
7229
7287
  // Real user wheel input is always isTrusted, so this only filters out
7230
7288
  // the synthetic replay.
7231
7289
  if (!event.isTrusted) return;
7290
+ // Pan/zoom mid-drag invalidates the gesture's cached snap viewport.
7291
+ invalidateSnapGesture();
7232
7292
  event.preventDefault();
7233
7293
  event.stopPropagation();
7234
7294
  enqueueWheelGestureFromClient({
@@ -8406,9 +8466,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8406
8466
  beginRotate(singleSelectedFrame.id, event)
8407
8467
  }
8408
8468
  onStartDrag={
8409
- // A running app's frame is dragged by its label. Blanketing its
8410
- // content with a drag surface would make the app unclickable the
8411
- // moment it is selected, which is most of the time.
8469
+ // A running app's frame is dragged by its label: its content
8470
+ // wants the raw click, not a layer selection, so the drag
8471
+ // surface must never cover it.
8412
8472
  singleSelectedFrameIsRunningApp
8413
8473
  ? undefined
8414
8474
  : (event) => beginFrameDrag(singleSelectedFrame.id, event)
@@ -8460,7 +8520,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8460
8520
  {alignmentGuides.map((guide, index) => (
8461
8521
  <span
8462
8522
  key={`${guide.orientation}-${guide.position}-${index}`}
8463
- className="pointer-events-none absolute z-30 bg-destructive/90"
8523
+ data-canvas-guide="alignment"
8524
+ className="pointer-events-none absolute z-30 bg-[var(--design-editor-measure-color)]"
8464
8525
  style={
8465
8526
  guide.orientation === "vertical"
8466
8527
  ? {
@@ -8479,33 +8540,26 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8479
8540
  />
8480
8541
  ))}
8481
8542
 
8482
- {/* Smart-spacing guides (CV11): highlight both equal-sized gaps
8483
- around the moving frame, with one label showing the shared
8484
- distance. */}
8485
8543
  {equalGapGuides.map((guide, index) =>
8486
8544
  guide.bands.map((band, bandIndex) => (
8487
- <span
8545
+ <SpacingGuideMark
8488
8546
  key={`equal-gap-${guide.orientation}-${index}-${bandIndex}`}
8489
- className="pointer-events-none absolute z-30 bg-[var(--design-editor-accent-color)]/25"
8490
- style={
8491
- guide.orientation === "vertical"
8492
- ? {
8493
- left: SURFACE_PADDING + band.gapStart,
8494
- top: SURFACE_PADDING + band.crossStart,
8495
- width: Math.max(1, band.gapEnd - band.gapStart),
8496
- height: Math.max(1, band.crossEnd - band.crossStart),
8497
- }
8498
- : {
8499
- left: SURFACE_PADDING + band.crossStart,
8500
- top: SURFACE_PADDING + band.gapStart,
8501
- width: Math.max(1, band.crossEnd - band.crossStart),
8502
- height: Math.max(1, band.gapEnd - band.gapStart),
8503
- }
8504
- }
8547
+ band={band}
8548
+ orientation={guide.orientation}
8549
+ chromeScale={chromeScale}
8505
8550
  />
8506
8551
  )),
8507
8552
  )}
8508
8553
 
8554
+ {proximityMeasurements.map((measurement) => (
8555
+ <SpacingGuideMark
8556
+ key={`proximity-${measurement.orientation}`}
8557
+ band={measurement.band}
8558
+ orientation={measurement.orientation}
8559
+ chromeScale={chromeScale}
8560
+ />
8561
+ ))}
8562
+
8509
8563
  {/* Figma-parity alt-hover measurement: orange edge-to-edge distance
8510
8564
  lines between the current selection and whatever frame/draft is
8511
8565
  under the cursor while Alt is held (pure hover, no drag). */}
@@ -8610,7 +8664,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8610
8664
  return (
8611
8665
  <span
8612
8666
  key={`equal-gap-label-${guide.orientation}-${index}`}
8613
- className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-accent-color)] px-1 py-0.5 text-[10px] font-medium leading-none text-[var(--design-editor-accent-contrast-color)] shadow-sm"
8667
+ className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-measure-color)] px-1 py-0.5 text-[10px] font-medium leading-none text-[var(--design-editor-accent-contrast-color)] shadow-sm"
8614
8668
  style={{
8615
8669
  left: pan.x + (SURFACE_PADDING + labelCanvasPoint.x) * scale,
8616
8670
  top: pan.y + (SURFACE_PADDING + labelCanvasPoint.y) * scale,
@@ -8621,6 +8675,28 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
8621
8675
  );
8622
8676
  })}
8623
8677
 
8678
+ {proximityMeasurements.map((measurement) => {
8679
+ const { band } = measurement;
8680
+ const crossMid = (band.crossStart + band.crossEnd) / 2;
8681
+ const gapMid = (band.gapStart + band.gapEnd) / 2;
8682
+ const point =
8683
+ measurement.orientation === "vertical"
8684
+ ? { x: gapMid, y: crossMid }
8685
+ : { x: crossMid, y: gapMid };
8686
+ return (
8687
+ <span
8688
+ key={`proximity-label-${measurement.orientation}`}
8689
+ className="pointer-events-none absolute z-40 -translate-x-1/2 -translate-y-1/2 rounded bg-[var(--design-editor-measure-color)] px-1 py-0.5 text-[10px] font-medium leading-none text-[var(--design-editor-accent-contrast-color)] shadow-sm"
8690
+ style={{
8691
+ left: pan.x + (SURFACE_PADDING + point.x) * scale,
8692
+ top: pan.y + (SURFACE_PADDING + point.y) * scale,
8693
+ }}
8694
+ >
8695
+ {Math.round(measurement.gap)}
8696
+ </span>
8697
+ );
8698
+ })}
8699
+
8624
8700
  {penActive || creationToolActive ? (
8625
8701
  <div
8626
8702
  data-canvas-creation-shield
@@ -10850,6 +10926,78 @@ function BreakpointPreviewRow({
10850
10926
  );
10851
10927
  }
10852
10928
 
10929
+ /** Figma's spacing indicator: a thin line down the middle of the gap with a
10930
+ * serif at each end, drawn in the same red as the alignment guides. Lives in
10931
+ * the pan/zoom-transformed world, so every screen-constant dimension is
10932
+ * multiplied by `chromeScale`. */
10933
+ function SpacingGuideMark({
10934
+ band,
10935
+ orientation,
10936
+ chromeScale,
10937
+ }: {
10938
+ band: DistanceGuideBand;
10939
+ orientation: EqualGapGuide["orientation"];
10940
+ chromeScale: number;
10941
+ }) {
10942
+ const line = chromeScale;
10943
+ const serif = 5 * chromeScale;
10944
+ const crossMid = (band.crossStart + band.crossEnd) / 2;
10945
+ const length = Math.max(0, band.gapEnd - band.gapStart);
10946
+ const alongAxis = orientation === "vertical";
10947
+
10948
+ return (
10949
+ <span
10950
+ data-canvas-guide="spacing"
10951
+ className="pointer-events-none absolute z-30"
10952
+ style={
10953
+ alongAxis
10954
+ ? {
10955
+ left: SURFACE_PADDING + band.gapStart,
10956
+ top: SURFACE_PADDING + crossMid - serif,
10957
+ width: length,
10958
+ height: serif * 2,
10959
+ }
10960
+ : {
10961
+ left: SURFACE_PADDING + crossMid - serif,
10962
+ top: SURFACE_PADDING + band.gapStart,
10963
+ width: serif * 2,
10964
+ height: length,
10965
+ }
10966
+ }
10967
+ >
10968
+ <span
10969
+ className="absolute bg-[var(--design-editor-measure-color)]"
10970
+ style={
10971
+ alongAxis
10972
+ ? { left: 0, top: serif - line / 2, width: length, height: line }
10973
+ : { left: serif - line / 2, top: 0, width: line, height: length }
10974
+ }
10975
+ />
10976
+ {[0, length].map((offset, index) => (
10977
+ <span
10978
+ key={index}
10979
+ className="absolute bg-[var(--design-editor-measure-color)]"
10980
+ style={
10981
+ alongAxis
10982
+ ? {
10983
+ left: offset - line / 2,
10984
+ top: 0,
10985
+ width: line,
10986
+ height: serif * 2,
10987
+ }
10988
+ : {
10989
+ left: 0,
10990
+ top: offset - line / 2,
10991
+ width: serif * 2,
10992
+ height: line,
10993
+ }
10994
+ }
10995
+ />
10996
+ ))}
10997
+ </span>
10998
+ );
10999
+ }
11000
+
10853
11001
  function GroupSelectionBox({
10854
11002
  bounds,
10855
11003
  chromeScale,