@widgetic/creator 0.3.65 → 0.3.66
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/README.md +15 -0
- package/dist/CreatorApp.svelte +418 -137
- package/dist/CreatorApp.svelte.d.ts +3 -1
- package/dist/components/WidgetDetails.svelte +34 -14
- package/dist/components/WidgetDetails.svelte.d.ts +3 -0
- package/dist/components/WidgetShapeLivePreviews.svelte +28 -2
- package/dist/utils/freezeWatchdog.d.ts +16 -0
- package/dist/utils/freezeWatchdog.js +87 -2
- package/dist/utils/persistWidgetThumbnail.d.ts +12 -1
- package/dist/utils/persistWidgetThumbnail.js +14 -7
- package/package.json +4 -2
package/dist/CreatorApp.svelte
CHANGED
|
@@ -27,7 +27,12 @@
|
|
|
27
27
|
UploadsApi,
|
|
28
28
|
ResponseError
|
|
29
29
|
} from '@widgetic/api-sdk';
|
|
30
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
Middleware,
|
|
32
|
+
UpdateComposition,
|
|
33
|
+
GenerateCodeRequest,
|
|
34
|
+
AgentModelsGet200ResponseModelsInner
|
|
35
|
+
} from '@widgetic/api-sdk';
|
|
31
36
|
import { parseVersionPreviewError } from './utils/versionPreviewError.js';
|
|
32
37
|
|
|
33
38
|
/**
|
|
@@ -71,7 +76,7 @@
|
|
|
71
76
|
import { persistWidgetThumbnail } from './utils/persistWidgetThumbnail.js';
|
|
72
77
|
import { nativeScrollbarPadding } from './utils/nativeScrollbarPadding.js';
|
|
73
78
|
import { pinScrollToBottom } from './utils/pinScrollToBottom.js';
|
|
74
|
-
import { installFreezeWatchdog } from './utils/freezeWatchdog.js';
|
|
79
|
+
import { installFreezeWatchdog, measureSync } from './utils/freezeWatchdog.js';
|
|
75
80
|
import { streamOperationUpdates } from './utils/operationStream.js';
|
|
76
81
|
import { generateEmbedCode, EMBED_FORMATS, EMBED_FORMAT_LABELS, type EmbedFormat } from './utils/embedCode.js';
|
|
77
82
|
|
|
@@ -113,7 +118,9 @@
|
|
|
113
118
|
/** Opt-in Dexie L1 — host (site / standalone) passes true. */
|
|
114
119
|
export let enableLocalCache: CreatorAppProps['enableLocalCache'] = false;
|
|
115
120
|
export let onPublishComplete: CreatorAppProps['onPublishComplete'] = undefined;
|
|
116
|
-
|
|
121
|
+
// Passed by the site host but never read here — export const keeps the
|
|
122
|
+
// init-set contract without the unused-reactive warning.
|
|
123
|
+
export const onBack: CreatorAppProps['onBack'] = undefined;
|
|
117
124
|
export let onWidgetRenamed: CreatorAppProps['onWidgetRenamed'] = undefined;
|
|
118
125
|
|
|
119
126
|
// Opt-in Dexie owner — reactive to prop changes without $effect (would force runes mode).
|
|
@@ -121,7 +128,8 @@
|
|
|
121
128
|
|
|
122
129
|
function getRealTokenExpUnix(token: string): number | null {
|
|
123
130
|
const decoded = decodeJWT(token);
|
|
124
|
-
|
|
131
|
+
const exp = decoded?.exp;
|
|
132
|
+
return typeof exp === 'number' && Number.isFinite(exp) ? exp : null;
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
let didForceCreateNewWidget = false;
|
|
@@ -485,7 +493,10 @@
|
|
|
485
493
|
|
|
486
494
|
// Lightweight static imports from canvas (excludes Canvas.svelte — 10K+ lines)
|
|
487
495
|
import { Tooltip } from '@widgetic/canvas/components';
|
|
488
|
-
|
|
496
|
+
// WidgetConversionPayload lives on the canvas root export (re-exported from
|
|
497
|
+
// ./canvas); the components barrel only carries the input controls. Type-only
|
|
498
|
+
// import, so the 10K-line Canvas.svelte still never loads.
|
|
499
|
+
import type { WidgetConversionPayload } from '@widgetic/canvas';
|
|
489
500
|
|
|
490
501
|
// Chat module for code generation conversation UI
|
|
491
502
|
import { Chat, ContextType } from '@widgetic/chat';
|
|
@@ -635,10 +646,31 @@
|
|
|
635
646
|
repositoryId?: string | null;
|
|
636
647
|
repository_id?: string | null;
|
|
637
648
|
repoId?: string | null;
|
|
649
|
+
repo_id?: string | null;
|
|
638
650
|
name?: string;
|
|
639
651
|
[extra: string]: unknown;
|
|
640
652
|
}
|
|
641
653
|
|
|
654
|
+
/**
|
|
655
|
+
* View any API payload as the app's open WidgetSummary shape. The SDK's
|
|
656
|
+
* generated models (Widget, CreateWidget201Response, …) lack the index
|
|
657
|
+
* signature callers rely on for snake_case/extra fields, so direct
|
|
658
|
+
* assignments fail structurally; this documents the deliberate widening.
|
|
659
|
+
*/
|
|
660
|
+
function asWidgetSummary(payload: unknown): WidgetSummary {
|
|
661
|
+
return (payload ?? {}) as WidgetSummary;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Reactivity marker for template {@const}s: a Svelte 4 {@const} only re-runs
|
|
666
|
+
* when a value referenced in its expression changes, so the panel-revision
|
|
667
|
+
* counter is passed explicitly. The bare comma idiom this replaces
|
|
668
|
+
* (`(revision, value)`) is a TS unused-expression error.
|
|
669
|
+
*/
|
|
670
|
+
function rev<T>(revision: number, value: T, ..._alsoTracked: unknown[]): T {
|
|
671
|
+
return value;
|
|
672
|
+
}
|
|
673
|
+
|
|
642
674
|
// LocalStorage keys for persisting selection across refresh
|
|
643
675
|
const SELECTED_WIDGET_STORAGE_KEY = 'widgetCreator_selectedWidgetId';
|
|
644
676
|
const SELECTED_CANVAS_STORAGE_KEY = 'widgetCreator_selectedCanvasId';
|
|
@@ -868,7 +900,7 @@
|
|
|
868
900
|
// Chat component reference and context (focused panel)
|
|
869
901
|
let chatMethods: {
|
|
870
902
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
871
|
-
addAssistantMessage: (content: string, skipPersist?: boolean) => void;
|
|
903
|
+
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: string[]) => void;
|
|
872
904
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
873
905
|
getConversationId: () => string | null;
|
|
874
906
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -1004,6 +1036,7 @@
|
|
|
1004
1036
|
fileSize?: number;
|
|
1005
1037
|
}>,
|
|
1006
1038
|
sourceWidgetId?: string,
|
|
1039
|
+
sendOptions?: { skipIntentGate?: boolean },
|
|
1007
1040
|
) {
|
|
1008
1041
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1009
1042
|
if (!widgetId) return;
|
|
@@ -1126,6 +1159,7 @@
|
|
|
1126
1159
|
attachmentIds: cdnUploadIds.length > 0 ? cdnUploadIds : undefined,
|
|
1127
1160
|
promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
|
|
1128
1161
|
persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
|
|
1162
|
+
skipIntentGate: sendOptions?.skipIntentGate === true,
|
|
1129
1163
|
});
|
|
1130
1164
|
}
|
|
1131
1165
|
|
|
@@ -1490,10 +1524,12 @@
|
|
|
1490
1524
|
: null) || userMessages[userMessages.length - 1] || null;
|
|
1491
1525
|
const existingUrls = new Set(
|
|
1492
1526
|
(targetUser?.attachments || [])
|
|
1493
|
-
.map((a: { url?:
|
|
1494
|
-
.filter((url: string
|
|
1527
|
+
.map((a: { url?: unknown }) => a.url)
|
|
1528
|
+
.filter((url): url is string => typeof url === 'string' && url.length > 0),
|
|
1529
|
+
);
|
|
1530
|
+
const existingHasRemoteImage = [...existingUrls].some(
|
|
1531
|
+
(url) => typeof url === 'string' && /^https?:\/\//i.test(url)
|
|
1495
1532
|
);
|
|
1496
|
-
const existingHasRemoteImage = [...existingUrls].some((url) => /^https?:\/\//i.test(url));
|
|
1497
1533
|
// Chat already persisted CDN attachments for this turn — do not merge data URLs/canvas shots.
|
|
1498
1534
|
if (targetUser && existingHasRemoteImage) {
|
|
1499
1535
|
if (convId !== visibleConversationId) {
|
|
@@ -1649,6 +1685,14 @@
|
|
|
1649
1685
|
* Capture via a temporary same-path Worker /preview iframe (has html2canvas handler).
|
|
1650
1686
|
*/
|
|
1651
1687
|
async function captureViaHiddenWorkerPreview(widgetId: string): Promise<string | null> {
|
|
1688
|
+
// Loading a widget in a hidden iframe costs a full Svelte + Tailwind CDN
|
|
1689
|
+
// startup on the main thread this page shares with it, and the capture that
|
|
1690
|
+
// follows is refused during a generation anyway (`requestIframeScreenshot`),
|
|
1691
|
+
// so the whole path is skipped rather than paying the load for nothing.
|
|
1692
|
+
if (isAnyWidgetGenerating()) {
|
|
1693
|
+
console.log('[Chat→Screenshot] Skip hidden Worker capture — codegen in progress', widgetId?.substring(0, 8));
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1652
1696
|
const workerUrl = `${widgetBuilderPreviewBaseUrl()}/preview/${widgetId}`;
|
|
1653
1697
|
console.log('[Chat→Screenshot] Capturing via hidden Worker preview', {
|
|
1654
1698
|
widgetId: widgetId.substring(0, 8),
|
|
@@ -1716,7 +1760,10 @@
|
|
|
1716
1760
|
await persistWidgetListThumbnail(widgetId, screenshot, isCurrent);
|
|
1717
1761
|
if (!isCurrent()) return;
|
|
1718
1762
|
capturedCanvas?.updateWidgetImage(widgetId, screenshot);
|
|
1719
|
-
const canvasJson =
|
|
1763
|
+
const canvasJson = measureSync(
|
|
1764
|
+
'canvas-serialize:shape-refresh-thumbnail',
|
|
1765
|
+
() => capturedCanvas?.saveCanvas?.() ?? null,
|
|
1766
|
+
);
|
|
1720
1767
|
if (canvasJson) void handleCanvasAutoSave(canvasJson);
|
|
1721
1768
|
} catch (error) {
|
|
1722
1769
|
console.warn('[WidgetShape] Refresh thumbnail capture failed', widgetId, error);
|
|
@@ -2479,8 +2526,14 @@
|
|
|
2479
2526
|
|
|
2480
2527
|
/** Explicit chat box height (px). null/0 = fill available; user drag sets a fixed px. */
|
|
2481
2528
|
const CHAT_DEFAULT_HEIGHT = 400;
|
|
2482
|
-
const CHAT_MIN_HEIGHT =
|
|
2529
|
+
const CHAT_MIN_HEIGHT = 260;
|
|
2483
2530
|
const CHAT_MAX_HEIGHT = 2000;
|
|
2531
|
+
/**
|
|
2532
|
+
* Floor applied to the chat box while it auto-fills the create step. A floor
|
|
2533
|
+
* taller than the available area makes the surrounding scroll container
|
|
2534
|
+
* scroll, which puts the chat input below the fold on open.
|
|
2535
|
+
*/
|
|
2536
|
+
const CHAT_AUTO_MIN_HEIGHT = 0;
|
|
2484
2537
|
|
|
2485
2538
|
function getChatContainerHeightForPanel(widgetId: string): number | null {
|
|
2486
2539
|
const stored = getPanelState(widgetId).chatContainerHeight;
|
|
@@ -2565,7 +2618,7 @@
|
|
|
2565
2618
|
if (!schema) return schema;
|
|
2566
2619
|
try {
|
|
2567
2620
|
const clone = structuredClone(schema);
|
|
2568
|
-
const groups = Array.isArray(clone) ? clone : clone.componentGroups;
|
|
2621
|
+
const groups = Array.isArray(clone) ? clone : (clone as { componentGroups?: unknown }).componentGroups;
|
|
2569
2622
|
if (!Array.isArray(groups) || groups.length === 0) return schema;
|
|
2570
2623
|
const allProps = groups.flatMap((group: { components?: Array<{ properties?: unknown[] }> }) =>
|
|
2571
2624
|
(group.components || []).flatMap((component) => component.properties || [])
|
|
@@ -3657,7 +3710,7 @@
|
|
|
3657
3710
|
!compositionRecordProjectId(selectedComposition as Record<string, unknown>);
|
|
3658
3711
|
const res = await compositionsClient.updateComposition({
|
|
3659
3712
|
id: selectedComposition.id,
|
|
3660
|
-
|
|
3713
|
+
updateComposition: {
|
|
3661
3714
|
content: buildCompositionContentPayload(),
|
|
3662
3715
|
...(needsProjectBackfill ? projectBackfill : {}),
|
|
3663
3716
|
}
|
|
@@ -3749,7 +3802,7 @@
|
|
|
3749
3802
|
try {
|
|
3750
3803
|
await compositionsClient.updateComposition({
|
|
3751
3804
|
id: compositionId,
|
|
3752
|
-
|
|
3805
|
+
updateComposition: { name: trimmed }
|
|
3753
3806
|
});
|
|
3754
3807
|
showToast('success', 'Composition renamed');
|
|
3755
3808
|
await loadCompositions();
|
|
@@ -3810,7 +3863,9 @@
|
|
|
3810
3863
|
};
|
|
3811
3864
|
await compositionsClient.updateComposition({
|
|
3812
3865
|
id: selectedComposition.id,
|
|
3813
|
-
|
|
3866
|
+
// The gateway accepts embedSettings (compositionSchemas update schema);
|
|
3867
|
+
// the generated UpdateComposition model predates that field.
|
|
3868
|
+
updateComposition: { embedSettings } as unknown as UpdateComposition
|
|
3814
3869
|
});
|
|
3815
3870
|
selectedComposition = { ...selectedComposition, embedSettings };
|
|
3816
3871
|
embedDimensionsChanged = false;
|
|
@@ -4006,11 +4061,11 @@
|
|
|
4006
4061
|
// SDK returns { success, models } directly (not nested under data)
|
|
4007
4062
|
if (response.success && Array.isArray(response.models)) {
|
|
4008
4063
|
// Transform API response to match our interface
|
|
4009
|
-
availableModels = response.models.map((m:
|
|
4010
|
-
id: m.id,
|
|
4011
|
-
name: m.displayName,
|
|
4012
|
-
provider: m.provider.charAt(0).toUpperCase() + m.provider.slice(1), // Capitalize provider
|
|
4013
|
-
description: m.description,
|
|
4064
|
+
availableModels = response.models.map((m: AgentModelsGet200ResponseModelsInner) => ({
|
|
4065
|
+
id: m.id ?? '',
|
|
4066
|
+
name: m.displayName ?? '',
|
|
4067
|
+
provider: (m.provider ?? '').charAt(0).toUpperCase() + (m.provider ?? '').slice(1), // Capitalize provider
|
|
4068
|
+
description: m.description ?? '',
|
|
4014
4069
|
supportsVision: m.supportsVision === true,
|
|
4015
4070
|
}));
|
|
4016
4071
|
|
|
@@ -4750,7 +4805,12 @@
|
|
|
4750
4805
|
|
|
4751
4806
|
// Step 3: Check operation status
|
|
4752
4807
|
try {
|
|
4753
|
-
const op = await operationsClient.operationsIdGet({ id: operationId })
|
|
4808
|
+
const op = (await operationsClient.operationsIdGet({ id: operationId })) as {
|
|
4809
|
+
status?: string;
|
|
4810
|
+
error?: string;
|
|
4811
|
+
progress?: number;
|
|
4812
|
+
metadata?: Record<string, unknown>;
|
|
4813
|
+
};
|
|
4754
4814
|
const status = (op?.status || '').toLowerCase();
|
|
4755
4815
|
|
|
4756
4816
|
if (status === 'completed') {
|
|
@@ -5698,22 +5758,29 @@
|
|
|
5698
5758
|
const res = await widgetsClient.getWidgets({ canvasId: canvasId ?? undefined });
|
|
5699
5759
|
|
|
5700
5760
|
// normalize error shape from apiRequest
|
|
5701
|
-
|
|
5761
|
+
const envelope = res as unknown as {
|
|
5762
|
+
error?: string;
|
|
5763
|
+
message?: string;
|
|
5764
|
+
data?: unknown;
|
|
5765
|
+
};
|
|
5766
|
+
if (envelope && (envelope.error || envelope.message)) {
|
|
5702
5767
|
// console.error('Widget Creator: loadWidgetsForCanvas: response not ok:', res);
|
|
5703
5768
|
widgets = [];
|
|
5704
5769
|
selectedWidgetId = null;
|
|
5705
5770
|
isLoadingWidgets = false;
|
|
5706
|
-
widgetsError =
|
|
5771
|
+
widgetsError = envelope.message || envelope.error || 'Failed to load widgets';
|
|
5707
5772
|
throw new Error(widgetsError);
|
|
5708
5773
|
}
|
|
5709
5774
|
|
|
5710
|
-
// if the response is ok, parse the data
|
|
5775
|
+
// if the response is ok, parse the data (bare list, {data: list} or {data: {data: list}})
|
|
5776
|
+
const envelopeData = envelope?.data;
|
|
5777
|
+
const envelopeNestedData = (envelopeData as { data?: unknown } | undefined)?.data;
|
|
5711
5778
|
const fetched = Array.isArray(res)
|
|
5712
|
-
? (res as WidgetSummary[])
|
|
5713
|
-
: Array.isArray(
|
|
5714
|
-
? (
|
|
5715
|
-
: Array.isArray(
|
|
5716
|
-
? (
|
|
5779
|
+
? (res as unknown as WidgetSummary[])
|
|
5780
|
+
: Array.isArray(envelopeNestedData)
|
|
5781
|
+
? (envelopeNestedData as unknown as WidgetSummary[])
|
|
5782
|
+
: Array.isArray(envelopeData)
|
|
5783
|
+
? (envelopeData as unknown as WidgetSummary[])
|
|
5717
5784
|
: [];
|
|
5718
5785
|
|
|
5719
5786
|
const previousById = new Map(
|
|
@@ -5810,9 +5877,13 @@
|
|
|
5810
5877
|
}
|
|
5811
5878
|
|
|
5812
5879
|
function getWidgetCreatedAtMs(widget: WidgetSummary): number {
|
|
5813
|
-
const
|
|
5814
|
-
if (!
|
|
5815
|
-
const ms = new Date(
|
|
5880
|
+
const rawCreated = widget.createdAt ?? widget.created_at;
|
|
5881
|
+
if (!rawCreated) return 0;
|
|
5882
|
+
const ms = new Date(
|
|
5883
|
+
typeof rawCreated === 'string' || typeof rawCreated === 'number' || rawCreated instanceof Date
|
|
5884
|
+
? rawCreated
|
|
5885
|
+
: String(rawCreated)
|
|
5886
|
+
).getTime();
|
|
5816
5887
|
return Number.isFinite(ms) ? ms : 0;
|
|
5817
5888
|
}
|
|
5818
5889
|
|
|
@@ -6218,10 +6289,10 @@
|
|
|
6218
6289
|
async function resolvePrimaryWidgetIdForCanvas(targetCanvasId: string): Promise<string | null> {
|
|
6219
6290
|
if (!canvasesClient || !targetCanvasId?.trim()) return null;
|
|
6220
6291
|
try {
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6292
|
+
const canvasRecord = await canvasesClient.getCanvas({ id: targetCanvasId });
|
|
6293
|
+
const record =
|
|
6294
|
+
(canvasRecord as { data?: Record<string, unknown> })?.data ??
|
|
6295
|
+
(canvasRecord as unknown as Record<string, unknown>);
|
|
6225
6296
|
const raw =
|
|
6226
6297
|
(record as { primaryWidgetId?: unknown })?.primaryWidgetId ??
|
|
6227
6298
|
(record as { primary_widget_id?: unknown })?.primary_widget_id ??
|
|
@@ -6703,7 +6774,12 @@
|
|
|
6703
6774
|
if (!widgetId || !agentClient) return null;
|
|
6704
6775
|
try {
|
|
6705
6776
|
const json = await agentClient.agentWidgetBuilderRepoHeadWidgetIdGet({ widgetId });
|
|
6706
|
-
const
|
|
6777
|
+
const repoHeadEnvelope = json as unknown as {
|
|
6778
|
+
data?: { repoHeadCommitSha?: string | null; repo_head_commit_sha?: string | null };
|
|
6779
|
+
repoHeadCommitSha?: string | null;
|
|
6780
|
+
repo_head_commit_sha?: string | null;
|
|
6781
|
+
};
|
|
6782
|
+
const data = repoHeadEnvelope?.data ?? repoHeadEnvelope;
|
|
6707
6783
|
const sha = data?.repoHeadCommitSha ?? data?.repo_head_commit_sha ?? null;
|
|
6708
6784
|
return typeof sha === 'string' && sha.length > 0 ? sha : null;
|
|
6709
6785
|
} catch (err) {
|
|
@@ -7021,13 +7097,14 @@
|
|
|
7021
7097
|
}
|
|
7022
7098
|
}
|
|
7023
7099
|
);
|
|
7024
|
-
|
|
7100
|
+
const createEnvelope = response as unknown as { error?: string; message?: string };
|
|
7101
|
+
if (!response || createEnvelope.error || createEnvelope.message) {
|
|
7025
7102
|
console.error('Widget create failed:', response);
|
|
7026
|
-
authStatus = `Create widget failed: ${
|
|
7103
|
+
authStatus = `Create widget failed: ${createEnvelope.message || createEnvelope.error || 'Unknown error'}`;
|
|
7027
7104
|
return;
|
|
7028
7105
|
}
|
|
7029
7106
|
// Ensure we have an id
|
|
7030
|
-
const newWidget = response.data
|
|
7107
|
+
const newWidget = asWidgetSummary((response as { data?: unknown }).data ?? response); // handle envelope or direct
|
|
7031
7108
|
if (newWidget?.id) {
|
|
7032
7109
|
widgets = [{ ...newWidget }, ...widgets];
|
|
7033
7110
|
selectWidget(newWidget);
|
|
@@ -7702,6 +7779,16 @@
|
|
|
7702
7779
|
canvasShapePreviewSyncTimers.set(widgetId, timer);
|
|
7703
7780
|
}
|
|
7704
7781
|
|
|
7782
|
+
/**
|
|
7783
|
+
* Serializing the canvas stringifies every shape's embedded image, so it is
|
|
7784
|
+
* megabytes of synchronous work on a busy canvas. The thumbnail paths run it
|
|
7785
|
+
* immediately after a capture, which is exactly where the freeze reports land,
|
|
7786
|
+
* so each call is measured and named.
|
|
7787
|
+
*/
|
|
7788
|
+
function serializeCanvasJson(reason: string): string | null {
|
|
7789
|
+
return measureSync(`canvas-serialize:${reason}`, () => canvasRef?.saveCanvas?.() ?? null);
|
|
7790
|
+
}
|
|
7791
|
+
|
|
7705
7792
|
async function handleRefreshScreenshot(widgetId: string, options: { quiet?: boolean } = {}) {
|
|
7706
7793
|
const quiet = !!options.quiet;
|
|
7707
7794
|
// console.log('[handleRefreshScreenshot] Capturing screenshot via postMessage for widget:', widgetId, quiet ? '(quiet)' : '');
|
|
@@ -7757,7 +7844,7 @@
|
|
|
7757
7844
|
const freshCapture = await handleChatScreenshot(widgetId, null, quiet);
|
|
7758
7845
|
if (freshCapture) {
|
|
7759
7846
|
canvasRef?.updateWidgetImage(widgetId, freshCapture);
|
|
7760
|
-
const freshJson =
|
|
7847
|
+
const freshJson = serializeCanvasJson('quiet-shape-thumbnail');
|
|
7761
7848
|
if (freshJson) void handleCanvasAutoSave(freshJson);
|
|
7762
7849
|
void persistWidgetListThumbnail(widgetId, freshCapture);
|
|
7763
7850
|
} else {
|
|
@@ -7816,7 +7903,7 @@
|
|
|
7816
7903
|
if (result.data && canvasRef) {
|
|
7817
7904
|
canvasRef.updateWidgetImage(widgetId, result.data);
|
|
7818
7905
|
setTimeout(() => {
|
|
7819
|
-
const json =
|
|
7906
|
+
const json = serializeCanvasJson('screenshot-refresh');
|
|
7820
7907
|
if (json) handleCanvasAutoSave(json);
|
|
7821
7908
|
}, 500);
|
|
7822
7909
|
void persistWidgetListThumbnail(widgetId, result.data);
|
|
@@ -7928,6 +8015,8 @@ Requirements:
|
|
|
7928
8015
|
attachmentIds?: string[];
|
|
7929
8016
|
/** Image URLs / data URLs captured before chat clearDraft. */
|
|
7930
8017
|
promptImagesOverride?: string[];
|
|
8018
|
+
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8019
|
+
skipIntentGate?: boolean;
|
|
7931
8020
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
7932
8021
|
persistFileAttachments?: Array<{
|
|
7933
8022
|
url?: string;
|
|
@@ -8017,6 +8106,7 @@ Requirements:
|
|
|
8017
8106
|
attachmentIds: options.attachmentIds,
|
|
8018
8107
|
promptImagesOverride: options.promptImagesOverride,
|
|
8019
8108
|
persistFileAttachments: options.persistFileAttachments,
|
|
8109
|
+
skipIntentGate: options.skipIntentGate,
|
|
8020
8110
|
});
|
|
8021
8111
|
}
|
|
8022
8112
|
|
|
@@ -8038,6 +8128,8 @@ Requirements:
|
|
|
8038
8128
|
forceRecreateRepo?: boolean;
|
|
8039
8129
|
attachmentIds?: string[];
|
|
8040
8130
|
promptImagesOverride?: string[];
|
|
8131
|
+
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8132
|
+
skipIntentGate?: boolean;
|
|
8041
8133
|
persistFileAttachments?: Array<{
|
|
8042
8134
|
url?: string;
|
|
8043
8135
|
file?: File;
|
|
@@ -8274,17 +8366,67 @@ Requirements:
|
|
|
8274
8366
|
modelId: coderModelForRequest || undefined,
|
|
8275
8367
|
plannerModelId: plannerModelIdForRequest,
|
|
8276
8368
|
widgetId: generationWidgetId,
|
|
8277
|
-
conversationId: activeConversationId ?? undefined,
|
|
8278
|
-
images: imagesToSend,
|
|
8369
|
+
conversationId: activeConversationId ?? undefined, images: imagesToSend,
|
|
8279
8370
|
attachmentIds: attachmentIdsToSend,
|
|
8280
8371
|
runtimeLogs: runtimeLogsForRequest,
|
|
8281
8372
|
compileFixOnly: options.compileFixOnly === true,
|
|
8282
|
-
|
|
8373
|
+
skipIntentGate: options.skipIntentGate === true,
|
|
8374
|
+
} as GenerateCodeRequest
|
|
8283
8375
|
});
|
|
8284
8376
|
// console.log('[WidgetCreator] Code generation request sent for widget', generationWidgetId.substring(0, 8), 'with model:', coderModelForRequest, 'planner:', plannerModelIdForRequest || 'none', 'conversationId:', activeConversationId || 'none', 'images:', imagesToSend?.length || 0, 'attachmentIds:', attachmentIdsToSend?.length || 0, 'runtimeLogs:', runtimeLogsForRequest?.length ?? 0);
|
|
8285
8377
|
// console.log('Widget Creator: Code generation response:', response);
|
|
8286
8378
|
|
|
8287
|
-
|
|
8379
|
+
// --- Intent gate: the server answered instead of generating ---
|
|
8380
|
+
// A question got a meta answer, an ambiguous ask got a clarification
|
|
8381
|
+
// card, or a publish/settings ask was redirected. None of these spent
|
|
8382
|
+
// a generation slot. Anything unexpected here falls through to the
|
|
8383
|
+
// normal path (operation tracking), never blocks a real build.
|
|
8384
|
+
const gatedData = (response as any)?.data;
|
|
8385
|
+
if (gatedData?.gated === true && typeof gatedData.action === 'string' && gatedData.action !== 'generate') {
|
|
8386
|
+
const gatedAction = gatedData.action as 'answer' | 'redirect' | 'clarify';
|
|
8387
|
+
const gatedReply: string = gatedData.reply ?? '';
|
|
8388
|
+
const suggestions: string[] = Array.isArray(gatedData.suggestions) ? gatedData.suggestions : [];
|
|
8389
|
+
|
|
8390
|
+
clearActiveOperationId();
|
|
8391
|
+
patchCodegenUiState(generationWidgetId, {
|
|
8392
|
+
lastGenerationFailed: false,
|
|
8393
|
+
lastGenerationError: null,
|
|
8394
|
+
generateCodeStatus:
|
|
8395
|
+
gatedAction === 'answer'
|
|
8396
|
+
? 'Answered from widget details — no generation needed.'
|
|
8397
|
+
: gatedAction === 'clarify'
|
|
8398
|
+
? 'Need a bit more detail before generating.'
|
|
8399
|
+
: 'No generation needed — use the publish or settings control.',
|
|
8400
|
+
});
|
|
8401
|
+
|
|
8402
|
+
const chatMethods = getChatMethodsForWidget(generationWidgetId);
|
|
8403
|
+
if (gatedAction === 'clarify' && suggestions.length > 0 && chatMethods?.addAssistantMessage) {
|
|
8404
|
+
// Suggestions travel as message data, not flattened text — the chat
|
|
8405
|
+
// renders them as clickable chips that send the next user message.
|
|
8406
|
+
chatMethods.addAssistantMessage(gatedReply, false, suggestions);
|
|
8407
|
+
} else if (chatMethods?.addAssistantMessage) {
|
|
8408
|
+
chatMethods.addAssistantMessage(gatedReply);
|
|
8409
|
+
}
|
|
8410
|
+
|
|
8411
|
+
showToast(
|
|
8412
|
+
gatedAction === 'answer' ? 'info' : 'warning',
|
|
8413
|
+
gatedAction === 'answer' ? 'Answered without generating' : 'No generation started',
|
|
8414
|
+
{
|
|
8415
|
+
description: gatedReply.slice(0, 140),
|
|
8416
|
+
duration: 8000,
|
|
8417
|
+
},
|
|
8418
|
+
);
|
|
8419
|
+
generatingWidgetIds = (() => {
|
|
8420
|
+
const { [generationWidgetId]: _gated, ...rest } = generatingWidgetIds;
|
|
8421
|
+
return rest;
|
|
8422
|
+
})();
|
|
8423
|
+
isGeneratingCode = !!(focusedPanelWidgetId && generatingWidgetIds[focusedPanelWidgetId]);
|
|
8424
|
+
setCrossTabGenerating(false, generationWidgetId);
|
|
8425
|
+
widgetAbortControllers.delete(generationWidgetId);
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8428
|
+
|
|
8429
|
+
if (response && (response as { error?: string }).error === 'TOKEN_EXPIRED') {
|
|
8288
8430
|
patchCodegenUiState(generationWidgetId, {
|
|
8289
8431
|
generateCodeStatus: '❌ Authentication expired. Please login again.',
|
|
8290
8432
|
lastGenerationFailed: true,
|
|
@@ -8297,7 +8439,8 @@ Requirements:
|
|
|
8297
8439
|
return;
|
|
8298
8440
|
}
|
|
8299
8441
|
|
|
8300
|
-
const
|
|
8442
|
+
const generateEnvelope = response as unknown as { error?: string; message?: string };
|
|
8443
|
+
const generateErrorMessage = generateEnvelope && (generateEnvelope.error || generateEnvelope.message);
|
|
8301
8444
|
if (generateErrorMessage) {
|
|
8302
8445
|
const normalizedMessage = normalizeErrorMessage(generateErrorMessage);
|
|
8303
8446
|
patchCodegenUiState(generationWidgetId, {
|
|
@@ -8313,8 +8456,10 @@ Requirements:
|
|
|
8313
8456
|
return;
|
|
8314
8457
|
}
|
|
8315
8458
|
|
|
8316
|
-
const
|
|
8317
|
-
|
|
8459
|
+
const generateData = ((response as { data?: unknown }).data ?? response) as {
|
|
8460
|
+
operationId?: string;
|
|
8461
|
+
};
|
|
8462
|
+
const operationId = generateData?.operationId;
|
|
8318
8463
|
|
|
8319
8464
|
// The Worker serves rebuilt code at the same preview URL, so the panel
|
|
8320
8465
|
// must know when the current generation started — hasLiveWorkerPreview()
|
|
@@ -8397,6 +8542,16 @@ Requirements:
|
|
|
8397
8542
|
|
|
8398
8543
|
const completedCommitSha = getOperationCommitId(op.metadata);
|
|
8399
8544
|
const completedPreviewBuildErr = getOperationPreviewBuildError(op.metadata);
|
|
8545
|
+
const completedCached = getOperationCached(op.metadata);
|
|
8546
|
+
const completedCachedFiles = getOperationCachedFiles(op.metadata);
|
|
8547
|
+
const cachedChatSummary = completedCached
|
|
8548
|
+
? `Already done — the widget is in the state you asked for.` +
|
|
8549
|
+
(completedCommitSha ? ` (Commit: ${completedCommitSha.substring(0, 7)})` : '') +
|
|
8550
|
+
'\nThat change was made earlier in this conversation, so no new generation was needed' +
|
|
8551
|
+
(completedCachedFiles.length > 0
|
|
8552
|
+
? ':\n' + completedCachedFiles.map((p) => `- ${p}`).join('\n')
|
|
8553
|
+
: '.')
|
|
8554
|
+
: null;
|
|
8400
8555
|
const generationPanelRef = getWidgetDetailsRef(generationWidgetId);
|
|
8401
8556
|
const isFocusedPanel = generationWidgetId === focusedPanelWidgetId;
|
|
8402
8557
|
const completedWidgetName = getWidgetById(generationWidgetId)?.name || 'widget';
|
|
@@ -8521,11 +8676,13 @@ Requirements:
|
|
|
8521
8676
|
});
|
|
8522
8677
|
|
|
8523
8678
|
const commitShort = completedCommitSha ? completedCommitSha.substring(0, 7) : '';
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8679
|
+
if (!completedCached) {
|
|
8680
|
+
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
8681
|
+
completedPreviewBuildErr
|
|
8682
|
+
? `Code saved${commitShort ? ` (${commitShort})` : ''} — building preview…`
|
|
8683
|
+
: `Code saved${commitShort ? ` (${commitShort})` : ''} — loading preview…`,
|
|
8684
|
+
);
|
|
8685
|
+
}
|
|
8529
8686
|
|
|
8530
8687
|
// console.log('Widget Creator: Code generation operation completed for widget', generationWidgetId.substring(0, 8), ':', op);
|
|
8531
8688
|
|
|
@@ -8561,13 +8718,17 @@ Requirements:
|
|
|
8561
8718
|
// Final chat message regardless of panel focus — without this, a
|
|
8562
8719
|
// closed/unfocused panel never receives the terminal update and the
|
|
8563
8720
|
// chat stays on the last progress line (e.g. stuck at 80%).
|
|
8721
|
+
// A cache-served completion gets the "already done" affordance:
|
|
8722
|
+
// it names the earlier change (and files) instead of claiming a
|
|
8723
|
+
// generation just happened.
|
|
8564
8724
|
panelChat?.addAssistantMessage(
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8725
|
+
cachedChatSummary ??
|
|
8726
|
+
(completedPreviewBuildErr
|
|
8727
|
+
? `Code saved${completedCommitSha ? ` (Commit: ${completedCommitSha.substring(0, 7)})` : ''}, but preview build failed:\n${completedPreviewBuildErr}\n\nRebuilding preview…`
|
|
8728
|
+
: `Code generation completed!${completedCommitSha ? ` Commit: ${completedCommitSha.substring(0, 7)}` : ''}\nPreview is loading…`),
|
|
8568
8729
|
true
|
|
8569
8730
|
);
|
|
8570
|
-
if (!completedPreviewBuildErr) {
|
|
8731
|
+
if (!completedPreviewBuildErr && !completedCached) {
|
|
8571
8732
|
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
8572
8733
|
`Code generation completed!${completedCommitSha ? ` Commit: ${completedCommitSha.substring(0, 7)}` : ''}\nPreview is loading…`,
|
|
8573
8734
|
);
|
|
@@ -8978,11 +9139,17 @@ Requirements:
|
|
|
8978
9139
|
publishingWidgetId = null;
|
|
8979
9140
|
setCrossTabPublishing(false, capturedWidgetId);
|
|
8980
9141
|
|
|
8981
|
-
const
|
|
8982
|
-
|
|
9142
|
+
const publishEnvelope = publishData as unknown as {
|
|
9143
|
+
data?: { version?: number; artifactUrl?: string | null; commitSha?: string | null };
|
|
9144
|
+
version?: number;
|
|
9145
|
+
artifactUrl?: string | null;
|
|
9146
|
+
commitSha?: string | null;
|
|
9147
|
+
};
|
|
9148
|
+
const version = publishEnvelope?.data?.version || publishEnvelope?.version || 1;
|
|
9149
|
+
const artifactUrl = publishEnvelope?.data?.artifactUrl || publishEnvelope?.artifactUrl || null;
|
|
8983
9150
|
const commitSha =
|
|
8984
|
-
|
|
8985
|
-
??
|
|
9151
|
+
publishEnvelope?.data?.commitSha
|
|
9152
|
+
?? publishEnvelope?.commitSha
|
|
8986
9153
|
?? commitShaForPublish
|
|
8987
9154
|
?? null;
|
|
8988
9155
|
applyPublishSuccessToWidgetState(capturedWidgetId, version, artifactUrl, commitSha);
|
|
@@ -9030,9 +9197,9 @@ Requirements:
|
|
|
9030
9197
|
|
|
9031
9198
|
|
|
9032
9199
|
// Token expiration check interval
|
|
9033
|
-
let tokenCheckInterval:
|
|
9200
|
+
let tokenCheckInterval: ReturnType<typeof setInterval>;
|
|
9034
9201
|
let tokenCheckIntervalTime: number = 10; // seconds — check frequently so logout is prompt
|
|
9035
|
-
let tokenDisplayInterval:
|
|
9202
|
+
let tokenDisplayInterval: ReturnType<typeof setInterval>;
|
|
9036
9203
|
|
|
9037
9204
|
// Reactive variables for real-time token status
|
|
9038
9205
|
let tokenTimeLeft: number = 0;
|
|
@@ -9115,6 +9282,9 @@ Requirements:
|
|
|
9115
9282
|
// Clean up cross-tab flags when tab is closed/refreshed. The handler lives at
|
|
9116
9283
|
// component scope so `onDestroy` can remove the same reference.
|
|
9117
9284
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
9285
|
+
// Keep a component-scope binding so onDestroy can remove the listener —
|
|
9286
|
+
// the arrow itself is scoped to this onMount callback.
|
|
9287
|
+
handleBeforeUnloadRef = handleBeforeUnload;
|
|
9118
9288
|
|
|
9119
9289
|
// Read canvas id from the route. The route path (`/canvas/<canvasId>`) is the
|
|
9120
9290
|
// AUTHORITATIVE canvas (site links, refresh, deep-links); localStorage is only a
|
|
@@ -9196,6 +9366,8 @@ Requirements:
|
|
|
9196
9366
|
};
|
|
9197
9367
|
});
|
|
9198
9368
|
|
|
9369
|
+
let handleBeforeUnloadRef: (() => void) | null = null;
|
|
9370
|
+
|
|
9199
9371
|
// Clean up interval on component destroy
|
|
9200
9372
|
onDestroy(() => {
|
|
9201
9373
|
clearCanvasLoadFailsafe();
|
|
@@ -9208,7 +9380,7 @@ Requirements:
|
|
|
9208
9380
|
}
|
|
9209
9381
|
// Cross-tab cleanup: remove flags so other tabs don't stay blocked
|
|
9210
9382
|
if (typeof window !== 'undefined') {
|
|
9211
|
-
window.removeEventListener('beforeunload',
|
|
9383
|
+
window.removeEventListener('beforeunload', handleBeforeUnloadRef);
|
|
9212
9384
|
window.removeEventListener('storage', handleCrossTabStorageEvent);
|
|
9213
9385
|
window.removeEventListener('widgetic:session-extended', handleSiteSessionExtended);
|
|
9214
9386
|
window.removeEventListener('click', handleCanvasDropdownOutsideClick);
|
|
@@ -9399,6 +9571,19 @@ Requirements:
|
|
|
9399
9571
|
return sanitizePreviewBuildError(raw);
|
|
9400
9572
|
}
|
|
9401
9573
|
|
|
9574
|
+
/** True when the operation completed from the semantic cache (no new generation ran). */
|
|
9575
|
+
function getOperationCached(metadata?: Record<string, unknown>): boolean {
|
|
9576
|
+
return metadata?.cached === true;
|
|
9577
|
+
}
|
|
9578
|
+
|
|
9579
|
+
/** Changed-file paths reported by a semantic-cache-served operation. */
|
|
9580
|
+
function getOperationCachedFiles(metadata?: Record<string, unknown>): string[] {
|
|
9581
|
+
if (!metadata) return [];
|
|
9582
|
+
const raw = metadata.generatedFiles;
|
|
9583
|
+
if (!Array.isArray(raw)) return [];
|
|
9584
|
+
return raw.filter((p): p is string => typeof p === 'string');
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9402
9587
|
function getChatMethodsForWidget(widgetId: string | null | undefined) {
|
|
9403
9588
|
if (!widgetId) return chatMethods ?? null;
|
|
9404
9589
|
const instanceKey = panelInstanceKeyByWidgetId[widgetId] ?? widgetId;
|
|
@@ -9788,8 +9973,7 @@ Requirements:
|
|
|
9788
9973
|
codeGenerationPrompt: storedPrompt,
|
|
9789
9974
|
promptImages: [],
|
|
9790
9975
|
lastCommitId: hydratedHead,
|
|
9791
|
-
lastPublishedVersion: publishedVersion,
|
|
9792
|
-
selectedWidgetJsPath: widget?.jsPath ?? null,
|
|
9976
|
+
lastPublishedVersion: publishedVersion, selectedWidgetJsPath: (widget as { jsPath?: string | null } | undefined)?.jsPath ?? null,
|
|
9793
9977
|
currentRepositoryId: normalizeRepositoryId(widget),
|
|
9794
9978
|
widgetDetailsStep: resolveCreatorWorkflowStep(publishedVersion !== null ? 'edit' : 'create'),
|
|
9795
9979
|
generateCodeStatus: null,
|
|
@@ -10047,7 +10231,7 @@ Requirements:
|
|
|
10047
10231
|
const widget = getWidgetById(widgetId);
|
|
10048
10232
|
const version = ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(widget);
|
|
10049
10233
|
if (version == null) return null;
|
|
10050
|
-
const jsPath = ps.selectedWidgetJsPath ?? widget?.jsPath ?? null;
|
|
10234
|
+
const jsPath = ps.selectedWidgetJsPath ?? (widget as { jsPath?: string | null } | undefined)?.jsPath ?? null;
|
|
10051
10235
|
return resolvePublishedWidgetUrl(widgetId, version, jsPath);
|
|
10052
10236
|
}
|
|
10053
10237
|
|
|
@@ -10071,6 +10255,8 @@ Requirements:
|
|
|
10071
10255
|
// Historical previews are never interchangeable with the shared HEAD preview.
|
|
10072
10256
|
let shapeVersionPreviewByWidgetId: Record<string, {
|
|
10073
10257
|
versionId: string; requestId: number; loading: boolean; error: string | null; url: string | null;
|
|
10258
|
+
/** Commit the selected version was built from — drives the chat's Head-at-commit badge. */
|
|
10259
|
+
commitSha?: string | null;
|
|
10074
10260
|
errorDetails?: Awaited<ReturnType<typeof parseVersionPreviewError>> | null;
|
|
10075
10261
|
}> = {};
|
|
10076
10262
|
let shapeVersionPreviewRequestId = 0;
|
|
@@ -10393,7 +10579,10 @@ Requirements:
|
|
|
10393
10579
|
}
|
|
10394
10580
|
shapeVersionPreviewByWidgetId = {
|
|
10395
10581
|
...shapeVersionPreviewByWidgetId,
|
|
10396
|
-
[widgetId]: {
|
|
10582
|
+
[widgetId]: {
|
|
10583
|
+
versionId, requestId, loading: false, error: null, url: previewUrl,
|
|
10584
|
+
commitSha: normalizeCommitSha(commitSha),
|
|
10585
|
+
},
|
|
10397
10586
|
};
|
|
10398
10587
|
syncWidgetShapeLivePreviews();
|
|
10399
10588
|
panelDisplayRevision += 1;
|
|
@@ -10432,6 +10621,14 @@ Requirements:
|
|
|
10432
10621
|
if (source.startsWith('v:')) {
|
|
10433
10622
|
const parsed = parseInt(source.slice(2), 10);
|
|
10434
10623
|
version = Number.isFinite(parsed) ? parsed : null;
|
|
10624
|
+
// A draft version has no publish-history row to read a commit from, so
|
|
10625
|
+
// without the sha the version preview reported we fell back to repo HEAD
|
|
10626
|
+
// and the badge stayed on the newest turn instead of moving to the
|
|
10627
|
+
// selected version's own turn.
|
|
10628
|
+
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10629
|
+
if (previewState?.versionId === source && previewState.commitSha) {
|
|
10630
|
+
return normalizeCommitSha(previewState.commitSha);
|
|
10631
|
+
}
|
|
10435
10632
|
} else {
|
|
10436
10633
|
version = ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(getWidgetById(widgetId));
|
|
10437
10634
|
}
|
|
@@ -10450,28 +10647,84 @@ Requirements:
|
|
|
10450
10647
|
return normalizeCommitSha(ps.lastCommitId);
|
|
10451
10648
|
}
|
|
10452
10649
|
|
|
10650
|
+
/**
|
|
10651
|
+
* Head commit per open panel, for the chat's Head-at-commit badge.
|
|
10652
|
+
*
|
|
10653
|
+
* The resolver reads the selected version and its preview state out of records
|
|
10654
|
+
* the compiler cannot follow through the call, so calling it straight from the
|
|
10655
|
+
* template never re-ran on a version switch: the chat kept the previous
|
|
10656
|
+
* commit and the badge stayed on the newest turn. Naming the records here is
|
|
10657
|
+
* what makes the switch reach the chat.
|
|
10658
|
+
*/
|
|
10659
|
+
let headCommitShaByWidgetId: Record<string, string | null> = {};
|
|
10660
|
+
/**
|
|
10661
|
+
* Short commit sha → "Version N" per panel, so a chat turn reads as the version
|
|
10662
|
+
* it produced. A hash is meaningless to a non-developer, so it stays behind
|
|
10663
|
+
* debug mode and this label is what the turn shows.
|
|
10664
|
+
*/
|
|
10665
|
+
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10666
|
+
$: {
|
|
10667
|
+
void shapePreviewSourceByWidgetId;
|
|
10668
|
+
void shapeVersionPreviewByWidgetId;
|
|
10669
|
+
void panelDisplayRevision;
|
|
10670
|
+
const nextHeads: Record<string, string | null> = {};
|
|
10671
|
+
const nextLabels: Record<string, Record<string, string>> = {};
|
|
10672
|
+
for (const widgetId of openWidgetPanelIds) {
|
|
10673
|
+
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
10674
|
+
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
10675
|
+
}
|
|
10676
|
+
headCommitShaByWidgetId = nextHeads;
|
|
10677
|
+
versionLabelsByWidgetId = nextLabels;
|
|
10678
|
+
}
|
|
10679
|
+
|
|
10680
|
+
/** Commit shas are keyed on 7 chars — that is what the chat summaries carry. */
|
|
10681
|
+
function versionLabelKey(sha: string | null | undefined): string | null {
|
|
10682
|
+
const normalized = normalizeCommitSha(sha);
|
|
10683
|
+
return normalized ? normalized.slice(0, 7) : null;
|
|
10684
|
+
}
|
|
10685
|
+
|
|
10686
|
+
function buildVersionLabelsForPanel(widgetId: string): Record<string, string> {
|
|
10687
|
+
const labels: Record<string, string> = {};
|
|
10688
|
+
const ps = getPanelDisplayState(widgetId);
|
|
10689
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10690
|
+
for (const record of history || []) {
|
|
10691
|
+
const key = versionLabelKey(record?.commitSha ?? record?.commit_sha);
|
|
10692
|
+
if (key && record?.version != null) labels[key] = `Version ${record.version}`;
|
|
10693
|
+
}
|
|
10694
|
+
// The version the user is previewing right now may have been fetched rather
|
|
10695
|
+
// than read from history, and it is the one whose label matters most.
|
|
10696
|
+
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10697
|
+
const previewKey = versionLabelKey(previewState?.commitSha);
|
|
10698
|
+
if (previewKey && previewState?.versionId?.startsWith('v:')) {
|
|
10699
|
+
labels[previewKey] = `Version ${previewState.versionId.slice(2)}`;
|
|
10700
|
+
}
|
|
10701
|
+
return labels;
|
|
10702
|
+
}
|
|
10703
|
+
|
|
10453
10704
|
function syncWidgetShapeLivePreviews(): void {
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10705
|
+
measureSync('sync-widget-shape-previews', () => {
|
|
10706
|
+
try {
|
|
10707
|
+
canvasSelectedWidgetShapeId = canvasRef?.getSelectedWidgetShapeId?.() ?? null;
|
|
10708
|
+
widgetShapeLivePreviewsRef?.sync?.();
|
|
10709
|
+
// Hide Fabric screenshots under live iframes (avoids ghost bleed in padding).
|
|
10710
|
+
const rects = canvasRef?.listWidgetShapeScreenRects?.() ?? [];
|
|
10711
|
+
const liveWidgetIds = new Set<string>();
|
|
10712
|
+
for (const rect of rects) {
|
|
10713
|
+
if (!rect?.widgetId) continue;
|
|
10714
|
+
if (!resolveWidgetShapeLivePreviewUrl(rect.widgetId)) continue;
|
|
10715
|
+
liveWidgetIds.add(rect.widgetId);
|
|
10716
|
+
canvasRef?.setWidgetShapeLivePreviewHiddenImage?.(rect.widgetId, true);
|
|
10717
|
+
}
|
|
10718
|
+
const linkedIds = canvasRef?.getLinkedWidgetIds?.() ?? new Set<string>();
|
|
10719
|
+
for (const widgetId of linkedIds) {
|
|
10720
|
+
if (!liveWidgetIds.has(widgetId)) {
|
|
10721
|
+
canvasRef?.setWidgetShapeLivePreviewHiddenImage?.(widgetId, false);
|
|
10722
|
+
}
|
|
10470
10723
|
}
|
|
10724
|
+
} catch (err) {
|
|
10725
|
+
console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
|
|
10471
10726
|
}
|
|
10472
|
-
}
|
|
10473
|
-
console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
|
|
10474
|
-
}
|
|
10727
|
+
});
|
|
10475
10728
|
}
|
|
10476
10729
|
|
|
10477
10730
|
function getPanelEmbedPreviewUrl(widgetId: string): string | null {
|
|
@@ -11917,13 +12170,12 @@ async function loadPublishHistoryForPanel(widgetId: string, opts: { silent?: boo
|
|
|
11917
12170
|
});
|
|
11918
12171
|
return;
|
|
11919
12172
|
}
|
|
11920
|
-
try {
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
: []);
|
|
12173
|
+
try { const historyRes = await widgetsClient.getWidgetPublishHistory({ widgetId });
|
|
12174
|
+
const historyEnvelope = historyRes as unknown as { data?: unknown };
|
|
12175
|
+
// SDK types the envelope ({ success, data }); a response-transform middleware
|
|
12176
|
+
// may unwrap it, so a bare array stays tolerated.
|
|
12177
|
+
const historyData = Array.isArray(historyRes) ? historyRes : historyEnvelope?.data;
|
|
12178
|
+
const history = (Array.isArray(historyData) ? historyData : []) as PublishHistoryRecord[];
|
|
11927
12179
|
patchPanelSnapshot(widgetId, {
|
|
11928
12180
|
publishHistory: history,
|
|
11929
12181
|
publishHistoryResolved: true,
|
|
@@ -12223,8 +12475,12 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12223
12475
|
// console.log('Signup response:', signupResponse);
|
|
12224
12476
|
|
|
12225
12477
|
// show error signup status
|
|
12226
|
-
|
|
12227
|
-
|
|
12478
|
+
// signupUser is typed Promise<void>; it resolves to undefined on the
|
|
12479
|
+
// success path, so a falsy response IS the success signal. Error details
|
|
12480
|
+
// arrive via the thrown ResponseError caught below.
|
|
12481
|
+
const signupEnvelope = signupResponse as unknown as { user?: unknown; message?: string } | undefined;
|
|
12482
|
+
if (!signupEnvelope || !signupEnvelope.user) {
|
|
12483
|
+
authStatus = signupEnvelope?.message ?? 'Signup failed.';
|
|
12228
12484
|
return;
|
|
12229
12485
|
}
|
|
12230
12486
|
|
|
@@ -12246,7 +12502,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12246
12502
|
// console.log('Login response:', loginResponse);
|
|
12247
12503
|
|
|
12248
12504
|
// show success login status
|
|
12249
|
-
|
|
12505
|
+
// The generated loginUser return type is mis-derived (a billing endpoint's
|
|
12506
|
+
// shape); the real /auth/login payload is a session envelope. Cast honestly.
|
|
12507
|
+
const payload = (loginResponse as unknown as { data?: { accessToken?: string; user?: Record<string, unknown> } })?.data;
|
|
12250
12508
|
if (payload?.accessToken) {
|
|
12251
12509
|
currentUserToken = payload.accessToken;
|
|
12252
12510
|
|
|
@@ -12312,7 +12570,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12312
12570
|
// console.log('WidgetCreator.updateTokenStatus: Decoded token:', decoded);
|
|
12313
12571
|
|
|
12314
12572
|
// if the decoded token has an expiration field, update the token status
|
|
12315
|
-
if (decoded && decoded.exp) {
|
|
12573
|
+
if (decoded && typeof decoded.exp === 'number') {
|
|
12316
12574
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
12317
12575
|
const expUnix = decoded.exp;
|
|
12318
12576
|
tokenTimeLeft = expUnix - currentTime;
|
|
@@ -12486,17 +12744,20 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12486
12744
|
// console.log('Available fields:', Object.keys(decodedToken));
|
|
12487
12745
|
|
|
12488
12746
|
// Extract user info from Supabase JWT structure
|
|
12747
|
+
// user_metadata is an untyped JWT claim blob — index it explicitly so the
|
|
12748
|
+
// accesses below are checked reads, not property probes on `unknown`.
|
|
12749
|
+
const userMetadata = (decodedToken.user_metadata ?? {}) as Record<string, unknown>;
|
|
12489
12750
|
user = {
|
|
12490
12751
|
id: decodedToken.sub || decodedToken.user_id || decodedToken.id || 'token-user',
|
|
12491
|
-
email:
|
|
12752
|
+
email: userMetadata.email || 'user@token-auth.com',
|
|
12492
12753
|
name:
|
|
12493
|
-
|
|
12494
|
-
|
|
12754
|
+
userMetadata.name ||
|
|
12755
|
+
userMetadata.full_name ||
|
|
12495
12756
|
decodedToken.name ||
|
|
12496
12757
|
decodedToken.full_name ||
|
|
12497
12758
|
'Token User',
|
|
12498
12759
|
role: decodedToken.role || 'user',
|
|
12499
|
-
avatar:
|
|
12760
|
+
avatar: userMetadata.avatar_url || userMetadata.picture
|
|
12500
12761
|
};
|
|
12501
12762
|
|
|
12502
12763
|
// Show more detailed status with actual extracted info
|
|
@@ -12743,10 +13004,10 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12743
13004
|
}
|
|
12744
13005
|
|
|
12745
13006
|
const sourceHasCode =
|
|
12746
|
-
!!normalizeRepositoryId(newWidget)
|
|
13007
|
+
!!normalizeRepositoryId(asWidgetSummary(newWidget))
|
|
12747
13008
|
|| !!((newWidget as { jsPath?: string | null; js_path?: string | null }).jsPath
|
|
12748
13009
|
?? (newWidget as { js_path?: string | null }).js_path)
|
|
12749
|
-
|| resolvePublishedVersionFromWidget(newWidget) !== null
|
|
13010
|
+
|| resolvePublishedVersionFromWidget(asWidgetSummary(newWidget)) !== null
|
|
12750
13011
|
|| !!sourceCommit;
|
|
12751
13012
|
|
|
12752
13013
|
if (!canvasPrimaryWidgetId) {
|
|
@@ -12754,7 +13015,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12754
13015
|
}
|
|
12755
13016
|
|
|
12756
13017
|
// Duplicate stays a sibling on the canvas — do not make it the primary widget.
|
|
12757
|
-
await selectWidget(newWidget, { skipPlaceShape: true });
|
|
13018
|
+
await selectWidget(asWidgetSummary(newWidget), { skipPlaceShape: true });
|
|
12758
13019
|
notifyHostWidgetName(getWidgetById(canvasPrimaryWidgetId) ?? getWidgetById(sourceWidgetId));
|
|
12759
13020
|
|
|
12760
13021
|
showToast('success', 'Widget duplicated', {
|
|
@@ -12784,11 +13045,11 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12784
13045
|
try {
|
|
12785
13046
|
await handleWidgetDataChanged(newWidget.id);
|
|
12786
13047
|
const freshWidget = getWidgetById(newWidget.id);
|
|
12787
|
-
|
|
13048
|
+
let repoId = freshWidget ? normalizeRepositoryId(freshWidget) : normalizeRepositoryId(asWidgetSummary(newWidget));
|
|
12788
13049
|
|
|
12789
13050
|
if (!repoId) {
|
|
12790
13051
|
// console.log('[WidgetCreator] Duplicate has no repo after re-fetch, creating one...');
|
|
12791
|
-
repoId = await ensureWidgetRepository(freshWidget || newWidget, { silent: true });
|
|
13052
|
+
repoId = await ensureWidgetRepository(freshWidget || asWidgetSummary(newWidget), { silent: true });
|
|
12792
13053
|
if (repoId) {
|
|
12793
13054
|
widgets = widgets.map((w) => w.id === newWidget.id ? { ...w, repositoryId: repoId } : w);
|
|
12794
13055
|
// console.log('[WidgetCreator] Repository created for duplicate:', repoId);
|
|
@@ -13249,14 +13510,20 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13249
13510
|
}
|
|
13250
13511
|
|
|
13251
13512
|
let objectCount = 0;
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13513
|
+
// Canvas JSON carries the shapes' embedded images, so it reaches megabytes on
|
|
13514
|
+
// a busy canvas and every parse of it blocks the main thread. Parse once and
|
|
13515
|
+
// reuse it for the API payload below.
|
|
13516
|
+
let parsedCanvas: any = null;
|
|
13517
|
+
measureSync('canvas-autosave:parse', () => {
|
|
13518
|
+
try {
|
|
13519
|
+
parsedCanvas = JSON.parse(json);
|
|
13520
|
+
// saveCanvas() wraps as { version, savedAt, viewport, canvas: { objects: [...] } }
|
|
13521
|
+
const objects = parsedCanvas?.canvas?.objects ?? parsedCanvas?.objects ?? [];
|
|
13522
|
+
objectCount = Array.isArray(objects) ? objects.length : 0;
|
|
13523
|
+
} catch {
|
|
13524
|
+
// ignore parse errors for debug
|
|
13525
|
+
}
|
|
13526
|
+
});
|
|
13260
13527
|
|
|
13261
13528
|
if (isUnmountSave) {
|
|
13262
13529
|
// console.log(`[CanvasDebug] Unmount save: objectCount=${objectCount}, lastSavedCount=${lastSavedCanvasObjectCount}`);
|
|
@@ -13289,11 +13556,18 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13289
13556
|
|
|
13290
13557
|
// Save to durable L1 fallback (always, even with 0 objects for legitimate clears)
|
|
13291
13558
|
saveCanvasState(canvasId, json);
|
|
13292
|
-
// Keep the legacy localStorage copy in sync for downgrade safety.
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13559
|
+
// Keep the legacy localStorage copy in sync for downgrade safety. `setItem` is
|
|
13560
|
+
// a synchronous disk write of the whole canvas, so during a generation it is
|
|
13561
|
+
// deferred to the end: the durable IndexedDB copy above is already written,
|
|
13562
|
+
// and the mirror exists only for a downgrade.
|
|
13563
|
+
if (!isAnyWidgetGenerating() || force || isUnmountSave) {
|
|
13564
|
+
measureSync('canvas-autosave:localStorage-mirror', () => {
|
|
13565
|
+
try {
|
|
13566
|
+
localStorage.setItem(getCanvasStorageKey(), json);
|
|
13567
|
+
} catch (err) {
|
|
13568
|
+
console.warn('[WidgetCreator] Failed to save canvas to localStorage:', err);
|
|
13569
|
+
}
|
|
13570
|
+
});
|
|
13297
13571
|
}
|
|
13298
13572
|
|
|
13299
13573
|
// Save to API via SDK with sequence race protection
|
|
@@ -13303,7 +13577,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13303
13577
|
await canvasesClient.updateCanvas({
|
|
13304
13578
|
id: canvasId,
|
|
13305
13579
|
updateCanvasRequest: {
|
|
13306
|
-
canvasContent: JSON.parse(json)
|
|
13580
|
+
canvasContent: parsedCanvas ?? JSON.parse(json)
|
|
13307
13581
|
}
|
|
13308
13582
|
});
|
|
13309
13583
|
if (currentSeq < lastResolvedAutosaveSequence) {
|
|
@@ -13513,13 +13787,14 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13513
13787
|
{ headers: { 'Idempotency-Key': idempotencyKey } }
|
|
13514
13788
|
);
|
|
13515
13789
|
|
|
13516
|
-
|
|
13790
|
+
const createEnvelope = response as unknown as { error?: string; message?: string };
|
|
13791
|
+
if (!response || createEnvelope.error || createEnvelope.message) {
|
|
13517
13792
|
console.error('[WidgetCreator] Widget create on first send failed:', response);
|
|
13518
13793
|
showToast('error', 'Failed to create widget from design');
|
|
13519
13794
|
return null;
|
|
13520
13795
|
}
|
|
13521
13796
|
|
|
13522
|
-
const newWidget = response.data
|
|
13797
|
+
const newWidget = asWidgetSummary((response as { data?: unknown }).data ?? response);
|
|
13523
13798
|
if (!newWidget?.id) {
|
|
13524
13799
|
showToast('warning', 'Widget created but no ID returned');
|
|
13525
13800
|
return null;
|
|
@@ -13790,7 +14065,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13790
14065
|
}
|
|
13791
14066
|
|
|
13792
14067
|
async function ensureWidgetRepository(
|
|
13793
|
-
widget: WidgetSummary,
|
|
14068
|
+
widget: WidgetSummary | null | undefined,
|
|
13794
14069
|
options: { forceRecreate?: boolean; silent?: boolean } = {}
|
|
13795
14070
|
): Promise<string | null> {
|
|
13796
14071
|
const { forceRecreate = false, silent = false } = options;
|
|
@@ -13915,8 +14190,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13915
14190
|
});
|
|
13916
14191
|
// console.log('Widget Creator: Repository creation response:', response);
|
|
13917
14192
|
|
|
13918
|
-
const repoData = response
|
|
13919
|
-
const repoId = repoData?.id || repoData?.repoId || repoData?.repo_id || null;
|
|
14193
|
+
const repoData = ((response as unknown as { data?: Record<string, unknown> }).data ?? response) as Record<string, unknown>;
|
|
14194
|
+
const repoId = (repoData?.id || repoData?.repoId || repoData?.repo_id || null) as string | null;
|
|
13920
14195
|
|
|
13921
14196
|
if (!repoId) {
|
|
13922
14197
|
showToast('error', 'Repository creation failed', {
|
|
@@ -14003,7 +14278,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14003
14278
|
const response = await widgetsClient.deleteWidget(
|
|
14004
14279
|
{
|
|
14005
14280
|
id: adminDeleteWidget.id,
|
|
14006
|
-
reason: 'admin_delete'
|
|
14281
|
+
deleteWidgetRequest: { reason: 'admin_delete' }
|
|
14007
14282
|
},
|
|
14008
14283
|
{
|
|
14009
14284
|
headers: {
|
|
@@ -14012,10 +14287,14 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14012
14287
|
}
|
|
14013
14288
|
);
|
|
14014
14289
|
|
|
14015
|
-
|
|
14290
|
+
// deleteWidget resolves void; only a thrown ResponseError (caught below)
|
|
14291
|
+
// carries a failure. The defensive envelope check is kept for the response-
|
|
14292
|
+
// transform middleware, but must go through an honest cast.
|
|
14293
|
+
const deleteEnvelope = response as unknown as { error?: string; message?: string } | undefined;
|
|
14294
|
+
if (deleteEnvelope && (deleteEnvelope.error || deleteEnvelope.message)) {
|
|
14016
14295
|
console.error('Admin widget deletion failed:', response);
|
|
14017
14296
|
showToast('error', 'Delete failed', {
|
|
14018
|
-
description:
|
|
14297
|
+
description: deleteEnvelope?.message || deleteEnvelope?.error || 'Unknown error'
|
|
14019
14298
|
});
|
|
14020
14299
|
adminDeleteLoading = false;
|
|
14021
14300
|
return;
|
|
@@ -15218,11 +15497,11 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15218
15497
|
class="code-generation-chat-host w-full min-h-0 {chatUserHeight == null ? 'flex-1' : 'shrink-0'}"
|
|
15219
15498
|
data-widget-chat-height={panelWidgetId}
|
|
15220
15499
|
style={chatUserHeight == null
|
|
15221
|
-
? `flex: 1 1 auto; min-height: ${
|
|
15500
|
+
? `flex: 1 1 auto; min-height: ${CHAT_AUTO_MIN_HEIGHT}px;`
|
|
15222
15501
|
: `flex: 0 0 ${chatUserHeight}px; min-height: ${CHAT_MIN_HEIGHT}px; height: ${chatUserHeight}px;`}
|
|
15223
15502
|
>
|
|
15224
15503
|
<Chat class="code-generation-chat w-full h-full rounded-none border-0"
|
|
15225
|
-
style="height: 100%; min-height: {CHAT_MIN_HEIGHT}px;"
|
|
15504
|
+
style="height: 100%; min-height: {chatUserHeight == null ? CHAT_AUTO_MIN_HEIGHT : CHAT_MIN_HEIGHT}px;"
|
|
15226
15505
|
resizeExpandsContainer={true}
|
|
15227
15506
|
onMessagesResize={(deltaPx) => handleChatMessagesResize(panelWidgetId, deltaPx)}
|
|
15228
15507
|
showLogs={debugMode}
|
|
@@ -15249,7 +15528,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15249
15528
|
autoCodeDetection: false,
|
|
15250
15529
|
}
|
|
15251
15530
|
}}
|
|
15252
|
-
widgetHeadCommitSha={
|
|
15531
|
+
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15532
|
+
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15253
15533
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15254
15534
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
15255
15535
|
onUploadChatFile={handleChatUploadFile}
|
|
@@ -15461,7 +15741,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15461
15741
|
{/if}
|
|
15462
15742
|
|
|
15463
15743
|
<!-- ─── Schema loading / error / PropsEditor ─── -->
|
|
15464
|
-
{@const panelSchemasLoading = (panelDisplayRevision, ps.compositionSchemasLoading)}
|
|
15744
|
+
{@const panelSchemasLoading = rev(panelDisplayRevision, ps.compositionSchemasLoading)}
|
|
15465
15745
|
{@const panelSchemaLoadLabel = getPanelSchemaLoadLabel(panelWidgetId)}
|
|
15466
15746
|
{#if panelSchemasLoading}
|
|
15467
15747
|
<div class="composition-editor-loading flex flex-col items-center justify-center py-8">
|
|
@@ -15766,7 +16046,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15766
16046
|
{/if}
|
|
15767
16047
|
|
|
15768
16048
|
<!-- Delete Widget Shape Confirmation Dialog -->
|
|
15769
|
-
|
|
16049
|
+
<!-- Dialog.Root (bits-ui AlertDialogRoot) exposes no class prop — styling hooks
|
|
16050
|
+
live on Content/Header below. -->
|
|
16051
|
+
<Dialog.Root bind:open={deleteDialogOpen} onOpenChange={(open) => { if (!open) handleDeleteDialogResponse('cancel'); }}>
|
|
15770
16052
|
<Dialog.Content class="confirm-delete-dialog-content max-w-md bg-white z-9999">
|
|
15771
16053
|
<Dialog.Header class="confirm-delete-dialog-header">
|
|
15772
16054
|
<Dialog.Title>Delete Widget Shape</Dialog.Title>
|
|
@@ -15808,7 +16090,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15808
16090
|
</Dialog.Root>
|
|
15809
16091
|
|
|
15810
16092
|
<!-- Delete Canvas Confirmation Dialog -->
|
|
15811
|
-
<Dialog.Root
|
|
16093
|
+
<Dialog.Root bind:open={showDeleteCanvasDialog} onOpenChange={(open) => { if (!open) { showDeleteCanvasDialog = false; deleteCanvasTarget = null; } }}>
|
|
15812
16094
|
<Dialog.Content class="confirm-delete-canvas-dialog-content max-w-sm bg-white z-9999">
|
|
15813
16095
|
<Dialog.Header class="confirm-delete-canvas-dialog-header">
|
|
15814
16096
|
<Dialog.Title>Delete Canvas</Dialog.Title>
|
|
@@ -15868,7 +16150,6 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15868
16150
|
|
|
15869
16151
|
<!-- Confirm Generation Dialog (shown on convert-to-widget before auto-generating) -->
|
|
15870
16152
|
<Dialog.Root
|
|
15871
|
-
class="confirm-generate-dialog-root"
|
|
15872
16153
|
open={confirmGenerateDialogOpen}
|
|
15873
16154
|
onOpenChange={(open) => {
|
|
15874
16155
|
confirmGenerateDialogOpen = open;
|