@agent-native/core 0.175.2-nightly-20260826165737 → 0.175.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 (31) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/components/player/comments-panel.tsx +1 -1
  3. package/corpus/templates/clips/desktop/src/app.tsx +171 -51
  4. package/corpus/templates/clips/desktop/src/lib/pill-completion.ts +111 -0
  5. package/corpus/templates/clips/desktop/src/lib/pill-session.ts +34 -0
  6. package/corpus/templates/clips/desktop/src/lib/recorder.ts +181 -79
  7. package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +28 -4
  8. package/corpus/templates/clips/desktop/src/main.tsx +4 -4
  9. package/corpus/templates/clips/desktop/src/overlays/record-pill.tsx +1320 -0
  10. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +1 -1
  11. package/corpus/templates/clips/desktop/src/styles.css +20 -308
  12. package/corpus/templates/clips/desktop/src/tailwind.css +85 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +153 -70
  14. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +15 -0
  15. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +25 -7
  16. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +3 -3
  17. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +375 -24
  18. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +22 -30
  19. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +5 -0
  20. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +134 -14
  21. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +233 -4
  22. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +8 -5
  23. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +50 -28
  24. package/corpus/templates/slides/app/context/DeckContext.tsx +78 -23
  25. package/corpus/templates/slides/app/pages/DeckEditor.tsx +5 -5
  26. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  27. package/dist/notifications/routes.d.ts +3 -3
  28. package/dist/observability/routes.d.ts +3 -3
  29. package/dist/secrets/routes.d.ts +9 -9
  30. package/package.json +3 -3
  31. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +0 -458
