@widgetic/creator 0.3.55 → 0.3.56

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.
@@ -67,6 +67,7 @@
67
67
  isInfrastructurePreviewError
68
68
  } from './pageHelpers.js';
69
69
  import { uploadChatFileToWidgeticApi } from './utils/widgeticChatUpload.js';
70
+ import { persistWidgetThumbnail } from './utils/persistWidgetThumbnail.js';
70
71
  import { nativeScrollbarPadding } from './utils/nativeScrollbarPadding.js';
71
72
  import { streamOperationUpdates } from './utils/operationStream.js';
72
73
  import { generateEmbedCode, EMBED_FORMATS, EMBED_FORMAT_LABELS, type EmbedFormat } from './utils/embedCode.js';
@@ -81,6 +82,14 @@
81
82
  import { setEnableLocalCache, isLocalCacheEnabled } from './localCacheGate.js';
82
83
  import type { CreatorAppProps } from './creator-types.js';
83
84
 
85
+ function resolveCreatorWorkflowStep(
86
+ step?: CreatorAppProps['initialStep'] | 'create' | 'edit' | 'embed' | null
87
+ ): 'create' | 'edit' | 'embed' {
88
+ if (!canvasAdvancedFeatures) return 'create';
89
+ if (step === 'edit' || step === 'embed') return step;
90
+ return 'create';
91
+ }
92
+
84
93
  /** Embedded mode props (site integration) */
85
94
  export let embedded: CreatorAppProps['embedded'] = false;
86
95
  export let canvasId: CreatorAppProps['canvasId'] = null;
@@ -928,10 +937,6 @@
928
937
  codegenWidgetId = finalizedWidgetId;
929
938
  const newPanelChat = getChatMethodsForWidget(finalizedWidgetId);
930
939
  newPanelChat?.setDraftText?.('');
931
- const hydrateImages = fallbackChatImageAttachments(finalizedWidgetId);
932
- if (hydrateImages.length > 0) {
933
- newPanelChat?.updateLastUserMessageAttachments?.(hydrateImages);
934
- }
935
940
  }
936
941
 
937
942
  const panelChat = getChatMethodsForWidget(codegenWidgetId);
@@ -1251,31 +1256,6 @@
1251
1256
  * picked, which may differ from convos[0] when several empty conversations
1252
1257
  * exist (feedback c: chat looked empty while messages went elsewhere).
1253
1258
  */
1254
- function userMessageImpliesAttachedFile(content: string): boolean {
1255
- return /imaginea ata[sș]at[ăa]|attached (reference )?(image|document|file|pdf|video)|reference image|din imagine|ata[sș]at[ăa]?\s+(document|pdf|fi[sș]ier|video)|attached document|starting from the attached image/i.test(
1256
- content || '',
1257
- );
1258
- }
1259
-
1260
- function fallbackChatImageAttachments(widgetId: string): Array<{
1261
- id: string;
1262
- url: string;
1263
- file_type: string;
1264
- file_name: string;
1265
- }> {
1266
- const urls = uniquePersistImageUrls([
1267
- ...(getPanelState(widgetId).promptImages ?? []),
1268
- ...(widgetId === focusedPanelWidgetId ? promptImages : []),
1269
- (typeof canvasRef?.getWidgetShapeDataUrl === 'function' ? canvasRef.getWidgetShapeDataUrl(widgetId) : '') || '',
1270
- ]);
1271
- return urls.map((url, i) => ({
1272
- id: `hydrate_img_${i}`,
1273
- url,
1274
- file_type: guessAttachmentFileType(url, 'design-screenshot.png', 'image/png'),
1275
- file_name: 'design-screenshot.png',
1276
- }));
1277
- }
1278
-
1279
1259
  function guessAttachmentFileType(url: string, fileName?: string | null, fallback?: string | null): string {
1280
1260
  if (fallback && String(fallback).includes('/')) return String(fallback);
1281
1261
  const hint = `${url || ''} ${fileName || ''}`;
@@ -1297,7 +1277,26 @@
1297
1277
  else if (url.startsWith('data:image/')) dataUrls.push(url);
1298
1278
  }
1299
1279
  // Same screenshot often exists as CDN URL + compressed data URL — keep HTTPS only.
