@widgetic/creator 0.3.50 → 0.3.51

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.
@@ -85,6 +85,7 @@
85
85
  export let apiUrl: CreatorAppProps['apiUrl'] = null;
86
86
  export let widgetBuilderUrl: CreatorAppProps['widgetBuilderUrl'] = null;
87
87
  export let initialDebugMode: CreatorAppProps['initialDebugMode'] = undefined;
88
+ export let initialAdvancedFeatures: CreatorAppProps['initialAdvancedFeatures'] = false;
88
89
  export let defaultCoderModelId: CreatorAppProps['defaultCoderModelId'] = undefined;
89
90
  export let defaultPlannerModelId: CreatorAppProps['defaultPlannerModelId'] = undefined;
90
91
  export let showWidgetsList: CreatorAppProps['showWidgetsList'] = true;
@@ -97,6 +98,11 @@
97
98
  // Opt-in Dexie owner — reactive to prop changes without $effect (would force runes mode).
98
99
  $: setEnableLocalCache(!!enableLocalCache);
99
100
 
101
+ function getRealTokenExpUnix(token: string): number | null {
102
+ const decoded = decodeJWT(token);
103
+ return decoded?.exp ?? null;
104
+ }
105
+
100
106
  let didForceCreateNewWidget = false;
101
107
  /** Site "+ New widget": never hydrate ConvertedW / last-canvas JSON onto this session. */
102
108
  let ignoreIncomingCanvasLoads = !!(forceCreateNewWidget && !initialWidgetId);
@@ -132,11 +138,13 @@
132
138
  const HOST_REQUEST_PUBLISH_EVENT = 'widgetic:host-request-publish';
133
139
  const HOST_TOGGLE_DEBUG_EVENT = 'widgetic:host-toggle-debug';
134
140
  const HOST_TOGGLE_WIREFRAME_EVENT = 'widgetic:host-toggle-wireframe';
141
+ const HOST_TOGGLE_ADVANCED_EVENT = 'widgetic:host-toggle-advanced';
135
142
  const HOST_OPEN_WIDGET_DETAILS_EVENT = 'widgetic:host-open-widget-details';
136
143
  const HOST_DUPLICATE_WIDGET_EVENT = 'widgetic:host-duplicate-widget';
137
144
  const HOST_DELETE_WIDGET_EVENT = 'widgetic:host-delete-widget';
138
145
  const CREATOR_DEBUG_CHANGED_EVENT = 'widgetic:creator-debug-changed';
139
146
  const CREATOR_WIREFRAME_CHANGED_EVENT = 'widgetic:creator-wireframe-changed';
147
+ const CREATOR_ADVANCED_CHANGED_EVENT = 'widgetic:creator-advanced-changed';
140
148
 
141
149
  /** Site AppHeader rename — embed is a second Svelte runtime, so mount props do not update. */
142
150
  async function applyHostWidgetName(widgetId: string | null | undefined, newName: string): Promise<void> {
@@ -316,9 +324,41 @@
316
324
  : new UploadsApi({ accessToken: token, middleware: [idempotencyKeyMiddleware] });
317
325
 
318
326
  authenticatedClientsToken = token;
327
+ try {
328
+ setWsAuthToken(token);
329
+ } catch {
330
+ /* websocket store may not be ready yet */
331
+ }
319
332
  syncFileBrowserUploadConfigForCreator();
320
333
  }
321
334
 
