@agent-native/core 0.163.4 → 0.164.3

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 (228) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
  3. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  4. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
  5. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
  7. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  8. package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
  9. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
  10. package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
  11. package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
  12. package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
  13. package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +2 -0
  15. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
  16. package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
  17. package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
  18. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
  19. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
  20. package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
  21. package/corpus/templates/assets/app/routes/picker.tsx +4 -1
  22. package/corpus/templates/assets/netlify.toml +2 -0
  23. package/corpus/templates/brain/netlify.toml +2 -0
  24. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  25. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  26. package/corpus/templates/calendar/netlify.toml +2 -0
  27. package/corpus/templates/chat/netlify.toml +2 -0
  28. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  29. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  30. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  31. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  33. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  34. package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
  35. package/corpus/templates/clips/app/routes/_index.tsx +7 -3
  36. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  37. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  38. package/corpus/templates/clips/netlify.toml +2 -0
  39. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  40. package/corpus/templates/content/netlify.toml +2 -0
  41. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  42. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  43. package/corpus/templates/design/AGENTS.md +3 -1
  44. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  45. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  46. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  47. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  48. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  49. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  50. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  51. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  52. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  53. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  54. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  55. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  56. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  126. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  127. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  128. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  129. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  130. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  131. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  132. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  134. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  135. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  136. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  137. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  138. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  139. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  140. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  141. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  142. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  143. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  144. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  145. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  146. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  147. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  148. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  149. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  150. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  151. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  152. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  153. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  154. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  155. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  156. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  157. package/corpus/templates/design/app/routes/examples.tsx +2 -1
  158. package/corpus/templates/design/netlify.toml +2 -0
  159. package/corpus/templates/design/shared/board-file.ts +2 -1
  160. package/corpus/templates/dispatch/netlify.toml +2 -0
  161. package/corpus/templates/forms/app/routes/_index.tsx +3 -2
  162. package/corpus/templates/forms/netlify.toml +2 -0
  163. package/corpus/templates/mail/app/routes/_index.tsx +3 -2
  164. package/corpus/templates/mail/netlify.toml +2 -0
  165. package/corpus/templates/plan/netlify.toml +2 -0
  166. package/corpus/templates/slides/netlify.toml +2 -0
  167. package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
  168. package/dist/agent/engine/builder-engine.d.ts +6 -4
  169. package/dist/agent/engine/builder-engine.js +91 -26
  170. package/dist/agent/engine/registry.js +16 -0
  171. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  172. package/dist/cli/code-agent-agent-tools.js +188 -0
  173. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  174. package/dist/cli/code-agent-collaboration.js +104 -0
  175. package/dist/cli/code-agent-executor.js +6 -0
  176. package/dist/cli/code-agent-schedules.d.ts +54 -0
  177. package/dist/cli/code-agent-schedules.js +198 -0
  178. package/dist/client/AgentPanel.js +1 -1
  179. package/dist/client/ConnectBuilderCard.js +5 -2
  180. package/dist/client/EnvironmentBadge.js +6 -5
  181. package/dist/client/chat/run-recovery.js +3 -2
  182. package/dist/client/chat/tool-call-display.js +2 -3
  183. package/dist/client/components/CodeRequiredDialog.js +4 -2
  184. package/dist/client/i18n.js +5 -4
  185. package/dist/client/settings/SettingsPanel.js +1 -1
  186. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  187. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  188. package/dist/client/settings/useBuilderStatus.js +22 -33
  189. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  190. package/dist/code-agents/index.d.ts +2 -0
  191. package/dist/code-agents/index.js +2 -0
  192. package/dist/collab/awareness.d.ts +2 -2
  193. package/dist/collab/routes.d.ts +1 -1
  194. package/dist/deploy/build.js +10 -4
  195. package/dist/localization/shared.d.ts +2 -0
  196. package/dist/localization/shared.js +5 -0
  197. package/dist/mcp-client/oauth-client.d.ts +1 -0
  198. package/dist/mcp-client/oauth-client.js +50 -1
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/observability/routes.d.ts +1 -1
  201. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  202. package/dist/server/auth-marketing-locales.d.ts +7 -0
  203. package/dist/server/auth-marketing-locales.js +1322 -0
  204. package/dist/server/auth-marketing.d.ts +1 -0
  205. package/dist/server/auth-marketing.js +35 -6
  206. package/dist/server/auth.js +4 -17
  207. package/dist/server/builder-browser.d.ts +22 -0
  208. package/dist/server/builder-browser.js +77 -1
  209. package/dist/server/builder-oauth.d.ts +50 -0
  210. package/dist/server/builder-oauth.js +263 -0
  211. package/dist/server/core-routes-plugin.d.ts +11 -0
  212. package/dist/server/core-routes-plugin.js +291 -415
  213. package/dist/server/credential-provider.d.ts +1 -1
  214. package/dist/server/credential-provider.js +1 -1
  215. package/dist/server/csrf.js +2 -2
  216. package/dist/server/onboarding-html.js +52 -120
  217. package/dist/server/ssr-handler.js +13 -4
  218. package/dist/settings/store.d.ts +9 -0
  219. package/dist/settings/store.js +19 -0
  220. package/dist/shared/cache-control.d.ts +22 -0
  221. package/dist/shared/cache-control.js +25 -0
  222. package/dist/shared/index.d.ts +1 -0
  223. package/dist/shared/index.js +1 -0
  224. package/dist/templates/chat/netlify.toml +2 -0
  225. package/docs/content/deployment.mdx +2 -0
  226. package/docs/content/multi-app-workspace.mdx +1 -1
  227. package/package.json +2 -2
  228. package/src/templates/chat/netlify.toml +2 -0