@@ -1,458 +0,0 @@
1
- import {
2
- IconAlertTriangle,
3
- IconLoader2,
4
- IconPlayerPauseFilled,
5
- IconPlayerPlayFilled,
6
- IconRefresh,
7
- IconTrash,
8
- } from "@tabler/icons-react";
9
- import { invoke } from "@tauri-apps/api/core";
10
- import { LogicalSize } from "@tauri-apps/api/dpi";
11
- import { emit, listen } from "@tauri-apps/api/event";
12
- import { getCurrentWindow } from "@tauri-apps/api/window";
13
- import { useEffect, useRef, useState } from "react";
14
- import type { FocusEvent } from "react";
15
-
16
- const OVERLAY_SHADOW_GUTTER = 18;
17
- const TOOLBAR_CONTENT_WIDTH = 72;
18
- const TOOLBAR_COLLAPSED_HEIGHT = 150;
19
- // Collapsed content box (150 − 20 padding = 130) holds the centered primary
20
- // zone; the expanded height must fit that fixed 130 zone + the 88px hover
21
- // actions (+2 margin) + 20 vertical padding so nothing clips on hover.
22
- const TOOLBAR_EXPANDED_HEIGHT = 240;
23
- const TOOLBAR_WINDOW_WIDTH = TOOLBAR_CONTENT_WIDTH + OVERLAY_SHADOW_GUTTER * 2;
24
- const TOOLBAR_COLLAPSED_WINDOW_HEIGHT =
25
- TOOLBAR_COLLAPSED_HEIGHT + OVERLAY_SHADOW_GUTTER * 2;
26
- const TOOLBAR_EXPANDED_WINDOW_HEIGHT =
27
- TOOLBAR_EXPANDED_HEIGHT + OVERLAY_SHADOW_GUTTER * 2;
28
-
29
- /**
30
- * Floating recording toolbar — vertical pill anchored to the LEFT edge of
31
- * the screen (Loom's placement). Big orange Stop at the top, elapsed time
32
- * below, pause underneath. On hover, it grows downward to expose restart
33
- * and cancel controls. Pure command emitter — the popover owns the
34
- * MediaRecorder.
35
- *
36
- * IPC contract:
37
- * receives → `clips:recorder-state` { paused, elapsedMs }
38
- * emits → `clips:recorder-stop`, `:pause`, `:resume`, `:restart`, `:cancel`
39
- *
40
- * IMPORTANT: The Stop button MUST NOT close its own window. The popover's
41
- * recorder listener is what drives the stop flow, and it invokes
42
- * `hide_overlays` from the Rust side once the MediaRecorder has been
43
- * flushed. Closing the toolbar window synchronously here races the
44
- * IPC delivery: Tauri's `emit()` promise resolves when the event is
45
- * queued on the wire, not when listeners have run — if we immediately
46
- * `.close()` the emitting window, the popover listener can miss the
47
- * event entirely (observed as: toolbar disappears, nothing else
48
- * happens, user has to hit the tray icon to actually stop the
49
- * recording). Let the recorder own the close.
50
- */
51
- export function Toolbar() {
52
- const [paused, setPaused] = useState(false);
53
- const [elapsed, setElapsed] = useState(0);
54
- const [pendingAction, setPendingAction] = useState<
55
- "stop" | "restart" | "cancel" | null
56
- >(null);
57
- // Pre-record mode: the toolbar shows alongside the pre-record bubble so
58
- // the user can drag both around and position them before hitting Start.
59
- // Stop / Pause are disabled until the recorder actually begins, at which
60
- // point `clips:toolbar-enabled` fires with `true` from the recorder.
61
- const [enabled, setEnabled] = useState(false);
62
- const [preparing, setPreparing] = useState(false);
63
- const [popoverVisible, setPopoverVisible] = useState(true);
64
- const [diskSpaceLevel, setDiskSpaceLevel] = useState<
65
- "ok" | "warning" | "critical"
66
- >("ok");
67
- const fallbackTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
68
- const expandedRef = useRef(false);
69
- const pauseTransitionRef = useRef<"pause" | "resume" | null>(null);
70
- const pauseTransitionTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
71
- null,
72
- );
73
-
74
- function clearPauseTransition() {
75
- pauseTransitionRef.current = null;
76
- if (pauseTransitionTimerRef.current) {
77
- clearTimeout(pauseTransitionTimerRef.current);
78
- pauseTransitionTimerRef.current = null;
79
- }
80
- }
81
-
82
- useEffect(() => {
83
- const unlistens: Array<() => void> = [];
84
- const listenerPromises: Array<Promise<() => void>> = [];
85
- let stopped = false;
86
- // Same race-safe listen tracker as elsewhere: if this effect
87
- // cleans up before `listen()` resolves, the unlisten is called
88
- // immediately — otherwise the listener lingers for the life of
89
- // the webview, holding the setState closures captive.
90
- const trackListen = (p: Promise<() => void>) => {
91
- listenerPromises.push(p);
92
- p.then((u) => {
93
- if (stopped) {
94
- try {
95
- u();
96
- } catch {
97
- // ignore
98
- }
99
- return;
100
- }
101
- unlistens.push(u);
102
- }).catch(() => {
103
- // ignore
104
- });
105
- };
106
- trackListen(
107
- listen<{ paused: boolean; elapsedMs: number }>(
108
- "clips:recorder-state",
109
- (ev) => {
110
- const nextPaused = !!ev.payload.paused;
111
- const pendingTransition = pauseTransitionRef.current;
112
- const transitionReached =
113
- (pendingTransition === "pause" && nextPaused) ||
114
- (pendingTransition === "resume" && !nextPaused);
115
- // Native pause/resume can take a beat. Keep the first click's
116
- // optimistic state instead of letting an in-flight timer tick briefly
117
- // flip the button back and invite duplicate clicks.
118
- if (!pendingTransition || transitionReached) {
119
- setPaused(nextPaused);
120
- }
121
- if (transitionReached) clearPauseTransition();
122
- setElapsed(ev.payload.elapsedMs ?? 0);
123
- },
124
- ),
125
- );
126
- trackListen(
127
- listen<boolean>("clips:toolbar-enabled", (ev) => {
128
- setEnabled(!!ev.payload);
129
- setPreparing(false);
130
- setPendingAction(null);
131
- if (!ev.payload) {
132
- setDiskSpaceLevel("ok");
133
- setPaused(false);
134
- setElapsed(0);
135
- }
136
- }),
137
- );
138
- trackListen(
139
- listen("clips:toolbar-sync", () => {
140
- emit("clips:toolbar-ready", {}).catch(() => {});
141
- }),
142
- );
143
- trackListen(
144
- listen<boolean>("clips:toolbar-preparing", (ev) => {
145
- setPreparing(!!ev.payload);
146
- }),
147
- );
148
- trackListen(
149
- listen<boolean>("clips:popover-visible", (ev) => {
150
- setPopoverVisible(!!ev.payload);
151
- }),
152
- );
153
- trackListen(
154
- listen<{ freeMb: number }>("clips:disk-space-warning", () => {
155
- setDiskSpaceLevel((prev) =>
156
- prev === "critical" ? "critical" : "warning",
157
- );
158
- }),
159
- );
160
- trackListen(
161
- listen<{ freeMb: number }>("clips:disk-space-critical", () => {
162
- setDiskSpaceLevel("critical");
163
- }),
164
- );
165
- trackListen(
166
- listen<{ freeMb: number }>("clips:disk-space-ok", () => {
167
- setDiskSpaceLevel("ok");
168
- }),
169
- );
170
- void Promise.allSettled(listenerPromises).then(() => {
171
- if (!stopped) emit("clips:toolbar-ready", {}).catch(() => {});
172
- });
173
- return () => {
174
- stopped = true;
175
- unlistens.forEach((u) => {
176
- try {
177
- u();
178
- } catch {
179
- // ignore
180
- }
181
- });
182
- unlistens.length = 0;
183
- if (fallbackTimer.current) {
184
- clearTimeout(fallbackTimer.current);
185
- fallbackTimer.current = null;
186
- }
187
- clearPauseTransition();
188
- };
189
- }, []);
190
-
191
- function scheduleCloseFallback(action: string) {
192
- fallbackTimer.current = setTimeout(() => {
193
- console.warn(
194
- `[clips-toolbar] recorder did not close toolbar within 3s after ${action} — self-closing`,
195
- );
196
- getCurrentWindow()
197
- .close()
198
- .catch(() => {});
199
- }, 3_000);
200
- }
201
-
202
- function resizeToolbarWindow(expanded: boolean) {
203
- if (expandedRef.current === expanded) return;
204
- expandedRef.current = expanded;
205
- const height = expanded
206
- ? TOOLBAR_EXPANDED_WINDOW_HEIGHT
207
- : TOOLBAR_COLLAPSED_WINDOW_HEIGHT;
208
- getCurrentWindow()
209
- .setSize(new LogicalSize(TOOLBAR_WINDOW_WIDTH, height))
210
- .catch((err) => {
211
- console.warn("[clips-toolbar] resize failed", err);
212
- });
213
- }
214
-
215
- function stop() {
216
- if (pendingAction || !enabled) return;
217
- setPendingAction("stop");
218
- console.log("[clips-toolbar] stop clicked — emitting clips:recorder-stop");
219
- emit("clips:recorder-stop")
220
- .then(() => scheduleCloseFallback("stop"))
221
- .catch((err) => {
222
- console.error("[clips-toolbar] emit clips:recorder-stop failed:", err);
223
- setPendingAction(null);
224
- });
225
- // Defensive fallback: the recorder normally closes us via
226
- // `hide_overlays` within a second or two. If for any reason the
227
- // popover listener never fires (popover window closed, listener
228
- // torn down mid-emit, etc.), self-close after 3s so the user isn't
229
- // left with a zombie pill floating over their screen. The recorder
230
- // closing us first is a no-op on the already-closed window.
231
- }
232
-
233
- const isPreparing = preparing || (!enabled && !popoverVisible);
234
- function togglePause() {
235
- if (!enabled || pendingAction) return;
236
- const transition = paused ? "resume" : "pause";
237
- clearPauseTransition();
238
- pauseTransitionRef.current = transition;
239
- setPaused(transition === "pause");
240
- pauseTransitionTimerRef.current = setTimeout(clearPauseTransition, 3_000);
241
- emit(`clips:recorder-${transition}`).catch((err) => {
242
- console.error(
243
- `[clips-toolbar] emit clips:recorder-${transition} failed:`,
244
- err,
245
- );
246
- clearPauseTransition();
247
- setPaused(paused);
248
- });
249
- }
250
- function activatePauseFromPointer(e: React.PointerEvent<HTMLButtonElement>) {
251
- if (e.button !== 0) return;
252
- // The native toolbar resizes as the pointer enters it. Dispatch on
253
- // pointer-down so that resize/focus changes cannot cancel the subsequent
254
- // click, while the click handler below remains available to keyboards.
255
- e.preventDefault();
256
- togglePause();
257
- }
258
- function restart() {
259
- if (pendingAction || !enabled) return;
260
- setPendingAction("restart");
261
- console.log(
262
- "[clips-toolbar] restart clicked — emitting clips:recorder-restart",
263
- );
264
- emit("clips:recorder-restart")
265
- .then(() => scheduleCloseFallback("restart"))
266
- .catch((err) => {
267
- console.error(
268
- "[clips-toolbar] emit clips:recorder-restart failed:",
269
- err,
270
- );
271
- setPendingAction(null);
272
- });
273
- }
274
- function cancel() {
275
- if (pendingAction) return;
276
- setPendingAction("cancel");
277
- console.log(
278
- "[clips-toolbar] cancel clicked — emitting clips:recorder-cancel",
279
- );
280
- emit("clips:recorder-cancel")
281
- .then(() => scheduleCloseFallback("cancel"))
282
- .catch((err) => {
283
- console.error(
284
- "[clips-toolbar] emit clips:recorder-cancel failed:",
285
- err,
286
- );
287
- setPendingAction(null);
288
- });
289
- }
290
-
291
- // Same explicit-drag pattern the bubble uses — `data-tauri-drag-region`
292
- // has been unreliable across iterations so we call `startDragging()`
293
- // directly on mousedown. Interactive controls are marked `data-no-drag`
294
- // so their clicks reach onClick instead of starting a drag.
295
- const handleToolbarMouseDown = (e: React.MouseEvent) => {
296
- if (e.button !== 0) return;
297
- const target = e.target as HTMLElement;
298
- if (target.closest("[data-no-drag]")) return;
299
- getCurrentWindow()
300
- .startDragging()
301
- .catch((err) => {
302
- console.warn("[clips-toolbar] startDragging failed", err);
303
- });
304
- };
305
- const handleToolbarBlur = (e: FocusEvent<HTMLDivElement>) => {
306
- if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;
307
- resizeToolbarWindow(false);
308
- };
309
-
310
- const pendingActionLabel =
311
- pendingAction === "restart"
312
- ? "Restarting..."
313
- : pendingAction === "cancel"
314
- ? "Cancelling..."
315
- : "Stopping...";
316
-
317
- return (
318
- <div
319
- className={`toolbar-v ${paused ? "toolbar-v-paused" : ""} ${enabled ? "" : "toolbar-v-disabled"} ${diskSpaceLevel !== "ok" ? `toolbar-v-disk-${diskSpaceLevel}` : ""}`}
320
- onMouseDown={handleToolbarMouseDown}
321
- onMouseEnter={() => resizeToolbarWindow(true)}
322
- onMouseLeave={() => resizeToolbarWindow(false)}
323
- onFocusCapture={() => resizeToolbarWindow(true)}
324
- onBlurCapture={handleToolbarBlur}
325
- >
326
- {/* Primary controls live in a fixed-height zone so they stay pinned
327
- to the same vertical position whether or not the pill is hovered.
328
- Centering happens INSIDE this zone (not on the pill), so the
329
- collapsed→expanded `justify-content` change can't nudge the Stop
330
- button up — only the hover actions below grow into the new space. */}
331
- <div className="toolbar-v-primary">
332
- <button
333
- className="toolbar-v-stop"
334
- onClick={stop}
335
- disabled={!!pendingAction || !enabled}
336
- aria-label={
337
- pendingAction === "stop" ? "Stopping recording" : "Stop recording"
338
- }
339
- title={
340
- pendingAction === "stop"
341
- ? pendingActionLabel
342
- : enabled
343
- ? "Stop recording"
344
- : "Recording not started yet"
345
- }
346
- data-no-drag
347
- >
348
- {pendingAction === "stop" || isPreparing ? (
349
- <IconLoader2 className="toolbar-v-spinner" size={18} />
350
- ) : (
351
- <span className="toolbar-v-stop-square" />
352
- )}
353
- </button>
354
- <button
355
- type="button"
356
- className={`toolbar-v-time ${isPreparing ? "toolbar-v-time-preparing" : ""}`}
357
- onClick={() => invoke("show_popover").catch(() => {})}
358
- aria-label="Open Clips"
359
- title="Open Clips"
360
- data-no-drag
361
- >
362
- {isPreparing ? "Preparing…" : formatTime(elapsed)}
363
- </button>
364
- {diskSpaceLevel !== "ok" && (
365
- <div
366
- className={`toolbar-v-disk-indicator toolbar-v-disk-indicator-${diskSpaceLevel}`}
367
- title={
368
- diskSpaceLevel === "critical"
369
- ? "Disk almost full. Stop recording now to avoid losing your clip."
370
- : "Low disk space. Save your recording soon."
371
- }
372
- data-no-drag
373
- >
374
- <IconAlertTriangle size={12} />
375
- </div>
376
- )}
377
- <button
378
- className="toolbar-v-pause"
379
- onPointerDown={activatePauseFromPointer}
380
- onClick={(e) => {
381
- if (e.detail === 0) togglePause();
382
- }}
383
- disabled={!enabled || !!pendingAction}
384
- aria-label={paused ? "Resume" : "Pause"}
385
- title={
386
- pendingAction
387
- ? pendingActionLabel
388
- : enabled
389
- ? paused
390
- ? "Resume"
391
- : "Pause"
392
- : "Recording not started yet"
393
- }
394
- data-no-drag
395
- >
396
- {paused ? (
397
- <IconPlayerPlayFilled size={18} />
398
- ) : (
399
- <IconPlayerPauseFilled size={18} />
400
- )}
401
- </button>
402
- </div>
403
- <div
404
- className="toolbar-v-hover-actions"
405
- role="group"
406
- aria-label="Recording actions"
407
- >
408
- <button
409
- className="toolbar-v-action"
410
- onClick={restart}
411
- disabled={!enabled || !!pendingAction}
412
- aria-label="Restart recording"
413
- title={
414
- pendingAction === "restart"
415
- ? pendingActionLabel
416
- : enabled
417
- ? "Restart"
418
- : "Recording not started yet"
419
- }
420
- data-no-drag
421
- >
422
- {pendingAction === "restart" ? (
423
- <IconLoader2 className="toolbar-v-spinner" size={18} />
424
- ) : (
425
- <IconRefresh size={24} stroke={1.9} />
426
- )}
427
- </button>
428
- <button
429
- className="toolbar-v-action toolbar-v-action-danger"
430
- onClick={cancel}
431
- disabled={!!pendingAction}
432
- aria-label={enabled ? "Cancel recording" : "Cancel recording setup"}
433
- title={
434
- pendingAction === "cancel"
435
- ? pendingActionLabel
436
- : enabled
437
- ? "Cancel"
438
- : "Cancel recording setup"
439
- }
440
- data-no-drag
441
- >
442
- {pendingAction === "cancel" ? (
443
- <IconLoader2 className="toolbar-v-spinner" size={18} />
444
- ) : (
445
- <IconTrash size={24} stroke={1.9} />
446
- )}
447
- </button>
448
- </div>
449
- </div>
450
- );
451
- }
452
-
453
- function formatTime(ms: number): string {
454
- const total = Math.max(0, Math.floor(ms / 1000));
455
- const m = Math.floor(total / 60);
456
- const s = total % 60;
457
- return `${m}:${s.toString().padStart(2, "0")}`;
458
- }