@agent-native/core 0.123.1 → 0.123.2
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/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/deploy/build.ts +8 -0
- package/corpus/core/src/server/builder-browser.ts +76 -0
- package/corpus/core/src/server/core-routes-plugin.ts +15 -5
- package/corpus/templates/assets/app/global.css +0 -4
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +195 -167
- package/corpus/templates/assets/changelog/2026-07-23-drafts-preview-panel-now-fills-the-full-width-with-a-large-p.md +6 -0
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +55 -9
- package/corpus/templates/content/actions/_database-source-utils.ts +756 -182
- package/corpus/templates/content/actions/change-content-database-source-role.ts +21 -20
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +49 -8
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +42 -9
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +88 -90
- package/corpus/templates/content/actions/preview-builder-source-review.ts +4 -3
- package/corpus/templates/content/actions/refresh-content-database-source.ts +36 -9
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +28 -24
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +62 -12
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +91 -16
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +15 -1
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +234 -44
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +194 -16
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +95 -42
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +4 -2
- package/corpus/templates/content/app/components/editor/extensions/CodeBlockNode.tsx +10 -166
- package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +32 -8
- package/corpus/templates/content/app/components/editor/extensions/VideoNode.ts +14 -0
- package/corpus/templates/content/app/components/editor/registrySlashItems.ts +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +59 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +18 -26
- package/corpus/templates/content/app/root.tsx +1 -6
- package/corpus/templates/content/changelog/2026-07-24-builder-can-now-publish-approved-updates-to-entries-that-are.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-collection-loading-no-longer-restarts-from-the-begin.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-connections-now-complete-securely-from-netlify-deplo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-draft-creation-now-scopes-execution-to-the-selected-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-review-now-distinguishes-preparing-a-safe-dry-run-from.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-source-imports-now-recover-from-a-transient-paginati.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-source-refresh-now-preserves-the-provider-bound-fiel.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-update-reviews-now-open-reliably-on-large-collection.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-builder-write-policy-controls-now-reflect-successful-changes.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-date-properties-now-save-reliably-when-selected-with-the-nat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-editor-slash-search-now-keeps-multi-word-block-names-open.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-exact-editor-slash-commands-now-execute-on-enter-even-if-the.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-new-collaborative-pages-now-save-their-first-edits.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-new-local-builder-rows-no-longer-remain-locked-behind-a-none.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-opening-or-closing-a-collaborative-document-no-longer-lets-u.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-selected-text-now-opens-the-link-editor-with-command-k-or-co.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-slash-menu-block-choices-no-longer-stall-pointer-interaction.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-uploaded-and-embedded-media-now-stay-intact-while-collaborative-document-drafts-save.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-video-link-insertion-now-keeps-its-source-panel-open-while-t.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-builder-publishing-now-shows-safe-validation-details-instead.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-builder-reviews-stay-responsive-on-large-collections-and-pre.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-collaborative-documents-no-longer-flash-duplicated-recent-bl.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-corrected-builder-updates-are-now-reviewed-separately-from-f.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-documents-now-remain-read-only-until-their-live-collaborativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-slash-command-menus-now-stay-reachable-when-editing-near-the.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-the-slash-menu-now-shows-one-clear-callout-choice-instead-of.md +6 -0
- package/corpus/templates/content/shared/api.ts +1 -0
- package/corpus/templates/content/shared/builder-mdx.ts +73 -16
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/editor/useCollabReconcile.ts +14 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +6 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/builder-browser.d.ts +13 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +59 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +10 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +2 -2
- package/src/deploy/build.ts +8 -0
- package/src/server/builder-browser.ts +76 -0
- package/src/server/core-routes-plugin.ts +15 -5
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.123.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 14818b6: Route Builder authorization callbacks for Netlify previews through the immutable deployment URL while returning popup status to the visible preview.
|
|
8
|
+
- Updated dependencies [14818b6]
|
|
9
|
+
- @agent-native/toolkit@0.10.8
|
|
10
|
+
|
|
3
11
|
## 0.123.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.123.
|
|
3
|
+
"version": "0.123.2",
|
|
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": {
|
|
@@ -3778,6 +3778,14 @@ export default bundle;
|
|
|
3778
3778
|
"virtual:agents-bundle": agentsBundleModuleSource,
|
|
3779
3779
|
},
|
|
3780
3780
|
replace: {
|
|
3781
|
+
// Netlify exposes DEPLOY_ID only while building. Embed it into the Nitro
|
|
3782
|
+
// function so preview OAuth relays can target this immutable deployment
|
|
3783
|
+
// even though the value is unavailable in the function runtime.
|
|
3784
|
+
"process.env.AGENT_NATIVE_BUILD_ID": JSON.stringify(
|
|
3785
|
+
process.env.DEPLOY_ID?.trim() ||
|
|
3786
|
+
process.env.AGENT_NATIVE_BUILD_ID?.trim() ||
|
|
3787
|
+
"",
|
|
3788
|
+
),
|
|
3781
3789
|
"process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(
|
|
3782
3790
|
process.env.GA_MEASUREMENT_ID?.trim() || "",
|
|
3783
3791
|
),
|
|
@@ -36,6 +36,10 @@ const BUILDER_RELAY_PURPOSE = "builder-preview-callback-relay";
|
|
|
36
36
|
const BUILDER_RELAY_STATE_VERSION = 1;
|
|
37
37
|
const BUILDER_RELAY_TTL_MS = 10 * 60 * 1000;
|
|
38
38
|
const BUILDER_RELAY_REQUEST_SKEW_MS = 2 * 60 * 1000;
|
|
39
|
+
const IMMUTABLE_NETLIFY_RELAY_HOST =
|
|
40
|
+
/^(?<deploy>[a-f0-9]{24})--(?<site>[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.netlify\.app$/;
|
|
41
|
+
const NETLIFY_DEPLOY_PREVIEW_HOST =
|
|
42
|
+
/^deploy-preview-\d+--(?<site>[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.netlify\.app$/;
|
|
39
43
|
|
|
40
44
|
export interface BuilderPreviewRelayState {
|
|
41
45
|
v: 1;
|
|
@@ -165,6 +169,45 @@ export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
|
|
|
165
169
|
)
|
|
166
170
|
);
|
|
167
171
|
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Netlify's deploy-preview alias is convenient for people but mutable, so it
|
|
175
|
+
* must never be the signed relay destination. The deploy builder embeds
|
|
176
|
+
* Netlify's DEPLOY_ID into the Nitro server bundle, while SITE_NAME remains
|
|
177
|
+
* available to Functions at runtime. Use that pair only when it
|
|
178
|
+
* identifies the same site as the visible preview alias; otherwise preserve
|
|
179
|
+
* the visible origin so callback validation fails closed.
|
|
180
|
+
*/
|
|
181
|
+
export function resolveBuilderPreviewRelayTargetOrigin(
|
|
182
|
+
previewOrigin: string,
|
|
183
|
+
): string {
|
|
184
|
+
let previewUrl: URL;
|
|
185
|
+
try {
|
|
186
|
+
previewUrl = new URL(previewOrigin);
|
|
187
|
+
} catch {
|
|
188
|
+
return previewOrigin;
|
|
189
|
+
}
|
|
190
|
+
const previewMatch = NETLIFY_DEPLOY_PREVIEW_HOST.exec(
|
|
191
|
+
previewUrl.hostname.toLowerCase(),
|
|
192
|
+
);
|
|
193
|
+
if (!previewMatch?.groups?.site) return previewOrigin;
|
|
194
|
+
|
|
195
|
+
const buildId = process.env.AGENT_NATIVE_BUILD_ID?.trim().toLowerCase();
|
|
196
|
+
const siteName = process.env.SITE_NAME?.trim().toLowerCase();
|
|
197
|
+
if (
|
|
198
|
+
!buildId ||
|
|
199
|
+
!siteName ||
|
|
200
|
+
!/^[a-f0-9]{24}$/.test(buildId) ||
|
|
201
|
+
siteName !== previewMatch.groups.site
|
|
202
|
+
) {
|
|
203
|
+
return previewOrigin;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const immutableOrigin = `https://${buildId}--${siteName}.netlify.app`;
|
|
207
|
+
return IMMUTABLE_NETLIFY_RELAY_HOST.test(new URL(immutableOrigin).hostname)
|
|
208
|
+
? immutableOrigin
|
|
209
|
+
: previewOrigin;
|
|
210
|
+
}
|
|
168
211
|
export function signBuilderPreviewRelayState(input: {
|
|
169
212
|
ownerEmail: string;
|
|
170
213
|
targetOrigin: string;
|
|
@@ -1228,6 +1271,39 @@ export function resolveSafePreviewUrl(
|
|
|
1228
1271
|
return getBuilderBrowserOriginForEvent(event);
|
|
1229
1272
|
}
|
|
1230
1273
|
|
|
1274
|
+
export function resolveBuilderPreviewRelayParentOrigin(options: {
|
|
1275
|
+
openerOrigin?: string | null;
|
|
1276
|
+
targetOrigin: string;
|
|
1277
|
+
}): string {
|
|
1278
|
+
if (!options.openerOrigin) return options.targetOrigin;
|
|
1279
|
+
let openerUrl: URL;
|
|
1280
|
+
let targetUrl: URL;
|
|
1281
|
+
try {
|
|
1282
|
+
openerUrl = new URL(options.openerOrigin);
|
|
1283
|
+
targetUrl = new URL(options.targetOrigin);
|
|
1284
|
+
} catch {
|
|
1285
|
+
return options.targetOrigin;
|
|
1286
|
+
}
|
|
1287
|
+
if (
|
|
1288
|
+
openerUrl.origin !== options.openerOrigin ||
|
|
1289
|
+
!isSafeBuilderRelayTargetOrigin(openerUrl.origin)
|
|
1290
|
+
) {
|
|
1291
|
+
return options.targetOrigin;
|
|
1292
|
+
}
|
|
1293
|
+
if (openerUrl.origin === targetUrl.origin) return openerUrl.origin;
|
|
1294
|
+
|
|
1295
|
+
const openerMatch = NETLIFY_DEPLOY_PREVIEW_HOST.exec(
|
|
1296
|
+
openerUrl.hostname.toLowerCase(),
|
|
1297
|
+
);
|
|
1298
|
+
const targetMatch = IMMUTABLE_NETLIFY_RELAY_HOST.exec(
|
|
1299
|
+
targetUrl.hostname.toLowerCase(),
|
|
1300
|
+
);
|
|
1301
|
+
return openerMatch?.groups?.site &&
|
|
1302
|
+
openerMatch.groups.site === targetMatch?.groups?.site
|
|
1303
|
+
? openerUrl.origin
|
|
1304
|
+
: options.targetOrigin;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1231
1307
|
export function resolveBuilderCallbackReturnUrl(options: {
|
|
1232
1308
|
event: H3Event;
|
|
1233
1309
|
openerOrigin?: string | null;
|
|
@@ -129,6 +129,8 @@ import {
|
|
|
129
129
|
resolveBuilderCallbackReturnUrl,
|
|
130
130
|
getBuilderBrowserStatusForEvent,
|
|
131
131
|
resolveBuilderBranchProjectId,
|
|
132
|
+
resolveBuilderPreviewRelayParentOrigin,
|
|
133
|
+
resolveBuilderPreviewRelayTargetOrigin,
|
|
132
134
|
resolveSafePreviewUrl,
|
|
133
135
|
runBuilderAgent,
|
|
134
136
|
signBuilderCallbackState,
|
|
@@ -2225,7 +2227,8 @@ export function createCoreRoutesPlugin(
|
|
|
2225
2227
|
try {
|
|
2226
2228
|
relay = signBuilderPreviewRelayState({
|
|
2227
2229
|
ownerEmail,
|
|
2228
|
-
targetOrigin:
|
|
2230
|
+
targetOrigin:
|
|
2231
|
+
resolveBuilderPreviewRelayTargetOrigin(previewOrigin),
|
|
2229
2232
|
basePath: getAppBasePath(),
|
|
2230
2233
|
});
|
|
2231
2234
|
} catch (err) {
|
|
@@ -2598,6 +2601,13 @@ export function createCoreRoutesPlugin(
|
|
|
2598
2601
|
);
|
|
2599
2602
|
}
|
|
2600
2603
|
|
|
2604
|
+
const relayOpenerOrigin =
|
|
2605
|
+
requestUrl.searchParams.get(BUILDER_OPENER_PARAM);
|
|
2606
|
+
const relayParentOrigin = resolveBuilderPreviewRelayParentOrigin({
|
|
2607
|
+
openerOrigin: relayOpenerOrigin,
|
|
2608
|
+
targetOrigin: relayPayload.targetOrigin,
|
|
2609
|
+
});
|
|
2610
|
+
|
|
2601
2611
|
const privateKey = requestUrl.searchParams.get("p-key");
|
|
2602
2612
|
const publicKey = requestUrl.searchParams.get("api-key");
|
|
2603
2613
|
if (!privateKey || !publicKey) {
|
|
@@ -2609,7 +2619,7 @@ export function createCoreRoutesPlugin(
|
|
|
2609
2619
|
);
|
|
2610
2620
|
return createBuilderBrowserCallbackErrorPage(
|
|
2611
2621
|
"Builder didn't return credentials. Restart the connect flow from settings.",
|
|
2612
|
-
{ parentOrigin:
|
|
2622
|
+
{ parentOrigin: relayParentOrigin },
|
|
2613
2623
|
);
|
|
2614
2624
|
}
|
|
2615
2625
|
|
|
@@ -2665,7 +2675,7 @@ export function createCoreRoutesPlugin(
|
|
|
2665
2675
|
"text/html; charset=utf-8",
|
|
2666
2676
|
);
|
|
2667
2677
|
return createBuilderBrowserCallbackErrorPage(message, {
|
|
2668
|
-
parentOrigin:
|
|
2678
|
+
parentOrigin: relayParentOrigin,
|
|
2669
2679
|
});
|
|
2670
2680
|
}
|
|
2671
2681
|
|
|
@@ -2675,8 +2685,8 @@ export function createCoreRoutesPlugin(
|
|
|
2675
2685
|
"text/html; charset=utf-8",
|
|
2676
2686
|
);
|
|
2677
2687
|
return createBuilderBrowserCallbackPage(
|
|
2678
|
-
`${
|
|
2679
|
-
{ parentOrigin:
|
|
2688
|
+
`${relayParentOrigin}${relayPayload.basePath || "/"}`,
|
|
2689
|
+
{ parentOrigin: relayParentOrigin },
|
|
2680
2690
|
);
|
|
2681
2691
|
}
|
|
2682
2692
|
|
|
@@ -207,10 +207,6 @@
|
|
|
207
207
|
grid-template-columns: minmax(0, 1fr) 284px;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
.assets-live-candidates-grid {
|
|
211
|
-
grid-template-columns: minmax(0, 1fr) minmax(18rem, 22rem);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
210
|
.assets-brand-kit-settings-grid {
|
|
215
211
|
grid-template-columns: minmax(0, 1fr) 320px;
|
|
216
212
|
}
|
|
@@ -1521,6 +1521,7 @@ type LaneGalleryItem = {
|
|
|
1521
1521
|
thumbnail: ReactNode; // i18n-ignore structural preview slot name
|
|
1522
1522
|
menu?: ReactNode;
|
|
1523
1523
|
primaryActions?: ReactNode;
|
|
1524
|
+
headerActions?: ReactNode;
|
|
1524
1525
|
onToggle?: (checked: boolean) => void;
|
|
1525
1526
|
};
|
|
1526
1527
|
|
|
@@ -3909,6 +3910,89 @@ export function LiveCandidatesStage({
|
|
|
3909
3910
|
);
|
|
3910
3911
|
}
|
|
3911
3912
|
|
|
3913
|
+
function candidateHeaderActions({
|
|
3914
|
+
canUseCandidate,
|
|
3915
|
+
saving,
|
|
3916
|
+
promoting,
|
|
3917
|
+
candidateLibraryId,
|
|
3918
|
+
onSaveCandidate,
|
|
3919
|
+
onAddToReferences,
|
|
3920
|
+
onUseCandidate,
|
|
3921
|
+
onDismiss,
|
|
3922
|
+
}: {
|
|
3923
|
+
canUseCandidate: boolean;
|
|
3924
|
+
saving?: boolean;
|
|
3925
|
+
promoting?: boolean;
|
|
3926
|
+
candidateLibraryId?: string | null;
|
|
3927
|
+
onSaveCandidate?: (folderId: string | null) => void;
|
|
3928
|
+
onAddToReferences?: () => void;
|
|
3929
|
+
onUseCandidate?: () => void;
|
|
3930
|
+
onDismiss: () => void;
|
|
3931
|
+
}) {
|
|
3932
|
+
const busy = saving || promoting || dismissing;
|
|
3933
|
+
const actionLibraryId = candidateLibraryId || libraryId;
|
|
3934
|
+
const candidateFolders =
|
|
3935
|
+
foldersByLibraryId[actionLibraryId] ??
|
|
3936
|
+
(actionLibraryId === libraryId ? folders : []);
|
|
3937
|
+
if (!canUseCandidate) {
|
|
3938
|
+
return (
|
|
3939
|
+
<Button
|
|
3940
|
+
variant="outline"
|
|
3941
|
+
size="sm"
|
|
3942
|
+
className="h-7 px-2 text-xs text-destructive hover:bg-destructive/10 hover:text-destructive"
|
|
3943
|
+
onClick={onDismiss}
|
|
3944
|
+
disabled={busy}
|
|
3945
|
+
>
|
|
3946
|
+
{t("library.dismiss")}
|
|
3947
|
+
</Button>
|
|
3948
|
+
);
|
|
3949
|
+
}
|
|
3950
|
+
return (
|
|
3951
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
3952
|
+
{onUseCandidate ? (
|
|
3953
|
+
<Button
|
|
3954
|
+
size="sm"
|
|
3955
|
+
className="h-7 px-2 text-xs"
|
|
3956
|
+
onClick={onUseCandidate}
|
|
3957
|
+
disabled={busy}
|
|
3958
|
+
>
|
|
3959
|
+
{t("library.useCandidate")}
|
|
3960
|
+
</Button>
|
|
3961
|
+
) : null}
|
|
3962
|
+
<CandidateSaveMenu
|
|
3963
|
+
libraryId={actionLibraryId}
|
|
3964
|
+
folders={candidateFolders}
|
|
3965
|
+
allowCreateFolder={allowCreateFolder}
|
|
3966
|
+
saving={saving}
|
|
3967
|
+
disabled={busy}
|
|
3968
|
+
onSave={(folderId) => onSaveCandidate?.(folderId)}
|
|
3969
|
+
/>
|
|
3970
|
+
<Button
|
|
3971
|
+
variant="outline"
|
|
3972
|
+
size="sm"
|
|
3973
|
+
className="h-7 px-2 text-xs"
|
|
3974
|
+
onClick={onAddToReferences}
|
|
3975
|
+
disabled={busy}
|
|
3976
|
+
>
|
|
3977
|
+
{promoting ? (
|
|
3978
|
+
<Spinner className="h-3.5 w-3.5" />
|
|
3979
|
+
) : (
|
|
3980
|
+
t("library.addToReferences")
|
|
3981
|
+
)}
|
|
3982
|
+
</Button>
|
|
3983
|
+
<Button
|
|
3984
|
+
variant="ghost"
|
|
3985
|
+
size="sm"
|
|
3986
|
+
className="h-7 px-2 text-xs text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
|
|
3987
|
+
onClick={onDismiss}
|
|
3988
|
+
disabled={busy}
|
|
3989
|
+
>
|
|
3990
|
+
{t("library.dismiss")}
|
|
3991
|
+
</Button>
|
|
3992
|
+
</div>
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3912
3996
|
function slotItem(slot: VariantSlot): LaneGalleryItem {
|
|
3913
3997
|
const isFailed = slot.status === "failed";
|
|
3914
3998
|
const canUseCandidate = slot.status === "ready" && Boolean(slot.assetId);
|
|
@@ -3953,6 +4037,21 @@ export function LiveCandidatesStage({
|
|
|
3953
4037
|
slot,
|
|
3954
4038
|
}),
|
|
3955
4039
|
}),
|
|
4040
|
+
headerActions: candidateHeaderActions({
|
|
4041
|
+
canUseCandidate,
|
|
4042
|
+
saving,
|
|
4043
|
+
promoting,
|
|
4044
|
+
candidateLibraryId: libraryId,
|
|
4045
|
+
onSaveCandidate: (folderId) => onSave(slot, folderId),
|
|
4046
|
+
onAddToReferences: () => onMoveToReferences(slot),
|
|
4047
|
+
onUseCandidate: onUse ? () => onUse(slot) : undefined,
|
|
4048
|
+
onDismiss: () =>
|
|
4049
|
+
setDismissTarget({
|
|
4050
|
+
kind: "slot",
|
|
4051
|
+
title,
|
|
4052
|
+
slot,
|
|
4053
|
+
}),
|
|
4054
|
+
}),
|
|
3956
4055
|
};
|
|
3957
4056
|
}
|
|
3958
4057
|
|
|
@@ -3997,6 +4096,21 @@ export function LiveCandidatesStage({
|
|
|
3997
4096
|
asset,
|
|
3998
4097
|
}),
|
|
3999
4098
|
}),
|
|
4099
|
+
headerActions: candidateHeaderActions({
|
|
4100
|
+
canUseCandidate: true,
|
|
4101
|
+
saving,
|
|
4102
|
+
promoting,
|
|
4103
|
+
candidateLibraryId: asset.libraryId,
|
|
4104
|
+
onSaveCandidate: (folderId) => onSaveDraft(asset, folderId),
|
|
4105
|
+
onAddToReferences: () => onMoveDraftToReferences(asset),
|
|
4106
|
+
onUseCandidate: onUseDraft ? () => onUseDraft(asset) : undefined,
|
|
4107
|
+
onDismiss: () =>
|
|
4108
|
+
setDismissTarget({
|
|
4109
|
+
kind: "asset",
|
|
4110
|
+
title: assetDisplayTitle(asset),
|
|
4111
|
+
asset,
|
|
4112
|
+
}),
|
|
4113
|
+
}),
|
|
4000
4114
|
};
|
|
4001
4115
|
}
|
|
4002
4116
|
|
|
@@ -4063,154 +4177,103 @@ export function LiveCandidatesStage({
|
|
|
4063
4177
|
</AlertDialog>
|
|
4064
4178
|
|
|
4065
4179
|
<section className="min-w-0 overflow-hidden rounded-lg border border-border bg-background">
|
|
4066
|
-
<div className="flex min-w-0 items-center justify-between gap-
|
|
4067
|
-
<
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
<div className="flex shrink-0 items-center gap-1.5">
|
|
4073
|
-
<LiveCandidatesActions
|
|
4074
|
-
slots={slots}
|
|
4075
|
-
draftAssets={draftAssets}
|
|
4076
|
-
libraryId={libraryId}
|
|
4077
|
-
/>
|
|
4078
|
-
</div>
|
|
4079
|
-
</div>
|
|
4080
|
-
<div className="assets-live-candidates-grid grid min-w-0">
|
|
4081
|
-
<div className="min-w-0 bg-muted/10 p-2.5 sm:p-3">
|
|
4082
|
-
<div
|
|
4083
|
-
className={[
|
|
4084
|
-
"group relative overflow-hidden rounded-lg border border-border bg-background shadow-sm",
|
|
4085
|
-
activeItem?.busy ? "opacity-80" : "",
|
|
4086
|
-
].join(" ")}
|
|
4087
|
-
aria-busy={activeItem?.busy}
|
|
4088
|
-
>
|
|
4089
|
-
<div className="h-36 bg-muted/30 sm:h-44 lg:h-56 2xl:h-64">
|
|
4090
|
-
{activeItem?.onOpen ? (
|
|
4091
|
-
<button
|
|
4092
|
-
type="button"
|
|
4093
|
-
onClick={activeItem.onOpen}
|
|
4094
|
-
className="block h-full w-full text-left"
|
|
4095
|
-
>
|
|
4096
|
-
{activeItem.preview}
|
|
4097
|
-
</button>
|
|
4098
|
-
) : activeItem?.href ? (
|
|
4099
|
-
<Link to={activeItem.href} className="block h-full w-full">
|
|
4100
|
-
{activeItem.preview}
|
|
4101
|
-
</Link>
|
|
4102
|
-
) : (
|
|
4103
|
-
activeItem?.preview
|
|
4104
|
-
)}
|
|
4105
|
-
</div>
|
|
4106
|
-
{activeItem?.busy ? (
|
|
4107
|
-
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-background/20">
|
|
4108
|
-
<Spinner className="h-5 w-5" />
|
|
4109
|
-
</div>
|
|
4110
|
-
) : null}
|
|
4111
|
-
{activeItem?.onOpen ? (
|
|
4112
|
-
<Button
|
|
4113
|
-
variant="secondary"
|
|
4114
|
-
size="sm"
|
|
4115
|
-
className="absolute right-2 top-2 h-8 gap-1.5 bg-background/85 px-2.5 text-xs opacity-0 shadow-sm backdrop-blur transition group-hover:opacity-100 focus-within:opacity-100"
|
|
4116
|
-
onClick={activeItem.onOpen}
|
|
4117
|
-
>
|
|
4118
|
-
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
4119
|
-
{t("library.details")}
|
|
4120
|
-
</Button>
|
|
4121
|
-
) : activeItem?.href ? (
|
|
4122
|
-
<Button
|
|
4123
|
-
asChild
|
|
4124
|
-
variant="secondary"
|
|
4125
|
-
size="sm"
|
|
4126
|
-
className="absolute right-2 top-2 h-8 gap-1.5 bg-background/85 px-2.5 text-xs opacity-0 shadow-sm backdrop-blur transition group-hover:opacity-100 focus-within:opacity-100"
|
|
4127
|
-
>
|
|
4128
|
-
<Link to={activeItem.href}>
|
|
4129
|
-
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
4130
|
-
{t("library.details")}
|
|
4131
|
-
</Link>
|
|
4132
|
-
</Button>
|
|
4133
|
-
) : null}
|
|
4134
|
-
</div>
|
|
4135
|
-
<div className="mt-2.5 flex gap-2 overflow-x-auto pb-1">
|
|
4136
|
-
{items.map((item) => {
|
|
4137
|
-
const active = item.id === activeItem?.id;
|
|
4138
|
-
return (
|
|
4139
|
-
<button
|
|
4140
|
-
key={item.id}
|
|
4141
|
-
type="button"
|
|
4142
|
-
onClick={() => setActiveItemId(item.id)}
|
|
4143
|
-
className={[
|
|
4144
|
-
"group relative h-16 w-24 shrink-0 overflow-hidden rounded-md border bg-background transition",
|
|
4145
|
-
active
|
|
4146
|
-
? "border-primary ring-2 ring-primary/25"
|
|
4147
|
-
: "border-border/80 hover:border-foreground/30",
|
|
4148
|
-
].join(" ")}
|
|
4149
|
-
aria-label={t("library.showCandidate", {
|
|
4150
|
-
title: item.title,
|
|
4151
|
-
})}
|
|
4152
|
-
aria-pressed={active}
|
|
4153
|
-
>
|
|
4154
|
-
{item.thumbnail}
|
|
4155
|
-
<span className="pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-linear-to-t from-background/90 to-transparent" />
|
|
4156
|
-
{item.busy && item.showBusyOverlay !== false ? (
|
|
4157
|
-
<span className="absolute right-1.5 top-1.5 rounded-full bg-background/90 p-1 shadow-sm">
|
|
4158
|
-
<Spinner className="h-3 w-3" />
|
|
4159
|
-
</span>
|
|
4160
|
-
) : null}
|
|
4161
|
-
</button>
|
|
4162
|
-
);
|
|
4163
|
-
})}
|
|
4164
|
-
</div>
|
|
4165
|
-
</div>
|
|
4166
|
-
<aside className="flex min-w-0 flex-col justify-between gap-3 border-t border-border bg-background p-3 lg:border-l lg:border-t-0 lg:p-4">
|
|
4167
|
-
<div className="min-w-0 space-y-3">
|
|
4168
|
-
<div className="min-w-0">
|
|
4169
|
-
<div className="flex min-w-0 flex-wrap items-center gap-1.5">
|
|
4170
|
-
{activeItem?.status ? (
|
|
4171
|
-
<CandidateStatusPill status={activeItem.status} />
|
|
4172
|
-
) : null}
|
|
4173
|
-
{activeItem?.metadata ? (
|
|
4174
|
-
<Badge
|
|
4175
|
-
variant="outline"
|
|
4176
|
-
className="h-6 max-w-full rounded-full px-2 text-[11px]"
|
|
4177
|
-
>
|
|
4178
|
-
{activeItem.metadata}
|
|
4179
|
-
</Badge>
|
|
4180
|
-
) : null}
|
|
4181
|
-
</div>
|
|
4182
|
-
<div className="mt-2 truncate text-sm font-semibold">
|
|
4183
|
-
{activeItem?.title}
|
|
4184
|
-
</div>
|
|
4185
|
-
{activeItem?.subtitle ? (
|
|
4186
|
-
<div className="mt-1 truncate text-xs text-muted-foreground">
|
|
4187
|
-
{activeItem.subtitle}
|
|
4188
|
-
</div>
|
|
4189
|
-
) : null}
|
|
4190
|
-
</div>
|
|
4191
|
-
{activeItem?.primaryActions ? (
|
|
4192
|
-
<div>{activeItem.primaryActions}</div>
|
|
4193
|
-
) : null}
|
|
4194
|
-
</div>
|
|
4180
|
+
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2 border-b border-border px-3 py-2 sm:px-4">
|
|
4181
|
+
<h3 className="shrink-0 text-sm font-semibold">
|
|
4182
|
+
{t("library.candidates")}
|
|
4183
|
+
</h3>
|
|
4184
|
+
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
4185
|
+
{activeItem?.headerActions}
|
|
4195
4186
|
{activeItem?.onOpen ? (
|
|
4196
4187
|
<Button
|
|
4197
4188
|
variant="ghost"
|
|
4198
4189
|
size="sm"
|
|
4199
|
-
className="gap-1
|
|
4190
|
+
className="h-7 gap-1 px-2 text-xs"
|
|
4200
4191
|
onClick={activeItem.onOpen}
|
|
4201
4192
|
>
|
|
4202
4193
|
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
4203
4194
|
{t("library.openDetails")}
|
|
4204
4195
|
</Button>
|
|
4205
4196
|
) : activeItem?.href ? (
|
|
4206
|
-
<Button
|
|
4197
|
+
<Button
|
|
4198
|
+
asChild
|
|
4199
|
+
variant="ghost"
|
|
4200
|
+
size="sm"
|
|
4201
|
+
className="h-7 gap-1 px-2 text-xs"
|
|
4202
|
+
>
|
|
4207
4203
|
<Link to={activeItem.href}>
|
|
4208
4204
|
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
4209
4205
|
{t("library.openDetails")}
|
|
4210
4206
|
</Link>
|
|
4211
4207
|
</Button>
|
|
4212
4208
|
) : null}
|
|
4213
|
-
|
|
4209
|
+
<LiveCandidatesActions
|
|
4210
|
+
slots={slots}
|
|
4211
|
+
draftAssets={draftAssets}
|
|
4212
|
+
libraryId={libraryId}
|
|
4213
|
+
/>
|
|
4214
|
+
</div>
|
|
4215
|
+
</div>
|
|
4216
|
+
<div className="min-w-0 bg-muted/10 p-2.5 sm:p-3">
|
|
4217
|
+
<div
|
|
4218
|
+
className={[
|
|
4219
|
+
"group relative overflow-hidden rounded-lg border border-border bg-background shadow-sm",
|
|
4220
|
+
activeItem?.busy ? "opacity-80" : "",
|
|
4221
|
+
].join(" ")}
|
|
4222
|
+
aria-busy={activeItem?.busy}
|
|
4223
|
+
>
|
|
4224
|
+
<div className="h-[min(60vh,30rem)] overflow-hidden bg-muted/30">
|
|
4225
|
+
{activeItem?.onOpen ? (
|
|
4226
|
+
<button
|
|
4227
|
+
type="button"
|
|
4228
|
+
onClick={activeItem.onOpen}
|
|
4229
|
+
className="block h-full w-full text-left"
|
|
4230
|
+
>
|
|
4231
|
+
{activeItem.preview}
|
|
4232
|
+
</button>
|
|
4233
|
+
) : activeItem?.href ? (
|
|
4234
|
+
<Link to={activeItem.href} className="block h-full w-full">
|
|
4235
|
+
{activeItem.preview}
|
|
4236
|
+
</Link>
|
|
4237
|
+
) : (
|
|
4238
|
+
activeItem?.preview
|
|
4239
|
+
)}
|
|
4240
|
+
</div>
|
|
4241
|
+
{activeItem?.busy ? (
|
|
4242
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-background/20">
|
|
4243
|
+
<Spinner className="h-5 w-5" />
|
|
4244
|
+
</div>
|
|
4245
|
+
) : null}
|
|
4246
|
+
</div>
|
|
4247
|
+
<div className="mt-2.5 flex gap-2 overflow-x-auto pb-1">
|
|
4248
|
+
{items.map((item) => {
|
|
4249
|
+
const active = item.id === activeItem?.id;
|
|
4250
|
+
return (
|
|
4251
|
+
<button
|
|
4252
|
+
key={item.id}
|
|
4253
|
+
type="button"
|
|
4254
|
+
onClick={() => setActiveItemId(item.id)}
|
|
4255
|
+
className={[
|
|
4256
|
+
"group relative h-16 w-24 shrink-0 overflow-hidden rounded-md border bg-background transition",
|
|
4257
|
+
active
|
|
4258
|
+
? "border-primary ring-2 ring-primary/25"
|
|
4259
|
+
: "border-border/80 hover:border-foreground/30",
|
|
4260
|
+
].join(" ")}
|
|
4261
|
+
aria-label={t("library.showCandidate", {
|
|
4262
|
+
title: item.title,
|
|
4263
|
+
})}
|
|
4264
|
+
aria-pressed={active}
|
|
4265
|
+
>
|
|
4266
|
+
{item.thumbnail}
|
|
4267
|
+
<span className="pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-linear-to-t from-background/90 to-transparent" />
|
|
4268
|
+
{item.busy && item.showBusyOverlay !== false ? (
|
|
4269
|
+
<span className="absolute right-1.5 top-1.5 rounded-full bg-background/90 p-1 shadow-sm">
|
|
4270
|
+
<Spinner className="h-3 w-3" />
|
|
4271
|
+
</span>
|
|
4272
|
+
) : null}
|
|
4273
|
+
</button>
|
|
4274
|
+
);
|
|
4275
|
+
})}
|
|
4276
|
+
</div>
|
|
4214
4277
|
</div>
|
|
4215
4278
|
</section>
|
|
4216
4279
|
|
|
@@ -4223,41 +4286,6 @@ export function LiveCandidatesStage({
|
|
|
4223
4286
|
);
|
|
4224
4287
|
}
|
|
4225
4288
|
|
|
4226
|
-
function CandidateStatusPill({ status }: { status: string }) {
|
|
4227
|
-
const t = useT();
|
|
4228
|
-
const normalized = status.toLowerCase();
|
|
4229
|
-
const label =
|
|
4230
|
-
normalized === "pending"
|
|
4231
|
-
? t("library.generating")
|
|
4232
|
-
: normalized === "ready"
|
|
4233
|
-
? t("library.ready")
|
|
4234
|
-
: normalized === "failed"
|
|
4235
|
-
? t("library.failed")
|
|
4236
|
-
: normalized === "draft"
|
|
4237
|
-
? t("library.draft")
|
|
4238
|
-
: status;
|
|
4239
|
-
const className =
|
|
4240
|
-
normalized === "ready"
|
|
4241
|
-
? "border-primary/30 bg-primary/10 text-primary"
|
|
4242
|
-
: normalized === "failed"
|
|
4243
|
-
? "border-destructive/30 bg-destructive/10 text-destructive"
|
|
4244
|
-
: normalized === "pending"
|
|
4245
|
-
? "border-border bg-muted/70 text-muted-foreground"
|
|
4246
|
-
: "border-border bg-background text-muted-foreground";
|
|
4247
|
-
|
|
4248
|
-
return (
|
|
4249
|
-
<span
|
|
4250
|
-
className={[
|
|
4251
|
-
"inline-flex h-6 max-w-full items-center gap-1.5 rounded-full border px-2 text-[11px] font-medium",
|
|
4252
|
-
className,
|
|
4253
|
-
].join(" ")}
|
|
4254
|
-
>
|
|
4255
|
-
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-current" />
|
|
4256
|
-
<span className="truncate">{label}</span>
|
|
4257
|
-
</span>
|
|
4258
|
-
);
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
4289
|
function VariantPreview({
|
|
4262
4290
|
slot,
|
|
4263
4291
|
fit = "cover",
|