1300
- return httpsUrls.length > 0 ? httpsUrls : dataUrls;
1280
+ // If only data URLs remain, keep the first one so convert does not triple the bubble.
1281
+ if (httpsUrls.length > 0) return httpsUrls;
1282
+ return dataUrls.slice(0, 1);
1283
+ }
1284
+
1285
+ async function persistWidgetListThumbnail(widgetId: string, imageDataUrl: string): Promise<void> {
1286
+ if (!widgetsClient || !widgetId || !imageDataUrl) return;
1287
+ try {
1288
+ const compact = imageDataUrl.startsWith('data:image/')
1289
+ ? await compressImageDataUrlForCodegen(imageDataUrl, 384, 180000)
1290
+ : imageDataUrl;
1291
+ const thumbnailUrl = await persistWidgetThumbnail(widgetsClient, widgetId, compact);
1292
+ if (thumbnailUrl) {
1293
+ widgets = widgets.map((w) =>
1294
+ w.id === widgetId ? { ...w, thumbnailUrl, thumbnail_url: thumbnailUrl } : w
1295
+ );
1296
+ }
1297
+ } catch (error) {
1298
+ console.warn('[CreatorApp] persist list thumbnail failed', widgetId.slice(0, 8), error);
1299
+ }
1301
1300
  }
1302
1301
 
1303
1302
  async function ensureCodegenUserMessagePersisted(
@@ -1400,6 +1399,14 @@
1400
1399
  .filter((url: string | undefined): url is string => !!url),
1401
1400
  );
1402
1401
  const existingHasRemoteImage = [...existingUrls].some((url) => /^https?:\/\//i.test(url));
1402
+ // Chat already persisted CDN attachments for this turn — do not merge data URLs/canvas shots.
1403
+ if (targetUser && existingHasRemoteImage) {
1404
+ if (convId !== visibleConversationId) {
1405
+ await panelChat?.selectConversation?.(convId);
1406
+ patchPanelSnapshot(widgetId, { currentChatConversationId: convId });
1407
+ }
1408
+ return;
1409
+ }
1403
1410
  const missingPersist = persistAttachments.filter((att) => {
1404
1411
  if (att.url && existingUrls.has(att.url)) return false;
1405
1412
  if (existingHasRemoteImage && typeof att.url === 'string' && att.url.startsWith('data:')) return false;
@@ -1627,14 +1634,6 @@
1627
1634
  created_at: content.created_at || content.createdAt || a.created_at || a.createdAt || new Date().toISOString(),
1628
1635
  };
1629
1636
  }).filter((a: { url: string }) => !!a.url);