@@ -28,6 +28,7 @@ export function Scrubber(props: ScrubberProps) {
28
28
  const [tooltip, setTooltip] = useState<
29
29
  | { kind: "comment"; content: string; ms: number }
30
30
  | { kind: "chapter"; title: string; ms: number }
31
+ | { kind: "reaction"; content: string; ms: number }
31
32
  | null
32
33
  >(null);
33
34
 
@@ -109,6 +110,17 @@ export function Scrubber(props: ScrubberProps) {
109
110
  return map;
110
111
  }, [comments]);
111
112
 
113
+ const reactionsByMs = useMemo(() => {
114
+ const map = new Map<number, { id: string; emoji: string }[]>();
115
+ recentReactions.forEach((reaction) => {
116
+ const key = Math.round(reaction.videoTimestampMs / 500) * 500;
117
+ const list = map.get(key) ?? [];
118
+ list.push({ id: reaction.id, emoji: reaction.emoji });
119
+ map.set(key, list);
120
+ });
121
+ return map;
122
+ }, [recentReactions]);
123
+
112
124
  return (
113
125
  <div
114
126
  className="relative h-10 flex items-center touch-none cursor-pointer"
@@ -142,7 +154,9 @@ export function Scrubber(props: ScrubberProps) {
142
154
  className="absolute -top-10 -translate-x-1/2 max-w-[240px] rounded bg-primary px-2 py-1 text-[11px] text-primary-foreground"
143
155
  style={{ left: (tooltip.ms / Math.max(1, durationMs)) * 100 + "%" }}
144
156
  >
145
- {tooltip.kind === "comment" ? tooltip.content : tooltip.title}
157
+ {tooltip.kind === "comment" || tooltip.kind === "reaction"
158
+ ? tooltip.content
159
+ : tooltip.title}
146
160
  </div>
147
161
  ) : null}
148
162
 
@@ -208,17 +222,36 @@ export function Scrubber(props: ScrubberProps) {
208
222
  </button>
209
223
  ))}
210
224
 
