@agent-native/core 0.68.3 → 0.70.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 (212) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +54 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/changelog/parse.ts +245 -0
  5. package/corpus/core/src/cli/changelog.ts +199 -0
  6. package/corpus/core/src/cli/index.ts +19 -0
  7. package/corpus/core/src/cli/recap.ts +24 -1
  8. package/corpus/core/src/cli/skills.ts +72 -45
  9. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  10. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  11. package/corpus/core/src/client/analytics.ts +181 -0
  12. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  13. package/corpus/core/src/client/index.ts +11 -0
  14. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  15. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  16. package/corpus/core/src/extensions/web-search-tool.ts +45 -4
  17. package/corpus/core/src/mcp/build-server.ts +1 -1
  18. package/corpus/core/src/mcp/embed-app.ts +112 -6
  19. package/corpus/core/src/secrets/register-framework-secrets.ts +7 -0
  20. package/corpus/core/src/server/attribution.ts +211 -0
  21. package/corpus/core/src/server/auth.ts +6 -0
  22. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  23. package/corpus/core/src/server/builder-browser.ts +27 -1
  24. package/corpus/core/src/server/core-routes-plugin.ts +58 -0
  25. package/corpus/core/src/server/google-oauth.ts +19 -6
  26. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  27. package/corpus/core/src/styles/agent-conversation.css +21 -0
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  29. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  30. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  31. package/corpus/templates/analytics/actions/install-dashboard-template.ts +2 -2
  32. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  33. package/corpus/templates/analytics/actions/update-dashboard.ts +5 -3
  34. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  35. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -2
  36. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  37. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -213
  38. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -3
  39. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  40. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  41. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  43. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  44. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  45. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  46. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  47. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  48. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  49. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  50. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  51. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  52. package/corpus/templates/analytics/server/db/index.ts +1 -1
  53. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  54. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  55. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  56. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  57. package/corpus/templates/assets/CHANGELOG.md +10 -0
  58. package/corpus/templates/assets/app/root.tsx +7 -1
  59. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  60. package/corpus/templates/brain/CHANGELOG.md +10 -0
  61. package/corpus/templates/brain/app/root.tsx +7 -1
  62. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  63. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  64. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  65. package/corpus/templates/calendar/app/root.tsx +7 -1
  66. package/corpus/templates/chat/CHANGELOG.md +10 -0
  67. package/corpus/templates/chat/app/root.tsx +7 -1
  68. package/corpus/templates/clips/CHANGELOG.md +10 -0
  69. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  70. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  71. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  72. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +72 -20
  73. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +69 -2
  74. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  75. package/corpus/templates/clips/app/lib/recorder-preferences.ts +67 -0
  76. package/corpus/templates/clips/app/root.tsx +7 -1
  77. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  78. package/corpus/templates/clips/app/routes/_app.trash.tsx +21 -0
  79. package/corpus/templates/clips/app/routes/record.tsx +92 -8
  80. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  81. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  86. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  87. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  88. package/corpus/templates/clips/desktop/src/styles.css +49 -0
  89. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  90. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +67 -1
  91. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  92. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  93. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +51 -1
  94. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  95. package/corpus/templates/content/CHANGELOG.md +10 -0
  96. package/corpus/templates/content/app/root.tsx +7 -1
  97. package/corpus/templates/content/vitest.config.ts +3 -0
  98. package/corpus/templates/design/CHANGELOG.md +10 -0
  99. package/corpus/templates/design/actions/generate-design.ts +4 -0
  100. package/corpus/templates/design/app/root.tsx +7 -1
  101. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  102. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  103. package/corpus/templates/dispatch/app/root.tsx +7 -1
  104. package/corpus/templates/forms/CHANGELOG.md +10 -0
  105. package/corpus/templates/forms/app/root.tsx +7 -1
  106. package/corpus/templates/macros/CHANGELOG.md +10 -0
  107. package/corpus/templates/macros/app/root.tsx +7 -1
  108. package/corpus/templates/mail/CHANGELOG.md +10 -0
  109. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  110. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  111. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  112. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  113. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  114. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  115. package/corpus/templates/plan/AGENTS.md +4 -0
  116. package/corpus/templates/plan/CHANGELOG.md +10 -0
  117. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  118. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  119. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  120. package/corpus/templates/plan/app/root.tsx +7 -1
  121. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  122. package/corpus/templates/slides/CHANGELOG.md +10 -0
  123. package/corpus/templates/slides/app/root.tsx +7 -1
  124. package/corpus/templates/videos/CHANGELOG.md +4 -151
  125. package/corpus/templates/videos/app/root.tsx +7 -1
  126. package/dist/changelog/parse.d.ts +70 -0
  127. package/dist/changelog/parse.d.ts.map +1 -0
  128. package/dist/changelog/parse.js +200 -0
  129. package/dist/changelog/parse.js.map +1 -0
  130. package/dist/cli/changelog.d.ts +4 -0
  131. package/dist/cli/changelog.d.ts.map +1 -0
  132. package/dist/cli/changelog.js +166 -0
  133. package/dist/cli/changelog.js.map +1 -0
  134. package/dist/cli/index.js +18 -0
  135. package/dist/cli/index.js.map +1 -1
  136. package/dist/cli/recap.d.ts.map +1 -1
  137. package/dist/cli/recap.js +22 -1
  138. package/dist/cli/recap.js.map +1 -1
  139. package/dist/cli/skills.d.ts +3 -3
  140. package/dist/cli/skills.d.ts.map +1 -1
  141. package/dist/cli/skills.js +72 -45
  142. package/dist/cli/skills.js.map +1 -1
  143. package/dist/client/CommandMenu.d.ts +17 -1
  144. package/dist/client/CommandMenu.d.ts.map +1 -1
  145. package/dist/client/CommandMenu.js +49 -13
  146. package/dist/client/CommandMenu.js.map +1 -1
  147. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  148. package/dist/client/agent-chat-adapter.js +43 -6
  149. package/dist/client/agent-chat-adapter.js.map +1 -1
  150. package/dist/client/analytics.d.ts +18 -0
  151. package/dist/client/analytics.d.ts.map +1 -1
  152. package/dist/client/analytics.js +177 -0
  153. package/dist/client/analytics.js.map +1 -1
  154. package/dist/client/changelog/Changelog.d.ts +47 -0
  155. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  156. package/dist/client/changelog/Changelog.js +138 -0
  157. package/dist/client/changelog/Changelog.js.map +1 -0
  158. package/dist/client/index.d.ts +2 -1
  159. package/dist/client/index.d.ts.map +1 -1
  160. package/dist/client/index.js +2 -1
  161. package/dist/client/index.js.map +1 -1
  162. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  163. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  164. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  165. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  166. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  167. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  168. package/dist/client/settings/useBuilderStatus.js +58 -10
  169. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  170. package/dist/extensions/web-search-tool.d.ts +5 -3
  171. package/dist/extensions/web-search-tool.d.ts.map +1 -1
  172. package/dist/extensions/web-search-tool.js +36 -4
  173. package/dist/extensions/web-search-tool.js.map +1 -1
  174. package/dist/mcp/build-server.js +1 -1
  175. package/dist/mcp/build-server.js.map +1 -1
  176. package/dist/mcp/embed-app.d.ts.map +1 -1
  177. package/dist/mcp/embed-app.js +112 -6
  178. package/dist/mcp/embed-app.js.map +1 -1
  179. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  180. package/dist/secrets/register-framework-secrets.js +6 -0
  181. package/dist/secrets/register-framework-secrets.js.map +1 -1
  182. package/dist/server/attribution.d.ts +83 -0
  183. package/dist/server/attribution.d.ts.map +1 -0
  184. package/dist/server/attribution.js +178 -0
  185. package/dist/server/attribution.js.map +1 -0
  186. package/dist/server/auth.d.ts.map +1 -1
  187. package/dist/server/auth.js +7 -0
  188. package/dist/server/auth.js.map +1 -1
  189. package/dist/server/better-auth-instance.d.ts +9 -1
  190. package/dist/server/better-auth-instance.d.ts.map +1 -1
  191. package/dist/server/better-auth-instance.js +32 -2
  192. package/dist/server/better-auth-instance.js.map +1 -1
  193. package/dist/server/builder-browser.d.ts +4 -0
  194. package/dist/server/builder-browser.d.ts.map +1 -1
  195. package/dist/server/builder-browser.js +20 -1
  196. package/dist/server/builder-browser.js.map +1 -1
  197. package/dist/server/core-routes-plugin.d.ts +25 -0
  198. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  199. package/dist/server/core-routes-plugin.js +38 -0
  200. package/dist/server/core-routes-plugin.js.map +1 -1
  201. package/dist/server/google-oauth.d.ts.map +1 -1
  202. package/dist/server/google-oauth.js +16 -2
  203. package/dist/server/google-oauth.js.map +1 -1
  204. package/dist/shared/mcp-embed-headers.js +1 -1
  205. package/dist/shared/mcp-embed-headers.js.map +1 -1
  206. package/dist/styles/agent-conversation.css +21 -0
  207. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  208. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  209. package/package.json +1 -1
  210. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  211. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  212. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-23
