@widgetic/creator 0.3.65 → 0.3.67
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 +494 -138
- 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/creatorDraftStorage.d.ts +5 -0
- package/dist/utils/creatorDraftStorage.js +41 -4
- 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';
|
|
@@ -518,6 +529,8 @@
|
|
|
518
529
|
onMount(async () => {
|
|
519
530
|
if (canvasModulePromise) {
|
|
520
531
|
const canvasModule = await canvasModulePromise;
|
|
532
|
+
// Installed before the canvas mounts so the first save already externalizes
|
|
533
|
+
installCanvasThumbnailStore(canvasModule);
|
|
521
534
|
WidgeticCanvas = canvasModule.Canvas;
|
|
522
535
|
}
|
|
523
536
|
installLongTaskFreezeReporter();
|
|
@@ -579,7 +592,10 @@
|
|
|
579
592
|
saveWidgetDraft,
|
|
580
593
|
readWidgetDraft,
|
|
581
594
|
deleteWidgetDraft,
|
|
582
|
-
copyWidgetDraft
|
|
595
|
+
copyWidgetDraft,
|
|
596
|
+
saveShapeThumbnail,
|
|
597
|
+
readShapeThumbnail,
|
|
598
|
+
canPersistShapeThumbnails
|
|
583
599
|
} from './utils/creatorDraftStorage';
|
|
584
600
|
|
|
585
601
|
/**
|
|
@@ -635,10 +651,31 @@
|
|
|
635
651
|
repositoryId?: string | null;
|
|
636
652
|
repository_id?: string | null;
|
|
637
653
|
repoId?: string | null;
|
|
654
|
+
repo_id?: string | null;
|
|
638
655
|
name?: string;
|
|
639
656
|
[extra: string]: unknown;
|
|
640
657
|
}
|
|
641
658
|
|
|
659
|
+
/**
|
|
660
|
+
* View any API payload as the app's open WidgetSummary shape. The SDK's
|
|
661
|
+
* generated models (Widget, CreateWidget201Response, …) lack the index
|
|
662
|
+
* signature callers rely on for snake_case/extra fields, so direct
|
|
663
|
+
* assignments fail structurally; this documents the deliberate widening.
|
|
664
|
+
*/
|
|
665
|
+
function asWidgetSummary(payload: unknown): WidgetSummary {
|
|
666
|
+
return (payload ?? {}) as WidgetSummary;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Reactivity marker for template {@const}s: a Svelte 4 {@const} only re-runs
|
|
671
|
+
* when a value referenced in its expression changes, so the panel-revision
|
|
672
|
+
* counter is passed explicitly. The bare comma idiom this replaces
|
|
673
|
+
* (`(revision, value)`) is a TS unused-expression error.
|
|
674
|
+
*/
|
|
675
|
+
function rev<T>(revision: number, value: T, ..._alsoTracked: unknown[]): T {
|
|
676
|
+
return value;
|
|
677
|
+
}
|
|
678
|
+
|
|
642
679
|
// LocalStorage keys for persisting selection across refresh
|
|
643
680
|
const SELECTED_WIDGET_STORAGE_KEY = 'widgetCreator_selectedWidgetId';
|
|
644
681
|
const SELECTED_CANVAS_STORAGE_KEY = 'widgetCreator_selectedCanvasId';
|
|
@@ -868,7 +905,7 @@
|
|
|
868
905
|
// Chat component reference and context (focused panel)
|
|
869
906
|
let chatMethods: {
|
|
870
907
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
871
|
-
addAssistantMessage: (content: string, skipPersist?: boolean) => void;
|
|
908
|
+
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: string[]) => void;
|
|
872
909
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
873
910
|
getConversationId: () => string | null;
|
|
874
911
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -1004,6 +1041,7 @@
|
|
|
1004
1041
|
fileSize?: number;
|
|
1005
1042
|
}>,
|
|
1006
1043
|
sourceWidgetId?: string,
|
|
1044
|
+
sendOptions?: { skipIntentGate?: boolean },
|
|
1007
1045
|
) {
|
|
1008
1046
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1009
1047
|
if (!widgetId) return;
|
|
@@ -1126,6 +1164,7 @@
|
|
|
1126
1164
|
attachmentIds: cdnUploadIds.length > 0 ? cdnUploadIds : undefined,
|
|
1127
1165
|
promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
|
|
1128
1166
|
persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
|
|
1167
|
+
skipIntentGate: sendOptions?.skipIntentGate === true,
|
|
1129
1168
|
});
|
|
1130
1169
|
}
|
|
1131
1170
|
|
|
@@ -1376,6 +1415,76 @@
|
|
|
1376
1415
|
return dataUrls.slice(0, 1);
|
|
1377
1416
|
}
|
|
1378
1417
|
|
|
1418
|
+
/**
|
|
1419
|
+
* Keeps widget screenshots out of the canvas JSON.
|
|
1420
|
+
*
|
|
1421
|
+
* Fabric serialized each widget shape's screenshot inline as base64, so an
|
|
1422
|
+
* autosave stringified megabytes of image data and wrote it three times over —
|
|
1423
|
+
* the main-thread block the freeze reports kept naming. The canvas now stores a
|
|
1424
|
+
* reference and asks for the bytes back through this store: the local copy
|
|
1425
|
+
* first, then the widget's persisted thumbnail so a fresh browser still paints
|
|
1426
|
+
* the shape.
|
|
1427
|
+
*/
|
|
1428
|
+
function installCanvasThumbnailStore(canvasModule: any): void {
|
|
1429
|
+
if (typeof canvasModule?.setShapeImageStore !== 'function') return;
|
|
1430
|
+
canvasModule.setShapeImageStore({
|
|
1431
|
+
save(ref: string, dataUrl: string) {
|
|
1432
|
+
// Refusing here tells the canvas to keep the bytes inline, so a save
|
|
1433
|
+
// before the owner is known cannot drop a screenshot on the floor.
|
|
1434
|
+
if (!canPersistShapeThumbnails()) throw new Error('No cache owner yet');
|
|
1435
|
+
saveShapeThumbnail(ref, dataUrl);
|
|
1436
|
+
},
|
|
1437
|
+
async load(ref: string): Promise<string | null> {
|
|
1438
|
+
const local = await readShapeThumbnail(ref);
|
|
1439
|
+
if (local) return local;
|
|
1440
|
+
const widgetId = ref.startsWith('widget:') ? ref.slice('widget:'.length) : null;
|
|
1441
|
+
if (!widgetId) return null;
|
|
1442
|
+
return await waitForWidgetThumbnailUrl(widgetId);
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* The canvas often loads before the widget list does, so a reference with no
|
|
1449
|
+
* local copy has nothing to resolve to yet. Give the list a few seconds to
|
|
1450
|
+
* arrive rather than leaving the shape blank until the next screenshot.
|
|
1451
|
+
*/
|
|
1452
|
+
async function waitForWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
|
|
1453
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1454
|
+
const widget = getWidgetById(widgetId) as any;
|
|
1455
|
+
const url = widget?.thumbnailUrl || widget?.thumbnail_url;
|
|
1456
|
+
if (url) return url;
|
|
1457
|
+
// The canvas widget list is slim and often omits the thumbnail, so ask the
|
|
1458
|
+
// API for this one widget rather than leaving the shape blank.
|
|
1459
|
+
if (widget) return await fetchWidgetThumbnailUrl(widgetId);
|
|
1460
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1461
|
+
}
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/** One-widget lookup for the persisted thumbnail, cached per widget. */
|
|
1466
|
+
const thumbnailUrlRequests = new Map<string, Promise<string | null>>();
|
|
1467
|
+
|
|
1468
|
+
function fetchWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
|
|
1469
|
+
const pending = thumbnailUrlRequests.get(widgetId);
|
|
1470
|
+
if (pending) return pending;
|
|
1471
|
+
const request = (async () => {
|
|
1472
|
+
if (!widgetsClient) return null;
|
|
1473
|
+
try {
|
|
1474
|
+
const response = (await widgetsClient.getWidgetsBatch({
|
|
1475
|
+
getWidgetsBatchRequest: { widgetIds: [widgetId] }
|
|
1476
|
+
})) as { widgets?: any[]; data?: { widgets?: any[] } };
|
|
1477
|
+
const fetched = (response?.widgets ?? response?.data?.widgets ?? [])[0];
|
|
1478
|
+
return fetched?.thumbnailUrl ?? fetched?.thumbnail_url ?? null;
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
console.warn('[CreatorApp] thumbnail lookup failed', widgetId.slice(0, 8), error);
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
})();
|
|
1484
|
+
thumbnailUrlRequests.set(widgetId, request);
|
|
1485
|
+
return request;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1379
1488
|
async function persistWidgetListThumbnail(widgetId: string, imageDataUrl: string, isCurrent = () => true): Promise<void> {
|
|
1380
1489
|
if (!widgetsClient || !widgetId || !imageDataUrl || !isCurrent()) return;
|
|
1381
1490
|
try {
|
|
@@ -1490,10 +1599,12 @@
|
|
|
1490
1599
|
: null) || userMessages[userMessages.length - 1] || null;
|
|
1491
1600
|
const existingUrls = new Set(
|
|
1492
1601
|
(targetUser?.attachments || [])
|
|
1493
|
-
.map((a: { url?:
|
|
1494
|
-
.filter((url: string
|
|
1602
|
+
.map((a: { url?: unknown }) => a.url)
|
|
1603
|
+
.filter((url): url is string => typeof url === 'string' && url.length > 0),
|
|
1604
|
+
);
|
|
1605
|
+
const existingHasRemoteImage = [...existingUrls].some(
|
|
1606
|
+
(url) => typeof url === 'string' && /^https?:\/\//i.test(url)
|
|
1495
1607
|
);
|
|
1496
|
-
const existingHasRemoteImage = [...existingUrls].some((url) => /^https?:\/\//i.test(url));
|
|
1497
1608
|
// Chat already persisted CDN attachments for this turn — do not merge data URLs/canvas shots.
|
|
1498
1609
|
if (targetUser && existingHasRemoteImage) {
|
|
1499
1610
|
if (convId !== visibleConversationId) {
|
|
@@ -1649,6 +1760,14 @@
|
|
|
1649
1760
|
* Capture via a temporary same-path Worker /preview iframe (has html2canvas handler).
|
|
1650
1761
|
*/
|
|
1651
1762
|
async function captureViaHiddenWorkerPreview(widgetId: string): Promise<string | null> {
|
|
1763
|
+
// Loading a widget in a hidden iframe costs a full Svelte + Tailwind CDN
|
|
1764
|
+
// startup on the main thread this page shares with it, and the capture that
|
|
1765
|
+
// follows is refused during a generation anyway (`requestIframeScreenshot`),
|
|
1766
|
+
// so the whole path is skipped rather than paying the load for nothing.
|
|
1767
|
+
if (isAnyWidgetGenerating()) {
|
|
1768
|
+
console.log('[Chat→Screenshot] Skip hidden Worker capture — codegen in progress', widgetId?.substring(0, 8));
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1652
1771
|
const workerUrl = `${widgetBuilderPreviewBaseUrl()}/preview/${widgetId}`;
|
|
1653
1772
|
console.log('[Chat→Screenshot] Capturing via hidden Worker preview', {
|
|
1654
1773
|
widgetId: widgetId.substring(0, 8),
|
|
@@ -1716,7 +1835,10 @@
|
|
|
1716
1835
|
await persistWidgetListThumbnail(widgetId, screenshot, isCurrent);
|
|
1717
1836
|
if (!isCurrent()) return;
|
|
1718
1837
|
capturedCanvas?.updateWidgetImage(widgetId, screenshot);
|
|
1719
|
-
const canvasJson =
|
|
1838
|
+
const canvasJson = measureSync(
|
|
1839
|
+
'canvas-serialize:shape-refresh-thumbnail',
|
|
1840
|
+
() => capturedCanvas?.saveCanvas?.() ?? null,
|
|
1841
|
+
);
|
|
1720
1842
|
if (canvasJson) void handleCanvasAutoSave(canvasJson);
|
|
1721
1843
|
} catch (error) {
|
|
1722
1844
|
console.warn('[WidgetShape] Refresh thumbnail capture failed', widgetId, error);
|
|
@@ -2479,8 +2601,14 @@
|
|
|
2479
2601
|
|
|
2480
2602
|
/** Explicit chat box height (px). null/0 = fill available; user drag sets a fixed px. */
|
|
2481
2603
|
const CHAT_DEFAULT_HEIGHT = 400;
|
|
2482
|
-
const CHAT_MIN_HEIGHT =
|
|
2604
|
+
const CHAT_MIN_HEIGHT = 260;
|
|
2483
2605
|
const CHAT_MAX_HEIGHT = 2000;
|
|
2606
|
+
/**
|
|
2607
|
+
* Floor applied to the chat box while it auto-fills the create step. A floor
|
|
2608
|
+
* taller than the available area makes the surrounding scroll container
|
|
2609
|
+
* scroll, which puts the chat input below the fold on open.
|
|
2610
|
+
*/
|
|
2611
|
+
const CHAT_AUTO_MIN_HEIGHT = 0;
|
|
2484
2612
|
|
|
2485
2613
|
function getChatContainerHeightForPanel(widgetId: string): number | null {
|
|
2486
2614
|
const stored = getPanelState(widgetId).chatContainerHeight;
|
|
@@ -2565,7 +2693,7 @@
|
|
|
2565
2693
|
if (!schema) return schema;
|
|
2566
2694
|
try {
|
|
2567
2695
|
const clone = structuredClone(schema);
|
|
2568
|
-
const groups = Array.isArray(clone) ? clone : clone.componentGroups;
|
|
2696
|
+
const groups = Array.isArray(clone) ? clone : (clone as { componentGroups?: unknown }).componentGroups;
|
|
2569
2697
|
if (!Array.isArray(groups) || groups.length === 0) return schema;
|
|
2570
2698
|
const allProps = groups.flatMap((group: { components?: Array<{ properties?: unknown[] }> }) =>
|
|
2571
2699
|
(group.components || []).flatMap((component) => component.properties || [])
|
|
@@ -3657,7 +3785,7 @@
|
|
|
3657
3785
|
!compositionRecordProjectId(selectedComposition as Record<string, unknown>);
|
|
3658
3786
|
const res = await compositionsClient.updateComposition({
|
|
3659
3787
|
id: selectedComposition.id,
|
|
3660
|
-
|
|
3788
|
+
updateComposition: {
|
|
3661
3789
|
content: buildCompositionContentPayload(),
|
|
3662
3790
|
...(needsProjectBackfill ? projectBackfill : {}),
|
|
3663
3791
|
}
|
|
@@ -3749,7 +3877,7 @@
|
|
|
3749
3877
|
try {
|
|
3750
3878
|
await compositionsClient.updateComposition({
|
|
3751
3879
|
id: compositionId,
|
|
3752
|
-
|
|
3880
|
+
updateComposition: { name: trimmed }
|
|
3753
3881
|
});
|
|
3754
3882
|
showToast('success', 'Composition renamed');
|
|
3755
3883
|
await loadCompositions();
|
|
@@ -3810,7 +3938,9 @@
|
|
|
3810
3938
|
};
|
|
3811
3939
|
await compositionsClient.updateComposition({
|
|
3812
3940
|
id: selectedComposition.id,
|
|
3813
|
-
|
|
3941
|
+
// The gateway accepts embedSettings (compositionSchemas update schema);
|
|
3942
|
+
// the generated UpdateComposition model predates that field.
|
|
3943
|
+
updateComposition: { embedSettings } as unknown as UpdateComposition
|
|
3814
3944
|
});
|
|
3815
3945
|
selectedComposition = { ...selectedComposition, embedSettings };
|
|
3816
3946
|
embedDimensionsChanged = false;
|
|
@@ -4006,11 +4136,11 @@
|
|
|
4006
4136
|
// SDK returns { success, models } directly (not nested under data)
|
|
4007
4137
|
if (response.success && Array.isArray(response.models)) {
|
|
4008
4138
|
// 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,
|
|
4139
|
+
availableModels = response.models.map((m: AgentModelsGet200ResponseModelsInner) => ({
|
|
4140
|
+
id: m.id ?? '',
|
|
4141
|
+
name: m.displayName ?? '',
|
|
4142
|
+
provider: (m.provider ?? '').charAt(0).toUpperCase() + (m.provider ?? '').slice(1), // Capitalize provider
|
|
4143
|
+
description: m.description ?? '',
|
|
4014
4144
|
supportsVision: m.supportsVision === true,
|
|
4015
4145
|
}));
|
|
4016
4146
|
|
|
@@ -4750,7 +4880,12 @@
|
|
|
4750
4880
|
|
|
4751
4881
|
// Step 3: Check operation status
|
|
4752
4882
|
try {
|
|
4753
|
-
const op = await operationsClient.operationsIdGet({ id: operationId })
|
|
4883
|
+
const op = (await operationsClient.operationsIdGet({ id: operationId })) as {
|
|
4884
|
+
status?: string;
|
|
4885
|
+
error?: string;
|
|
4886
|
+
progress?: number;
|
|
4887
|
+
metadata?: Record<string, unknown>;
|
|
4888
|
+
};
|
|
4754
4889
|
const status = (op?.status || '').toLowerCase();
|
|
4755
4890
|
|
|
4756
4891
|
if (status === 'completed') {
|
|
@@ -5698,22 +5833,29 @@
|
|
|
5698
5833
|
const res = await widgetsClient.getWidgets({ canvasId: canvasId ?? undefined });
|
|
5699
5834
|
|
|
5700
5835
|
// normalize error shape from apiRequest
|
|
5701
|
-
|
|
5836
|
+
const envelope = res as unknown as {
|
|
5837
|
+
error?: string;
|
|
5838
|
+
message?: string;
|
|
5839
|
+
data?: unknown;
|
|
5840
|
+
};
|
|
5841
|
+
if (envelope && (envelope.error || envelope.message)) {
|
|
5702
5842
|
// console.error('Widget Creator: loadWidgetsForCanvas: response not ok:', res);
|
|
5703
5843
|
widgets = [];
|
|
5704
5844
|
selectedWidgetId = null;
|
|
5705
5845
|
isLoadingWidgets = false;
|
|
5706
|
-
widgetsError =
|
|
5846
|
+
widgetsError = envelope.message || envelope.error || 'Failed to load widgets';
|
|
5707
5847
|
throw new Error(widgetsError);
|
|
5708
5848
|
}
|
|
5709
5849
|
|
|
5710
|
-
// if the response is ok, parse the data
|
|
5850
|
+
// if the response is ok, parse the data (bare list, {data: list} or {data: {data: list}})
|
|
5851
|
+
const envelopeData = envelope?.data;
|
|
5852
|
+
const envelopeNestedData = (envelopeData as { data?: unknown } | undefined)?.data;
|
|
5711
5853
|
const fetched = Array.isArray(res)
|
|
5712
|
-
? (res as WidgetSummary[])
|
|
5713
|
-
: Array.isArray(
|
|
5714
|
-
? (
|
|
5715
|
-
: Array.isArray(
|
|
5716
|
-
? (
|
|
5854
|
+
? (res as unknown as WidgetSummary[])
|
|
5855
|
+
: Array.isArray(envelopeNestedData)
|
|
5856
|
+
? (envelopeNestedData as unknown as WidgetSummary[])
|
|
5857
|
+
: Array.isArray(envelopeData)
|
|
5858
|
+
? (envelopeData as unknown as WidgetSummary[])
|
|
5717
5859
|
: [];
|
|
5718
5860
|
|
|
5719
5861
|
const previousById = new Map(
|
|
@@ -5810,9 +5952,13 @@
|
|
|
5810
5952
|
}
|
|
5811
5953
|
|
|
5812
5954
|
function getWidgetCreatedAtMs(widget: WidgetSummary): number {
|
|
5813
|
-
const
|
|
5814
|
-
if (!
|
|
5815
|
-
const ms = new Date(
|
|
5955
|
+
const rawCreated = widget.createdAt ?? widget.created_at;
|
|
5956
|
+
if (!rawCreated) return 0;
|
|
5957
|
+
const ms = new Date(
|
|
5958
|
+
typeof rawCreated === 'string' || typeof rawCreated === 'number' || rawCreated instanceof Date
|
|
5959
|
+
? rawCreated
|
|
5960
|
+
: String(rawCreated)
|
|
5961
|
+
).getTime();
|
|
5816
5962
|
return Number.isFinite(ms) ? ms : 0;
|
|
5817
5963
|
}
|
|
5818
5964
|
|
|
@@ -6218,10 +6364,10 @@
|
|
|
6218
6364
|
async function resolvePrimaryWidgetIdForCanvas(targetCanvasId: string): Promise<string | null> {
|
|
6219
6365
|
if (!canvasesClient || !targetCanvasId?.trim()) return null;
|
|
6220
6366
|
try {
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6367
|
+
const canvasRecord = await canvasesClient.getCanvas({ id: targetCanvasId });
|
|
6368
|
+
const record =
|
|
6369
|
+
(canvasRecord as { data?: Record<string, unknown> })?.data ??
|
|
6370
|
+
(canvasRecord as unknown as Record<string, unknown>);
|
|
6225
6371
|
const raw =
|
|
6226
6372
|
(record as { primaryWidgetId?: unknown })?.primaryWidgetId ??
|
|
6227
6373
|
(record as { primary_widget_id?: unknown })?.primary_widget_id ??
|
|
@@ -6703,7 +6849,12 @@
|
|
|
6703
6849
|
if (!widgetId || !agentClient) return null;
|
|
6704
6850
|
try {
|
|
6705
6851
|
const json = await agentClient.agentWidgetBuilderRepoHeadWidgetIdGet({ widgetId });
|
|
6706
|
-
const
|
|
6852
|
+
const repoHeadEnvelope = json as unknown as {
|
|
6853
|
+
data?: { repoHeadCommitSha?: string | null; repo_head_commit_sha?: string | null };
|
|
6854
|
+
repoHeadCommitSha?: string | null;
|
|
6855
|
+
repo_head_commit_sha?: string | null;
|
|
6856
|
+
};
|
|
6857
|
+
const data = repoHeadEnvelope?.data ?? repoHeadEnvelope;
|
|
6707
6858
|
const sha = data?.repoHeadCommitSha ?? data?.repo_head_commit_sha ?? null;
|
|
6708
6859
|
return typeof sha === 'string' && sha.length > 0 ? sha : null;
|
|
6709
6860
|
} catch (err) {
|
|
@@ -7021,13 +7172,14 @@
|
|
|
7021
7172
|
}
|
|
7022
7173
|
}
|
|
7023
7174
|
);
|
|
7024
|
-
|
|
7175
|
+
const createEnvelope = response as unknown as { error?: string; message?: string };
|
|
7176
|
+
if (!response || createEnvelope.error || createEnvelope.message) {
|
|
7025
7177
|
console.error('Widget create failed:', response);
|
|
7026
|
-
authStatus = `Create widget failed: ${
|
|
7178
|
+
authStatus = `Create widget failed: ${createEnvelope.message || createEnvelope.error || 'Unknown error'}`;
|
|
7027
7179
|
return;
|
|
7028
7180
|
}
|
|
7029
7181
|
// Ensure we have an id
|
|
7030
|
-
const newWidget = response.data
|
|
7182
|
+
const newWidget = asWidgetSummary((response as { data?: unknown }).data ?? response); // handle envelope or direct
|
|
7031
7183
|
if (newWidget?.id) {
|
|
7032
7184
|
widgets = [{ ...newWidget }, ...widgets];
|
|
7033
7185
|
selectWidget(newWidget);
|
|
@@ -7702,6 +7854,16 @@
|
|
|
7702
7854
|
canvasShapePreviewSyncTimers.set(widgetId, timer);
|
|
7703
7855
|
}
|
|
7704
7856
|
|
|
7857
|
+
/**
|
|
7858
|
+
* Serializing the canvas stringifies every shape's embedded image, so it is
|
|
7859
|
+
* megabytes of synchronous work on a busy canvas. The thumbnail paths run it
|
|
7860
|
+
* immediately after a capture, which is exactly where the freeze reports land,
|
|
7861
|
+
* so each call is measured and named.
|
|
7862
|
+
*/
|
|
7863
|
+
function serializeCanvasJson(reason: string): string | null {
|
|
7864
|
+
return measureSync(`canvas-serialize:${reason}`, () => canvasRef?.saveCanvas?.() ?? null);
|
|
7865
|
+
}
|
|
7866
|
+
|
|
7705
7867
|
async function handleRefreshScreenshot(widgetId: string, options: { quiet?: boolean } = {}) {
|
|
7706
7868
|
const quiet = !!options.quiet;
|
|
7707
7869
|
// console.log('[handleRefreshScreenshot] Capturing screenshot via postMessage for widget:', widgetId, quiet ? '(quiet)' : '');
|
|
@@ -7757,7 +7919,7 @@
|
|
|
7757
7919
|
const freshCapture = await handleChatScreenshot(widgetId, null, quiet);
|
|
7758
7920
|
if (freshCapture) {
|
|
7759
7921
|
canvasRef?.updateWidgetImage(widgetId, freshCapture);
|
|
7760
|
-
const freshJson =
|
|
7922
|
+
const freshJson = serializeCanvasJson('quiet-shape-thumbnail');
|
|
7761
7923
|
if (freshJson) void handleCanvasAutoSave(freshJson);
|
|
7762
7924
|
void persistWidgetListThumbnail(widgetId, freshCapture);
|
|
7763
7925
|
} else {
|
|
@@ -7816,7 +7978,7 @@
|
|
|
7816
7978
|
if (result.data && canvasRef) {
|
|
7817
7979
|
canvasRef.updateWidgetImage(widgetId, result.data);
|
|
7818
7980
|
setTimeout(() => {
|
|
7819
|
-
const json =
|
|
7981
|
+
const json = serializeCanvasJson('screenshot-refresh');
|
|
7820
7982
|
if (json) handleCanvasAutoSave(json);
|
|
7821
7983
|
}, 500);
|
|
7822
7984
|
void persistWidgetListThumbnail(widgetId, result.data);
|
|
@@ -7928,6 +8090,8 @@ Requirements:
|
|
|
7928
8090
|
attachmentIds?: string[];
|
|
7929
8091
|
/** Image URLs / data URLs captured before chat clearDraft. */
|
|
7930
8092
|
promptImagesOverride?: string[];
|
|
8093
|
+
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8094
|
+
skipIntentGate?: boolean;
|
|
7931
8095
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
7932
8096
|
persistFileAttachments?: Array<{
|
|
7933
8097
|
url?: string;
|
|
@@ -8017,6 +8181,7 @@ Requirements:
|
|
|
8017
8181
|
attachmentIds: options.attachmentIds,
|
|
8018
8182
|
promptImagesOverride: options.promptImagesOverride,
|
|
8019
8183
|
persistFileAttachments: options.persistFileAttachments,
|
|
8184
|
+
skipIntentGate: options.skipIntentGate,
|
|
8020
8185
|
});
|
|
8021
8186
|
}
|
|
8022
8187
|
|
|
@@ -8038,6 +8203,8 @@ Requirements:
|
|
|
8038
8203
|
forceRecreateRepo?: boolean;
|
|
8039
8204
|
attachmentIds?: string[];
|
|
8040
8205
|
promptImagesOverride?: string[];
|
|
8206
|
+
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8207
|
+
skipIntentGate?: boolean;
|
|
8041
8208
|
persistFileAttachments?: Array<{
|
|
8042
8209
|
url?: string;
|
|
8043
8210
|
file?: File;
|
|
@@ -8274,17 +8441,67 @@ Requirements:
|
|
|
8274
8441
|
modelId: coderModelForRequest || undefined,
|
|
8275
8442
|
plannerModelId: plannerModelIdForRequest,
|
|
8276
8443
|
widgetId: generationWidgetId,
|
|
8277
|
-
conversationId: activeConversationId ?? undefined,
|
|
8278
|
-
images: imagesToSend,
|
|
8444
|
+
conversationId: activeConversationId ?? undefined, images: imagesToSend,
|
|
8279
8445
|
attachmentIds: attachmentIdsToSend,
|
|
8280
8446
|
runtimeLogs: runtimeLogsForRequest,
|
|
8281
8447
|
compileFixOnly: options.compileFixOnly === true,
|
|
8282
|
-
|
|
8448
|
+
skipIntentGate: options.skipIntentGate === true,
|
|
8449
|
+
} as GenerateCodeRequest
|
|
8283
8450
|
});
|
|
8284
8451
|
// 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
8452
|
// console.log('Widget Creator: Code generation response:', response);
|
|
8286
8453
|
|
|
8287
|
-
|
|
8454
|
+
// --- Intent gate: the server answered instead of generating ---
|
|
8455
|
+
// A question got a meta answer, an ambiguous ask got a clarification
|
|
8456
|
+
// card, or a publish/settings ask was redirected. None of these spent
|
|
8457
|
+
// a generation slot. Anything unexpected here falls through to the
|
|
8458
|
+
// normal path (operation tracking), never blocks a real build.
|
|
8459
|
+
const gatedData = (response as any)?.data;
|
|
8460
|
+
if (gatedData?.gated === true && typeof gatedData.action === 'string' && gatedData.action !== 'generate') {
|
|
8461
|
+
const gatedAction = gatedData.action as 'answer' | 'redirect' | 'clarify';
|
|
8462
|
+
const gatedReply: string = gatedData.reply ?? '';
|
|
8463
|
+
const suggestions: string[] = Array.isArray(gatedData.suggestions) ? gatedData.suggestions : [];
|
|
8464
|
+
|
|
8465
|
+
clearActiveOperationId();
|
|
8466
|
+
patchCodegenUiState(generationWidgetId, {
|
|
8467
|
+
lastGenerationFailed: false,
|
|
8468
|
+
lastGenerationError: null,
|
|
8469
|
+
generateCodeStatus:
|
|
8470
|
+
gatedAction === 'answer'
|
|
8471
|
+
? 'Answered from widget details — no generation needed.'
|
|
8472
|
+
: gatedAction === 'clarify'
|
|
8473
|
+
? 'Need a bit more detail before generating.'
|
|
8474
|
+
: 'No generation needed — use the publish or settings control.',
|
|
8475
|
+
});
|
|
8476
|
+
|
|
8477
|
+
const chatMethods = getChatMethodsForWidget(generationWidgetId);
|
|
8478
|
+
if (gatedAction === 'clarify' && suggestions.length > 0 && chatMethods?.addAssistantMessage) {
|
|
8479
|
+
// Suggestions travel as message data, not flattened text — the chat
|
|
8480
|
+
// renders them as clickable chips that send the next user message.
|
|
8481
|
+
chatMethods.addAssistantMessage(gatedReply, false, suggestions);
|
|
8482
|
+
} else if (chatMethods?.addAssistantMessage) {
|
|
8483
|
+
chatMethods.addAssistantMessage(gatedReply);
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
showToast(
|
|
8487
|
+
gatedAction === 'answer' ? 'info' : 'warning',
|
|
8488
|
+
gatedAction === 'answer' ? 'Answered without generating' : 'No generation started',
|
|
8489
|
+
{
|
|
8490
|
+
description: gatedReply.slice(0, 140),
|
|
8491
|
+
duration: 8000,
|
|
8492
|
+
},
|
|
8493
|
+
);
|
|
8494
|
+
generatingWidgetIds = (() => {
|
|
8495
|
+
const { [generationWidgetId]: _gated, ...rest } = generatingWidgetIds;
|
|
8496
|
+
return rest;
|
|
8497
|
+
})();
|
|
8498
|
+
isGeneratingCode = !!(focusedPanelWidgetId && generatingWidgetIds[focusedPanelWidgetId]);
|
|
8499
|
+
setCrossTabGenerating(false, generationWidgetId);
|
|
8500
|
+
widgetAbortControllers.delete(generationWidgetId);
|
|
8501
|
+
return;
|
|
8502
|
+
}
|
|
8503
|
+
|
|
8504
|
+
if (response && (response as { error?: string }).error === 'TOKEN_EXPIRED') {
|
|
8288
8505
|
patchCodegenUiState(generationWidgetId, {
|
|
8289
8506
|
generateCodeStatus: '❌ Authentication expired. Please login again.',
|
|
8290
8507
|
lastGenerationFailed: true,
|
|
@@ -8297,7 +8514,8 @@ Requirements:
|
|
|
8297
8514
|
return;
|
|
8298
8515
|
}
|
|
8299
8516
|
|
|
8300
|
-
const
|
|
8517
|
+
const generateEnvelope = response as unknown as { error?: string; message?: string };
|
|
8518
|
+
const generateErrorMessage = generateEnvelope && (generateEnvelope.error || generateEnvelope.message);
|
|
8301
8519
|
if (generateErrorMessage) {
|
|
8302
8520
|
const normalizedMessage = normalizeErrorMessage(generateErrorMessage);
|
|
8303
8521
|
patchCodegenUiState(generationWidgetId, {
|
|
@@ -8313,8 +8531,10 @@ Requirements:
|
|
|
8313
8531
|
return;
|
|
8314
8532
|
}
|
|
8315
8533
|
|
|
8316
|
-
const
|
|
8317
|
-
|
|
8534
|
+
const generateData = ((response as { data?: unknown }).data ?? response) as {
|
|
8535
|
+
operationId?: string;
|
|
8536
|
+
};
|
|
8537
|
+
const operationId = generateData?.operationId;
|
|
8318
8538
|
|
|
8319
8539
|
// The Worker serves rebuilt code at the same preview URL, so the panel
|
|
8320
8540
|
// must know when the current generation started — hasLiveWorkerPreview()
|
|
@@ -8397,6 +8617,16 @@ Requirements:
|
|
|
8397
8617
|
|
|
8398
8618
|
const completedCommitSha = getOperationCommitId(op.metadata);
|
|
8399
8619
|
const completedPreviewBuildErr = getOperationPreviewBuildError(op.metadata);
|
|
8620
|
+
const completedCached = getOperationCached(op.metadata);
|
|
8621
|
+
const completedCachedFiles = getOperationCachedFiles(op.metadata);
|
|
8622
|
+
const cachedChatSummary = completedCached
|
|
8623
|
+
? `Already done — the widget is in the state you asked for.` +
|
|
8624
|
+
(completedCommitSha ? ` (Commit: ${completedCommitSha.substring(0, 7)})` : '') +
|
|
8625
|
+
'\nThat change was made earlier in this conversation, so no new generation was needed' +
|
|
8626
|
+
(completedCachedFiles.length > 0
|
|
8627
|
+
? ':\n' + completedCachedFiles.map((p) => `- ${p}`).join('\n')
|
|
8628
|
+
: '.')
|
|
8629
|
+
: null;
|
|
8400
8630
|
const generationPanelRef = getWidgetDetailsRef(generationWidgetId);
|
|
8401
8631
|
const isFocusedPanel = generationWidgetId === focusedPanelWidgetId;
|
|
8402
8632
|
const completedWidgetName = getWidgetById(generationWidgetId)?.name || 'widget';
|
|
@@ -8521,11 +8751,13 @@ Requirements:
|
|
|
8521
8751
|
});
|
|
8522
8752
|
|
|
8523
8753
|
const commitShort = completedCommitSha ? completedCommitSha.substring(0, 7) : '';
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8754
|
+
if (!completedCached) {
|
|
8755
|
+
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
8756
|
+
completedPreviewBuildErr
|
|
8757
|
+
? `Code saved${commitShort ? ` (${commitShort})` : ''} — building preview…`
|
|
8758
|
+
: `Code saved${commitShort ? ` (${commitShort})` : ''} — loading preview…`,
|
|
8759
|
+
);
|
|
8760
|
+
}
|
|
8529
8761
|
|
|
8530
8762
|
// console.log('Widget Creator: Code generation operation completed for widget', generationWidgetId.substring(0, 8), ':', op);
|
|
8531
8763
|
|
|
@@ -8561,13 +8793,17 @@ Requirements:
|
|
|
8561
8793
|
// Final chat message regardless of panel focus — without this, a
|
|
8562
8794
|
// closed/unfocused panel never receives the terminal update and the
|
|
8563
8795
|
// chat stays on the last progress line (e.g. stuck at 80%).
|
|
8796
|
+
// A cache-served completion gets the "already done" affordance:
|
|
8797
|
+
// it names the earlier change (and files) instead of claiming a
|
|
8798
|
+
// generation just happened.
|
|
8564
8799
|
panelChat?.addAssistantMessage(
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8800
|
+
cachedChatSummary ??
|
|
8801
|
+
(completedPreviewBuildErr
|
|
8802
|
+
? `Code saved${completedCommitSha ? ` (Commit: ${completedCommitSha.substring(0, 7)})` : ''}, but preview build failed:\n${completedPreviewBuildErr}\n\nRebuilding preview…`
|
|
8803
|
+
: `Code generation completed!${completedCommitSha ? ` Commit: ${completedCommitSha.substring(0, 7)}` : ''}\nPreview is loading…`),
|
|
8568
8804
|
true
|
|
8569
8805
|
);
|
|
8570
|
-
if (!completedPreviewBuildErr) {
|
|
8806
|
+
if (!completedPreviewBuildErr && !completedCached) {
|
|
8571
8807
|
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
8572
8808
|
`Code generation completed!${completedCommitSha ? ` Commit: ${completedCommitSha.substring(0, 7)}` : ''}\nPreview is loading…`,
|
|
8573
8809
|
);
|
|
@@ -8978,11 +9214,17 @@ Requirements:
|
|
|
8978
9214
|
publishingWidgetId = null;
|
|
8979
9215
|
setCrossTabPublishing(false, capturedWidgetId);
|
|
8980
9216
|
|
|
8981
|
-
const
|
|
8982
|
-
|
|
9217
|
+
const publishEnvelope = publishData as unknown as {
|
|
9218
|
+
data?: { version?: number; artifactUrl?: string | null; commitSha?: string | null };
|
|
9219
|
+
version?: number;
|
|
9220
|
+
artifactUrl?: string | null;
|
|
9221
|
+
commitSha?: string | null;
|
|
9222
|
+
};
|
|
9223
|
+
const version = publishEnvelope?.data?.version || publishEnvelope?.version || 1;
|
|
9224
|
+
const artifactUrl = publishEnvelope?.data?.artifactUrl || publishEnvelope?.artifactUrl || null;
|
|
8983
9225
|
const commitSha =
|
|
8984
|
-
|
|
8985
|
-
??
|
|
9226
|
+
publishEnvelope?.data?.commitSha
|
|
9227
|
+
?? publishEnvelope?.commitSha
|
|
8986
9228
|
?? commitShaForPublish
|
|
8987
9229
|
?? null;
|
|
8988
9230
|
applyPublishSuccessToWidgetState(capturedWidgetId, version, artifactUrl, commitSha);
|
|
@@ -9030,9 +9272,9 @@ Requirements:
|
|
|
9030
9272
|
|
|
9031
9273
|
|
|
9032
9274
|
// Token expiration check interval
|
|
9033
|
-
let tokenCheckInterval:
|
|
9275
|
+
let tokenCheckInterval: ReturnType<typeof setInterval>;
|
|
9034
9276
|
let tokenCheckIntervalTime: number = 10; // seconds — check frequently so logout is prompt
|
|
9035
|
-
let tokenDisplayInterval:
|
|
9277
|
+
let tokenDisplayInterval: ReturnType<typeof setInterval>;
|
|
9036
9278
|
|
|
9037
9279
|
// Reactive variables for real-time token status
|
|
9038
9280
|
let tokenTimeLeft: number = 0;
|
|
@@ -9115,6 +9357,9 @@ Requirements:
|
|
|
9115
9357
|
// Clean up cross-tab flags when tab is closed/refreshed. The handler lives at
|
|
9116
9358
|
// component scope so `onDestroy` can remove the same reference.
|
|
9117
9359
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
9360
|
+
// Keep a component-scope binding so onDestroy can remove the listener —
|
|
9361
|
+
// the arrow itself is scoped to this onMount callback.
|
|
9362
|
+
handleBeforeUnloadRef = handleBeforeUnload;
|
|
9118
9363
|
|
|
9119
9364
|
// Read canvas id from the route. The route path (`/canvas/<canvasId>`) is the
|
|
9120
9365
|
// AUTHORITATIVE canvas (site links, refresh, deep-links); localStorage is only a
|
|
@@ -9196,6 +9441,8 @@ Requirements:
|
|
|
9196
9441
|
};
|
|
9197
9442
|
});
|
|
9198
9443
|
|
|
9444
|
+
let handleBeforeUnloadRef: (() => void) | null = null;
|
|
9445
|
+
|
|
9199
9446
|
// Clean up interval on component destroy
|
|
9200
9447
|
onDestroy(() => {
|
|
9201
9448
|
clearCanvasLoadFailsafe();
|
|
@@ -9208,7 +9455,7 @@ Requirements:
|
|
|
9208
9455
|
}
|
|
9209
9456
|
// Cross-tab cleanup: remove flags so other tabs don't stay blocked
|
|
9210
9457
|
if (typeof window !== 'undefined') {
|
|
9211
|
-
window.removeEventListener('beforeunload',
|
|
9458
|
+
window.removeEventListener('beforeunload', handleBeforeUnloadRef);
|
|
9212
9459
|
window.removeEventListener('storage', handleCrossTabStorageEvent);
|
|
9213
9460
|
window.removeEventListener('widgetic:session-extended', handleSiteSessionExtended);
|
|
9214
9461
|
window.removeEventListener('click', handleCanvasDropdownOutsideClick);
|
|
@@ -9399,6 +9646,19 @@ Requirements:
|
|
|
9399
9646
|
return sanitizePreviewBuildError(raw);
|
|
9400
9647
|
}
|
|
9401
9648
|
|
|
9649
|
+
/** True when the operation completed from the semantic cache (no new generation ran). */
|
|
9650
|
+
function getOperationCached(metadata?: Record<string, unknown>): boolean {
|
|
9651
|
+
return metadata?.cached === true;
|
|
9652
|
+
}
|
|
9653
|
+
|
|
9654
|
+
/** Changed-file paths reported by a semantic-cache-served operation. */
|
|
9655
|
+
function getOperationCachedFiles(metadata?: Record<string, unknown>): string[] {
|
|
9656
|
+
if (!metadata) return [];
|
|
9657
|
+
const raw = metadata.generatedFiles;
|
|
9658
|
+
if (!Array.isArray(raw)) return [];
|
|
9659
|
+
return raw.filter((p): p is string => typeof p === 'string');
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9402
9662
|
function getChatMethodsForWidget(widgetId: string | null | undefined) {
|
|
9403
9663
|
if (!widgetId) return chatMethods ?? null;
|
|
9404
9664
|
const instanceKey = panelInstanceKeyByWidgetId[widgetId] ?? widgetId;
|
|
@@ -9788,8 +10048,7 @@ Requirements:
|
|
|
9788
10048
|
codeGenerationPrompt: storedPrompt,
|
|
9789
10049
|
promptImages: [],
|
|
9790
10050
|
lastCommitId: hydratedHead,
|
|
9791
|
-
lastPublishedVersion: publishedVersion,
|
|
9792
|
-
selectedWidgetJsPath: widget?.jsPath ?? null,
|
|
10051
|
+
lastPublishedVersion: publishedVersion, selectedWidgetJsPath: (widget as { jsPath?: string | null } | undefined)?.jsPath ?? null,
|
|
9793
10052
|
currentRepositoryId: normalizeRepositoryId(widget),
|
|
9794
10053
|
widgetDetailsStep: resolveCreatorWorkflowStep(publishedVersion !== null ? 'edit' : 'create'),
|
|
9795
10054
|
generateCodeStatus: null,
|
|
@@ -10047,7 +10306,7 @@ Requirements:
|
|
|
10047
10306
|
const widget = getWidgetById(widgetId);
|
|
10048
10307
|
const version = ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(widget);
|
|
10049
10308
|
if (version == null) return null;
|
|
10050
|
-
const jsPath = ps.selectedWidgetJsPath ?? widget?.jsPath ?? null;
|
|
10309
|
+
const jsPath = ps.selectedWidgetJsPath ?? (widget as { jsPath?: string | null } | undefined)?.jsPath ?? null;
|
|
10051
10310
|
return resolvePublishedWidgetUrl(widgetId, version, jsPath);
|
|
10052
10311
|
}
|
|
10053
10312
|
|
|
@@ -10071,6 +10330,8 @@ Requirements:
|
|
|
10071
10330
|
// Historical previews are never interchangeable with the shared HEAD preview.
|
|
10072
10331
|
let shapeVersionPreviewByWidgetId: Record<string, {
|
|
10073
10332
|
versionId: string; requestId: number; loading: boolean; error: string | null; url: string | null;
|
|
10333
|
+
/** Commit the selected version was built from — drives the chat's Head-at-commit badge. */
|
|
10334
|
+
commitSha?: string | null;
|
|
10074
10335
|
errorDetails?: Awaited<ReturnType<typeof parseVersionPreviewError>> | null;
|
|
10075
10336
|
}> = {};
|
|
10076
10337
|
let shapeVersionPreviewRequestId = 0;
|
|
@@ -10393,7 +10654,10 @@ Requirements:
|
|
|
10393
10654
|
}
|
|
10394
10655
|
shapeVersionPreviewByWidgetId = {
|
|
10395
10656
|
...shapeVersionPreviewByWidgetId,
|
|
10396
|
-
[widgetId]: {
|
|
10657
|
+
[widgetId]: {
|
|
10658
|
+
versionId, requestId, loading: false, error: null, url: previewUrl,
|
|
10659
|
+
commitSha: normalizeCommitSha(commitSha),
|
|
10660
|
+
},
|
|
10397
10661
|
};
|
|
10398
10662
|
syncWidgetShapeLivePreviews();
|
|
10399
10663
|
panelDisplayRevision += 1;
|
|
@@ -10432,6 +10696,14 @@ Requirements:
|
|
|
10432
10696
|
if (source.startsWith('v:')) {
|
|
10433
10697
|
const parsed = parseInt(source.slice(2), 10);
|
|
10434
10698
|
version = Number.isFinite(parsed) ? parsed : null;
|
|
10699
|
+
// A draft version has no publish-history row to read a commit from, so
|
|
10700
|
+
// without the sha the version preview reported we fell back to repo HEAD
|
|
10701
|
+
// and the badge stayed on the newest turn instead of moving to the
|
|
10702
|
+
// selected version's own turn.
|
|
10703
|
+
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10704
|
+
if (previewState?.versionId === source && previewState.commitSha) {
|
|
10705
|
+
return normalizeCommitSha(previewState.commitSha);
|
|
10706
|
+
}
|
|
10435
10707
|
} else {
|
|
10436
10708
|
version = ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(getWidgetById(widgetId));
|
|
10437
10709
|
}
|
|
@@ -10450,28 +10722,84 @@ Requirements:
|
|
|
10450
10722
|
return normalizeCommitSha(ps.lastCommitId);
|
|
10451
10723
|
}
|
|
10452
10724
|
|
|
10725
|
+
/**
|
|
10726
|
+
* Head commit per open panel, for the chat's Head-at-commit badge.
|
|
10727
|
+
*
|
|
10728
|
+
* The resolver reads the selected version and its preview state out of records
|
|
10729
|
+
* the compiler cannot follow through the call, so calling it straight from the
|
|
10730
|
+
* template never re-ran on a version switch: the chat kept the previous
|
|
10731
|
+
* commit and the badge stayed on the newest turn. Naming the records here is
|
|
10732
|
+
* what makes the switch reach the chat.
|
|
10733
|
+
*/
|
|
10734
|
+
let headCommitShaByWidgetId: Record<string, string | null> = {};
|
|
10735
|
+
/**
|
|
10736
|
+
* Short commit sha → "Version N" per panel, so a chat turn reads as the version
|
|
10737
|
+
* it produced. A hash is meaningless to a non-developer, so it stays behind
|
|
10738
|
+
* debug mode and this label is what the turn shows.
|
|
10739
|
+
*/
|
|
10740
|
+
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10741
|
+
$: {
|
|
10742
|
+
void shapePreviewSourceByWidgetId;
|
|
10743
|
+
void shapeVersionPreviewByWidgetId;
|
|
10744
|
+
void panelDisplayRevision;
|
|
10745
|
+
const nextHeads: Record<string, string | null> = {};
|
|
10746
|
+
const nextLabels: Record<string, Record<string, string>> = {};
|
|
10747
|
+
for (const widgetId of openWidgetPanelIds) {
|
|
10748
|
+
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
10749
|
+
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
10750
|
+
}
|
|
10751
|
+
headCommitShaByWidgetId = nextHeads;
|
|
10752
|
+
versionLabelsByWidgetId = nextLabels;
|
|
10753
|
+
}
|
|
10754
|
+
|
|
10755
|
+
/** Commit shas are keyed on 7 chars — that is what the chat summaries carry. */
|
|
10756
|
+
function versionLabelKey(sha: string | null | undefined): string | null {
|
|
10757
|
+
const normalized = normalizeCommitSha(sha);
|
|
10758
|
+
return normalized ? normalized.slice(0, 7) : null;
|
|
10759
|
+
}
|
|
10760
|
+
|
|
10761
|
+
function buildVersionLabelsForPanel(widgetId: string): Record<string, string> {
|
|
10762
|
+
const labels: Record<string, string> = {};
|
|
10763
|
+
const ps = getPanelDisplayState(widgetId);
|
|
10764
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10765
|
+
for (const record of history || []) {
|
|
10766
|
+
const key = versionLabelKey(record?.commitSha ?? record?.commit_sha);
|
|
10767
|
+
if (key && record?.version != null) labels[key] = `Version ${record.version}`;
|
|
10768
|
+
}
|
|
10769
|
+
// The version the user is previewing right now may have been fetched rather
|
|
10770
|
+
// than read from history, and it is the one whose label matters most.
|
|
10771
|
+
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10772
|
+
const previewKey = versionLabelKey(previewState?.commitSha);
|
|
10773
|
+
if (previewKey && previewState?.versionId?.startsWith('v:')) {
|
|
10774
|
+
labels[previewKey] = `Version ${previewState.versionId.slice(2)}`;
|
|
10775
|
+
}
|
|
10776
|
+
return labels;
|
|
10777
|
+
}
|
|
10778
|
+
|
|
10453
10779
|
function syncWidgetShapeLivePreviews(): void {
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
const linkedIds = canvasRef?.getLinkedWidgetIds?.() ?? new Set<string>();
|
|
10467
|
-
for (const widgetId of linkedIds) {
|
|
10468
|
-
if (!liveWidgetIds.has(widgetId)) {
|
|
10469
|
-
canvasRef?.setWidgetShapeLivePreviewHiddenImage?.(widgetId, false);
|
|
10780
|
+
measureSync('sync-widget-shape-previews', () => {
|
|
10781
|
+
try {
|
|
10782
|
+
canvasSelectedWidgetShapeId = canvasRef?.getSelectedWidgetShapeId?.() ?? null;
|
|
10783
|
+
widgetShapeLivePreviewsRef?.sync?.();
|
|
10784
|
+
// Hide Fabric screenshots under live iframes (avoids ghost bleed in padding).
|
|
10785
|
+
const rects = canvasRef?.listWidgetShapeScreenRects?.() ?? [];
|
|
10786
|
+
const liveWidgetIds = new Set<string>();
|
|
10787
|
+
for (const rect of rects) {
|
|
10788
|
+
if (!rect?.widgetId) continue;
|
|
10789
|
+
if (!resolveWidgetShapeLivePreviewUrl(rect.widgetId)) continue;
|
|
10790
|
+
liveWidgetIds.add(rect.widgetId);
|
|
10791
|
+
canvasRef?.setWidgetShapeLivePreviewHiddenImage?.(rect.widgetId, true);
|
|
10470
10792
|
}
|
|
10793
|
+
const linkedIds = canvasRef?.getLinkedWidgetIds?.() ?? new Set<string>();
|
|
10794
|
+
for (const widgetId of linkedIds) {
|
|
10795
|
+
if (!liveWidgetIds.has(widgetId)) {
|
|
10796
|
+
canvasRef?.setWidgetShapeLivePreviewHiddenImage?.(widgetId, false);
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
} catch (err) {
|
|
10800
|
+
console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
|
|
10471
10801
|
}
|
|
10472
|
-
}
|
|
10473
|
-
console.warn('[CreatorApp] WidgetShape live preview sync failed', err);
|
|
10474
|
-
}
|
|
10802
|
+
});
|
|
10475
10803
|
}
|
|
10476
10804
|
|
|
10477
10805
|
function getPanelEmbedPreviewUrl(widgetId: string): string | null {
|
|
@@ -11917,13 +12245,12 @@ async function loadPublishHistoryForPanel(widgetId: string, opts: { silent?: boo
|
|
|
11917
12245
|
});
|
|
11918
12246
|
return;
|
|
11919
12247
|
}
|
|
11920
|
-
try {
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
: []);
|
|
12248
|
+
try { const historyRes = await widgetsClient.getWidgetPublishHistory({ widgetId });
|
|
12249
|
+
const historyEnvelope = historyRes as unknown as { data?: unknown };
|
|
12250
|
+
// SDK types the envelope ({ success, data }); a response-transform middleware
|
|
12251
|
+
// may unwrap it, so a bare array stays tolerated.
|
|
12252
|
+
const historyData = Array.isArray(historyRes) ? historyRes : historyEnvelope?.data;
|
|
12253
|
+
const history = (Array.isArray(historyData) ? historyData : []) as PublishHistoryRecord[];
|
|
11927
12254
|
patchPanelSnapshot(widgetId, {
|
|
11928
12255
|
publishHistory: history,
|
|
11929
12256
|
publishHistoryResolved: true,
|
|
@@ -12223,8 +12550,12 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12223
12550
|
// console.log('Signup response:', signupResponse);
|
|
12224
12551
|
|
|
12225
12552
|
// show error signup status
|
|
12226
|
-
|
|
12227
|
-
|
|
12553
|
+
// signupUser is typed Promise<void>; it resolves to undefined on the
|
|
12554
|
+
// success path, so a falsy response IS the success signal. Error details
|
|
12555
|
+
// arrive via the thrown ResponseError caught below.
|
|
12556
|
+
const signupEnvelope = signupResponse as unknown as { user?: unknown; message?: string } | undefined;
|
|
12557
|
+
if (!signupEnvelope || !signupEnvelope.user) {
|
|
12558
|
+
authStatus = signupEnvelope?.message ?? 'Signup failed.';
|
|
12228
12559
|
return;
|
|
12229
12560
|
}
|
|
12230
12561
|
|
|
@@ -12246,7 +12577,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12246
12577
|
// console.log('Login response:', loginResponse);
|
|
12247
12578
|
|
|
12248
12579
|
// show success login status
|
|
12249
|
-
|
|
12580
|
+
// The generated loginUser return type is mis-derived (a billing endpoint's
|
|
12581
|
+
// shape); the real /auth/login payload is a session envelope. Cast honestly.
|
|
12582
|
+
const payload = (loginResponse as unknown as { data?: { accessToken?: string; user?: Record<string, unknown> } })?.data;
|
|
12250
12583
|
if (payload?.accessToken) {
|
|
12251
12584
|
currentUserToken = payload.accessToken;
|
|
12252
12585
|
|
|
@@ -12312,7 +12645,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12312
12645
|
// console.log('WidgetCreator.updateTokenStatus: Decoded token:', decoded);
|
|
12313
12646
|
|
|
12314
12647
|
// if the decoded token has an expiration field, update the token status
|
|
12315
|
-
if (decoded && decoded.exp) {
|
|
12648
|
+
if (decoded && typeof decoded.exp === 'number') {
|
|
12316
12649
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
12317
12650
|
const expUnix = decoded.exp;
|
|
12318
12651
|
tokenTimeLeft = expUnix - currentTime;
|
|
@@ -12486,17 +12819,20 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12486
12819
|
// console.log('Available fields:', Object.keys(decodedToken));
|
|
12487
12820
|
|
|
12488
12821
|
// Extract user info from Supabase JWT structure
|
|
12822
|
+
// user_metadata is an untyped JWT claim blob — index it explicitly so the
|
|
12823
|
+
// accesses below are checked reads, not property probes on `unknown`.
|
|
12824
|
+
const userMetadata = (decodedToken.user_metadata ?? {}) as Record<string, unknown>;
|
|
12489
12825
|
user = {
|
|
12490
12826
|
id: decodedToken.sub || decodedToken.user_id || decodedToken.id || 'token-user',
|
|
12491
|
-
email:
|
|
12827
|
+
email: userMetadata.email || 'user@token-auth.com',
|
|
12492
12828
|
name:
|
|
12493
|
-
|
|
12494
|
-
|
|
12829
|
+
userMetadata.name ||
|
|
12830
|
+
userMetadata.full_name ||
|
|
12495
12831
|
decodedToken.name ||
|
|
12496
12832
|
decodedToken.full_name ||
|
|
12497
12833
|
'Token User',
|
|
12498
12834
|
role: decodedToken.role || 'user',
|
|
12499
|
-
avatar:
|
|
12835
|
+
avatar: userMetadata.avatar_url || userMetadata.picture
|
|
12500
12836
|
};
|
|
12501
12837
|
|
|
12502
12838
|
// Show more detailed status with actual extracted info
|
|
@@ -12743,10 +13079,10 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12743
13079
|
}
|
|
12744
13080
|
|
|
12745
13081
|
const sourceHasCode =
|
|
12746
|
-
!!normalizeRepositoryId(newWidget)
|
|
13082
|
+
!!normalizeRepositoryId(asWidgetSummary(newWidget))
|
|
12747
13083
|
|| !!((newWidget as { jsPath?: string | null; js_path?: string | null }).jsPath
|
|
12748
13084
|
?? (newWidget as { js_path?: string | null }).js_path)
|
|
12749
|
-
|| resolvePublishedVersionFromWidget(newWidget) !== null
|
|
13085
|
+
|| resolvePublishedVersionFromWidget(asWidgetSummary(newWidget)) !== null
|
|
12750
13086
|
|| !!sourceCommit;
|
|
12751
13087
|
|
|
12752
13088
|
if (!canvasPrimaryWidgetId) {
|
|
@@ -12754,7 +13090,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12754
13090
|
}
|
|
12755
13091
|
|
|
12756
13092
|
// Duplicate stays a sibling on the canvas — do not make it the primary widget.
|
|
12757
|
-
await selectWidget(newWidget, { skipPlaceShape: true });
|
|
13093
|
+
await selectWidget(asWidgetSummary(newWidget), { skipPlaceShape: true });
|
|
12758
13094
|
notifyHostWidgetName(getWidgetById(canvasPrimaryWidgetId) ?? getWidgetById(sourceWidgetId));
|
|
12759
13095
|
|
|
12760
13096
|
showToast('success', 'Widget duplicated', {
|
|
@@ -12784,11 +13120,11 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12784
13120
|
try {
|
|
12785
13121
|
await handleWidgetDataChanged(newWidget.id);
|
|
12786
13122
|
const freshWidget = getWidgetById(newWidget.id);
|
|
12787
|
-
|
|
13123
|
+
let repoId = freshWidget ? normalizeRepositoryId(freshWidget) : normalizeRepositoryId(asWidgetSummary(newWidget));
|
|
12788
13124
|
|
|
12789
13125
|
if (!repoId) {
|
|
12790
13126
|
// console.log('[WidgetCreator] Duplicate has no repo after re-fetch, creating one...');
|
|
12791
|
-
repoId = await ensureWidgetRepository(freshWidget || newWidget, { silent: true });
|
|
13127
|
+
repoId = await ensureWidgetRepository(freshWidget || asWidgetSummary(newWidget), { silent: true });
|
|
12792
13128
|
if (repoId) {
|
|
12793
13129
|
widgets = widgets.map((w) => w.id === newWidget.id ? { ...w, repositoryId: repoId } : w);
|
|
12794
13130
|
// console.log('[WidgetCreator] Repository created for duplicate:', repoId);
|
|
@@ -13249,14 +13585,20 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13249
13585
|
}
|
|
13250
13586
|
|
|
13251
13587
|
let objectCount = 0;
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
|
|
13256
|
-
|
|
13257
|
-
|
|
13258
|
-
|
|
13259
|
-
|
|
13588
|
+
// Canvas JSON carries the shapes' embedded images, so it reaches megabytes on
|
|
13589
|
+
// a busy canvas and every parse of it blocks the main thread. Parse once and
|
|
13590
|
+
// reuse it for the API payload below.
|
|
13591
|
+
let parsedCanvas: any = null;
|
|
13592
|
+
measureSync('canvas-autosave:parse', () => {
|
|
13593
|
+
try {
|
|
13594
|
+
parsedCanvas = JSON.parse(json);
|
|
13595
|
+
// saveCanvas() wraps as { version, savedAt, viewport, canvas: { objects: [...] } }
|
|
13596
|
+
const objects = parsedCanvas?.canvas?.objects ?? parsedCanvas?.objects ?? [];
|
|
13597
|
+
objectCount = Array.isArray(objects) ? objects.length : 0;
|
|
13598
|
+
} catch {
|
|
13599
|
+
// ignore parse errors for debug
|
|
13600
|
+
}
|
|
13601
|
+
});
|
|
13260
13602
|
|
|
13261
13603
|
if (isUnmountSave) {
|
|
13262
13604
|
// console.log(`[CanvasDebug] Unmount save: objectCount=${objectCount}, lastSavedCount=${lastSavedCanvasObjectCount}`);
|
|
@@ -13289,11 +13631,18 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13289
13631
|
|
|
13290
13632
|
// Save to durable L1 fallback (always, even with 0 objects for legitimate clears)
|
|
13291
13633
|
saveCanvasState(canvasId, json);
|
|
13292
|
-
// Keep the legacy localStorage copy in sync for downgrade safety.
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13634
|
+
// Keep the legacy localStorage copy in sync for downgrade safety. `setItem` is
|
|
13635
|
+
// a synchronous disk write of the whole canvas, so during a generation it is
|
|
13636
|
+
// deferred to the end: the durable IndexedDB copy above is already written,
|
|
13637
|
+
// and the mirror exists only for a downgrade.
|
|
13638
|
+
if (!isAnyWidgetGenerating() || force || isUnmountSave) {
|
|
13639
|
+
measureSync('canvas-autosave:localStorage-mirror', () => {
|
|
13640
|
+
try {
|
|
13641
|
+
localStorage.setItem(getCanvasStorageKey(), json);
|
|
13642
|
+
} catch (err) {
|
|
13643
|
+
console.warn('[WidgetCreator] Failed to save canvas to localStorage:', err);
|
|
13644
|
+
}
|
|
13645
|
+
});
|
|
13297
13646
|
}
|
|
13298
13647
|
|
|
13299
13648
|
// Save to API via SDK with sequence race protection
|
|
@@ -13303,7 +13652,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13303
13652
|
await canvasesClient.updateCanvas({
|
|
13304
13653
|
id: canvasId,
|
|
13305
13654
|
updateCanvasRequest: {
|
|
13306
|
-
canvasContent: JSON.parse(json)
|
|
13655
|
+
canvasContent: parsedCanvas ?? JSON.parse(json)
|
|
13307
13656
|
}
|
|
13308
13657
|
});
|
|
13309
13658
|
if (currentSeq < lastResolvedAutosaveSequence) {
|
|
@@ -13513,13 +13862,14 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13513
13862
|
{ headers: { 'Idempotency-Key': idempotencyKey } }
|
|
13514
13863
|
);
|
|
13515
13864
|
|
|
13516
|
-
|
|
13865
|
+
const createEnvelope = response as unknown as { error?: string; message?: string };
|
|
13866
|
+
if (!response || createEnvelope.error || createEnvelope.message) {
|
|
13517
13867
|
console.error('[WidgetCreator] Widget create on first send failed:', response);
|
|
13518
13868
|
showToast('error', 'Failed to create widget from design');
|
|
13519
13869
|
return null;
|
|
13520
13870
|
}
|
|
13521
13871
|
|
|
13522
|
-
const newWidget = response.data
|
|
13872
|
+
const newWidget = asWidgetSummary((response as { data?: unknown }).data ?? response);
|
|
13523
13873
|
if (!newWidget?.id) {
|
|
13524
13874
|
showToast('warning', 'Widget created but no ID returned');
|
|
13525
13875
|
return null;
|
|
@@ -13790,7 +14140,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13790
14140
|
}
|
|
13791
14141
|
|
|
13792
14142
|
async function ensureWidgetRepository(
|
|
13793
|
-
widget: WidgetSummary,
|
|
14143
|
+
widget: WidgetSummary | null | undefined,
|
|
13794
14144
|
options: { forceRecreate?: boolean; silent?: boolean } = {}
|
|
13795
14145
|
): Promise<string | null> {
|
|
13796
14146
|
const { forceRecreate = false, silent = false } = options;
|
|
@@ -13915,8 +14265,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13915
14265
|
});
|
|
13916
14266
|
// console.log('Widget Creator: Repository creation response:', response);
|
|
13917
14267
|
|
|
13918
|
-
const repoData = response
|
|
13919
|
-
const repoId = repoData?.id || repoData?.repoId || repoData?.repo_id || null;
|
|
14268
|
+
const repoData = ((response as unknown as { data?: Record<string, unknown> }).data ?? response) as Record<string, unknown>;
|
|
14269
|
+
const repoId = (repoData?.id || repoData?.repoId || repoData?.repo_id || null) as string | null;
|
|
13920
14270
|
|
|
13921
14271
|
if (!repoId) {
|
|
13922
14272
|
showToast('error', 'Repository creation failed', {
|
|
@@ -14003,7 +14353,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14003
14353
|
const response = await widgetsClient.deleteWidget(
|
|
14004
14354
|
{
|
|
14005
14355
|
id: adminDeleteWidget.id,
|
|
14006
|
-
reason: 'admin_delete'
|
|
14356
|
+
deleteWidgetRequest: { reason: 'admin_delete' }
|
|
14007
14357
|
},
|
|
14008
14358
|
{
|
|
14009
14359
|
headers: {
|
|
@@ -14012,10 +14362,14 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14012
14362
|
}
|
|
14013
14363
|
);
|
|
14014
14364
|
|
|
14015
|
-
|
|
14365
|
+
// deleteWidget resolves void; only a thrown ResponseError (caught below)
|
|
14366
|
+
// carries a failure. The defensive envelope check is kept for the response-
|
|
14367
|
+
// transform middleware, but must go through an honest cast.
|
|
14368
|
+
const deleteEnvelope = response as unknown as { error?: string; message?: string } | undefined;
|
|
14369
|
+
if (deleteEnvelope && (deleteEnvelope.error || deleteEnvelope.message)) {
|
|
14016
14370
|
console.error('Admin widget deletion failed:', response);
|
|
14017
14371
|
showToast('error', 'Delete failed', {
|
|
14018
|
-
description:
|
|
14372
|
+
description: deleteEnvelope?.message || deleteEnvelope?.error || 'Unknown error'
|
|
14019
14373
|
});
|
|
14020
14374
|
adminDeleteLoading = false;
|
|
14021
14375
|
return;
|
|
@@ -15218,11 +15572,11 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15218
15572
|
class="code-generation-chat-host w-full min-h-0 {chatUserHeight == null ? 'flex-1' : 'shrink-0'}"
|
|
15219
15573
|
data-widget-chat-height={panelWidgetId}
|
|
15220
15574
|
style={chatUserHeight == null
|
|
15221
|
-
? `flex: 1 1 auto; min-height: ${
|
|
15575
|
+
? `flex: 1 1 auto; min-height: ${CHAT_AUTO_MIN_HEIGHT}px;`
|
|
15222
15576
|
: `flex: 0 0 ${chatUserHeight}px; min-height: ${CHAT_MIN_HEIGHT}px; height: ${chatUserHeight}px;`}
|
|
15223
15577
|
>
|
|
15224
15578
|
<Chat class="code-generation-chat w-full h-full rounded-none border-0"
|
|
15225
|
-
style="height: 100%; min-height: {CHAT_MIN_HEIGHT}px;"
|
|
15579
|
+
style="height: 100%; min-height: {chatUserHeight == null ? CHAT_AUTO_MIN_HEIGHT : CHAT_MIN_HEIGHT}px;"
|
|
15226
15580
|
resizeExpandsContainer={true}
|
|
15227
15581
|
onMessagesResize={(deltaPx) => handleChatMessagesResize(panelWidgetId, deltaPx)}
|
|
15228
15582
|
showLogs={debugMode}
|
|
@@ -15249,7 +15603,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15249
15603
|
autoCodeDetection: false,
|
|
15250
15604
|
}
|
|
15251
15605
|
}}
|
|
15252
|
-
widgetHeadCommitSha={
|
|
15606
|
+
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15607
|
+
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15253
15608
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15254
15609
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
15255
15610
|
onUploadChatFile={handleChatUploadFile}
|
|
@@ -15461,7 +15816,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15461
15816
|
{/if}
|
|
15462
15817
|
|
|
15463
15818
|
<!-- ─── Schema loading / error / PropsEditor ─── -->
|
|
15464
|
-
{@const panelSchemasLoading = (panelDisplayRevision, ps.compositionSchemasLoading)}
|
|
15819
|
+
{@const panelSchemasLoading = rev(panelDisplayRevision, ps.compositionSchemasLoading)}
|
|
15465
15820
|
{@const panelSchemaLoadLabel = getPanelSchemaLoadLabel(panelWidgetId)}
|
|
15466
15821
|
{#if panelSchemasLoading}
|
|
15467
15822
|
<div class="composition-editor-loading flex flex-col items-center justify-center py-8">
|
|
@@ -15766,7 +16121,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15766
16121
|
{/if}
|
|
15767
16122
|
|
|
15768
16123
|
<!-- Delete Widget Shape Confirmation Dialog -->
|
|
15769
|
-
|
|
16124
|
+
<!-- Dialog.Root (bits-ui AlertDialogRoot) exposes no class prop — styling hooks
|
|
16125
|
+
live on Content/Header below. -->
|
|
16126
|
+
<Dialog.Root bind:open={deleteDialogOpen} onOpenChange={(open) => { if (!open) handleDeleteDialogResponse('cancel'); }}>
|
|
15770
16127
|
<Dialog.Content class="confirm-delete-dialog-content max-w-md bg-white z-9999">
|
|
15771
16128
|
<Dialog.Header class="confirm-delete-dialog-header">
|
|
15772
16129
|
<Dialog.Title>Delete Widget Shape</Dialog.Title>
|
|
@@ -15808,7 +16165,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15808
16165
|
</Dialog.Root>
|
|
15809
16166
|
|
|
15810
16167
|
<!-- Delete Canvas Confirmation Dialog -->
|
|
15811
|
-
<Dialog.Root
|
|
16168
|
+
<Dialog.Root bind:open={showDeleteCanvasDialog} onOpenChange={(open) => { if (!open) { showDeleteCanvasDialog = false; deleteCanvasTarget = null; } }}>
|
|
15812
16169
|
<Dialog.Content class="confirm-delete-canvas-dialog-content max-w-sm bg-white z-9999">
|
|
15813
16170
|
<Dialog.Header class="confirm-delete-canvas-dialog-header">
|
|
15814
16171
|
<Dialog.Title>Delete Canvas</Dialog.Title>
|
|
@@ -15868,7 +16225,6 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15868
16225
|
|
|
15869
16226
|
<!-- Confirm Generation Dialog (shown on convert-to-widget before auto-generating) -->
|
|
15870
16227
|
<Dialog.Root
|
|
15871
|
-
class="confirm-generate-dialog-root"
|
|
15872
16228
|
open={confirmGenerateDialogOpen}
|
|
15873
16229
|
onOpenChange={(open) => {
|
|
15874
16230
|
confirmGenerateDialogOpen = open;
|