211
- {/* Reaction dots */}
212
- {recentReactions.map((r) => (
213
- <div
214
- key={r.id}
215
- className="absolute -bottom-4 -translate-x-1/2 text-[11px] pointer-events-none"
216
- style={{
217
- left: (r.videoTimestampMs / Math.max(1, durationMs)) * 100 + "%",
225
+ {/* Reaction markers */}
226
+ {Array.from(reactionsByMs.entries()).map(([ms, list]) => (
227
+ <button
228
+ key={`reaction-${ms}`}
229
+ type="button"
230
+ data-player-reaction-marker
231
+ onPointerDown={(event) => event.stopPropagation()}
232
+ onMouseEnter={() =>
233
+ setTooltip({
234
+ kind: "reaction",
235
+ content: `${list.map((reaction) => reaction.emoji).join(" ")} · ${list.length} reaction${list.length === 1 ? "" : "s"}`,
236
+ ms,
237
+ })
238
+ }
239
+ onMouseLeave={() => setTooltip(null)}
240
+ onClick={(event) => {
241
+ event.stopPropagation();
242
+ onSeek(ms);
218
243
  }}
244
+ className="absolute -bottom-5 flex h-5 min-w-5 -translate-x-1/2 items-center justify-center rounded-full bg-background/95 px-1 text-sm shadow-md ring-1 ring-primary/40 transition-transform hover:scale-125"
245
+ style={{ left: (ms / Math.max(1, durationMs)) * 100 + "%" }}
246
+ aria-label={`${list.length} reaction${list.length === 1 ? "" : "s"} at ${msToClock(ms)}`}
219
247
  >
220
- {r.emoji}
221
- </div>
248
+ {list[0].emoji}
249
+ {list.length > 1 ? (
250
+ <span className="absolute -right-1.5 -top-1.5 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-primary px-0.5 text-[8px] font-bold leading-none text-primary-foreground ring-1 ring-background">
251
+ {list.length}
252
+ </span>
253
+ ) : null}
254
+ </button>
222
255
  ))}
223
256
 
224
257
  {/* Thumb */}
@@ -57,6 +57,11 @@ import {
57
57
  type PlaybackComment,
58
58
  } from "./playback-comment-overlay";
59
59
  import { PlayerControls, SPEED_OPTIONS } from "./player-controls";
60
+ import type {
61
+ ReactionHandler,
62
+ ReactionHandlerResult,
63
+ ReactionSummary,
64
+ } from "./reactions-tray";
60
65
 
61
66
  function resolveLocalUrl(url: string | null | undefined): string | undefined {
62
67
  if (!url) return undefined;
@@ -251,7 +256,7 @@ export interface VideoPlayerProps {
251
256
  * visible only while `isFullscreen` is true.
252
257
  */
253
258
  enableReactions?: boolean;
254
- onReact?: (emoji: string) => void;
259
+ onReact?: ReactionHandler;
255
260
  enableComments?: boolean;
256
261
  /**
257
262
  * Opens the existing comment composer/panel. While fullscreen, the caller
@@ -374,6 +379,10 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
374
379
  const [currentMs, setCurrentMs] = useState(startMs ?? 0);
375
380
  currentMsRef.current = currentMs;
376
381
  isPlayingRef.current = isPlaying;
382
+ const [optimisticReactions, setOptimisticReactions] = useState<
383
+ ReactionSummary[]
384
+ >([]);
385
+ const optimisticReactionIdRef = useRef(0);
377
386
  const [loomStartMs, setLoomStartMs] = useState<number | null>(null);
378
387
  const [volume, setVolume] = useState(1);
379
388
  // Autoplaying players (e.g. the Slack unfurl embed, `?autoplay=1`) must
@@ -492,20 +501,101 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
492
501
  ? []
493
502
  : [{ startMs: editedMs, title: chapter.title }];
494
503
  }),
495
- reactions: (reactions ?? []).flatMap((reaction) => {
496
- const editedMs = mapMarker(reaction.videoTimestampMs);
497
- return editedMs === null
498
- ? []
499
- : [
500
- {
501
- id: reaction.id,
502
- emoji: reaction.emoji,
503
- videoTimestampMs: editedMs,
504
- },
505
- ];
506
- }),
504
+ reactions: [
505
+ ...(reactions ?? []).flatMap((reaction) => {
506
+ const editedMs = mapMarker(reaction.videoTimestampMs);
507
+ return editedMs === null
508
+ ? []
509
+ : [
510
+ {
511
+ id: reaction.id,
512
+ emoji: reaction.emoji,
513
+ videoTimestampMs: editedMs,
514
+ },
515
+ ];
516
+ }),
517
+ ...optimisticReactions.flatMap((reaction) => {
518
+ const editedMs = mapMarker(reaction.videoTimestampMs);
519
+ return editedMs === null
520
+ ? []
521
+ : [
522
+ {
523
+ id: reaction.id,
524
+ emoji: reaction.emoji,
525
+ videoTimestampMs: editedMs,
526
+ },
527
+ ];
528
+ }),
529
+ ],
507
530
  };
508
- }, [chapters, comments, currentMs, edits, reactions, resolvedDurationMs]);
531
+ }, [
532
+ chapters,
533
+ comments,
534
+ currentMs,
535
+ edits,
536
+ optimisticReactions,
537
+ reactions,
538
+ resolvedDurationMs,
539
+ ]);
540
+
541
+ useEffect(() => {
542
+ if (!optimisticReactions.length || !reactions?.length) return;
543
+ setOptimisticReactions((current) => {
544
+ const next = current.filter(
545
+ (optimistic) =>
546
+ !reactions.some(
547
+ (persisted) =>
548
+ persisted.emoji === optimistic.emoji &&
549
+ Math.abs(
550
+ persisted.videoTimestampMs - optimistic.videoTimestampMs,
551
+ ) < 1000,
552
+ ),
553
+ );
554
+ return next.length === current.length ? current : next;
555
+ });
556
+ }, [optimisticReactions.length, reactions]);
557
+
558
+ const handleReact = useCallback<ReactionHandler>(
559
+ (emoji) => {
560
+ const optimistic = {
561
+ id: `optimistic-reaction-${++optimisticReactionIdRef.current}`,
562
+ emoji,
563
+ videoTimestampMs: currentMsRef.current,
564
+ };
565
+ setOptimisticReactions((current) => [...current, optimistic]);
566
+
567
+ const removeOptimistic = () => {
568
+ setOptimisticReactions((current) =>
569
+ current.filter((reaction) => reaction.id !== optimistic.id),
570
+ );
571
+ };
572
+
573
+ let result: ReactionHandlerResult | undefined;
574
+ try {
575
+ result = onReact?.(emoji);
576
+ } catch {
577
+ removeOptimistic();
578
+ return false;
579
+ }
580
+
581
+ if (result && typeof result === "object" && "then" in result) {
582
+ return Promise.resolve(result).then(
583
+ (saved) => {
584
+ if (saved === false) removeOptimistic();
585
+ return saved !== false;
586
+ },
587
+ () => {
588
+ removeOptimistic();
589
+ return false;
590
+ },
591
+ );
592
+ }
593
+
594
+ if (result === false) removeOptimistic();
595
+ return result !== false;
596
+ },
597
+ [onReact],
598
+ );
509
599
  const activeVideoSourceIdentity = useMemo(
510
600
  () => videoSourceIdentity(activeVideoSrc),
511
601
  [activeVideoSrc],
@@ -2016,7 +2106,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
2016
2106
  menuPortalContainer={fullscreenMenuContainer}
2017
2107
  showReactionsAndComment={isFullscreen}
2018
2108
  enableReactions={enableReactions}
2019
- onReact={onReact}
2109
+ onReact={handleReact}
2020
2110
  enableComments={enableComments}
2021
2111
  onAddComment={onAddComment}
2022
2112
  />
@@ -106,8 +106,7 @@ export default function SettingsIndexRoute() {
106
106
  const builderStatus = useBuilderStatus();
107
107
  const connectRequestedRef = useRef(false);
108
108
  const builderConnect = useBuilderConnectFlow({
109
- popupUrl:
110
- builderStatus.status?.cliAuthUrl ?? builderStatus.status?.connectUrl,
109
+ popupUrl: builderStatus.status?.connectUrl,
111
110
  trackingSource: "clips_settings",
112
111
  trackingFlow: "clips_setup",
113
112
  onConnected: async () => {
@@ -1,7 +1,8 @@
1
+ import { withSsrHtmlContentType } from "@agent-native/core/shared";
1
2
  import { redirect } from "react-router";
2
3
 
3
4
  export function loader() {
4
- return redirect("/dictate");
5
+ return withSsrHtmlContentType(redirect("/dictate"));
5
6
  }
6
7
 
7
8
  export default function LegacyWisprRedirect() {
@@ -1,10 +1,10 @@
1
1
  import { DefaultSpinner } from "@agent-native/core/client/ui";
2
+ import { withSsrHtmlContentType } from "@agent-native/core/shared";
2
3
  import { redirect, type LoaderFunctionArgs } from "react-router";
3
4
 
4
5
  const SEO_TITLE = "Clips - Open Source screen recorder";
5
6
  const SEO_DESCRIPTION =
6
7
  "Open Source screen recorder and meeting-notes app with AI transcripts, summaries, search, dictation, and agent-readable share links.";
7
-
8
8
  export function meta() {
9
9
  return [
10
10
  { title: SEO_TITLE },
@@ -38,11 +38,15 @@ function buildTarget(url: URL): string {
38
38
  }
39
39
 
40
40
  export function loader({ url }: LoaderFunctionArgs) {
41
- throw redirect(buildTarget(url));
41
+ throw withSsrHtmlContentType(redirect(buildTarget(url)), {
42
+ varyByQuery: true,
43
+ });
42
44
  }
43
45
 
44
46
  export function clientLoader({ url }: LoaderFunctionArgs) {
45
- throw redirect(buildTarget(url));
47
+ throw withSsrHtmlContentType(redirect(buildTarget(url)), {
48
+ varyByQuery: true,
49
+ });
46
50
  }
47
51
 
48
52
  export function HydrateFallback() {
@@ -1628,7 +1628,7 @@ export default function RecordingPage() {
1628
1628
  onReact={(emoji) => {
1629
1629
  tracking.reportReaction(emoji);
1630
1630
  const liveMs = resolvePlaybackMs();
1631
- fetch(
1631
+ return fetch(
1632
1632
  agentNativePath(
1633
1633
  "/_agent-native/actions/react-to-recording",
1634
1634
  ),
@@ -1647,9 +1647,11 @@ export default function RecordingPage() {
1647
1647
  throw new Error(`react failed: ${res.status}`);
1648
1648
  return playerDataQ.refetch();
1649
1649
  })
1650
- .catch((err) =>
1651
- console.warn("[clips] react failed", err),
1652
- );
1650
+ .then(() => true)
1651
+ .catch((err) => {
1652
+ console.warn("[clips] react failed", err);
1653
+ return false;
1654
+ });
1653
1655
  }}
1654
1656
  className="h-full w-full rounded-none sm:rounded-xl"
1655
1657
  />
@@ -1736,11 +1738,12 @@ export default function RecordingPage() {
1736
1738
  ) : null}
1737
1739
  {recording.enableReactions ? (
1738
1740
  <ReactionsTray
1741
+ reactions={reactions}
1739
1742
  disabled={!recording.enableReactions || !canComment}
1740
1743
  onReact={(emoji) => {
1741
1744
  tracking.reportReaction(emoji);
1742
1745
  const liveMs = resolvePlaybackMs();
1743
- fetch(
1746
+ return fetch(
1744
1747
  agentNativePath(
1745
1748
  "/_agent-native/actions/react-to-recording",
1746
1749
  ),
@@ -1759,9 +1762,11 @@ export default function RecordingPage() {
1759
1762
  throw new Error(`react failed: ${res.status}`);
1760
1763
  return playerDataQ.refetch();
1761
1764
  })
1762
- .catch((err) =>
1763
- console.warn("[clips] react failed", err),
1764
- );
1765
+ .then(() => true)
1766
+ .catch((err) => {
1767
+ console.warn("[clips] react failed", err);
1768
+ return false;
1769
+ });
1765
1770
  }}
1766
1771
  />
1767
1772
  ) : null}
@@ -1274,7 +1274,7 @@ export default function ShareRoute() {
1274
1274
  onReact={(emoji) => {
1275
1275
  if (!session) {
1276
1276
  requireSignIn("react");
1277
- return;
1277
+ return false;
1278
1278
  }
1279
1279
  tracking.reportReaction(emoji);
1280
1280
  const liveCt = isLoomEmbedBacked
@@ -1287,7 +1287,7 @@ export default function ShareRoute() {
1287
1287
  liveCt < 1e7
1288
1288
  ? Math.floor(liveCt * 1000)
1289
1289
  : currentMs;
1290
- fetch(
1290
+ return fetch(
1291
1291
  agentNativePath("/_agent-native/actions/react-to-recording"),
1292
1292
  {
1293
1293
  method: "POST",
@@ -1303,7 +1303,11 @@ export default function ShareRoute() {
1303
1303
  if (!res.ok) throw new Error(`react failed: ${res.status}`);
1304
1304
  return dataQ.refetch();
1305
1305
  })
1306
- .catch((err) => console.warn("[clips] react failed", err));
1306
+ .then(() => true)
1307
+ .catch((err) => {
1308
+ console.warn("[clips] react failed", err);
1309
+ return false;
1310
+ });
1307
1311
  }}
1308
1312
  className="h-full w-full rounded-none sm:rounded-xl"
1309
1313
  />
@@ -1371,10 +1375,11 @@ export default function ShareRoute() {
1371
1375
  ) : null}
1372
1376
  {recording.enableReactions ? (
1373
1377
  <ReactionsTray
1378
+ reactions={reactions}
1374
1379
  onReact={(emoji) => {
1375
1380
  if (!session) {
1376
1381
  requireSignIn("react");
1377
- return;
1382
+ return false;
1378
1383
  }
1379
1384
  tracking.reportReaction(emoji);
1380
1385
  const liveCt = isLoomEmbedBacked
@@ -1387,7 +1392,7 @@ export default function ShareRoute() {
1387
1392
  liveCt < 1e7
1388
1393
  ? Math.floor(liveCt * 1000)
1389
1394
  : currentMs;
1390
- fetch(
1395
+ return fetch(
1391
1396
  agentNativePath(
1392
1397
  "/_agent-native/actions/react-to-recording",
1393
1398
  ),
@@ -1406,9 +1411,11 @@ export default function ShareRoute() {
1406
1411
  throw new Error(`react failed: ${res.status}`);
1407
1412
  return dataQ.refetch();
1408
1413
  })
1409
- .catch((err) =>
1410
- console.warn("[clips] react failed", err),
1411
- );
1414
+ .then(() => true)
1415
+ .catch((err) => {
1416
+ console.warn("[clips] react failed", err);
1417
+ return false;
1418
+ });
1412
1419
  }}
1413
1420
  />
1414
1421
  ) : null}
@@ -16,6 +16,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
16
16
 
17
17
  [context.production.environment]
18
18
  AGENT_NATIVE_HOSTED_HARNESS = "true"
19
+ AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
20
+ AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
19
21
 
20
22
  # Netlify Functions run on linux/x64 (AWS Lambda). This tells the deploy build
21
23
  # to bundle the matching ffmpeg-static binary into the server bundle so frame
@@ -16,6 +16,11 @@ export default defineEventHandler((event) => {
16
16
  return new Response(null, {
17
17
  status: 302,
18
18
  headers: {
19
+ // Keep this redirect on the same public SSR-cache path as the shell.
20
+ // Without a content type the deploy adapter cannot apply the canonical
21
+ // cache policy, so every visit to `/` reaches the origin before the
22
+ // already-cached `/library` shell can load.
23
+ "content-type": "text/html; charset=utf-8",
19
24
  location: "/library",
20
25
  "Permissions-Policy": MEDIA_CAPTURE_PERMISSIONS_POLICY,
21
26
  },
@@ -16,6 +16,8 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
16
16
 
17
17
  [context.production.environment]
18
18
  AGENT_NATIVE_HOSTED_HARNESS = "true"
19
+ AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
20
+ AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
19
21
 
20
22
  # A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
21
23
  # The framework does not impose a default agent-run timeout; if a template sets
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: design-editor-architecture
3
+ description: >-
4
+ Where design editor behavior lives and how to navigate DesignEditor.tsx. Use
5
+ before changing any editor behavior — undo/redo, paste, delete, duplicate,
6
+ style commit, layer move/rename, export, structure change — or before opening
7
+ `app/pages/DesignEditor.tsx`.
8
+ scope: dev
9
+ metadata:
10
+ internal: true
11
+ ---
12
+
13
+ # Design Editor Architecture
14
+
15
+ ## The routing rule
16
+
17
+ Editor **behavior** lives in `app/pages/design-editor/`, not in
18
+ `app/pages/DesignEditor.tsx`.
19
+
20
+ `DesignEditor.tsx` is ~20,900 lines and holds only three things: state
21
+ declarations, the `useCallback` wrappers that gather arguments, and the JSX.
22
+ Every wrapper delegates to a `run<Name>()` command module.
23
+
24
+ **To change what an editor action does, edit the command module.** Opening
25
+ `DesignEditor.tsx` to change behavior is almost always the wrong move — it will
26
+ exhaust your context before you find the code.
27
+
28
+ | Directory | Holds |
29
+ | --- | --- |
30
+ | `design-editor/commands/` | 86 one-per-action modules, each exporting `run<Name>(args, …)`. Start at `commands/README.md` |
31
+ | `design-editor/effects/` | Subscription and autosave loops (collab text, motion autosave, agent selection mirroring) |
32
+ | `design-editor/derive/` | Pure derivations (`overview-screens.ts`, `design-breakpoints.ts`) |
33
+ | `design-editor/domains/` | Whole-domain hooks owning state + refs + effects + handlers together (`use-tweaks.ts`) |
34
+ | `design-editor/*.ts` | Shared helpers: `history.ts`, `selection-state.ts`, `pending-edits.ts`, `editor-state.ts`, `editor-helpers.ts`, … |
35
+
36
+ ## Common task → file
37
+
38
+ | Task | File |
39
+ | --- | --- |
40
+ | Undo / redo | `commands/undo.ts`, `commands/redo.ts` |
41
+ | Paste | `commands/editor-paste.ts` routes; `commands/paste-selection.ts` and `commands/paste-over-selection.ts` do layers |
42
+ | Copy | `commands/copy-selection.ts` |
43
+ | Delete | `commands/delete-selection.ts` (layers), `commands/delete-files.ts` (screens) |
44
+ | Duplicate | `commands/duplicate-selection.ts`, `commands/duplicate-screen.ts` |
45
+ | Style commit | `commands/commit-visual-styles.ts` |
46
+ | Inspector style edit | `commands/style-change.ts` (one property), `commands/styles-change.ts` (many) |
47
+ | Structure change | `commands/visual-structure-change.ts`, `commands/screen-visual-structure-change.ts` |
48
+ | Layer move / rename | `commands/layer-move.ts`, `commands/layer-move-to-screen.ts`, `commands/layer-rename.ts` |
49
+ | Lock / hide a layer | `commands/toggle-layer-locked.ts`, `commands/toggle-layer-hidden.ts` |
50
+ | Export | `commands/render-png-blob.ts`, `download-pdf.ts`, `download-svg.ts`, `copy-as-figma-svg.ts` |
51
+ | Save / persistence | `commands/save-file-content.ts`, `commands/apply-file-content-update.ts` |
52
+ | Breakpoints | `responsive-breakpoints` skill; `derive/design-breakpoints.ts` |
53
+
54
+ A `screen-` prefix means the command is addressed by an explicit `screenId`
55
+ (overview canvas or board). The unprefixed twin acts on the focused screen.
56
+
57
+ Before adding a `domains/` hook, count what it would expose. Past roughly 16
58
+ returned values the hook stops hiding anything and just relocates the wiring —
59
+ measured surfaces for share/export (20), generation (18), and motion (48) are
60
+ all why those still live inline. Also check no input it needs is declared after
61
+ the point where its own outputs are first consumed; responsive-interact fails
62
+ that test and cannot be extracted without changing when values are read.
63
+
64
+ ## Navigating DesignEditor.tsx when you must
65
+
66
+ The file carries ~82 section banners. This prints a table of contents:
67
+
68
+ ```bash
69
+ grep -n "──" app/pages/DesignEditor.tsx
70
+ ```
71
+
72
+ Read one region with an offset and a limit instead of opening the file. Every
73
+ section is under ~800 lines. Banners use `// ── Name ──` in the component body
74
+ and `{/* ── Render: name ── */}` inside the JSX.
75
+
76
+ When you add or move a region, add a banner for it.
77
+
78
+ ## Hard constraints
79
+
80
+ **`DesignEditor.tsx` must keep exactly one runtime export — the default — and
81
+ zero runtime named exports.** Type-only exports are fine. Both routes
82
+ (`app/routes/design.$id.tsx`, `app/routes/visual-edit.$id.tsx`) import that
83
+ default. A named export breaks React Fast Refresh for the whole editor.
84
+
85
+ **The render-callback trio's dependency arrays are load-bearing.**
86
+ `DesignEditor.routeRefreshBoundary.test.ts` parses the file with the TypeScript
87
+ compiler and enforces:
88
+
89
+ - `renderScreenContent` deps are exactly `[renderEditableScreenContent]`
90
+ - `renderBreakpointContent` deps are exactly `[renderEditableScreenContent]`
91
+ - `renderEditableScreenContent` deps include `activeBreakpointWidthState`,
92
+ `motionDefaultEase`, `motionDurationMs`, `inScreenGradientEditTarget`,
93
+ `handleInScreenGradientEditChange`, and `statePreviewTarget`
94
+
95
+ That last one exists because cached overview canvases must invalidate on
96
+ preview-only state changes. Dropping a name from it renders a stale canvas.
97
+
98
+ **Many specs read source as text.** ~20 specs `readFileSync` either
99
+ `DesignEditor.tsx` or a command module and slice it with `indexOf` markers, then
100
+ assert on the source string. Moving code breaks them with a confusing failure.
101
+ Re-point the path and the marker in the same commit that moves the code. Prefer
102
+ asserting against the command module (`commandSource("undo.ts")`) over the
103
+ editor file.
@@ -907,7 +907,8 @@ read `references/code-layers.md`. For the VS Code-style source workbench
907
907
  reserved for editor-internal state (selection chrome, editor-chrome scale
908
908
  compensation, clipboard/surface tokens) — the editor strips any property
909
909
  under those prefixes before persisting a cross-screen style capture
910
- (`isEditorInternalCssVar` in `app/pages/DesignEditor.tsx`), so a design that
910
+ (`isEditorInternalCssVar` in `app/pages/design-editor/portable-style.ts`),
911
+ so a design that
911
912
  defines its own theming under that namespace would silently lose those
912
913
  values on a cross-screen move. Use `--color-*`, `--font-*`, or another
913
914
  design-owned prefix for tokens/tweaks instead.
@@ -99,4 +99,6 @@ Read the relevant skill before deeper work in that area.
99
99
  ## Source Changes
100
100
 
101
101
  Before building common workspace or agent UI, read `agent-native-toolkit`; read
102
- `customizing-agent-native` before adapting shared UI.
102
+ `customizing-agent-native` before adapting shared UI. Editor behavior lives in
103
+ `app/pages/design-editor/commands/*.ts`, not `DesignEditor.tsx` — read
104
+ `design-editor-architecture` before changing it.
@@ -1768,14 +1768,15 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
1768
1768
  // Editor-internal CSS custom-property prefixes — selection chrome colors,
1769
1769
  // editor-chrome scale compensation, framework clipboard/surface tokens.
1770
1770
  // These have no meaning outside this editor session and must never leak
1771
- // into persisted user HTML/exports. DesignEditor.tsx's
1771
+ // into persisted user HTML/exports. design-editor/portable-style.ts's
1772
1772
  // applyPortableStyleSnapshotToHtml (isEditorInternalCssVar /
1773
1773
  // EDITOR_INTERNAL_CSS_VAR_PREFIXES) already filters them back out on the
1774
1774
  // apply side; filtering here too at COLLECTION time is pure bloat
1775
1775
  // reduction (skips carrying them across the postMessage boundary at all)
1776
1776
  // and changes no observable behavior on the apply side.
1777
1777
  //
1778
- // keep in sync with DesignEditor.tsx's EDITOR_INTERNAL_CSS_VAR_PREFIXES
1778
+ // keep in sync with design-editor/portable-style.ts's
1779
+ // EDITOR_INTERNAL_CSS_VAR_PREFIXES
1779
1780
  var EDITOR_INTERNAL_CSS_VAR_PREFIXES = [
1780
1781
  "--design-editor-",
1781
1782
  "--agent-native-editor-chrome-",
@@ -0,0 +1,25 @@
1
+ export function AgentNativeMenuMark({ className }: { className?: string }) {
2
+ return (
3
+ <svg
4
+ viewBox="-5 -5 145 88"
5
+ fill="none"
6
+ aria-hidden="true"
7
+ className={className}
8
+ >
9
+ <path
10
+ d="M29.0771 77.8838H2.5L18.9 48.9L43.2 6.6C44 5.2 45.9 5.2 46.7 6.6L69.1 44.2C69.9 45.5 69 46.7305 67.5 46.7305H48.3C47.6 46.7305 46.9 47.1 46.6 47.7L30.8 76.9C30.45 77.5 29.8 77.8838 29.0771 77.8838Z"
11
+ stroke="currentColor"
12
+ strokeWidth="10.5"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <path
17
+ d="M105.927 0H132.5C134 0 134.9 1.6 134.15 2.9L91.5 76.9C91.15 77.5 90.5 77.8853 89.8 77.8853H63.8C62.3 77.8853 61.4 76.3 62.15 75L104.2 1C104.55 0.38 105.2 0 105.927 0Z"
18
+ stroke="currentColor"
19
+ strokeWidth="10.5"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }