@agent-native/core 0.168.10 → 0.168.11
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/clips/changelog/2026-08-19-desktop-settings-are-tighter-with-a-new-advanced-tab.md +6 -0
- package/corpus/templates/clips/desktop/README.md +79 -1
- package/corpus/templates/clips/desktop/components.json +20 -0
- package/corpus/templates/clips/desktop/package.json +10 -1
- package/corpus/templates/clips/desktop/src/app.tsx +1812 -1994
- package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +23 -65
- package/corpus/templates/clips/desktop/src/components/settings/settings-ui.tsx +399 -0
- package/corpus/templates/clips/desktop/src/components/ui/alert-dialog.tsx +166 -0
- package/corpus/templates/clips/desktop/src/components/ui/button.tsx +57 -0
- package/corpus/templates/clips/desktop/src/components/ui/empty.tsx +104 -0
- package/corpus/templates/clips/desktop/src/components/ui/input.tsx +22 -0
- package/corpus/templates/clips/desktop/src/components/ui/popover.tsx +38 -0
- package/corpus/templates/clips/desktop/src/components/ui/select.tsx +164 -0
- package/corpus/templates/clips/desktop/src/components/ui/skeleton.tsx +15 -0
- package/corpus/templates/clips/desktop/src/components/ui/spinner.tsx +17 -0
- package/corpus/templates/clips/desktop/src/components/ui/switch.tsx +27 -0
- package/corpus/templates/clips/desktop/src/components/ui/textarea.tsx +22 -0
- package/corpus/templates/clips/desktop/src/dev/browser-preview.ts +65 -0
- package/corpus/templates/clips/desktop/src/hooks/useSystemAccessRows.ts +128 -0
- package/corpus/templates/clips/desktop/src/hooks/useWhisperSettings.ts +0 -2
- package/corpus/templates/clips/desktop/src/lib/permission-status.ts +84 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +6 -5
- package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +18 -5
- package/corpus/templates/clips/desktop/src/lib/utils.ts +6 -0
- package/corpus/templates/clips/desktop/src/main.tsx +31 -4
- package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +2 -2
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +2 -2
- package/corpus/templates/clips/desktop/src/styles.css +75 -1427
- package/corpus/templates/clips/desktop/src/tailwind.css +282 -0
- package/corpus/templates/clips/desktop/tsconfig.json +4 -1
- package/corpus/templates/clips/desktop/vite.config.ts +7 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/action-routes.js +3 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-origin.d.ts +6 -0
- package/dist/server/request-origin.js +12 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lets the tray's React surfaces render in a plain browser tab (`pnpm
|
|
3
|
+
* vite:dev`, http://localhost:1420) so layout and copy can be reviewed without
|
|
4
|
+
* a Rust build.
|
|
5
|
+
*
|
|
6
|
+
* Outside Tauri there is no IPC: `getCurrentWindow()` reads
|
|
7
|
+
* `window.__TAURI_INTERNALS__.metadata` and throws during the first effect,
|
|
8
|
+
* which blanks the whole app. This installs the smallest stub that keeps React
|
|
9
|
+
* mounted.
|
|
10
|
+
*
|
|
11
|
+
* Every `invoke` REJECTS rather than returning invented data. A settings row
|
|
12
|
+
* that cannot read its value should show its default and say so, not a
|
|
13
|
+
* fabricated state that looks real in a screenshot — so what you see here is
|
|
14
|
+
* the surface's genuine "backend unavailable" rendering. Anything needing true
|
|
15
|
+
* device state (permission grants, Whisper catalog, Rewind) has to be checked
|
|
16
|
+
* in the real app.
|
|
17
|
+
*
|
|
18
|
+
* Dev-only: `main.tsx` calls this behind `import.meta.env.DEV`, so it is absent
|
|
19
|
+
* from any production bundle.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type TauriInternals = {
|
|
23
|
+
metadata: {
|
|
24
|
+
currentWindow: { label: string };
|
|
25
|
+
currentWebview: { label: string; windowLabel: string };
|
|
26
|
+
};
|
|
27
|
+
invoke: (cmd: string, args?: unknown) => Promise<never>;
|
|
28
|
+
transformCallback: (cb: (payload: unknown) => void) => number;
|
|
29
|
+
convertFileSrc: (path: string) => string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
declare global {
|
|
33
|
+
interface Window {
|
|
34
|
+
__TAURI_INTERNALS__?: TauriInternals;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function installBrowserPreview(): void {
|
|
39
|
+
if (window.__TAURI_INTERNALS__) return;
|
|
40
|
+
|
|
41
|
+
let callbackId = 0;
|
|
42
|
+
const callbacks = new Map<number, (payload: unknown) => void>();
|
|
43
|
+
|
|
44
|
+
window.__TAURI_INTERNALS__ = {
|
|
45
|
+
metadata: {
|
|
46
|
+
currentWindow: { label: "main" },
|
|
47
|
+
currentWebview: { label: "main", windowLabel: "main" },
|
|
48
|
+
},
|
|
49
|
+
invoke: (cmd) =>
|
|
50
|
+
Promise.reject(
|
|
51
|
+
new Error(`Tauri command "${cmd}" is unavailable in browser preview`),
|
|
52
|
+
),
|
|
53
|
+
transformCallback: (cb) => {
|
|
54
|
+
const id = ++callbackId;
|
|
55
|
+
callbacks.set(id, cb);
|
|
56
|
+
return id;
|
|
57
|
+
},
|
|
58
|
+
convertFileSrc: (path) => path,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
console.info(
|
|
62
|
+
"[clips-tray] browser preview: Tauri commands will reject. Add a route " +
|
|
63
|
+
"hash to jump straight to a surface, e.g. #settings or #settings/advanced.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
permissionStatusForPane,
|
|
5
|
+
readPermissionStatuses,
|
|
6
|
+
requestOrOpenPermission,
|
|
7
|
+
type MacosPrivacyPane,
|
|
8
|
+
type PermissionStatuses,
|
|
9
|
+
} from "../lib/permission-status";
|
|
10
|
+
import { isMacPlatform } from "../lib/platform";
|
|
11
|
+
|
|
12
|
+
export type SystemAccessRow = {
|
|
13
|
+
key: string;
|
|
14
|
+
label: string;
|
|
15
|
+
description: string;
|
|
16
|
+
/** `null` means the grant could not be read — never render it as denied. */
|
|
17
|
+
granted: boolean | null;
|
|
18
|
+
onGrant: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type SystemAccessDefinition = {
|
|
22
|
+
key: string;
|
|
23
|
+
label: string;
|
|
24
|
+
description: string;
|
|
25
|
+
panes: MacosPrivacyPane[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collapses the per-pane permission list into the few lines a user acts on:
|
|
30
|
+
* capture as one grant, then only the extras their configuration needs.
|
|
31
|
+
*/
|
|
32
|
+
export function useSystemAccessRows({
|
|
33
|
+
includeVoicePaste,
|
|
34
|
+
includeFnMonitoring,
|
|
35
|
+
onOpenSettings,
|
|
36
|
+
}: {
|
|
37
|
+
includeVoicePaste: boolean;
|
|
38
|
+
includeFnMonitoring: boolean;
|
|
39
|
+
onOpenSettings: (pane: MacosPrivacyPane) => void;
|
|
40
|
+
}): SystemAccessRow[] {
|
|
41
|
+
const mac = isMacPlatform();
|
|
42
|
+
const [statuses, setStatuses] = useState<PermissionStatuses | null>(null);
|
|
43
|
+
|
|
44
|
+
const recheck = useCallback(() => {
|
|
45
|
+
void readPermissionStatuses().then(setStatuses);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// Grants are changed in System Settings, outside this window. Re-reading on
|
|
49
|
+
// focus is what stops a row from still demanding "Grant" after the user did.
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
recheck();
|
|
52
|
+
window.addEventListener("focus", recheck);
|
|
53
|
+
return () => window.removeEventListener("focus", recheck);
|
|
54
|
+
}, [recheck]);
|
|
55
|
+
|
|
56
|
+
// One row per OS grant, worded exactly like the recorder's readiness panel —
|
|
57
|
+
// the same grant must read as the same thing on both surfaces, and each
|
|
58
|
+
// Grant button must open exactly the pane its row names. The earlier
|
|
59
|
+
// combined capture row walked three panes one click at a time, which read
|
|
60
|
+
// as one grant that mysteriously kept not taking.
|
|
61
|
+
const definitions: SystemAccessDefinition[] = [
|
|
62
|
+
{
|
|
63
|
+
key: "screen",
|
|
64
|
+
label: "Screen Recording",
|
|
65
|
+
description: "Allows Clips to record your screen",
|
|
66
|
+
panes: ["screen"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: "microphone",
|
|
70
|
+
label: "Microphone",
|
|
71
|
+
description: "Allows Clips to access your microphone",
|
|
72
|
+
panes: ["microphone"],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "camera",
|
|
76
|
+
label: "Camera",
|
|
77
|
+
description: "Allows Clips to access your camera",
|
|
78
|
+
panes: ["camera"],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
if (mac) {
|
|
82
|
+
definitions.push({
|
|
83
|
+
key: "speech",
|
|
84
|
+
label: "Speech Recognition",
|
|
85
|
+
description: "Allows Clips to use speech recognition",
|
|
86
|
+
panes: ["speech"],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (mac && includeVoicePaste) {
|
|
90
|
+
definitions.push({
|
|
91
|
+
key: "accessibility",
|
|
92
|
+
label: "Accessibility",
|
|
93
|
+
description: "Allows Clips to control this device to paste dictated text",
|
|
94
|
+
panes: ["accessibility"],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (mac && includeFnMonitoring) {
|
|
98
|
+
definitions.push({
|
|
99
|
+
key: "input-monitoring",
|
|
100
|
+
label: "Input Monitoring",
|
|
101
|
+
description: "Allows Clips to detect the Fn key",
|
|
102
|
+
panes: ["input-monitoring"],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return definitions.map(({ panes, ...definition }) => {
|
|
107
|
+
const results = panes.map((pane) =>
|
|
108
|
+
permissionStatusForPane(pane, statuses),
|
|
109
|
+
);
|
|
110
|
+
const granted = results.some((result) => result === null)
|
|
111
|
+
? null
|
|
112
|
+
: results.every(Boolean);
|
|
113
|
+
return {
|
|
114
|
+
...definition,
|
|
115
|
+
granted,
|
|
116
|
+
onGrant: () => {
|
|
117
|
+
const target =
|
|
118
|
+
panes.find(
|
|
119
|
+
(pane) => permissionStatusForPane(pane, statuses) === false,
|
|
120
|
+
) ?? panes[0];
|
|
121
|
+
void requestOrOpenPermission(target, {
|
|
122
|
+
onOpenSettings,
|
|
123
|
+
onRecheck: recheck,
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -48,7 +48,6 @@ export function useWhisperSettings(
|
|
|
48
48
|
|
|
49
49
|
const enabled = featureConfig?.whisperModelEnabled !== false;
|
|
50
50
|
const modelId = featureConfig?.whisperModelId ?? "base";
|
|
51
|
-
const selectedModel = catalog.find((m) => m.id === modelId) ?? null;
|
|
52
51
|
const deletableModels = catalog.filter(
|
|
53
52
|
(m) => m.id !== modelId && downloadedModelIds.includes(m.id),
|
|
54
53
|
);
|
|
@@ -162,7 +161,6 @@ export function useWhisperSettings(
|
|
|
162
161
|
status,
|
|
163
162
|
enabled,
|
|
164
163
|
modelId,
|
|
165
|
-
selectedModel,
|
|
166
164
|
deletableModels,
|
|
167
165
|
triggerDownload,
|
|
168
166
|
setEnabled,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Reading and repairing OS privacy grants. Shared by the recorder's readiness
|
|
2
|
+
// list and Settings › General › System access so both surfaces report the same
|
|
3
|
+
// three states.
|
|
4
|
+
import { invoke } from "@tauri-apps/api/core";
|
|
5
|
+
|
|
6
|
+
import { isMacPlatform } from "./platform";
|
|
7
|
+
|
|
8
|
+
export type MacosPrivacyPane =
|
|
9
|
+
| "camera"
|
|
10
|
+
| "microphone"
|
|
11
|
+
| "screen"
|
|
12
|
+
| "speech"
|
|
13
|
+
| "accessibility"
|
|
14
|
+
| "input-monitoring";
|
|
15
|
+
|
|
16
|
+
export type PermissionStatuses = {
|
|
17
|
+
screen: boolean;
|
|
18
|
+
camera: boolean;
|
|
19
|
+
microphone: boolean;
|
|
20
|
+
speech: boolean;
|
|
21
|
+
accessibility: boolean;
|
|
22
|
+
inputMonitoring: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `null` means the grants could not be read — a non-macOS host, or the command
|
|
27
|
+
* failing. It is not the same as "denied", and callers must not render it as
|
|
28
|
+
* one: an unreadable grant offers to open System Settings, a denied grant asks
|
|
29
|
+
* the user to fix it.
|
|
30
|
+
*/
|
|
31
|
+
export async function readPermissionStatuses(): Promise<PermissionStatuses | null> {
|
|
32
|
+
if (!isMacPlatform()) return null;
|
|
33
|
+
try {
|
|
34
|
+
return await invoke<PermissionStatuses>("check_permission_statuses");
|
|
35
|
+
} catch {
|
|
36
|
+
// coercion-ok: null IS the typed "could not read" value — callers render
|
|
37
|
+
// it as unknown, never as denied (locked by permission-status.test.ts).
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function permissionStatusForPane(
|
|
43
|
+
pane: MacosPrivacyPane,
|
|
44
|
+
statuses: PermissionStatuses | null,
|
|
45
|
+
): boolean | null {
|
|
46
|
+
if (!statuses) return null;
|
|
47
|
+
const map: Record<MacosPrivacyPane, boolean> = {
|
|
48
|
+
screen: statuses.screen,
|
|
49
|
+
camera: statuses.camera,
|
|
50
|
+
microphone: statuses.microphone,
|
|
51
|
+
speech: statuses.speech,
|
|
52
|
+
accessibility: statuses.accessibility,
|
|
53
|
+
"input-monitoring": statuses.inputMonitoring,
|
|
54
|
+
};
|
|
55
|
+
return map[pane];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Screen Recording is the one grant macOS will still prompt for in-app, so try
|
|
60
|
+
* that before sending the user to System Settings. Every other pane only opens.
|
|
61
|
+
*/
|
|
62
|
+
export async function requestOrOpenPermission(
|
|
63
|
+
pane: MacosPrivacyPane,
|
|
64
|
+
{
|
|
65
|
+
onOpenSettings,
|
|
66
|
+
onRecheck,
|
|
67
|
+
}: {
|
|
68
|
+
onOpenSettings: (pane: MacosPrivacyPane) => void;
|
|
69
|
+
onRecheck?: () => void;
|
|
70
|
+
},
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
if (isMacPlatform() && pane === "screen") {
|
|
73
|
+
try {
|
|
74
|
+
const granted = await invoke<boolean>("system_audio_request_permission");
|
|
75
|
+
onRecheck?.();
|
|
76
|
+
if (granted) return;
|
|
77
|
+
} catch {
|
|
78
|
+
// coercion-ok: the request API may be unavailable on an older macOS
|
|
79
|
+
// build, or the user may have denied the prompt once already — falling
|
|
80
|
+
// through to open the dedicated privacy pane IS the recovery path.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
onOpenSettings(pane);
|
|
84
|
+
}
|
|
@@ -52,7 +52,8 @@ export function getRewindStatusPresentation({
|
|
|
52
52
|
return {
|
|
53
53
|
kind: "excluded",
|
|
54
54
|
title: "Rewind is protecting a private moment",
|
|
55
|
-
detail:
|
|
55
|
+
detail:
|
|
56
|
+
status.coverage || "An excluded app is in front, so capture is paused.",
|
|
56
57
|
isLive: false,
|
|
57
58
|
hasError: false,
|
|
58
59
|
};
|
|
@@ -62,7 +63,7 @@ export function getRewindStatusPresentation({
|
|
|
62
63
|
return {
|
|
63
64
|
kind: "unavailable",
|
|
64
65
|
title: "Rewind is unavailable",
|
|
65
|
-
detail: "
|
|
66
|
+
detail: "Capture isn't available on this device.",
|
|
66
67
|
isLive: false,
|
|
67
68
|
hasError: false,
|
|
68
69
|
};
|
|
@@ -75,7 +76,7 @@ export function getRewindStatusPresentation({
|
|
|
75
76
|
detail:
|
|
76
77
|
status.lastError ||
|
|
77
78
|
status.coverage ||
|
|
78
|
-
"
|
|
79
|
+
"New moments are being saved to this device.",
|
|
79
80
|
isLive: true,
|
|
80
81
|
hasError: Boolean(status.lastError),
|
|
81
82
|
};
|
|
@@ -93,10 +94,10 @@ export function getRewindStatusPresentation({
|
|
|
93
94
|
|
|
94
95
|
return {
|
|
95
96
|
kind: "idle",
|
|
96
|
-
title: "Rewind is
|
|
97
|
+
title: "Rewind is on but not capturing",
|
|
97
98
|
detail: clipRecordingActive
|
|
98
99
|
? "Rewind will resume when this Clip ends."
|
|
99
|
-
: "
|
|
100
|
+
: "Nothing new is being saved right now.",
|
|
100
101
|
isLive: false,
|
|
101
102
|
hasError: false,
|
|
102
103
|
};
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// Order is the settings nav order; app.tsx's settingsTabs labels/icons follow it.
|
|
2
|
+
export const DESKTOP_SETTINGS_TABS = [
|
|
3
|
+
"general",
|
|
4
|
+
"recording",
|
|
5
|
+
"rewind",
|
|
6
|
+
"meetings",
|
|
7
|
+
"dictation",
|
|
8
|
+
"advanced",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type DesktopSettingsTab = (typeof DESKTOP_SETTINGS_TABS)[number];
|
|
6
12
|
|
|
7
13
|
export function initialDesktopSettingsTab(
|
|
8
14
|
tab?: DesktopSettingsTab,
|
|
9
15
|
): DesktopSettingsTab {
|
|
10
16
|
return tab ?? "general";
|
|
11
17
|
}
|
|
18
|
+
|
|
19
|
+
/** `#settings/<tab>` detail → a known tab, or undefined for anything else. */
|
|
20
|
+
export function asDesktopSettingsTab(
|
|
21
|
+
value: string | undefined,
|
|
22
|
+
): DesktopSettingsTab | undefined {
|
|
23
|
+
return DESKTOP_SETTINGS_TABS.find((tab) => tab === value);
|
|
24
|
+
}
|
|
@@ -2,8 +2,10 @@ import { invoke } from "@tauri-apps/api/core";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import ReactDOM from "react-dom/client";
|
|
4
4
|
|
|
5
|
-
import { App } from "./app";
|
|
5
|
+
import { App, installAuthFetchInterceptor } from "./app";
|
|
6
|
+
import { installBrowserPreview } from "./dev/browser-preview";
|
|
6
7
|
import { initDesktopSentry } from "./lib/sentry";
|
|
8
|
+
import { asDesktopSettingsTab } from "./lib/settings-navigation";
|
|
7
9
|
import { Bubble } from "./overlays/bubble";
|
|
8
10
|
import { Countdown } from "./overlays/countdown";
|
|
9
11
|
import { Finalizing } from "./overlays/finalizing";
|
|
@@ -17,7 +19,8 @@ import { RegionGuideEditor, RegionGuides } from "./overlays/region-guides";
|
|
|
17
19
|
import { RegionRecordBorder } from "./overlays/region-record-border";
|
|
18
20
|
import { Toolbar } from "./overlays/toolbar";
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
// Imports styles.css itself, into a lower cascade layer — see tailwind.css.
|
|
23
|
+
import "./tailwind.css";
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* One bundle, one HTML, many views. We pick which component to mount based
|
|
@@ -26,7 +29,14 @@ import "./styles.css";
|
|
|
26
29
|
*/
|
|
27
30
|
function currentRoute(): string {
|
|
28
31
|
const hash = window.location.hash.replace(/^#/, "").toLowerCase();
|
|
29
|
-
|
|
32
|
+
// `#settings/advanced` names a surface plus where to open it; the route is
|
|
33
|
+
// the part before the slash.
|
|
34
|
+
return hash.split("/")[0] || "popover";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `#settings/advanced` → "advanced". */
|
|
38
|
+
function currentRouteDetail(): string | undefined {
|
|
39
|
+
return window.location.hash.replace(/^#/, "").toLowerCase().split("/")[1];
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
function installRouteAttributes(route: string): void {
|
|
@@ -64,6 +74,16 @@ function pickRoute(route: string): React.ReactElement {
|
|
|
64
74
|
return <RegionRecordBorder />;
|
|
65
75
|
case "monitor-picker":
|
|
66
76
|
return <MonitorPicker />;
|
|
77
|
+
// Opens the popover straight onto Settings. Rust never spawns this route —
|
|
78
|
+
// it exists so `#settings` / `#settings/advanced` reaches the surface
|
|
79
|
+
// directly in a browser tab or a dev window.
|
|
80
|
+
case "settings":
|
|
81
|
+
return (
|
|
82
|
+
<App
|
|
83
|
+
initialView="settings"
|
|
84
|
+
initialSettingsTab={asDesktopSettingsTab(currentRouteDetail())}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
67
87
|
default:
|
|
68
88
|
return <App />;
|
|
69
89
|
}
|
|
@@ -214,7 +234,14 @@ function installConsoleCapture(route: string): void {
|
|
|
214
234
|
const rootEl = document.getElementById("root");
|
|
215
235
|
if (rootEl) {
|
|
216
236
|
const route = currentRoute();
|
|
217
|
-
|
|
237
|
+
// Before anything reads Tauri internals: outside the app shell this keeps the
|
|
238
|
+
// surfaces mountable in a browser tab. No-ops inside Tauri, absent in prod.
|
|
239
|
+
if (import.meta.env.DEV) installBrowserPreview();
|
|
240
|
+
// Before React renders: covers fetches made during the first commit.
|
|
241
|
+
installAuthFetchInterceptor();
|
|
242
|
+
// `settings` is the popover window showing one of its views, so it takes the
|
|
243
|
+
// popover's shell styling rather than a full-viewport overlay's.
|
|
244
|
+
installRouteAttributes(route === "settings" ? "popover" : route);
|
|
218
245
|
initDesktopSentry(route);
|
|
219
246
|
installConsoleCapture(route);
|
|
220
247
|
installBeforeUnloadCleanup();
|
|
@@ -219,8 +219,8 @@ export function Finalizing() {
|
|
|
219
219
|
? "Uploaded"
|
|
220
220
|
: progress.stage === "failed"
|
|
221
221
|
? progress.savedLocally
|
|
222
|
-
? "Upload paused
|
|
223
|
-
: "Upload paused
|
|
222
|
+
? "Upload paused. Clip saved locally."
|
|
223
|
+
: "Upload paused. Open Clips to recover."
|
|
224
224
|
: progress.stage === "uploading" ||
|
|
225
225
|
progress.stage === "processing" ||
|
|
226
226
|
progress.stage === "opening"
|
|
@@ -366,8 +366,8 @@ export function Toolbar() {
|
|
|
366
366
|
className={`toolbar-v-disk-indicator toolbar-v-disk-indicator-${diskSpaceLevel}`}
|
|
367
367
|
title={
|
|
368
368
|
diskSpaceLevel === "critical"
|
|
369
|
-
? "Disk almost full
|
|
370
|
-
: "Low disk space
|
|
369
|
+
? "Disk almost full. Stop recording now to avoid losing your clip."
|
|
370
|
+
: "Low disk space. Save your recording soon."
|
|
371
371
|
}
|
|
372
372
|
data-no-drag
|
|
373
373
|
>
|