@agent-native/core 0.161.17 → 0.161.20
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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/analytics/AGENTS.md +1 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +4 -0
- package/corpus/templates/assets/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/assets/AGENTS.md +1 -0
- package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/brain/AGENTS.md +1 -0
- package/corpus/templates/calendar/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/calendar/AGENTS.md +1 -0
- package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/chat/AGENTS.md +4 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +4 -0
- package/corpus/templates/clips/app/hooks/use-playback-position.ts +10 -2
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +26 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-opens-welcome-window-on-first-launch.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-18-pending-recording-files-now-use-a-clips-prefixed-name.md +6 -0
- package/corpus/templates/clips/desktop/src/main.tsx +0 -3
- package/corpus/templates/clips/desktop/src/shared/config.ts +0 -1
- package/corpus/templates/clips/desktop/src/styles.css +0 -118
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +0 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +0 -45
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +1 -4
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +0 -10
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +57 -5
- package/corpus/templates/content/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/crm/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/crm/AGENTS.md +1 -0
- package/corpus/templates/design/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/design/AGENTS.md +1 -0
- package/corpus/templates/design/app/i18n-data.ts +11 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +9 -8
- package/corpus/templates/dispatch/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/dispatch/AGENTS.md +5 -5
- package/corpus/templates/factory/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/factory/AGENTS.md +1 -0
- package/corpus/templates/forms/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/forms/AGENTS.md +1 -0
- package/corpus/templates/macros/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/macros/AGENTS.md +1 -0
- package/corpus/templates/mail/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/mail/AGENTS.md +1 -0
- package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/plan/AGENTS.md +1 -0
- package/corpus/templates/slides/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/slides/AGENTS.md +2 -1
- package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +29 -2
- package/corpus/templates/tasks/AGENTS.md +1 -0
- package/dist/action.js +43 -1
- package/dist/client/chat/run-recovery.js +1 -1
- package/dist/client/route-state.js +17 -13
- package/dist/collab/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.js +23 -5
- package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/chat/AGENTS.md +4 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/dist/templates/workspace-core/AGENTS.md +4 -0
- package/dist/templates/workspace-root/AGENTS.md +9 -0
- package/docs/content/deployment.mdx +70 -0
- package/docs/content/faq.mdx +1 -1
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/chat/AGENTS.md +4 -0
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +10 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +24 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -2
- package/src/templates/workspace-core/AGENTS.md +4 -0
- package/src/templates/workspace-root/AGENTS.md +9 -0
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +0 -314
|
@@ -183,6 +183,8 @@ export interface VideoPlayerProps {
|
|
|
183
183
|
defaultSpeed?: number;
|
|
184
184
|
/** Autoplay on mount. */
|
|
185
185
|
autoPlay?: boolean;
|
|
186
|
+
/** Persist resume position for an authenticated viewer. */
|
|
187
|
+
persistPlaybackPosition?: boolean;
|
|
186
188
|
/** Start time in ms. */
|
|
187
189
|
startMs?: number;
|
|
188
190
|
/** Comment + chapter overlays for the scrubber. */
|
|
@@ -247,6 +249,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
247
249
|
thumbnailUrl,
|
|
248
250
|
defaultSpeed = 1.2,
|
|
249
251
|
autoPlay,
|
|
252
|
+
persistPlaybackPosition = true,
|
|
250
253
|
startMs,
|
|
251
254
|
editsJson,
|
|
252
255
|
comments,
|
|
@@ -520,6 +523,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
|
|
|
520
523
|
recordingId,
|
|
521
524
|
videoEl: playbackVideoEl,
|
|
522
525
|
durationMs,
|
|
526
|
+
enabled: persistPlaybackPosition,
|
|
523
527
|
explicitStartMs: startMs,
|
|
524
528
|
allowRestoreWhilePlaying: autoPlay,
|
|
525
529
|
onRestore: restorePlaybackPosition,
|
|
@@ -13,6 +13,7 @@ export interface UsePlaybackPositionOptions {
|
|
|
13
13
|
recordingId: string;
|
|
14
14
|
videoEl: HTMLVideoElement | null;
|
|
15
15
|
durationMs: number;
|
|
16
|
+
enabled?: boolean;
|
|
16
17
|
explicitStartMs?: number;
|
|
17
18
|
allowRestoreWhilePlaying?: boolean;
|
|
18
19
|
onRestore?: (positionMs: number) => void;
|
|
@@ -27,6 +28,7 @@ export function usePlaybackPosition({
|
|
|
27
28
|
recordingId,
|
|
28
29
|
videoEl,
|
|
29
30
|
durationMs,
|
|
31
|
+
enabled = true,
|
|
30
32
|
explicitStartMs,
|
|
31
33
|
allowRestoreWhilePlaying = false,
|
|
32
34
|
onRestore,
|
|
@@ -38,7 +40,7 @@ export function usePlaybackPosition({
|
|
|
38
40
|
onRestoreRef.current = onRestore;
|
|
39
41
|
|
|
40
42
|
useEffect(() => {
|
|
41
|
-
if (!videoEl || !recordingId) return;
|
|
43
|
+
if (!enabled || !videoEl || !recordingId) return;
|
|
42
44
|
|
|
43
45
|
const controller = new AbortController();
|
|
44
46
|
let cancelled = false;
|
|
@@ -184,5 +186,11 @@ export function usePlaybackPosition({
|
|
|
184
186
|
window.removeEventListener("pagehide", onPageHide);
|
|
185
187
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
186
188
|
};
|
|
187
|
-
}, [
|
|
189
|
+
}, [
|
|
190
|
+
allowRestoreWhilePlaying,
|
|
191
|
+
enabled,
|
|
192
|
+
explicitStartMs,
|
|
193
|
+
recordingId,
|
|
194
|
+
videoEl,
|
|
195
|
+
]);
|
|
188
196
|
}
|
|
@@ -236,6 +236,7 @@ export default function EmbedRoute() {
|
|
|
236
236
|
videoFormat={recording.videoFormat}
|
|
237
237
|
embedProvider={isLoomEmbedBacked ? "loom" : null}
|
|
238
238
|
durationMs={recording.durationMs}
|
|
239
|
+
persistPlaybackPosition={false}
|
|
239
240
|
editsJson={recording.editsJson}
|
|
240
241
|
thumbnailUrl={recording.thumbnailUrl}
|
|
241
242
|
defaultSpeed={parsePlaybackSpeed(recording.defaultSpeed) ?? 1.2}
|
|
@@ -187,6 +187,30 @@ function nativeSaveFailureMessage(reason: string | null | undefined): string {
|
|
|
187
187
|
return "The desktop recorder finished and saved a local copy, but Clips could not upload it. You can retry from the Clips menu without recording again.";
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
export function BackToLibraryButton() {
|
|
191
|
+
const t = useT();
|
|
192
|
+
const navigate = useNavigate();
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Tooltip>
|
|
196
|
+
<TooltipTrigger asChild>
|
|
197
|
+
<Button
|
|
198
|
+
variant="ghost"
|
|
199
|
+
size="icon"
|
|
200
|
+
className="shrink-0"
|
|
201
|
+
onClick={() => navigate("/library", { replace: true })}
|
|
202
|
+
aria-label={t("recordingPage.backToLibrary")}
|
|
203
|
+
>
|
|
204
|
+
<IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
|
|
205
|
+
</Button>
|
|
206
|
+
</TooltipTrigger>
|
|
207
|
+
<TooltipContent side="bottom" align="start">
|
|
208
|
+
{t("recordingPage.backToLibrary")}
|
|
209
|
+
</TooltipContent>
|
|
210
|
+
</Tooltip>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
function parseTimeParam(raw: string | null): number {
|
|
191
215
|
if (!raw) return 0;
|
|
192
216
|
const value = raw.trim();
|
|
@@ -965,17 +989,7 @@ export default function RecordingPage() {
|
|
|
965
989
|
return (
|
|
966
990
|
<div className="flex min-h-screen w-full flex-col bg-background">
|
|
967
991
|
<header className="flex min-w-0 shrink-0 items-center gap-3 border-b border-border px-3 py-2 sm:px-4 sm:py-3">
|
|
968
|
-
<
|
|
969
|
-
asChild
|
|
970
|
-
variant="ghost"
|
|
971
|
-
size="icon"
|
|
972
|
-
className="shrink-0"
|
|
973
|
-
aria-label={t("recordingPage.backToLibrary")}
|
|
974
|
-
>
|
|
975
|
-
<Link to="/library" replace>
|
|
976
|
-
<IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
|
|
977
|
-
</Link>
|
|
978
|
-
</Button>
|
|
992
|
+
<BackToLibraryButton />
|
|
979
993
|
<div className="min-w-0 flex-1">
|
|
980
994
|
<p className="truncate text-sm font-medium">{visibleTitle}</p>
|
|
981
995
|
<p className="truncate text-xs text-muted-foreground">
|
|
@@ -1294,20 +1308,7 @@ export default function RecordingPage() {
|
|
|
1294
1308
|
{/* Main video column */}
|
|
1295
1309
|
<div className="flex w-full min-w-0 flex-col xl:flex-1">
|
|
1296
1310
|
<header className="flex min-w-0 shrink-0 items-center gap-2 border-b border-border px-3 py-2 sm:gap-3 sm:px-4 sm:py-3">
|
|
1297
|
-
<
|
|
1298
|
-
asChild
|
|
1299
|
-
variant="ghost"
|
|
1300
|
-
size="icon"
|
|
1301
|
-
className="shrink-0 sm:w-auto sm:px-2"
|
|
1302
|
-
aria-label={t("recordingPage.backToLibrary")}
|
|
1303
|
-
>
|
|
1304
|
-
<Link to="/library" replace>
|
|
1305
|
-
<IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
|
|
1306
|
-
<span className="hidden sm:inline">
|
|
1307
|
-
{t("recordingPage.backToLibrary")}
|
|
1308
|
-
</span>
|
|
1309
|
-
</Link>
|
|
1310
|
-
</Button>
|
|
1311
|
+
<BackToLibraryButton />
|
|
1311
1312
|
<div className="flex-1 min-w-0">
|
|
1312
1313
|
<EditableRecordingTitle
|
|
1313
1314
|
recordingId={recording.id}
|
|
@@ -1226,6 +1226,7 @@ export default function ShareRoute() {
|
|
|
1226
1226
|
videoFormat={recording.videoFormat}
|
|
1227
1227
|
embedProvider={isLoomEmbedBacked ? "loom" : null}
|
|
1228
1228
|
durationMs={recording.durationMs}
|
|
1229
|
+
persistPlaybackPosition={Boolean(session)}
|
|
1229
1230
|
editsJson={recording.editsJson}
|
|
1230
1231
|
thumbnailUrl={recording.thumbnailUrl}
|
|
1231
1232
|
role={viewerRole ?? (viewerCanEdit ? "owner" : "viewer")}
|
|
@@ -11,7 +11,6 @@ import { FlowBar } from "./overlays/flow-bar";
|
|
|
11
11
|
import { MeetingNotification } from "./overlays/meeting-notification";
|
|
12
12
|
import { MeetingNub } from "./overlays/meeting-nub";
|
|
13
13
|
import { MonitorPicker } from "./overlays/monitor-picker";
|
|
14
|
-
import { Onboarding } from "./overlays/onboarding";
|
|
15
14
|
import { Preparing } from "./overlays/preparing";
|
|
16
15
|
import { RecordingPill } from "./overlays/recording-pill";
|
|
17
16
|
import { RegionGuideEditor, RegionGuides } from "./overlays/region-guides";
|
|
@@ -47,8 +46,6 @@ function pickRoute(route: string): React.ReactElement {
|
|
|
47
46
|
return <Finalizing />;
|
|
48
47
|
case "preparing":
|
|
49
48
|
return <Preparing />;
|
|
50
|
-
case "onboarding":
|
|
51
|
-
return <Onboarding />;
|
|
52
49
|
case "meeting-notif":
|
|
53
50
|
return <MeetingNotification />;
|
|
54
51
|
case "meeting-nub":
|
|
@@ -5263,124 +5263,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5263
5263
|
}
|
|
5264
5264
|
}
|
|
5265
5265
|
|
|
5266
|
-
/* ------------------------------------------------------------------------- */
|
|
5267
|
-
/* Overlay: onboarding */
|
|
5268
|
-
/* ------------------------------------------------------------------------- */
|
|
5269
|
-
|
|
5270
|
-
.onboarding-root {
|
|
5271
|
-
position: fixed;
|
|
5272
|
-
inset: 0;
|
|
5273
|
-
display: flex;
|
|
5274
|
-
align-items: center;
|
|
5275
|
-
justify-content: center;
|
|
5276
|
-
background: #212121;
|
|
5277
|
-
color: #f5f5f5;
|
|
5278
|
-
}
|
|
5279
|
-
|
|
5280
|
-
.onboarding-card {
|
|
5281
|
-
display: flex;
|
|
5282
|
-
flex-direction: column;
|
|
5283
|
-
align-items: center;
|
|
5284
|
-
gap: 20px;
|
|
5285
|
-
padding: 40px 36px;
|
|
5286
|
-
max-width: 380px;
|
|
5287
|
-
width: 100%;
|
|
5288
|
-
}
|
|
5289
|
-
|
|
5290
|
-
.onboarding-title {
|
|
5291
|
-
font-size: 22px;
|
|
5292
|
-
font-weight: 700;
|
|
5293
|
-
color: white;
|
|
5294
|
-
margin: 0;
|
|
5295
|
-
letter-spacing: -0.3px;
|
|
5296
|
-
}
|
|
5297
|
-
|
|
5298
|
-
.onboarding-subtitle {
|
|
5299
|
-
font-size: 14px;
|
|
5300
|
-
color: #a3a3a3;
|
|
5301
|
-
margin: -8px 0 0;
|
|
5302
|
-
}
|
|
5303
|
-
|
|
5304
|
-
.onboarding-features {
|
|
5305
|
-
display: flex;
|
|
5306
|
-
flex-direction: column;
|
|
5307
|
-
gap: 10px;
|
|
5308
|
-
width: 100%;
|
|
5309
|
-
}
|
|
5310
|
-
|
|
5311
|
-
.onboarding-feature {
|
|
5312
|
-
display: flex;
|
|
5313
|
-
align-items: flex-start;
|
|
5314
|
-
gap: 12px;
|
|
5315
|
-
padding: 14px 16px;
|
|
5316
|
-
background: rgba(255, 255, 255, 0.04);
|
|
5317
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
5318
|
-
border-radius: 12px;
|
|
5319
|
-
cursor: pointer;
|
|
5320
|
-
user-select: none;
|
|
5321
|
-
}
|
|
5322
|
-
|
|
5323
|
-
.onboarding-feature:hover {
|
|
5324
|
-
background: rgba(255, 255, 255, 0.06);
|
|
5325
|
-
}
|
|
5326
|
-
|
|
5327
|
-
.onboarding-checkbox {
|
|
5328
|
-
width: 18px;
|
|
5329
|
-
height: 18px;
|
|
5330
|
-
margin: 1px 0 0;
|
|
5331
|
-
accent-color: #16a34a;
|
|
5332
|
-
cursor: pointer;
|
|
5333
|
-
flex-shrink: 0;
|
|
5334
|
-
}
|
|
5335
|
-
|
|
5336
|
-
.onboarding-feature-text {
|
|
5337
|
-
display: flex;
|
|
5338
|
-
flex-direction: column;
|
|
5339
|
-
gap: 3px;
|
|
5340
|
-
}
|
|
5341
|
-
|
|
5342
|
-
.onboarding-feature-name {
|
|
5343
|
-
font-size: 14px;
|
|
5344
|
-
font-weight: 600;
|
|
5345
|
-
color: white;
|
|
5346
|
-
}
|
|
5347
|
-
|
|
5348
|
-
.onboarding-feature-desc {
|
|
5349
|
-
font-size: 12px;
|
|
5350
|
-
color: #a3a3a3;
|
|
5351
|
-
line-height: 1.4;
|
|
5352
|
-
}
|
|
5353
|
-
|
|
5354
|
-
.onboarding-cta {
|
|
5355
|
-
width: 100%;
|
|
5356
|
-
border: none;
|
|
5357
|
-
border-radius: 12px;
|
|
5358
|
-
padding: 12px 16px;
|
|
5359
|
-
background: #f5f5f5;
|
|
5360
|
-
color: #212121;
|
|
5361
|
-
font-size: 14px;
|
|
5362
|
-
font-weight: 600;
|
|
5363
|
-
cursor: pointer;
|
|
5364
|
-
height: 44px;
|
|
5365
|
-
display: inline-flex;
|
|
5366
|
-
align-items: center;
|
|
5367
|
-
justify-content: center;
|
|
5368
|
-
margin-top: 4px;
|
|
5369
|
-
}
|
|
5370
|
-
|
|
5371
|
-
.onboarding-cta:hover {
|
|
5372
|
-
background: #e5e5e5;
|
|
5373
|
-
}
|
|
5374
|
-
|
|
5375
|
-
.onboarding-cta:active {
|
|
5376
|
-
transform: translateY(1px);
|
|
5377
|
-
}
|
|
5378
|
-
|
|
5379
|
-
.onboarding-cta:disabled {
|
|
5380
|
-
opacity: 0.6;
|
|
5381
|
-
cursor: not-allowed;
|
|
5382
|
-
}
|
|
5383
|
-
|
|
5384
5266
|
/* ------------------------------------------------------------------------- */
|
|
5385
5267
|
/* Overlay: meeting notification */
|
|
5386
5268
|
/* ------------------------------------------------------------------------- */
|
|
@@ -48,7 +48,6 @@ const REGION_GUIDES_LABEL: &str = "region-guides";
|
|
|
48
48
|
const REGION_GUIDE_EDITOR_LABEL: &str = "region-guide-editor";
|
|
49
49
|
const REGION_RECORD_BORDER_LABEL: &str = "region-record-border";
|
|
50
50
|
const MONITOR_PICKER_LABEL_PREFIX: &str = "monitor-picker-";
|
|
51
|
-
const ONBOARDING_LABEL: &str = "onboarding";
|
|
52
51
|
const OVERLAY_LABELS: &[&str] = &[
|
|
53
52
|
COUNTDOWN_LABEL,
|
|
54
53
|
TOOLBAR_LABEL,
|
|
@@ -59,8 +58,6 @@ const OVERLAY_LABELS: &[&str] = &[
|
|
|
59
58
|
REGION_GUIDES_LABEL,
|
|
60
59
|
REGION_RECORD_BORDER_LABEL,
|
|
61
60
|
];
|
|
62
|
-
const ONBOARDING_WIDTH_LOGICAL: f64 = 560.0;
|
|
63
|
-
const ONBOARDING_HEIGHT_LOGICAL: f64 = 640.0;
|
|
64
61
|
|
|
65
62
|
/// Physical-pixel bubble sizes. Logical px on retina = physical / 2, so these
|
|
66
63
|
/// map to ~96 (small) and ~180 (medium) logical px — matching Loom's camera
|
|
@@ -599,48 +596,6 @@ pub async fn hide_finalizing(app: AppHandle) -> Result<(), String> {
|
|
|
599
596
|
Ok(())
|
|
600
597
|
}
|
|
601
598
|
|
|
602
|
-
/// First-run onboarding window (ONBOARD-WINDOW). Unlike the transparent,
|
|
603
|
-
/// click-through overlays above, this is a normal decorated, focused window
|
|
604
|
-
/// with its own solid dark background (`.onboarding-root` in styles.css) —
|
|
605
|
-
/// centered on the primary display so it reads as a real app window, not a
|
|
606
|
-
/// HUD. Called once from `lib.rs`'s `setup()` when `onboarding_complete` is
|
|
607
|
-
/// false. Reuses an existing window if one is somehow already open (e.g. a
|
|
608
|
-
/// hot-reload re-triggering setup in dev) instead of building a second one.
|
|
609
|
-
pub fn show_onboarding_window(app: &AppHandle) {
|
|
610
|
-
if let Some(existing) = app.get_webview_window(ONBOARDING_LABEL) {
|
|
611
|
-
let _ = existing.show();
|
|
612
|
-
let _ = existing.set_focus();
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
let win =
|
|
616
|
-
match WebviewWindowBuilder::new(app, ONBOARDING_LABEL, build_overlay_url("onboarding"))
|
|
617
|
-
.title("Welcome to Clips")
|
|
618
|
-
.inner_size(ONBOARDING_WIDTH_LOGICAL, ONBOARDING_HEIGHT_LOGICAL)
|
|
619
|
-
.resizable(false)
|
|
620
|
-
.center()
|
|
621
|
-
.focused(true)
|
|
622
|
-
.build()
|
|
623
|
-
{
|
|
624
|
-
Ok(w) => w,
|
|
625
|
-
Err(e) => {
|
|
626
|
-
eprintln!("[clips-tray] onboarding window build failed: {}", e);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
let _ = win.show();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/// Close the first-run onboarding window once the overlay's finish handler
|
|
634
|
-
/// has saved feature config + opened the popover. Idempotent — closing an
|
|
635
|
-
/// already-closed/never-built window is a no-op.
|
|
636
|
-
#[tauri::command]
|
|
637
|
-
pub async fn hide_onboarding_window(app: AppHandle) -> Result<(), String> {
|
|
638
|
-
if let Some(w) = app.get_webview_window(ONBOARDING_LABEL) {
|
|
639
|
-
let _ = w.close();
|
|
640
|
-
}
|
|
641
|
-
Ok(())
|
|
642
|
-
}
|
|
643
|
-
|
|
644
599
|
/// Full-screen, click-through recording guides. The React view renders the
|
|
645
600
|
/// saved translucent rectangles, while AppKit marks the whole overlay as
|
|
646
601
|
/// non-shareable so the guides stay private to the recorder.
|
|
@@ -134,7 +134,6 @@ pub struct FeatureConfig {
|
|
|
134
134
|
pub region_guides: RegionGuidesConfig,
|
|
135
135
|
#[serde(default)]
|
|
136
136
|
pub screen_memory: ScreenMemoryConfig,
|
|
137
|
-
pub onboarding_complete: bool,
|
|
138
137
|
#[serde(default = "default_whisper_model_enabled")]
|
|
139
138
|
pub whisper_model_enabled: bool,
|
|
140
139
|
#[serde(default = "default_whisper_model_id")]
|
|
@@ -238,7 +237,6 @@ impl Default for FeatureConfig {
|
|
|
238
237
|
show_in_screen_capture: false,
|
|
239
238
|
region_guides: RegionGuidesConfig::default(),
|
|
240
239
|
screen_memory: ScreenMemoryConfig::default(),
|
|
241
|
-
onboarding_complete: false,
|
|
242
240
|
whisper_model_enabled: default_whisper_model_enabled(),
|
|
243
241
|
whisper_model_id: default_whisper_model_id(),
|
|
244
242
|
}
|
|
@@ -437,8 +435,7 @@ mod tests {
|
|
|
437
435
|
let config: FeatureConfig = serde_json::from_value(serde_json::json!({
|
|
438
436
|
"clipsEnabled": true,
|
|
439
437
|
"meetingsEnabled": true,
|
|
440
|
-
"voiceEnabled": true
|
|
441
|
-
"onboardingComplete": true
|
|
438
|
+
"voiceEnabled": true
|
|
442
439
|
}))
|
|
443
440
|
.unwrap();
|
|
444
441
|
|
|
@@ -116,7 +116,6 @@ pub fn run() {
|
|
|
116
116
|
clips::close_signin,
|
|
117
117
|
clips::show_flow_bar,
|
|
118
118
|
clips::hide_flow_bar,
|
|
119
|
-
clips::hide_onboarding_window,
|
|
120
119
|
clips::complete_voice_dictation,
|
|
121
120
|
clips::paste_last_dictation,
|
|
122
121
|
clips::set_recording_state,
|
|
@@ -343,15 +342,6 @@ pub fn run() {
|
|
|
343
342
|
tray::build_tray(app)?;
|
|
344
343
|
config::sync_launch_at_login(app.handle());
|
|
345
344
|
let feature_config = config::feature_config(app.handle());
|
|
346
|
-
// ONBOARD-WINDOW: first-run onboarding never had a Rust-side
|
|
347
|
-
// window to appear in — the `#onboarding` hash route and overlay
|
|
348
|
-
// component existed, but nothing ever called
|
|
349
|
-
// WebviewWindowBuilder for it. Build it here, after the popover
|
|
350
|
-
// exists, gated on the persisted `onboarding_complete` flag so
|
|
351
|
-
// returning users never see it again.
|
|
352
|
-
if !feature_config.onboarding_complete {
|
|
353
|
-
clips::show_onboarding_window(app.handle());
|
|
354
|
-
}
|
|
355
345
|
screen_memory::sync_from_config(app.handle(), &feature_config);
|
|
356
346
|
// Re-show always-on region guides after relaunch/reboot when the
|
|
357
347
|
// setting is on (no-op if a recording owns the window or the
|
|
@@ -270,6 +270,19 @@ mod native_upload_mode_tests {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
#[cfg(test)]
|
|
274
|
+
mod pending_recording_file_name_tests {
|
|
275
|
+
use super::pending_recording_file_stem;
|
|
276
|
+
|
|
277
|
+
#[test]
|
|
278
|
+
fn uses_a_clips_specific_pending_recording_prefix() {
|
|
279
|
+
assert_eq!(
|
|
280
|
+
pending_recording_file_stem("FxGL6zX7HWhU", 4242),
|
|
281
|
+
"clips-pending-recording-FxGL6zX7HWhU-4242",
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
273
286
|
#[derive(Default)]
|
|
274
287
|
pub struct NativeFullscreenRecordingState {
|
|
275
288
|
inner: Mutex<Option<NativeFullscreenSession>>,
|
|
@@ -2764,14 +2777,26 @@ fn discard_session(session: &mut NativeFullscreenSession) {
|
|
|
2764
2777
|
|
|
2765
2778
|
/// Sibling path next to the original pending recording, numbered with
|
|
2766
2779
|
/// the segment counter so multiple resume cycles don't clobber each
|
|
2767
|
-
/// other. Example: `clips-
|
|
2780
|
+
/// other. Example: `clips-pending-recording-<id>-<pid>-seg2.mp4`.
|
|
2768
2781
|
fn segment_path_for(
|
|
2769
2782
|
app: &AppHandle,
|
|
2770
2783
|
safe_id: &str,
|
|
2771
2784
|
extension: &str,
|
|
2772
2785
|
counter: u32,
|
|
2773
2786
|
) -> Result<PathBuf, String> {
|
|
2774
|
-
pending_recording_path(app,
|
|
2787
|
+
let base_path = pending_recording_path(app, safe_id, extension)?;
|
|
2788
|
+
let base_stem = base_path
|
|
2789
|
+
.file_stem()
|
|
2790
|
+
.and_then(|value| value.to_str())
|
|
2791
|
+
.ok_or_else(|| "pending recording path has no valid file stem".to_string())?;
|
|
2792
|
+
Ok(base_path.with_file_name(format!(
|
|
2793
|
+
"{base_stem}-seg{counter}.{}",
|
|
2794
|
+
extension.trim_start_matches('.')
|
|
2795
|
+
)))
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
fn pending_recording_file_stem(safe_id: &str, pid: u32) -> String {
|
|
2799
|
+
format!("clips-pending-recording-{safe_id}-{pid}")
|
|
2775
2800
|
}
|
|
2776
2801
|
|
|
2777
2802
|
/// Dispatches to the right backend starter for resume. Mirrors the
|
|
@@ -3202,7 +3227,9 @@ fn orphan_recording_id(path: &Path) -> Option<String> {
|
|
|
3202
3227
|
return None;
|
|
3203
3228
|
}
|
|
3204
3229
|
let stem = path.file_stem().and_then(|value| value.to_str())?;
|
|
3205
|
-
let value = if let Some(value) = stem.strip_prefix("
|
|
3230
|
+
let value = if let Some(value) = stem.strip_prefix("clips-pending-recording-") {
|
|
3231
|
+
value
|
|
3232
|
+
} else if let Some(value) = stem.strip_prefix("rewind-") {
|
|
3206
3233
|
value
|
|
3207
3234
|
} else if let Some(value) = stem.strip_prefix("clips-fullscreen-") {
|
|
3208
3235
|
value
|
|
@@ -3525,15 +3552,40 @@ mod orphan_recovery_tests {
|
|
|
3525
3552
|
orphan_recording_id(Path::new("clips-fullscreen-e4esSx9NZCZa-1234.mov")),
|
|
3526
3553
|
Some("e4esSx9NZCZa".into())
|
|
3527
3554
|
);
|
|
3555
|
+
assert_eq!(
|
|
3556
|
+
orphan_recording_id(Path::new("clips-pending-recording-e4esSx9NZCZa-1234.mp4")),
|
|
3557
|
+
Some("e4esSx9NZCZa".into())
|
|
3558
|
+
);
|
|
3528
3559
|
assert!(is_recording_segment_path(Path::new(
|
|
3529
3560
|
"clips-fullscreen-e4esSx9NZCZa-1234-seg2.mp4"
|
|
3530
3561
|
)));
|
|
3562
|
+
assert!(is_recording_segment_path(Path::new(
|
|
3563
|
+
"clips-pending-recording-e4esSx9NZCZa-1234-seg2.mp4"
|
|
3564
|
+
)));
|
|
3531
3565
|
assert!(
|
|
3532
3566
|
orphan_recording_id(Path::new("clips-fullscreen-e4esSx9NZCZa-1234-seg2.mp4")).is_none()
|
|
3533
3567
|
);
|
|
3568
|
+
assert!(orphan_recording_id(Path::new(
|
|
3569
|
+
"clips-pending-recording-e4esSx9NZCZa-1234-seg2.mp4"
|
|
3570
|
+
))
|
|
3571
|
+
.is_none());
|
|
3534
3572
|
assert!(orphan_recording_id(Path::new("unrelated.mp4")).is_none());
|
|
3535
3573
|
}
|
|
3536
3574
|
|
|
3575
|
+
#[test]
|
|
3576
|
+
fn recovers_new_pending_recording_name_without_intent_sidecar() {
|
|
3577
|
+
let intent = orphan_recording_intent(
|
|
3578
|
+
Path::new("clips-pending-recording-recording-123-456.mp4"),
|
|
3579
|
+
"https://clips.example.test/",
|
|
3580
|
+
)
|
|
3581
|
+
.unwrap()
|
|
3582
|
+
.unwrap();
|
|
3583
|
+
|
|
3584
|
+
assert_eq!(intent.recording_id, "recording-123");
|
|
3585
|
+
assert_eq!(intent.server_url, "https://clips.example.test");
|
|
3586
|
+
assert!(!intent.custom_pipeline);
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3537
3589
|
#[test]
|
|
3538
3590
|
fn intent_sidecars_round_trip_and_map_back_to_media() {
|
|
3539
3591
|
let root = std::env::temp_dir().join(format!(
|
|
@@ -4167,8 +4219,8 @@ fn pending_recording_path(
|
|
|
4167
4219
|
extension: &str,
|
|
4168
4220
|
) -> Result<PathBuf, String> {
|
|
4169
4221
|
Ok(pending_uploads_dir(app)?.join(format!(
|
|
4170
|
-
"
|
|
4171
|
-
std::process::id(),
|
|
4222
|
+
"{}.{}",
|
|
4223
|
+
pending_recording_file_stem(safe_id, std::process::id()),
|
|
4172
4224
|
extension.trim_start_matches('.')
|
|
4173
4225
|
)))
|
|
4174
4226
|
}
|
|
@@ -76,6 +76,16 @@ Builder/internal data, or customer data in the action, UI, seed data, fixtures,
|
|
|
76
76
|
docs, prompts, or generated extension/app content. Register required secrets,
|
|
77
77
|
use OAuth helpers, or read scoped values from the vault/credential store.
|
|
78
78
|
|
|
79
|
+
Before writing custom setup or credential UI, perform the shared-primitive
|
|
80
|
+
preflight from `agent-native-toolkit`: inspect the workspace/provider connection
|
|
81
|
+
catalog first, then settings, secrets/vault, OAuth, onboarding, and provider API
|
|
82
|
+
support. Reuse an existing connection's app grant and scoped credential
|
|
83
|
+
resolver; only use app-local vault or OAuth primitives when no reusable
|
|
84
|
+
connection exists. Keep any wizard thin and provider-specific - it may explain
|
|
85
|
+
prerequisites, ordering, or readiness, but must not become a second vault or
|
|
86
|
+
custom credential transport. Do not mark each field `required` by reflex;
|
|
87
|
+
model onboarding around the logical connection outcome.
|
|
88
|
+
|
|
79
89
|
If the feature involves attachments, images, recordings, screenshots, exports,
|
|
80
90
|
or other file-like payloads, design the upload path in the same change:
|
|
81
91
|
provider upload first, then URL/id/blob handle in SQL. Do not add base64/binary
|
|
@@ -152,6 +152,30 @@ preference, notification preference, or usage/billing surface, register it as a
|
|
|
152
152
|
settings tab or app settings panel first. Only add sidebar UI when it is needed
|
|
153
153
|
in the moment of agent use.
|
|
154
154
|
|
|
155
|
+
## Integration Setup Preflight
|
|
156
|
+
|
|
157
|
+
Before building any setup, settings, credential, OAuth, or connection surface,
|
|
158
|
+
search the workspace/provider connection catalog first. If the provider already
|
|
159
|
+
has a reusable connection, use its catalog, app grant, and scoped credential
|
|
160
|
+
resolver rather than registering a parallel secret. Only then classify fields
|
|
161
|
+
that still need app-local setup by lifecycle and scope:
|
|
162
|
+
|
|
163
|
+
| Need | Default primitive |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Deploy- or app-level configuration | Runtime configuration or deployment env vars |
|
|
166
|
+
| Existing workspace/provider connection | Workspace-connection catalog/grant plus `resolveWorkspaceConnectionCredential(s)ForApp` |
|
|
167
|
+
| App-local API/service key with no reusable connection | `registerRequiredSecret({ kind: "api-key" })` and the vault |
|
|
168
|
+
| Authorization-code or refresh-token flow | `kind: "oauth"` with `@agent-native/core/oauth-tokens` |
|
|
169
|
+
| Account, customer, or other non-secret identifiers | Scoped connection metadata or app data |
|
|
170
|
+
| Provider-specific prerequisites, sequencing, or health | A thin app-local guide over the shared primitives |
|
|
171
|
+
|
|
172
|
+
Do not register every provider field as a generic secret, mark every field as
|
|
173
|
+
required, or create a second credential-management surface. One logical
|
|
174
|
+
connection should normally produce one onboarding outcome. A custom setup page
|
|
175
|
+
is appropriate only when it adds domain-specific guidance or readiness checks;
|
|
176
|
+
it should link to or call the shared settings, OAuth, and action surfaces rather
|
|
177
|
+
than duplicating their storage or transport.
|
|
178
|
+
|
|
155
179
|
## Reusable Kits
|
|
156
180
|
|
|
157
181
|
- **Settings kit**: a searchable settings page with account, workspace, AI
|
|
@@ -3,8 +3,8 @@ name: secrets
|
|
|
3
3
|
description: >-
|
|
4
4
|
Declaratively register API keys and service credentials a template needs so
|
|
5
5
|
they appear in the agent sidebar settings UI and the onboarding checklist.
|
|
6
|
-
Use
|
|
7
|
-
|
|
6
|
+
Use before adding any third-party credential or setup UI so API keys, OAuth
|
|
7
|
+
connections, and scoped configuration use the correct shared primitive.
|
|
8
8
|
scope: dev
|
|
9
9
|
metadata:
|
|
10
10
|
internal: true
|
|
@@ -24,6 +24,33 @@ encrypted `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
|
|
|
24
24
|
`${keys.NAME}` substitution. Examples must use obvious placeholders such as
|
|
25
25
|
`<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not real-looking copied values.
|
|
26
26
|
|
|
27
|
+
## Credential Modeling Preflight
|
|
28
|
+
|
|
29
|
+
Before registering a provider's fields, inspect the workspace/provider connection
|
|
30
|
+
catalog first. If a reusable connection exists, use its app grant and scoped
|
|
31
|
+
`resolveWorkspaceConnectionCredential(s)ForApp` path instead of registering a
|
|
32
|
+
parallel secret. Only classify fields for app-local setup when no reusable
|
|
33
|
+
connection exists:
|
|
34
|
+
|
|
35
|
+
- **API or service key** - register it as `kind: "api-key"` with the narrowest
|
|
36
|
+
correct `scope`, a human label, a description, a docs link, and a validator.
|
|
37
|
+
- **OAuth authorization or refresh token** - use the OAuth token store and
|
|
38
|
+
register a `kind: "oauth"` entry so the shared UI renders Connect and the
|
|
39
|
+
runtime owns status, refresh, and reauthorization.
|
|
40
|
+
- **Deploy- or app-level configuration** - use deployment/runtime
|
|
41
|
+
configuration, not a per-user secret row.
|
|
42
|
+
- **Account, customer, manager, or other non-secret identifier** - store it as
|
|
43
|
+
scoped connection metadata or app data, not as a masked secret field.
|
|
44
|
+
|
|
45
|
+
`required: true` is for a logical setup requirement. If a provider needs
|
|
46
|
+
several values, do not automatically create one required checklist item per
|
|
47
|
+
field; use one composite onboarding step or a registered connection readiness
|
|
48
|
+
check.
|
|
49
|
+
|
|
50
|
+
Custom setup UI is allowed for provider-specific prerequisites, ordering, or
|
|
51
|
+
health checks, but it must delegate credential storage and connection state to
|
|
52
|
+
the shared vault/OAuth/settings surfaces.
|
|
53
|
+
|
|
27
54
|
## When to use
|
|
28
55
|
|
|
29
56
|
Use this for any external credential your template needs: API keys, service
|
|
@@ -49,6 +49,7 @@ Read the relevant skill before deeper work:
|
|
|
49
49
|
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
50
50
|
or `resources`; persist URLs, ids, or handles instead.
|
|
51
51
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
52
|
+
- For external integrations, inspect the workspace/provider connection catalog first; reuse its scoped resolver.
|
|
52
53
|
|
|
53
54
|
## Application State
|
|
54
55
|
|