@agent-native/core 0.70.3 → 0.72.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 (221) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +51 -0
  3. package/corpus/core/docs/content/actions.md +23 -0
  4. package/corpus/core/docs/content/audit-log.md +111 -0
  5. package/corpus/core/docs/content/multi-app-workspace.md +2 -2
  6. package/corpus/core/docs/design/durable-agent-runs.md +458 -4
  7. package/corpus/core/package.json +2 -1
  8. package/corpus/core/src/action.ts +80 -1
  9. package/corpus/core/src/agent/durable-background.ts +192 -0
  10. package/corpus/core/src/agent/production-agent.ts +357 -13
  11. package/corpus/core/src/agent/run-manager.ts +66 -3
  12. package/corpus/core/src/agent/run-store.ts +143 -23
  13. package/corpus/core/src/agent/types.ts +20 -0
  14. package/corpus/core/src/application-state/store.ts +5 -0
  15. package/corpus/core/src/audit/actions/get-audit-event.ts +23 -0
  16. package/corpus/core/src/audit/actions/list-audit-events.ts +65 -0
  17. package/corpus/core/src/audit/cleanup-job.ts +100 -0
  18. package/corpus/core/src/audit/config.ts +91 -0
  19. package/corpus/core/src/audit/index.ts +43 -0
  20. package/corpus/core/src/audit/record.ts +143 -0
  21. package/corpus/core/src/audit/redact.ts +109 -0
  22. package/corpus/core/src/audit/store.ts +244 -0
  23. package/corpus/core/src/audit/types.ts +125 -0
  24. package/corpus/core/src/chat-threads/store.ts +10 -0
  25. package/corpus/core/src/client/AgentPanel.tsx +12 -9
  26. package/corpus/core/src/client/AssistantChat.tsx +6 -0
  27. package/corpus/core/src/client/blocks/library/FileTreeBlock.tsx +72 -14
  28. package/corpus/core/src/client/chat/repo-helpers.ts +38 -0
  29. package/corpus/core/src/db/client.ts +4 -0
  30. package/corpus/core/src/db/widen-columns.ts +75 -0
  31. package/corpus/core/src/deploy/build.ts +96 -0
  32. package/corpus/core/src/deploy/workspace-deploy.ts +121 -0
  33. package/corpus/core/src/mcp/build-server.ts +23 -13
  34. package/corpus/core/src/oauth-tokens/store.ts +6 -0
  35. package/corpus/core/src/provider-api/custom-registry.ts +8 -0
  36. package/corpus/core/src/resources/store.ts +10 -0
  37. package/corpus/core/src/scripts/runner.ts +6 -3
  38. package/corpus/core/src/server/action-discovery.ts +6 -0
  39. package/corpus/core/src/server/action-routes.ts +1 -0
  40. package/corpus/core/src/server/agent-chat-plugin.ts +133 -66
  41. package/corpus/core/src/server/auth.ts +5 -0
  42. package/corpus/core/src/server/core-routes-plugin.ts +14 -0
  43. package/corpus/core/src/server/security-headers.ts +9 -6
  44. package/corpus/core/src/settings/store.ts +6 -0
  45. package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
  46. package/corpus/core/src/usage/store.ts +6 -0
  47. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -0
  48. package/corpus/templates/analytics/AGENTS.md +8 -0
  49. package/corpus/templates/analytics/actions/compose-dashboard.ts +317 -0
  50. package/corpus/templates/analytics/changelog/2026-06-23-build-large-first-party-analytics-dashboards-in-one-fast-cal.md +6 -0
  51. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +574 -0
  52. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +60 -6
  53. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +6 -0
  54. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +20 -43
  55. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +5 -1
  56. package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +3 -51
  57. package/corpus/templates/clips/changelog/2026-06-23-dragging-the-desktop-camera-bubble-now-glides-to-a-stop-at-t.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-06-23-public-clips-now-play-inline-in-slack-connect-a-workspac.md +6 -0
  59. package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +72 -0
  60. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -10
  61. package/corpus/templates/clips/chrome-extension/src/background.ts +163 -32
  62. package/corpus/templates/clips/chrome-extension/src/content-script.ts +301 -24
  63. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +245 -67
  64. package/corpus/templates/clips/chrome-extension/src/overlay.css +77 -43
  65. package/corpus/templates/clips/chrome-extension/src/overlay.html +7 -1
  66. package/corpus/templates/clips/chrome-extension/src/overlay.ts +102 -38
  67. package/corpus/templates/clips/chrome-extension/src/popup.html +62 -5
  68. package/corpus/templates/clips/chrome-extension/src/popup.ts +275 -4
  69. package/corpus/templates/clips/chrome-extension/src/styles.css +34 -0
  70. package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
  71. package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +21 -16
  72. package/corpus/templates/clips/desktop/src/lib/recorder.ts +10 -17
  73. package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +73 -23
  74. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +0 -6
  75. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +56 -46
  76. package/corpus/templates/clips/desktop/src/styles.css +33 -5
  77. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +113 -0
  78. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  79. package/corpus/templates/clips/server/lib/media-permissions.ts +5 -1
  80. package/corpus/templates/clips/shared/recording-audio.ts +62 -0
  81. package/corpus/templates/clips/shared/recording-core.ts +94 -0
  82. package/dist/action.d.ts +31 -0
  83. package/dist/action.d.ts.map +1 -1
  84. package/dist/action.js +45 -1
  85. package/dist/action.js.map +1 -1
  86. package/dist/agent/context-xray/schema.d.ts +1 -1
  87. package/dist/agent/durable-background.d.ts +60 -0
  88. package/dist/agent/durable-background.d.ts.map +1 -0
  89. package/dist/agent/durable-background.js +144 -0
  90. package/dist/agent/durable-background.js.map +1 -0
  91. package/dist/agent/observational-memory/schema.d.ts +1 -1
  92. package/dist/agent/production-agent.d.ts +20 -0
  93. package/dist/agent/production-agent.d.ts.map +1 -1
  94. package/dist/agent/production-agent.js +320 -14
  95. package/dist/agent/production-agent.js.map +1 -1
  96. package/dist/agent/run-manager.d.ts +48 -0
  97. package/dist/agent/run-manager.d.ts.map +1 -1
  98. package/dist/agent/run-manager.js +45 -3
  99. package/dist/agent/run-manager.js.map +1 -1
  100. package/dist/agent/run-store.d.ts +30 -1
  101. package/dist/agent/run-store.d.ts.map +1 -1
  102. package/dist/agent/run-store.js +138 -24
  103. package/dist/agent/run-store.js.map +1 -1
  104. package/dist/agent/types.d.ts +20 -0
  105. package/dist/agent/types.d.ts.map +1 -1
  106. package/dist/agent/types.js.map +1 -1
  107. package/dist/application-state/store.d.ts.map +1 -1
  108. package/dist/application-state/store.js +5 -0
  109. package/dist/application-state/store.js.map +1 -1
  110. package/dist/audit/actions/get-audit-event.d.ts +11 -0
  111. package/dist/audit/actions/get-audit-event.d.ts.map +1 -0
  112. package/dist/audit/actions/get-audit-event.js +22 -0
  113. package/dist/audit/actions/get-audit-event.js.map +1 -0
  114. package/dist/audit/actions/list-audit-events.d.ts +22 -0
  115. package/dist/audit/actions/list-audit-events.d.ts.map +1 -0
  116. package/dist/audit/actions/list-audit-events.js +61 -0
  117. package/dist/audit/actions/list-audit-events.js.map +1 -0
  118. package/dist/audit/cleanup-job.d.ts +12 -0
  119. package/dist/audit/cleanup-job.d.ts.map +1 -0
  120. package/dist/audit/cleanup-job.js +93 -0
  121. package/dist/audit/cleanup-job.js.map +1 -0
  122. package/dist/audit/config.d.ts +30 -0
  123. package/dist/audit/config.d.ts.map +1 -0
  124. package/dist/audit/config.js +65 -0
  125. package/dist/audit/config.js.map +1 -0
  126. package/dist/audit/index.d.ts +13 -0
  127. package/dist/audit/index.d.ts.map +1 -0
  128. package/dist/audit/index.js +6 -0
  129. package/dist/audit/index.js.map +1 -0
  130. package/dist/audit/record.d.ts +24 -0
  131. package/dist/audit/record.d.ts.map +1 -0
  132. package/dist/audit/record.js +106 -0
  133. package/dist/audit/record.js.map +1 -0
  134. package/dist/audit/redact.d.ts +28 -0
  135. package/dist/audit/redact.d.ts.map +1 -0
  136. package/dist/audit/redact.js +109 -0
  137. package/dist/audit/redact.js.map +1 -0
  138. package/dist/audit/store.d.ts +14 -0
  139. package/dist/audit/store.d.ts.map +1 -0
  140. package/dist/audit/store.js +219 -0
  141. package/dist/audit/store.js.map +1 -0
  142. package/dist/audit/types.d.ts +114 -0
  143. package/dist/audit/types.d.ts.map +1 -0
  144. package/dist/audit/types.js +15 -0
  145. package/dist/audit/types.js.map +1 -0
  146. package/dist/chat-threads/store.d.ts.map +1 -1
  147. package/dist/chat-threads/store.js +10 -0
  148. package/dist/chat-threads/store.js.map +1 -1
  149. package/dist/client/AgentPanel.d.ts.map +1 -1
  150. package/dist/client/AgentPanel.js +2 -2
  151. package/dist/client/AgentPanel.js.map +1 -1
  152. package/dist/client/AssistantChat.d.ts.map +1 -1
  153. package/dist/client/AssistantChat.js +6 -1
  154. package/dist/client/AssistantChat.js.map +1 -1
  155. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  156. package/dist/client/blocks/library/FileTreeBlock.js +37 -4
  157. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  158. package/dist/client/chat/repo-helpers.d.ts +16 -0
  159. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  160. package/dist/client/chat/repo-helpers.js +40 -0
  161. package/dist/client/chat/repo-helpers.js.map +1 -1
  162. package/dist/db/client.d.ts.map +1 -1
  163. package/dist/db/client.js +3 -0
  164. package/dist/db/client.js.map +1 -1
  165. package/dist/db/widen-columns.d.ts +39 -0
  166. package/dist/db/widen-columns.d.ts.map +1 -0
  167. package/dist/db/widen-columns.js +73 -0
  168. package/dist/db/widen-columns.js.map +1 -0
  169. package/dist/deploy/build.d.ts +29 -0
  170. package/dist/deploy/build.d.ts.map +1 -1
  171. package/dist/deploy/build.js +85 -0
  172. package/dist/deploy/build.js.map +1 -1
  173. package/dist/deploy/workspace-deploy.d.ts.map +1 -1
  174. package/dist/deploy/workspace-deploy.js +108 -0
  175. package/dist/deploy/workspace-deploy.js.map +1 -1
  176. package/dist/mcp/build-server.d.ts.map +1 -1
  177. package/dist/mcp/build-server.js +24 -9
  178. package/dist/mcp/build-server.js.map +1 -1
  179. package/dist/oauth-tokens/store.d.ts.map +1 -1
  180. package/dist/oauth-tokens/store.js +6 -0
  181. package/dist/oauth-tokens/store.js.map +1 -1
  182. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  183. package/dist/provider-api/custom-registry.js +8 -0
  184. package/dist/provider-api/custom-registry.js.map +1 -1
  185. package/dist/resources/store.d.ts.map +1 -1
  186. package/dist/resources/store.js +9 -0
  187. package/dist/resources/store.js.map +1 -1
  188. package/dist/scripts/runner.js +4 -3
  189. package/dist/scripts/runner.js.map +1 -1
  190. package/dist/server/action-discovery.d.ts.map +1 -1
  191. package/dist/server/action-discovery.js +6 -0
  192. package/dist/server/action-discovery.js.map +1 -1
  193. package/dist/server/action-routes.d.ts.map +1 -1
  194. package/dist/server/action-routes.js +1 -0
  195. package/dist/server/action-routes.js.map +1 -1
  196. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  197. package/dist/server/agent-chat-plugin.js +68 -13
  198. package/dist/server/agent-chat-plugin.js.map +1 -1
  199. package/dist/server/auth.d.ts.map +1 -1
  200. package/dist/server/auth.js +5 -0
  201. package/dist/server/auth.js.map +1 -1
  202. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  203. package/dist/server/core-routes-plugin.js +13 -0
  204. package/dist/server/core-routes-plugin.js.map +1 -1
  205. package/dist/server/security-headers.d.ts +8 -5
  206. package/dist/server/security-headers.d.ts.map +1 -1
  207. package/dist/server/security-headers.js +9 -6
  208. package/dist/server/security-headers.js.map +1 -1
  209. package/dist/settings/store.d.ts.map +1 -1
  210. package/dist/settings/store.js +6 -0
  211. package/dist/settings/store.js.map +1 -1
  212. package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
  213. package/dist/usage/store.d.ts.map +1 -1
  214. package/dist/usage/store.js +5 -0
  215. package/dist/usage/store.js.map +1 -1
  216. package/docs/content/actions.md +23 -0
  217. package/docs/content/audit-log.md +111 -0
  218. package/docs/content/multi-app-workspace.md +2 -2
  219. package/docs/design/durable-agent-runs.md +458 -4
  220. package/package.json +2 -1
  221. package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