1630
- if (
1631
- attachments.length === 0
1632
- && (m.message_type || m.messageType || 'user') === 'user'
1633
- && userMessageImpliesAttachedFile(m.message_content || m.messageContent || '')
1634
- && widgetIdForSync
1635
- ) {
1636
- attachments = fallbackChatImageAttachments(widgetIdForSync);
1637
- }
1638
1637
  return {
1639
1638
  id: m.id,
1640
1639
  conversationId: m.conversation_id || m.conversationId || conversationId,
@@ -3794,8 +3793,17 @@
3794
3793
 
3795
3794
  function handleHostToggleAdvancedEvent() {
3796
3795
  canvasAdvancedFeatures = !canvasAdvancedFeatures;
3797
- // console.log('[CreatorApp] Advanced canvas features:', canvasAdvancedFeatures ? 'ON' : 'OFF');
3798
3796
  emitCreatorAdvancedChanged();
3797
+ console.log('[CreatorApp] Advanced', canvasAdvancedFeatures ? 'ON' : 'OFF',
3798
+ '— composition steps', canvasAdvancedFeatures ? 'visible' : 'hidden');
3799
+ if (!canvasAdvancedFeatures) {
3800
+ widgetDetailsStep = 'create';
3801
+ for (const panelId of Object.keys(widgetPanelSnapshots || {})) {
3802
+ if (getPanelState(panelId).widgetDetailsStep !== 'create') {
3803
+ patchPanelSnapshot(panelId, { widgetDetailsStep: 'create' });
3804
+ }
3805
+ }
3806
+ }
3799
3807
  }
3800
3808
 
3801
3809
  function resolveHostTargetWidgetId(): string | null {
@@ -5499,7 +5507,7 @@
5499
5507
  selectedWidgetId === deepLinkWidgetId && !!getWidgetById(deepLinkWidgetId);
5500
5508
  if (alreadyLinked) {
5501
5509
  if (embeddedInitialStepPending && initialStep) {
5502
- widgetDetailsStep = initialStep;
5510
+ widgetDetailsStep = resolveCreatorWorkflowStep(initialStep);
5503
5511
  embeddedInitialStepPending = false;
5504
5512
  }
5505
5513
  embeddedDeepLinkAutoOpenDone = true;
@@ -5550,7 +5558,7 @@
5550
5558
  canvasRef.selectWidgetShape(target.id);
5551
5559
  }
5552
5560
  if (embeddedInitialStepPending && initialStep) {
5553
- widgetDetailsStep = initialStep;
5561
+ widgetDetailsStep = resolveCreatorWorkflowStep(initialStep);
5554
5562
  embeddedInitialStepPending = false;
5555
5563
  }
5556
5564
 
@@ -5988,10 +5996,10 @@
5988
5996
  }
5989
5997
  if (!isGeneratingCode && !_isRestoringCheckpoint) {
5990
5998
  if (embeddedInitialStepPending && initialStep) {
5991
- widgetDetailsStep = initialStep;
5999
+ widgetDetailsStep = resolveCreatorWorkflowStep(initialStep);
5992
6000
  embeddedInitialStepPending = false;
5993
6001
  } else if (initialStep) {
5994
- widgetDetailsStep = initialStep;
6002
+ widgetDetailsStep = resolveCreatorWorkflowStep(initialStep);
5995
6003
  } else {
5996
6004
  widgetDetailsStep = 'create';
5997
6005
  }
@@ -6839,8 +6847,8 @@
6839
6847
  if (quiet || isConvertOrDraftPanelOpen()) {
6840
6848
  const shapeDataUrl = canvasRef?.getWidgetShapeDataUrl?.(widgetId);
6841
6849
  if (shapeDataUrl && canvasRef) {
6842
- // console.log('[handleRefreshScreenshot] Using canvas shape screenshot (panel closed):', widgetId.substring(0, 8));
6843
6850
  canvasRef.updateWidgetImage(widgetId, shapeDataUrl);
6851
+ void persistWidgetListThumbnail(widgetId, shapeDataUrl);
6844
6852
  } else {
6845
6853
  // console.log('[handleRefreshScreenshot] Skip quiet capture — panel closed:', widgetId.substring(0, 8));
6846
6854
  }
@@ -6892,6 +6900,7 @@
6892
6900
  const json = canvasRef?.saveCanvas();
6893
6901
  if (json) handleCanvasAutoSave(json);
6894
6902
  }, 500);
6903
+ void persistWidgetListThumbnail(widgetId, result.data);
6895
6904
  if (!quiet) {
6896
6905
  showToast('success', 'Widget preview updated on canvas', { duration: 2000 });
6897
6906
  } else {
@@ -8080,9 +8089,10 @@ Requirements:
8080
8089
  selectedWidgetId = initialWidgetId;
8081
8090
  saveSelectedWidgetId(initialWidgetId);
8082
8091
  }
8083
- // Site deep-link: Edit widget → create, New Composition → edit (before widget hydrate).
8092
+ // Site deep-link: Edit widget / New Composition used to open Edit/Embed in Creator.
8093
+ // Canvas is Create-only now; /composition owns the editor.
8084
8094
  if (embedded && initialStep) {
8085
- widgetDetailsStep = initialStep;
8095
+ widgetDetailsStep = resolveCreatorWorkflowStep(initialStep);
8086
8096
  }
8087
8097
  if (embedded && projectId) {
8088
8098
  // console.log('[CreatorApp] Embedded project context:', {
@@ -8733,7 +8743,7 @@ Requirements:
8733
8743
  lastPublishedVersion: publishedVersion,
8734
8744
  selectedWidgetJsPath: widget?.jsPath ?? null,
8735
8745
  currentRepositoryId: normalizeRepositoryId(widget),
8736
- widgetDetailsStep: publishedVersion !== null ? 'edit' : 'create',
8746
+ widgetDetailsStep: resolveCreatorWorkflowStep(publishedVersion !== null ? 'edit' : 'create'),
8737
8747
  generateCodeStatus: null,
8738
8748
  lastGenerationFailed: false,
8739
8749
  lastGenerationError: null,
@@ -8949,12 +8959,18 @@ Requirements:
8949
8959
  const resolvedVersion = resolvePublishedVersionFromWidget(widget);
8950
8960
  const resolvedJsPath = ((widget as { jsPath?: string | null; js_path?: string | null })?.jsPath
8951
8961
  ?? (widget as { js_path?: string | null })?.js_path) as string | null;
8952
- if (resolvedVersion !== null && (snap.lastPublishedVersion === null || snap.widgetDetailsStep === 'create')) {
8962
+ if (resolvedVersion !== null && snap.lastPublishedVersion === null) {
8953
8963
  patchPanelSnapshot(widgetId, {
8954
8964
  lastPublishedVersion: resolvedVersion,
8955
8965
  selectedWidgetJsPath: snap.selectedWidgetJsPath ?? resolvedJsPath,
8956
- widgetDetailsStep: 'edit',
8966
+ ...(canvasAdvancedFeatures ? { widgetDetailsStep: 'edit' as const } : {}),
8957
8967
  });
8968
+ } else if (
8969
+ canvasAdvancedFeatures
8970
+ && resolvedVersion !== null
8971
+ && snap.widgetDetailsStep === 'create'
8972
+ ) {
8973
+ patchPanelSnapshot(widgetId, { widgetDetailsStep: 'edit' });
8958
8974
  }
8959
8975
  }
8960
8976
 
@@ -11889,6 +11905,10 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
11889
11905
  }
11890
11906
  }
