@agent-native/core 0.114.15 → 0.114.16
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/bin/agent-native.js +35 -14
- package/bin/launcher.js +34 -0
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/bin/agent-native.js +35 -14
- package/corpus/core/bin/launcher.js +34 -0
- package/corpus/core/package.json +1 -1
- package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +25 -8
- package/corpus/templates/clips/app/components/library/library-grid.tsx +118 -9
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +10 -8
- package/corpus/templates/clips/app/i18n/en-US.ts +6 -0
- package/corpus/templates/clips/app/routes/_app.trash.tsx +16 -1
- package/corpus/templates/clips/desktop/src/app.tsx +24 -3
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +20 -38
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +40 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/bin/agent-native.js
CHANGED
|
@@ -5,6 +5,8 @@ import { existsSync, statSync } from "node:fs";
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
+
import { shouldUseSourceFallback } from "./launcher.js";
|
|
9
|
+
|
|
8
10
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const distEntry = join(binDir, "../dist/cli/index.js");
|
|
10
12
|
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
@@ -16,31 +18,50 @@ const freshnessChecks = [
|
|
|
16
18
|
],
|
|
17
19
|
];
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
// The tsx source fallback and mtime freshness check are for local monorepo
|
|
22
|
+
// development only. Published installs ship both src and dist, and tarball
|
|
23
|
+
// extraction can leave .ts files newer than .js, which must not trigger tsx
|
|
24
|
+
// (not a runtime dependency). Only consider the fallback in a source checkout.
|
|
25
|
+
const isSourceCheckout = existsSync(join(binDir, "../tsconfig.cli.json"));
|
|
26
|
+
|
|
27
|
+
function statMtimeMs(path) {
|
|
22
28
|
try {
|
|
23
|
-
return
|
|
24
|
-
([source, dist]) =>
|
|
25
|
-
existsSync(source) &&
|
|
26
|
-
existsSync(dist) &&
|
|
27
|
-
statSync(source).mtimeMs > statSync(dist).mtimeMs,
|
|
28
|
-
);
|
|
29
|
+
return statSync(path).mtimeMs;
|
|
29
30
|
} catch {
|
|
30
|
-
return
|
|
31
|
+
return 0;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const freshness = freshnessChecks.map(([source, dist]) => {
|
|
36
|
+
const sourceExists = existsSync(source);
|
|
37
|
+
const distExists = existsSync(dist);
|
|
38
|
+
return {
|
|
39
|
+
sourceExists,
|
|
40
|
+
distExists,
|
|
41
|
+
sourceMtimeMs: sourceExists ? statMtimeMs(source) : 0,
|
|
42
|
+
distMtimeMs: distExists ? statMtimeMs(dist) : 0,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const useSourceFallback = shouldUseSourceFallback({
|
|
47
|
+
isSourceCheckout,
|
|
48
|
+
sourceEntryExists: existsSync(sourceEntry),
|
|
49
|
+
distEntryExists: existsSync(distEntry),
|
|
50
|
+
freshness,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!useSourceFallback) {
|
|
54
|
+
// Installed packages (and up-to-date checkouts) always run the shipped build.
|
|
55
|
+
// tsx is not a runtime dependency, so it must never be invoked here.
|
|
56
|
+
if (!existsSync(distEntry)) {
|
|
38
57
|
console.error(
|
|
39
58
|
"agent-native CLI build output is missing. Run `pnpm --filter @agent-native/core build` and try again.",
|
|
40
59
|
);
|
|
41
60
|
process.exit(1);
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
await import(pathToFileURL(distEntry).href);
|
|
64
|
+
} else {
|
|
44
65
|
const child = spawn("tsx", [sourceEntry, ...process.argv.slice(2)], {
|
|
45
66
|
stdio: "inherit",
|
|
46
67
|
env: process.env,
|
package/bin/launcher.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Pure decision logic for the agent-native CLI launcher shim, kept dependency
|
|
2
|
+
// free so it can be unit tested without touching the filesystem.
|
|
3
|
+
//
|
|
4
|
+
// The tsx source fallback and mtime freshness check exist for local monorepo
|
|
5
|
+
// development only. Published installs ship both src and dist, and tarball
|
|
6
|
+
// extraction can leave .ts files newer than .js — that must never route to tsx
|
|
7
|
+
// (not a runtime dependency), or `npx @agent-native/core ...` fails with
|
|
8
|
+
// `spawn tsx ENOENT`. The `isSourceCheckout` gate is what keeps installed
|
|
9
|
+
// packages on the shipped dist build.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} input
|
|
13
|
+
* @param {boolean} input.isSourceCheckout Whether we run from a monorepo checkout.
|
|
14
|
+
* @param {boolean} input.sourceEntryExists Whether the .ts CLI entry exists.
|
|
15
|
+
* @param {boolean} input.distEntryExists Whether the compiled .js CLI entry exists.
|
|
16
|
+
* @param {Array<{sourceExists: boolean, distExists: boolean, sourceMtimeMs: number, distMtimeMs: number}>} [input.freshness]
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function shouldUseSourceFallback({
|
|
20
|
+
isSourceCheckout,
|
|
21
|
+
sourceEntryExists,
|
|
22
|
+
distEntryExists,
|
|
23
|
+
freshness = [],
|
|
24
|
+
}) {
|
|
25
|
+
if (!isSourceCheckout) return false;
|
|
26
|
+
if (!sourceEntryExists) return false;
|
|
27
|
+
if (!distEntryExists) return true;
|
|
28
|
+
return freshness.some(
|
|
29
|
+
(pair) =>
|
|
30
|
+
pair.sourceExists &&
|
|
31
|
+
pair.distExists &&
|
|
32
|
+
pair.sourceMtimeMs > pair.distMtimeMs,
|
|
33
|
+
);
|
|
34
|
+
}
|
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.114.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3a3eb9a: Fix `npx @agent-native/core create` failing with `spawn tsx ENOENT`. The CLI launcher's tsx source fallback is now scoped to monorepo source checkouts, so installed packages always run the shipped `dist` build even when tarball extraction leaves `.ts` files newer than their compiled `.js`.
|
|
8
|
+
|
|
3
9
|
## 0.114.15
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -5,6 +5,8 @@ import { existsSync, statSync } from "node:fs";
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
+
import { shouldUseSourceFallback } from "./launcher.js";
|
|
9
|
+
|
|
8
10
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const distEntry = join(binDir, "../dist/cli/index.js");
|
|
10
12
|
const sourceEntry = join(binDir, "../src/cli/index.ts");
|
|
@@ -16,31 +18,50 @@ const freshnessChecks = [
|
|
|
16
18
|
],
|
|
17
19
|
];
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
// The tsx source fallback and mtime freshness check are for local monorepo
|
|
22
|
+
// development only. Published installs ship both src and dist, and tarball
|
|
23
|
+
// extraction can leave .ts files newer than .js, which must not trigger tsx
|
|
24
|
+
// (not a runtime dependency). Only consider the fallback in a source checkout.
|
|
25
|
+
const isSourceCheckout = existsSync(join(binDir, "../tsconfig.cli.json"));
|
|
26
|
+
|
|
27
|
+
function statMtimeMs(path) {
|
|
22
28
|
try {
|
|
23
|
-
return
|
|
24
|
-
([source, dist]) =>
|
|
25
|
-
existsSync(source) &&
|
|
26
|
-
existsSync(dist) &&
|
|
27
|
-
statSync(source).mtimeMs > statSync(dist).mtimeMs,
|
|
28
|
-
);
|
|
29
|
+
return statSync(path).mtimeMs;
|
|
29
30
|
} catch {
|
|
30
|
-
return
|
|
31
|
+
return 0;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const freshness = freshnessChecks.map(([source, dist]) => {
|
|
36
|
+
const sourceExists = existsSync(source);
|
|
37
|
+
const distExists = existsSync(dist);
|
|
38
|
+
return {
|
|
39
|
+
sourceExists,
|
|
40
|
+
distExists,
|
|
41
|
+
sourceMtimeMs: sourceExists ? statMtimeMs(source) : 0,
|
|
42
|
+
distMtimeMs: distExists ? statMtimeMs(dist) : 0,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const useSourceFallback = shouldUseSourceFallback({
|
|
47
|
+
isSourceCheckout,
|
|
48
|
+
sourceEntryExists: existsSync(sourceEntry),
|
|
49
|
+
distEntryExists: existsSync(distEntry),
|
|
50
|
+
freshness,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!useSourceFallback) {
|
|
54
|
+
// Installed packages (and up-to-date checkouts) always run the shipped build.
|
|
55
|
+
// tsx is not a runtime dependency, so it must never be invoked here.
|
|
56
|
+
if (!existsSync(distEntry)) {
|
|
38
57
|
console.error(
|
|
39
58
|
"agent-native CLI build output is missing. Run `pnpm --filter @agent-native/core build` and try again.",
|
|
40
59
|
);
|
|
41
60
|
process.exit(1);
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
await import(pathToFileURL(distEntry).href);
|
|
64
|
+
} else {
|
|
44
65
|
const child = spawn("tsx", [sourceEntry, ...process.argv.slice(2)], {
|
|
45
66
|
stdio: "inherit",
|
|
46
67
|
env: process.env,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Pure decision logic for the agent-native CLI launcher shim, kept dependency
|
|
2
|
+
// free so it can be unit tested without touching the filesystem.
|
|
3
|
+
//
|
|
4
|
+
// The tsx source fallback and mtime freshness check exist for local monorepo
|
|
5
|
+
// development only. Published installs ship both src and dist, and tarball
|
|
6
|
+
// extraction can leave .ts files newer than .js — that must never route to tsx
|
|
7
|
+
// (not a runtime dependency), or `npx @agent-native/core ...` fails with
|
|
8
|
+
// `spawn tsx ENOENT`. The `isSourceCheckout` gate is what keeps installed
|
|
9
|
+
// packages on the shipped dist build.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} input
|
|
13
|
+
* @param {boolean} input.isSourceCheckout Whether we run from a monorepo checkout.
|
|
14
|
+
* @param {boolean} input.sourceEntryExists Whether the .ts CLI entry exists.
|
|
15
|
+
* @param {boolean} input.distEntryExists Whether the compiled .js CLI entry exists.
|
|
16
|
+
* @param {Array<{sourceExists: boolean, distExists: boolean, sourceMtimeMs: number, distMtimeMs: number}>} [input.freshness]
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function shouldUseSourceFallback({
|
|
20
|
+
isSourceCheckout,
|
|
21
|
+
sourceEntryExists,
|
|
22
|
+
distEntryExists,
|
|
23
|
+
freshness = [],
|
|
24
|
+
}) {
|
|
25
|
+
if (!isSourceCheckout) return false;
|
|
26
|
+
if (!sourceEntryExists) return false;
|
|
27
|
+
if (!distEntryExists) return true;
|
|
28
|
+
return freshness.some(
|
|
29
|
+
(pair) =>
|
|
30
|
+
pair.sourceExists &&
|
|
31
|
+
pair.distExists &&
|
|
32
|
+
pair.sourceMtimeMs > pair.distMtimeMs,
|
|
33
|
+
);
|
|
34
|
+
}
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.16",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -27,6 +27,8 @@ export interface BulkMoveTarget {
|
|
|
27
27
|
|
|
28
28
|
interface BulkActionToolbarProps {
|
|
29
29
|
count: number;
|
|
30
|
+
allSelected?: boolean;
|
|
31
|
+
onSelectAll?: () => void;
|
|
30
32
|
onArchive?: () => void;
|
|
31
33
|
onMove?: (folderId: string | null) => void;
|
|
32
34
|
onTrash?: () => void;
|
|
@@ -38,6 +40,8 @@ interface BulkActionToolbarProps {
|
|
|
38
40
|
|
|
39
41
|
export function BulkActionToolbar({
|
|
40
42
|
count,
|
|
43
|
+
allSelected = false,
|
|
44
|
+
onSelectAll,
|
|
41
45
|
onArchive,
|
|
42
46
|
onMove,
|
|
43
47
|
onTrash,
|
|
@@ -53,17 +57,30 @@ export function BulkActionToolbar({
|
|
|
53
57
|
return (
|
|
54
58
|
<div
|
|
55
59
|
className={cn(
|
|
56
|
-
"flex w-fit max-w-full items-center gap-1 rounded-xl
|
|
60
|
+
"flex w-fit max-w-full items-center gap-1 rounded-xl bg-foreground px-3 py-2 text-background shadow-2xl ring-1 ring-black/10 dark:ring-white/10",
|
|
57
61
|
)}
|
|
58
62
|
>
|
|
59
|
-
<span className="pe-2 text-xs font-medium
|
|
63
|
+
<span className="pe-2 text-xs font-medium">
|
|
60
64
|
{t("clipsFinalRaw.selectedCount", { count })}
|
|
61
65
|
</span>
|
|
62
|
-
|
|
66
|
+
{onSelectAll && (
|
|
67
|
+
<Button
|
|
68
|
+
variant="ghost"
|
|
69
|
+
size="sm"
|
|
70
|
+
className="h-8 gap-1.5 text-background hover:bg-background/15 hover:text-background"
|
|
71
|
+
onClick={onSelectAll}
|
|
72
|
+
disabled={isPending}
|
|
73
|
+
>
|
|
74
|
+
{allSelected
|
|
75
|
+
? t("clipsFinalRaw.deselectAll")
|
|
76
|
+
: t("clipsFinalRaw.selectAll")}
|
|
77
|
+
</Button>
|
|
78
|
+
)}
|
|
79
|
+
<div className="h-4 w-px bg-background/20" />
|
|
63
80
|
<Button
|
|
64
81
|
variant="ghost"
|
|
65
82
|
size="sm"
|
|
66
|
-
className="h-8 gap-1.5"
|
|
83
|
+
className="h-8 gap-1.5 text-background hover:bg-background/15 hover:text-background"
|
|
67
84
|
onClick={onArchive}
|
|
68
85
|
disabled={isPending}
|
|
69
86
|
>
|
|
@@ -75,7 +92,7 @@ export function BulkActionToolbar({
|
|
|
75
92
|
<Button
|
|
76
93
|
variant="ghost"
|
|
77
94
|
size="sm"
|
|
78
|
-
className="h-8 gap-1.5"
|
|
95
|
+
className="h-8 gap-1.5 text-background hover:bg-background/15 hover:text-background"
|
|
79
96
|
disabled={isPending}
|
|
80
97
|
>
|
|
81
98
|
<IconFolder className="h-3.5 w-3.5" /> {t("clipsFinalRaw.move")}
|
|
@@ -121,17 +138,17 @@ export function BulkActionToolbar({
|
|
|
121
138
|
<Button
|
|
122
139
|
variant="ghost"
|
|
123
140
|
size="sm"
|
|
124
|
-
className="h-8 gap-1.5 text-
|
|
141
|
+
className="h-8 gap-1.5 text-red-400 hover:bg-background/15 hover:text-red-400"
|
|
125
142
|
onClick={onTrash}
|
|
126
143
|
disabled={isPending}
|
|
127
144
|
>
|
|
128
145
|
<IconTrash className="h-3.5 w-3.5" /> {t("navigation.trash")}
|
|
129
146
|
</Button>
|
|
130
|
-
<div className="h-4 w-px bg-
|
|
147
|
+
<div className="mx-1 h-4 w-px bg-background/20" />
|
|
131
148
|
<button
|
|
132
149
|
type="button"
|
|
133
150
|
onClick={onClear}
|
|
134
|
-
className="rounded p-1 text-
|
|
151
|
+
className="rounded p-1 text-background/70 hover:bg-background/15 hover:text-background"
|
|
135
152
|
aria-label={t("clipsFinalRaw.clearSelection")}
|
|
136
153
|
>
|
|
137
154
|
<IconX className="h-3.5 w-3.5" />
|
|
@@ -3,8 +3,12 @@ import {
|
|
|
3
3
|
setClientAppState,
|
|
4
4
|
} from "@agent-native/core/client/hooks";
|
|
5
5
|
import { useT } from "@agent-native/core/client/i18n";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
IconAlertTriangle,
|
|
8
|
+
IconChevronLeft,
|
|
9
|
+
IconChevronRight,
|
|
10
|
+
} from "@tabler/icons-react";
|
|
11
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
8
12
|
import { toast } from "sonner";
|
|
9
13
|
|
|
10
14
|
import { CreateFolderDialog } from "@/components/library/create-folder-dialog";
|
|
@@ -13,6 +17,7 @@ import { Button } from "@/components/ui/button";
|
|
|
13
17
|
import {
|
|
14
18
|
useFolders,
|
|
15
19
|
useRecordings,
|
|
20
|
+
useRecordingsCount,
|
|
16
21
|
useTrashRecording,
|
|
17
22
|
useArchiveRecording,
|
|
18
23
|
useRestoreRecording,
|
|
@@ -54,6 +59,8 @@ function Skeleton() {
|
|
|
54
59
|
);
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
const PAGE_SIZE = 100;
|
|
63
|
+
|
|
57
64
|
interface FolderTargetRow {
|
|
58
65
|
id: string;
|
|
59
66
|
parentId: string | null;
|
|
@@ -105,13 +112,38 @@ export function LibraryGrid({
|
|
|
105
112
|
const t = useT();
|
|
106
113
|
const [sort, setSort] = useState<SortKey>("recent");
|
|
107
114
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
115
|
+
const [lastSelectedId, setLastSelectedId] = useState<string | null>(null);
|
|
108
116
|
const selectionMode = selected.size > 0;
|
|
109
117
|
const [sharingRec, setSharingRec] = useState<RecordingSummary | null>(null);
|
|
110
118
|
const [createFolderTarget, setCreateFolderTarget] =
|
|
111
119
|
useState<CreateFolderTarget | null>(null);
|
|
112
120
|
const [isBulkPending, setIsBulkPending] = useState(false);
|
|
121
|
+
const [page, setPage] = useState(1);
|
|
113
122
|
const selectionStateKey = useMemo(() => `selection:${getBrowserTabId()}`, []);
|
|
114
123
|
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
setPage(1);
|
|
126
|
+
setSelected(new Set());
|
|
127
|
+
setLastSelectedId(null);
|
|
128
|
+
}, [view, folderId, spaceId, tagFilter, sort]);
|
|
129
|
+
|
|
130
|
+
const countArgs = useMemo(
|
|
131
|
+
() => ({
|
|
132
|
+
view,
|
|
133
|
+
folderId: folderId ?? null,
|
|
134
|
+
spaceId: spaceId ?? null,
|
|
135
|
+
tag: tagFilter ?? null,
|
|
136
|
+
}),
|
|
137
|
+
[view, folderId, spaceId, tagFilter],
|
|
138
|
+
);
|
|
139
|
+
const { data: totalCount } = useRecordingsCount(countArgs);
|
|
140
|
+
const total = totalCount ?? 0;
|
|
141
|
+
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (page > totalPages) setPage(totalPages);
|
|
145
|
+
}, [page, totalPages]);
|
|
146
|
+
|
|
115
147
|
const args: ListRecordingsArgs = useMemo(
|
|
116
148
|
() => ({
|
|
117
149
|
view,
|
|
@@ -119,8 +151,10 @@ export function LibraryGrid({
|
|
|
119
151
|
spaceId: spaceId ?? null,
|
|
120
152
|
tag: tagFilter ?? null,
|
|
121
153
|
sort,
|
|
154
|
+
limit: PAGE_SIZE,
|
|
155
|
+
offset: (page - 1) * PAGE_SIZE,
|
|
122
156
|
}),
|
|
123
|
-
[view, folderId, spaceId, tagFilter, sort],
|
|
157
|
+
[view, folderId, spaceId, tagFilter, sort, page],
|
|
124
158
|
);
|
|
125
159
|
|
|
126
160
|
const { data, isLoading, isError, refetch, isRefetching } =
|
|
@@ -189,17 +223,53 @@ export function LibraryGrid({
|
|
|
189
223
|
};
|
|
190
224
|
}, [selectionStateKey]);
|
|
191
225
|
|
|
192
|
-
const
|
|
226
|
+
const handleToggleSelect = useCallback(
|
|
227
|
+
(id: string, shiftKey = false) => {
|
|
228
|
+
setSelected((prev) => {
|
|
229
|
+
if (shiftKey && lastSelectedId && lastSelectedId !== id) {
|
|
230
|
+
const ids = recordings.map((r) => r.id);
|
|
231
|
+
const fromIndex = ids.indexOf(lastSelectedId);
|
|
232
|
+
const toIndex = ids.indexOf(id);
|
|
233
|
+
if (fromIndex !== -1 && toIndex !== -1) {
|
|
234
|
+
const [start, end] =
|
|
235
|
+
fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
|
|
236
|
+
const next = new Set(prev);
|
|
237
|
+
for (let i = start; i <= end; i++) next.add(ids[i]);
|
|
238
|
+
return next;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const next = new Set(prev);
|
|
242
|
+
if (next.has(id)) next.delete(id);
|
|
243
|
+
else next.add(id);
|
|
244
|
+
return next;
|
|
245
|
+
});
|
|
246
|
+
setLastSelectedId(id);
|
|
247
|
+
},
|
|
248
|
+
[lastSelectedId, recordings],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const allSelected =
|
|
252
|
+
recordings.length > 0 && recordings.every(({ id }) => selected.has(id));
|
|
253
|
+
|
|
254
|
+
const toggleSelectAll = useCallback(() => {
|
|
193
255
|
setSelected((prev) => {
|
|
194
256
|
const next = new Set(prev);
|
|
195
|
-
|
|
196
|
-
|
|
257
|
+
const visibleIds = recordings.map(({ id }) => id);
|
|
258
|
+
const allVisibleSelected = visibleIds.every((id) => prev.has(id));
|
|
259
|
+
|
|
260
|
+
for (const id of visibleIds) {
|
|
261
|
+
if (allVisibleSelected) next.delete(id);
|
|
262
|
+
else next.add(id);
|
|
263
|
+
}
|
|
264
|
+
|
|
197
265
|
return next;
|
|
198
266
|
});
|
|
199
|
-
|
|
267
|
+
setLastSelectedId(null);
|
|
268
|
+
}, [recordings]);
|
|
200
269
|
|
|
201
270
|
const clearSelection = () => {
|
|
202
271
|
setSelected(new Set());
|
|
272
|
+
setLastSelectedId(null);
|
|
203
273
|
};
|
|
204
274
|
|
|
205
275
|
const moveRecordings = async (
|
|
@@ -321,7 +391,7 @@ export function LibraryGrid({
|
|
|
321
391
|
)}
|
|
322
392
|
|
|
323
393
|
{/* Grid body */}
|
|
324
|
-
<div className="relative flex min-h-0 flex-1 overflow-hidden">
|
|
394
|
+
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
325
395
|
<div
|
|
326
396
|
className={cn(
|
|
327
397
|
"min-h-0 flex-1 overflow-y-auto",
|
|
@@ -371,7 +441,7 @@ export function LibraryGrid({
|
|
|
371
441
|
}
|
|
372
442
|
selectionMode={canManageRecordings && selectionMode}
|
|
373
443
|
onToggleSelect={
|
|
374
|
-
canManageRecordings ?
|
|
444
|
+
canManageRecordings ? handleToggleSelect : undefined
|
|
375
445
|
}
|
|
376
446
|
onShare={(rec) => setSharingRec(rec)}
|
|
377
447
|
moveTargets={moveTargets}
|
|
@@ -426,12 +496,51 @@ export function LibraryGrid({
|
|
|
426
496
|
</div>
|
|
427
497
|
</div>
|
|
428
498
|
|
|
499
|
+
{!isLoading && recordings.length > 0 && totalPages > 1 && (
|
|
500
|
+
<div className="flex shrink-0 items-center justify-between gap-3 border-t border-border px-5 py-2.5">
|
|
501
|
+
<span className="text-xs text-muted-foreground">
|
|
502
|
+
{t("libraryGrid.paginationRange", {
|
|
503
|
+
start: (page - 1) * PAGE_SIZE + 1,
|
|
504
|
+
end: (page - 1) * PAGE_SIZE + recordings.length,
|
|
505
|
+
total,
|
|
506
|
+
})}
|
|
507
|
+
</span>
|
|
508
|
+
<div className="flex items-center gap-2">
|
|
509
|
+
<Button
|
|
510
|
+
variant="outline"
|
|
511
|
+
size="sm"
|
|
512
|
+
className="gap-1"
|
|
513
|
+
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
514
|
+
disabled={page <= 1}
|
|
515
|
+
>
|
|
516
|
+
<IconChevronLeft className="h-3.5 w-3.5" />
|
|
517
|
+
{t("libraryGrid.paginationPrevious")}
|
|
518
|
+
</Button>
|
|
519
|
+
<span className="text-xs text-muted-foreground">
|
|
520
|
+
{t("libraryGrid.paginationPage", { page, totalPages })}
|
|
521
|
+
</span>
|
|
522
|
+
<Button
|
|
523
|
+
variant="outline"
|
|
524
|
+
size="sm"
|
|
525
|
+
className="gap-1"
|
|
526
|
+
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
|
527
|
+
disabled={page >= totalPages}
|
|
528
|
+
>
|
|
529
|
+
{t("libraryGrid.paginationNext")}
|
|
530
|
+
<IconChevronRight className="h-3.5 w-3.5" />
|
|
531
|
+
</Button>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
|
|
429
536
|
{/* Keep selected-library actions visible while the recording list scrolls. */}
|
|
430
537
|
{canManageRecordings && selected.size > 0 && (
|
|
431
538
|
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-30 flex justify-center px-4 pb-4">
|
|
432
539
|
<div className="pointer-events-auto max-w-full overflow-x-auto">
|
|
433
540
|
<BulkActionToolbar
|
|
434
541
|
count={selected.size}
|
|
542
|
+
allSelected={allSelected}
|
|
543
|
+
onSelectAll={toggleSelectAll}
|
|
435
544
|
moveTargets={moveTargets}
|
|
436
545
|
onArchive={async () => {
|
|
437
546
|
setIsBulkPending(true);
|
|
@@ -566,7 +566,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
566
566
|
browserTabId={getBrowserTabId()}
|
|
567
567
|
>
|
|
568
568
|
{/* Main content area */}
|
|
569
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
569
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
570
570
|
{!pageOwnsToolbar && (
|
|
571
571
|
<header className="flex shrink-0 items-center gap-3 border-b border-border px-5 py-3">
|
|
572
572
|
<button
|
|
@@ -65,7 +65,7 @@ interface RecordingCardProps {
|
|
|
65
65
|
recording: RecordingSummary;
|
|
66
66
|
selected?: boolean;
|
|
67
67
|
selectionMode?: boolean;
|
|
68
|
-
onToggleSelect?: (id: string) => void;
|
|
68
|
+
onToggleSelect?: (id: string, shiftKey: boolean) => void;
|
|
69
69
|
onShare?: (rec: RecordingSummary) => void;
|
|
70
70
|
onMove?: (rec: RecordingSummary, folderId: string | null) => void;
|
|
71
71
|
moveTargets?: BulkMoveTarget[];
|
|
@@ -147,18 +147,18 @@ export function RecordingCard({
|
|
|
147
147
|
const handleLinkClick = useCallback(
|
|
148
148
|
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
|
149
149
|
if (
|
|
150
|
-
!
|
|
150
|
+
!onToggleSelect ||
|
|
151
151
|
event.button !== 0 ||
|
|
152
152
|
event.metaKey ||
|
|
153
153
|
event.ctrlKey ||
|
|
154
|
-
event.
|
|
155
|
-
event.
|
|
154
|
+
event.altKey ||
|
|
155
|
+
(!selectionMode && !event.shiftKey)
|
|
156
156
|
) {
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
event.preventDefault();
|
|
161
|
-
onToggleSelect
|
|
161
|
+
onToggleSelect(recording.id, event.shiftKey);
|
|
162
162
|
},
|
|
163
163
|
[onToggleSelect, recording.id, selectionMode],
|
|
164
164
|
);
|
|
@@ -166,7 +166,7 @@ export function RecordingCard({
|
|
|
166
166
|
const handleCheckbox = useCallback(
|
|
167
167
|
(e: React.MouseEvent) => {
|
|
168
168
|
e.stopPropagation();
|
|
169
|
-
onToggleSelect?.(recording.id);
|
|
169
|
+
onToggleSelect?.(recording.id, e.shiftKey);
|
|
170
170
|
},
|
|
171
171
|
[onToggleSelect, recording.id],
|
|
172
172
|
);
|
|
@@ -243,8 +243,10 @@ export function RecordingCard({
|
|
|
243
243
|
>
|
|
244
244
|
<Checkbox
|
|
245
245
|
checked={selected}
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
onClick={(e) => {
|
|
247
|
+
e.stopPropagation();
|
|
248
|
+
onToggleSelect?.(recording.id, e.shiftKey);
|
|
249
|
+
}}
|
|
248
250
|
className="h-3.5 w-3.5"
|
|
249
251
|
/>
|
|
250
252
|
</div>
|
|
@@ -833,6 +833,10 @@ All notable user-facing changes to Clips are documented here. Open it any time f
|
|
|
833
833
|
loadFailedBody:
|
|
834
834
|
"Something went wrong while loading this list. Your recordings are safe — try again.",
|
|
835
835
|
retry: "Retry",
|
|
836
|
+
paginationRange: "{{start}}–{{end}} of {{total}}",
|
|
837
|
+
paginationPrevious: "Previous",
|
|
838
|
+
paginationNext: "Next",
|
|
839
|
+
paginationPage: "Page {{page}} of {{totalPages}}",
|
|
836
840
|
},
|
|
837
841
|
notificationsRoute: {
|
|
838
842
|
pageTitle: "Notifications · Clips",
|
|
@@ -1370,6 +1374,8 @@ All notable user-facing changes to Clips are documented here. Open it any time f
|
|
|
1370
1374
|
clipsFinalRaw: {
|
|
1371
1375
|
splitAtPlayhead: "Split at playhead (S)",
|
|
1372
1376
|
selectedCount: "{{count}} selected",
|
|
1377
|
+
selectAll: "Select all",
|
|
1378
|
+
deselectAll: "Deselect all",
|
|
1373
1379
|
move: "Move",
|
|
1374
1380
|
moveSelected: "Move {{count}} selected",
|
|
1375
1381
|
current: "Current",
|
|
@@ -46,6 +46,7 @@ export default function TrashRoute() {
|
|
|
46
46
|
const t = useT();
|
|
47
47
|
const [sort, setSort] = useState<SortKey>("recent");
|
|
48
48
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
49
|
+
const [lastSelectedId, setLastSelectedId] = useState<string | null>(null);
|
|
49
50
|
const [confirmPurge, setConfirmPurge] = useState(false);
|
|
50
51
|
const [singlePurgeId, setSinglePurgeId] = useState<string | null>(null);
|
|
51
52
|
const [isBulkPending, setIsBulkPending] = useState(false);
|
|
@@ -60,13 +61,26 @@ export default function TrashRoute() {
|
|
|
60
61
|
"delete-recording-permanent",
|
|
61
62
|
);
|
|
62
63
|
|
|
63
|
-
const toggleSelect = (id: string) => {
|
|
64
|
+
const toggleSelect = (id: string, shiftKey = false) => {
|
|
64
65
|
setSelected((prev) => {
|
|
66
|
+
if (shiftKey && lastSelectedId && lastSelectedId !== id) {
|
|
67
|
+
const ids = recordings.map((r) => r.id);
|
|
68
|
+
const fromIndex = ids.indexOf(lastSelectedId);
|
|
69
|
+
const toIndex = ids.indexOf(id);
|
|
70
|
+
if (fromIndex !== -1 && toIndex !== -1) {
|
|
71
|
+
const [start, end] =
|
|
72
|
+
fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
|
|
73
|
+
const next = new Set(prev);
|
|
74
|
+
for (let i = start; i <= end; i++) next.add(ids[i]);
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
65
78
|
const next = new Set(prev);
|
|
66
79
|
if (next.has(id)) next.delete(id);
|
|
67
80
|
else next.add(id);
|
|
68
81
|
return next;
|
|
69
82
|
});
|
|
83
|
+
setLastSelectedId(id);
|
|
70
84
|
};
|
|
71
85
|
|
|
72
86
|
const restoreAll = async (ids: string[]) => {
|
|
@@ -170,6 +184,7 @@ export default function TrashRoute() {
|
|
|
170
184
|
? new Set()
|
|
171
185
|
: new Set(recordings.map((r) => r.id)),
|
|
172
186
|
);
|
|
187
|
+
setLastSelectedId(null);
|
|
173
188
|
};
|
|
174
189
|
|
|
175
190
|
return (
|
|
@@ -572,6 +572,21 @@ function openPrivacySettings(pane: MacosPrivacyPane): void {
|
|
|
572
572
|
}
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
+
// Same explicit-drag pattern the toolbar/bubble overlays use —
|
|
576
|
+
// `data-tauri-drag-region` has been unreliable, so we call `startDragging()`
|
|
577
|
+
// directly on mousedown. Clicks on buttons/inputs still reach their handlers
|
|
578
|
+
// since we only start a drag when the mousedown target isn't inside one.
|
|
579
|
+
function handlePopoverHeaderMouseDown(event: React.MouseEvent) {
|
|
580
|
+
if (event.button !== 0) return;
|
|
581
|
+
const target = event.target as HTMLElement;
|
|
582
|
+
if (target.closest("button, a, input, select, textarea")) return;
|
|
583
|
+
getCurrentWindow()
|
|
584
|
+
.startDragging()
|
|
585
|
+
.catch((err) => {
|
|
586
|
+
console.warn("[clips-popover] startDragging failed:", err);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
575
590
|
function nativeVoiceProvider(): VoiceProvider {
|
|
576
591
|
return isMacPlatform() ? "macos-native" : "browser";
|
|
577
592
|
}
|
|
@@ -4248,7 +4263,10 @@ function Header({
|
|
|
4248
4263
|
// close button lives top-right as an absolute-positioned sibling, so the
|
|
4249
4264
|
// tabs aren't offset by the close button's width.
|
|
4250
4265
|
return (
|
|
4251
|
-
<div
|
|
4266
|
+
<div
|
|
4267
|
+
className="header header-centered"
|
|
4268
|
+
onMouseDown={handlePopoverHeaderMouseDown}
|
|
4269
|
+
>
|
|
4252
4270
|
<FeedbackButton submitterEmail={submitterEmail} />
|
|
4253
4271
|
<div
|
|
4254
4272
|
className="mode-toggle"
|
|
@@ -4444,7 +4462,10 @@ function PopoverSubViewHeader({
|
|
|
4444
4462
|
action?: ReactNode;
|
|
4445
4463
|
}) {
|
|
4446
4464
|
return (
|
|
4447
|
-
<div
|
|
4465
|
+
<div
|
|
4466
|
+
className="setup-header popover-view-header"
|
|
4467
|
+
onMouseDown={handlePopoverHeaderMouseDown}
|
|
4468
|
+
>
|
|
4448
4469
|
<button
|
|
4449
4470
|
type="button"
|
|
4450
4471
|
className="setup-back"
|
|
@@ -6376,7 +6397,7 @@ function Setup({
|
|
|
6376
6397
|
|
|
6377
6398
|
return (
|
|
6378
6399
|
<div className="setup">
|
|
6379
|
-
<div className="setup-header">
|
|
6400
|
+
<div className="setup-header" onMouseDown={handlePopoverHeaderMouseDown}>
|
|
6380
6401
|
{onCancel ? (
|
|
6381
6402
|
<button
|
|
6382
6403
|
type="button"
|
|
@@ -4143,25 +4143,6 @@ function DatabaseItemPreview({
|
|
|
4143
4143
|
draftVersionsRef.current.set(documentId, null);
|
|
4144
4144
|
else if (result.draft) {
|
|
4145
4145
|
draftVersionsRef.current.set(documentId, result.draft.version);
|
|
4146
|
-
if (
|
|
4147
|
-
previewDraftNeedsConflict({
|
|
4148
|
-
returnedDraft: result.draft,
|
|
4149
|
-
pending: controller.pending,
|
|
4150
|
-
})
|
|
4151
|
-
) {
|
|
4152
|
-
controller.notifyDraftConflict({
|
|
4153
|
-
lastSaved: snapshot.lastSaved,
|
|
4154
|
-
pending: {
|
|
4155
|
-
title: result.draft.title,
|
|
4156
|
-
content: result.draft.content,
|
|
4157
|
-
loadedUpdatedAt:
|
|
4158
|
-
result.draft.baseDocumentUpdatedAt ?? undefined,
|
|
4159
|
-
loadedContentWasEmpty:
|
|
4160
|
-
result.draft.loadedContentWasEmpty === 1,
|
|
4161
|
-
},
|
|
4162
|
-
deferredReason: "conflict",
|
|
4163
|
-
});
|
|
4164
|
-
}
|
|
4165
4146
|
} else {
|
|
4166
4147
|
// Another tab already removed the row. Treat the stale delete as
|
|
4167
4148
|
// converged instead of retaining a version that can never match.
|
|
@@ -4186,24 +4167,6 @@ function DatabaseItemPreview({
|
|
|
4186
4167
|
draftVersionsRef.current.set(documentId, result.draft.version);
|
|
4187
4168
|
} else if (result.draft) {
|
|
4188
4169
|
draftVersionsRef.current.set(documentId, result.draft.version);
|
|
4189
|
-
if (
|
|
4190
|
-
previewDraftNeedsConflict({
|
|
4191
|
-
returnedDraft: result.draft,
|
|
4192
|
-
pending: controller.pending,
|
|
4193
|
-
})
|
|
4194
|
-
) {
|
|
4195
|
-
controller.notifyDraftConflict({
|
|
4196
|
-
lastSaved: snapshot.lastSaved,
|
|
4197
|
-
pending: {
|
|
4198
|
-
title: result.draft.title,
|
|
4199
|
-
content: result.draft.content,
|
|
4200
|
-
loadedUpdatedAt:
|
|
4201
|
-
result.draft.baseDocumentUpdatedAt ?? undefined,
|
|
4202
|
-
loadedContentWasEmpty: result.draft.loadedContentWasEmpty === 1,
|
|
4203
|
-
},
|
|
4204
|
-
deferredReason: "conflict",
|
|
4205
|
-
});
|
|
4206
|
-
}
|
|
4207
4170
|
} else {
|
|
4208
4171
|
// A competing tab deleted the version we tried to update. Reset to
|
|
4209
4172
|
// create mode and enqueue the latest pending payload once; keeping
|
|
@@ -4298,7 +4261,13 @@ function DatabaseItemPreview({
|
|
|
4298
4261
|
);
|
|
4299
4262
|
return;
|
|
4300
4263
|
}
|
|
4301
|
-
resolve(
|
|
4264
|
+
resolve({
|
|
4265
|
+
outcome: "saved" as const,
|
|
4266
|
+
loadedUpdatedAt: result.updatedAt,
|
|
4267
|
+
loadedContentWasEmpty: isEffectivelyEmptyDocumentContent(
|
|
4268
|
+
result.content,
|
|
4269
|
+
),
|
|
4270
|
+
});
|
|
4302
4271
|
},
|
|
4303
4272
|
onError: reject,
|
|
4304
4273
|
},
|
|
@@ -4410,6 +4379,8 @@ function DatabaseItemPreview({
|
|
|
4410
4379
|
}
|
|
4411
4380
|
return;
|
|
4412
4381
|
}
|
|
4382
|
+
const previouslyExpectedVersion =
|
|
4383
|
+
draftVersionsRef.current.get(documentId) ?? null;
|
|
4413
4384
|
draftVersionsRef.current.set(documentId, draft.version);
|
|
4414
4385
|
const controller = peekPreviewDocumentSaveController(documentId);
|
|
4415
4386
|
if (!controller) return;
|
|
@@ -4431,8 +4402,19 @@ function DatabaseItemPreview({
|
|
|
4431
4402
|
? draft.deferredReason
|
|
4432
4403
|
: null,
|
|
4433
4404
|
} as const;
|
|
4405
|
+
// A poll tick simply confirming a version this client's own debounced
|
|
4406
|
+
// write already advanced the ref to is not an external change — it is
|
|
4407
|
+
// this client's typing racing ahead of its own last round trip. Comparing
|
|
4408
|
+
// that echo against the still-live `pending` would flag a conflict on
|
|
4409
|
+
// almost every keystroke. Only treat the draft as having moved out from
|
|
4410
|
+
// under the user when its version is newer than what this client itself
|
|
4411
|
+
// last recorded.
|
|
4412
|
+
const draftAdvancedExternally =
|
|
4413
|
+
previouslyExpectedVersion !== null &&
|
|
4414
|
+
draft.version > previouslyExpectedVersion;
|
|
4434
4415
|
if (controllerDirty) {
|
|
4435
4416
|
if (
|
|
4417
|
+
draftAdvancedExternally &&
|
|
4436
4418
|
previewDraftNeedsConflict({
|
|
4437
4419
|
returnedDraft: draft,
|
|
4438
4420
|
pending: controller.pending,
|
|
@@ -67,6 +67,19 @@ export interface PreviewDocumentSaveDeferred {
|
|
|
67
67
|
conflictSnapshot?: PreviewDocumentDraftSnapshot;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* A successful save can report the server's fresh `updatedAt`/emptiness back
|
|
72
|
+
* to the controller so its baseline stops looking stale. Without this, a
|
|
73
|
+
* baseline seeded as empty (a brand-new page) stays flagged empty forever, so
|
|
74
|
+
* a later poll of content the user just saved themselves gets mistaken for a
|
|
75
|
+
* non-empty body arriving externally over an empty one.
|
|
76
|
+
*/
|
|
77
|
+
export interface PreviewDocumentSaveSuccess {
|
|
78
|
+
outcome: "saved";
|
|
79
|
+
loadedUpdatedAt?: string;
|
|
80
|
+
loadedContentWasEmpty?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
export interface PreviewDocumentSaveAdapter {
|
|
71
84
|
save: (
|
|
72
85
|
documentId: string,
|
|
@@ -151,6 +164,18 @@ function payloadsEqual(a: PreviewDocumentPayload, b: PreviewDocumentPayload) {
|
|
|
151
164
|
return a.title === b.title && a.content === b.content;
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
function asSaveSuccess(result: unknown): PreviewDocumentSaveSuccess | null {
|
|
168
|
+
if (
|
|
169
|
+
result &&
|
|
170
|
+
typeof result === "object" &&
|
|
171
|
+
"outcome" in result &&
|
|
172
|
+
(result as { outcome?: unknown }).outcome === "saved"
|
|
173
|
+
) {
|
|
174
|
+
return result as PreviewDocumentSaveSuccess;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
154
179
|
export function createPreviewDocumentSaveController(
|
|
155
180
|
args: PreviewDocumentSaveAdapter & {
|
|
156
181
|
/**
|
|
@@ -230,7 +255,21 @@ export function createPreviewDocumentSaveController(
|
|
|
230
255
|
}
|
|
231
256
|
return;
|
|
232
257
|
}
|
|
233
|
-
|
|
258
|
+
// Adopt the server's fresh metadata (if the adapter reported it) into
|
|
259
|
+
// the confirmed baseline. Otherwise `loadedUpdatedAt`/
|
|
260
|
+
// `loadedContentWasEmpty` would keep carrying whatever was true when
|
|
261
|
+
// the controller was created/last marked — e.g. "empty" for a
|
|
262
|
+
// brand-new page — forever, even after real content has been saved.
|
|
263
|
+
const success = asSaveSuccess(result);
|
|
264
|
+
lastSaved = {
|
|
265
|
+
...attempted,
|
|
266
|
+
...(success?.loadedUpdatedAt !== undefined
|
|
267
|
+
? { loadedUpdatedAt: success.loadedUpdatedAt }
|
|
268
|
+
: {}),
|
|
269
|
+
...(success?.loadedContentWasEmpty !== undefined
|
|
270
|
+
? { loadedContentWasEmpty: success.loadedContentWasEmpty }
|
|
271
|
+
: {}),
|
|
272
|
+
};
|
|
234
273
|
hasSavedLocally = true;
|
|
235
274
|
deferredReason = null;
|
|
236
275
|
inFlight = null;
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error?: undefined;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
ok?: undefined;
|
|
22
21
|
error?: undefined;
|
|
22
|
+
ok?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
@@ -28,7 +28,7 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
28
28
|
} | {
|
|
29
29
|
count?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
|
-
ok: boolean;
|
|
32
31
|
error?: undefined;
|
|
32
|
+
ok: boolean;
|
|
33
33
|
}>>;
|
|
34
34
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
error?: undefined;
|
|
45
|
+
ok?: undefined;
|
|
44
46
|
summary: import("./types.js").TraceSummary;
|
|
45
47
|
spans: import("./types.js").TraceSpan[];
|
|
46
48
|
id?: undefined;
|
|
49
|
+
} | {
|
|
47
50
|
error?: undefined;
|
|
48
51
|
ok?: undefined;
|
|
49
|
-
} | {
|
|
50
52
|
summary?: undefined;
|
|
51
53
|
spans?: undefined;
|
|
52
54
|
id: string;
|
|
53
|
-
error?: undefined;
|
|
54
|
-
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
|
+
ok?: undefined;
|
|
56
57
|
summary?: undefined;
|
|
57
58
|
spans?: undefined;
|
|
58
59
|
id?: undefined;
|
|
59
60
|
error: any;
|
|
60
|
-
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
+
error?: undefined;
|
|
62
63
|
summary?: undefined;
|
|
63
64
|
spans?: undefined;
|
|
64
65
|
id?: undefined;
|
|
65
|
-
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
68
68
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
status?: undefined;
|
|
38
37
|
ok?: undefined;
|
|
38
|
+
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
+
error?: undefined;
|
|
40
41
|
ok: boolean;
|
|
41
42
|
status: string;
|
|
42
|
-
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
+
ok?: undefined;
|
|
44
45
|
error: string;
|
|
45
46
|
removed?: undefined;
|
|
46
|
-
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
|
+
error?: undefined;
|
|
48
49
|
ok: boolean;
|
|
49
50
|
removed: boolean;
|
|
50
|
-
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — re-run the validator against the
|
|
54
54
|
* current stored value without changing anything. Useful for the "Test" button.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
+
ok?: undefined;
|
|
57
58
|
error: string;
|
|
58
59
|
note?: undefined;
|
|
59
|
-
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
|
+
error?: undefined;
|
|
61
62
|
ok: boolean;
|
|
62
63
|
note?: undefined;
|
|
63
|
-
error?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
ok: boolean;
|
|
66
67
|
note: string;
|
|
67
|
-
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,12 +95,12 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
+
error?: undefined;
|
|
98
99
|
ok: boolean;
|
|
99
100
|
key: string;
|
|
100
|
-
error?: undefined;
|
|
101
101
|
} | {
|
|
102
|
+
error?: undefined;
|
|
102
103
|
ok: boolean;
|
|
103
104
|
removed: boolean;
|
|
104
|
-
error?: undefined;
|
|
105
105
|
}>>;
|
|
106
106
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -27,11 +27,11 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
27
27
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
28
|
error: any;
|
|
29
29
|
} | {
|
|
30
|
+
error?: undefined;
|
|
30
31
|
ok: boolean;
|
|
31
32
|
key: string;
|
|
32
33
|
baseUrlKey?: string;
|
|
33
34
|
scope: AgentEngineApiKeyScope;
|
|
34
|
-
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|
|
37
37
|
//# sourceMappingURL=agent-engine-api-key-route.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.16",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|