@widgetic/creator 0.3.66 → 0.3.68
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 +52 -98
- package/dist/CreatorApp.svelte +512 -147
- package/dist/components/WidgetDetails.svelte +32 -3
- package/dist/components/WidgetDetails.svelte.d.ts +2 -0
- package/dist/components/WidgetShapeLivePreviews.svelte +133 -46
- package/dist/components/WidgetShapeLivePreviews.svelte.d.ts +2 -0
- package/dist/stores/websocketStore.d.ts +1 -1
- package/dist/stores/websocketStore.js +1 -1
- package/dist/utils/creatorDraftStorage.d.ts +5 -0
- package/dist/utils/creatorDraftStorage.js +41 -4
- package/dist/utils/freezeWatchdog.js +38 -7
- package/package.json +4 -5
package/dist/CreatorApp.svelte
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
} from '@widgetic/api-sdk';
|
|
30
30
|
import type {
|
|
31
31
|
Middleware,
|
|
32
|
-
UpdateComposition,
|
|
33
32
|
GenerateCodeRequest,
|
|
34
33
|
AgentModelsGet200ResponseModelsInner
|
|
35
34
|
} from '@widgetic/api-sdk';
|
|
@@ -500,6 +499,7 @@
|
|
|
500
499
|
|
|
501
500
|
// Chat module for code generation conversation UI
|
|
502
501
|
import { Chat, ContextType } from '@widgetic/chat';
|
|
502
|
+
import type { ClarificationSuggestion } from '@widgetic/chat';
|
|
503
503
|
|
|
504
504
|
// Eagerly initiate canvas chunk fetching at script evaluation (parallel with DOM parsing)
|
|
505
505
|
const canvasModulePromise = typeof window !== 'undefined' ? import('@widgetic/canvas') : null;
|
|
@@ -529,10 +529,13 @@
|
|
|
529
529
|
onMount(async () => {
|
|
530
530
|
if (canvasModulePromise) {
|
|
531
531
|
const canvasModule = await canvasModulePromise;
|
|
532
|
+
// Installed before the canvas mounts so the first save already externalizes
|
|
533
|
+
installCanvasThumbnailStore(canvasModule);
|
|
532
534
|
WidgeticCanvas = canvasModule.Canvas;
|
|
533
535
|
}
|
|
534
536
|
installLongTaskFreezeReporter();
|
|
535
537
|
installFreezeWatchdog();
|
|
538
|
+
console.info('[FreezeWatchdog] Freeze reports log on this page. In DevTools, pick the top frame — not a widget iframe.');
|
|
536
539
|
});
|
|
537
540
|
|
|
538
541
|
/**
|
|
@@ -590,7 +593,10 @@
|
|
|
590
593
|
saveWidgetDraft,
|
|
591
594
|
readWidgetDraft,
|
|
592
595
|
deleteWidgetDraft,
|
|
593
|
-
copyWidgetDraft
|
|
596
|
+
copyWidgetDraft,
|
|
597
|
+
saveShapeThumbnail,
|
|
598
|
+
readShapeThumbnail,
|
|
599
|
+
canPersistShapeThumbnails
|
|
594
600
|
} from './utils/creatorDraftStorage';
|
|
595
601
|
|
|
596
602
|
/**
|
|
@@ -866,6 +872,21 @@
|
|
|
866
872
|
function isAnyWidgetGenerating(): boolean {
|
|
867
873
|
return isGeneratingCode || Object.keys(generatingWidgetIds).some((id) => generatingWidgetIds[id]);
|
|
868
874
|
}
|
|
875
|
+
|
|
876
|
+
/** Synchronous hold so a screenshot cannot postMessage after codegen has started. */
|
|
877
|
+
function noteCodegenActive(active: boolean): void {
|
|
878
|
+
if (typeof window === 'undefined') return;
|
|
879
|
+
const holder = window as Window & { __widgeticCodegenCount?: number };
|
|
880
|
+
holder.__widgeticCodegenCount = Math.max(0, (holder.__widgeticCodegenCount || 0) + (active ? 1 : -1));
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function codegenCaptureBlocked(): boolean {
|
|
884
|
+
if (typeof window !== 'undefined' && ((window as Window & { __widgeticCodegenCount?: number }).__widgeticCodegenCount || 0) > 0) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (isAnyWidgetGenerating()) return true;
|
|
888
|
+
return /generating code|saving to repository/i.test(generateCodeStatus || '');
|
|
889
|
+
}
|
|
869
890
|
const widgetAbortControllers = new Map<string, AbortController>();
|
|
870
891
|
let currentGenerationOperationId: string | null = null;
|
|
871
892
|
let generateCodeStatus: string | null = null;
|
|
@@ -900,7 +921,7 @@
|
|
|
900
921
|
// Chat component reference and context (focused panel)
|
|
901
922
|
let chatMethods: {
|
|
902
923
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
903
|
-
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?:
|
|
924
|
+
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: ClarificationSuggestion[]) => void;
|
|
904
925
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
905
926
|
getConversationId: () => string | null;
|
|
906
927
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -1036,7 +1057,7 @@
|
|
|
1036
1057
|
fileSize?: number;
|
|
1037
1058
|
}>,
|
|
1038
1059
|
sourceWidgetId?: string,
|
|
1039
|
-
sendOptions?: { skipIntentGate?: boolean },
|
|
1060
|
+
sendOptions?: { skipIntentGate?: boolean; chipIntent?: ClarificationSuggestion['intent'] },
|
|
1040
1061
|
) {
|
|
1041
1062
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1042
1063
|
if (!widgetId) return;
|
|
@@ -1160,6 +1181,7 @@
|
|
|
1160
1181
|
promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
|
|
1161
1182
|
persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
|
|
1162
1183
|
skipIntentGate: sendOptions?.skipIntentGate === true,
|
|
1184
|
+
chipIntent: sendOptions?.chipIntent,
|
|
1163
1185
|
});
|
|
1164
1186
|
}
|
|
1165
1187
|
|
|
@@ -1410,6 +1432,76 @@
|
|
|
1410
1432
|
return dataUrls.slice(0, 1);
|
|
1411
1433
|
}
|
|
1412
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* Keeps widget screenshots out of the canvas JSON.
|
|
1437
|
+
*
|
|
1438
|
+
* Fabric serialized each widget shape's screenshot inline as base64, so an
|
|
1439
|
+
* autosave stringified megabytes of image data and wrote it three times over —
|
|
1440
|
+
* the main-thread block the freeze reports kept naming. The canvas now stores a
|
|
1441
|
+
* reference and asks for the bytes back through this store: the local copy
|
|
1442
|
+
* first, then the widget's persisted thumbnail so a fresh browser still paints
|
|
1443
|
+
* the shape.
|
|
1444
|
+
*/
|
|
1445
|
+
function installCanvasThumbnailStore(canvasModule: any): void {
|
|
1446
|
+
if (typeof canvasModule?.setShapeImageStore !== 'function') return;
|
|
1447
|
+
canvasModule.setShapeImageStore({
|
|
1448
|
+
save(ref: string, dataUrl: string) {
|
|
1449
|
+
// Refusing here tells the canvas to keep the bytes inline, so a save
|
|
1450
|
+
// before the owner is known cannot drop a screenshot on the floor.
|
|
1451
|
+
if (!canPersistShapeThumbnails()) throw new Error('No cache owner yet');
|
|
1452
|
+
saveShapeThumbnail(ref, dataUrl);
|
|
1453
|
+
},
|
|
1454
|
+
async load(ref: string): Promise<string | null> {
|
|
1455
|
+
const local = await readShapeThumbnail(ref);
|
|
1456
|
+
if (local) return local;
|
|
1457
|
+
const widgetId = ref.startsWith('widget:') ? ref.slice('widget:'.length) : null;
|
|
1458
|
+
if (!widgetId) return null;
|
|
1459
|
+
return await waitForWidgetThumbnailUrl(widgetId);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* The canvas often loads before the widget list does, so a reference with no
|
|
1466
|
+
* local copy has nothing to resolve to yet. Give the list a few seconds to
|
|
1467
|
+
* arrive rather than leaving the shape blank until the next screenshot.
|
|
1468
|
+
*/
|
|
1469
|
+
async function waitForWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
|
|
1470
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1471
|
+
const widget = getWidgetById(widgetId) as any;
|
|
1472
|
+
const url = widget?.thumbnailUrl || widget?.thumbnail_url;
|
|
1473
|
+
if (url) return url;
|
|
1474
|
+
// The canvas widget list is slim and often omits the thumbnail, so ask the
|
|
1475
|
+
// API for this one widget rather than leaving the shape blank.
|
|
1476
|
+
if (widget) return await fetchWidgetThumbnailUrl(widgetId);
|
|
1477
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1478
|
+
}
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/** One-widget lookup for the persisted thumbnail, cached per widget. */
|
|
1483
|
+
const thumbnailUrlRequests = new Map<string, Promise<string | null>>();
|
|
1484
|
+
|
|
1485
|
+
function fetchWidgetThumbnailUrl(widgetId: string): Promise<string | null> {
|
|
1486
|
+
const pending = thumbnailUrlRequests.get(widgetId);
|
|
1487
|
+
if (pending) return pending;
|
|
1488
|
+
const request = (async () => {
|
|
1489
|
+
if (!widgetsClient) return null;
|
|
1490
|
+
try {
|
|
1491
|
+
const response = (await widgetsClient.getWidgetsBatch({
|
|
1492
|
+
getWidgetsBatchRequest: { widgetIds: [widgetId] }
|
|
1493
|
+
})) as { widgets?: any[]; data?: { widgets?: any[] } };
|
|
1494
|
+
const fetched = (response?.widgets ?? response?.data?.widgets ?? [])[0];
|
|
1495
|
+
return fetched?.thumbnailUrl ?? fetched?.thumbnail_url ?? null;
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
console.warn('[CreatorApp] thumbnail lookup failed', widgetId.slice(0, 8), error);
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
})();
|
|
1501
|
+
thumbnailUrlRequests.set(widgetId, request);
|
|
1502
|
+
return request;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1413
1505
|
async function persistWidgetListThumbnail(widgetId: string, imageDataUrl: string, isCurrent = () => true): Promise<void> {
|
|
1414
1506
|
if (!widgetsClient || !widgetId || !imageDataUrl || !isCurrent()) return;
|
|
1415
1507
|
try {
|
|
@@ -1551,6 +1643,7 @@
|
|
|
1551
1643
|
buildUserPromptMessageContent(prompt),
|
|
1552
1644
|
'user',
|
|
1553
1645
|
persistAttachments.length > 0 ? persistAttachments : undefined,
|
|
1646
|
+
undefined,
|
|
1554
1647
|
widgetId,
|
|
1555
1648
|
);
|
|
1556
1649
|
const savedAtt = saved?.attachments || saved?.data?.attachments || [];
|
|
@@ -1651,32 +1744,46 @@
|
|
|
1651
1744
|
iframe: HTMLIFrameElement,
|
|
1652
1745
|
timeoutMs = 6000,
|
|
1653
1746
|
): Promise<{ data: string | null; error: string | null }> {
|
|
1654
|
-
if (
|
|
1747
|
+
if (codegenCaptureBlocked()) {
|
|
1655
1748
|
return Promise.resolve({ data: null, error: 'generation in progress' });
|
|
1656
1749
|
}
|
|
1750
|
+
const captureEpoch = screenshotGenerationEpoch;
|
|
1657
1751
|
return new Promise((resolve) => {
|
|
1658
1752
|
if (!iframe.contentWindow) {
|
|
1659
1753
|
resolve({ data: null, error: 'no-content-window' });
|
|
1660
1754
|
return;
|
|
1661
1755
|
}
|
|
1662
|
-
const
|
|
1756
|
+
const finish = (result: { data: string | null; error: string | null }) => {
|
|
1663
1757
|
window.removeEventListener('message', handler);
|
|
1664
|
-
|
|
1758
|
+
clearTimeout(timeout);
|
|
1759
|
+
if (captureEpoch !== screenshotGenerationEpoch || isAnyWidgetGenerating()) {
|
|
1760
|
+
resolve({ data: null, error: 'generation in progress' });
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
resolve(result);
|
|
1764
|
+
};
|
|
1765
|
+
const timeout = setTimeout(() => {
|
|
1766
|
+
finish({ data: null, error: 'timeout' });
|
|
1665
1767
|
}, timeoutMs);
|
|
1666
1768
|
|
|
1667
1769
|
function handler(event: MessageEvent) {
|
|
1668
1770
|
if (event.data?.type !== 'widgetic:screenshot') return;
|
|
1669
|
-
// Ignore other widgets and synthetic replies with no source window.
|
|
1670
1771
|
if (event.source !== iframe.contentWindow) return;
|
|
1671
|
-
|
|
1672
|
-
window.removeEventListener('message', handler);
|
|
1673
|
-
resolve({
|
|
1772
|
+
finish({
|
|
1674
1773
|
data: typeof event.data.data === 'string' ? event.data.data : null,
|
|
1675
1774
|
error: typeof event.data.error === 'string' ? event.data.error : null,
|
|
1676
1775
|
});
|
|
1677
1776
|
}
|
|
1678
1777
|
window.addEventListener('message', handler);
|
|
1679
|
-
|
|
1778
|
+
try {
|
|
1779
|
+
if (codegenCaptureBlocked()) {
|
|
1780
|
+
finish({ data: null, error: 'generation in progress' });
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
iframe.contentWindow.postMessage({ type: 'widgetic:request-screenshot' }, '*');
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
finish({ data: null, error: err instanceof Error ? err.message : 'postMessage failed' });
|
|
1786
|
+
}
|
|
1680
1787
|
});
|
|
1681
1788
|
}
|
|
1682
1789
|
|
|
@@ -1689,7 +1796,7 @@
|
|
|
1689
1796
|
// startup on the main thread this page shares with it, and the capture that
|
|
1690
1797
|
// follows is refused during a generation anyway (`requestIframeScreenshot`),
|
|
1691
1798
|
// so the whole path is skipped rather than paying the load for nothing.
|
|
1692
|
-
if (
|
|
1799
|
+
if (codegenCaptureBlocked()) {
|
|
1693
1800
|
console.log('[Chat→Screenshot] Skip hidden Worker capture — codegen in progress', widgetId?.substring(0, 8));
|
|
1694
1801
|
return null;
|
|
1695
1802
|
}
|
|
@@ -1741,14 +1848,14 @@
|
|
|
1741
1848
|
|
|
1742
1849
|
/** Explicit Refresh only: capture the loaded frame without opening a panel or publishing. */
|
|
1743
1850
|
async function handleWidgetShapeRefresh(widgetId, iframe, isCurrentFrame) {
|
|
1744
|
-
if (
|
|
1851
|
+
if (codegenCaptureBlocked()) return;
|
|
1745
1852
|
const capturedCanvasId = getEffectiveCanvasId();
|
|
1746
1853
|
const capturedCanvas = canvasRef;
|
|
1747
1854
|
const capturedSource = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
1748
1855
|
// Frame/intent identity owns this capture, not focused-panel HEAD hydration
|
|
1749
1856
|
// or another widget's global generation operation.
|
|
1750
1857
|
const isCurrent = () => isCurrentFrame() && !!getWidgetById(widgetId)
|
|
1751
|
-
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !
|
|
1858
|
+
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !codegenCaptureBlocked()
|
|
1752
1859
|
&& capturedCanvasId === getEffectiveCanvasId() && capturedCanvas === canvasRef
|
|
1753
1860
|
&& capturedSource === (shapePreviewSourceByWidgetId[widgetId] || 'live')
|
|
1754
1861
|
&& resolveLiveWidgetShapeIframe(widgetId) === iframe;
|
|
@@ -1779,7 +1886,7 @@
|
|
|
1779
1886
|
}
|
|
1780
1887
|
// Never run background / refresh screenshot captures while code generation is running —
|
|
1781
1888
|
// html2canvas inside any same-origin iframe freezes the browser main thread.
|
|
1782
|
-
if ((
|
|
1889
|
+
if (codegenCaptureBlocked()) {
|
|
1783
1890
|
console.log('[Chat→Screenshot] Skip capture — generation in progress');
|
|
1784
1891
|
return null;
|
|
1785
1892
|
}
|
|
@@ -1807,6 +1914,10 @@
|
|
|
1807
1914
|
|
|
1808
1915
|
// 1) Always try the visible live iframe first (published HTML from Worker includes screenshot script)
|
|
1809
1916
|
if (liveIframe?.contentWindow) {
|
|
1917
|
+
if (codegenCaptureBlocked()) {
|
|
1918
|
+
console.log('[Chat→Screenshot] Skip live capture — codegen in progress', targetId.substring(0, 8));
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1810
1921
|
console.log('[Chat→Screenshot] Live WidgetShape iframe', {
|
|
1811
1922
|
targetId: targetId.substring(0, 8),
|
|
1812
1923
|
isWorker: isWorkerPreviewUrl(liveSrc),
|
|
@@ -1975,7 +2086,14 @@
|
|
|
1975
2086
|
messageContent: m.message_content || m.messageContent || '',
|
|
1976
2087
|
messageType: m.message_type || m.messageType || 'user',
|
|
1977
2088
|
attachments,
|
|
2089
|
+
// Suggestion chips from a persisted clarification card — restored
|
|
2090
|
+
// on reload exactly as they were served.
|
|
2091
|
+
suggestions: Array.isArray(m.suggestions) && m.suggestions.length > 0 ? m.suggestions : undefined,
|
|
1978
2092
|
isCommit: m.is_commit || m.isCommit || false,
|
|
2093
|
+
// What the turn shipped, in the commit's own words — persisted beside
|
|
2094
|
+
// is_commit by the codegen pipeline and rendered on the turn's commit
|
|
2095
|
+
// chrome (the surface that survives a reload).
|
|
2096
|
+
commitTitle: m.commit_title || m.commitTitle || undefined,
|
|
1979
2097
|
userId: m.user_id || m.userId || '',
|
|
1980
2098
|
createdAt: new Date(m.created_at || m.createdAt),
|
|
1981
2099
|
updatedAt: new Date(m.updated_at || m.updatedAt || m.created_at || m.createdAt),
|
|
@@ -2148,6 +2266,7 @@
|
|
|
2148
2266
|
file_size?: number | null;
|
|
2149
2267
|
display_order?: number;
|
|
2150
2268
|
}>,
|
|
2269
|
+
suggestions?: ClarificationSuggestion[],
|
|
2151
2270
|
persistWidgetId?: string | null,
|
|
2152
2271
|
): Promise<any> {
|
|
2153
2272
|
try {
|
|
@@ -2183,25 +2302,39 @@
|
|
|
2183
2302
|
user_id: userId,
|
|
2184
2303
|
is_commit: false,
|
|
2185
2304
|
attachments: apiAttachments.length > 0 ? apiAttachments : undefined,
|
|
2305
|
+
// Clarification-card suggestions ride the same write as the reply
|
|
2306
|
+
// they belong to, so a reload re-renders the card with its chips.
|
|
2307
|
+
...(suggestions && suggestions.length > 0 ? { suggestions } : {}),
|
|
2186
2308
|
};
|
|
2187
|
-
|
|
2309
|
+
const persistMessage = async (payload: typeof createPayload) => {
|
|
2188
2310
|
const response = await messagesClient.createMessageRaw({
|
|
2189
2311
|
conversationId,
|
|
2190
|
-
createMessageRequest:
|
|
2312
|
+
createMessageRequest: payload,
|
|
2191
2313
|
});
|
|
2192
2314
|
const json = await response.raw.json();
|
|
2193
|
-
// console.log('[Chat→Backend] Saved message to conversation:', conversationId);
|
|
2194
2315
|
const saved = json?.data || json;
|
|
2195
2316
|
const savedAtt = saved?.attachments;
|
|
2196
2317
|
return {
|
|
2197
2318
|
...saved,
|
|
2198
|
-
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt :
|
|
2319
|
+
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt : (payload.attachments || []),
|
|
2199
2320
|
};
|
|
2321
|
+
};
|
|
2322
|
+
try {
|
|
2323
|
+
return await persistMessage(createPayload);
|
|
2200
2324
|
} catch (withAttachmentsError) {
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2325
|
+
if (apiAttachments.length > 0) {
|
|
2326
|
+
console.warn(
|
|
2327
|
+
'[Chat-Backend] Save with attachments failed — retrying text-only so the turn is not lost:',
|
|
2328
|
+
withAttachmentsError,
|
|
2329
|
+
);
|
|
2330
|
+
try {
|
|
2331
|
+
return await persistMessage({ ...createPayload, attachments: undefined });
|
|
2332
|
+
} catch (textOnlyError) {
|
|
2333
|
+
console.error('[Chat-Backend] Text-only message save also failed:', textOnlyError);
|
|
2334
|
+
throw textOnlyError;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
console.error('[Chat-Backend] Message save failed:', withAttachmentsError);
|
|
2205
2338
|
throw withAttachmentsError;
|
|
2206
2339
|
}
|
|
2207
2340
|
} catch (error) {
|
|
@@ -2242,34 +2375,47 @@
|
|
|
2242
2375
|
null;
|
|
2243
2376
|
// console.log('[Chat→Backend] Git rollback result:', gitRollback, { undoMode, targetCommit, panelWidgetId });
|
|
2244
2377
|
if (gitRollback?.success) {
|
|
2245
|
-
//
|
|
2246
|
-
// artifact —
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2378
|
+
// Restore changes Git HEAD (often a new rollback commit). Never reuse a
|
|
2379
|
+
// published CDN artifact — that is Switch Version, and it hid the rebuild.
|
|
2380
|
+
const rollbackHeadSha =
|
|
2381
|
+
gitRollback.commitId ?? gitRollback.commit_id ?? targetCommit ?? null;
|
|
2382
|
+
const historicalSha = targetCommit || rollbackHeadSha;
|
|
2383
|
+
if (panelWidgetId) {
|
|
2384
|
+
if (rollbackHeadSha) {
|
|
2385
|
+
lastCommitId = rollbackHeadSha;
|
|
2386
|
+
saveCommitIdForWidget(panelWidgetId, rollbackHeadSha);
|
|
2387
|
+
patchPanelSnapshot(panelWidgetId, { lastCommitId: rollbackHeadSha });
|
|
2388
|
+
patchCodegenUiState(panelWidgetId, { lastCommitId: rollbackHeadSha });
|
|
2389
|
+
}
|
|
2390
|
+
const targetSha = String(historicalSha || '').toLowerCase();
|
|
2391
|
+
const matchedVersion = (resolveWidgetShapeVersionOptions(panelWidgetId) ?? [])
|
|
2392
|
+
.find((option) => {
|
|
2393
|
+
const optionSha = String(option.commitSha ?? '').toLowerCase();
|
|
2394
|
+
return optionSha.length >= 7 && targetSha.length >= 7
|
|
2395
|
+
&& (optionSha === targetSha || optionSha.startsWith(targetSha) || targetSha.startsWith(optionSha));
|
|
2396
|
+
});
|
|
2397
|
+
if (matchedVersion?.id?.startsWith('v:')) {
|
|
2398
|
+
const restoredVersion = parseInt(matchedVersion.id.slice(2), 10);
|
|
2399
|
+
if (Number.isFinite(restoredVersion)) {
|
|
2400
|
+
restoredViewingVersionByWidgetId = {
|
|
2401
|
+
...restoredViewingVersionByWidgetId,
|
|
2402
|
+
[panelWidgetId]: restoredVersion,
|
|
2403
|
+
};
|
|
2404
|
+
widgetShapeLivePreviewsRef?.setSelectedVersion?.(panelWidgetId, `v:${restoredVersion}`);
|
|
2266
2405
|
}
|
|
2267
2406
|
}
|
|
2407
|
+
shapePreviewSourceByWidgetId = {
|
|
2408
|
+
...shapePreviewSourceByWidgetId,
|
|
2409
|
+
[panelWidgetId]: 'preview',
|
|
2410
|
+
};
|
|
2411
|
+
getWidgetDetailsRef(panelWidgetId)?.clearCodegenPreviewFresh?.();
|
|
2412
|
+
syncWidgetShapeLivePreviews();
|
|
2413
|
+
panelDisplayRevision += 1;
|
|
2268
2414
|
}
|
|
2269
2415
|
showToast('success', undoMode ? 'Change undone' : 'Checkpoint restored', {
|
|
2270
2416
|
description: undoMode
|
|
2271
|
-
? `Code reverted to commit ${formatCommitShaShort(
|
|
2272
|
-
: `Widget code at commit ${formatCommitShaShort(
|
|
2417
|
+
? `Code reverted to commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`
|
|
2418
|
+
: `Widget code at commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`,
|
|
2273
2419
|
duration: 6000,
|
|
2274
2420
|
});
|
|
2275
2421
|
liveDesignValues = {};
|
|
@@ -2278,18 +2424,12 @@
|
|
|
2278
2424
|
compositionDesignSchema = null;
|
|
2279
2425
|
compositionContentSchema = null;
|
|
2280
2426
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
2281
|
-
_isRestoringCheckpoint =
|
|
2282
|
-
if (previewFromVersionCache) {
|
|
2283
|
-
// Immutable artifact already serves the preview; no rebuild ladder.
|
|
2284
|
-
syncWidgetShapeLivePreviews();
|
|
2285
|
-
setTimeout(() => handleRefreshScreenshot(panelWidgetId, { quiet: true }), 2000);
|
|
2286
|
-
return { ...data, previewFromVersionCache };
|
|
2287
|
-
}
|
|
2427
|
+
_isRestoringCheckpoint = true;
|
|
2288
2428
|
const panelRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : widgetDetails;
|
|
2289
2429
|
const scheduleBuildRetry = async () => {
|
|
2290
|
-
const delays = [2500, 5000, 8000];
|
|
2430
|
+
const delays = [400, 2500, 5000, 8000];
|
|
2291
2431
|
for (let i = 0; i < delays.length; i++) {
|
|
2292
|
-
await new Promise((r) => setTimeout(r, delays[i]));
|
|
2432
|
+
if (i > 0) await new Promise((r) => setTimeout(r, delays[i]));
|
|
2293
2433
|
if (i === 0) suppressNextBuildFromRepoChatMessage = true;
|
|
2294
2434
|
try {
|
|
2295
2435
|
const activeRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : panelRef;
|
|
@@ -2303,7 +2443,9 @@
|
|
|
2303
2443
|
[panelWidgetId]: 'preview',
|
|
2304
2444
|
};
|
|
2305
2445
|
syncWidgetShapeLivePreviews();
|
|
2306
|
-
|
|
2446
|
+
widgetShapeLivePreviewsRef?.requestReload?.(panelWidgetId);
|
|
2447
|
+
panelDisplayRevision += 1;
|
|
2448
|
+
scheduleIdleThumbnailPersist(panelWidgetId);
|
|
2307
2449
|
}
|
|
2308
2450
|
return;
|
|
2309
2451
|
}
|
|
@@ -3863,9 +4005,7 @@
|
|
|
3863
4005
|
};
|
|
3864
4006
|
await compositionsClient.updateComposition({
|
|
3865
4007
|
id: selectedComposition.id,
|
|
3866
|
-
|
|
3867
|
-
// the generated UpdateComposition model predates that field.
|
|
3868
|
-
updateComposition: { embedSettings } as unknown as UpdateComposition
|
|
4008
|
+
updateComposition: { embedSettings }
|
|
3869
4009
|
});
|
|
3870
4010
|
selectedComposition = { ...selectedComposition, embedSettings };
|
|
3871
4011
|
embedDimensionsChanged = false;
|
|
@@ -4004,6 +4144,11 @@
|
|
|
4004
4144
|
|
|
4005
4145
|
// debugMode — current ON/OFF state. User can still toggle when the UI is available.
|
|
4006
4146
|
let debugMode = debugModeAvailable;
|
|
4147
|
+
if (typeof localStorage !== 'undefined' && debugModeAvailable) {
|
|
4148
|
+
const storedDebugMode = localStorage.getItem('widgetic:site-debug-on');
|
|
4149
|
+
if (storedDebugMode === '1' || storedDebugMode === 'true') debugMode = true;
|
|
4150
|
+
if (storedDebugMode === '0' || storedDebugMode === 'false') debugMode = false;
|
|
4151
|
+
}
|
|
4007
4152
|
// console.log('[WidgetCreator] Debug from host/env', {
|
|
4008
4153
|
// hostProvidedDebug,
|
|
4009
4154
|
// initialDebugMode,
|
|
@@ -4188,6 +4333,11 @@
|
|
|
4188
4333
|
function toggleDebugMode() {
|
|
4189
4334
|
if (!debugModeAvailable) return;
|
|
4190
4335
|
debugMode = !debugMode;
|
|
4336
|
+
try {
|
|
4337
|
+
localStorage.setItem('widgetic:site-debug-on', debugMode ? '1' : '0');
|
|
4338
|
+
} catch (err) {
|
|
4339
|
+
console.warn('[WidgetCreator] Could not persist debug mode', err);
|
|
4340
|
+
}
|
|
4191
4341
|
// When turning OFF: switches to default model (from API config), mock console
|
|
4192
4342
|
// When turning ON: enables model selector, real-time console
|
|
4193
4343
|
const effectiveModel = debugMode ? userSelectedModelId : defaultModelId;
|
|
@@ -4386,9 +4536,8 @@
|
|
|
4386
4536
|
const anyW = widget as { thumbnailUrl?: string | null; thumbnail_url?: string | null } | null;
|
|
4387
4537
|
const hasThumb = !!(anyW?.thumbnailUrl || anyW?.thumbnail_url);
|
|
4388
4538
|
if (!hasThumb) {
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
}, 1200);
|
|
4539
|
+
// A capture 1s after open runs html2canvas on the shared main thread.
|
|
4540
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
4392
4541
|
}
|
|
4393
4542
|
return true;
|
|
4394
4543
|
}
|
|
@@ -7665,11 +7814,18 @@
|
|
|
7665
7814
|
const ctx = canvas.getContext('2d');
|
|
7666
7815
|
if (!ctx) return dataUrl;
|
|
7667
7816
|
ctx.drawImage(img, 0, 0, width, height);
|
|
7817
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
7818
|
+
const encode = (quality: number) =>
|
|
7819
|
+
new Promise<string>((resolve) => {
|
|
7820
|
+
setTimeout(() => {
|
|
7821
|
+
resolve(canvas.toDataURL('image/jpeg', quality));
|
|
7822
|
+
}, 0);
|
|
7823
|
+
});
|
|
7668
7824
|
let quality = 0.82;
|
|
7669
|
-
let out =
|
|
7825
|
+
let out = await encode(quality);
|
|
7670
7826
|
while (out.length > maxPayloadChars && quality > 0.45) {
|
|
7671
7827
|
quality -= 0.08;
|
|
7672
|
-
out =
|
|
7828
|
+
out = await encode(quality);
|
|
7673
7829
|
}
|
|
7674
7830
|
// console.log('[WidgetCreator] Compressed reference image for codegen', {
|
|
7675
7831
|
// fromChars: dataUrl.length,
|
|
@@ -7748,6 +7904,7 @@
|
|
|
7748
7904
|
*/
|
|
7749
7905
|
/** Debounced quiet sync: live Worker preview → canvas WidgetShape thumbnail. */
|
|
7750
7906
|
const canvasShapePreviewSyncTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7907
|
+
const quietScreenshotTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7751
7908
|
function cancelCanvasShapePreviewSync(widgetId?: string | null): void {
|
|
7752
7909
|
if (widgetId) {
|
|
7753
7910
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
@@ -7758,13 +7915,86 @@
|
|
|
7758
7915
|
for (const timer of canvasShapePreviewSyncTimers.values()) clearTimeout(timer);
|
|
7759
7916
|
canvasShapePreviewSyncTimers.clear();
|
|
7760
7917
|
}
|
|
7918
|
+
function cancelQuietScreenshots(widgetId?: string | null): void {
|
|
7919
|
+
if (widgetId) {
|
|
7920
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7921
|
+
if (existing) clearTimeout(existing);
|
|
7922
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7923
|
+
cancelCanvasShapePreviewSync(widgetId);
|
|
7924
|
+
return;
|
|
7925
|
+
}
|
|
7926
|
+
for (const timer of quietScreenshotTimers.values()) clearTimeout(timer);
|
|
7927
|
+
quietScreenshotTimers.clear();
|
|
7928
|
+
cancelCanvasShapePreviewSync();
|
|
7929
|
+
}
|
|
7930
|
+
function scheduleQuietScreenshot(widgetId: string, delayMs = 2000): void {
|
|
7931
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7932
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7933
|
+
if (existing) clearTimeout(existing);
|
|
7934
|
+
const timer = setTimeout(() => {
|
|
7935
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7936
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7937
|
+
void handleRefreshScreenshot(widgetId, { quiet: true });
|
|
7938
|
+
}, delayMs);
|
|
7939
|
+
quietScreenshotTimers.set(widgetId, timer);
|
|
7940
|
+
}
|
|
7941
|
+
/**
|
|
7942
|
+
* Latest widget capture for the next LLM turn, without freezing a generation.
|
|
7943
|
+
* html2canvas shares the page main thread, so this waits until codegen has
|
|
7944
|
+
* been idle, then persists thumbnail_url only (no canvas JSON serialize).
|
|
7945
|
+
*/
|
|
7946
|
+
const idleThumbnailTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7947
|
+
const IDLE_THUMBNAIL_DELAY_MS = 12000;
|
|
7948
|
+
function cancelIdleThumbnailPersist(widgetId?: string | null): void {
|
|
7949
|
+
if (widgetId) {
|
|
7950
|
+
const existing = idleThumbnailTimers.get(widgetId);
|
|
7951
|
+
if (existing) clearTimeout(existing);
|
|
7952
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7953
|
+
return;
|
|
7954
|
+
}
|
|
7955
|
+
for (const timer of idleThumbnailTimers.values()) clearTimeout(timer);
|
|
7956
|
+
idleThumbnailTimers.clear();
|
|
7957
|
+
}
|
|
7958
|
+
function scheduleIdleThumbnailPersist(widgetId: string): void {
|
|
7959
|
+
if (!widgetId) return;
|
|
7960
|
+
cancelIdleThumbnailPersist(widgetId);
|
|
7961
|
+
const timer = setTimeout(() => {
|
|
7962
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7963
|
+
const run = () => {
|
|
7964
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7965
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
7966
|
+
return;
|
|
7967
|
+
}
|
|
7968
|
+
void persistIdleWidgetThumbnail(widgetId);
|
|
7969
|
+
};
|
|
7970
|
+
if (typeof requestIdleCallback === 'function') {
|
|
7971
|
+
requestIdleCallback(() => run(), { timeout: 8000 });
|
|
7972
|
+
} else {
|
|
7973
|
+
run();
|
|
7974
|
+
}
|
|
7975
|
+
}, IDLE_THUMBNAIL_DELAY_MS);
|
|
7976
|
+
idleThumbnailTimers.set(widgetId, timer);
|
|
7977
|
+
}
|
|
7978
|
+
async function persistIdleWidgetThumbnail(widgetId: string): Promise<void> {
|
|
7979
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7980
|
+
const iframe = resolveLiveWidgetShapeIframe(widgetId);
|
|
7981
|
+
if (!iframe?.contentWindow) return;
|
|
7982
|
+
try {
|
|
7983
|
+
const screenshot = await handleChatScreenshot(widgetId, iframe, true);
|
|
7984
|
+
if (!screenshot || codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7985
|
+
await persistWidgetListThumbnail(widgetId, screenshot);
|
|
7986
|
+
console.log('[Chat→Screenshot] Idle thumbnail persisted', widgetId.substring(0, 8));
|
|
7987
|
+
} catch (error) {
|
|
7988
|
+
console.warn('[Chat→Screenshot] Idle thumbnail persist failed', widgetId, error);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7761
7991
|
function scheduleCanvasShapePreviewSync(widgetId: string, delayMs = 1800): void {
|
|
7762
|
-
if (!widgetId ||
|
|
7992
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7763
7993
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
7764
7994
|
if (existing) clearTimeout(existing);
|
|
7765
7995
|
const timer = setTimeout(() => {
|
|
7766
7996
|
canvasShapePreviewSyncTimers.delete(widgetId);
|
|
7767
|
-
if (
|
|
7997
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7768
7998
|
// console.log('[CreatorApp] Skip shape preview sync — codegen in progress:', widgetId.substring(0, 8));
|
|
7769
7999
|
return;
|
|
7770
8000
|
}
|
|
@@ -7795,8 +8025,8 @@
|
|
|
7795
8025
|
|
|
7796
8026
|
// Never run quiet background screenshot captures while ANY code generation is running —
|
|
7797
8027
|
// html2canvas execution inside any preview iframe blocks the single shared main thread.
|
|
7798
|
-
if (
|
|
7799
|
-
console.log('[handleRefreshScreenshot] Skip
|
|
8028
|
+
if (codegenCaptureBlocked()) {
|
|
8029
|
+
console.log('[handleRefreshScreenshot] Skip capture — codegen in progress', widgetId?.substring(0, 8));
|
|
7800
8030
|
return;
|
|
7801
8031
|
}
|
|
7802
8032
|
|
|
@@ -7897,6 +8127,12 @@
|
|
|
7897
8127
|
});
|
|
7898
8128
|
}
|
|
7899
8129
|
window.addEventListener('message', handler);
|
|
8130
|
+
if (codegenCaptureBlocked()) {
|
|
8131
|
+
clearTimeout(timeout);
|
|
8132
|
+
window.removeEventListener('message', handler);
|
|
8133
|
+
resolve({ data: null, error: 'generation in progress' });
|
|
8134
|
+
return;
|
|
8135
|
+
}
|
|
7900
8136
|
iframe.contentWindow!.postMessage({ type: 'widgetic:request-screenshot' }, '*');
|
|
7901
8137
|
});
|
|
7902
8138
|
|
|
@@ -8017,6 +8253,8 @@ Requirements:
|
|
|
8017
8253
|
promptImagesOverride?: string[];
|
|
8018
8254
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8019
8255
|
skipIntentGate?: boolean;
|
|
8256
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8257
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8020
8258
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
8021
8259
|
persistFileAttachments?: Array<{
|
|
8022
8260
|
url?: string;
|
|
@@ -8107,6 +8345,7 @@ Requirements:
|
|
|
8107
8345
|
promptImagesOverride: options.promptImagesOverride,
|
|
8108
8346
|
persistFileAttachments: options.persistFileAttachments,
|
|
8109
8347
|
skipIntentGate: options.skipIntentGate,
|
|
8348
|
+
chipIntent: options.chipIntent,
|
|
8110
8349
|
});
|
|
8111
8350
|
}
|
|
8112
8351
|
|
|
@@ -8130,6 +8369,8 @@ Requirements:
|
|
|
8130
8369
|
promptImagesOverride?: string[];
|
|
8131
8370
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8132
8371
|
skipIntentGate?: boolean;
|
|
8372
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8373
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8133
8374
|
persistFileAttachments?: Array<{
|
|
8134
8375
|
url?: string;
|
|
8135
8376
|
file?: File;
|
|
@@ -8145,7 +8386,6 @@ Requirements:
|
|
|
8145
8386
|
|
|
8146
8387
|
const panelChat = getChatMethodsForWidget(generationWidgetId);
|
|
8147
8388
|
const panelSnap = getPanelState(generationWidgetId);
|
|
8148
|
-
const hadCodeBeforeGeneration = !!(panelSnap?.hasGeneratedCode || panelSnap?.lastCommitId);
|
|
8149
8389
|
|
|
8150
8390
|
// Per-widget abort: only abort if the SAME widget already has a running generation
|
|
8151
8391
|
const existingController = widgetAbortControllers.get(generationWidgetId);
|
|
@@ -8158,6 +8398,22 @@ Requirements:
|
|
|
8158
8398
|
const signal = abortController.signal;
|
|
8159
8399
|
|
|
8160
8400
|
generatingWidgetIds = { ...generatingWidgetIds, [generationWidgetId]: true };
|
|
8401
|
+
noteCodegenActive(true);
|
|
8402
|
+
screenshotGenerationEpoch += 1;
|
|
8403
|
+
document.querySelectorAll('iframe').forEach((frame) => {
|
|
8404
|
+
try {
|
|
8405
|
+
frame.contentWindow?.postMessage({ type: 'widgetic:abort-screenshot' }, '*');
|
|
8406
|
+
} catch (err) {
|
|
8407
|
+
console.warn('[WidgetCreator] Could not abort in-flight screenshot', err);
|
|
8408
|
+
}
|
|
8409
|
+
});
|
|
8410
|
+
cancelQuietScreenshots();
|
|
8411
|
+
cancelIdleThumbnailPersist();
|
|
8412
|
+
widgetShapeLivePreviewsRef?.cancelReloads?.();
|
|
8413
|
+
if (restoredViewingVersionByWidgetId[generationWidgetId] != null) {
|
|
8414
|
+
const { [generationWidgetId]: _droppedRestore, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
8415
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
8416
|
+
}
|
|
8161
8417
|
lastHarnessChatLine = '';
|
|
8162
8418
|
|
|
8163
8419
|
// Show user prompt in chat when triggered outside Chat (confirm dialog / Generate Now).
|
|
@@ -8371,6 +8627,7 @@ Requirements:
|
|
|
8371
8627
|
runtimeLogs: runtimeLogsForRequest,
|
|
8372
8628
|
compileFixOnly: options.compileFixOnly === true,
|
|
8373
8629
|
skipIntentGate: options.skipIntentGate === true,
|
|
8630
|
+
chipIntent: options.chipIntent,
|
|
8374
8631
|
} as GenerateCodeRequest
|
|
8375
8632
|
});
|
|
8376
8633
|
// 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);
|
|
@@ -8385,7 +8642,7 @@ Requirements:
|
|
|
8385
8642
|
if (gatedData?.gated === true && typeof gatedData.action === 'string' && gatedData.action !== 'generate') {
|
|
8386
8643
|
const gatedAction = gatedData.action as 'answer' | 'redirect' | 'clarify';
|
|
8387
8644
|
const gatedReply: string = gatedData.reply ?? '';
|
|
8388
|
-
const suggestions:
|
|
8645
|
+
const suggestions: ClarificationSuggestion[] = Array.isArray(gatedData.suggestions) ? gatedData.suggestions : [];
|
|
8389
8646
|
|
|
8390
8647
|
clearActiveOperationId();
|
|
8391
8648
|
patchCodegenUiState(generationWidgetId, {
|
|
@@ -8597,8 +8854,10 @@ Requirements:
|
|
|
8597
8854
|
shapeHistoryResolvedByWidgetId = restResolved;
|
|
8598
8855
|
setTimeout(() => {
|
|
8599
8856
|
void loadPublishHistoryForPanel(generationWidgetId, { silent: true }).then(() => {
|
|
8600
|
-
|
|
8601
|
-
if (
|
|
8857
|
+
const newestDraft = getNewestDraftVersionId(generationWidgetId);
|
|
8858
|
+
if (newestDraft != null) {
|
|
8859
|
+
selectVersionInDropdowns(generationWidgetId, `v:${newestDraft}`);
|
|
8860
|
+
}
|
|
8602
8861
|
});
|
|
8603
8862
|
}, 1200);
|
|
8604
8863
|
}
|
|
@@ -8770,11 +9029,9 @@ Requirements:
|
|
|
8770
9029
|
[generationWidgetId]: 'preview',
|
|
8771
9030
|
};
|
|
8772
9031
|
syncWidgetShapeLivePreviews();
|
|
8773
|
-
//
|
|
8774
|
-
//
|
|
8775
|
-
// on the canvas shape overlay or it keeps showing the previous version.
|
|
9032
|
+
// WS preview-ready was deferred during codegen so the Worker
|
|
9033
|
+
// iframe does not boot on the shared main thread at 80%.
|
|
8776
9034
|
widgetShapeLivePreviewsRef?.requestReload?.(generationWidgetId);
|
|
8777
|
-
panelDisplayRevision += 1;
|
|
8778
9035
|
if (!completedPreviewBuildErr) {
|
|
8779
9036
|
await applyDesignSchemaDefaultsToPreview(generationWidgetId);
|
|
8780
9037
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
@@ -8788,15 +9045,10 @@ Requirements:
|
|
|
8788
9045
|
await panelChat?.reloadConversationMessages?.();
|
|
8789
9046
|
panelChat?.markWidgetHeadAtLatestCommit?.();
|
|
8790
9047
|
}, 2500);
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
void (async () => {
|
|
8796
|
-
await handleRefreshScreenshot(generationWidgetId, { quiet: true });
|
|
8797
|
-
})();
|
|
8798
|
-
}
|
|
8799
|
-
}, 3000);
|
|
9048
|
+
scheduleIdleThumbnailPersist(generationWidgetId);
|
|
9049
|
+
// Do not html2canvas here. Capture during/after generation freezes
|
|
9050
|
+
// the tab (iframe shares the main thread). Thumb for the next prompt
|
|
9051
|
+
// is persisted later, only while the tab is idle.
|
|
8800
9052
|
// Do NOT auto-publish — user previews first, then clicks Publish / Set live.
|
|
8801
9053
|
} else {
|
|
8802
9054
|
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
@@ -8874,6 +9126,7 @@ Requirements:
|
|
|
8874
9126
|
showToast('error', 'Code generation error', { description: message, duration: 6000 });
|
|
8875
9127
|
pushCodegenFailureToChat(generationWidgetId, message);
|
|
8876
9128
|
} finally {
|
|
9129
|
+
noteCodegenActive(false);
|
|
8877
9130
|
const { [generationWidgetId]: _done, ...restFinally } = generatingWidgetIds;
|
|
8878
9131
|
generatingWidgetIds = restFinally;
|
|
8879
9132
|
widgetAbortControllers.delete(generationWidgetId);
|
|
@@ -9032,7 +9285,7 @@ Requirements:
|
|
|
9032
9285
|
// rather than letting them silently become version 0.
|
|
9033
9286
|
const rawTargetVersion = publishOpts.targetVersion;
|
|
9034
9287
|
const targetVersion =
|
|
9035
|
-
rawTargetVersion == null || rawTargetVersion === ''
|
|
9288
|
+
rawTargetVersion == null || (typeof rawTargetVersion === 'string' && rawTargetVersion === '')
|
|
9036
9289
|
? null
|
|
9037
9290
|
: Number.isFinite(Number(rawTargetVersion))
|
|
9038
9291
|
? Number(rawTargetVersion)
|
|
@@ -10221,8 +10474,8 @@ Requirements:
|
|
|
10221
10474
|
record?.artifact_url ||
|
|
10222
10475
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10223
10476
|
if (isPublished && artifact) {
|
|
10224
|
-
const
|
|
10225
|
-
if (url) return url;
|
|
10477
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10478
|
+
if (isolated?.url) return isolated.url;
|
|
10226
10479
|
}
|
|
10227
10480
|
const previewState = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10228
10481
|
if (previewState?.url) return previewState.url;
|
|
@@ -10252,6 +10505,10 @@ Requirements:
|
|
|
10252
10505
|
* Publish history is loaded without requiring chat to be open.
|
|
10253
10506
|
*/
|
|
10254
10507
|
let shapePreviewSourceByWidgetId: Record<string, string> = {};
|
|
10508
|
+
/** Dropdown label only. Changing it must not reload the shape iframe. */
|
|
10509
|
+
let dropdownVersionByWidgetId: Record<string, string> = {};
|
|
10510
|
+
/** After Restore Checkpoint, dropdowns stay on the restored version number while preview is Worker HEAD. */
|
|
10511
|
+
let restoredViewingVersionByWidgetId: Record<string, number> = {};
|
|
10255
10512
|
// Historical previews are never interchangeable with the shared HEAD preview.
|
|
10256
10513
|
let shapeVersionPreviewByWidgetId: Record<string, {
|
|
10257
10514
|
versionId: string; requestId: number; loading: boolean; error: string | null; url: string | null;
|
|
@@ -10365,12 +10622,21 @@ Requirements:
|
|
|
10365
10622
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10366
10623
|
|
|
10367
10624
|
if (isPublished && artifact) {
|
|
10368
|
-
const
|
|
10369
|
-
if (url) return url;
|
|
10625
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10626
|
+
if (isolated?.url && isolated.versionId === `v:${version}`) return isolated.url;
|
|
10627
|
+
// Do not return Live CDN here — Switch Version must wait for the
|
|
10628
|
+
// commit-specific build, otherwise the iframe stays on the last publish.
|
|
10370
10629
|
}
|
|
10371
|
-
// The newest draft
|
|
10630
|
+
// The newest draft is repo HEAD only while HEAD still points at its commit.
|
|
10631
|
+
// A Restore Checkpoint writes a rollback commit on top, so the shared
|
|
10632
|
+
// `/preview/{widgetId}` artifact then serves the restored code under the
|
|
10633
|
+
// newest version's number — ask for a commit-specific build instead.
|
|
10372
10634
|
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10373
|
-
|
|
10635
|
+
const draftCommitSha = normalizeCommitSha(record?.commitSha ?? record?.commit_sha ?? null);
|
|
10636
|
+
const headCommitSha = normalizeCommitSha(ps.lastCommitId ?? loadCommitIdForWidget(widgetId));
|
|
10637
|
+
const draftIsRepoHead =
|
|
10638
|
+
!draftCommitSha || !headCommitSha || commitsMatch(draftCommitSha, headCommitSha);
|
|
10639
|
+
if (!isPublished && newestDraft != null && version === newestDraft && draftIsRepoHead && workerUrl) {
|
|
10374
10640
|
return workerUrl;
|
|
10375
10641
|
}
|
|
10376
10642
|
return resolveWidgetShapeVersionPreviewState(widgetId)?.url ?? null;
|
|
@@ -10389,6 +10655,8 @@ Requirements:
|
|
|
10389
10655
|
}
|
|
10390
10656
|
|
|
10391
10657
|
function resolveWidgetShapeVersionLabel(widgetId: string): string | null {
|
|
10658
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10659
|
+
if (Number.isFinite(restored)) return `v${restored}`;
|
|
10392
10660
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10393
10661
|
const ps = getPanelDisplayState(widgetId);
|
|
10394
10662
|
const liveVersion =
|
|
@@ -10451,6 +10719,7 @@ Requirements:
|
|
|
10451
10719
|
dateLabel?: string;
|
|
10452
10720
|
isLive?: boolean;
|
|
10453
10721
|
isDraft?: boolean;
|
|
10722
|
+
commitSha?: string;
|
|
10454
10723
|
}> = [];
|
|
10455
10724
|
for (const record of history) {
|
|
10456
10725
|
const version = Number(record.version);
|
|
@@ -10489,6 +10758,10 @@ Requirements:
|
|
|
10489
10758
|
}
|
|
10490
10759
|
|
|
10491
10760
|
function resolveWidgetShapeVersionSelectedId(widgetId: string): string | null {
|
|
10761
|
+
const menuVersion = dropdownVersionByWidgetId[widgetId];
|
|
10762
|
+
if (menuVersion) return menuVersion;
|
|
10763
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10764
|
+
if (Number.isFinite(restored)) return `v:${restored}`;
|
|
10492
10765
|
const source = shapePreviewSourceByWidgetId[widgetId];
|
|
10493
10766
|
if (source?.startsWith('v:')) return source;
|
|
10494
10767
|
if (source === 'preview') {
|
|
@@ -10504,8 +10777,21 @@ Requirements:
|
|
|
10504
10777
|
return liveVersion != null ? `v:${liveVersion}` : null;
|
|
10505
10778
|
}
|
|
10506
10779
|
|
|
10780
|
+
function selectVersionInDropdowns(widgetId: string, versionId: string): void {
|
|
10781
|
+
dropdownVersionByWidgetId = { ...dropdownVersionByWidgetId, [widgetId]: versionId };
|
|
10782
|
+
widgetShapeLivePreviewsRef?.setSelectedVersion?.(widgetId, versionId);
|
|
10783
|
+
panelDisplayRevision += 1;
|
|
10784
|
+
}
|
|
10785
|
+
|
|
10507
10786
|
function handleWidgetShapeVersionChange(widgetId: string, versionId: string, buildPreview = true): void {
|
|
10508
10787
|
if (!versionId) return;
|
|
10788
|
+
// Both dropdowns show the pick. Only the dropdown the user clicked reloads the preview.
|
|
10789
|
+
selectVersionInDropdowns(widgetId, versionId);
|
|
10790
|
+
if (!buildPreview) return;
|
|
10791
|
+
if (restoredViewingVersionByWidgetId[widgetId] != null) {
|
|
10792
|
+
const { [widgetId]: _dropped, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
10793
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
10794
|
+
}
|
|
10509
10795
|
// Re-selecting an already successful/in-flight preview must not reset its state.
|
|
10510
10796
|
const currentPreview = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10511
10797
|
if (shapePreviewSourceByWidgetId[widgetId] === versionId
|
|
@@ -10524,17 +10810,19 @@ Requirements:
|
|
|
10524
10810
|
};
|
|
10525
10811
|
syncWidgetShapeLivePreviews();
|
|
10526
10812
|
panelDisplayRevision += 1;
|
|
10527
|
-
//
|
|
10528
|
-
|
|
10529
|
-
//
|
|
10813
|
+
// Always compile the selected commit into an isolated Worker preview.
|
|
10814
|
+
// Published CDN artifacts are Live only — using them on Switch made
|
|
10815
|
+
// draft/restore look like the last publish even when git had moved.
|
|
10530
10816
|
if (versionId.startsWith('v:')) {
|
|
10531
10817
|
const version = Number(versionId.slice(2));
|
|
10532
|
-
const
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10818
|
+
const record = getShapePublishHistory(widgetId).find((row) => Number(row.version) === version);
|
|
10819
|
+
const isPublished = !!(record?.publishedAt ?? record?.published_at);
|
|
10820
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10821
|
+
const workerUrl = resolveWidgetShapeLivePreviewUrl(widgetId);
|
|
10822
|
+
const viewingHeadDraft = !isPublished && newestDraft != null && version === newestDraft && workerUrl && !restoredViewingVersionByWidgetId[widgetId];
|
|
10823
|
+
if (viewingHeadDraft) {
|
|
10536
10824
|
const detailsRef = getWidgetDetailsRef(widgetId);
|
|
10537
|
-
detailsRef?.showPublishedArtifact?.(
|
|
10825
|
+
detailsRef?.showPublishedArtifact?.(workerUrl);
|
|
10538
10826
|
} else {
|
|
10539
10827
|
void ensureWidgetShapeVersionPreview(widgetId, version);
|
|
10540
10828
|
}
|
|
@@ -10613,6 +10901,13 @@ Requirements:
|
|
|
10613
10901
|
/** Commit SHA for the version currently selected in the version menu (drives Head-at-commit badge). */
|
|
10614
10902
|
function resolveViewingCommitShaForPanel(widgetId: string): string | null {
|
|
10615
10903
|
const ps = getPanelDisplayState(widgetId);
|
|
10904
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10905
|
+
if (Number.isFinite(restored)) {
|
|
10906
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10907
|
+
const record = history.find((r) => Number(r.version) === Number(restored));
|
|
10908
|
+
const fromRestored = normalizeCommitSha(record?.commitSha ?? record?.commit_sha);
|
|
10909
|
+
if (fromRestored) return fromRestored;
|
|
10910
|
+
}
|
|
10616
10911
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10617
10912
|
if (source === 'preview') {
|
|
10618
10913
|
return normalizeCommitSha(ps.lastCommitId);
|
|
@@ -10663,18 +10958,28 @@ Requirements:
|
|
|
10663
10958
|
* debug mode and this label is what the turn shows.
|
|
10664
10959
|
*/
|
|
10665
10960
|
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10961
|
+
let versionPromptLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10962
|
+
let viewingVersionByWidgetId: Record<string, number | null> = {};
|
|
10666
10963
|
$: {
|
|
10667
10964
|
void shapePreviewSourceByWidgetId;
|
|
10668
10965
|
void shapeVersionPreviewByWidgetId;
|
|
10966
|
+
void restoredViewingVersionByWidgetId;
|
|
10967
|
+
void dropdownVersionByWidgetId;
|
|
10669
10968
|
void panelDisplayRevision;
|
|
10670
10969
|
const nextHeads: Record<string, string | null> = {};
|
|
10671
10970
|
const nextLabels: Record<string, Record<string, string>> = {};
|
|
10971
|
+
const nextPrompts: Record<string, Record<string, string>> = {};
|
|
10972
|
+
const nextViewing: Record<string, number | null> = {};
|
|
10672
10973
|
for (const widgetId of openWidgetPanelIds) {
|
|
10673
10974
|
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
10674
10975
|
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
10976
|
+
nextPrompts[widgetId] = buildVersionPromptLabelsForPanel(widgetId);
|
|
10977
|
+
nextViewing[widgetId] = viewingVersionNumberForPanel(widgetId);
|
|
10675
10978
|
}
|
|
10676
10979
|
headCommitShaByWidgetId = nextHeads;
|
|
10677
10980
|
versionLabelsByWidgetId = nextLabels;
|
|
10981
|
+
versionPromptLabelsByWidgetId = nextPrompts;
|
|
10982
|
+
viewingVersionByWidgetId = nextViewing;
|
|
10678
10983
|
}
|
|
10679
10984
|
|
|
10680
10985
|
/** Commit shas are keyed on 7 chars — that is what the chat summaries carry. */
|
|
@@ -10683,24 +10988,55 @@ Requirements:
|
|
|
10683
10988
|
return normalized ? normalized.slice(0, 7) : null;
|
|
10684
10989
|
}
|
|
10685
10990
|
|
|
10991
|
+
function assignVersionLabelKeys(labels: Record<string, string>, sha: string | null | undefined, label: string): void {
|
|
10992
|
+
const normalized = normalizeCommitSha(sha);
|
|
10993
|
+
if (!normalized) return;
|
|
10994
|
+
labels[normalized] = label;
|
|
10995
|
+
labels[normalized.slice(0, 7)] = label;
|
|
10996
|
+
labels[normalized.slice(0, 8)] = label;
|
|
10997
|
+
}
|
|
10998
|
+
|
|
10686
10999
|
function buildVersionLabelsForPanel(widgetId: string): Record<string, string> {
|
|
10687
11000
|
const labels: Record<string, string> = {};
|
|
10688
11001
|
const ps = getPanelDisplayState(widgetId);
|
|
10689
11002
|
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10690
11003
|
for (const record of history || []) {
|
|
10691
|
-
|
|
10692
|
-
|
|
11004
|
+
if (record?.version == null) continue;
|
|
11005
|
+
assignVersionLabelKeys(labels, record?.commitSha ?? record?.commit_sha, `Version ${record.version}`);
|
|
10693
11006
|
}
|
|
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
11007
|
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
11008
|
+
if (previewState?.versionId?.startsWith('v:')) {
|
|
11009
|
+
assignVersionLabelKeys(labels, previewState?.commitSha, `Version ${previewState.versionId.slice(2)}`);
|
|
11010
|
+
}
|
|
11011
|
+
return labels;
|
|
11012
|
+
}
|
|
11013
|
+
|
|
11014
|
+
function versionPromptKey(prompt: string | null | undefined): string | null {
|
|
11015
|
+
const text = String(prompt || '').replace(/\s+/g, ' ').trim();
|
|
11016
|
+
return text || null;
|
|
11017
|
+
}
|
|
11018
|
+
|
|
11019
|
+
/** User prompt → "Version N", for turns whose commit line was never saved. */
|
|
11020
|
+
function buildVersionPromptLabelsForPanel(widgetId: string): Record<string, string> {
|
|
11021
|
+
const labels: Record<string, string> = {};
|
|
11022
|
+
const ps = getPanelDisplayState(widgetId);
|
|
11023
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
11024
|
+
for (const record of history || []) {
|
|
11025
|
+
if (record?.version == null) continue;
|
|
11026
|
+
const key = versionPromptKey(record?.prompt);
|
|
11027
|
+
if (!key) continue;
|
|
11028
|
+
labels[key] = `Version ${record.version}`;
|
|
10700
11029
|
}
|
|
10701
11030
|
return labels;
|
|
10702
11031
|
}
|
|
10703
11032
|
|
|
11033
|
+
function viewingVersionNumberForPanel(widgetId: string): number | null {
|
|
11034
|
+
const selected = resolveWidgetShapeVersionSelectedId(widgetId);
|
|
11035
|
+
if (!selected?.startsWith('v:')) return null;
|
|
11036
|
+
const version = parseInt(selected.slice(2), 10);
|
|
11037
|
+
return Number.isFinite(version) ? version : null;
|
|
11038
|
+
}
|
|
11039
|
+
|
|
10704
11040
|
function syncWidgetShapeLivePreviews(): void {
|
|
10705
11041
|
measureSync('sync-widget-shape-previews', () => {
|
|
10706
11042
|
try {
|
|
@@ -10935,10 +11271,13 @@ Requirements:
|
|
|
10935
11271
|
if (embedded && deepLinkId && widgetId === deepLinkId) {
|
|
10936
11272
|
embeddedDeepLinkPanelDismissed = false;
|
|
10937
11273
|
}
|
|
10938
|
-
//
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
11274
|
+
// At most two chats. Opening a third closes the oldest one.
|
|
11275
|
+
const maxOpenWidgetChats = 2;
|
|
11276
|
+
if (!openWidgetPanelIds.includes(widgetId)) {
|
|
11277
|
+
while (openWidgetPanelIds.length >= maxOpenWidgetChats) {
|
|
11278
|
+
const oldestId = openWidgetPanelIds[0];
|
|
11279
|
+
if (!oldestId) break;
|
|
11280
|
+
closeWidgetDetailsPanel(oldestId, { fromShapeSwitch: true });
|
|
10942
11281
|
}
|
|
10943
11282
|
}
|
|
10944
11283
|
const isNewPanel = !openWidgetPanelIds.includes(widgetId);
|
|
@@ -11946,7 +12285,7 @@ Requirements:
|
|
|
11946
12285
|
* from whatever HEAD happens to contain.
|
|
11947
12286
|
*/
|
|
11948
12287
|
function canPublishDraftVersionForPanel(widgetId: string, version: number | null | undefined): boolean {
|
|
11949
|
-
if (version == null || version === '' || !Number.isFinite(Number(version))) return false;
|
|
12288
|
+
if (version == null || (typeof version === 'string' && version === '') || !Number.isFinite(Number(version))) return false;
|
|
11950
12289
|
if (isPublishing && publishingWidgetId === widgetId) return false;
|
|
11951
12290
|
if (isPublishing && publishingWidgetId !== null && publishingWidgetId !== widgetId) return false;
|
|
11952
12291
|
if (generatingWidgetIds[widgetId]) return false;
|
|
@@ -12075,7 +12414,7 @@ $: if ($widgetPublishStatus.timestamp && $widgetPublishStatus.widgetId) {
|
|
|
12075
12414
|
}
|
|
12076
12415
|
if (publishWidgetId) {
|
|
12077
12416
|
// console.log('[Publish] Refreshing canvas widget screenshot after successful publish');
|
|
12078
|
-
|
|
12417
|
+
scheduleQuietScreenshot(publishWidgetId, 2000);
|
|
12079
12418
|
}
|
|
12080
12419
|
if (embedded && onPublishComplete && publishWidgetId) {
|
|
12081
12420
|
onPublishComplete({
|
|
@@ -12123,7 +12462,7 @@ $: if ($widgetPublishStatus.widgetId === selectedWidgetId && $widgetPublishStatu
|
|
|
12123
12462
|
// console.log('[CrossTab/WS] Publish building status from WebSocket — disabling Publish button');
|
|
12124
12463
|
}
|
|
12125
12464
|
|
|
12126
|
-
// Agent harness repair status (DW Phase 2) — real-time via
|
|
12465
|
+
// Agent harness repair status (DW Phase 2) — real-time via codegen:output WebSocket.
|
|
12127
12466
|
$: if ($agentHarnessOutput?.timestamp) {
|
|
12128
12467
|
const raw = ($agentHarnessOutput.output || '').trim();
|
|
12129
12468
|
const line = raw.replace(/^🔧\s*/, '').trim();
|
|
@@ -12139,15 +12478,27 @@ $: if ($agentHarnessOutput?.timestamp) {
|
|
|
12139
12478
|
}
|
|
12140
12479
|
}
|
|
12141
12480
|
|
|
12142
|
-
// Worker preview ready via WebSocket event — reload canvas shape preview immediately
|
|
12481
|
+
// Worker preview ready via WebSocket event — reload canvas shape preview immediately.
|
|
12482
|
+
// The store keeps the last event forever, and this block writes state it also reads,
|
|
12483
|
+
// so without the event key it re-ran on every unrelated update: each re-run snapped the
|
|
12484
|
+
// version selector back to HEAD and cancelled an in-flight version preview.
|
|
12485
|
+
let lastWsWorkerPreviewEventKey: string | null = null;
|
|
12143
12486
|
$: if ($dynamicWorkerPreviewStatus.widgetId && $dynamicWorkerPreviewStatus.previewUrl) {
|
|
12144
12487
|
const wsReadyWidgetId = $dynamicWorkerPreviewStatus.widgetId;
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12488
|
+
const wsEventKey = `${wsReadyWidgetId}:${$dynamicWorkerPreviewStatus.previewUrl}:${$dynamicWorkerPreviewStatus.timestamp ?? ''}`;
|
|
12489
|
+
if (wsEventKey !== lastWsWorkerPreviewEventKey) {
|
|
12490
|
+
lastWsWorkerPreviewEventKey = wsEventKey;
|
|
12491
|
+
shapePreviewSourceByWidgetId = {
|
|
12492
|
+
...shapePreviewSourceByWidgetId,
|
|
12493
|
+
[wsReadyWidgetId]: 'preview',
|
|
12494
|
+
};
|
|
12495
|
+
if (codegenCaptureBlocked()) {
|
|
12496
|
+
console.log('[CreatorApp] Defer canvas Worker preview reload until codegen finishes', wsReadyWidgetId.substring(0, 8));
|
|
12497
|
+
} else {
|
|
12498
|
+
syncWidgetShapeLivePreviews();
|
|
12499
|
+
widgetShapeLivePreviewsRef?.requestReload?.(wsReadyWidgetId);
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12151
12502
|
}
|
|
12152
12503
|
|
|
12153
12504
|
// ===================== Publish History =====================
|
|
@@ -12823,6 +13174,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12823
13174
|
* parent-page [FreezeReport] (long tasks attributed to the iframe).
|
|
12824
13175
|
*/
|
|
12825
13176
|
const screenshotCaptureInFlight = new Set<string>();
|
|
13177
|
+
/** Bumped when a generation starts so in-flight html2canvas results are dropped. */
|
|
13178
|
+
let screenshotGenerationEpoch = 0;
|
|
12826
13179
|
|
|
12827
13180
|
/**
|
|
12828
13181
|
* Called when a Widget Image shape is clicked on the canvas.
|
|
@@ -14913,7 +15266,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14913
15266
|
{@const panelInstanceKey = panelInstanceKeyByWidgetId[panelWidgetId] ?? panelWidgetId}
|
|
14914
15267
|
{@const panelWidget = widgetsById.get(panelWidgetId) ?? null}
|
|
14915
15268
|
{@const isPanelFocused = focusedPanelWidgetId === panelWidgetId}
|
|
14916
|
-
{@const ps = (panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
15269
|
+
{@const ps = rev(panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
14917
15270
|
<!--
|
|
14918
15271
|
Same published-version resolution the WidgetDetails instance below gets.
|
|
14919
15272
|
`ps.lastPublishedVersion` alone is only hydrated from the focused panel /
|
|
@@ -14924,9 +15277,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14924
15277
|
{@const panelPublishedVersion = isDraftConvertId(panelWidgetId)
|
|
14925
15278
|
? null
|
|
14926
15279
|
: (ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null))}
|
|
14927
|
-
{@const panelHasUnpublishedChanges = (panelDisplayRevision,
|
|
14928
|
-
{@const panelHasCompileErrorState = (panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
14929
|
-
{@const panelHasInfraPreviewError = (panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
15280
|
+
{@const panelHasUnpublishedChanges = rev(panelDisplayRevision, hasUnpublishedChangesForPanel(panelWidgetId), widgets.length)}
|
|
15281
|
+
{@const panelHasCompileErrorState = rev(panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
15282
|
+
{@const panelHasInfraPreviewError = rev(panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
14930
15283
|
<svelte:component this={WidgetDetails}
|
|
14931
15284
|
bind:this={widgetDetailsRefs[panelInstanceKey]}
|
|
14932
15285
|
userSession={$userSession}
|
|
@@ -15255,17 +15608,19 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15255
15608
|
{@const defaultVersionSource = panelPublishedVersion !== null
|
|
15256
15609
|
? `v:${panelPublishedVersion}`
|
|
15257
15610
|
: (newestDraft ? `v:${newestDraft.version}` : null)}
|
|
15258
|
-
{@const
|
|
15259
|
-
{@const
|
|
15611
|
+
{@const restoredVersion = restoredViewingVersionByWidgetId[panelWidgetId]}
|
|
15612
|
+
{@const menuVersion = dropdownVersionByWidgetId[panelWidgetId]}
|
|
15613
|
+
{@const shapeSource = menuVersion || shapePreviewSourceByWidgetId[panelWidgetId] || defaultVersionSource}
|
|
15614
|
+
{@const viewingVersion = Number.isFinite(restoredVersion)
|
|
15615
|
+
? restoredVersion
|
|
15616
|
+
: shapeSource && shapeSource.startsWith('v:')
|
|
15260
15617
|
? parseInt(shapeSource.slice(2), 10)
|
|
15261
15618
|
: shapeSource === 'preview'
|
|
15262
|
-
|
|
15263
|
-
// version — falling back to panelPublishedVersion produced `vnull`
|
|
15264
|
-
// right after generation AND showed the stale Live version when a
|
|
15265
|
-
// draft existed. The newest draft IS this HEAD's version.
|
|
15266
|
-
? (newestDraft ? newestDraft.version : panelPublishedVersion)
|
|
15619
|
+
? (getNewestDraftVersionId(panelWidgetId) ?? panelPublishedVersion)
|
|
15267
15620
|
: panelPublishedVersion}
|
|
15268
|
-
{@const viewingIsLive = Number(
|
|
15621
|
+
{@const viewingIsLive = !Number.isFinite(restoredVersion)
|
|
15622
|
+
&& shapeSource !== 'preview'
|
|
15623
|
+
&& Number(viewingVersion) === Number(panelPublishedVersion)}
|
|
15269
15624
|
<!--
|
|
15270
15625
|
A version is a Draft until it is published. The history is one
|
|
15271
15626
|
sequence (`v1, v2, v3…`), so "draft" is read from the record's
|
|
@@ -15466,9 +15821,16 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15466
15821
|
<Button
|
|
15467
15822
|
onclick={() => {
|
|
15468
15823
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15824
|
+
patchPanelSnapshot(panelWidgetId, {
|
|
15825
|
+
lastGenerationFailed: false,
|
|
15826
|
+
lastGenerationError: null,
|
|
15827
|
+
generateCodeStatus: 'Ready',
|
|
15828
|
+
});
|
|
15829
|
+
if (isPanelFocused || focusedPanelWidgetId === panelWidgetId) {
|
|
15830
|
+
lastGenerationFailed = false;
|
|
15831
|
+
lastGenerationError = null;
|
|
15832
|
+
generateCodeStatus = 'Ready';
|
|
15833
|
+
}
|
|
15472
15834
|
}}
|
|
15473
15835
|
variant="outline"
|
|
15474
15836
|
size="sm"
|
|
@@ -15483,8 +15845,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15483
15845
|
</svelte:fragment>
|
|
15484
15846
|
|
|
15485
15847
|
<svelte:fragment slot="code-generation">
|
|
15848
|
+
{@const chatUserHeight = rev(panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15486
15849
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
15487
|
-
{@const chatUserHeight = (panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15488
15850
|
<div
|
|
15489
15851
|
class="generation-scroll-ct flex flex-col h-full min-h-0 overflow-y-auto border-t border-gray-200"
|
|
15490
15852
|
style="padding: 0;"
|
|
@@ -15530,6 +15892,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15530
15892
|
}}
|
|
15531
15893
|
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15532
15894
|
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15895
|
+
versionLabelByPrompt={versionPromptLabelsByWidgetId[panelWidgetId] || {}}
|
|
15896
|
+
viewingVersion={viewingVersionByWidgetId[panelWidgetId] ?? null}
|
|
15533
15897
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15534
15898
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
15535
15899
|
onUploadChatFile={handleChatUploadFile}
|
|
@@ -15539,8 +15903,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15539
15903
|
onLoadConversations={handleLoadConversations}
|
|
15540
15904
|
onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
|
|
15541
15905
|
onCreateConversation={handleCreateConversation}
|
|
15542
|
-
onSaveMessage={(conversationId, content, messageType, attachments) =>
|
|
15543
|
-
handleSaveMessage(conversationId, content, messageType, attachments, panelWidgetId)
|
|
15906
|
+
onSaveMessage={(conversationId, content, messageType, attachments, suggestions) =>
|
|
15907
|
+
handleSaveMessage(conversationId, content, messageType, attachments, suggestions, panelWidgetId)
|
|
15544
15908
|
}
|
|
15545
15909
|
onRestoreCheckpointBackend={(conversationId, checkpointMessageId) =>
|
|
15546
15910
|
handleRestoreCheckpoint(conversationId, checkpointMessageId, panelWidgetId)
|
|
@@ -15831,11 +16195,13 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15831
16195
|
|
|
15832
16196
|
<!-- Embed Dimensions -->
|
|
15833
16197
|
<div class="embed-dimensions-ct flex flex-col gap-2">
|
|
15834
|
-
<label class="text-xs font-medium text-gray-600">Dimensions</label>
|
|
16198
|
+
<label class="text-xs font-medium text-gray-600" for="embed-width-input">Dimensions</label>
|
|
15835
16199
|
<div class="embed-dimensions-row flex items-center gap-2">
|
|
15836
16200
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15837
16201
|
<span class="text-[10px] text-gray-400 font-medium">W</span>
|
|
15838
16202
|
<input
|
|
16203
|
+
id="embed-width-input"
|
|
16204
|
+
aria-label="Embed width"
|
|
15839
16205
|
type="number"
|
|
15840
16206
|
min="100"
|
|
15841
16207
|
max="4000"
|
|
@@ -15854,6 +16220,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15854
16220
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15855
16221
|
<span class="text-[10px] text-gray-400 font-medium">H</span>
|
|
15856
16222
|
<input
|
|
16223
|
+
id="embed-height-input"
|
|
16224
|
+
aria-label="Embed height"
|
|
15857
16225
|
type="number"
|
|
15858
16226
|
min="50"
|
|
15859
16227
|
max="4000"
|
|
@@ -15899,6 +16267,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15899
16267
|
<!-- Embed Code Block -->
|
|
15900
16268
|
<div class="embed-code-ct flex flex-col gap-2">
|
|
15901
16269
|
<div class="embed-code-header flex items-center justify-between">
|
|
16270
|
+
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
15902
16271
|
<label class="text-xs font-medium text-gray-600">Embed Code ({EMBED_FORMAT_LABELS[effectiveFormat]})</label>
|
|
15903
16272
|
<!-- svelte-ignore a11y_consider_explicit_label -->
|
|
15904
16273
|
<button
|
|
@@ -16020,8 +16389,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16020
16389
|
<p class="text-sm text-gray-600 mb-4">
|
|
16021
16390
|
Permanently delete <strong>{adminDeleteWidget?.name || adminDeleteWidget?.id?.slice(0, 8)}</strong>? This action cannot be undone.
|
|
16022
16391
|
</p>
|
|
16023
|
-
<label class="block text-sm font-medium text-gray-700 mb-1">Admin API Key</label>
|
|
16392
|
+
<label class="block text-sm font-medium text-gray-700 mb-1" for="admin-delete-api-key">Admin API Key</label>
|
|
16024
16393
|
<input
|
|
16394
|
+
id="admin-delete-api-key"
|
|
16025
16395
|
type="password"
|
|
16026
16396
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-red-500 focus:border-red-500 outline-none"
|
|
16027
16397
|
placeholder="Enter admin key..."
|
|
@@ -16280,11 +16650,6 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16280
16650
|
:global([data-dialog-overlay]) {
|
|
16281
16651
|
z-index: 9998 !important;
|
|
16282
16652
|
}
|
|
16283
|
-
.generation-models-header select:focus,
|
|
16284
|
-
.generation-models-header select:focus-visible {
|
|
16285
|
-
outline: none;
|
|
16286
|
-
box-shadow: none;
|
|
16287
|
-
}
|
|
16288
16653
|
.wc-version-history-toggle:focus,
|
|
16289
16654
|
.wc-version-history-toggle:focus-visible,
|
|
16290
16655
|
.wc-live-history-backdrop:focus,
|