@widgetic/creator 0.3.67 → 0.3.69
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 +495 -164
- package/dist/components/WidgetDetails.svelte +32 -3
- package/dist/components/WidgetDetails.svelte.d.ts +2 -0
- package/dist/components/WidgetShapeLivePreviews.svelte +118 -45
- 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/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;
|
|
@@ -535,6 +535,7 @@
|
|
|
535
535
|
}
|
|
536
536
|
installLongTaskFreezeReporter();
|
|
537
537
|
installFreezeWatchdog();
|
|
538
|
+
console.info('[FreezeWatchdog] Freeze reports log on this page. In DevTools, pick the top frame — not a widget iframe.');
|
|
538
539
|
});
|
|
539
540
|
|
|
540
541
|
/**
|
|
@@ -871,6 +872,21 @@
|
|
|
871
872
|
function isAnyWidgetGenerating(): boolean {
|
|
872
873
|
return isGeneratingCode || Object.keys(generatingWidgetIds).some((id) => generatingWidgetIds[id]);
|
|
873
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
|
+
}
|
|
874
890
|
const widgetAbortControllers = new Map<string, AbortController>();
|
|
875
891
|
let currentGenerationOperationId: string | null = null;
|
|
876
892
|
let generateCodeStatus: string | null = null;
|
|
@@ -905,7 +921,7 @@
|
|
|
905
921
|
// Chat component reference and context (focused panel)
|
|
906
922
|
let chatMethods: {
|
|
907
923
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
908
|
-
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?:
|
|
924
|
+
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: ClarificationSuggestion[]) => void;
|
|
909
925
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
910
926
|
getConversationId: () => string | null;
|
|
911
927
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -1041,7 +1057,7 @@
|
|
|
1041
1057
|
fileSize?: number;
|
|
1042
1058
|
}>,
|
|
1043
1059
|
sourceWidgetId?: string,
|
|
1044
|
-
sendOptions?: { skipIntentGate?: boolean },
|
|
1060
|
+
sendOptions?: { skipIntentGate?: boolean; chipIntent?: ClarificationSuggestion['intent'] },
|
|
1045
1061
|
) {
|
|
1046
1062
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1047
1063
|
if (!widgetId) return;
|
|
@@ -1165,6 +1181,7 @@
|
|
|
1165
1181
|
promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
|
|
1166
1182
|
persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
|
|
1167
1183
|
skipIntentGate: sendOptions?.skipIntentGate === true,
|
|
1184
|
+
chipIntent: sendOptions?.chipIntent,
|
|
1168
1185
|
});
|
|
1169
1186
|
}
|
|
1170
1187
|
|
|
@@ -1626,6 +1643,7 @@
|
|
|
1626
1643
|
buildUserPromptMessageContent(prompt),
|
|
1627
1644
|
'user',
|
|
1628
1645
|
persistAttachments.length > 0 ? persistAttachments : undefined,
|
|
1646
|
+
undefined,
|
|
1629
1647
|
widgetId,
|
|
1630
1648
|
);
|
|
1631
1649
|
const savedAtt = saved?.attachments || saved?.data?.attachments || [];
|
|
@@ -1726,32 +1744,46 @@
|
|
|
1726
1744
|
iframe: HTMLIFrameElement,
|
|
1727
1745
|
timeoutMs = 6000,
|
|
1728
1746
|
): Promise<{ data: string | null; error: string | null }> {
|
|
1729
|
-
if (
|
|
1747
|
+
if (codegenCaptureBlocked()) {
|
|
1730
1748
|
return Promise.resolve({ data: null, error: 'generation in progress' });
|
|
1731
1749
|
}
|
|
1750
|
+
const captureEpoch = screenshotGenerationEpoch;
|
|
1732
1751
|
return new Promise((resolve) => {
|
|
1733
1752
|
if (!iframe.contentWindow) {
|
|
1734
1753
|
resolve({ data: null, error: 'no-content-window' });
|
|
1735
1754
|
return;
|
|
1736
1755
|
}
|
|
1737
|
-
const
|
|
1756
|
+
const finish = (result: { data: string | null; error: string | null }) => {
|
|
1738
1757
|
window.removeEventListener('message', handler);
|
|
1739
|
-
|
|
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' });
|
|
1740
1767
|
}, timeoutMs);
|
|
1741
1768
|
|
|
1742
1769
|
function handler(event: MessageEvent) {
|
|
1743
1770
|
if (event.data?.type !== 'widgetic:screenshot') return;
|
|
1744
|
-
// Ignore other widgets and synthetic replies with no source window.
|
|
1745
1771
|
if (event.source !== iframe.contentWindow) return;
|
|
1746
|
-
|
|
1747
|
-
window.removeEventListener('message', handler);
|
|
1748
|
-
resolve({
|
|
1772
|
+
finish({
|
|
1749
1773
|
data: typeof event.data.data === 'string' ? event.data.data : null,
|
|
1750
1774
|
error: typeof event.data.error === 'string' ? event.data.error : null,
|
|
1751
1775
|
});
|
|
1752
1776
|
}
|
|
1753
1777
|
window.addEventListener('message', handler);
|
|
1754
|
-
|
|
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
|
+
}
|
|
1755
1787
|
});
|
|
1756
1788
|
}
|
|
1757
1789
|
|
|
@@ -1764,7 +1796,7 @@
|
|
|
1764
1796
|
// startup on the main thread this page shares with it, and the capture that
|
|
1765
1797
|
// follows is refused during a generation anyway (`requestIframeScreenshot`),
|
|
1766
1798
|
// so the whole path is skipped rather than paying the load for nothing.
|
|
1767
|
-
if (
|
|
1799
|
+
if (codegenCaptureBlocked()) {
|
|
1768
1800
|
console.log('[Chat→Screenshot] Skip hidden Worker capture — codegen in progress', widgetId?.substring(0, 8));
|
|
1769
1801
|
return null;
|
|
1770
1802
|
}
|
|
@@ -1816,14 +1848,14 @@
|
|
|
1816
1848
|
|
|
1817
1849
|
/** Explicit Refresh only: capture the loaded frame without opening a panel or publishing. */
|
|
1818
1850
|
async function handleWidgetShapeRefresh(widgetId, iframe, isCurrentFrame) {
|
|
1819
|
-
if (
|
|
1851
|
+
if (codegenCaptureBlocked()) return;
|
|
1820
1852
|
const capturedCanvasId = getEffectiveCanvasId();
|
|
1821
1853
|
const capturedCanvas = canvasRef;
|
|
1822
1854
|
const capturedSource = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
1823
1855
|
// Frame/intent identity owns this capture, not focused-panel HEAD hydration
|
|
1824
1856
|
// or another widget's global generation operation.
|
|
1825
1857
|
const isCurrent = () => isCurrentFrame() && !!getWidgetById(widgetId)
|
|
1826
|
-
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !
|
|
1858
|
+
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !codegenCaptureBlocked()
|
|
1827
1859
|
&& capturedCanvasId === getEffectiveCanvasId() && capturedCanvas === canvasRef
|
|
1828
1860
|
&& capturedSource === (shapePreviewSourceByWidgetId[widgetId] || 'live')
|
|
1829
1861
|
&& resolveLiveWidgetShapeIframe(widgetId) === iframe;
|
|
@@ -1854,7 +1886,7 @@
|
|
|
1854
1886
|
}
|
|
1855
1887
|
// Never run background / refresh screenshot captures while code generation is running —
|
|
1856
1888
|
// html2canvas inside any same-origin iframe freezes the browser main thread.
|
|
1857
|
-
if ((
|
|
1889
|
+
if (codegenCaptureBlocked()) {
|
|
1858
1890
|
console.log('[Chat→Screenshot] Skip capture — generation in progress');
|
|
1859
1891
|
return null;
|
|
1860
1892
|
}
|
|
@@ -1882,6 +1914,10 @@
|
|
|
1882
1914
|
|
|
1883
1915
|
// 1) Always try the visible live iframe first (published HTML from Worker includes screenshot script)
|
|
1884
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
|
+
}
|
|
1885
1921
|
console.log('[Chat→Screenshot] Live WidgetShape iframe', {
|
|
1886
1922
|
targetId: targetId.substring(0, 8),
|
|
1887
1923
|
isWorker: isWorkerPreviewUrl(liveSrc),
|
|
@@ -2050,7 +2086,14 @@
|
|
|
2050
2086
|
messageContent: m.message_content || m.messageContent || '',
|
|
2051
2087
|
messageType: m.message_type || m.messageType || 'user',
|
|
2052
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,
|
|
2053
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,
|
|
2054
2097
|
userId: m.user_id || m.userId || '',
|
|
2055
2098
|
createdAt: new Date(m.created_at || m.createdAt),
|
|
2056
2099
|
updatedAt: new Date(m.updated_at || m.updatedAt || m.created_at || m.createdAt),
|
|
@@ -2223,6 +2266,7 @@
|
|
|
2223
2266
|
file_size?: number | null;
|
|
2224
2267
|
display_order?: number;
|
|
2225
2268
|
}>,
|
|
2269
|
+
suggestions?: ClarificationSuggestion[],
|
|
2226
2270
|
persistWidgetId?: string | null,
|
|
2227
2271
|
): Promise<any> {
|
|
2228
2272
|
try {
|
|
@@ -2258,25 +2302,39 @@
|
|
|
2258
2302
|
user_id: userId,
|
|
2259
2303
|
is_commit: false,
|
|
2260
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 } : {}),
|
|
2261
2308
|
};
|
|
2262
|
-
|
|
2309
|
+
const persistMessage = async (payload: typeof createPayload) => {
|
|
2263
2310
|
const response = await messagesClient.createMessageRaw({
|
|
2264
2311
|
conversationId,
|
|
2265
|
-
createMessageRequest:
|
|
2312
|
+
createMessageRequest: payload,
|
|
2266
2313
|
});
|
|
2267
2314
|
const json = await response.raw.json();
|
|
2268
|
-
// console.log('[Chat→Backend] Saved message to conversation:', conversationId);
|
|
2269
2315
|
const saved = json?.data || json;
|
|
2270
2316
|
const savedAtt = saved?.attachments;
|
|
2271
2317
|
return {
|
|
2272
2318
|
...saved,
|
|
2273
|
-
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt :
|
|
2319
|
+
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt : (payload.attachments || []),
|
|
2274
2320
|
};
|
|
2321
|
+
};
|
|
2322
|
+
try {
|
|
2323
|
+
return await persistMessage(createPayload);
|
|
2275
2324
|
} catch (withAttachmentsError) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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);
|
|
2280
2338
|
throw withAttachmentsError;
|
|
2281
2339
|
}
|
|
2282
2340
|
} catch (error) {
|
|
@@ -2317,34 +2375,47 @@
|
|
|
2317
2375
|
null;
|
|
2318
2376
|
// console.log('[Chat→Backend] Git rollback result:', gitRollback, { undoMode, targetCommit, panelWidgetId });
|
|
2319
2377
|
if (gitRollback?.success) {
|
|
2320
|
-
//
|
|
2321
|
-
// artifact —
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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}`);
|
|
2341
2405
|
}
|
|
2342
2406
|
}
|
|
2407
|
+
shapePreviewSourceByWidgetId = {
|
|
2408
|
+
...shapePreviewSourceByWidgetId,
|
|
2409
|
+
[panelWidgetId]: 'preview',
|
|
2410
|
+
};
|
|
2411
|
+
getWidgetDetailsRef(panelWidgetId)?.clearCodegenPreviewFresh?.();
|
|
2412
|
+
syncWidgetShapeLivePreviews();
|
|
2413
|
+
panelDisplayRevision += 1;
|
|
2343
2414
|
}
|
|
2344
2415
|
showToast('success', undoMode ? 'Change undone' : 'Checkpoint restored', {
|
|
2345
2416
|
description: undoMode
|
|
2346
|
-
? `Code reverted to commit ${formatCommitShaShort(
|
|
2347
|
-
: `Widget code at commit ${formatCommitShaShort(
|
|
2417
|
+
? `Code reverted to commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`
|
|
2418
|
+
: `Widget code at commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`,
|
|
2348
2419
|
duration: 6000,
|
|
2349
2420
|
});
|
|
2350
2421
|
liveDesignValues = {};
|
|
@@ -2353,18 +2424,12 @@
|
|
|
2353
2424
|
compositionDesignSchema = null;
|
|
2354
2425
|
compositionContentSchema = null;
|
|
2355
2426
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
2356
|
-
_isRestoringCheckpoint =
|
|
2357
|
-
if (previewFromVersionCache) {
|
|
2358
|
-
// Immutable artifact already serves the preview; no rebuild ladder.
|
|
2359
|
-
syncWidgetShapeLivePreviews();
|
|
2360
|
-
setTimeout(() => handleRefreshScreenshot(panelWidgetId, { quiet: true }), 2000);
|
|
2361
|
-
return { ...data, previewFromVersionCache };
|
|
2362
|
-
}
|
|
2427
|
+
_isRestoringCheckpoint = true;
|
|
2363
2428
|
const panelRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : widgetDetails;
|
|
2364
2429
|
const scheduleBuildRetry = async () => {
|
|
2365
|
-
const delays = [2500, 5000, 8000];
|
|
2430
|
+
const delays = [400, 2500, 5000, 8000];
|
|
2366
2431
|
for (let i = 0; i < delays.length; i++) {
|
|
2367
|
-
await new Promise((r) => setTimeout(r, delays[i]));
|
|
2432
|
+
if (i > 0) await new Promise((r) => setTimeout(r, delays[i]));
|
|
2368
2433
|
if (i === 0) suppressNextBuildFromRepoChatMessage = true;
|
|
2369
2434
|
try {
|
|
2370
2435
|
const activeRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : panelRef;
|
|
@@ -2378,7 +2443,9 @@
|
|
|
2378
2443
|
[panelWidgetId]: 'preview',
|
|
2379
2444
|
};
|
|
2380
2445
|
syncWidgetShapeLivePreviews();
|
|
2381
|
-
|
|
2446
|
+
widgetShapeLivePreviewsRef?.requestReload?.(panelWidgetId);
|
|
2447
|
+
panelDisplayRevision += 1;
|
|
2448
|
+
scheduleIdleThumbnailPersist(panelWidgetId);
|
|
2382
2449
|
}
|
|
2383
2450
|
return;
|
|
2384
2451
|
}
|
|
@@ -3938,9 +4005,7 @@
|
|
|
3938
4005
|
};
|
|
3939
4006
|
await compositionsClient.updateComposition({
|
|
3940
4007
|
id: selectedComposition.id,
|
|
3941
|
-
|
|
3942
|
-
// the generated UpdateComposition model predates that field.
|
|
3943
|
-
updateComposition: { embedSettings } as unknown as UpdateComposition
|
|
4008
|
+
updateComposition: { embedSettings }
|
|
3944
4009
|
});
|
|
3945
4010
|
selectedComposition = { ...selectedComposition, embedSettings };
|
|
3946
4011
|
embedDimensionsChanged = false;
|
|
@@ -4079,6 +4144,11 @@
|
|
|
4079
4144
|
|
|
4080
4145
|
// debugMode — current ON/OFF state. User can still toggle when the UI is available.
|
|
4081
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
|
+
}
|
|
4082
4152
|
// console.log('[WidgetCreator] Debug from host/env', {
|
|
4083
4153
|
// hostProvidedDebug,
|
|
4084
4154
|
// initialDebugMode,
|
|
@@ -4263,6 +4333,11 @@
|
|
|
4263
4333
|
function toggleDebugMode() {
|
|
4264
4334
|
if (!debugModeAvailable) return;
|
|
4265
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
|
+
}
|
|
4266
4341
|
// When turning OFF: switches to default model (from API config), mock console
|
|
4267
4342
|
// When turning ON: enables model selector, real-time console
|
|
4268
4343
|
const effectiveModel = debugMode ? userSelectedModelId : defaultModelId;
|
|
@@ -4461,9 +4536,8 @@
|
|
|
4461
4536
|
const anyW = widget as { thumbnailUrl?: string | null; thumbnail_url?: string | null } | null;
|
|
4462
4537
|
const hasThumb = !!(anyW?.thumbnailUrl || anyW?.thumbnail_url);
|
|
4463
4538
|
if (!hasThumb) {
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
}, 1200);
|
|
4539
|
+
// A capture 1s after open runs html2canvas on the shared main thread.
|
|
4540
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
4467
4541
|
}
|
|
4468
4542
|
return true;
|
|
4469
4543
|
}
|
|
@@ -5598,6 +5672,52 @@
|
|
|
5598
5672
|
}
|
|
5599
5673
|
|
|
5600
5674
|
// ─── Create new canvas ───────────────────────────────────────────────────────
|
|
5675
|
+
/** New-widget opens on a local canvas. The DB row is created on the first real edit. */
|
|
5676
|
+
let canvasRowDeferred = false;
|
|
5677
|
+
let canvasRowCreatePromise: Promise<string | null> | null = null;
|
|
5678
|
+
|
|
5679
|
+
async function ensureCanvasRowForFirstEdit(): Promise<string | null> {
|
|
5680
|
+
const existingId = typeof canvasId === 'string' ? canvasId.trim() : '';
|
|
5681
|
+
if (existingId) return existingId;
|
|
5682
|
+
if (canvasRowCreatePromise) return canvasRowCreatePromise;
|
|
5683
|
+
canvasRowCreatePromise = (async () => {
|
|
5684
|
+
if (!canvasesClient || !$userSession.user) {
|
|
5685
|
+
console.warn('[CreatorApp] First edit not persisted — canvas client is not ready');
|
|
5686
|
+
return null;
|
|
5687
|
+
}
|
|
5688
|
+
try {
|
|
5689
|
+
const usedNames = new Set(userCanvases.map((c) => (c.name || '').trim()).filter(Boolean));
|
|
5690
|
+
let canvasIndex = userCanvases.length + 1;
|
|
5691
|
+
let newName = `Canvas ${canvasIndex}`;
|
|
5692
|
+
while (usedNames.has(newName)) {
|
|
5693
|
+
canvasIndex += 1;
|
|
5694
|
+
newName = `Canvas ${canvasIndex}`;
|
|
5695
|
+
}
|
|
5696
|
+
const createdRaw = await canvasesClient.createCanvas({
|
|
5697
|
+
createCanvasRequest: { persistenceKey: newName }
|
|
5698
|
+
});
|
|
5699
|
+
const createdId = unwrapCreatedCanvasId(createdRaw);
|
|
5700
|
+
if (!createdId) {
|
|
5701
|
+
console.warn('[CreatorApp] First-edit canvas create response missing id');
|
|
5702
|
+
return null;
|
|
5703
|
+
}
|
|
5704
|
+
userCanvases = [{ id: createdId, name: newName, hasContent: true }, ...userCanvases];
|
|
5705
|
+
rememberCanvasId(createdId);
|
|
5706
|
+
activeCanvasName = newName;
|
|
5707
|
+
canvasRowDeferred = false;
|
|
5708
|
+
syncEmbeddedCanvasUrl(createdId);
|
|
5709
|
+
console.log('[CreatorApp] Canvas row created on first edit', createdId);
|
|
5710
|
+
return createdId;
|
|
5711
|
+
} catch (err) {
|
|
5712
|
+
console.error('[CreatorApp] Failed to create canvas on first edit:', err);
|
|
5713
|
+
return null;
|
|
5714
|
+
} finally {
|
|
5715
|
+
canvasRowCreatePromise = null;
|
|
5716
|
+
}
|
|
5717
|
+
})();
|
|
5718
|
+
return canvasRowCreatePromise;
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5601
5721
|
async function createNewCanvas(options: { skipSaveCurrent?: boolean } = {}): Promise<string | null> {
|
|
5602
5722
|
if (!canvasesClient || !$userSession.user) {
|
|
5603
5723
|
console.warn('[WidgetCreator] createNewCanvas skipped — missing client or user');
|
|
@@ -5702,24 +5822,10 @@
|
|
|
5702
5822
|
selectedWidgetId = null;
|
|
5703
5823
|
selectedWidget = null;
|
|
5704
5824
|
beginBlankCanvasSession('site New widget');
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
} else {
|
|
5710
|
-
const newCanvasId = await createNewCanvas({ skipSaveCurrent: true });
|
|
5711
|
-
if (!newCanvasId) {
|
|
5712
|
-
showToast('error', 'Could not create a blank canvas', {
|
|
5713
|
-
description: 'Try reloading the page to get a fresh canvas.',
|
|
5714
|
-
duration: 8000
|
|
5715
|
-
});
|
|
5716
|
-
} else {
|
|
5717
|
-
// Replace `new_widget=1` with the concrete canvas_id so a page refresh
|
|
5718
|
-
// reloads THIS canvas (and its sketch) instead of re-running the
|
|
5719
|
-
// new-widget bootstrap and creating yet another blank canvas row.
|
|
5720
|
-
syncEmbeddedCanvasUrl(newCanvasId);
|
|
5721
|
-
}
|
|
5722
|
-
}
|
|
5825
|
+
rememberCanvasId(null);
|
|
5826
|
+
canvasRowDeferred = true;
|
|
5827
|
+
// No DB row yet. Autosave creates one on the first edit that has shapes.
|
|
5828
|
+
// An untouched new-widget refresh stays on new_widget=1 and writes nothing.
|
|
5723
5829
|
// F3: New-widget flow starts with an EMPTY canvas — no widget record, no
|
|
5724
5830
|
// WidgetShape, no auto-opened Widget Details. The widget record is created
|
|
5725
5831
|
// only when the user sketches something and clicks "Convert to Widget".
|
|
@@ -7155,6 +7261,7 @@
|
|
|
7155
7261
|
typeof crypto !== 'undefined' && crypto.randomUUID
|
|
7156
7262
|
? crypto.randomUUID()
|
|
7157
7263
|
: `${Date.now()}-new-widget`;
|
|
7264
|
+
if (!canvasId) await ensureCanvasRowForFirstEdit();
|
|
7158
7265
|
const response = await widgetsClient.createWidget(
|
|
7159
7266
|
{
|
|
7160
7267
|
newWidget: {
|
|
@@ -7740,11 +7847,18 @@
|
|
|
7740
7847
|
const ctx = canvas.getContext('2d');
|
|
7741
7848
|
if (!ctx) return dataUrl;
|
|
7742
7849
|
ctx.drawImage(img, 0, 0, width, height);
|
|
7850
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
7851
|
+
const encode = (quality: number) =>
|
|
7852
|
+
new Promise<string>((resolve) => {
|
|
7853
|
+
setTimeout(() => {
|
|
7854
|
+
resolve(canvas.toDataURL('image/jpeg', quality));
|
|
7855
|
+
}, 0);
|
|
7856
|
+
});
|
|
7743
7857
|
let quality = 0.82;
|
|
7744
|
-
let out =
|
|
7858
|
+
let out = await encode(quality);
|
|
7745
7859
|
while (out.length > maxPayloadChars && quality > 0.45) {
|
|
7746
7860
|
quality -= 0.08;
|
|
7747
|
-
out =
|
|
7861
|
+
out = await encode(quality);
|
|
7748
7862
|
}
|
|
7749
7863
|
// console.log('[WidgetCreator] Compressed reference image for codegen', {
|
|
7750
7864
|
// fromChars: dataUrl.length,
|
|
@@ -7823,6 +7937,7 @@
|
|
|
7823
7937
|
*/
|
|
7824
7938
|
/** Debounced quiet sync: live Worker preview → canvas WidgetShape thumbnail. */
|
|
7825
7939
|
const canvasShapePreviewSyncTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7940
|
+
const quietScreenshotTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7826
7941
|
function cancelCanvasShapePreviewSync(widgetId?: string | null): void {
|
|
7827
7942
|
if (widgetId) {
|
|
7828
7943
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
@@ -7833,13 +7948,86 @@
|
|
|
7833
7948
|
for (const timer of canvasShapePreviewSyncTimers.values()) clearTimeout(timer);
|
|
7834
7949
|
canvasShapePreviewSyncTimers.clear();
|
|
7835
7950
|
}
|
|
7951
|
+
function cancelQuietScreenshots(widgetId?: string | null): void {
|
|
7952
|
+
if (widgetId) {
|
|
7953
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7954
|
+
if (existing) clearTimeout(existing);
|
|
7955
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7956
|
+
cancelCanvasShapePreviewSync(widgetId);
|
|
7957
|
+
return;
|
|
7958
|
+
}
|
|
7959
|
+
for (const timer of quietScreenshotTimers.values()) clearTimeout(timer);
|
|
7960
|
+
quietScreenshotTimers.clear();
|
|
7961
|
+
cancelCanvasShapePreviewSync();
|
|
7962
|
+
}
|
|
7963
|
+
function scheduleQuietScreenshot(widgetId: string, delayMs = 2000): void {
|
|
7964
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7965
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7966
|
+
if (existing) clearTimeout(existing);
|
|
7967
|
+
const timer = setTimeout(() => {
|
|
7968
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7969
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7970
|
+
void handleRefreshScreenshot(widgetId, { quiet: true });
|
|
7971
|
+
}, delayMs);
|
|
7972
|
+
quietScreenshotTimers.set(widgetId, timer);
|
|
7973
|
+
}
|
|
7974
|
+
/**
|
|
7975
|
+
* Latest widget capture for the next LLM turn, without freezing a generation.
|
|
7976
|
+
* html2canvas shares the page main thread, so this waits until codegen has
|
|
7977
|
+
* been idle, then persists thumbnail_url only (no canvas JSON serialize).
|
|
7978
|
+
*/
|
|
7979
|
+
const idleThumbnailTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7980
|
+
const IDLE_THUMBNAIL_DELAY_MS = 12000;
|
|
7981
|
+
function cancelIdleThumbnailPersist(widgetId?: string | null): void {
|
|
7982
|
+
if (widgetId) {
|
|
7983
|
+
const existing = idleThumbnailTimers.get(widgetId);
|
|
7984
|
+
if (existing) clearTimeout(existing);
|
|
7985
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7986
|
+
return;
|
|
7987
|
+
}
|
|
7988
|
+
for (const timer of idleThumbnailTimers.values()) clearTimeout(timer);
|
|
7989
|
+
idleThumbnailTimers.clear();
|
|
7990
|
+
}
|
|
7991
|
+
function scheduleIdleThumbnailPersist(widgetId: string): void {
|
|
7992
|
+
if (!widgetId) return;
|
|
7993
|
+
cancelIdleThumbnailPersist(widgetId);
|
|
7994
|
+
const timer = setTimeout(() => {
|
|
7995
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7996
|
+
const run = () => {
|
|
7997
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7998
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
7999
|
+
return;
|
|
8000
|
+
}
|
|
8001
|
+
void persistIdleWidgetThumbnail(widgetId);
|
|
8002
|
+
};
|
|
8003
|
+
if (typeof requestIdleCallback === 'function') {
|
|
8004
|
+
requestIdleCallback(() => run(), { timeout: 8000 });
|
|
8005
|
+
} else {
|
|
8006
|
+
run();
|
|
8007
|
+
}
|
|
8008
|
+
}, IDLE_THUMBNAIL_DELAY_MS);
|
|
8009
|
+
idleThumbnailTimers.set(widgetId, timer);
|
|
8010
|
+
}
|
|
8011
|
+
async function persistIdleWidgetThumbnail(widgetId: string): Promise<void> {
|
|
8012
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
8013
|
+
const iframe = resolveLiveWidgetShapeIframe(widgetId);
|
|
8014
|
+
if (!iframe?.contentWindow) return;
|
|
8015
|
+
try {
|
|
8016
|
+
const screenshot = await handleChatScreenshot(widgetId, iframe, true);
|
|
8017
|
+
if (!screenshot || codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
8018
|
+
await persistWidgetListThumbnail(widgetId, screenshot);
|
|
8019
|
+
console.log('[Chat→Screenshot] Idle thumbnail persisted', widgetId.substring(0, 8));
|
|
8020
|
+
} catch (error) {
|
|
8021
|
+
console.warn('[Chat→Screenshot] Idle thumbnail persist failed', widgetId, error);
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
7836
8024
|
function scheduleCanvasShapePreviewSync(widgetId: string, delayMs = 1800): void {
|
|
7837
|
-
if (!widgetId ||
|
|
8025
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7838
8026
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
7839
8027
|
if (existing) clearTimeout(existing);
|
|
7840
8028
|
const timer = setTimeout(() => {
|
|
7841
8029
|
canvasShapePreviewSyncTimers.delete(widgetId);
|
|
7842
|
-
if (
|
|
8030
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7843
8031
|
// console.log('[CreatorApp] Skip shape preview sync — codegen in progress:', widgetId.substring(0, 8));
|
|
7844
8032
|
return;
|
|
7845
8033
|
}
|
|
@@ -7870,8 +8058,8 @@
|
|
|
7870
8058
|
|
|
7871
8059
|
// Never run quiet background screenshot captures while ANY code generation is running —
|
|
7872
8060
|
// html2canvas execution inside any preview iframe blocks the single shared main thread.
|
|
7873
|
-
if (
|
|
7874
|
-
console.log('[handleRefreshScreenshot] Skip
|
|
8061
|
+
if (codegenCaptureBlocked()) {
|
|
8062
|
+
console.log('[handleRefreshScreenshot] Skip capture — codegen in progress', widgetId?.substring(0, 8));
|
|
7875
8063
|
return;
|
|
7876
8064
|
}
|
|
7877
8065
|
|
|
@@ -7972,6 +8160,12 @@
|
|
|
7972
8160
|
});
|
|
7973
8161
|
}
|
|
7974
8162
|
window.addEventListener('message', handler);
|
|
8163
|
+
if (codegenCaptureBlocked()) {
|
|
8164
|
+
clearTimeout(timeout);
|
|
8165
|
+
window.removeEventListener('message', handler);
|
|
8166
|
+
resolve({ data: null, error: 'generation in progress' });
|
|
8167
|
+
return;
|
|
8168
|
+
}
|
|
7975
8169
|
iframe.contentWindow!.postMessage({ type: 'widgetic:request-screenshot' }, '*');
|
|
7976
8170
|
});
|
|
7977
8171
|
|
|
@@ -8092,6 +8286,8 @@ Requirements:
|
|
|
8092
8286
|
promptImagesOverride?: string[];
|
|
8093
8287
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8094
8288
|
skipIntentGate?: boolean;
|
|
8289
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8290
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8095
8291
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
8096
8292
|
persistFileAttachments?: Array<{
|
|
8097
8293
|
url?: string;
|
|
@@ -8182,6 +8378,7 @@ Requirements:
|
|
|
8182
8378
|
promptImagesOverride: options.promptImagesOverride,
|
|
8183
8379
|
persistFileAttachments: options.persistFileAttachments,
|
|
8184
8380
|
skipIntentGate: options.skipIntentGate,
|
|
8381
|
+
chipIntent: options.chipIntent,
|
|
8185
8382
|
});
|
|
8186
8383
|
}
|
|
8187
8384
|
|
|
@@ -8205,6 +8402,8 @@ Requirements:
|
|
|
8205
8402
|
promptImagesOverride?: string[];
|
|
8206
8403
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8207
8404
|
skipIntentGate?: boolean;
|
|
8405
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8406
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8208
8407
|
persistFileAttachments?: Array<{
|
|
8209
8408
|
url?: string;
|
|
8210
8409
|
file?: File;
|
|
@@ -8220,7 +8419,6 @@ Requirements:
|
|
|
8220
8419
|
|
|
8221
8420
|
const panelChat = getChatMethodsForWidget(generationWidgetId);
|
|
8222
8421
|
const panelSnap = getPanelState(generationWidgetId);
|
|
8223
|
-
const hadCodeBeforeGeneration = !!(panelSnap?.hasGeneratedCode || panelSnap?.lastCommitId);
|
|
8224
8422
|
|
|
8225
8423
|
// Per-widget abort: only abort if the SAME widget already has a running generation
|
|
8226
8424
|
const existingController = widgetAbortControllers.get(generationWidgetId);
|
|
@@ -8233,6 +8431,22 @@ Requirements:
|
|
|
8233
8431
|
const signal = abortController.signal;
|
|
8234
8432
|
|
|
8235
8433
|
generatingWidgetIds = { ...generatingWidgetIds, [generationWidgetId]: true };
|
|
8434
|
+
noteCodegenActive(true);
|
|
8435
|
+
screenshotGenerationEpoch += 1;
|
|
8436
|
+
document.querySelectorAll('iframe').forEach((frame) => {
|
|
8437
|
+
try {
|
|
8438
|
+
frame.contentWindow?.postMessage({ type: 'widgetic:abort-screenshot' }, '*');
|
|
8439
|
+
} catch (err) {
|
|
8440
|
+
console.warn('[WidgetCreator] Could not abort in-flight screenshot', err);
|
|
8441
|
+
}
|
|
8442
|
+
});
|
|
8443
|
+
cancelQuietScreenshots();
|
|
8444
|
+
cancelIdleThumbnailPersist();
|
|
8445
|
+
widgetShapeLivePreviewsRef?.cancelReloads?.();
|
|
8446
|
+
if (restoredViewingVersionByWidgetId[generationWidgetId] != null) {
|
|
8447
|
+
const { [generationWidgetId]: _droppedRestore, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
8448
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
8449
|
+
}
|
|
8236
8450
|
lastHarnessChatLine = '';
|
|
8237
8451
|
|
|
8238
8452
|
// Show user prompt in chat when triggered outside Chat (confirm dialog / Generate Now).
|
|
@@ -8446,6 +8660,7 @@ Requirements:
|
|
|
8446
8660
|
runtimeLogs: runtimeLogsForRequest,
|
|
8447
8661
|
compileFixOnly: options.compileFixOnly === true,
|
|
8448
8662
|
skipIntentGate: options.skipIntentGate === true,
|
|
8663
|
+
chipIntent: options.chipIntent,
|
|
8449
8664
|
} as GenerateCodeRequest
|
|
8450
8665
|
});
|
|
8451
8666
|
// 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);
|
|
@@ -8460,7 +8675,7 @@ Requirements:
|
|
|
8460
8675
|
if (gatedData?.gated === true && typeof gatedData.action === 'string' && gatedData.action !== 'generate') {
|
|
8461
8676
|
const gatedAction = gatedData.action as 'answer' | 'redirect' | 'clarify';
|
|
8462
8677
|
const gatedReply: string = gatedData.reply ?? '';
|
|
8463
|
-
const suggestions:
|
|
8678
|
+
const suggestions: ClarificationSuggestion[] = Array.isArray(gatedData.suggestions) ? gatedData.suggestions : [];
|
|
8464
8679
|
|
|
8465
8680
|
clearActiveOperationId();
|
|
8466
8681
|
patchCodegenUiState(generationWidgetId, {
|
|
@@ -8672,8 +8887,10 @@ Requirements:
|
|
|
8672
8887
|
shapeHistoryResolvedByWidgetId = restResolved;
|
|
8673
8888
|
setTimeout(() => {
|
|
8674
8889
|
void loadPublishHistoryForPanel(generationWidgetId, { silent: true }).then(() => {
|
|
8675
|
-
|
|
8676
|
-
if (
|
|
8890
|
+
const newestDraft = getNewestDraftVersionId(generationWidgetId);
|
|
8891
|
+
if (newestDraft != null) {
|
|
8892
|
+
selectVersionInDropdowns(generationWidgetId, `v:${newestDraft}`);
|
|
8893
|
+
}
|
|
8677
8894
|
});
|
|
8678
8895
|
}, 1200);
|
|
8679
8896
|
}
|
|
@@ -8845,11 +9062,9 @@ Requirements:
|
|
|
8845
9062
|
[generationWidgetId]: 'preview',
|
|
8846
9063
|
};
|
|
8847
9064
|
syncWidgetShapeLivePreviews();
|
|
8848
|
-
//
|
|
8849
|
-
//
|
|
8850
|
-
// on the canvas shape overlay or it keeps showing the previous version.
|
|
9065
|
+
// WS preview-ready was deferred during codegen so the Worker
|
|
9066
|
+
// iframe does not boot on the shared main thread at 80%.
|
|
8851
9067
|
widgetShapeLivePreviewsRef?.requestReload?.(generationWidgetId);
|
|
8852
|
-
panelDisplayRevision += 1;
|
|
8853
9068
|
if (!completedPreviewBuildErr) {
|
|
8854
9069
|
await applyDesignSchemaDefaultsToPreview(generationWidgetId);
|
|
8855
9070
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
@@ -8863,15 +9078,10 @@ Requirements:
|
|
|
8863
9078
|
await panelChat?.reloadConversationMessages?.();
|
|
8864
9079
|
panelChat?.markWidgetHeadAtLatestCommit?.();
|
|
8865
9080
|
}, 2500);
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
void (async () => {
|
|
8871
|
-
await handleRefreshScreenshot(generationWidgetId, { quiet: true });
|
|
8872
|
-
})();
|
|
8873
|
-
}
|
|
8874
|
-
}, 3000);
|
|
9081
|
+
scheduleIdleThumbnailPersist(generationWidgetId);
|
|
9082
|
+
// Do not html2canvas here. Capture during/after generation freezes
|
|
9083
|
+
// the tab (iframe shares the main thread). Thumb for the next prompt
|
|
9084
|
+
// is persisted later, only while the tab is idle.
|
|
8875
9085
|
// Do NOT auto-publish — user previews first, then clicks Publish / Set live.
|
|
8876
9086
|
} else {
|
|
8877
9087
|
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
@@ -8949,6 +9159,7 @@ Requirements:
|
|
|
8949
9159
|
showToast('error', 'Code generation error', { description: message, duration: 6000 });
|
|
8950
9160
|
pushCodegenFailureToChat(generationWidgetId, message);
|
|
8951
9161
|
} finally {
|
|
9162
|
+
noteCodegenActive(false);
|
|
8952
9163
|
const { [generationWidgetId]: _done, ...restFinally } = generatingWidgetIds;
|
|
8953
9164
|
generatingWidgetIds = restFinally;
|
|
8954
9165
|
widgetAbortControllers.delete(generationWidgetId);
|
|
@@ -9107,7 +9318,7 @@ Requirements:
|
|
|
9107
9318
|
// rather than letting them silently become version 0.
|
|
9108
9319
|
const rawTargetVersion = publishOpts.targetVersion;
|
|
9109
9320
|
const targetVersion =
|
|
9110
|
-
rawTargetVersion == null || rawTargetVersion === ''
|
|
9321
|
+
rawTargetVersion == null || (typeof rawTargetVersion === 'string' && rawTargetVersion === '')
|
|
9111
9322
|
? null
|
|
9112
9323
|
: Number.isFinite(Number(rawTargetVersion))
|
|
9113
9324
|
? Number(rawTargetVersion)
|
|
@@ -10296,8 +10507,8 @@ Requirements:
|
|
|
10296
10507
|
record?.artifact_url ||
|
|
10297
10508
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10298
10509
|
if (isPublished && artifact) {
|
|
10299
|
-
const
|
|
10300
|
-
if (url) return url;
|
|
10510
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10511
|
+
if (isolated?.url) return isolated.url;
|
|
10301
10512
|
}
|
|
10302
10513
|
const previewState = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10303
10514
|
if (previewState?.url) return previewState.url;
|
|
@@ -10327,6 +10538,10 @@ Requirements:
|
|
|
10327
10538
|
* Publish history is loaded without requiring chat to be open.
|
|
10328
10539
|
*/
|
|
10329
10540
|
let shapePreviewSourceByWidgetId: Record<string, string> = {};
|
|
10541
|
+
/** Dropdown label only. Changing it must not reload the shape iframe. */
|
|
10542
|
+
let dropdownVersionByWidgetId: Record<string, string> = {};
|
|
10543
|
+
/** After Restore Checkpoint, dropdowns stay on the restored version number while preview is Worker HEAD. */
|
|
10544
|
+
let restoredViewingVersionByWidgetId: Record<string, number> = {};
|
|
10330
10545
|
// Historical previews are never interchangeable with the shared HEAD preview.
|
|
10331
10546
|
let shapeVersionPreviewByWidgetId: Record<string, {
|
|
10332
10547
|
versionId: string; requestId: number; loading: boolean; error: string | null; url: string | null;
|
|
@@ -10440,12 +10655,21 @@ Requirements:
|
|
|
10440
10655
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10441
10656
|
|
|
10442
10657
|
if (isPublished && artifact) {
|
|
10443
|
-
const
|
|
10444
|
-
if (url) return url;
|
|
10658
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10659
|
+
if (isolated?.url && isolated.versionId === `v:${version}`) return isolated.url;
|
|
10660
|
+
// Do not return Live CDN here — Switch Version must wait for the
|
|
10661
|
+
// commit-specific build, otherwise the iframe stays on the last publish.
|
|
10445
10662
|
}
|
|
10446
|
-
// The newest draft
|
|
10663
|
+
// The newest draft is repo HEAD only while HEAD still points at its commit.
|
|
10664
|
+
// A Restore Checkpoint writes a rollback commit on top, so the shared
|
|
10665
|
+
// `/preview/{widgetId}` artifact then serves the restored code under the
|
|
10666
|
+
// newest version's number — ask for a commit-specific build instead.
|
|
10447
10667
|
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10448
|
-
|
|
10668
|
+
const draftCommitSha = normalizeCommitSha(record?.commitSha ?? record?.commit_sha ?? null);
|
|
10669
|
+
const headCommitSha = normalizeCommitSha(ps.lastCommitId ?? loadCommitIdForWidget(widgetId));
|
|
10670
|
+
const draftIsRepoHead =
|
|
10671
|
+
!draftCommitSha || !headCommitSha || commitsMatch(draftCommitSha, headCommitSha);
|
|
10672
|
+
if (!isPublished && newestDraft != null && version === newestDraft && draftIsRepoHead && workerUrl) {
|
|
10449
10673
|
return workerUrl;
|
|
10450
10674
|
}
|
|
10451
10675
|
return resolveWidgetShapeVersionPreviewState(widgetId)?.url ?? null;
|
|
@@ -10464,6 +10688,8 @@ Requirements:
|
|
|
10464
10688
|
}
|
|
10465
10689
|
|
|
10466
10690
|
function resolveWidgetShapeVersionLabel(widgetId: string): string | null {
|
|
10691
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10692
|
+
if (Number.isFinite(restored)) return `v${restored}`;
|
|
10467
10693
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10468
10694
|
const ps = getPanelDisplayState(widgetId);
|
|
10469
10695
|
const liveVersion =
|
|
@@ -10526,6 +10752,7 @@ Requirements:
|
|
|
10526
10752
|
dateLabel?: string;
|
|
10527
10753
|
isLive?: boolean;
|
|
10528
10754
|
isDraft?: boolean;
|
|
10755
|
+
commitSha?: string;
|
|
10529
10756
|
}> = [];
|
|
10530
10757
|
for (const record of history) {
|
|
10531
10758
|
const version = Number(record.version);
|
|
@@ -10564,6 +10791,10 @@ Requirements:
|
|
|
10564
10791
|
}
|
|
10565
10792
|
|
|
10566
10793
|
function resolveWidgetShapeVersionSelectedId(widgetId: string): string | null {
|
|
10794
|
+
const menuVersion = dropdownVersionByWidgetId[widgetId];
|
|
10795
|
+
if (menuVersion) return menuVersion;
|
|
10796
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10797
|
+
if (Number.isFinite(restored)) return `v:${restored}`;
|
|
10567
10798
|
const source = shapePreviewSourceByWidgetId[widgetId];
|
|
10568
10799
|
if (source?.startsWith('v:')) return source;
|
|
10569
10800
|
if (source === 'preview') {
|
|
@@ -10579,8 +10810,21 @@ Requirements:
|
|
|
10579
10810
|
return liveVersion != null ? `v:${liveVersion}` : null;
|
|
10580
10811
|
}
|
|
10581
10812
|
|
|
10813
|
+
function selectVersionInDropdowns(widgetId: string, versionId: string): void {
|
|
10814
|
+
dropdownVersionByWidgetId = { ...dropdownVersionByWidgetId, [widgetId]: versionId };
|
|
10815
|
+
widgetShapeLivePreviewsRef?.setSelectedVersion?.(widgetId, versionId);
|
|
10816
|
+
panelDisplayRevision += 1;
|
|
10817
|
+
}
|
|
10818
|
+
|
|
10582
10819
|
function handleWidgetShapeVersionChange(widgetId: string, versionId: string, buildPreview = true): void {
|
|
10583
10820
|
if (!versionId) return;
|
|
10821
|
+
// Both dropdowns show the pick. Only the dropdown the user clicked reloads the preview.
|
|
10822
|
+
selectVersionInDropdowns(widgetId, versionId);
|
|
10823
|
+
if (!buildPreview) return;
|
|
10824
|
+
if (restoredViewingVersionByWidgetId[widgetId] != null) {
|
|
10825
|
+
const { [widgetId]: _dropped, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
10826
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
10827
|
+
}
|
|
10584
10828
|
// Re-selecting an already successful/in-flight preview must not reset its state.
|
|
10585
10829
|
const currentPreview = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10586
10830
|
if (shapePreviewSourceByWidgetId[widgetId] === versionId
|
|
@@ -10599,17 +10843,19 @@ Requirements:
|
|
|
10599
10843
|
};
|
|
10600
10844
|
syncWidgetShapeLivePreviews();
|
|
10601
10845
|
panelDisplayRevision += 1;
|
|
10602
|
-
//
|
|
10603
|
-
|
|
10604
|
-
//
|
|
10846
|
+
// Always compile the selected commit into an isolated Worker preview.
|
|
10847
|
+
// Published CDN artifacts are Live only — using them on Switch made
|
|
10848
|
+
// draft/restore look like the last publish even when git had moved.
|
|
10605
10849
|
if (versionId.startsWith('v:')) {
|
|
10606
10850
|
const version = Number(versionId.slice(2));
|
|
10607
|
-
const
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10851
|
+
const record = getShapePublishHistory(widgetId).find((row) => Number(row.version) === version);
|
|
10852
|
+
const isPublished = !!(record?.publishedAt ?? record?.published_at);
|
|
10853
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10854
|
+
const workerUrl = resolveWidgetShapeLivePreviewUrl(widgetId);
|
|
10855
|
+
const viewingHeadDraft = !isPublished && newestDraft != null && version === newestDraft && workerUrl && !restoredViewingVersionByWidgetId[widgetId];
|
|
10856
|
+
if (viewingHeadDraft) {
|
|
10611
10857
|
const detailsRef = getWidgetDetailsRef(widgetId);
|
|
10612
|
-
detailsRef?.showPublishedArtifact?.(
|
|
10858
|
+
detailsRef?.showPublishedArtifact?.(workerUrl);
|
|
10613
10859
|
} else {
|
|
10614
10860
|
void ensureWidgetShapeVersionPreview(widgetId, version);
|
|
10615
10861
|
}
|
|
@@ -10688,6 +10934,13 @@ Requirements:
|
|
|
10688
10934
|
/** Commit SHA for the version currently selected in the version menu (drives Head-at-commit badge). */
|
|
10689
10935
|
function resolveViewingCommitShaForPanel(widgetId: string): string | null {
|
|
10690
10936
|
const ps = getPanelDisplayState(widgetId);
|
|
10937
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10938
|
+
if (Number.isFinite(restored)) {
|
|
10939
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10940
|
+
const record = history.find((r) => Number(r.version) === Number(restored));
|
|
10941
|
+
const fromRestored = normalizeCommitSha(record?.commitSha ?? record?.commit_sha);
|
|
10942
|
+
if (fromRestored) return fromRestored;
|
|
10943
|
+
}
|
|
10691
10944
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10692
10945
|
if (source === 'preview') {
|
|
10693
10946
|
return normalizeCommitSha(ps.lastCommitId);
|
|
@@ -10738,18 +10991,28 @@ Requirements:
|
|
|
10738
10991
|
* debug mode and this label is what the turn shows.
|
|
10739
10992
|
*/
|
|
10740
10993
|
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10994
|
+
let versionPromptLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10995
|
+
let viewingVersionByWidgetId: Record<string, number | null> = {};
|
|
10741
10996
|
$: {
|
|
10742
10997
|
void shapePreviewSourceByWidgetId;
|
|
10743
10998
|
void shapeVersionPreviewByWidgetId;
|
|
10999
|
+
void restoredViewingVersionByWidgetId;
|
|
11000
|
+
void dropdownVersionByWidgetId;
|
|
10744
11001
|
void panelDisplayRevision;
|
|
10745
11002
|
const nextHeads: Record<string, string | null> = {};
|
|
10746
11003
|
const nextLabels: Record<string, Record<string, string>> = {};
|
|
11004
|
+
const nextPrompts: Record<string, Record<string, string>> = {};
|
|
11005
|
+
const nextViewing: Record<string, number | null> = {};
|
|
10747
11006
|
for (const widgetId of openWidgetPanelIds) {
|
|
10748
11007
|
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
10749
11008
|
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
11009
|
+
nextPrompts[widgetId] = buildVersionPromptLabelsForPanel(widgetId);
|
|
11010
|
+
nextViewing[widgetId] = viewingVersionNumberForPanel(widgetId);
|
|
10750
11011
|
}
|
|
10751
11012
|
headCommitShaByWidgetId = nextHeads;
|
|
10752
11013
|
versionLabelsByWidgetId = nextLabels;
|
|
11014
|
+
versionPromptLabelsByWidgetId = nextPrompts;
|
|
11015
|
+
viewingVersionByWidgetId = nextViewing;
|
|
10753
11016
|
}
|
|
10754
11017
|
|
|
10755
11018
|
/** Commit shas are keyed on 7 chars — that is what the chat summaries carry. */
|
|
@@ -10758,24 +11021,55 @@ Requirements:
|
|
|
10758
11021
|
return normalized ? normalized.slice(0, 7) : null;
|
|
10759
11022
|
}
|
|
10760
11023
|
|
|
11024
|
+
function assignVersionLabelKeys(labels: Record<string, string>, sha: string | null | undefined, label: string): void {
|
|
11025
|
+
const normalized = normalizeCommitSha(sha);
|
|
11026
|
+
if (!normalized) return;
|
|
11027
|
+
labels[normalized] = label;
|
|
11028
|
+
labels[normalized.slice(0, 7)] = label;
|
|
11029
|
+
labels[normalized.slice(0, 8)] = label;
|
|
11030
|
+
}
|
|
11031
|
+
|
|
10761
11032
|
function buildVersionLabelsForPanel(widgetId: string): Record<string, string> {
|
|
10762
11033
|
const labels: Record<string, string> = {};
|
|
10763
11034
|
const ps = getPanelDisplayState(widgetId);
|
|
10764
11035
|
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10765
11036
|
for (const record of history || []) {
|
|
10766
|
-
|
|
10767
|
-
|
|
11037
|
+
if (record?.version == null) continue;
|
|
11038
|
+
assignVersionLabelKeys(labels, record?.commitSha ?? record?.commit_sha, `Version ${record.version}`);
|
|
10768
11039
|
}
|
|
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
11040
|
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
labels[previewKey] = `Version ${previewState.versionId.slice(2)}`;
|
|
11041
|
+
if (previewState?.versionId?.startsWith('v:')) {
|
|
11042
|
+
assignVersionLabelKeys(labels, previewState?.commitSha, `Version ${previewState.versionId.slice(2)}`);
|
|
10775
11043
|
}
|
|
10776
11044
|
return labels;
|
|
10777
11045
|
}
|
|
10778
11046
|
|
|
11047
|
+
function versionPromptKey(prompt: string | null | undefined): string | null {
|
|
11048
|
+
const text = String(prompt || '').replace(/\s+/g, ' ').trim();
|
|
11049
|
+
return text || null;
|
|
11050
|
+
}
|
|
11051
|
+
|
|
11052
|
+
/** User prompt → "Version N", for turns whose commit line was never saved. */
|
|
11053
|
+
function buildVersionPromptLabelsForPanel(widgetId: string): Record<string, string> {
|
|
11054
|
+
const labels: Record<string, string> = {};
|
|
11055
|
+
const ps = getPanelDisplayState(widgetId);
|
|
11056
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
11057
|
+
for (const record of history || []) {
|
|
11058
|
+
if (record?.version == null) continue;
|
|
11059
|
+
const key = versionPromptKey(record?.prompt);
|
|
11060
|
+
if (!key) continue;
|
|
11061
|
+
labels[key] = `Version ${record.version}`;
|
|
11062
|
+
}
|
|
11063
|
+
return labels;
|
|
11064
|
+
}
|
|
11065
|
+
|
|
11066
|
+
function viewingVersionNumberForPanel(widgetId: string): number | null {
|
|
11067
|
+
const selected = resolveWidgetShapeVersionSelectedId(widgetId);
|
|
11068
|
+
if (!selected?.startsWith('v:')) return null;
|
|
11069
|
+
const version = parseInt(selected.slice(2), 10);
|
|
11070
|
+
return Number.isFinite(version) ? version : null;
|
|
11071
|
+
}
|
|
11072
|
+
|
|
10779
11073
|
function syncWidgetShapeLivePreviews(): void {
|
|
10780
11074
|
measureSync('sync-widget-shape-previews', () => {
|
|
10781
11075
|
try {
|
|
@@ -11010,10 +11304,13 @@ Requirements:
|
|
|
11010
11304
|
if (embedded && deepLinkId && widgetId === deepLinkId) {
|
|
11011
11305
|
embeddedDeepLinkPanelDismissed = false;
|
|
11012
11306
|
}
|
|
11013
|
-
//
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11307
|
+
// At most two chats. Opening a third closes the oldest one.
|
|
11308
|
+
const maxOpenWidgetChats = 2;
|
|
11309
|
+
if (!openWidgetPanelIds.includes(widgetId)) {
|
|
11310
|
+
while (openWidgetPanelIds.length >= maxOpenWidgetChats) {
|
|
11311
|
+
const oldestId = openWidgetPanelIds[0];
|
|
11312
|
+
if (!oldestId) break;
|
|
11313
|
+
closeWidgetDetailsPanel(oldestId, { fromShapeSwitch: true });
|
|
11017
11314
|
}
|
|
11018
11315
|
}
|
|
11019
11316
|
const isNewPanel = !openWidgetPanelIds.includes(widgetId);
|
|
@@ -12021,7 +12318,7 @@ Requirements:
|
|
|
12021
12318
|
* from whatever HEAD happens to contain.
|
|
12022
12319
|
*/
|
|
12023
12320
|
function canPublishDraftVersionForPanel(widgetId: string, version: number | null | undefined): boolean {
|
|
12024
|
-
if (version == null || version === '' || !Number.isFinite(Number(version))) return false;
|
|
12321
|
+
if (version == null || (typeof version === 'string' && version === '') || !Number.isFinite(Number(version))) return false;
|
|
12025
12322
|
if (isPublishing && publishingWidgetId === widgetId) return false;
|
|
12026
12323
|
if (isPublishing && publishingWidgetId !== null && publishingWidgetId !== widgetId) return false;
|
|
12027
12324
|
if (generatingWidgetIds[widgetId]) return false;
|
|
@@ -12150,7 +12447,7 @@ $: if ($widgetPublishStatus.timestamp && $widgetPublishStatus.widgetId) {
|
|
|
12150
12447
|
}
|
|
12151
12448
|
if (publishWidgetId) {
|
|
12152
12449
|
// console.log('[Publish] Refreshing canvas widget screenshot after successful publish');
|
|
12153
|
-
|
|
12450
|
+
scheduleQuietScreenshot(publishWidgetId, 2000);
|
|
12154
12451
|
}
|
|
12155
12452
|
if (embedded && onPublishComplete && publishWidgetId) {
|
|
12156
12453
|
onPublishComplete({
|
|
@@ -12198,7 +12495,7 @@ $: if ($widgetPublishStatus.widgetId === selectedWidgetId && $widgetPublishStatu
|
|
|
12198
12495
|
// console.log('[CrossTab/WS] Publish building status from WebSocket — disabling Publish button');
|
|
12199
12496
|
}
|
|
12200
12497
|
|
|
12201
|
-
// Agent harness repair status (DW Phase 2) — real-time via
|
|
12498
|
+
// Agent harness repair status (DW Phase 2) — real-time via codegen:output WebSocket.
|
|
12202
12499
|
$: if ($agentHarnessOutput?.timestamp) {
|
|
12203
12500
|
const raw = ($agentHarnessOutput.output || '').trim();
|
|
12204
12501
|
const line = raw.replace(/^🔧\s*/, '').trim();
|
|
@@ -12214,15 +12511,27 @@ $: if ($agentHarnessOutput?.timestamp) {
|
|
|
12214
12511
|
}
|
|
12215
12512
|
}
|
|
12216
12513
|
|
|
12217
|
-
// Worker preview ready via WebSocket event — reload canvas shape preview immediately
|
|
12514
|
+
// Worker preview ready via WebSocket event — reload canvas shape preview immediately.
|
|
12515
|
+
// The store keeps the last event forever, and this block writes state it also reads,
|
|
12516
|
+
// so without the event key it re-ran on every unrelated update: each re-run snapped the
|
|
12517
|
+
// version selector back to HEAD and cancelled an in-flight version preview.
|
|
12518
|
+
let lastWsWorkerPreviewEventKey: string | null = null;
|
|
12218
12519
|
$: if ($dynamicWorkerPreviewStatus.widgetId && $dynamicWorkerPreviewStatus.previewUrl) {
|
|
12219
12520
|
const wsReadyWidgetId = $dynamicWorkerPreviewStatus.widgetId;
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12521
|
+
const wsEventKey = `${wsReadyWidgetId}:${$dynamicWorkerPreviewStatus.previewUrl}:${$dynamicWorkerPreviewStatus.timestamp ?? ''}`;
|
|
12522
|
+
if (wsEventKey !== lastWsWorkerPreviewEventKey) {
|
|
12523
|
+
lastWsWorkerPreviewEventKey = wsEventKey;
|
|
12524
|
+
shapePreviewSourceByWidgetId = {
|
|
12525
|
+
...shapePreviewSourceByWidgetId,
|
|
12526
|
+
[wsReadyWidgetId]: 'preview',
|
|
12527
|
+
};
|
|
12528
|
+
if (codegenCaptureBlocked()) {
|
|
12529
|
+
console.log('[CreatorApp] Defer canvas Worker preview reload until codegen finishes', wsReadyWidgetId.substring(0, 8));
|
|
12530
|
+
} else {
|
|
12531
|
+
syncWidgetShapeLivePreviews();
|
|
12532
|
+
widgetShapeLivePreviewsRef?.requestReload?.(wsReadyWidgetId);
|
|
12533
|
+
}
|
|
12534
|
+
}
|
|
12226
12535
|
}
|
|
12227
12536
|
|
|
12228
12537
|
// ===================== Publish History =====================
|
|
@@ -12898,6 +13207,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12898
13207
|
* parent-page [FreezeReport] (long tasks attributed to the iframe).
|
|
12899
13208
|
*/
|
|
12900
13209
|
const screenshotCaptureInFlight = new Set<string>();
|
|
13210
|
+
/** Bumped when a generation starts so in-flight html2canvas results are dropped. */
|
|
13211
|
+
let screenshotGenerationEpoch = 0;
|
|
12901
13212
|
|
|
12902
13213
|
/**
|
|
12903
13214
|
* Called when a Widget Image shape is clicked on the canvas.
|
|
@@ -13645,6 +13956,13 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13645
13956
|
});
|
|
13646
13957
|
}
|
|
13647
13958
|
|
|
13959
|
+
// A new-widget session has no row until the user actually adds something.
|
|
13960
|
+
// Empty autosaves (open, refresh, unmount of a blank canvas) stay local.
|
|
13961
|
+
if (canvasRowDeferred && !canvasId && objectCount > 0) {
|
|
13962
|
+
await ensureCanvasRowForFirstEdit();
|
|
13963
|
+
if (canvasId) saveCanvasState(canvasId, json);
|
|
13964
|
+
}
|
|
13965
|
+
|
|
13648
13966
|
// Save to API via SDK with sequence race protection
|
|
13649
13967
|
if (canvasId && canvasesClient) {
|
|
13650
13968
|
const currentSeq = ++autosaveSequence;
|
|
@@ -13845,6 +14163,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
13845
14163
|
?? `ConvertedW-${Date.now()}`;
|
|
13846
14164
|
|
|
13847
14165
|
authStatus = 'Creating widget from design...';
|
|
14166
|
+
if (!canvasId) await ensureCanvasRowForFirstEdit();
|
|
13848
14167
|
const idempotencyKey = crypto?.randomUUID?.() ?? `${Date.now()}-convert`;
|
|
13849
14168
|
const response = await widgetsClient.createWidget(
|
|
13850
14169
|
{
|
|
@@ -14988,7 +15307,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14988
15307
|
{@const panelInstanceKey = panelInstanceKeyByWidgetId[panelWidgetId] ?? panelWidgetId}
|
|
14989
15308
|
{@const panelWidget = widgetsById.get(panelWidgetId) ?? null}
|
|
14990
15309
|
{@const isPanelFocused = focusedPanelWidgetId === panelWidgetId}
|
|
14991
|
-
{@const ps = (panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
15310
|
+
{@const ps = rev(panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
14992
15311
|
<!--
|
|
14993
15312
|
Same published-version resolution the WidgetDetails instance below gets.
|
|
14994
15313
|
`ps.lastPublishedVersion` alone is only hydrated from the focused panel /
|
|
@@ -14999,9 +15318,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14999
15318
|
{@const panelPublishedVersion = isDraftConvertId(panelWidgetId)
|
|
15000
15319
|
? null
|
|
15001
15320
|
: (ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null))}
|
|
15002
|
-
{@const panelHasUnpublishedChanges = (panelDisplayRevision,
|
|
15003
|
-
{@const panelHasCompileErrorState = (panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
15004
|
-
{@const panelHasInfraPreviewError = (panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
15321
|
+
{@const panelHasUnpublishedChanges = rev(panelDisplayRevision, hasUnpublishedChangesForPanel(panelWidgetId), widgets.length)}
|
|
15322
|
+
{@const panelHasCompileErrorState = rev(panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
15323
|
+
{@const panelHasInfraPreviewError = rev(panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
15005
15324
|
<svelte:component this={WidgetDetails}
|
|
15006
15325
|
bind:this={widgetDetailsRefs[panelInstanceKey]}
|
|
15007
15326
|
userSession={$userSession}
|
|
@@ -15330,17 +15649,19 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15330
15649
|
{@const defaultVersionSource = panelPublishedVersion !== null
|
|
15331
15650
|
? `v:${panelPublishedVersion}`
|
|
15332
15651
|
: (newestDraft ? `v:${newestDraft.version}` : null)}
|
|
15333
|
-
{@const
|
|
15334
|
-
{@const
|
|
15652
|
+
{@const restoredVersion = restoredViewingVersionByWidgetId[panelWidgetId]}
|
|
15653
|
+
{@const menuVersion = dropdownVersionByWidgetId[panelWidgetId]}
|
|
15654
|
+
{@const shapeSource = menuVersion || shapePreviewSourceByWidgetId[panelWidgetId] || defaultVersionSource}
|
|
15655
|
+
{@const viewingVersion = Number.isFinite(restoredVersion)
|
|
15656
|
+
? restoredVersion
|
|
15657
|
+
: shapeSource && shapeSource.startsWith('v:')
|
|
15335
15658
|
? parseInt(shapeSource.slice(2), 10)
|
|
15336
15659
|
: shapeSource === 'preview'
|
|
15337
|
-
|
|
15338
|
-
// version — falling back to panelPublishedVersion produced `vnull`
|
|
15339
|
-
// right after generation AND showed the stale Live version when a
|
|
15340
|
-
// draft existed. The newest draft IS this HEAD's version.
|
|
15341
|
-
? (newestDraft ? newestDraft.version : panelPublishedVersion)
|
|
15660
|
+
? (getNewestDraftVersionId(panelWidgetId) ?? panelPublishedVersion)
|
|
15342
15661
|
: panelPublishedVersion}
|
|
15343
|
-
{@const viewingIsLive = Number(
|
|
15662
|
+
{@const viewingIsLive = !Number.isFinite(restoredVersion)
|
|
15663
|
+
&& shapeSource !== 'preview'
|
|
15664
|
+
&& Number(viewingVersion) === Number(panelPublishedVersion)}
|
|
15344
15665
|
<!--
|
|
15345
15666
|
A version is a Draft until it is published. The history is one
|
|
15346
15667
|
sequence (`v1, v2, v3…`), so "draft" is read from the record's
|
|
@@ -15541,9 +15862,16 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15541
15862
|
<Button
|
|
15542
15863
|
onclick={() => {
|
|
15543
15864
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15865
|
+
patchPanelSnapshot(panelWidgetId, {
|
|
15866
|
+
lastGenerationFailed: false,
|
|
15867
|
+
lastGenerationError: null,
|
|
15868
|
+
generateCodeStatus: 'Ready',
|
|
15869
|
+
});
|
|
15870
|
+
if (isPanelFocused || focusedPanelWidgetId === panelWidgetId) {
|
|
15871
|
+
lastGenerationFailed = false;
|
|
15872
|
+
lastGenerationError = null;
|
|
15873
|
+
generateCodeStatus = 'Ready';
|
|
15874
|
+
}
|
|
15547
15875
|
}}
|
|
15548
15876
|
variant="outline"
|
|
15549
15877
|
size="sm"
|
|
@@ -15558,8 +15886,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15558
15886
|
</svelte:fragment>
|
|
15559
15887
|
|
|
15560
15888
|
<svelte:fragment slot="code-generation">
|
|
15889
|
+
{@const chatUserHeight = rev(panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15561
15890
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
15562
|
-
{@const chatUserHeight = (panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15563
15891
|
<div
|
|
15564
15892
|
class="generation-scroll-ct flex flex-col h-full min-h-0 overflow-y-auto border-t border-gray-200"
|
|
15565
15893
|
style="padding: 0;"
|
|
@@ -15605,6 +15933,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15605
15933
|
}}
|
|
15606
15934
|
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15607
15935
|
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15936
|
+
versionLabelByPrompt={versionPromptLabelsByWidgetId[panelWidgetId] || {}}
|
|
15937
|
+
viewingVersion={viewingVersionByWidgetId[panelWidgetId] ?? null}
|
|
15608
15938
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15609
15939
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
15610
15940
|
onUploadChatFile={handleChatUploadFile}
|
|
@@ -15614,8 +15944,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15614
15944
|
onLoadConversations={handleLoadConversations}
|
|
15615
15945
|
onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
|
|
15616
15946
|
onCreateConversation={handleCreateConversation}
|
|
15617
|
-
onSaveMessage={(conversationId, content, messageType, attachments) =>
|
|
15618
|
-
handleSaveMessage(conversationId, content, messageType, attachments, panelWidgetId)
|
|
15947
|
+
onSaveMessage={(conversationId, content, messageType, attachments, suggestions) =>
|
|
15948
|
+
handleSaveMessage(conversationId, content, messageType, attachments, suggestions, panelWidgetId)
|
|
15619
15949
|
}
|
|
15620
15950
|
onRestoreCheckpointBackend={(conversationId, checkpointMessageId) =>
|
|
15621
15951
|
handleRestoreCheckpoint(conversationId, checkpointMessageId, panelWidgetId)
|
|
@@ -15906,11 +16236,13 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15906
16236
|
|
|
15907
16237
|
<!-- Embed Dimensions -->
|
|
15908
16238
|
<div class="embed-dimensions-ct flex flex-col gap-2">
|
|
15909
|
-
<label class="text-xs font-medium text-gray-600">Dimensions</label>
|
|
16239
|
+
<label class="text-xs font-medium text-gray-600" for="embed-width-input">Dimensions</label>
|
|
15910
16240
|
<div class="embed-dimensions-row flex items-center gap-2">
|
|
15911
16241
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15912
16242
|
<span class="text-[10px] text-gray-400 font-medium">W</span>
|
|
15913
16243
|
<input
|
|
16244
|
+
id="embed-width-input"
|
|
16245
|
+
aria-label="Embed width"
|
|
15914
16246
|
type="number"
|
|
15915
16247
|
min="100"
|
|
15916
16248
|
max="4000"
|
|
@@ -15929,6 +16261,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15929
16261
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15930
16262
|
<span class="text-[10px] text-gray-400 font-medium">H</span>
|
|
15931
16263
|
<input
|
|
16264
|
+
id="embed-height-input"
|
|
16265
|
+
aria-label="Embed height"
|
|
15932
16266
|
type="number"
|
|
15933
16267
|
min="50"
|
|
15934
16268
|
max="4000"
|
|
@@ -15974,6 +16308,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15974
16308
|
<!-- Embed Code Block -->
|
|
15975
16309
|
<div class="embed-code-ct flex flex-col gap-2">
|
|
15976
16310
|
<div class="embed-code-header flex items-center justify-between">
|
|
16311
|
+
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
15977
16312
|
<label class="text-xs font-medium text-gray-600">Embed Code ({EMBED_FORMAT_LABELS[effectiveFormat]})</label>
|
|
15978
16313
|
<!-- svelte-ignore a11y_consider_explicit_label -->
|
|
15979
16314
|
<button
|
|
@@ -16095,8 +16430,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16095
16430
|
<p class="text-sm text-gray-600 mb-4">
|
|
16096
16431
|
Permanently delete <strong>{adminDeleteWidget?.name || adminDeleteWidget?.id?.slice(0, 8)}</strong>? This action cannot be undone.
|
|
16097
16432
|
</p>
|
|
16098
|
-
<label class="block text-sm font-medium text-gray-700 mb-1">Admin API Key</label>
|
|
16433
|
+
<label class="block text-sm font-medium text-gray-700 mb-1" for="admin-delete-api-key">Admin API Key</label>
|
|
16099
16434
|
<input
|
|
16435
|
+
id="admin-delete-api-key"
|
|
16100
16436
|
type="password"
|
|
16101
16437
|
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"
|
|
16102
16438
|
placeholder="Enter admin key..."
|
|
@@ -16355,11 +16691,6 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16355
16691
|
:global([data-dialog-overlay]) {
|
|
16356
16692
|
z-index: 9998 !important;
|
|
16357
16693
|
}
|
|
16358
|
-
.generation-models-header select:focus,
|
|
16359
|
-
.generation-models-header select:focus-visible {
|
|
16360
|
-
outline: none;
|
|
16361
|
-
box-shadow: none;
|
|
16362
|
-
}
|
|
16363
16694
|
.wc-version-history-toggle:focus,
|
|
16364
16695
|
.wc-version-history-toggle:focus-visible,
|
|
16365
16696
|
.wc-live-history-backdrop:focus,
|