335
+ function applySiteSessionToCreator(): boolean {
336
+ const synced = syncSessionFromSiteAuth();
337
+ if (!synced) return false;
338
+ try {
339
+ const sessionStr = localStorage.getItem('session');
340
+ const session = sessionStr ? JSON.parse(sessionStr) : null;
341
+ currentUserToken =
342
+ session?.accessToken ||
343
+ session?.access_token ||
344
+ localStorage.getItem('jwt') ||
345
+ currentUserToken;
346
+ } catch {
347
+ currentUserToken = localStorage.getItem('jwt') || currentUserToken;
348
+ }
349
+ if (currentUserToken) {
350
+ authenticatedClientsToken = null;
351
+ initializeAuthenticatedClients(currentUserToken);
352
+ }
353
+ updateTokenStatus();
354
+ console.log('[Auth] Creator SDK clients re-bound from site session');
355
+ return hasValidToken;
356
+ }
357
+
358
+ function handleSiteSessionExtended(): void {
359
+ applySiteSessionToCreator();
360
+ }
361
+
322
362
  function syncFileBrowserUploadConfigForCreator() {
323
363
  if (!authenticatedClientsToken) {
324
364
  setFileBrowserUploadConfig(null);
@@ -517,6 +557,8 @@
517
557
  let deleteDialogWidgetId = '';
518
558
  let deleteDialogStep: 'confirm-remove' | 'confirm-delete-db' = 'confirm-remove';
519
559
  let deleteDialogResolve: ((result: { proceed: boolean; deleteFromDb: boolean }) => void) | null = null;
560
+ /** Keep second-step UI/code; MVP deletes the DB widget after the first confirm. */
561
+ const ASK_DELETE_FROM_DB_CONFIRM = false;
520
562
 
521
563
  function showDeleteWidgetDialog(widgetId: string, widgetName: string): Promise<{ proceed: boolean; deleteFromDb: boolean }> {
522
564
  return new Promise((resolve) => {
@@ -535,10 +577,15 @@
535
577
  if (deleteDialogResolve) deleteDialogResolve({ proceed: false, deleteFromDb: false });
536
578
  break;
537
579
  case 'remove-and-ask-db':
538
- if (deleteDialogResolve) deleteDialogResolve({ proceed: true, deleteFromDb: false });
539
- deleteDialogResolve = null;
540
- deleteDialogStep = 'confirm-delete-db';
541
- return;
580
+ if (ASK_DELETE_FROM_DB_CONFIRM) {
581
+ if (deleteDialogResolve) deleteDialogResolve({ proceed: true, deleteFromDb: false });
582
+ deleteDialogResolve = null;
583
+ deleteDialogStep = 'confirm-delete-db';
584
+ return;
585
+ }
586
+ deleteDialogOpen = false;
587
+ if (deleteDialogResolve) deleteDialogResolve({ proceed: true, deleteFromDb: true });
588
+ break;
542
589
  case 'keep-in-db':
543
590
  deleteDialogOpen = false;
544
591
  showToast('info', 'Shape removed', { description: 'Widget kept in database' });
@@ -663,6 +710,8 @@
663
710
  const draftConvertScreenshots = new Map<string, string>();
664
711
  /** True while a draft conversion is being finalized (widget creation + shape replace). */
665
712
  const finalizingDraftConversions = new Set<string>();
713
+ /** Keep Widget Details on this convert/draft widget until the user closes the panel. */
714
+ let detailsSessionWidgetId: string | null = null;
666
715
 
667
716
  // Chat component reference and context (focused panel)
668
717
  let chatMethods: {
@@ -681,8 +730,8 @@
681
730
  markWidgetHeadAtLatestCommit?: () => void;
682
731
  updateLatestCodegenStatusMessage?: (content: string) => void;
683
732
  reloadConversationMessages?: () => Promise<void>;
684
- /** Select an existing conversation in the chat UI (keep DB thread and visible chat in sync). */
685
- selectConversation?: (conversationId: string) => void;
733
+ selectConversation?: (conversationId: string) => void | Promise<void>;
734
+ updateLastUserMessageAttachments?: (attachments: Array<{ id?: string; url: string; file_type?: string; file_name?: string | null }>) => void;
686
735
  } | null = null;
687
736
  let chatMethodsByWidgetId: Record<string, NonNullable<typeof chatMethods>> = {};
688
737
  let pendingChatDraftText = '';
@@ -704,7 +753,7 @@
704
753
 
705
754
  /** Prompt mentions a reference image but chat has no attachment (common UX mistake). */
706
755
  function promptImpliesVisionReference(prompt: string): boolean {
707
- return /\b(attached|attachment|reference\s+image|screenshot|see\s+image|cu\s+imagine|atașat|atasat)\b/i.test(
756
+ return /(attached\s+image|image\s+attached|screenshot|reference\s+image|see\s+(the\s+)?image|cu\s+imagine|imagine[aă]?\s+ata[sș]|ata[sș]at[ăa]?\s+imagine)/i.test(
708
757
  prompt,
709
758
  );
710
759
  }
@@ -713,6 +762,9 @@
713
762
  file?: File,
714
763
  uploadedUrl?: string,
715
764
  ): Promise<string | null> {
765
+ if (file && !file.type.startsWith('image/')) {
766
+ return null;
767
+ }
716
768
  if (uploadedUrl && /^https?:\/\//i.test(uploadedUrl)) {
717
769
  try {
718
770
  const response = await fetch(uploadedUrl);
@@ -740,7 +792,7 @@
740
792
 
741
793
  async function handleChatUploadFile(
742
794
  file: File,
743
- options?: { onProgress?: (percentage: number) => void; source?: string },
795
+ options?: { onProgress?: (percentage: number) => void; source?: string; widgetId?: string },
744
796
  ) {
745
797
  if (!authenticatedClientsToken) {
746
798
  throw new Error('Upload client not initialized');
@@ -750,6 +802,7 @@
750
802
  const uploadBasePath =
751
803
  apiBasePath ||
752
804
  `${(apiRoot || import.meta.env.VITE_API_URL || 'http://localhost:3000').replace(/\/+$/, '')}/v1`;
805
+ const persistWidgetId = options?.widgetId || selectedWidgetId || undefined;
753
806
  const result = await uploadChatFileToWidgeticApi(
754
807
  file,
755
808
  {
@@ -757,7 +810,7 @@
757
810
  accessToken: authenticatedClientsToken,
758
811
  contextType: options?.source === 'preview-screenshot' ? 'browser-ic' : 'widget-codegen',
759
812
  source: options?.source || 'chat-upload',
760
- widgetId: selectedWidgetId || undefined,
813
+ widgetId: persistWidgetId && !isDraftConvertId(persistWidgetId) ? persistWidgetId : undefined,
761
814
  conversationId: currentChatConversationId || undefined,
762
815
  },
763
816
  { onProgress: options?.onProgress },
@@ -816,6 +869,7 @@
816
869
 
817
870
  const panelChat = getChatMethodsForWidget(codegenWidgetId);
818
871
  const cdnUploadIds = (attachments || [])
872
+ .filter((attachment) => attachment.uploadId && attachment.fileType.startsWith('image/'))
819
873
  .map((attachment) => attachment.uploadId)
820
874
  .filter((id): id is string => Boolean(id));
821
875
 
@@ -866,14 +920,45 @@
866
920
  ? promptImages
867
921
  : getPanelState(codegenWidgetId).promptImages;
868
922
  const hasImages = effectivePromptImages.length > 0 || cdnUploadIds.length > 0;
869
- if (!hasImages && promptImpliesVisionReference(content)) {
923
+ const hasNonImageFiles = (attachments || []).some(
924
+ (attachment) => attachment.fileType && !attachment.fileType.startsWith('image/'),
925
+ );
926
+ if (!hasImages && !hasNonImageFiles && promptImpliesVisionReference(content)) {
927
+ const missingImageError =
928
+ 'Promptul menționează o imagine atașată, dar în chat nu e niciun attachment. Folosește butonul Upload.';
870
929
  showToast('warning', 'Lipsește imaginea de referință', {
871
- description:
872
- 'Promptul menționează o imagine atașată, dar în chat nu e niciun attachment. Folosește butonul Upload sau ?attachStairsRef=1.',
930
+ description: missingImageError,
873
931
  duration: 10000,
874
932
  });
933
+ patchCodegenUiState(codegenWidgetId, {
934
+ lastGenerationFailed: true,
935
+ lastGenerationError: missingImageError,
936
+ generateCodeStatus: '❌ Missing reference image',
937
+ });
938
+ pushCodegenFailureToChat(codegenWidgetId, missingImageError);
875
939
  return;
876
940
  }
941
+ const persistFileAttachments = (attachments || [])
942
+ .filter((attachment) =>
943
+ !!attachment.file
944
+ || (attachment.url && (attachment.url.startsWith('blob:') || attachment.url.startsWith('data:') || /^https?:\/\//i.test(attachment.url))),
945
+ )
946
+ .map((attachment, i) => ({
947
+ url: attachment.url,
948
+ file: attachment.file,
949
+ file_type: guessAttachmentFileType(attachment.url || '', attachment.fileName, attachment.fileType),
950
+ file_name: attachment.fileName,
951
+ file_size: attachment.fileSize,
952
+ display_order: i + 1,
953
+ }));
954
+ if (persistFileAttachments.length > 0) {
955
+ await ensureCodegenUserMessagePersisted(
956
+ codegenWidgetId,
957
+ content,
958
+ undefined,
959
+ persistFileAttachments,
960
+ );
961
+ }
877
962
  // Pass upload IDs / images explicitly — clearDraft already wiped Chat's local attachments.
878
963
  await generateCode({
879
964
  prompt: content,
@@ -881,9 +966,19 @@
881
966
  widgetId: codegenWidgetId,
882
967
  attachmentIds: cdnUploadIds.length > 0 ? cdnUploadIds : undefined,
883
968
  promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
969
+ persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
884
970
  });
885
971
  }
886
972
 
973
+ function pushCodegenFailureToChat(widgetId: string, errorText: string) {
974
+ const panelChat = getChatMethodsForWidget(widgetId);
975
+ const content = /^Code generation failed/i.test(errorText)
976
+ ? errorText
977
+ : `Code generation failed: ${errorText}`;
978
+ panelChat?.addAssistantMessage?.(content);
979
+ console.log('[Chat] Codegen failure bubble:', content.slice(0, 160));
980
+ }
981
+
887
982
  /** Retry failed codegen without duplicating the user bubble in chat. */
888
983
  async function handleChatRetryMessage(content: string, sourceWidgetId?: string) {
889
984
  const widgetId = sourceWidgetId ?? selectedWidgetId;
@@ -894,7 +989,53 @@
894
989
  ':',
895
990
  content.substring(0, 80),
896
991
  );
897
- await generateCode({ prompt: content, fromChat: true, widgetId });
992
+ const widget = getWidgetById(widgetId) || (selectedWidget?.id === widgetId ? selectedWidget : null);
993
+ if (widget) {
994
+ await ensureWidgetRepository(widget, { forceRecreate: true, silent: true });
995
+ }
996
+ const panelChat = getChatMethodsForWidget(widgetId);
997
+ const draftUploadIds = panelChat?.getAttachmentUploadIds?.() ?? [];
998
+ const draftImageUrls = panelChat?.getAttachmentDataUrls?.() ?? [];
999
+
1000
+ // Re-use attachments from previous user messages in the conversation
1001
+ // so that Retry doesn't lose PDFs/files that were uploaded with the original prompt.
1002
+ let retryFileAttachments: Array<{ url: string; file_type: string; file_name: string | null; file_size: number | null }> = [];
1003
+ if (draftUploadIds.length === 0 && draftImageUrls.length === 0) {
1004
+ try {
1005
+ const panelState = getPanelState(widgetId);
1006
+ const convId = panelChat?.getConversationId?.() ?? panelState.currentChatConversationId ?? null;
1007
+ if (convId) {
1008
+ const convMessages = await handleLoadMessages(convId, widgetId);
1009
+ // Find most-recent user message that has persisted attachments
1010
+ const userMsgsWithAtt = (convMessages || [])
1011
+ .filter((m: any) => (m.messageType || m.message_type) === 'user' && (m.attachments || []).length > 0);
1012
+ if (userMsgsWithAtt.length > 0) {
1013
+ const latestWithAtt = userMsgsWithAtt[userMsgsWithAtt.length - 1];
1014
+ retryFileAttachments = (latestWithAtt.attachments || [])
1015
+ .filter((a: any) => a.url && /^https?:\/\//i.test(a.url))
1016
+ .map((a: any) => ({
1017
+ url: a.url,
1018
+ file_type: a.file_type || 'application/octet-stream',
1019
+ file_name: a.file_name || null,
1020
+ file_size: a.file_size || null,
1021
+ }));
1022
+ console.log('[Chat→Retry] Re-using', retryFileAttachments.length, 'attachment(s) from previous user message for retry');
1023
+ }
1024
+ }
1025
+ } catch (retryAttErr) {
1026
+ console.warn('[Chat→Retry] Could not load previous attachments for retry:', retryAttErr);
1027
+ }
1028
+ }
1029
+
1030
+ await generateCode({
1031
+ prompt: content,
1032
+ fromChat: true,
1033
+ widgetId,
1034
+ forceRecreateRepo: true,
1035
+ attachmentIds: draftUploadIds.length > 0 ? draftUploadIds : undefined,
1036
+ promptImagesOverride: draftImageUrls.length > 0 ? draftImageUrls : undefined,
1037
+ persistFileAttachments: retryFileAttachments.length > 0 ? retryFileAttachments : undefined,
1038
+ });
898
1039
  }
899
1040
 
900
1041
  function handleChatConversationChange(conversationId: string | null, sourceWidgetId?: string) {
@@ -970,7 +1111,43 @@
970
1111
  * picked, which may differ from convos[0] when several empty conversations
971
1112
  * exist (feedback c: chat looked empty while messages went elsewhere).
972
1113
  */
973
- async function ensureCodegenUserMessagePersisted(widgetId: string, prompt: string): Promise<void> {
1114
+ function guessAttachmentFileType(url: string, fileName?: string | null, fallback?: string | null): string {
1115
+ if (fallback && String(fallback).includes('/')) return String(fallback);
1116
+ const hint = `${url || ''} ${fileName || ''}`;
1117
+ if (/\.pdf(\?|$)/i.test(hint)) return 'application/pdf';
1118
+ if (/\.(mp4|webm|mov)(\?|$)/i.test(hint)) return 'video/mp4';
1119
+ if (/\.(png|jpe?g|gif|webp|svg)(\?|$)/i.test(hint)) return 'image/png';
1120
+ if (fallback) return String(fallback);
1121
+ return 'application/octet-stream';
1122
+ }
1123
+
1124
+ function uniquePersistImageUrls(urls: string[]): string[] {
1125
+ const seen = new Set<string>();
1126
+ const httpsUrls: string[] = [];
1127
+ const dataUrls: string[] = [];
1128
+ for (const url of urls) {
1129
+ if (typeof url !== 'string' || url.length < 20 || seen.has(url)) continue;
1130
+ seen.add(url);
1131
+ if (/^https?:\/\//i.test(url)) httpsUrls.push(url);
1132
+ else if (url.startsWith('data:image/')) dataUrls.push(url);
1133
+ }
1134
+ // Same screenshot often exists as CDN URL + compressed data URL — keep HTTPS only.
1135
+ return httpsUrls.length > 0 ? httpsUrls : dataUrls;
1136
+ }
1137
+
1138
+ async function ensureCodegenUserMessagePersisted(
1139
+ widgetId: string,
1140
+ prompt: string,
1141
+ imageUrls?: string[],
1142
+ fileAttachments?: Array<{
1143
+ url?: string;
1144
+ file?: File;
1145
+ file_type?: string;
1146
+ file_name?: string | null;
1147
+ file_size?: number | null;
1148
+ display_order?: number;
1149
+ }>,
1150
+ ): Promise<void> {
974
1151
  if (!conversationsClient || !messagesClient) return;
975
1152
  try {
976
1153
  const panelChat = getChatMethodsForWidget(widgetId);
@@ -990,20 +1167,113 @@
990
1167
  convId = conv?.id;
991
1168
  }
992
1169
  if (!convId) return;
993
- if (convId !== visibleConversationId) {
994
- // Point the panel chat at the conversation that will hold the messages
995
- // so a later reopen shows the same thread the codegen used.
996
- panelChat?.selectConversation?.(convId);
997
- patchPanelSnapshot(widgetId, { currentChatConversationId: convId });
998
- }
999
1170
 
1000
- const messages = await handleLoadMessages(convId);
1001
- const hasUserMessage = messages?.some(
1171
+ const persistImages = uniquePersistImageUrls([
1172
+ ...(imageUrls || []),
1173
+ ...(getPanelState(widgetId).promptImages ?? []),
1174
+ ]);
1175
+ const compressedPersistImages: string[] = [];
1176
+ for (const url of persistImages) {
1177
+ if (typeof url === 'string' && url.startsWith('data:image/')) {
1178
+ compressedPersistImages.push(await compressImageDataUrlForCodegen(url));
1179
+ } else if (typeof url === 'string' && /^https?:\/\//i.test(url)) {
1180
+ compressedPersistImages.push(url);
1181
+ }
1182
+ }
1183
+ const persistFileItems = (fileAttachments || [])
1184
+ .filter((att) =>
1185
+ (typeof att.url === 'string' && (att.url.startsWith('blob:') || att.url.startsWith('data:') || /^https?:\/\//i.test(att.url)))
1186
+ || !!(att as { file?: File }).file,
1187
+ )
1188
+ .map((att, i) => ({
1189
+ url: att.url || '',
1190
+ file: (att as { file?: File }).file,
1191
+ file_type: guessAttachmentFileType(att.url || '', att.file_name, att.file_type),
1192
+ file_name: att.file_name || (att as { file?: File }).file?.name || `attachment-${i + 1}`,
1193
+ file_size: att.file_size && att.file_size > 0 ? att.file_size : (att as { file?: File }).file?.size,
1194
+ display_order: att.display_order ?? i + 1,
1195
+ }));
1196
+ if (compressedPersistImages.length === 0 && persistFileItems.length === 0) {
1197
+ console.warn('[Codegen] Persist user message has no attachment URLs for widget', widgetId?.substring(0, 8));
1198
+ }
1199
+ const imagePersistItems = compressedPersistImages.map((url, i) => ({
1200
+ url,
1201
+ file_type: guessAttachmentFileType(url, 'design-screenshot.png', 'image/png'),
1202
+ file_name: 'design-screenshot.png',
1203
+ display_order: i + 1,
1204
+ }));
1205
+ const persistAttachments = [...persistFileItems, ...imagePersistItems].map((att, i) => ({
1206
+ ...att,
1207
+ display_order: i + 1,
1208
+ }));
1209
+ const convMessages = await handleLoadMessages(convId, widgetId);
1210
+ const userMessages = (convMessages || []).filter(
1002
1211
  (m) => (m.messageType || (m as any).message_type) === 'user'
1003
1212
  );
1004
- if (!hasUserMessage) {
1005
- await handleSaveMessage(convId, buildUserPromptMessageContent(prompt), 'user');
1006
- console.log('[Codegen] Persisted initial user prompt on generate:', convId);
1213
+ const promptNeedle = (prompt || '').trim();
1214
+ const targetUser =
1215
+ (promptNeedle
1216
+ ? [...userMessages].reverse().find((m) => {
1217
+ const text = String(m.messageContent || (m as any).message_content || '').trim();
1218
+ return (
1219
+ text === promptNeedle ||
1220
+ text.endsWith(promptNeedle) ||
1221
+ (promptNeedle.length >= 12 && text.includes(promptNeedle.slice(0, 80)))
1222
+ );
1223
+ })
1224
+ : null) || userMessages[userMessages.length - 1] || null;
1225
+ const existingUrls = new Set(
1226
+ (targetUser?.attachments || [])
1227
+ .map((a: { url?: string }) => a.url)
1228
+ .filter((url: string | undefined): url is string => !!url),
1229
+ );
1230
+ const missingPersist = persistAttachments.filter((att) => {
1231
+ if (att.url && existingUrls.has(att.url)) return false;
1232
+ return true;
1233
+ });
1234
+ let savedAttachmentCount = targetUser?.attachments?.length || 0;
1235
+ if (!targetUser) {
1236
+ const saved = await handleSaveMessage(
1237
+ convId,
1238
+ buildUserPromptMessageContent(prompt),
1239
+ 'user',
1240
+ persistAttachments.length > 0 ? persistAttachments : undefined,
1241
+ widgetId,
1242
+ );
1243
+ const savedAtt = saved?.attachments || saved?.data?.attachments || [];
1244
+ savedAttachmentCount = Array.isArray(savedAtt) ? savedAtt.length : 0;
1245
+ console.log('[Codegen] Persisted initial user prompt on generate:', convId, 'attachments:', persistAttachments.length, 'saved:', savedAttachmentCount);
1246
+ if (Array.isArray(savedAtt) && savedAtt.length > 0) {
1247
+ panelChat?.updateLastUserMessageAttachments?.(savedAtt);
1248
+ }
1249
+ } else if (missingPersist.length > 0) {
1250
+ const merged = [...(targetUser.attachments || []), ...missingPersist];
1251
+ const apiAtt = await mapChatAttachmentsForApi(merged, widgetId);
1252
+ if (apiAtt.length > 0) {
1253
+ try {
1254
+ await messagesClient.updateMessageRaw({
1255
+ conversationId: convId,
1256
+ messageId: targetUser.id,
1257
+ updateMessageRequest: { attachments: apiAtt },
1258
+ });
1259
+ savedAttachmentCount = apiAtt.length;
1260
+ console.log('[Codegen] Saved missing attachments on user message:', targetUser.id, apiAtt.length);
1261
+ } catch (updateErr) {
1262
+ console.warn('[Codegen] Failed to PATCH user message attachments:', updateErr);
1263
+ }
1264
+ panelChat?.updateLastUserMessageAttachments?.(apiAtt);
1265
+ } else {
1266
+ console.warn('[Codegen] mapChatAttachmentsForApi returned 0 items — attachments not stored');
1267
+ }
1268
+ }
1269
+ if (persistAttachments.length > 0 && savedAttachmentCount === 0) {
1270
+ console.error('[Codegen] User prompt saved without attachments — chat reopen will show placeholder');
1271
+ }
1272
+ if (convId !== visibleConversationId) {
1273
+ await panelChat?.selectConversation?.(convId);
1274
+ patchPanelSnapshot(widgetId, { currentChatConversationId: convId });
1275
+ } else {
1276
+ console.log('[Codegen] Keep local chat attachments after persist; skip reload wipe');
1007
1277
  }
1008
1278
  } catch (err) {
1009
1279
  console.warn('[Codegen] Could not persist user prompt before generation:', err);
@@ -1028,18 +1298,27 @@
1028
1298
  console.log('[WidgetCreator] Cleared prompt draft for widget:', widgetId.substring(0, 8));
1029
1299
  }
1030
1300
 
1031
- /** Screenshot: capture widget preview iframe and return data URL */
1032
- async function handleChatScreenshot(): Promise<string | null> {
1033
- if (!widgetDetails?.hasValidPreview()) {
1034
- console.log('[Chat→Screenshot] Preview not ready');
1301
+ /** Screenshot: capture this panel's widget preview iframe and return data URL */
1302
+ async function handleChatScreenshot(widgetId?: string | null): Promise<string | null> {
1303
+ const targetId = widgetId || focusedPanelWidgetId || selectedWidgetId;
1304
+ const panelRef = getWidgetDetailsRef(targetId);
1305
+ const iframe =
1306
+ (panelRef?.getPreviewIframe?.() as HTMLIFrameElement | null) ||
1307
+ (document.querySelector('iframe[title="Widget preview"]') as HTMLIFrameElement | null);
1308
+
1309
+ if (!iframe?.contentWindow) {
1310
+ console.log('[Chat→Screenshot] Preview iframe not ready', {
1311
+ targetId: targetId?.substring(0, 8),
1312
+ hasValidPreview: panelRef?.hasValidPreview?.() ?? false,
1313
+ });
1035
1314
  return null;
1036
1315
  }
1037
- const iframe = document.querySelector('iframe[title="Widget preview"]') as HTMLIFrameElement;
1038
- if (!iframe?.contentWindow) return null;
1039
1316
 
1317
+ console.log('[Chat→Screenshot] Requesting screenshot from preview iframe', targetId?.substring(0, 8));
1040
1318
  return new Promise<string | null>((resolve) => {
1041
1319
  const timeout = setTimeout(() => {
1042
1320
  window.removeEventListener('message', handler);
1321
+ console.warn('[Chat→Screenshot] Timed out waiting for widgetic:screenshot');
1043
1322
  resolve(null);
1044
1323
  }, 8000);
1045
1324
 
@@ -1139,26 +1418,40 @@
1139
1418
  const json = await response.raw.json();
1140
1419
  const messages = json?.data || [];
1141
1420
  console.log('[Chat→Backend] Loaded messages for conversation:', conversationId, messages.length);
1142
- const mappedMessages = messages.map((m: any) => ({
1421
+ const mappedMessages = messages.map((m: any) => {
1422
+ const rawAttachments = m.attachments || m.message_contents || m.messageContents || [];
1423
+ const attachments = (Array.isArray(rawAttachments) ? rawAttachments : []).map((a: any, idx: number) => {
1424
+ const nested = a?.contents || a?.content;
1425
+ const content = Array.isArray(nested) ? (nested[0] || {}) : (nested || a || {});
1426
+ const url = content.url || content.public_url || content.publicUrl || content.file_url || a.url || '';
1427
+ let fileType = content.file_type || content.fileType || a.file_type || a.fileType || '';
1428
+ const fileName = content.file_name || content.fileName || a.file_name || a.fileName || null;
1429
+ if (!fileType || !String(fileType).includes('/')) {
1430
+ fileType = guessAttachmentFileType(url, fileName, fileType);
1431
+ }
1432
+ return {
1433
+ id: content.id || a.id || `att_${idx}`,
1434
+ url,
1435
+ file_type: fileType,
1436
+ file_name: fileName,
1437
+ file_size: content.file_size ?? content.fileSize ?? a.file_size ?? a.fileSize ?? null,
1438
+ display_order: a.display_order ?? a.displayOrder ?? content.display_order ?? idx + 1,
1439
+ created_at: content.created_at || content.createdAt || a.created_at || a.createdAt || new Date().toISOString(),
1440
+ };
1441
+ }).filter((a: { url: string }) => !!a.url);
1442
+ return {
1143
1443
  id: m.id,
1144
1444
  conversationId: m.conversation_id || m.conversationId || conversationId,
1145
1445
  messageContent: m.message_content || m.messageContent || '',
1146
1446
  messageType: m.message_type || m.messageType || 'user',
1147
- attachments: (m.attachments || []).map((a: any, idx: number) => ({
1148
- id: a.id || `att_${idx}`,
1149
- url: a.url || '',
1150
- file_type: a.file_type || a.fileType || 'image/png',
1151
- file_name: a.file_name || a.fileName || null,
1152
- file_size: a.file_size ?? a.fileSize ?? null,
1153
- display_order: a.display_order ?? a.displayOrder ?? idx + 1,
1154
- created_at: a.created_at || a.createdAt || new Date().toISOString(),
1155
- })),
1447
+ attachments,
1156
1448
  isCommit: m.is_commit || m.isCommit || false,
1157
1449
  userId: m.user_id || m.userId || '',
1158
1450
  createdAt: new Date(m.created_at || m.createdAt),
1159
1451
  updatedAt: new Date(m.updated_at || m.updatedAt || m.created_at || m.createdAt),
1160
1452
  rolledBackAt: m.rolled_back_at || m.rolledBackAt || null,
1161
- }));
1453
+ };
1454
+ });
1162
1455
  const withHead = markWidgetHeadOnLoadedMessages(mappedMessages);
1163
1456
  const syncWidgetId = widgetIdForSync ?? selectedWidgetId;
1164
1457
  if (syncWidgetId) {
@@ -1195,48 +1488,96 @@
1195
1488
  }
1196
1489
  }
1197
1490
 
1198
- /** Map chat attachment objects to API content items (compressed data URLs for images). */
1491
+ function dataUrlToUploadFile(dataUrl: string, fileName: string): File | null {
1492
+ try {
1493
+ const comma = dataUrl.indexOf(',');
1494
+ if (comma < 0) return null;
1495
+ const header = dataUrl.slice(0, comma);
1496
+ const mimeMatch = header.match(/data:([^;,]+)/);
1497
+ const mimeType = mimeMatch?.[1] || 'application/octet-stream';
1498
+ const binary = atob(dataUrl.slice(comma + 1));
1499
+ const bytes = new Uint8Array(binary.length);
1500
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1501
+ return new File([bytes], fileName, { type: mimeType });
1502
+ } catch (err) {
1503
+ console.warn('[Chat→Backend] Could not convert data URL to File:', err);
1504
+ return null;
1505
+ }
1506
+ }
1507
+
1508
+ /** Map chat attachments to API items. Inline data/blob files are uploaded to user storage (CDN URL). */
1199
1509
  async function mapChatAttachmentsForApi(
1200
1510
  attachments?: Array<{
1201
1511
  url?: string;
1512
+ file?: File;
1202
1513
  file_type?: string;
1203
1514
  file_name?: string | null;
1204
1515
  file_size?: number | null;
1205
1516
  display_order?: number;
1206
1517
  }>,
1518
+ persistWidgetId?: string | null,
1207
1519
  ): Promise<Array<{ url: string; file_type: string; file_name?: string; file_size?: number; display_order?: number }>> {
1208
1520
  if (!attachments?.length) return [];
1209
1521
  const items: Array<{ url: string; file_type: string; file_name?: string; file_size?: number; display_order?: number }> = [];
1210
1522
  for (let i = 0; i < attachments.length; i++) {
1211
1523
  const att = attachments[i];
1212
1524
  let url = att.url || '';
1213
- if (!url) continue;
1214
- if (url.startsWith('blob:')) {
1525
+ const fileName = att.file_name || att.file?.name || `attachment-${i + 1}`;
1526
+ if (att.file instanceof File && (!url || url.startsWith('blob:') || url.startsWith('data:'))) {
1215
1527
  try {
1216
- const blob = await fetch(url).then((r) => r.blob());
1217
- url = await new Promise<string>((resolve, reject) => {
1218
- const reader = new FileReader();
1219
- reader.onload = () => resolve(String(reader.result));
1220
- reader.onerror = () => reject(reader.error);
1221
- reader.readAsDataURL(blob);
1528
+ const uploaded = await handleChatUploadFile(att.file, {
1529
+ source: 'chat-message-persist',
1530
+ widgetId: persistWidgetId || undefined,
1222
1531
  });
1532
+ url = uploaded.url;
1533
+ console.log('[Chat→Backend] Uploaded chat File to user storage:', uploaded.id, url.slice(0, 80));
1534
+ items.push({
1535
+ url,
1536
+ file_type: guessAttachmentFileType(url, uploaded.fileName || fileName, uploaded.fileType || att.file_type || att.file.type),
1537
+ file_name: uploaded.fileName || fileName,
1538
+ file_size: uploaded.fileSize,
1539
+ display_order: att.display_order ?? i + 1,
1540
+ });
1541
+ continue;
1223
1542
  } catch (err) {
1224
- console.warn('[Chat→Backend] Could not convert blob attachment to data URL:', err);
1543
+ console.warn('[Chat→Backend] Chat File upload failed, skipping attachment:', err);
1225
1544
  continue;
1226
1545
  }
1227
1546
  }
1228
- if (url.startsWith('data:image/')) {
1229
- url = await compressImageDataUrlForCodegen(url, 960, 80_000);
1230
- // Oversized inline payloads often 500 on remote create_message RPC — skip attachment.
1231
- if (url.length > 100_000) {
1232
- console.warn('[Chat→Backend] Skipping oversized data-URL attachment for message save');
1547
+ if (!url) continue;
1548
+ if (url.startsWith('blob:') || url.startsWith('data:')) {
1549
+ try {
1550
+ let file: File | null = null;
1551
+ if (url.startsWith('blob:')) {
1552
+ const blob = await fetch(url).then((r) => r.blob());
1553
+ file = new File([blob], fileName, { type: blob.type || att.file_type || 'application/octet-stream' });
1554
+ } else {
1555
+ file = dataUrlToUploadFile(url, fileName);
1556
+ }
1557
+ if (!file) continue;
1558
+ const uploaded = await handleChatUploadFile(file, {
1559
+ source: 'chat-message-persist',
1560
+ widgetId: persistWidgetId || undefined,
1561
+ });
1562
+ url = uploaded.url;
1563
+ console.log('[Chat→Backend] Uploaded chat file to user storage:', uploaded.id, url.slice(0, 80));
1564
+ items.push({
1565
+ url,
1566
+ file_type: guessAttachmentFileType(url, uploaded.fileName || fileName, uploaded.fileType || att.file_type),
1567
+ file_name: uploaded.fileName || fileName,
1568
+ file_size: uploaded.fileSize,
1569
+ display_order: att.display_order ?? i + 1,
1570
+ });
1571
+ continue;
1572
+ } catch (err) {
1573
+ console.warn('[Chat→Backend] Chat file upload failed, skipping attachment:', err);
1233
1574
  continue;
1234
1575
  }
1235
1576
  }
1236
1577
  items.push({
1237
1578
  url,
1238
- file_type: att.file_type || 'image/png',
1239
- file_name: att.file_name || `attachment-${i + 1}.png`,
1579
+ file_type: guessAttachmentFileType(url, fileName, att.file_type),
1580
+ file_name: fileName,
1240
1581
  file_size: att.file_size ?? undefined,
1241
1582
  display_order: att.display_order ?? i + 1,
1242
1583
  });
@@ -1251,11 +1592,13 @@
1251
1592
  messageType: string,
1252
1593
  attachments?: Array<{
1253
1594
  url?: string;
1595
+ file?: File;
1254
1596
  file_type?: string;
1255
1597
  file_name?: string | null;
1256
1598
  file_size?: number | null;
1257
1599
  display_order?: number;
1258
1600
  }>,
1601
+ persistWidgetId?: string | null,
1259
1602
  ): Promise<any> {
1260
1603
  try {
1261
1604
  if (!messagesClient) {
@@ -1268,7 +1611,17 @@
1268
1611
  return null;
1269
1612
  }
1270
1613
  const apiAttachments =
1271
- messageType === 'user' ? await mapChatAttachmentsForApi(attachments) : [];
1614
+ messageType === 'user' ? await mapChatAttachmentsForApi(
1615
+ (attachments || []).map((att, i) => ({
1616
+ url: att.url,
1617
+ file: att.file,
1618
+ file_type: guessAttachmentFileType(att.url || '', att.file_name, att.file_type),
1619
+ file_name: att.file_name || att.file?.name || `attachment-${i + 1}`,
1620
+ file_size: att.file_size && att.file_size > 0 ? att.file_size : att.file?.size,
1621
+ display_order: att.display_order ?? i + 1,
1622
+ })),
1623
+ persistWidgetId,
1624
+ ) : [];
1272
1625
  const createPayload = {
1273
1626
  message_content: content,
1274
1627
  message_type: messageType as 'user' | 'assistant',
@@ -1283,25 +1636,18 @@
1283
1636
  });
1284
1637
  const json = await response.raw.json();
1285
1638
  console.log('[Chat→Backend] Saved message to conversation:', conversationId);
1286
- return json?.data || json;
1639
+ const saved = json?.data || json;
1640
+ const savedAtt = saved?.attachments;
1641
+ return {
1642
+ ...saved,
1643
+ attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt : apiAttachments,
1644
+ };
1287
1645
  } catch (withAttachmentsError) {
1288
- // Remote RPC sometimes 500s on large/strict attachment payloads — retry text-only
1289
- // so codegen can continue (vision uses attachmentIds separately).
1290
- if (apiAttachments.length === 0) throw withAttachmentsError;
1291
- console.warn(
1292
- '[Chat→Backend] Save with attachments failed, retrying text-only:',
1646
+ console.error(
1647
+ '[Chat→Backend] Save with attachments failed not falling back to text-only:',
1293
1648
  withAttachmentsError,
1294
1649
  );
1295
- const response = await messagesClient.createMessageRaw({
1296
- conversationId,
1297
- createMessageRequest: {
1298
- ...createPayload,
1299
- attachments: undefined,
1300
- },
1301
- });
1302
- const json = await response.raw.json();
1303
- console.log('[Chat→Backend] Saved message (text-only fallback):', conversationId);
1304
- return json?.data || json;
1650
+ throw withAttachmentsError;
1305
1651
  }
1306
1652
  } catch (error) {
1307
1653
  console.error('[Chat→Backend] Failed to save message:', error);
@@ -1496,6 +1842,9 @@
1496
1842
  );
1497
1843
  const sessionAhead = sessionOrdered.find((candidate) => !commitsMatch(candidate, liveSha));
1498
1844
  if (sessionAhead) return sessionAhead;
1845
+ // localStorage HEAD written at codegen complete — must win over live SHA
1846
+ // even before snap/live.lastCommitId propagate (reopen currently "fixed" this).
1847
+ if (stored && !commitsMatch(stored, liveSha)) return stored;
1499
1848
  const sessionMatch = sessionOrdered.find((candidate) => commitsMatch(candidate, liveSha));
1500
1849
  if (sessionMatch) return sessionMatch;
1501
1850
  if (stored && commitsMatch(stored, liveSha)) return stored;
@@ -1554,18 +1903,21 @@
1554
1903
  let editingCompositionName = '';
1555
1904
  let deletingCompositionId: string | null = null;
1556
1905
 
1557
- /** Minimum chat height; flex-1 grows to fill leftover space above Publish. */
1558
- const DEFAULT_CHAT_CONTAINER_HEIGHT = 280;
1906
+ /** Explicit chat box height (px). Drag grows this and pushes Publish down. */
1907
+ const CHAT_DEFAULT_HEIGHT = 550;
1559
1908
  const CHAT_MIN_HEIGHT = 260;
1560
- const CHAT_MAX_HEIGHT = 1400;
1909
+ const CHAT_MAX_HEIGHT = 2000;
1561
1910
 
1562
1911
  function getChatContainerHeightForPanel(widgetId: string): number {
1563
- return getPanelState(widgetId).chatContainerHeight ?? DEFAULT_CHAT_CONTAINER_HEIGHT;
1912
+ const stored = getPanelState(widgetId).chatContainerHeight;
1913
+ if (stored == null || stored <= 0) return CHAT_DEFAULT_HEIGHT;
1914
+ return Math.max(CHAT_MIN_HEIGHT, Math.min(CHAT_MAX_HEIGHT, stored));
1564
1915
  }
1565
1916
 
1566
1917
  function handleChatMessagesResize(widgetId: string, deltaPx: number): void {
1567
1918
  const current = getChatContainerHeightForPanel(widgetId);
1568
1919
  const next = Math.max(CHAT_MIN_HEIGHT, Math.min(CHAT_MAX_HEIGHT, current + deltaPx));
1920
+ console.log('[Chat] Resize container height:', widgetId?.substring(0, 8), current, '→', next);
1569
1921
  patchPanelSnapshot(widgetId, { chatContainerHeight: next });
1570
1922
  }
1571
1923
 
@@ -1924,7 +2276,11 @@
1924
2276
  compositionSchemasError = schemaError;
1925
2277
  }
1926
2278
  if (designSchema || contentSchema) {
1927
- queueMicrotask(() => loadCompositionsForPanel(widgetId));
2279
+ queueMicrotask(() => {
2280
+ void loadCompositionsForPanel(widgetId).then(() => {
2281
+ applyPanelCompositionToPreview(widgetId);
2282
+ });
2283
+ });
1928
2284
  }
1929
2285
  } finally {
1930
2286
  schemaLoadInFlight.delete(widgetId);
@@ -1962,20 +2318,32 @@
1962
2318
  return values;
1963
2319
  }
1964
2320
 
1965
- /** After codegen, push design.json defaults into Create preview (Worker HTML uses var() fallbacks only). */
2321
+ /** After codegen, push design.json + content.json defaults into Create preview. */
1966
2322
  async function applyDesignSchemaDefaultsToPreview(widgetId: string) {
1967
2323
  if (!widgetId || !widgetsClient) return;
1968
2324
  await scheduleWidgetSchemaLoad(widgetId);
1969
2325
  const schema = getPanelDisplayState(widgetId).compositionDesignSchema;
1970
2326
  const defaults = extractDesignDefaultsFromSchema(schema);
1971
- if (Object.keys(defaults).length === 0) return;
1972
- if (widgetId === focusedPanelWidgetId) {
1973
- liveDesignValues = defaults;
1974
- } else {
1975
- patchPanelSnapshot(widgetId, { liveDesignValues: defaults });
2327
+ if (Object.keys(defaults).length > 0) {
2328
+ if (widgetId === focusedPanelWidgetId) {
2329
+ liveDesignValues = defaults;
2330
+ } else {
2331
+ patchPanelSnapshot(widgetId, { liveDesignValues: defaults });
2332
+ }
2333
+ getWidgetDetailsRef(widgetId)?.sendMessageToPreview({ type: 'widgetic:update', design: defaults });
2334
+ console.log('[Create Preview] Applied design schema defaults:', Object.keys(defaults));
2335
+ }
2336
+ const contentSchema = getPanelDisplayState(widgetId).compositionContentSchema;
2337
+ const schemaItems = contentSchema?.contentItems ?? contentSchema?.data?.contentItems;
2338
+ if (Array.isArray(schemaItems) && schemaItems.length > 0) {
2339
+ const existing = getPanelDisplayState(widgetId).liveContentItems || [];
2340
+ const merged = mergeContentItemsWithSchema(existing, schemaItems);
2341
+ patchPanelSnapshot(widgetId, { liveContentItems: merged });
2342
+ if (widgetId === focusedPanelWidgetId) liveContentItems = merged;
1976
2343
  }
1977
- getWidgetDetailsRef(widgetId)?.sendMessageToPreview({ type: 'widgetic:update', design: defaults });
1978
- console.log('[Create Preview] Applied design schema defaults:', Object.keys(defaults));
2344
+ applyPanelCompositionToPreview(widgetId);
2345
+ setTimeout(() => applyPanelCompositionToPreview(widgetId), 250);
2346
+ setTimeout(() => applyPanelCompositionToPreview(widgetId), 800);
1979
2347
  }
1980
2348
 
1981
2349
  /** Toggle composition editor panel open/closed */
@@ -2045,9 +2413,20 @@
2045
2413
  });
2046
2414
  }
2047
2415
  if (event.detail.step === 'create') {
2048
- if (Object.keys(ps.liveDesignValues).length > 0) {
2049
- panelRef?.sendMessageToPreview({ type: 'widgetic:update', design: ps.liveDesignValues });
2416
+ let psCreate = getPanelDisplayState(panelWidgetId);
2417
+ if (!psCreate.compositionContentSchema && !psCreate.compositionSchemasLoading) {
2418
+ void scheduleWidgetSchemaLoad(panelWidgetId);
2050
2419
  }
2420
+ if (!psCreate.liveContentItems || psCreate.liveContentItems.length === 0) {
2421
+ const schemaItems = psCreate.compositionContentSchema?.contentItems
2422
+ ?? psCreate.compositionContentSchema?.data?.contentItems;
2423
+ if (Array.isArray(schemaItems) && schemaItems.length > 0) {
2424
+ const seeded = JSON.parse(JSON.stringify(schemaItems));
2425
+ patchPanelSnapshot(panelWidgetId, { liveContentItems: seeded });
2426
+ if (panelWidgetId === focusedPanelWidgetId) liveContentItems = seeded;
2427
+ }
2428
+ }
2429
+ applyPanelCompositionToPreview(panelWidgetId);
2051
2430
  if (panelWidgetId && pendingCreatePreviewDefaultsWidgetId === panelWidgetId) {
2052
2431
  pendingCreatePreviewDefaultsWidgetId = null;
2053
2432
  if (panelWidgetId === focusedPanelWidgetId) {
@@ -2593,7 +2972,10 @@
2593
2972
  widgetDetails?.sendMessageToPreview({ type: 'widgetic:update', content: liveContentValues });
2594
2973
  }
2595
2974
  if (liveContentItems.length > 0) {
2596
- widgetDetails?.sendMessageToPreview({ type: 'widgetic:update', contentItems: liveContentItems });
2975
+ widgetDetails?.sendMessageToPreview({
2976
+ type: 'widgetic:update',
2977
+ contentItems: flattenContentItemsForPreview(liveContentItems)
2978
+ });
2597
2979
  }
2598
2980
  }
2599
2981
 
@@ -3003,6 +3385,8 @@
3003
3385
  }
3004
3386
 
3005
3387
  let canvasWireframeMode = true;
3388
+ /** Extra canvas chrome — default off. Not tied to debugMode. */
3389
+ let canvasAdvancedFeatures = !!initialAdvancedFeatures;
3006
3390
  let canvasPrimaryWidgetId: string | null = null;
3007
3391
 
3008
3392
  function emitCreatorWireframeChanged() {
@@ -3020,6 +3404,21 @@
3020
3404
  emitCreatorWireframeChanged();
3021
3405
  }
3022
3406
 
3407
+ function emitCreatorAdvancedChanged() {
3408
+ if (typeof window === 'undefined') return;
3409
+ window.dispatchEvent(
3410
+ new CustomEvent(CREATOR_ADVANCED_CHANGED_EVENT, {
3411
+ detail: { advancedFeatures: canvasAdvancedFeatures }
3412
+ })
3413
+ );
3414
+ }
3415
+
3416
+ function handleHostToggleAdvancedEvent() {
3417
+ canvasAdvancedFeatures = !canvasAdvancedFeatures;
3418
+ console.log('[CreatorApp] Advanced canvas features:', canvasAdvancedFeatures ? 'ON' : 'OFF');
3419
+ emitCreatorAdvancedChanged();
3420
+ }
3421
+
3023
3422
  function resolveHostTargetWidgetId(): string | null {
3024
3423
  const id = focusedPanelWidgetId || selectedWidgetId;
3025
3424
  if (!id || isDraftConvertId(id)) return null;
@@ -3616,7 +4015,7 @@
3616
4015
  canvasPrimaryWidgetId ||
3617
4016
  canvasRef.getPrimaryWidgetId?.() ||
3618
4017
  selectedWidgetId;
3619
- if (primaryId) {
4018
+ if (primaryId && !isConvertOrDraftPanelOpen()) {
3620
4019
  canvasRef.setPrimaryWidgetShape?.(primaryId);
3621
4020
  canvasRef.selectWidgetShape(primaryId);
3622
4021
  }
@@ -4481,6 +4880,11 @@
4481
4880
  if (embeddedDeepLinkAutoOpenDone) {
4482
4881
  return;
4483
4882
  }
4883
+ if (isConvertOrDraftPanelOpen()) {
4884
+ console.log('[CreatorApp] Skip embedded deep-link select — convert/draft panel is open');
4885
+ embeddedDeepLinkAutoOpenDone = true;
4886
+ return;
4887
+ }
4484
4888
 
4485
4889
  let deepLinkWidgetId = resolveEmbeddedDeepLinkWidgetId();
4486
4890
  // Canvas-only deep-link: no ?widget_id= / initialWidgetId — select canvases.primary_widget_id.
@@ -5247,11 +5651,7 @@
5247
5651
 
5248
5652
  const status = (op?.status || '').toLowerCase();
5249
5653
  if (status === 'completed' || status === 'failed' || status === 'cancelled') {
5250
- if (status === 'failed') {
5251
- settle('reject', new Error(op?.error || 'Operation failed.'));
5252
- } else {
5253
- settle('resolve', op);
5254
- }
5654
+ settle('resolve', op);
5255
5655
  return;
5256
5656
  }
5257
5657
 
@@ -5290,11 +5690,7 @@
5290
5690
  signal
5291
5691
  })
5292
5692
  .then((finalOp) => {
5293
- if (String(finalOp?.status || '').toLowerCase() === 'failed') {
5294
- settle('reject', new Error(finalOp?.error || 'Operation failed.'));
5295
- } else {
5296
- settle('resolve', finalOp as OperationStatus);
5297
- }
5693
+ settle('resolve', finalOp as OperationStatus);
5298
5694
  })
5299
5695
  .catch((streamError) => {
5300
5696
  if (settled || signal?.aborted) return;
@@ -5822,11 +6218,22 @@
5822
6218
  const quiet = !!options.quiet;
5823
6219
  console.log('[handleRefreshScreenshot] Capturing screenshot via postMessage for widget:', widgetId, quiet ? '(quiet)' : '');
5824
6220
 
6221
+ if (isConvertOrDraftPanelOpen() && !isDraftConvertId(widgetId)) {
6222
+ console.log('[handleRefreshScreenshot] Skip iframe capture — convert/draft panel is open:', widgetId.substring(0, 8));
6223
+ return;
6224
+ }
6225
+
5825
6226
  // Quiet canvas-thumbnail sync must never force-open Widget Details.
5826
- // Manual refresh may open the panel so the preview iframe exists.
6227
+ // Convert drafts capture from the canvas shape, never by opening a panel.
5827
6228
  if (!openWidgetPanelIds.includes(widgetId)) {
5828
- if (quiet) {
5829
- console.log('[handleRefreshScreenshot] Skip quiet capture — panel closed:', widgetId.substring(0, 8));
6229
+ if (quiet || isConvertOrDraftPanelOpen()) {
6230
+ const shapeDataUrl = canvasRef?.getWidgetShapeDataUrl?.(widgetId);
6231
+ if (shapeDataUrl && canvasRef) {
6232
+ console.log('[handleRefreshScreenshot] Using canvas shape screenshot (panel closed):', widgetId.substring(0, 8));
6233
+ canvasRef.updateWidgetImage(widgetId, shapeDataUrl);
6234
+ } else {
6235
+ console.log('[handleRefreshScreenshot] Skip quiet capture — panel closed:', widgetId.substring(0, 8));
6236
+ }
5830
6237
  return;
5831
6238
  }
5832
6239
  // Manual "Refresh Widget Preview" always opens the panel so the iframe exists.
@@ -5836,8 +6243,9 @@
5836
6243
  }
5837
6244
 
5838
6245
  const panelRef = getWidgetDetailsRef(widgetId);
5839
- if (!panelRef?.hasValidPreview()) {
5840
- console.log('[handleRefreshScreenshot] Preview not ready or has error — skipping empty screenshot capture');
6246
+ const iframe = panelRef?.getPreviewIframe?.() as HTMLIFrameElement | null;
6247
+ if (!iframe?.contentWindow) {
6248
+ console.log('[handleRefreshScreenshot] Preview iframe not ready — skipping capture');
5841
6249
  if (!quiet) {
5842
6250
  showToast('error', 'No valid preview available', { description: 'Generate code first to capture a screenshot.', duration: 5000 });
5843
6251
  }
@@ -5848,14 +6256,6 @@
5848
6256
  showToast('info', 'Capturing widget screenshot...', { duration: 3000 });
5849
6257
  }
5850
6258
 
5851
- const iframe = panelRef?.getPreviewIframe?.() as HTMLIFrameElement | null;
5852
- if (!iframe?.contentWindow) {
5853
- if (!quiet) {
5854
- showToast('error', 'No preview iframe available', { description: 'Generate code first to capture a screenshot.', duration: 5000 });
5855
- }
5856
- return;
5857
- }
5858
-
5859
6259
  try {
5860
6260
  const result = await new Promise<{ data: string | null; error: string | null }>((resolve) => {
5861
6261
  const timeout = setTimeout(() => {
@@ -5982,10 +6382,21 @@ Requirements:
5982
6382
  fromChat?: boolean;
5983
6383
  widgetId?: string;
5984
6384
  compileFixOnly?: boolean;
6385
+ /** Recreate GitLab repo when the previous turn failed with missing repository. */
6386
+ forceRecreateRepo?: boolean;
5985
6387
  /** CDN upload row IDs captured before chat clearDraft. */
5986
6388
  attachmentIds?: string[];
5987
6389
  /** Image URLs / data URLs captured before chat clearDraft. */
5988
6390
  promptImagesOverride?: string[];
6391
+ /** Chat files (PDF, video, images) to store on the user message before codegen. */
6392
+ persistFileAttachments?: Array<{
6393
+ url?: string;
6394
+ file?: File;
6395
+ file_type?: string;
6396
+ file_name?: string | null;
6397
+ file_size?: number | null;
6398
+ display_order?: number;
6399
+ }>;
5989
6400
  } = {}) {
5990
6401
  const targetWidgetId = options.widgetId ?? selectedWidgetId;
5991
6402
  if (!targetWidgetId) return;
@@ -6062,8 +6473,10 @@ Requirements:
6062
6473
  fromChat: options.fromChat,
6063
6474
  widgetId: codegenTargetWidgetId,
6064
6475
  compileFixOnly: options.compileFixOnly,
6476
+ forceRecreateRepo: options.forceRecreateRepo,
6065
6477
  attachmentIds: options.attachmentIds,
6066
6478
  promptImagesOverride: options.promptImagesOverride,
6479
+ persistFileAttachments: options.persistFileAttachments,
6067
6480
  });
6068
6481
  }
6069
6482
 
@@ -6082,8 +6495,17 @@ Requirements:
6082
6495
  fromChat?: boolean;
6083
6496
  widgetId?: string;
6084
6497
  compileFixOnly?: boolean;
6498
+ forceRecreateRepo?: boolean;
6085
6499
  attachmentIds?: string[];
6086
6500
  promptImagesOverride?: string[];
6501
+ persistFileAttachments?: Array<{
6502
+ url?: string;
6503
+ file?: File;
6504
+ file_type?: string;
6505
+ file_name?: string | null;
6506
+ file_size?: number | null;
6507
+ display_order?: number;
6508
+ }>;
6087
6509
  } = {},
6088
6510
  ) {
6089
6511
  const generationWidgetId = options.widgetId ?? selectedWidgetId;
@@ -6106,7 +6528,6 @@ Requirements:
6106
6528
  lastHarnessChatLine = '';
6107
6529
 
6108
6530
  // Show user prompt in chat when triggered outside Chat (confirm dialog / Generate Now).
6109
- // DB write happens here too — not on widget select (so "Edit Prompt First" stays editable).
6110
6531
  if (panelChat && !options.fromChat) {
6111
6532
  const snapImages =
6112
6533
  generationWidgetId === focusedPanelWidgetId ? promptImages : panelSnap.promptImages;
@@ -6120,8 +6541,7 @@ Requirements:
6120
6541
  : undefined;
6121
6542
  panelChat.addUserMessage(prompt, codegenImageAttachments);
6122
6543
  }
6123
- await ensureCodegenUserMessagePersisted(generationWidgetId, prompt);
6124
-
6544
+
6125
6545
  isGeneratingCode = generationWidgetId === focusedPanelWidgetId;
6126
6546
  setCrossTabGenerating(true, generationWidgetId);
6127
6547
  lastGenerationPrompt = prompt;
@@ -6132,6 +6552,16 @@ Requirements:
6132
6552
  ? 'Processing queued code generation request...'
6133
6553
  : 'Preparing code generation...',
6134
6554
  });
6555
+
6556
+ // Persist chat attachments before repo/codegen so reopen + failed GLM still show the file.
6557
+ if (!options.compileFixOnly) {
6558
+ await ensureCodegenUserMessagePersisted(
6559
+ generationWidgetId,
6560
+ prompt,
6561
+ options.promptImagesOverride,
6562
+ options.persistFileAttachments,
6563
+ );
6564
+ }
6135
6565
 
6136
6566
  // Keep preview tab active so user sees the "Generating..." overlay.
6137
6567
  // Console logs are still accessible via the Console tab button.
@@ -6141,22 +6571,52 @@ Requirements:
6141
6571
  console.log('Widget Creator: Starting code generation for widget:', generationWidgetId);
6142
6572
 
6143
6573
  try {
6144
- const generationWidget = getWidgetById(generationWidgetId);
6145
- const repoId = generationWidget
6146
- ? await ensureWidgetRepository(generationWidget)
6147
- : panelSnap.currentRepositoryId;
6574
+ const generationWidget =
6575
+ getWidgetById(generationWidgetId) ||
6576
+ (selectedWidget?.id === generationWidgetId ? selectedWidget : null);
6577
+ let repoId: string | null = null;
6578
+ try {
6579
+ repoId = generationWidget
6580
+ ? await ensureWidgetRepository(generationWidget, {
6581
+ forceRecreate: !!options.forceRecreateRepo,
6582
+ silent: true,
6583
+ })
6584
+ : panelSnap.currentRepositoryId;
6585
+ if (!repoId && generationWidget) {
6586
+ console.warn('[CodeGen] Repository missing — retrying create for widget', generationWidgetId.substring(0, 8));
6587
+ repoId = await ensureWidgetRepository(generationWidget, { forceRecreate: true, silent: true });
6588
+ }
6589
+ } catch (repoError) {
6590
+ if (repoError instanceof ResponseError && repoError.response?.status === 401) {
6591
+ const sessionError = 'Session expired. Sign in again, then retry.';
6592
+ patchCodegenUiState(generationWidgetId, {
6593
+ generateCodeStatus: '❌ Session expired.',
6594
+ lastGenerationFailed: true,
6595
+ lastGenerationError: sessionError,
6596
+ });
6597
+ showToast('error', 'Session expired', {
6598
+ description: sessionError,
6599
+ duration: 5000
6600
+ });
6601
+ pushCodegenFailureToChat(generationWidgetId, sessionError);
6602
+ return;
6603
+ }
6604
+ throw repoError;
6605
+ }
6148
6606
  console.log('Widget Creator: Repository resolved for code generation:', repoId);
6149
6607
 
6150
6608
  if (!repoId) {
6609
+ const missingRepoError = 'Unable to resolve repository for this widget.';
6151
6610
  patchCodegenUiState(generationWidgetId, {
6152
6611
  generateCodeStatus: '❌ Repository is required to generate code.',
6153
6612
  lastGenerationFailed: true,
6154
- lastGenerationError: 'Unable to resolve repository for this widget.',
6613
+ lastGenerationError: missingRepoError,
6155
6614
  });
6156
6615
  showToast('error', 'Missing repository', {
6157
- description: 'Unable to resolve repository for this widget.',
6616
+ description: missingRepoError,
6158
6617
  duration: 5000
6159
6618
  });
6619
+ pushCodegenFailureToChat(generationWidgetId, missingRepoError);
6160
6620
  return;
6161
6621
  }
6162
6622
 
@@ -6182,7 +6642,7 @@ Requirements:
6182
6642
  // Drop invalid entries (empty strings, non-data-URLs) so a failed capture
6183
6643
  // can never silently become a vision-less generation request.
6184
6644
  panelPromptImages = panelPromptImages.filter(
6185
- (url) => typeof url === 'string' && url.startsWith('data:image/') && url.length > 100,
6645
+ (url) => typeof url === 'string' && (url.startsWith('data:image/') || /^https?:\/\//i.test(url)) && url.length > 20,
6186
6646
  );
6187
6647
  const chatAttachmentUrls = panelChat?.getAttachmentDataUrls?.() ?? [];
6188
6648
  const chatUploadIds = [
@@ -6190,9 +6650,12 @@ Requirements:
6190
6650
  ...(panelChat?.getAttachmentUploadIds?.() ?? []),
6191
6651
  ].filter((id, index, arr) => id && arr.indexOf(id) === index);
6192
6652
  if (chatAttachmentUrls.length > 0 && !(options.promptImagesOverride?.length)) {
6193
- panelPromptImages = chatAttachmentUrls.filter(
6194
- (url) => typeof url === 'string' && url.startsWith('data:image/') && url.length > 100,
6653
+ const fromChat = chatAttachmentUrls.filter(
6654
+ (url) => typeof url === 'string' && (url.startsWith('data:image/') || /^https?:\/\//i.test(url)) && url.length > 20,
6195
6655
  );
6656
+ if (fromChat.length > 0) {
6657
+ panelPromptImages = fromChat;
6658
+ }
6196
6659
  }
6197
6660
  let imagesToSend: string[] | undefined;
6198
6661
  let attachmentIdsToSend: string[] | undefined;
@@ -6250,6 +6713,18 @@ Requirements:
6250
6713
  });
6251
6714
  }
6252
6715
 
6716
+ const persistImageUrls = uniquePersistImageUrls([
6717
+ ...(imagesToSend || []),
6718
+ ...(panelPromptImages || []),
6719
+ ...(options.promptImagesOverride || []),
6720
+ ]);
6721
+ await ensureCodegenUserMessagePersisted(
6722
+ generationWidgetId,
6723
+ prompt,
6724
+ persistImageUrls,
6725
+ options.persistFileAttachments,
6726
+ );
6727
+
6253
6728
  // Clear unsent draft/attachments once payload is captured — prevents restore on widget return
6254
6729
  clearPromptDraftForWidget(generationWidgetId);
6255
6730
 
@@ -6405,6 +6880,11 @@ Requirements:
6405
6880
  hasGeneratedCode: true,
6406
6881
  ...(completedCommitSha ? { lastCommitId: completedCommitSha } : {}),
6407
6882
  });
6883
+ if (isFocusedPanel && completedCommitSha) {
6884
+ lastCommitId = completedCommitSha;
6885
+ hasGeneratedCode = true;
6886
+ }
6887
+ panelDisplayRevision += 1;
6408
6888
  console.log(
6409
6889
  '[Publish] Codegen completed — pendingPublishAfterCodegen=true',
6410
6890
  generationWidgetId.substring(0, 8),
@@ -6548,8 +7028,9 @@ Requirements:
6548
7028
  );
6549
7029
  setTimeout(async () => {
6550
7030
  await panelChat?.reloadConversationMessages?.();
6551
- }, 1500);
6552
- setTimeout(() => handleRefreshScreenshot(generationWidgetId), 3000);
7031
+ panelChat?.markWidgetHeadAtLatestCommit?.();
7032
+ }, 2500);
7033
+ setTimeout(() => handleRefreshScreenshot(generationWidgetId, { quiet: true }), 3000);
6553
7034
  } else {
6554
7035
  panelChat?.updateLatestCodegenStatusMessage?.(
6555
7036
  `Code saved${commitShort ? ` (${commitShort})` : ''} — preview rebuilt from saved code.`,
@@ -6621,6 +7102,7 @@ Requirements:
6621
7102
  });
6622
7103
  clearActiveOperationId(); // Clear persisted operationId on error
6623
7104
  showToast('error', 'Code generation error', { description: message, duration: 6000 });
7105
+ pushCodegenFailureToChat(generationWidgetId, message);
6624
7106
  } finally {
6625
7107
  const { [generationWidgetId]: _done, ...restFinally } = generatingWidgetIds;
6626
7108
  generatingWidgetIds = restFinally;
@@ -6965,6 +7447,7 @@ Requirements:
6965
7447
 
6966
7448
  // Cross-tab operation sync: listen for localStorage changes from other tabs
6967
7449
  window.addEventListener('storage', handleCrossTabStorageEvent);
7450
+ window.addEventListener('widgetic:session-extended', handleSiteSessionExtended);
6968
7451
 
6969
7452
  // Clean up cross-tab flags when tab is closed/refreshed
6970
7453
  window.addEventListener('beforeunload', () => {
@@ -7058,11 +7541,13 @@ Requirements:
7058
7541
  // Cross-tab cleanup: remove flags so other tabs don't stay blocked
7059
7542
  if (typeof window !== 'undefined') {
7060
7543
  window.removeEventListener('storage', handleCrossTabStorageEvent);
7544
+ window.removeEventListener('widgetic:session-extended', handleSiteSessionExtended);
7061
7545
  window.removeEventListener('click', handleCanvasDropdownOutsideClick);
7062
7546
  window.removeEventListener(HOST_WIDGET_RENAMED_EVENT, handleHostWidgetRenamedEvent);
7063
7547
  window.removeEventListener(HOST_REQUEST_PUBLISH_EVENT, handleHostRequestPublishEvent);
7064
7548
  window.removeEventListener(HOST_TOGGLE_DEBUG_EVENT, handleHostToggleDebugEvent);
7065
7549
  window.removeEventListener(HOST_TOGGLE_WIREFRAME_EVENT, handleHostToggleWireframeEvent);
7550
+ window.removeEventListener(HOST_TOGGLE_ADVANCED_EVENT, handleHostToggleAdvancedEvent);
7066
7551
  window.removeEventListener(HOST_OPEN_WIDGET_DETAILS_EVENT, handleHostOpenWidgetDetailsEvent);
7067
7552
  window.removeEventListener(HOST_DUPLICATE_WIDGET_EVENT, handleHostDuplicateWidgetEvent);
7068
7553
  window.removeEventListener(HOST_DELETE_WIDGET_EVENT, handleHostDeleteWidgetEvent);
@@ -7091,11 +7576,13 @@ Requirements:
7091
7576
  window.addEventListener(HOST_REQUEST_PUBLISH_EVENT, handleHostRequestPublishEvent);
7092
7577
  window.addEventListener(HOST_TOGGLE_DEBUG_EVENT, handleHostToggleDebugEvent);
7093
7578
  window.addEventListener(HOST_TOGGLE_WIREFRAME_EVENT, handleHostToggleWireframeEvent);
7579
+ window.addEventListener(HOST_TOGGLE_ADVANCED_EVENT, handleHostToggleAdvancedEvent);
7094
7580
  window.addEventListener(HOST_OPEN_WIDGET_DETAILS_EVENT, handleHostOpenWidgetDetailsEvent);
7095
7581
  window.addEventListener(HOST_DUPLICATE_WIDGET_EVENT, handleHostDuplicateWidgetEvent);
7096
7582
  window.addEventListener(HOST_DELETE_WIDGET_EVENT, handleHostDeleteWidgetEvent);
7097
7583
  emitCreatorDebugChanged();
7098
7584
  emitCreatorWireframeChanged();
7585
+ emitCreatorAdvancedChanged();
7099
7586
  });
7100
7587
 
7101
7588
  // Capture-phase Escape: close lightbox before Canvas can deselect → close WidgetDetails
@@ -7488,7 +7975,7 @@ Requirements:
7488
7975
  compositionSchemasLoading,
7489
7976
  compositionSchemasError,
7490
7977
  schemaLoadStartedAt: focusedSnap?.schemaLoadStartedAt ?? null,
7491
- chatContainerHeight: focusedSnap?.chatContainerHeight ?? DEFAULT_CHAT_CONTAINER_HEIGHT,
7978
+ chatContainerHeight: focusedSnap?.chatContainerHeight ?? CHAT_DEFAULT_HEIGHT,
7492
7979
  panelPreviewCompileError: focusedSnap?.panelPreviewCompileError ?? null,
7493
7980
  };
7494
7981
  }
@@ -7541,7 +8028,7 @@ Requirements:
7541
8028
  compositionSchemasLoading: false,
7542
8029
  compositionSchemasError: null,
7543
8030
  schemaLoadStartedAt: null,
7544
- chatContainerHeight: DEFAULT_CHAT_CONTAINER_HEIGHT,
8031
+ chatContainerHeight: 550,
7545
8032
  panelPreviewCompileError: null,
7546
8033
  };
7547
8034
  }
@@ -7567,16 +8054,18 @@ Requirements:
7567
8054
  const snapPublishReady =
7568
8055
  snap.publishHistoryResolved && snap.publishHistoryResolvedForWidgetId === widgetId;
7569
8056
  const livePublishMatchesPanel = live.publishHistoryResolvedForWidgetId === widgetId;
7570
- const resolvedHead = resolvePanelHeadCommitId(widgetId, snap, live);
8057
+ const resolvedHead = isDraftConvertId(widgetId) ? null : resolvePanelHeadCommitId(widgetId, snap, live);
7571
8058
  return {
7572
8059
  ...snap,
7573
8060
  ...live,
7574
- // Prefer HEAD ahead of live publish SHA (localStorage may be newer than stale publish globals).
7575
- lastCommitId: resolvedHead,
7576
- // Never let focused globals wipe a true flag already on the panel snapshot / storage.
7577
- hasGeneratedCode: !!(live.hasGeneratedCode || snap.hasGeneratedCode || resolvedHead),
8061
+ lastCommitId: isDraftConvertId(widgetId) ? null : resolvedHead,
8062
+ hasGeneratedCode: isDraftConvertId(widgetId)
8063
+ ? false
8064
+ : !!(live.hasGeneratedCode || snap.hasGeneratedCode || resolvedHead),
7578
8065
  pendingPublishAfterCodegen: !!(snap.pendingPublishAfterCodegen || live.pendingPublishAfterCodegen),
7579
- lastPublishedVersion: live.lastPublishedVersion ?? snap.lastPublishedVersion,
8066
+ lastPublishedVersion: isDraftConvertId(widgetId)
8067
+ ? null
8068
+ : (live.lastPublishedVersion ?? snap.lastPublishedVersion),
7580
8069
  currentRepositoryId: live.currentRepositoryId ?? snap.currentRepositoryId,
7581
8070
  selectedWidgetJsPath: live.selectedWidgetJsPath ?? snap.selectedWidgetJsPath,
7582
8071
  // Once publish history loaded into this panel's snapshot, prefer it over stale globals.
@@ -7595,7 +8084,7 @@ Requirements:
7595
8084
  : livePublishMatchesPanel
7596
8085
  ? live.publishHistoryResolvedForWidgetId
7597
8086
  : snap.publishHistoryResolvedForWidgetId,
7598
- chatContainerHeight: snap.chatContainerHeight ?? DEFAULT_CHAT_CONTAINER_HEIGHT,
8087
+ chatContainerHeight: snap.chatContainerHeight ?? CHAT_DEFAULT_HEIGHT,
7599
8088
  compositionDesignSchema: resolvedDesignSchema,
7600
8089
  compositionContentSchema: resolvedContentSchema,
7601
8090
  compositionSchemasLoading: schemaLoading,
@@ -7613,7 +8102,7 @@ Requirements:
7613
8102
  ...snap,
7614
8103
  lastCommitId: snap.lastCommitId || loadCommitIdForWidget(widgetId),
7615
8104
  hasGeneratedCode: !!(snap.hasGeneratedCode || snap.lastCommitId || loadCommitIdForWidget(widgetId)),
7616
- chatContainerHeight: snap.chatContainerHeight ?? DEFAULT_CHAT_CONTAINER_HEIGHT,
8105
+ chatContainerHeight: snap.chatContainerHeight ?? CHAT_DEFAULT_HEIGHT,
7617
8106
  compositionDesignSchema: resolvedDesignSchema,
7618
8107
  compositionContentSchema: resolvedContentSchema,
7619
8108
  compositionSchemasError: resolvedSchemaError,
@@ -7903,6 +8392,22 @@ Requirements:
7903
8392
  selectedWidget = widget;
7904
8393
  saveSelectedWidgetId(widgetId);
7905
8394
  notifyHostWidgetName(widget);
8395
+ if (isDraftConvertId(widgetId)) {
8396
+ lastPublishedVersion = null;
8397
+ lastCommitId = null;
8398
+ hasGeneratedCode = false;
8399
+ publishHistory = [];
8400
+ publishHistoryResolved = true;
8401
+ publishHistoryResolvedForWidgetId = widgetId;
8402
+ widgetDetailsStep = 'create';
8403
+ patchPanelSnapshot(widgetId, {
8404
+ lastPublishedVersion: null,
8405
+ lastCommitId: null,
8406
+ hasGeneratedCode: false,
8407
+ publishHistory: [],
8408
+ widgetDetailsStep: 'create',
8409
+ });
8410
+ }
7906
8411
  if (widget?.id && canvasRef) {
7907
8412
  canvasRef.selectWidgetShape(widget.id);
7908
8413
  }
@@ -7920,7 +8425,13 @@ Requirements:
7920
8425
  }
7921
8426
 
7922
8427
  function positionOpenDetailsPanel(widgetId: string): void {
7923
- const rect = canvasRef?.getWidgetShapeScreenRect?.(widgetId) ?? null;
8428
+ let rect = canvasRef?.getWidgetShapeScreenRect?.(widgetId) ?? null;
8429
+ if (!rect) {
8430
+ const objectId = draftConvertObjectIds.get(widgetId);
8431
+ if (objectId && canvasRef?.getObjectScreenRect) {
8432
+ rect = canvasRef.getObjectScreenRect(objectId);
8433
+ }
8434
+ }
7924
8435
  getWidgetDetailsRef(widgetId)?.positionBesideShape?.(rect);
7925
8436
  }
7926
8437
 
@@ -7952,6 +8463,14 @@ Requirements:
7952
8463
  openWidgetPanelIds = [...openWidgetPanelIds, widgetId];
7953
8464
  }
7954
8465
  focusWidgetPanel(widgetId);
8466
+ if (isDraftConvertId(widgetId)) {
8467
+ detailsSessionWidgetId = widgetId;
8468
+ }
8469
+ const alreadyMounted = getWidgetDetailsRef(widgetId);
8470
+ if (alreadyMounted && !alreadyMounted.getShowWidgetDetails?.()) {
8471
+ alreadyMounted.openWidgetDetails();
8472
+ positionOpenDetailsPanel(widgetId);
8473
+ }
7955
8474
  if (openGen !== widgetDetailsOpenGen || widgetDetailsOpenTargetId !== widgetId) {
7956
8475
  console.log('[CreatorApp] Skip stale Widget Details open:', widgetId?.substring(0, 8));
7957
8476
  return;
@@ -8048,6 +8567,8 @@ Requirements:
8048
8567
  openWidgetPanelIds = replaced.includes(toId) ? replaced : [...replaced, toId];
8049
8568
  if (focusedPanelWidgetId === fromId) focusedPanelWidgetId = toId;
8050
8569
  if (selectedWidgetId === fromId) selectedWidgetId = toId;
8570
+ if (detailsSessionWidgetId === fromId) detailsSessionWidgetId = toId;
8571
+ if (widgetDetailsOpenTargetId === fromId) widgetDetailsOpenTargetId = toId;
8051
8572
  showWidgetDetails = openWidgetPanelIds.length > 0;
8052
8573
  console.log(
8053
8574
  '[CreatorApp] Migrated Widget Details panel',
@@ -8074,6 +8595,9 @@ Requirements:
8074
8595
  if (!options.fromShapeSwitch && widgetDetailsOpenTargetId === widgetId) {
8075
8596
  widgetDetailsOpenTargetId = null;
8076
8597
  }
8598
+ if (detailsSessionWidgetId === widgetId) {
8599
+ detailsSessionWidgetId = null;
8600
+ }
8077
8601
  getWidgetDetailsRef(widgetId)?.closeWidgetDetails({ silent: true });
8078
8602
  openWidgetPanelIds = openWidgetPanelIds.filter((id) => id !== widgetId);
8079
8603
  const { [widgetId]: _z, ...restZ } = panelZIndexByWidgetId;
@@ -8106,6 +8630,8 @@ Requirements:
8106
8630
  panelInstanceKeyByWidgetId = {};
8107
8631
  focusedPanelWidgetId = null;
8108
8632
  panelZIndexByWidgetId = {};
8633
+ detailsSessionWidgetId = null;
8634
+ widgetDetailsOpenTargetId = null;
8109
8635
  showWidgetDetails = false;
8110
8636
  }
8111
8637
 
@@ -8378,11 +8904,10 @@ Requirements:
8378
8904
  // If the token expired during generation, we delayed logout to avoid disrupting the operation.
8379
8905
  // Now that the operation is done, proceed with logout.
8380
8906
  $: if (pendingTokenExpiryLogout && !isGeneratingCode && Object.keys(generatingWidgetIds).length === 0) {
8381
- console.log('WidgetCreator: Deferred logoutactive operations completed, proceeding with logout');
8907
+ console.log('WidgetCreator: Token expired after operations keeping canvas, prompting re-auth');
8382
8908
  pendingTokenExpiryLogout = false;
8383
8909
  tokenExpiryWarningShown = false;
8384
- handleLogout();
8385
- authStatus = 'Token expired. Please login again.';
8910
+ authStatus = 'Session expired. Sign in again to keep saving.';
8386
8911
  }
8387
8912
 
8388
8913
 
@@ -8464,35 +8989,56 @@ Requirements:
8464
8989
  return [];
8465
8990
  }
8466
8991
 
8992
+ function unwrapContentMediaValue(raw: unknown): unknown {
8993
+ if (raw && typeof raw === 'object' && 'url' in (raw as Record<string, unknown>)) {
8994
+ return (raw as { url?: string }).url ?? '';
8995
+ }
8996
+ return raw;
8997
+ }
8998
+
8999
+ function pickLiveContentValue(...candidates: unknown[]): unknown {
9000
+ for (const candidate of candidates) {
9001
+ const unwrapped = unwrapContentMediaValue(candidate);
9002
+ if (unwrapped === undefined || unwrapped === null || unwrapped === '') continue;
9003
+ return unwrapped;
9004
+ }
9005
+ return '';
9006
+ }
9007
+
8467
9008
  /** Flatten schema-shaped content items to runtime values for preview postMessage. */
8468
9009
  function flattenContentItemsForPreview(items: any[]): any[] {
8469
9010
  return items.map((item) => {
8470
9011
  if (!item || typeof item !== 'object') return item;
8471
9012
  const flat: Record<string, unknown> = { id: item.id, title: item.title };
8472
- if (item.imageUrl) flat.imageUrl = item.imageUrl;
8473
- if (item.description) flat.description = item.description;
9013
+ for (const [key, value] of Object.entries(item)) {
9014
+ if (key === 'properties' || key === 'inputController' || key === 'id' || key === 'title') continue;
9015
+ const unwrapped = unwrapContentMediaValue(value);
9016
+ if (unwrapped === undefined) continue;
9017
+ flat[key] = unwrapped;
9018
+ }
8474
9019
  const props = item.properties;
8475
9020
  if (Array.isArray(props)) {
8476
9021
  for (const prop of props) {
8477
9022
  const name = prop?.name || prop?.id;
8478
9023
  if (!name) continue;
8479
- const raw = prop.value !== undefined ? prop.value : prop.defaultValue;
8480
- if (raw === undefined || flat[name] !== undefined) continue;
8481
- if (raw && typeof raw === 'object' && 'url' in raw) {
8482
- flat[name] = (raw as { url?: string }).url ?? '';
8483
- } else {
8484
- flat[name] = raw;
8485
- }
9024
+ if (flat[name] !== undefined && flat[name] !== '') continue;
9025
+ const picked = pickLiveContentValue(prop.value, prop.defaultValue);
9026
+ if (picked !== '') flat[name] = picked;
8486
9027
  }
8487
9028
  }
8488
- for (const [key, value] of Object.entries(item)) {
8489
- if (key === 'properties' || key === 'inputController') continue;
8490
- if (flat[key] === undefined && value !== undefined) flat[key] = value;
8491
- }
8492
9029
  return flat;
8493
9030
  });
8494
9031
  }
8495
9032
 
9033
+ function contentItemsToEditorValueMap(items: any[]): Record<string, Record<string, unknown>> {
9034
+ const map: Record<string, Record<string, unknown>> = {};
9035
+ for (const item of flattenContentItemsForPreview(items || [])) {
9036
+ if (!item?.id) continue;
9037
+ map[String(item.id)] = item as Record<string, unknown>;
9038
+ }
9039
+ return map;
9040
+ }
9041
+
8496
9042
  function contentItemHasProperties(item: any): boolean {
8497
9043
  return Array.isArray(item?.properties) && item.properties.length > 0;
8498
9044
  }
@@ -8500,22 +9046,28 @@ Requirements:
8500
9046
  /** New items from the editor arrive flat ({id, caption, image}) — reattach schema fields so the form stays editable. */
8501
9047
  function attachSchemaProperties(item: any, template: any): any {
8502
9048
  if (!item || typeof item !== 'object') return item;
8503
- if (contentItemHasProperties(item)) return item;
8504
- if (!contentItemHasProperties(template)) return item;
8505
- const clonedTemplate = JSON.parse(JSON.stringify(template));
8506
- const nextProperties = clonedTemplate.properties.map((prop: any) => {
9049
+ const templateHasProps = contentItemHasProperties(template);
9050
+ if (!contentItemHasProperties(item) && !templateHasProps) return item;
9051
+ const sourceProperties = contentItemHasProperties(item)
9052
+ ? item.properties
9053
+ : JSON.parse(JSON.stringify(template.properties));
9054
+ const nextProperties = sourceProperties.map((prop: any) => {
8507
9055
  const key = prop.id || prop.name;
8508
- const incoming = key != null ? item[key] : undefined;
9056
+ const hasOwn = key != null && Object.prototype.hasOwnProperty.call(item, key);
9057
+ const incoming = hasOwn ? item[key] : undefined;
9058
+ const picked = hasOwn
9059
+ ? (unwrapContentMediaValue(incoming) ?? '')
9060
+ : pickLiveContentValue(prop.value, prop.defaultValue);
8509
9061
  return {
8510
9062
  ...prop,
8511
- value: incoming !== undefined ? incoming : (prop.defaultValue ?? prop.value)
9063
+ value: picked
8512
9064
  };
8513
9065
  });
8514
9066
  return {
8515
- ...clonedTemplate,
9067
+ ...(templateHasProps ? JSON.parse(JSON.stringify(template)) : {}),
8516
9068
  ...item,
8517
9069
  id: item.id,
8518
- title: item.title || clonedTemplate.title,
9070
+ title: item.title || template?.title,
8519
9071
  properties: nextProperties
8520
9072
  };
8521
9073
  }
@@ -8764,8 +9316,26 @@ Requirements:
8764
9316
  getLivePublishedCommitShaForPanel(widgetId)
8765
9317
  ?? getWidgetLastPublishCommitSha(widgetId);
8766
9318
  const pendingPublish = !!ps.pendingPublishAfterCodegen;
9319
+ const unpublishedAhead = hasUnpublishedChangesForPanel(widgetId)
9320
+ || !!(ps.lastCommitId && liveSha && !commitsMatch(ps.lastCommitId, liveSha));
9321
+
9322
+ if (pendingPublish || unpublishedAhead) {
9323
+ console.log('[Publish] Gate: unpublished HEAD — enable', widgetId.substring(0, 8), {
9324
+ pendingPublish,
9325
+ unpublishedAhead,
9326
+ headSha: headSha?.substring(0, 8),
9327
+ displayHead: ps.lastCommitId?.substring(0, 8),
9328
+ liveSha: liveSha?.substring(0, 8),
9329
+ });
9330
+ return {
9331
+ disabled: false,
9332
+ label: `Publish new v${nextVersion}`,
9333
+ reason: null,
9334
+ showDisabledHint: false,
9335
+ };
9336
+ }
8767
9337
 
8768
- // Known up-to-date: HEAD matches live (pending flag ignored when proven equal).
9338
+ // Known up-to-date: HEAD matches live.
8769
9339
  if (headSha && liveSha && commitsMatch(headSha, liveSha)) {
8770
9340
  return {
8771
9341
  disabled: true,
@@ -8775,10 +9345,8 @@ Requirements:
8775
9345
  };
8776
9346
  }
8777
9347
 
8778
- // After a live version exists, enable vN+1 only when codegen just finished
8779
- // or HEAD is proven different from the live SHA. Do not enable just because
8780
- // live SHA is still hydrating (that looked like "always publish v2").
8781
- if (pendingPublish || (headSha && liveSha && !commitsMatch(headSha, liveSha))) {
9348
+ // After a live version exists, enable vN+1 when HEAD is proven different.
9349
+ if (headSha && liveSha && !commitsMatch(headSha, liveSha)) {
8782
9350
  return {
8783
9351
  disabled: false,
8784
9352
  label: `Publish new v${nextVersion}`,
@@ -8920,7 +9488,7 @@ $: if ($widgetPublishStatus.timestamp && $widgetPublishStatus.widgetId) {
8920
9488
  }
8921
9489
  if (publishWidgetId) {
8922
9490
  console.log('[Publish] Refreshing canvas widget screenshot after successful publish');
8923
- setTimeout(() => handleRefreshScreenshot(publishWidgetId), 2000);
9491
+ setTimeout(() => handleRefreshScreenshot(publishWidgetId, { quiet: true }), 2000);
8924
9492
  }
8925
9493
  if (embedded && onPublishComplete && publishWidgetId) {
8926
9494
  onPublishComplete({
@@ -9316,6 +9884,9 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9316
9884
  hasValidToken = false;
9317
9885
  inlinePanelView = null;
9318
9886
  selectedWidget = null;
9887
+ if (typeof window !== 'undefined') {
9888
+ window.location.href = '/login';
9889
+ }
9319
9890
  }
9320
9891
 
9321
9892
  // decodeJWT imported from ./pageHelpers.ts
@@ -9332,11 +9903,12 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9332
9903
 
9333
9904
  // if the decoded token has an expiration field, update the token status
9334
9905
  if (decoded && decoded.exp) {
9335
- // calculate the token time left
9336
9906
  const currentTime = Math.floor(Date.now() / 1000);
9337
- tokenTimeLeft = decoded.exp - currentTime;
9907
+ const expUnix = decoded.exp;
9908
+ tokenTimeLeft = expUnix - currentTime;
9338
9909
  tokenExpired = tokenTimeLeft <= 0;
9339
- hasValidToken = !tokenExpired;
9910
+ // API calls must follow the real JWT, never the sessionBannerTest clock.
9911
+ hasValidToken = decoded.exp - currentTime > 0;
9340
9912
 
9341
9913
  // log the token expiration status
9342
9914
  if (tokenExpired) {
@@ -9384,33 +9956,26 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9384
9956
  // If there are active operations (generating, loading preview), defer logout
9385
9957
  // and show a warning instead of abruptly cutting off the operation
9386
9958
  function checkTokenExpiration() {
9959
+ updateTokenStatus();
9387
9960
  if (currentUserToken) {
9388
- if (isTokenExpired(currentUserToken)) {
9389
- // Check if there are active operations that would be disrupted by logout
9961
+ if (tokenExpired || tokenTimeLeft <= 0) {
9390
9962
  const hasActiveOperations = isGeneratingCode || (showPreviewModal && previewLoading);
9391
9963
 
9392
9964
  if (hasActiveOperations) {
9393
- // Don't logout yet — defer until operations complete
9394
9965
  pendingTokenExpiryLogout = true;
9395
-
9396
9966
  if (!tokenExpiryWarningShown) {
9397
9967
  tokenExpiryWarningShown = true;
9398
- console.warn('WidgetCreator.checkTokenExpiration: Token expired but active operations in progress — deferring logout');
9399
- authStatus = 'Token expired — finishing current operation before logout...';
9400
- showToast('warning', 'Session expiring', {
9401
- description: 'Your login token has expired. You will be logged out after the current operation completes.',
9402
- duration: 8000
9403
- });
9968
+ console.warn('WidgetCreator.checkTokenExpiration: Token expired but active operations in progress — keeping the workspace open');
9969
+ authStatus = 'Session expired — finish this operation, then sign in again to save.';
9404
9970
  }
9405
9971
  return false;
9406
9972
  }
9407
9973
 
9408
- // No active operations (or deferred logout is now safe) — proceed with logout
9409
- console.log('WidgetCreator.checkTokenExpiration: Token expired, logging out automatically');
9410
- pendingTokenExpiryLogout = false;
9411
- tokenExpiryWarningShown = false;
9412
- handleLogout();
9413
- authStatus = 'Token expired. Please login again.';
9974
+ if (!tokenExpiryWarningShown) {
9975
+ tokenExpiryWarningShown = true;
9976
+ console.log('WidgetCreator.checkTokenExpiration: Token expired — waiting for site session handler');
9977
+ authStatus = 'Session expired.';
9978
+ }
9414
9979
  return false;
9415
9980
  }
9416
9981
  }
@@ -9582,6 +10147,24 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9582
10147
  });
9583
10148
  return;
9584
10149
  }
10150
+ const lockedPanelVisible = !!(
10151
+ detailsSessionWidgetId
10152
+ && openWidgetPanelIds.includes(detailsSessionWidgetId)
10153
+ && (getWidgetDetailsRef(detailsSessionWidgetId)?.getShowWidgetDetails?.() ?? false)
10154
+ );
10155
+ if (
10156
+ detailsSessionWidgetId
10157
+ && detailsSessionWidgetId !== widgetId
10158
+ && lockedPanelVisible
10159
+ ) {
10160
+ console.log('[WidgetCreator] Skip widget-shape click — convert session locked to', detailsSessionWidgetId.substring(0, 8));
10161
+ return;
10162
+ }
10163
+ if (lockedPanelVisible && isConvertOrDraftPanelOpen() && !isDraftConvertId(widgetId)) {
10164
+ console.log('[WidgetCreator] Skip widget-shape click — convert/draft panel is open:', widgetId.substring(0, 8));
10165
+ return;
10166
+ }
10167
+
9585
10168
  console.log('[WidgetCreator] Widget Image clicked, opening widget details panel:', widgetId);
9586
10169
 
9587
10170
  // Explicit user intent — cancel any in-flight deep-link auto-open and allow this panel.
@@ -9736,7 +10319,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9736
10319
  /**
9737
10320
  * Called when a widget shape is about to be deleted from canvas.
9738
10321
  * Step 1: Confirm removing the shape from canvas.
9739
- * Step 2: Ask if user also wants to delete the widget from DB.
10322
+ * Step 2 (ASK_DELETE_FROM_DB_CONFIRM): Ask if user also wants to delete the widget from DB.
10323
+ * MVP: first confirm also deletes the associated widget from the database.
9740
10324
  * Returns true to proceed with canvas shape deletion, false to cancel.
9741
10325
  */
9742
10326
  async function handleWidgetShapeDeleting(widgetId: string): Promise<boolean> {
@@ -9749,7 +10333,11 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9749
10333
 
9750
10334
  if (!result.proceed) return false;
9751
10335
 
9752
- showToast('info', 'Shape removed from canvas', { description: `Widget "${widgetName}" — check dialog for DB deletion` });
10336
+ if (result.deleteFromDb) {
10337
+ await deleteWidgetFromDb(widgetId);
10338
+ } else {
10339
+ showToast('info', 'Shape removed from canvas', { description: `Widget "${widgetName}" kept in database` });
10340
+ }
9753
10341
  return true;
9754
10342
  }
9755
10343
 
@@ -9823,10 +10411,12 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
9823
10411
  canvasPrimaryWidgetId ||
9824
10412
  canvasRef.getPrimaryWidgetId?.() ||
9825
10413
  selectedWidgetId;
9826
- if (primaryId && canvasRef) {
10414
+ if (primaryId && canvasRef && !isConvertOrDraftPanelOpen()) {
9827
10415
  canvasRef.setPrimaryWidgetShape?.(primaryId);
9828
10416
  const found = canvasRef.selectWidgetShape(primaryId);
9829
10417
  console.log('[CreatorApp] Primary widget shape after canvas load:', primaryId, found);
10418
+ } else if (isConvertOrDraftPanelOpen()) {
10419
+ console.log('[CreatorApp] Skip primary shape select — convert/draft panel is open');
9830
10420
  }
9831
10421
  }
9832
10422
 
@@ -10173,6 +10763,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10173
10763
  : `ConvertedW-${Date.now()}`;
10174
10764
  draftConvertScreenshots.set(draftId, payload.screenshot);
10175
10765
  draftConvertObjectIds.set(draftId, payload.frameId);
10766
+ // Grouping deselects the sketch; that must not close the draft or open the primary widget.
10767
+ suppressWidgetDetailsClose = true;
10176
10768
 
10177
10769
  // Group the sketch into one editable object and keep it on canvas — the
10178
10770
  // user may keep editing it (ungroup, move, restyle) and re-convert later.
@@ -10200,6 +10792,11 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10200
10792
  // Refresh the stored screenshot so finalize uses the latest sketch look
10201
10793
  draftConvertScreenshots.set(existingDraftId, payload.screenshot);
10202
10794
  authStatus = 'Widget draft ready — send the prompt to generate.';
10795
+ setTimeout(() => {
10796
+ if (!finalizingDraftConversions.size) {
10797
+ suppressWidgetDetailsClose = false;
10798
+ }
10799
+ }, 1500);
10203
10800
  return;
10204
10801
  }
10205
10802
 
@@ -10210,6 +10807,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10210
10807
  // Draft panel snapshot (no widget record in DB). WidgetDetails is
10211
10808
  // null-safe for a missing widget — the panel shows the draft name.
10212
10809
  await openWidgetDetailsPanel(draftId);
10810
+ embeddedDeepLinkAutoOpenDone = true;
10213
10811
  patchPanelSnapshot(draftId, {
10214
10812
  codeGenerationPrompt: CONVERT_PROMPT,
10215
10813
  promptImages: [payload.screenshot],
@@ -10251,6 +10849,11 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10251
10849
  showToast('error', 'Failed to prepare widget draft');
10252
10850
  } finally {
10253
10851
  isWidgetOperationInProgress = false;
10852
+ setTimeout(() => {
10853
+ if (!finalizingDraftConversions.size) {
10854
+ suppressWidgetDetailsClose = false;
10855
+ }
10856
+ }, 1500);
10254
10857
  }
10255
10858
  }
10256
10859
 
@@ -10364,7 +10967,10 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10364
10967
  // panel remounts: the freshly mounted Chat then activates this
10365
10968
  // conversation (best-conversation = one with messages) instead of
10366
10969
  // starting empty after a close/reopen.
10367
- await ensureCodegenUserMessagePersisted(widgetId, prompt);
10970
+ await ensureCodegenUserMessagePersisted(widgetId, prompt, [
10971
+ ...(draftPanelState.promptImages ?? []),
10972
+ screenshot,
10973
+ ].filter(Boolean));
10368
10974
  // NOTE: never copy currentChatConversationId from the draft — the draft
10369
10975
  // chat's conversation is a local UUID that doesn't exist in the DB, and
10370
10976
  // migrating it would point codegen/summaries at a phantom conversation.
@@ -10372,6 +10978,11 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10372
10978
  draftPanelState as Record<string, unknown>;
10373
10979
  patchPanelSnapshot(widgetId, {
10374
10980
  ...draftPanelStateClean,
10981
+ lastPublishedVersion: null,
10982
+ lastCommitId: null,
10983
+ hasGeneratedCode: false,
10984
+ publishHistory: [],
10985
+ widgetDetailsStep: 'create',
10375
10986
  } as Partial<WidgetPanelSnapshot>);
10376
10987
  migrateOpenPanelWidgetId(draftId, widgetId);
10377
10988
  widgetPanelSnapshots = Object.fromEntries(
@@ -10387,8 +10998,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10387
10998
  await tick();
10388
10999
  const migratedRef = getWidgetDetailsRef(widgetId);
10389
11000
  if (migratedRef?.getShowWidgetDetails?.()) {
10390
- console.log('[CreatorApp] Draft→widget: Widget Details stayed open, updating in place');
10391
- positionOpenDetailsPanel(widgetId);
11001
+ console.log('[CreatorApp] Draft→widget: Widget Details stayed open keep current position');
10392
11002
  } else {
10393
11003
  await openWidgetDetailsPanel(widgetId);
10394
11004
  }
@@ -10425,6 +11035,16 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10425
11035
  return !!widgetId && widgetId.startsWith(DRAFT_CONVERT_ID_PREFIX);
10426
11036
  }
10427
11037
 
11038
+ function isConvertOrDraftPanelOpen(): boolean {
11039
+ return (
11040
+ isDraftConvertId(focusedPanelWidgetId)
11041
+ || isDraftConvertId(widgetDetailsOpenTargetId)
11042
+ || isDraftConvertId(selectedWidgetId)
11043
+ || openWidgetPanelIds.some((id) => isDraftConvertId(id))
11044
+ || (!!detailsSessionWidgetId && openWidgetPanelIds.includes(detailsSessionWidgetId))
11045
+ );
11046
+ }
11047
+
10428
11048
 
10429
11049
  /**
10430
11050
  * Creates or recreates a repository for a specific widget.
@@ -10525,6 +11145,30 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10525
11145
  } catch (error) {
10526
11146
  if (error instanceof ResponseError) {
10527
11147
  const status = error.response?.status;
11148
+ if (status === 401) {
11149
+ console.warn('[Auth] Repository verify got 401 — rebinding session and retrying once', {
11150
+ repoId
11151
+ });
11152
+ applySiteSessionToCreator();
11153
+ try {
11154
+ const retryDetails = await agentClient.agentRepositoriesRepoIdGet({ repoId });
11155
+ if (retryDetails?.id) return true;
11156
+ } catch (retryError) {
11157
+ if (retryError instanceof ResponseError && retryError.response?.status === 401) {
11158
+ if (!silent) {
11159
+ showToast('warning', 'Session expired', {
11160
+ description: 'Sign in again, then retry. The widget repository is still linked.',
11161
+ duration: 6000
11162
+ });
11163
+ }
11164
+ if (typeof window !== 'undefined') {
11165
+ window.dispatchEvent(new CustomEvent('widgetic:session-needs-reauth'));
11166
+ }
11167
+ throw retryError;
11168
+ }
11169
+ }
11170
+ return false;
11171
+ }
10528
11172
  if (status === 404 || status === 403) {
10529
11173
  console.warn('Repository reference is stale or forbidden; it will be recreated.', {
10530
11174
  repoId,
@@ -10873,6 +11517,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10873
11517
  </div>
10874
11518
  {/if}
10875
11519
 
11520
+
10876
11521
  <!-- Widgetic Canvas Editor - Full width background (lazy-loaded) -->
10877
11522
  <div class="wc-canvas-area flex-1 min-h-0 relative">
10878
11523
  <!-- Embedded: Debug toggle lives in site AppHeader (avoids overlapping Props Panel). -->
@@ -10903,6 +11548,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10903
11548
  bind:this={canvasRef}
10904
11549
  showContainerFrame={!embedded}
10905
11550
  bind:wireframeMode={canvasWireframeMode}
11551
+ advancedFeatures={canvasAdvancedFeatures}
10906
11552
  onReady={handleCanvasReady}
10907
11553
  onAutoSave={handleCanvasAutoSave}
10908
11554
  onBeforeUnmount={(json) => {
@@ -10922,8 +11568,8 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
10922
11568
  getWidgetName={getWidgetNameById}
10923
11569
  isWidgetKnown={isWidgetKnownById}
10924
11570
  onNonWidgetSelected={() => {
10925
- if (suppressWidgetDetailsClose) {
10926
- console.log('[CreatorApp] Skip Widget Details close — canvas selection changed during draft finalize');
11571
+ if (suppressWidgetDetailsClose || isConvertOrDraftPanelOpen()) {
11572
+ console.log('[CreatorApp] Skip Widget Details close — convert/draft panel is open');
10927
11573
  return;
10928
11574
  }
10929
11575
  const active = canvasRef?.getSelectedObject?.() ?? canvasRef?.selectedObject;
@@ -11259,7 +11905,7 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11259
11905
  {isPublishing}
11260
11906
  {publishWidgetStatus}
11261
11907
  publishNeedsSaveFirst={publishNeedsSaveFirstForPanel(panelWidgetId)}
11262
- lastPublishedVersion={ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null)}
11908
+ lastPublishedVersion={isDraftConvertId(panelWidgetId) ? null : (ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null))}
11263
11909
  widgetJsPath={ps.selectedWidgetJsPath ?? panelWidget?.jsPath ?? null}
11264
11910
  {isGeneratingInOtherTab}
11265
11911
  {isPublishingInOtherTab}
@@ -11335,6 +11981,9 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11335
11981
  }
11336
11982
  // Keep canvas WidgetShape thumbnail aligned with the live Worker preview.
11337
11983
  scheduleCanvasShapePreviewSync(previewWidgetId);
11984
+ if (detail?.step === 'create' || ps.widgetDetailsStep === 'create') {
11985
+ applyPanelCompositionToPreview(previewWidgetId);
11986
+ }
11338
11987
  }}
11339
11988
  on:widgetReady={({ detail }) => {
11340
11989
  if (detail.widgetId !== panelWidgetId) return;
@@ -11485,6 +12134,13 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11485
12134
  console.log('[WidgetCreator] Generate Now clicked for widget:', detail.widgetId);
11486
12135
  const widgetId = detail.widgetId;
11487
12136
  const ps = getPanelDisplayState(widgetId);
12137
+ if (ps.lastGenerationFailed) {
12138
+ void handleChatRetryMessage(
12139
+ lastGenerationPrompt || ps.codeGenerationPrompt,
12140
+ widgetId,
12141
+ );
12142
+ return;
12143
+ }
11488
12144
  const hasCode = !!(ps.lastCommitId || ps.lastPublishedVersion !== null);
11489
12145
  const compileErr = getPanelPreviewCompileError(widgetId);
11490
12146
  if (hasCode && compileErr) {
@@ -11501,10 +12157,9 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11501
12157
  console.log('[CreatorApp] Ignoring Widget Details close during draft→widget migrate');
11502
12158
  return;
11503
12159
  }
11504
- if (widgetDetailsOpenTargetId && widgetDetailsOpenTargetId !== panelWidgetId) {
11505
- console.log('[CreatorApp] Ignoring close from previous details panel:', panelWidgetId?.substring(0, 8));
11506
- return;
11507
- }
12160
+ // After convert, the panel id changes but the X is still the visible
12161
+ // panel — always close it. Stale-target ignore left the child hidden
12162
+ // while openWidgetPanelIds stayed set, so nothing would reopen.
11508
12163
  if (isPanelFocused && selectedWidgetId) {
11509
12164
  savePromptForWidget(selectedWidgetId);
11510
12165
  captureFocusedPanelSnapshot();
@@ -11534,8 +12189,9 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11534
12189
  >
11535
12190
  <svelte:fragment slot="code-generation">
11536
12191
  <!-- svelte-ignore a11y_no_static_element_interactions -->
12192
+ {@const chatUserHeight = (panelDisplayRevision, Math.max(CHAT_MIN_HEIGHT, Math.min(CHAT_MAX_HEIGHT, (ps.chatContainerHeight > 0 ? ps.chatContainerHeight : CHAT_DEFAULT_HEIGHT))))}
11537
12193
  <div
11538
- class="parallel-panel-left-slot-ct flex flex-col flex-1 min-h-0 overflow-hidden h-full"
12194
+ class="parallel-panel-left-slot-ct flex flex-col shrink-0 overflow-visible"
11539
12195
  onmousedown={() => { if (!isPanelFocused) focusWidgetPanel(panelWidgetId); }}
11540
12196
  >
11541
12197
  <!-- Sticky header: title + model selectors (debug mode).
@@ -11582,11 +12238,11 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11582
12238
  </div>
11583
12239
  {/if}
11584
12240
 
11585
- <div class="generation-scroll-ct flex flex-col flex-1 min-h-0 overflow-y-auto scrollbar-thin">
12241
+ <div class="generation-scroll-ct flex flex-col shrink-0 overflow-visible">
11586
12242
  <!-- Chat-based Code Generation Panel -->
11587
12243
  {#key panelInstanceKey}
11588
- <Chat class="code-generation-chat flex-1 w-full shrink-0"
11589
- style="min-height: {ps.chatContainerHeight}px;"
12244
+ <Chat class="code-generation-chat w-full shrink-0"
12245
+ style="height: {chatUserHeight}px; min-height: {CHAT_MIN_HEIGHT}px; flex: 0 0 {chatUserHeight}px;"
11590
12246
  resizeExpandsContainer={true}
11591
12247
  onMessagesResize={(deltaPx) => handleChatMessagesResize(panelWidgetId, deltaPx)}
11592
12248
  showLogs={debugMode}
@@ -11616,11 +12272,13 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11616
12272
  onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
11617
12273
  onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
11618
12274
  onUploadChatFile={handleChatUploadFile}
11619
- onTakeScreenshot={handleChatScreenshot}
12275
+ onTakeScreenshot={() => handleChatScreenshot(panelWidgetId)}
11620
12276
  onLoadConversations={handleLoadConversations}
11621
12277
  onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
11622
12278
  onCreateConversation={handleCreateConversation}
11623
- onSaveMessage={handleSaveMessage}
12279
+ onSaveMessage={(conversationId, content, messageType, attachments) =>
12280
+ handleSaveMessage(conversationId, content, messageType, attachments, panelWidgetId)
12281
+ }
11624
12282
  onRestoreCheckpointBackend={handleRestoreCheckpoint}
11625
12283
  onChatReady={(methods) => handleChatReady(methods, panelWidgetId)}
11626
12284
  onDraftChange={() => handleChatDraftChange(panelWidgetId)}
@@ -11695,37 +12353,34 @@ $: if (codeGenerationPrompt && selectedWidgetId) {
11695
12353
  </div>
11696
12354
  {/if}
11697
12355
  </div>
12356
+ </div> <!-- generation-scroll-ct -->
12357
+ </div> <!-- parallel-panel-left-slot-ct -->
12358
+ </svelte:fragment>
12359
+
12360
+ <svelte:fragment slot="publish-widget">
11698
12361
  <!-- svelte-ignore a11y_no_static_element_interactions -->
11699
12362
  <div
11700
- class="parallel-panel-publish-slot-ct shrink-0 mt-2"
12363
+ class="parallel-panel-publish-slot-ct shrink-0"
11701
12364
  onmousedown={() => { if (!isPanelFocused) focusWidgetPanel(panelWidgetId); }}
11702
12365
  >
11703
12366
  <!-- ===== Publish Widget Section ===== -->
11704
- <div class="publish-widget-section flex flex-col gap-3 rounded border border-emerald-200 bg-emerald-50 p-3 mt-1">
11705
- <!-- Section Header -->
12367
+ <div class="publish-widget-section flex flex-col gap-1 rounded border border-emerald-200 bg-emerald-50 px-2 py-1.5 mt-1 shrink-0">
11706
12368
  <div class="publish-widget-section-header flex items-center justify-between gap-2">
11707
- <span class="text-sm font-semibold text-emerald-800">🚀 Publish Widget</span>
11708
- <!-- Current published version badge — shown when widget has been published -->
12369
+ <span class="text-xs font-semibold text-emerald-800">🚀 Publish</span>
11709
12370
  {#if ps.lastPublishedVersion !== null}
11710
- <span class="text-xs font-mono bg-emerald-700 text-white px-1.5 py-0.5 rounded">
12371
+ <span class="text-[10px] font-mono bg-emerald-700 text-white px-1.5 py-0.5 rounded">
11711
12372
  live: v{ps.lastPublishedVersion}
11712
12373
  </span>
11713
12374
  {/if}
11714
12375
  </div>
11715
12376
 
11716
- <!-- Description -->
11717
- <p class="text-xs text-emerald-700 leading-relaxed">
11718
- Publish when you’re happy with the preview. You’ll get a stable embed URL for your site.
11719
- </p>
11720
-
11721
- <!-- Publish Button + Build Status -->
11722
- <div class="publish-widget-section-actions flex flex-col gap-2 items-center">
11723
- <!-- Publish Widget Button — triggers production build via Trigger.dev -->
12377
+ <div class="publish-widget-section-actions flex flex-col gap-1 items-center">
11724
12378
  <Button
11725
12379
  onclick={() => runPublishForPanel(panelWidgetId)}
11726
- disabled={isPublishDisabledForPanel(panelWidgetId)}
12380
+ disabled={(panelDisplayRevision, isPublishDisabledForPanel(panelWidgetId))}
11727
12381
  title={getPublishDisabledReasonForPanel(panelWidgetId) ?? undefined}
11728
- class={`publish-widget-btn flex justify-center items-center border text-white w-full
12382
+ size="sm"
12383
+ class={`publish-widget-btn flex justify-center items-center border text-white w-full h-8 text-xs
11729
12384
  ${isPublishing && publishingWidgetId === panelWidgetId
11730
12385
  ? 'bg-emerald-500 border-emerald-500 opacity-80 cursor-not-allowed'
11731
12386
  : isPublishDisabledForPanel(panelWidgetId)
@@ -11740,7 +12395,7 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
11740
12395
  <span class="mr-1 inline-block">⏳</span>
11741
12396
  <span>Publishing...</span>
11742
12397
  {:else}
11743
- <span>{getPublishButtonLabelForPanel(panelWidgetId)}</span>
12398
+ <span>{(panelDisplayRevision, getPublishButtonLabelForPanel(panelWidgetId))}</span>
11744
12399
  {#if isPublishingInOtherTab}
11745
12400
  <!-- svelte-ignore a11y-missing-attribute -->
11746
12401
  <span class="text-xs opacity-70 ml-1" title="Publish is running in another tab">⏳</span>
@@ -11754,24 +12409,23 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
11754
12409
  </Button>
11755
12410
 
11756
12411
  {#if isPublishDisabledForPanel(panelWidgetId) && shouldShowPublishDisabledHintForPanel(panelWidgetId)}
11757
- <p class="publish-widget-disabled-hint text-xs text-center text-emerald-800/80 leading-relaxed px-1">
12412
+ <p class="publish-widget-disabled-hint text-[10px] text-center text-emerald-800/80 leading-snug px-1">
11758
12413
  {getPublishDisabledReasonForPanel(panelWidgetId)}
11759
12414
  </p>
11760
12415
  {/if}
11761
12416
 
11762
12417
  {#if ps.lastPublishedVersion !== null}
11763
- <a href="/test-widget?widgetId={panelWidgetId}&version={ps.lastPublishedVersion}&url={encodeURIComponent(publishedWidgetHtmlUrl(panelWidgetId, ps.lastPublishedVersion))}" target="_blank" rel="noopener noreferrer" class="flex justify-center items-center gap-1.5 w-full text-sm px-3 py-2 rounded-lg border border-emerald-200 bg-white text-emerald-700 hover:bg-emerald-50 transition-colors">
11764
- Open Published Widget (v{ps.lastPublishedVersion})
12418
+ <a href="/test-widget?widgetId={panelWidgetId}&version={ps.lastPublishedVersion}&url={encodeURIComponent(publishedWidgetHtmlUrl(panelWidgetId, ps.lastPublishedVersion))}" target="_blank" rel="noopener noreferrer" class="text-[11px] text-emerald-700 hover:underline">
12419
+ Open published v{ps.lastPublishedVersion} ↗
11765
12420
  </a>
11766
12421
  {/if}
11767
12422
 
11768
- <!-- Save-first hint — shown when changes aren't saved yet -->
11769
12423
  {#if publishNeedsSaveFirstForPanel(panelWidgetId)}
11770
- <p class="text-xs text-center text-amber-700">
12424
+ <p class="text-[10px] text-center text-amber-700">
11771
12425
  {#if !resolvePublishHeadCommitId(panelWidgetId, ps)}
11772
- 💾 Generate &amp; save the widget before publishing.
12426
+ Save the widget before publishing.
11773
12427
  {:else}
11774
- 💾 Save your changes before publishing.
12428
+ Save your changes before publishing.
11775
12429
  {/if}
11776
12430
  </p>
11777
12431
  {/if}
@@ -11784,7 +12438,7 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
11784
12438
  if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
11785
12439
  togglePublishHistory();
11786
12440
  }}
11787
- class="publish-history-toggle flex items-center justify-between w-full text-xs text-emerald-700 hover:text-emerald-900 py-1 px-0.5 rounded transition-colors"
12441
+ class="publish-history-toggle flex items-center justify-between w-full text-[10px] text-emerald-700 hover:text-emerald-900 py-0.5 px-0.5 rounded transition-colors"
11788
12442
  >
11789
12443
  <span class="font-medium">📋 Version History</span>
11790
12444
  <span class="text-emerald-400">{isHistoryOpen && isPanelFocused ? '▲' : '▼'}</span>
@@ -11842,53 +12496,41 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
11842
12496
 
11843
12497
  </div> <!-- end of publish-widget-section -->
11844
12498
  </div>
11845
- </div> <!-- generation-scroll-ct -->
11846
- </div> <!-- parallel-panel-left-slot-ct -->
11847
12499
  </svelte:fragment>
11848
12500
 
11849
- <svelte:fragment slot="publish-widget"></svelte:fragment>
11850
-
11851
12501
  <svelte:fragment slot="composition-editor">
11852
12502
  <!-- svelte-ignore a11y_no_static_element_interactions -->
11853
12503
  <div
11854
- class="parallel-panel-composition-slot-ct flex flex-col h-full min-h-0"
12504
+ class="parallel-panel-composition-slot-ct flex flex-col h-full min-h-0 min-w-0 overflow-y-auto overflow-x-hidden scrollbar-thin"
11855
12505
  onmousedown={() => { if (!isPanelFocused) focusWidgetPanel(panelWidgetId); }}
11856
12506
  >
11857
12507
  <!-- ===== Composition Editor (Step 2: Edit) ===== -->
11858
12508
  {#if ps.lastCommitId || ps.lastPublishedVersion !== null}
11859
- <div class="composition-editor-section flex flex-col h-full min-h-0">
12509
+ <div class="composition-editor-section flex flex-col flex-1 min-h-0 h-full">
11860
12510
 
11861
12511
  {#if panelHasUnpublishedChanges}
11862
- <div class="edit-unpublished-banner-ct mx-3 mt-2 px-3 py-2 rounded-md border border-amber-200 bg-amber-50 text-amber-800 text-xs flex flex-col gap-2">
11863
- <p class="edit-unpublished-banner-text leading-relaxed">
11864
- <strong class="font-semibold">Newer code than Live v{ps.lastPublishedVersion ?? '—'}.</strong>
11865
- Widget is published, but latest commit
12512
+ <div class="edit-unpublished-banner-ct mx-3 mt-2 px-2 py-1.5 rounded-md border border-amber-200 bg-amber-50 text-amber-800 text-xs flex items-center gap-2 shrink-0">
12513
+ <p class="edit-unpublished-banner-text leading-snug min-w-0 flex-1 truncate" title="Newer code than Live v{ps.lastPublishedVersion ?? '—'}. Publish to promote commit {formatCommitShaShort(ps.lastCommitId)}.">
12514
+ <strong class="font-semibold">Newer than Live v{ps.lastPublishedVersion ?? '—'}</strong>
11866
12515
  <span class="font-mono">{formatCommitShaShort(ps.lastCommitId)}</span>
11867
- is ahead of the live artifact
11868
- (<span class="font-mono">{formatCommitShaShort(getLivePublishedCommitShaForPanel(panelWidgetId))}</span>).
11869
- Edit preview shows <strong>Live v{ps.lastPublishedVersion ?? '—'}</strong> with your composition settings.
11870
- Publish to promote the latest code to Live — this preview will refresh automatically.
12516
+ ahead of
12517
+ <span class="font-mono">{formatCommitShaShort(getLivePublishedCommitShaForPanel(panelWidgetId))}</span>
11871
12518
  </p>
11872
- <div class="edit-unpublished-banner-actions flex items-center gap-2 flex-wrap">
11873
- <Button
11874
- onclick={() => runPublishForPanel(panelWidgetId)}
11875
- disabled={isPublishDisabledForPanel(panelWidgetId)}
11876
- title={getPublishDisabledReasonForPanel(panelWidgetId) ?? undefined}
11877
- class="edit-banner-publish-btn flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-md border transition-colors
11878
- {isPublishDisabledForPanel(panelWidgetId)
11879
- ? 'bg-emerald-600/40 border-emerald-600/40 text-white cursor-not-allowed'
11880
- : 'bg-emerald-600 border-emerald-600 text-white hover:bg-emerald-700 hover:border-emerald-700 cursor-pointer'}"
11881
- >
11882
- {#if isPublishing && publishingWidgetId === panelWidgetId}
11883
- <span>⏳ Publishing…</span>
11884
- {:else}
11885
- <span>🚀 Publish to Live</span>
11886
- {/if}
11887
- </Button>
11888
- {#if publishNeedsSaveFirstForPanel(panelWidgetId)}
11889
- <span class="text-amber-700">Generate &amp; save code before publishing.</span>
12519
+ <Button
12520
+ onclick={() => runPublishForPanel(panelWidgetId)}
12521
+ disabled={isPublishDisabledForPanel(panelWidgetId)}
12522
+ title={getPublishDisabledReasonForPanel(panelWidgetId) ?? undefined}
12523
+ class="edit-banner-publish-btn shrink-0 flex items-center justify-center gap-1 px-2 py-1 text-xs font-semibold rounded-md border transition-colors
12524
+ {isPublishDisabledForPanel(panelWidgetId)
12525
+ ? 'bg-emerald-600/40 border-emerald-600/40 text-white cursor-not-allowed'
12526
+ : 'bg-emerald-600 border-emerald-600 text-white hover:bg-emerald-700 hover:border-emerald-700 cursor-pointer'}"
12527
+ >
12528
+ {#if isPublishing && publishingWidgetId === panelWidgetId}
12529
+ <span>⏳</span>
12530
+ {:else}
12531
+ <span>🚀 Publish</span>
11890
12532
  {/if}
11891
- </div>
12533
+ </Button>
11892
12534
  </div>
11893
12535
  {/if}
11894
12536
 
@@ -12050,14 +12692,17 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
12050
12692
  </button>
12051
12693
  </div>
12052
12694
  {:else if ps.compositionDesignSchema || ps.compositionContentSchema}
12053
- <div class="composition-editor-props-ct flex flex-1 min-h-0 flex-col overflow-hidden p-2">
12695
+ <div class="composition-editor-props-ct flex flex-col flex-1 min-h-[550px] max-h-[800px] overflow-y-auto overflow-x-hidden p-2">
12054
12696
  {#key `${panelWidgetId}-${ps.selectedComposition?.id ?? 'new'}`}
12055
12697
  <PropsEditor
12056
12698
  class="composition-props-editor flex-1 min-h-0 min-w-0"
12057
12699
  designSchema={ps.compositionDesignSchema}
12058
12700
  contentSchema={buildPropsEditorContentSchema(ps.compositionContentSchema, ps.liveContentItems)}
12059
12701
  initialDesignValues={ps.liveDesignValues}
12060
- initialContentValues={ps.liveContentValues}
12702
+ initialContentValues={{
12703
+ ...ps.liveContentValues,
12704
+ items: contentItemsToEditorValueMap(ps.liveContentItems)
12705
+ }}
12061
12706
  width="100%"
12062
12707
  height="100%"
12063
12708
  showDragHandle={false}
@@ -12336,7 +12981,7 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
12336
12981
  <Dialog.Title>Delete Widget Shape</Dialog.Title>
12337
12982
  <Dialog.Description>
12338
12983
  {#if deleteDialogStep === 'confirm-remove'}
12339
- Remove <strong>"{deleteDialogWidgetName}"</strong> shape from the canvas?
12984
+ Remove <strong>"{deleteDialogWidgetName}"</strong> from the canvas? This also deletes the associated widget.
12340
12985
  {:else}
12341
12986
  Also delete the widget <strong>"{deleteDialogWidgetName}"</strong> from the database?
12342
12987
  {/if}
@@ -12354,8 +12999,8 @@ class={`publish-widget-btn flex justify-center items-center border text-white w-
12354
12999
  type="button"
12355
13000
  class="px-4 py-2 text-sm rounded-lg bg-red-600 hover:bg-red-700 text-white cursor-pointer"
12356
13001
  onclick={() => handleDeleteDialogResponse('remove-and-ask-db')}
12357
- >Remove from Canvas</button>
12358
- {:else}
13002
+ >Delete</button>
13003
+ {:else if ASK_DELETE_FROM_DB_CONFIRM}
12359
13004
  <button
12360
13005
  type="button"
12361
13006
  class="px-4 py-2 text-sm rounded-lg border border-gray-300 hover:bg-gray-100 cursor-pointer"