4
+ ---
5
+
6
+ Screen + camera recordings now keep the presenter's face in the thumbnail shown on the library card, video poster, and Slack unfurl
@@ -1473,6 +1473,22 @@ async function showRegionGuidesForRecording(wantsScreen: boolean) {
1473
1473
  });
1474
1474
  }
1475
1475
 
1476
+ // Frame the chosen screen region with a live border so the user can see exactly
1477
+ // what's being captured throughout the countdown and recording. The overlay is
1478
+ // capture-excluded and draws its stroke outside the captured pixels, so it never
1479
+ // lands in the video. Torn down with the rest of the recording chrome on stop.
1480
+ async function showRegionRecordBorder(region: RegionCaptureRect | null) {
1481
+ if (!region) return;
1482
+ await invoke("show_region_record_border", {
1483
+ x: region.x,
1484
+ y: region.y,
1485
+ width: region.width,
1486
+ height: region.height,
1487
+ }).catch((err) => {
1488
+ console.warn("[clips-recorder] show_region_record_border failed:", err);
1489
+ });
1490
+ }
1491
+
1476
1492
  async function runRecordingCountdown(wantsScreen: boolean, audioCue: AudioCue) {
1477
1493
  const countdownEvent = waitForCountdownEvent(
1478
1494
  COUNTDOWN_EVENT_TIMEOUT_MS,
@@ -1566,6 +1582,10 @@ async function startNativeFullscreenRecording(
1566
1582
 
1567
1583
  if (params.source === "region") {
1568
1584
  captureRegion = await selectRegionForRecording();
1585
+ // Frame the selected area now so it stays visible through the countdown
1586
+ // and the whole recording. hide_recording_chrome / hide_overlays tear it
1587
+ // down on stop, cancel, and the error paths below.
1588
+ await showRegionRecordBorder(captureRegion);
1569
1589
  }
1570
1590
 
1571
1591
  if (localOnly && localRecordingMode === "separate" && wantsCamera) {
@@ -12,6 +12,7 @@ import { MeetingNub } from "./overlays/meeting-nub";
12
12
  import { FlowBar } from "./overlays/flow-bar";
13
13
  import { RecordingPill } from "./overlays/recording-pill";
14
14
  import { RegionGuideEditor, RegionGuides } from "./overlays/region-guides";
15
+ import { RegionRecordBorder } from "./overlays/region-record-border";
15
16
  import "./styles.css";
16
17
 
17
18
  /**
@@ -55,6 +56,8 @@ function pickRoute(route: string): React.ReactElement {
55
56
  return <RegionGuideEditor />;
56
57
  case "region-capture-selector":
57
58
  return <RegionGuideEditor mode="capture" />;
59
+ case "region-record-border":
60
+ return <RegionRecordBorder />;
58
61
  default:
59
62
  return <App />;
60
63
  }
@@ -0,0 +1,76 @@
1
+ import { useEffect, useState } from "react";
2
+ import { getCurrentWindow } from "@tauri-apps/api/window";
3
+
4
+ interface BorderRect {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ }
10
+
11
+ function clamp01(value: number): number {
12
+ if (!Number.isFinite(value)) return 0;
13
+ return Math.min(1, Math.max(0, value));
14
+ }
15
+
16
+ /**
17
+ * The live recording region is handed to this overlay window as a normalized
18
+ * rect in the URL query (`?region=x,y,width,height`) so it never has to wait
19
+ * on a Tauri event (which could race the webview load). All four values are
20
+ * fractions [0,1] of the recording monitor — the same space the region-capture
21
+ * selector emitted them in.
22
+ */
23
+ function parseRect(search: string): BorderRect | null {
24
+ const raw = new URLSearchParams(search).get("region");
25
+ if (!raw) return null;
26
+ const parts = raw.split(",").map((value) => Number.parseFloat(value));
27
+ if (parts.length !== 4 || parts.some((value) => !Number.isFinite(value))) {
28
+ return null;
29
+ }
30
+ const [x, y, width, height] = parts;
31
+ if (width <= 0 || height <= 0) return null;
32
+ return {
33
+ x: clamp01(x),
34
+ y: clamp01(y),
35
+ width: clamp01(width),
36
+ height: clamp01(height),
37
+ };
38
+ }
39
+
40
+ /**
41
+ * A thin frame painted around the screen region currently being recorded. The
42
+ * window itself is full-screen, click-through, and capture-excluded (see
43
+ * `show_region_record_border` on the Rust side). The frame is drawn entirely
44
+ * OUTWARD via an outset box-shadow with no fill, so its pixels sit just outside
45
+ * the captured rect and never leak into the recording even on the macOS 15.4+
46
+ * builds where `NSWindowSharingNone` is occasionally bypassed.
47
+ */
48
+ export function RegionRecordBorder() {
49
+ const [rect] = useState<BorderRect | null>(() =>
50
+ typeof window === "undefined" ? null : parseRect(window.location.search),
51
+ );
52
+
53
+ useEffect(() => {
54
+ if (rect) return;
55
+ // No usable region — close the overlay so an empty window never lingers.
56
+ getCurrentWindow()
57
+ .close()
58
+ .catch(() => {});
59
+ }, [rect]);
60
+
61
+ if (!rect) return null;
62
+
63
+ return (
64
+ <div className="region-record-border-layer" aria-hidden>
65
+ <div
66
+ className="region-record-border-frame"
67
+ style={{
68
+ left: `${rect.x * 100}%`,
69
+ top: `${rect.y * 100}%`,
70
+ width: `${rect.width * 100}%`,
71
+ height: `${rect.height * 100}%`,
72
+ }}
73
+ />
74
+ </div>
75
+ );
76
+ }
@@ -3749,6 +3749,55 @@ body[data-clips-route="recording-pill"] #root {
3749
3749
  backdrop-filter: blur(14px);
3750
3750
  }
3751
3751
 
3752
+ /* ------------------------------------------------------------------------- */
3753
+ /* Overlay: live recording-region border */
3754
+ /* ------------------------------------------------------------------------- */
3755
+
3756
+ .region-record-border-layer {
3757
+ position: fixed;
3758
+ inset: 0;
3759
+ width: 100vw;
3760
+ height: 100vh;
3761
+ overflow: hidden;
3762
+ pointer-events: none;
3763
+ }
3764
+
3765
+ .region-record-border-frame {
3766
+ position: absolute;
3767
+ border-radius: 5px;
3768
+ /* No fill, and the stroke is painted OUTWARD with an outset box-shadow so it
3769
+ lands just outside the captured rect — never inside the recorded video. */
3770
+ box-shadow:
3771
+ 0 0 0 2px rgba(239, 68, 68, 0.95),
3772
+ 0 0 0 4px rgba(239, 68, 68, 0.32),
3773
+ 0 0 26px 6px rgba(239, 68, 68, 0.26);
3774
+ animation: region-record-border-pulse 1.8s ease-in-out infinite;
3775
+ }
3776
+
3777
+ /* Gentle pulse that mirrors the live recording dot, signalling "recording
3778
+ now". Honour reduced-motion by holding the frame steady. */
3779
+ @keyframes region-record-border-pulse {
3780
+ 0%,
3781
+ 100% {
3782
+ box-shadow:
3783
+ 0 0 0 2px rgba(239, 68, 68, 0.9),
3784
+ 0 0 0 4px rgba(239, 68, 68, 0.28),
3785
+ 0 0 22px 5px rgba(239, 68, 68, 0.2);
3786
+ }
3787
+ 50% {
3788
+ box-shadow:
3789
+ 0 0 0 2px rgba(239, 68, 68, 1),
3790
+ 0 0 0 4px rgba(239, 68, 68, 0.44),
3791
+ 0 0 30px 8px rgba(239, 68, 68, 0.34);
3792
+ }
3793
+ }
3794
+
3795
+ @media (prefers-reduced-motion: reduce) {
3796
+ .region-record-border-frame {
3797
+ animation: none;
3798
+ }
3799
+ }
3800
+
3752
3801
  @keyframes flow-bar-in {
3753
3802
  from {
3754
3803
  opacity: 0;
@@ -15,7 +15,8 @@
15
15
  "flow-bar",
16
16
  "recording-pill",
17
17
  "region-guides",
18
- "region-guide-editor"
18
+ "region-guide-editor",
19
+ "region-record-border"
19
20
  ],
20
21
  "permissions": [
21
22
  "core:default",
@@ -31,6 +31,7 @@ const FINALIZING_LABEL: &str = "finalizing";
31
31
  const FLOW_BAR_LABEL: &str = "flow-bar";
32
32
  const REGION_GUIDES_LABEL: &str = "region-guides";
33
33
  const REGION_GUIDE_EDITOR_LABEL: &str = "region-guide-editor";
34
+ const REGION_RECORD_BORDER_LABEL: &str = "region-record-border";
34
35
 
35
36
  /// Physical-pixel bubble sizes. Logical px on retina = physical / 2, so these
36
37
  /// map to ~96 (small) and ~180 (medium) logical px — matching Loom's camera
@@ -448,6 +449,65 @@ pub async fn hide_region_guides(app: AppHandle) -> Result<(), String> {
448
449
  Ok(())
449
450
  }
450
451
 
452
+ /// Live border framing the region currently being recorded. Like the region
453
+ /// guides this is a full-screen, click-through, capture-excluded overlay — but
454
+ /// the rect is ephemeral (it belongs to one recording, not the saved preset),
455
+ /// so it's handed in via the URL query rather than read from config. The React
456
+ /// view paints only an OUTWARD frame so the stroke stays out of the captured
457
+ /// pixels. The recording flow owns this window; it's torn down by
458
+ /// `hide_recording_chrome` / `hide_overlays` when capture stops.
459
+ #[tauri::command]
460
+ pub async fn show_region_record_border(
461
+ app: AppHandle,
462
+ x: f64,
463
+ y: f64,
464
+ width: f64,
465
+ height: f64,
466
+ ) -> Result<(), String> {
467
+ if !(width > 0.0 && height > 0.0) {
468
+ return Ok(());
469
+ }
470
+ if let Some(existing) = app.get_webview_window(REGION_RECORD_BORDER_LABEL) {
471
+ let _ = existing.close();
472
+ }
473
+
474
+ let (mx, my, mw, mh) = tray_monitor_physical_rect(&app);
475
+ let url = WebviewUrl::App(
476
+ format!("index.html?region={x:.6},{y:.6},{width:.6},{height:.6}#region-record-border")
477
+ .into(),
478
+ );
479
+ let win = WebviewWindowBuilder::new(&app, REGION_RECORD_BORDER_LABEL, url)
480
+ .title("Clips Recording Region")
481
+ .decorations(false)
482
+ .transparent(true)
483
+ .always_on_top(true)
484
+ .skip_taskbar(true)
485
+ .resizable(false)
486
+ .shadow(false)
487
+ .visible(false)
488
+ .focused(false)
489
+ .build()
490
+ .map_err(|e| {
491
+ eprintln!("[clips-tray] region record border build failed: {}", e);
492
+ e.to_string()
493
+ })?;
494
+ let _ = win.set_size(tauri::Size::Physical(PhysicalSize::new(mw, mh)));
495
+ let _ = win.set_position(PhysicalPosition::new(mx, my));
496
+ let _ = win.set_ignore_cursor_events(true);
497
+ set_capture_excluded_always(&win);
498
+ configure_overlay_behavior(&win);
499
+ crate::util::show_without_activation(&win);
500
+ Ok(())
501
+ }
502
+
503
+ #[tauri::command]
504
+ pub async fn hide_region_record_border(app: AppHandle) -> Result<(), String> {
505
+ if let Some(w) = app.get_webview_window(REGION_RECORD_BORDER_LABEL) {
506
+ let _ = w.close();
507
+ }
508
+ Ok(())
509
+ }
510
+
451
511
  /// Reconcile the always-on region-guides overlay with the current config.
452
512
  /// Called from config saves and on startup so the guides window matches the
453
513
  /// `always_visible` toggle without needing a recording-flow round trip. When a
@@ -748,6 +808,7 @@ pub async fn hide_overlays(app: AppHandle) -> Result<(), String> {
748
808
  FINALIZING_LABEL,
749
809
  FLOW_BAR_LABEL,
750
810
  REGION_GUIDES_LABEL,
811
+ REGION_RECORD_BORDER_LABEL,
751
812
  ] {
752
813
  if let Some(w) = app.get_webview_window(label) {
753
814
  let _ = w.close();
@@ -772,7 +833,9 @@ pub async fn hide_recording_chrome(app: AppHandle) -> Result<(), String> {
772
833
  // toggle — otherwise we'd flicker close→reopen right after stop.
773
834
  let g = crate::config::feature_config(&app).region_guides;
774
835
  let keep_region_guides = g.always_visible && g.enabled && !g.rects.is_empty();
775
- let mut labels: Vec<&str> = vec![COUNTDOWN_LABEL, TOOLBAR_LABEL];
836
+ // The recording-region border belongs to a single recording (never pinned),
837
+ // so it always tears down here alongside the countdown + toolbar.
838
+ let mut labels: Vec<&str> = vec![COUNTDOWN_LABEL, TOOLBAR_LABEL, REGION_RECORD_BORDER_LABEL];
776
839
  if !keep_region_guides {
777
840
  labels.push(REGION_GUIDES_LABEL);
778
841
  }
@@ -1438,6 +1501,9 @@ pub async fn reset_state(app: AppHandle) -> Result<(), String> {
1438
1501
  if let Some(w) = app.get_webview_window(REGION_GUIDES_LABEL) {
1439
1502
  let _ = w.close();
1440
1503
  }
1504
+ if let Some(w) = app.get_webview_window(REGION_RECORD_BORDER_LABEL) {
1505
+ let _ = w.close();
1506
+ }
1441
1507
  if let Some(window) = app.get_webview_window("popover") {
1442
1508
  // Restore normal size in case the window was shrunk to a pinhole
1443
1509
  // during recording — otherwise it would reappear as a 2×2 dot.
@@ -68,6 +68,8 @@ pub fn run() {
68
68
  clips::hide_recording_chrome,
69
69
  clips::show_region_guides,
70
70
  clips::hide_region_guides,
71
+ clips::show_region_record_border,
72
+ clips::hide_region_record_border,
71
73
  clips::show_region_guide_editor,
72
74
  clips::show_region_capture_selector,
73
75
  clips::close_bubble,
@@ -208,7 +208,10 @@ pub fn reapply_capture_exclusion_to_overlays(app: &tauri::AppHandle) {
208
208
  set_window_capture_excluded(window, false);
209
209
  continue;
210
210
  }
211
- let private_guide = matches!(label.as_str(), "region-guides" | "region-guide-editor");
211
+ let private_guide = matches!(
212
+ label.as_str(),
213
+ "region-guides" | "region-guide-editor" | "region-record-border"
214
+ );
212
215
  set_window_capture_excluded(window, private_guide || !visible);
213
216
  }
214
217
  }
@@ -49,6 +49,7 @@ import {
49
49
  import { getOrgContext } from "@agent-native/core/org";
50
50
  import { resolveAccess } from "@agent-native/core/sharing";
51
51
  import {
52
+ captureRouteError,
52
53
  getSession,
53
54
  runWithRequestContext,
54
55
  signShortLivedToken,
@@ -404,7 +405,56 @@ export default defineEventHandler(async (event: H3Event) => {
404
405
  setResponseStatus(event, upstream.status);
405
406
  return { error: upstream.error };
406
407
  }
407
- return providerResponse(upstream);
408
+ // The provider answered, but with a server error (e.g. the CDN asset is
409
+ // broken and returns 5xx). Don't proxy an opaque upstream error — and
410
+ // don't risk reconstructing a Response from it, which previously threw
411
+ // and surfaced as an unhandled 500 on every request. Capture it so the
412
+ // broken asset is diagnosable, and return a clean 502.
413
+ if (upstream.status >= 500) {
414
+ captureRouteError(
415
+ new Error(
416
+ `Storage provider returned ${upstream.status} for recording media`,
417
+ ),
418
+ {
419
+ route: "api/video",
420
+ tags: {
421
+ mediaPath: "provider-proxy",
422
+ upstreamStatus: String(upstream.status),
423
+ },
424
+ extra: { recordingId },
425
+ },
426
+ );
427
+ setResponseStatus(event, 502);
428
+ setResponseHeader(
429
+ event,
430
+ "Cache-Control",
431
+ "private, max-age=0, no-store",
432
+ );
433
+ return {
434
+ error: "The recording's media could not be loaded from storage.",
435
+ upstreamStatus: upstream.status,
436
+ };
437
+ }
438
+ try {
439
+ return providerResponse(upstream);
440
+ } catch (err) {
441
+ // Reconstructing the proxied Response should not happen, but if it
442
+ // does, fail cleanly instead of as an unhandled 500.
443
+ captureRouteError(err, {
444
+ route: "api/video",
445
+ tags: { mediaPath: "provider-proxy-response" },
446
+ extra: { recordingId, upstreamStatus: String(upstream.status) },
447
+ });
448
+ setResponseStatus(event, 502);
449
+ setResponseHeader(
450
+ event,
451
+ "Cache-Control",
452
+ "private, max-age=0, no-store",
453
+ );
454
+ return {
455
+ error: "The recording's media could not be loaded from storage.",
456
+ };
457
+ }
408
458
  }
409
459
  const mimeType =
410
460
  typeof blob?.mimeType === "string" ? blob.mimeType : "video/webm";
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Viral attribution contract for shared Clips links.
3
+ *
4
+ * Shared clip URLs self-attribute so the signup funnel can be measured even
5
+ * when `document.referrer` is empty (desktop app, Slack, native clients, etc.).
6
+ * The framework captures first-touch from these params into a cookie and
7
+ * enriches the `signup` event; Clips' job is to (a) tag share/embed URLs and
8
+ * (b) emit funnel events from the public share page.
9
+ *
10
+ * Contract strings are centralized here so the minting side (share dialog) and
11
+ * the measuring side (public share page) cannot drift apart.
12
+ *
13
+ * Privacy: `via` must be a non-PII stable id (e.g. the owner's user id), never
14
+ * an email. Omit `via` rather than leak PII into a public URL or event.
15
+ */
16
+
17
+ /** Fixed referral source for clip shares. */
18
+ export const CLIP_SHARE_REF = "clip_share";
19
+
20
+ /** Query param names that carry attribution. */
21
+ export const REF_PARAM = "ref";
22
+ export const VIA_PARAM = "via";
23
+
24
+ /**
25
+ * Append `ref=clip_share` (and `via=<ownerId>` when a non-PII owner id is
26
+ * known) to an absolute share/embed URL, preserving any existing query params.
27
+ * Returns the input unchanged when it isn't a parseable absolute URL.
28
+ */
29
+ export function withShareAttribution(
30
+ url: string,
31
+ ownerId?: string | null,
32
+ ): string {
33
+ if (!url) return url;
34
+ try {
35
+ const parsed = new URL(url);
36
+ parsed.searchParams.set(REF_PARAM, CLIP_SHARE_REF);
37
+ const owner = (ownerId ?? "").trim();
38
+ if (owner) parsed.searchParams.set(VIA_PARAM, owner);
39
+ return parsed.toString();
40
+ } catch {
41
+ return url;
42
+ }
43
+ }
44
+
45
+ export type ShareAttribution = {
46
+ ref: string | undefined;
47
+ via: string | undefined;
48
+ };
49
+
50
+ /**
51
+ * Read `ref`/`via` from a query string (e.g. `window.location.search`). Falls
52
+ * back to `ref=clip_share` so downstream attribution stays meaningful even when
53
+ * the visitor arrived via a link that lost the param.
54
+ */
55
+ export function readShareAttribution(search: string): ShareAttribution {
56
+ let ref: string | undefined;
57
+ let via: string | undefined;
58
+ try {
59
+ const params = new URLSearchParams(search ?? "");
60
+ ref = params.get(REF_PARAM) ?? undefined;
61
+ via = params.get(VIA_PARAM) ?? undefined;
62
+ } catch {
63
+ // Ignore malformed query strings — fall through to the default below.
64
+ }
65
+ return { ref: ref || CLIP_SHARE_REF, via: via || undefined };
66
+ }
67
+
68
+ /**
69
+ * Build the attribution-forwarding signup path so attribution survives even if
70
+ * cookies are blocked. The framework also captures these params on the
71
+ * `/signup` page load.
72
+ */
73
+ export function buildSignupAttributionQuery(via?: string | null): string {
74
+ const params = new URLSearchParams();
75
+ params.set(REF_PARAM, CLIP_SHARE_REF);
76
+ const owner = (via ?? "").trim();
77
+ if (owner) params.set(VIA_PARAM, owner);
78
+ return params.toString();
79
+ }
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
+ time from the command menu (Cmd+K → "What's new").
5
+
6
+ ## 2026-06-23
7
+
8
+ ### Added
9
+
10
+ - See what's new right inside Agent-Native Content — a changelog now lives in the command menu (Cmd+K).
@@ -26,6 +26,7 @@ import {
26
26
  } from "@agent-native/core/client";
27
27
  import { useDbSync } from "./hooks/use-db-sync";
28
28
  import { useNavigationState } from "./hooks/use-navigation-state";
29
+ import changelog from "../CHANGELOG.md?raw";
29
30
  import type { LinksFunction } from "react-router";
30
31
  import stylesheet from "./global.css?url";
31
32
  import { configureTracking } from "@agent-native/core/client";
@@ -247,7 +248,12 @@ export default function Root() {
247
248
  >
248
249
  <AppSetup />
249
250
  <Toaster />
250
- <CommandMenu open={cmdkOpen} onOpenChange={setCmdkOpen}>
251
+ <CommandMenu
252
+ open={cmdkOpen}
253
+ onOpenChange={setCmdkOpen}
254
+ changelog={changelog}
255
+ changelogKey="content"
256
+ >
251
257
  <CommandMenu.Group heading="Content">
252
258
  <CommandMenu.Item onSelect={() => {}}>
253
259
  Search documents
@@ -13,5 +13,8 @@ export default defineConfig({
13
13
  test: {
14
14
  include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
15
15
  exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/e2e/**"],
16
+ hookTimeout: 60_000,
17
+ testTimeout: 60_000,
18
+ maxWorkers: "50%",
16
19
  },
17
20
  });
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable user-facing changes to Design are documented here. Open it any time
4
+ from the command menu (Cmd+K → "What's new") or from Settings.
5
+
6
+ ## 2026-06-23
7
+
8
+ ### Added
9
+
10
+ - See what's new right inside Design — a changelog now lives in the command menu (Cmd+K) and in Settings.
@@ -29,6 +29,10 @@ export default defineAction({
29
29
  "The agent calls this after generating HTML/CSS/JSX content to persist it " +
30
30
  "as files in the design project. Creates or updates files as needed. " +
31
31
  "Returns the saved files and design URL path for iframe rendering. " +
32
+ "Keep the first save compact and working; for large designs, persist a minimal " +
33
+ "version then refine individual files with `edit-design` (search/replace) rather " +
34
+ "than resending a big multi-file payload — a single oversized payload can get cut " +
35
+ "off mid-stream and stall the turn. " +
32
36
  "Do not report a design as ready until this action succeeds.",
33
37
  schema: z.object({
34
38
  designId: z.string().describe("Design project ID to save content to"),
@@ -14,6 +14,7 @@ import {
14
14
  configureTracking,
15
15
  } from "@agent-native/core/client";
16
16
  import { IconSun, IconMoon } from "@tabler/icons-react";
17
+ import changelog from "../CHANGELOG.md?raw";
17
18
  import { Toaster } from "@/components/ui/sonner";
18
19
  import { Layout as AppLayout } from "@/components/layout/Layout";
19
20
  import type { LinksFunction } from "react-router";
@@ -99,7 +100,12 @@ export default function Root() {
99
100
  <AppProviders queryClient={queryClient}>
100
101
  <DbSyncSetup />
101
102
  <Toaster richColors position="bottom-left" />
102
- <CommandMenu open={cmdkOpen} onOpenChange={setCmdkOpen}>
103
+ <CommandMenu
104
+ open={cmdkOpen}
105
+ onOpenChange={setCmdkOpen}
106
+ changelog={changelog}
107
+ changelogKey="design"
108
+ >
103
109
  <CommandMenu.Group heading="Actions">
104
110
  <CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
105
111
  </CommandMenu.Group>
@@ -1,4 +1,9 @@
1
- import { SettingsPanel, useDevMode } from "@agent-native/core/client";
1
+ import {
2
+ SettingsPanel,
3
+ useDevMode,
4
+ ChangelogSettingsCard,
5
+ } from "@agent-native/core/client";
6
+ import changelog from "../../CHANGELOG.md?raw";
2
7
 
3
8
  export function meta() {
4
9
  return [{ title: "Settings — Design" }];
@@ -8,12 +13,15 @@ export default function SettingsRoute() {
8
13
  const { isDevMode, canToggle, setDevMode } = useDevMode();
9
14
 
10
15
  return (
11
- <div className="flex h-full min-h-0 flex-col bg-background">
16
+ <div className="flex h-full min-h-0 flex-col overflow-y-auto bg-background">
12
17
  <SettingsPanel
13
18
  isDevMode={isDevMode}
14
19
  onToggleDevMode={() => setDevMode(!isDevMode)}
15
20
  showDevToggle={canToggle}
16
21
  />
22
+ <div className="mx-auto w-full max-w-2xl px-4 pb-8">
23
+ <ChangelogSettingsCard markdown={changelog} />
24
+ </div>
17
25
  </div>
18
26
  );
19
27
  }
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable user-facing changes to Agent-Native Dispatch are documented here. Open it any
4
+ time from the command menu (Cmd+K → "What's new").
5
+
6
+ ## 2026-06-23
7
+
8
+ ### Added
9
+
10
+ - See what's new right inside Agent-Native Dispatch — a changelog now lives in the command menu (Cmd+K).
@@ -25,6 +25,7 @@ import { useTheme } from "next-themes";
25
25
  import { Layout as AppLayout } from "@agent-native/dispatch/components";
26
26
  import type { LinksFunction } from "react-router";
27
27
  import { dispatchExtensions } from "./dispatch-extensions";
28
+ import changelog from "../CHANGELOG.md?raw";
28
29
  import stylesheet from "./global.css?url";
29
30
 
30
31
  configureTracking({
@@ -166,7 +167,12 @@ function AppContent() {
166
167
  return (
167
168
  <>
168
169
  <DbSyncSetup />
169
- <CommandMenu open={cmdkOpen} onOpenChange={setCmdkOpen}>
170
+ <CommandMenu
171
+ open={cmdkOpen}
172
+ onOpenChange={setCmdkOpen}
173
+ changelog={changelog}
174
+ changelogKey="dispatch"
175
+ >
170
176
  <CommandMenu.Group heading="Actions">
171
177
  <CommandMenu.Item onSelect={() => {}}>Search</CommandMenu.Item>
172
178
  </CommandMenu.Group>
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable user-facing changes to Agent-Native Forms are documented here. Open it any
4
+ time from the command menu (Cmd+K → "What's new").
5
+
6
+ ## 2026-06-23
7
+
8
+ ### Added
9
+
10
+ - See what's new right inside Agent-Native Forms — a changelog now lives in the command menu (Cmd+K).
@@ -27,6 +27,7 @@ import {
27
27
  setClientAppState,
28
28
  } from "@agent-native/core/client";
29
29
  import type { LinksFunction } from "react-router";
30
+ import changelog from "../CHANGELOG.md?raw";
30
31
  import stylesheet from "./global.css?url";
31
32
  import { TAB_ID } from "@/lib/tab-id";
32
33
 
@@ -198,7 +199,12 @@ export default function Root() {
198
199
  <NavigationStateSync />
199
200
  <UrlStateSync />
200
201
  <OpenLinkInterceptor />
201
- <CommandMenu open={cmdkOpen} onOpenChange={setCmdkOpen}>
202
+ <CommandMenu
203
+ open={cmdkOpen}
204
+ onOpenChange={setCmdkOpen}
205
+ changelog={changelog}
206
+ changelogKey="forms"
207
+ >
202
208
  <CommandMenu.Group heading="Forms">
203
209
  <CommandMenu.Item onSelect={() => {}}>Search forms</CommandMenu.Item>
204
210
  </CommandMenu.Group>