@@ -75,21 +75,82 @@ function send(type: string, extra: Record<string, unknown> = {}): void {
75
75
 
76
76
  /* ---------------------------------------------------------------- bubble --- */
77
77
 
78
+ function postBubble(kind: string, extra: Record<string, unknown> = {}): void {
79
+ try {
80
+ window.parent.postMessage(
81
+ { source: "clips-overlay", kind, part: "bubble", ...extra },
82
+ "*",
83
+ );
84
+ } catch {
85
+ /* parent gone */
86
+ }
87
+ }
88
+
78
89
  async function initBubble(): Promise<void> {
79
90
  const bubble = document.createElement("div");
80
91
  bubble.className = "bubble";
81
92
  const ring = document.createElement("div");
82
93
  ring.className = "bubble-ring";
83
94
  bubble.appendChild(ring);
95
+
96
+ // Drag: the content script owns the iframe position, so we just signal the
97
+ // start of a drag and it captures the pointer page-wide.
98
+ bubble.style.cursor = "grab";
99
+ bubble.addEventListener("pointerdown", (e) => {
100
+ if (e.button !== 0) return;
101
+ if ((e.target as HTMLElement).closest("[data-no-drag]")) return;
102
+ e.preventDefault();
103
+ bubble.style.cursor = "grabbing";
104
+ postBubble("bubble-drag-start");
105
+ const restore = (): void => {
106
+ bubble.style.cursor = "grab";
107
+ window.removeEventListener("pointerup", restore);
108
+ };
109
+ window.addEventListener("pointerup", restore);
110
+ });
111
+
112
+ // Size dots (small / large), revealed on hover — like the desktop bubble.
113
+ const sizes = document.createElement("div");
114
+ sizes.className = "bubble-sizes";
115
+ sizes.setAttribute("data-no-drag", "");
116
+ for (const key of ["sm", "lg"] as const) {
117
+ const dot = document.createElement("button");
118
+ dot.type = "button";
119
+ dot.className = `bubble-size-dot bubble-size-${key}`;
120
+ dot.title = key === "sm" ? "Small" : "Large";
121
+ dot.setAttribute(
122
+ "aria-label",
123
+ key === "sm" ? "Small bubble" : "Large bubble",
124
+ );
125
+ dot.setAttribute("data-no-drag", "");
126
+ dot.addEventListener("click", (e) => {
127
+ e.stopPropagation();
128
+ postBubble("bubble-size", { size: key });
129
+ });
130
+ sizes.appendChild(dot);
131
+ }
132
+ bubble.appendChild(sizes);
133
+
84
134
  root.appendChild(bubble);
85
135
 
86
136
  try {
137
+ const videoDeviceId = await new Promise<string>((resolve) => {
138
+ try {
139
+ chrome.storage.sync.get("videoDeviceId", (v) =>
140
+ resolve(typeof v.videoDeviceId === "string" ? v.videoDeviceId : ""),
141
+ );
142
+ } catch {
143
+ resolve("");
144
+ }
145
+ });
146
+ const videoConstraint: MediaTrackConstraints = {
147
+ width: { ideal: 640 },
148
+ height: { ideal: 640 },
149
+ };
150
+ if (videoDeviceId) videoConstraint.deviceId = { exact: videoDeviceId };
151
+ else videoConstraint.facingMode = "user";
87
152
  const stream = await navigator.mediaDevices.getUserMedia({
88
- video: {
89
- width: { ideal: 640 },
90
- height: { ideal: 640 },
91
- facingMode: "user",
92
- },
153
+ video: videoConstraint,
93
154
  audio: false,
94
155
  });
95
156
  const video = document.createElement("video");
@@ -99,11 +160,18 @@ async function initBubble(): Promise<void> {
99
160
  video.srcObject = stream;
100
161
  ring.appendChild(video);
101
162
  await video.play().catch(() => undefined);
102
- } catch {
163
+ console.log("[clips-overlay] camera bubble live");
164
+ // Tell the host the feed is live so it can start the countdown — the "3"
165
+ // shouldn't appear until the camera is actually showing.
166
+ postBubble("camera-ready");
167
+ } catch (err) {
168
+ console.warn("[clips-overlay] camera getUserMedia failed:", err);
103
169
  const empty = document.createElement("div");
104
170
  empty.className = "bubble-empty";
105
171
  empty.innerHTML = ICONS.cameraOff;
106
172
  ring.appendChild(empty);
173
+ // Still release the countdown — a blocked/failed camera must not hang it.
174
+ postBubble("camera-ready");
107
175
  }
108
176
  }
109
177
 
@@ -147,37 +215,36 @@ function initCountdown(): void {
147
215
  wrap.append(controls, hint);
148
216
  root.appendChild(wrap);
149
217
 
150
- let lastShown = "";
151
- const render = (): void => {
152
- const fallback =
153
- Number(params.get("seconds") || String(COUNTDOWN_FALLBACK)) ||
154
- COUNTDOWN_FALLBACK;
155
- const endsAt =
156
- state.countdownEndsAtMs > 0
157
- ? state.countdownEndsAtMs
158
- : Date.now() + fallback * 1000;
159
- const remainingMs = endsAt - Date.now();
160
- if (state.phase !== "countdown" || remainingMs <= 0) {
161
- if (lastShown !== "Go") {
162
- number.textContent = "Go";
163
- number.classList.add("countdown-go");
164
- lastShown = "Go";
218
+ // Each number is shown via a CHAINED setTimeout — the next step is scheduled
219
+ // one second after the current one actually renders, not on a fixed interval.
220
+ // This is deliberate: when the camera is slow to connect (e.g. an iPhone
221
+ // Continuity Camera) it can hog the main thread and stall a tick. A setInterval
222
+ // would then fire all the missed ticks back-to-back ("3"… then "2 1 Go" in a
223
+ // burst); chaining means a stall only delays the next number, it never bursts.
224
+ // At "Go" we tell the worker to start the recorder; the worker's own timer is
225
+ // just a fallback for pages where no overlay can be injected.
226
+ const STEP_MS = 1000;
227
+ const steps = ["3", "2", "1", "Go"];
228
+ let doneSent = false;
229
+
230
+ const showStep = (index: number): void => {
231
+ const text = steps[index];
232
+ number.textContent = text;
233
+ number.classList.toggle("countdown-go", text === "Go");
234
+ number.style.animation = "none";
235
+ void number.offsetWidth;
236
+ number.style.animation = "";
237
+ if (text === "Go") {
238
+ if (!doneSent) {
239
+ doneSent = true;
240
+ send("CLIPS_OVERLAY_COUNTDOWN_DONE");
165
241
  }
166
242
  return;
167
243
  }
168
- const text = String(Math.ceil(remainingMs / 1000));
169
- if (text !== lastShown) {
170
- number.textContent = text;
171
- number.classList.remove("countdown-go");
172
- number.style.animation = "none";
173
- void number.offsetWidth;
174
- number.style.animation = "";
175
- lastShown = text;
176
- }
244
+ window.setTimeout(() => showStep(index + 1), STEP_MS);
177
245
  };
178
- countdownRender = render;
179
- window.setInterval(render, 100);
180
- render();
246
+
247
+ showStep(0); // "3" immediately, then chain "2" → "1" → "Go"
181
248
  }
182
249
 
183
250
  /* --------------------------------------------------------------- toolbar --- */
@@ -272,8 +339,6 @@ function initToolbar(): void {
272
339
  function initSaving(): void {
273
340
  const card = document.createElement("div");
274
341
  card.className = "saving-card";
275
- const spinner = document.createElement("div");
276
- spinner.className = "saving-spinner";
277
342
  const caption = document.createElement("div");
278
343
  caption.className = "saving-caption";
279
344
  caption.textContent = "Saving clip…";
@@ -282,7 +347,8 @@ function initSaving(): void {
282
347
  const fill = document.createElement("div");
283
348
  fill.className = "saving-bar-fill";
284
349
  bar.appendChild(fill);
285
- card.append(spinner, caption, bar);
350
+ // One progress indicator only — the indeterminate bar (no circular spinner).
351
+ card.append(caption, bar);
286
352
  root.appendChild(card);
287
353
  }
288
354
 
@@ -302,7 +368,6 @@ const state: OverlayState = {
302
368
  countdownEndsAtMs: 0,
303
369
  };
304
370
  let toolbarRender: (() => void) | null = null;
305
- let countdownRender: (() => void) | null = null;
306
371
 
307
372
  chrome.runtime.onMessage.addListener((message) => {
308
373
  if (!message || typeof message !== "object") return;
@@ -317,7 +382,6 @@ chrome.runtime.onMessage.addListener((message) => {
317
382
  if (typeof next.countdownEndsAtMs === "number")
318
383
  state.countdownEndsAtMs = next.countdownEndsAtMs;
319
384
  toolbarRender?.();
320
- countdownRender?.();
321
385
  });
322
386
 
323
387
  if (part === "bubble") void initBubble();
@@ -162,6 +162,31 @@
162
162
  </header>
163
163
 
164
164
  <div id="idle-content">
165
+ <div id="unsupported-notice" class="notice" role="alert" hidden>
166
+ <svg
167
+ class="notice-icon"
168
+ viewBox="0 0 24 24"
169
+ fill="none"
170
+ aria-hidden="true"
171
+ >
172
+ <path
173
+ d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"
174
+ stroke="currentColor"
175
+ stroke-width="1.75"
176
+ stroke-linecap="round"
177
+ stroke-linejoin="round"
178
+ />
179
+ </svg>
180
+ <div class="notice-body">
181
+ <div class="notice-title">Clips can't draw controls here</div>
182
+ <div class="notice-text">
183
+ This is a browser page (like New&nbsp;Tab), so the countdown and
184
+ on-screen controls won't appear. You can still record a window or
185
+ full screen — stop from the Clips icon — or open a website for the
186
+ full experience.
187
+ </div>
188
+ </div>
189
+ </div>
165
190
  <section class="panel" aria-label="Recorder setup">
166
191
  <div id="source-row" class="row row-on">
167
192
  <span class="row-icon" aria-hidden="true">
@@ -242,10 +267,24 @@
242
267
  />
243
268
  </svg>
244
269
  </span>
245
- <button type="button" class="row-button" disabled>
246
- <span class="row-label">Default camera</span>
270
+ <button id="camera-device-button" type="button" class="row-button">
271
+ <span id="camera-device-label" class="row-label"
272
+ >Default camera</span
273
+ >
247
274
  <span class="row-flex" aria-hidden="true"></span>
275
+ <span class="row-chev" aria-hidden="true">
276
+ <svg viewBox="0 0 24 24" fill="none">
277
+ <path
278
+ d="M6 9l6 6 6-6"
279
+ stroke="currentColor"
280
+ stroke-width="2"
281
+ stroke-linecap="round"
282
+ stroke-linejoin="round"
283
+ />
284
+ </svg>
285
+ </span>
248
286
  </button>
287
+ <div id="camera-device-menu" class="row-menu" hidden></div>
249
288
  <button
250
289
  id="include-camera"
251
290
  class="toggle toggle-on"
@@ -257,7 +296,7 @@
257
296
  </button>
258
297
  </div>
259
298
 
260
- <div class="row row-on">
299
+ <div id="microphone-row" class="row row-on">
261
300
  <span class="row-icon" aria-hidden="true">
262
301
  <svg viewBox="0 0 24 24" fill="none">
263
302
  <rect
@@ -277,10 +316,28 @@
277
316
  />
278
317
  </svg>
279
318
  </span>
280
- <button type="button" class="row-button" disabled>
281
- <span class="row-label">Default mic</span>
319
+ <button
320
+ id="microphone-device-button"
321
+ type="button"
322
+ class="row-button"
323
+ >
324
+ <span id="microphone-device-label" class="row-label"
325
+ >Default mic</span
326
+ >
282
327
  <span class="row-flex" aria-hidden="true"></span>
328
+ <span class="row-chev" aria-hidden="true">
329
+ <svg viewBox="0 0 24 24" fill="none">
330
+ <path
331
+ d="M6 9l6 6 6-6"
332
+ stroke="currentColor"
333
+ stroke-width="2"
334
+ stroke-linecap="round"
335
+ stroke-linejoin="round"
336
+ />
337
+ </svg>
338
+ </span>
283
339
  </button>
340
+ <div id="microphone-device-menu" class="row-menu" hidden></div>
284
341
  <button
285
342
  id="include-microphone"
286
343
  class="toggle toggle-on"