11891
11907
 
11908
+ if (screenshot) {
11909
+ void persistWidgetListThumbnail(widgetId, screenshot);
11910
+ }
11911
+
11892
11912
  // Auto-create repository so generation can start right away.
11893
11913
  try {
11894
11914
  const repoId = await ensureWidgetRepository(newWidget, { silent: true });
@@ -11907,11 +11927,15 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
11907
11927
  // panel remounts: the freshly mounted Chat then activates this
11908
11928
  // conversation (best-conversation = one with messages) instead of
11909
11929
  // starting empty after a close/reopen.
11910
- await ensureCodegenUserMessagePersisted(widgetId, prompt, [
11911
- ...(draftPanelState.promptImages ?? []),
11912
- ...extraImageUrls,
11913
- screenshot,
11914
- ].filter(Boolean));
11930
+ await ensureCodegenUserMessagePersisted(
11931
+ widgetId,
11932
+ prompt,
11933
+ uniquePersistImageUrls([
11934
+ ...extraImageUrls,
11935
+ ...(draftPanelState.promptImages ?? []),
11936
+ screenshot,
11937
+ ]),
11938
+ );
11915
11939
  // NOTE: never copy currentChatConversationId from the draft — the draft
11916
11940
  // chat's conversation is a local UUID that doesn't exist in the DB, and
11917
11941
  // migrating it would point codegen/summaries at a phantom conversation.
@@ -12882,6 +12906,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
12882
12906
  panelTopOffset={widgetsPanelTopOffset}
12883
12907
  panelBottomMargin={embedded ? 24 : widgetsPanelBottomMargin}
12884
12908
  fillViewport={false}
12909
+ showCompositionSteps={canvasAdvancedFeatures}
12885
12910
  currentStep={ps.widgetDetailsStep}
12886
12911
  on:focusPanel={() => focusWidgetPanel(panelWidgetId)}
12887
12912
  on:stepChange={({ detail }) => {
@@ -285,6 +285,12 @@
285
285
  // ═══════════════════════════════════════════════════════════════════════
286
286
  type WidgetStep = 'create' | 'edit' | 'embed';
287
287
  export let currentStep: WidgetStep = 'create';
288
+ /**
289
+ * Canvas Creator is code-only (chat + preview). Edit/Embed header steps stay
290
+ * off until composition editing lives on /composition. Keep the Edit/Embed
291
+ * panels mounted (CSS-hidden) so we can turn this back on without ripping slots.
292
+ */
293
+ export let showCompositionSteps: boolean = false;
288
294
  /** After Set live, Create preview stays on that published artifact until the next Worker build. */
289
295
  let pinnedPublishedPreviewUrl: string | null = null;
290
296
 
@@ -308,8 +314,15 @@
308
314
  previewError = null;
309
315
  if (!iframeSrc) previewLoading = true;
310
316
  }
311
- $: canEditStep = lastPublishedVersion !== null && !isGeneratingCode;
312
- $: canEmbedStep = lastPublishedVersion !== null && !isGeneratingCode;
317
+ $: canEditStep = showCompositionSteps && lastPublishedVersion !== null && !isGeneratingCode;
318
+ $: canEmbedStep = showCompositionSteps && lastPublishedVersion !== null && !isGeneratingCode;
319
+ $: activeWorkflowStep = showCompositionSteps ? currentStep : 'create';
320
+
321
+ $: if (!showCompositionSteps && currentStep !== 'create') {
322
+ currentStep = 'create';
323
+ dispatch('stepChange', { step: 'create' });
324
+ switchPreviewUrlForStep('create');
325
+ }
313
326
 
314
327
  // Auto-switch back to Create when code generation starts
315
328
  let _justFinishedGenerating = false;
@@ -358,6 +371,7 @@
358
371
 
359
372
  function canNavigateToStep(step: WidgetStep): boolean {
360
373
  if (step === 'create') return true;
374
+ if (!showCompositionSteps) return false;
361
375
  if (step === 'edit') return canEditStep;
362
376
  if (step === 'embed') return canEmbedStep;
363
377
  return false;
@@ -978,6 +992,7 @@
978
992
  let detailsDragBarEl: HTMLElement | null = null;
979
993
  let widgetDetailsNameCtEl: HTMLElement | null = null;
980
994
  let widgetDetailsStepsRowEl: HTMLElement | null = null;
995
+ let widgetDetailsDebugButtonsEl: HTMLElement | null = null;
981
996
  let headerNameMaxWidthPx = 96;
982
997
  let detailsDragStartX = 0;
983
998
  let detailsDragStartY = 0;
@@ -2818,10 +2833,14 @@
2818
2833
  }
2819
2834
 
2820
2835
  function updateHeaderNameMaxWidth() {
2821
- if (!widgetDetailsNameCtEl || !widgetDetailsStepsRowEl) return;
2836
+ if (!widgetDetailsNameCtEl) return;
2822
2837
  const nameRect = widgetDetailsNameCtEl.getBoundingClientRect();
2823
- const stepsRect = widgetDetailsStepsRowEl.getBoundingClientRect();
2824
- const available = Math.floor(stepsRect.left - nameRect.left - HEADER_ELEMENTS_GAP_PX);
2838
+ const rightEl = showCompositionSteps && widgetDetailsStepsRowEl
2839
+ ? widgetDetailsStepsRowEl
2840
+ : widgetDetailsDebugButtonsEl;
2841
+ if (!rightEl) return;
2842
+ const rightRect = rightEl.getBoundingClientRect();
2843
+ const available = Math.floor(rightRect.left - nameRect.left - HEADER_ELEMENTS_GAP_PX);
2825
2844
  const next = Math.max(HEADER_NAME_MIN_WIDTH_PX, available);
2826
2845
  if (next !== headerNameMaxWidthPx) {
2827
2846
  headerNameMaxWidthPx = next;
@@ -3359,6 +3378,7 @@
3359
3378
  }}
3360
3379
  />
3361
3380
  </div>
3381
+ {#if showCompositionSteps}
3362
3382
  <div
3363
3383
  class="widget-details-step-indicator-row pointer-events-none absolute left-1/2 top-1/2 z-0 flex -translate-x-1/2 -translate-y-1/2 justify-center"
3364
3384
  bind:this={widgetDetailsStepsRowEl}
@@ -3421,8 +3441,12 @@
3421
3441
  </HoverTooltip>
3422
3442
  </div>
3423
3443
  </div>
3444
+ {/if}
3424
3445
 
3425
- <div class="widget-details-debug-buttons relative z-[1] ml-auto flex items-center shrink-0">
3446
+ <div
3447
+ class="widget-details-debug-buttons relative z-[1] ml-auto flex items-center shrink-0"
3448
+ bind:this={widgetDetailsDebugButtonsEl}
3449
+ >
3426
3450
  <HoverTooltip text={previewColumnCollapsed ? 'Open live preview' : 'Close live preview'}>
3427
3451
  <button
3428
3452
  type="button"
@@ -3505,7 +3529,7 @@
3505
3529
  <div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
3506
3530
 
3507
3531
  <!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
3508
- <div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden overflow-x-hidden {currentStep === 'create' ? '' : 'hidden'}">
3532
+ <div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden overflow-x-hidden {activeWorkflowStep === 'create' ? '' : 'hidden'}">
3509
3533
  <div class="widget-details-create-models-ct shrink-0 empty:hidden">
3510
3534
  <slot name="code-generation-models" />
3511
3535
  </div>
@@ -3523,10 +3547,10 @@
3523
3547
  <slot name="publish-widget" />
3524
3548
  </div>
3525
3549
  </div>
3526
- <div class="widget-details-edit-step flex flex-col flex-1 min-h-0 overflow-hidden {currentStep === 'edit' ? '' : 'hidden'}">
3550
+ <div class="widget-details-edit-step flex flex-col flex-1 min-h-0 overflow-hidden {activeWorkflowStep === 'edit' ? '' : 'hidden'}">
3527
3551
  <slot name="composition-editor" />
3528
3552
  </div>
3529
- <div class="widget-details-embed-step flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin gap-2 p-2 {currentStep === 'embed' ? '' : 'hidden'}">
3553
+ <div class="widget-details-embed-step flex flex-col flex-1 min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin gap-2 p-2 {activeWorkflowStep === 'embed' ? '' : 'hidden'}">
3530
3554
  <slot name="embed-section" />
3531
3555
  </div>
3532
3556
 
@@ -99,6 +99,11 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
99
99
  /** Cascade offset index (0 = centered, 1+ = shifted down-right). */ panelCascadeIndex?: number;
100
100
  /** False when another panel has keyboard/chat focus. */ isFocusedPanel?: boolean;
101
101
  currentStep?: "embed" | "create" | "edit";
102
+ /**
103
+ * Canvas Creator is code-only (chat + preview). Edit/Embed header steps stay
104
+ * off until composition editing lives on /composition. Keep the Edit/Embed
105
+ * panels mounted (CSS-hidden) so we can turn this back on without ripping slots.
106
+ */ showCompositionSteps?: boolean;
102
107
  beginCompositionHydration?: () => void;
103
108
  showPublishedArtifact?: (url: string | null) => void;
104
109
  finishCompositionHydration?: () => void;
@@ -6,7 +6,7 @@ export interface CreatorAppProps {
6
6
  initialWidgetId?: string | null;
7
7
  /** Select this composition when opening Edit step from site. */
8
8
  initialCompositionId?: string | null;
9
- /** Open Widget Details on this step when embedded from site (overrides auto step once). */
9
+ /** Open Widget Details on this step when embedded from site (overrides auto step once). Ignored while Creator composition steps are hidden. */
10
10
  initialStep?: 'create' | 'edit' | 'embed';
11
11
  /**
12
12
  * Site "+ New widget": create an empty canvas + Untitled widget instead of
@@ -35,6 +35,7 @@ export interface CreatorAppProps {
35
35
  /**
36
36
  * Extra canvas tools (undo/frame/image/library, zoom 1:1/fit/shortcuts).
37
37
  * Independent of debugMode — Debug is staging-only UI.
38
+ * Also shows Widget Details Edit/Embed steps while ON (dev testing).
38
39
  * Site owns the header toggle; embed uses CustomEvent after mount.
39
40
  */
40
41
  initialAdvancedFeatures?: boolean;
@@ -0,0 +1,5 @@
1
+ import type { WidgetsApi } from '@widgetic/api-sdk';
2
+ /**
3
+ * Upload a convert/preview screenshot to widgets.thumbnail_url via the API gateway.
4
+ */
5
+ export declare function persistWidgetThumbnail(widgetsClient: WidgetsApi | null | undefined, widgetId: string | null | undefined, imageDataUrl: string | null | undefined): Promise<string | null>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Upload a convert/preview screenshot to widgets.thumbnail_url via the API gateway.
3
+ */
4
+ export async function persistWidgetThumbnail(widgetsClient, widgetId, imageDataUrl) {
5
+ if (!widgetsClient || !widgetId || typeof imageDataUrl !== 'string')
6
+ return null;
7
+ if (!imageDataUrl.startsWith('data:image/'))
8
+ return null;
9
+ try {
10
+ const response = await widgetsClient.uploadWidgetThumbnailRaw({
11
+ id: widgetId,
12
+ uploadWidgetThumbnailRequest: { imageDataUrl },
13
+ });
14
+ const json = await response.raw.json();
15
+ const thumbnailUrl = json?.data?.thumbnailUrl ||
16
+ json?.data?.thumbnail_url ||
17
+ json?.thumbnailUrl ||
18
+ null;
19
+ if (thumbnailUrl) {
20
+ console.log('[persistWidgetThumbnail] stored', widgetId.slice(0, 8), thumbnailUrl.slice(0, 80));
21
+ }
22
+ return thumbnailUrl;
23
+ }
24
+ catch (error) {
25
+ console.warn('[persistWidgetThumbnail] failed', widgetId?.slice(0, 8), error);
26
+ return null;
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/creator",
3
- "version": "0.3.55",
3
+ "version": "0.3.56",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@sveltejs/kit": "^2.20.0",