@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.
@@ -25,6 +25,7 @@ declare const CreatorApp: $$__sveltets_2_IsomorphicComponent<{
25
25
  apiUrl?: CreatorAppProps["apiUrl"];
26
26
  widgetBuilderUrl?: CreatorAppProps["widgetBuilderUrl"];
27
27
  initialDebugMode?: CreatorAppProps["initialDebugMode"];
28
+ initialAdvancedFeatures?: CreatorAppProps["initialAdvancedFeatures"];
28
29
  defaultCoderModelId?: CreatorAppProps["defaultCoderModelId"];
29
30
  defaultPlannerModelId?: CreatorAppProps["defaultPlannerModelId"];
30
31
  showWidgetsList?: CreatorAppProps["showWidgetsList"];
@@ -868,6 +868,8 @@
868
868
  const LEFT_COL_DEFAULT_PERCENT = 58;
869
869
  const LEFT_COL_MIN_PERCENT = 32;
870
870
  const LEFT_COL_MAX_PERCENT = 72;
871
+ const COL_RESIZER_PX = 10;
872
+ const PREVIEW_COL_MIN_PX = 240;
871
873
  /** Chat-first layout: preview column starts collapsed and opens on generate. */
872
874
  let previewColumnCollapsed = true;
873
875
  let lastAutoExpandForGenerating = false;
@@ -891,6 +893,23 @@
891
893
  expandPreviewColumn();
892
894
  }
893
895
 
896
+ function maxLeftColPercent(): number {
897
+ const twoCol =
898
+ typeof document !== 'undefined'
899
+ ? (document.querySelector('.widget-details-two-col') as HTMLElement | null)
900
+ : null;
901
+ const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
902
+ if (width <= 0) return LEFT_COL_MAX_PERCENT;
903
+ const maxLeftPx = Math.max(160, width - COL_RESIZER_PX - PREVIEW_COL_MIN_PX);
904
+ return Math.min(LEFT_COL_MAX_PERCENT, (maxLeftPx / width) * 100);
905
+ }
906
+
907
+ function clampLeftColPercent(percent: number): number {
908
+ const maxP = maxLeftColPercent();
909
+ const minP = Math.min(LEFT_COL_MIN_PERCENT, maxP);
910
+ return Math.min(maxP, Math.max(minP, percent));
911
+ }
912
+
894
913
  function collapsePreviewColumn() {
895
914
  previewColumnCollapsed = true;
896
915
  if (widthBeforePreviewExpand > 0) {
@@ -911,10 +930,7 @@
911
930
  const fittedWidth = Math.min(expandedWidth, Math.max(widthBeforePreviewExpand, maxRight - detailsPanelX));
912
931
  detailsPanelWidth = Math.max(widthBeforePreviewExpand, fittedWidth);
913
932
  const usable = Math.max(1, detailsPanelWidth);
914
- leftColPercent = Math.min(
915
- LEFT_COL_MAX_PERCENT,
916
- Math.max(LEFT_COL_MIN_PERCENT, (widthBeforePreviewExpand / usable) * 100),
917
- );
933
+ leftColPercent = clampLeftColPercent((widthBeforePreviewExpand / usable) * 100);
918
934
  console.log('[WidgetDetails] Preview column expanded', {
919
935
  from: widthBeforePreviewExpand,
920
936
  to: detailsPanelWidth,
@@ -1461,7 +1477,9 @@
1461
1477
  }
1462
1478
 
1463
1479
  export function hasValidPreview(): boolean {
1464
- return !!iframeSrc && !previewError && !previewLoading;
1480
+ // Iframe can already show a compiled preview while `previewLoading`/`previewError`
1481
+ // still lag (e.g. iteration rebuild). Screenshot only needs a live contentWindow.
1482
+ return !!previewIframe?.contentWindow;
1465
1483
  }
1466
1484
 
1467
1485
  export function getPreviewIframe(): HTMLIFrameElement | null {
@@ -1590,19 +1608,65 @@
1590
1608
  notifyPreviewCompileErrorChanged();
1591
1609
  }
1592
1610
  flushPendingPreviewMessages();
1593
- if (currentStep === 'edit' || currentStep === 'embed') {
1594
- dispatch('previewLoaded', { step: currentStep });
1595
- }
1611
+ dispatch('previewLoaded', { step: currentStep });
1596
1612
  // Emit previewReady so the parent app can clear the "…loading preview…" status.
1597
1613
  dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
1598
1614
  // Fill available preview area once the widget confirms it is ready.
1599
1615
  scheduleFitPreviewToArea();
1600
1616
  }
1601
1617
 
1618
+ function looksLikeHtmlMarkup(value: unknown): value is string {
1619
+ return typeof value === 'string' && /<[a-z][\s\S]*>/i.test(value);
1620
+ }
1621
+
1622
+ /** Same-origin preview iframes (Vite /__widget_builder__ proxy): restore bold/italic after widgets set textContent. */
1623
+ function applyRichTextInPreviewIframe(message: Record<string, unknown>): void {
1624
+ if (message.type !== 'widgetic:update') return;
1625
+ const doc = previewIframe?.contentDocument;
1626
+ if (!doc) return;
1627
+ const apply = () => {
1628
+ try {
1629
+ const content = message.content as Record<string, unknown> | undefined;
1630
+ if (content) {
1631
+ for (const [key, value] of Object.entries(content)) {
1632
+ if (!looksLikeHtmlMarkup(value)) continue;
1633
+ doc.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
1634
+ if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
1635
+ el.innerHTML = value;
1636
+ });
1637
+ }
1638
+ }
1639
+ const items = message.contentItems as Array<Record<string, unknown>> | undefined;
1640
+ if (Array.isArray(items)) {
1641
+ for (const item of items) {
1642
+ const itemEl = item?.id
1643
+ ? doc.querySelector(`[data-content-item="${CSS.escape(String(item.id))}"]`)
1644
+ : null;
1645
+ const scope = itemEl || doc;
1646
+ for (const [key, value] of Object.entries(item || {})) {
1647
+ if (key === 'id' || !looksLikeHtmlMarkup(value)) continue;
1648
+ scope.querySelectorAll(`[data-content="${CSS.escape(key)}"]`).forEach((el) => {
1649
+ if (el instanceof HTMLImageElement || el instanceof HTMLInputElement) return;
1650
+ el.innerHTML = value;
1651
+ });
1652
+ }
1653
+ }
1654
+ }
1655
+ } catch (error) {
1656
+ console.warn('[WidgetDetails] Rich-text preview overlay skipped:', error);
1657
+ }
1658
+ };
1659
+ requestAnimationFrame(() => {
1660
+ apply();
1661
+ setTimeout(apply, 50);
1662
+ });
1663
+ }
1664
+
1602
1665
  /** Send a postMessage to the widget preview iframe (for live property editing) */
1603
1666
  export function sendMessageToPreview(message: Record<string, unknown>): void {
1604
1667
  if (previewWidgetReady && previewIframe?.contentWindow) {
1605
1668
  previewIframe.contentWindow.postMessage(message, '*');
1669
+ applyRichTextInPreviewIframe(message);
1606
1670
  return;
1607
1671
  }
1608
1672
  pendingPreviewMessages.push(message);
@@ -1944,9 +2008,15 @@
1944
2008
  }
1945
2009
  } else {
1946
2010
  previewHydrating = false;
1947
- previewWidgetReady = true;
1948
2011
  dispatch('previewReady', { widgetId: selectedWidgetId, step: currentStep });
1949
- // Create step: iframe load does not go through markPreviewWidgetReady still fit-to-area.
2012
+ // Create: wait for widgetic:ready so contentItems overlay is not sent before the widget listens.
2013
+ if (!previewWidgetReady) {
2014
+ if (previewReadyFallbackTimeout) clearTimeout(previewReadyFallbackTimeout);
2015
+ previewReadyFallbackTimeout = setTimeout(() => {
2016
+ console.warn('[WidgetDetails] Create step widgetic:ready fallback — applying overlay');
2017
+ markPreviewWidgetReady();
2018
+ }, 1500);
2019
+ }
1950
2020
  scheduleFitPreviewToArea();
1951
2021
  }
1952
2022
  }
@@ -2273,10 +2343,7 @@
2273
2343
  const width = twoCol?.clientWidth || detailsPanelWidth || DETAILS_PANEL_DEFAULT_WIDTH;
2274
2344
  if (width <= 0) return;
2275
2345
  const deltaPercent = ((e.clientX - columnResizeStartX) / width) * 100;
2276
- leftColPercent = Math.min(
2277
- LEFT_COL_MAX_PERCENT,
2278
- Math.max(LEFT_COL_MIN_PERCENT, columnResizeStartPercent + deltaPercent)
2279
- );
2346
+ leftColPercent = clampLeftColPercent(columnResizeStartPercent + deltaPercent);
2280
2347
  // Keep preview iframe inside the shrinking right column (avoid clipped "cut off" widgets).
2281
2348
  requestAnimationFrame(() => {
2282
2349
  clampPreviewToArea();
@@ -2363,15 +2430,27 @@
2363
2430
  window.removeEventListener('blur', stopFn);
2364
2431
  }
2365
2432
 
2366
- function startDetailsResize(e: MouseEvent) {
2433
+ let detailsResizeEdge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se';
2434
+ let detailsResizeStartPanelX = 0;
2435
+ let detailsResizeStartPanelY = 0;
2436
+
2437
+ function startDetailsResize(e: MouseEvent, edge: 'se' | 'sw' | 'e' | 'w' | 's' = 'se') {
2367
2438
  e.preventDefault();
2368
2439
  e.stopPropagation();
2369
2440
  isResizingDetails = true;
2441
+ detailsResizeEdge = edge;
2370
2442
  detailsResizeStartX = e.clientX;
2371
2443
  detailsResizeStartY = e.clientY;
2372
2444
  detailsResizeStartWidth = detailsPanelWidth;
2373
2445
  detailsResizeStartHeight = detailsPanelHeight || getDefaultDetailsPanelHeight();
2374
- document.body.style.cursor = 'nwse-resize';
2446
+ detailsResizeStartPanelX = detailsPanelX;
2447
+ detailsResizeStartPanelY = detailsPanelY;
2448
+ const cursor =
2449
+ edge === 'e' || edge === 'w' ? 'ew-resize'
2450
+ : edge === 's' ? 'ns-resize'
2451
+ : edge === 'sw' ? 'nesw-resize'
2452
+ : 'nwse-resize';
2453
+ document.body.style.cursor = cursor;
2375
2454
  document.body.style.userSelect = 'none';
2376
2455
  window.addEventListener('mousemove', onDetailsResize);
2377
2456
  addGlobalStopListeners(stopDetailsResize);
@@ -2381,10 +2460,46 @@
2381
2460
  if (!isResizingDetails) return;
2382
2461
  const deltaX = e.clientX - detailsResizeStartX;
2383
2462
  const deltaY = e.clientY - detailsResizeStartY;
2384
- const maxWidth = window.innerWidth - detailsPanelX - 20;
2385
- const maxHeight = window.innerHeight - detailsPanelY - 20;
2386
- detailsPanelWidth = Math.min(maxWidth, Math.max(DETAILS_PANEL_MIN_WIDTH, detailsResizeStartWidth + deltaX));
2387
- detailsPanelHeight = Math.min(maxHeight, Math.max(DETAILS_PANEL_MIN_HEIGHT, detailsResizeStartHeight + deltaY));
2463
+ const minW = DETAILS_PANEL_MIN_WIDTH;
2464
+ const minH = DETAILS_PANEL_MIN_HEIGHT;
2465
+ const maxRight = window.innerWidth - 8;
2466
+ const maxBottom = window.innerHeight - 8;
2467
+
2468
+ let nextW = detailsResizeStartWidth;
2469
+ let nextH = detailsResizeStartHeight;
2470
+ let nextX = detailsResizeStartPanelX;
2471
+ let nextY = detailsResizeStartPanelY;
2472
+
2473
+ if (detailsResizeEdge === 'e' || detailsResizeEdge === 'se') {
2474
+ nextW = detailsResizeStartWidth + deltaX;
2475
+ }
2476
+ if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
2477
+ nextW = detailsResizeStartWidth - deltaX;
2478
+ nextX = detailsResizeStartPanelX + deltaX;
2479
+ }
2480
+ if (detailsResizeEdge === 's' || detailsResizeEdge === 'se' || detailsResizeEdge === 'sw') {
2481
+ nextH = detailsResizeStartHeight + deltaY;
2482
+ }
2483
+
2484
+ nextW = Math.max(minW, nextW);
2485
+ nextH = Math.max(minH, nextH);
2486
+ if (detailsResizeEdge === 'w' || detailsResizeEdge === 'sw') {
2487
+ nextX = detailsResizeStartPanelX + (detailsResizeStartWidth - nextW);
2488
+ if (nextX < 8) {
2489
+ nextW -= (8 - nextX);
2490
+ nextX = 8;
2491
+ }
2492
+ }
2493
+ if (nextX + nextW > maxRight) nextW = maxRight - nextX;
2494
+ if (nextY + nextH > maxBottom) nextH = maxBottom - nextY;
2495
+
2496
+ detailsPanelWidth = nextW;
2497
+ detailsPanelHeight = nextH;
2498
+ detailsPanelX = nextX;
2499
+ detailsPanelY = nextY;
2500
+ if (!previewColumnCollapsed) {
2501
+ leftColPercent = clampLeftColPercent(leftColPercent);
2502
+ }
2388
2503
  }
2389
2504
 
2390
2505
  function stopDetailsResize() {
@@ -2847,11 +2962,11 @@
2847
2962
  {#if selectedWidgetId}
2848
2963
  <!-- svelte-ignore a11y-no-static-element-interactions -->
2849
2964
  <div
2850
- class="widget-details-floating-panel pointer-events-auto fixed overflow-hidden
2965
+ class="widget-details-floating-panel pointer-events-auto fixed
2851
2966
  {showWidgetDetails ? 'flex flex-col' : 'hidden'}
2852
2967
  {fillViewport
2853
- ? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none'
2854
- : 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border'}
2968
+ ? 'bg-white rounded-none border-x border-b border-t-0 ring-0 shadow-none overflow-hidden'
2969
+ : 'bg-white/95 backdrop-blur-sm shadow-2xl rounded-xl border overflow-visible'}
2855
2970
  {isFocusedPanel
2856
2971
  ? (fillViewport ? 'border-blue-400' : 'border-blue-400 ring-2 ring-blue-200/80')
2857
2972
  : 'border-gray-300 opacity-[0.97]'}"
@@ -2866,7 +2981,7 @@
2866
2981
  onpointerdown={startDetailsDrag}
2867
2982
  >
2868
2983
  <span
2869
- class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[22%] truncate text-xs font-medium text-gray-600"
2984
+ class="widget-details-name pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 max-w-[28%] truncate text-sm font-bold text-gray-800"
2870
2985
  title={selectedWidget?.name || ''}
2871
2986
  >
2872
2987
  {selectedWidget?.name || (isDraftConvertPanel ? 'Widget Draft' : 'Widget')}
@@ -2925,7 +3040,7 @@
2925
3040
  </div>
2926
3041
  </div>
2927
3042
 
2928
- <div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0">
3043
+ <div class="widget-details-debug-buttons flex items-center gap-1.5 shrink-0 pr-1">
2929
3044
  <button
2930
3045
  type="button"
2931
3046
  onclick={() => previewColumnCollapsed ? expandPreviewColumn() : collapsePreviewColumn()}
@@ -2939,23 +3054,24 @@
2939
3054
  >
2940
3055
  {previewColumnCollapsed ? 'Open Preview' : 'Close Preview'}
2941
3056
  </button>
2942
- <button
2943
- type="button"
2944
- onclick={handleCloseButtonClick}
2945
- onmousedown={(e) => e.stopPropagation()}
2946
- onpointerdown={(e) => e.stopPropagation()}
2947
- class="widget-details-close-bt w-6 h-6 flex items-center justify-center rounded-lg hover:bg-gray-200 text-gray-500 hover:text-gray-700 cursor-pointer transition-colors text-sm"
2948
- title="Close widget details"
2949
- >
2950
-
2951
- </button>
2952
3057
  </div>
2953
3058
  </div>
2954
3059
  </div>
2955
3060
 
3061
+ <button
3062
+ type="button"
3063
+ onclick={handleCloseButtonClick}
3064
+ onmousedown={(e) => e.stopPropagation()}
3065
+ onpointerdown={(e) => e.stopPropagation()}
3066
+ class="widget-details-close-bt absolute -top-2.5 -right-2.5 z-30 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-gray-300 shadow-md hover:bg-red-50 hover:border-red-300 hover:text-red-600 text-gray-500 cursor-pointer transition-colors text-sm"
3067
+ title="Close widget details"
3068
+ >
3069
+
3070
+ </button>
3071
+
2956
3072
  <!-- Widget Details Content: Two-column layout -->
2957
- <div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1">
2958
- <div class="widget-details-two-col flex h-full min-h-0 gap-0">
3073
+ <div class="widget-details-content-wrapper relative m-2.5 min-w-0 min-h-0 flex-1 overflow-x-hidden">
3074
+ <div class="widget-details-two-col flex h-full min-h-0 min-w-0 gap-0 overflow-x-hidden">
2959
3075
 
2960
3076
  <!-- ═══ LEFT COLUMN: Step Content (scrollable) ═══ -->
2961
3077
  <div
@@ -2995,17 +3111,17 @@
2995
3111
 
2996
3112
  <!-- Scrollable step content -->
2997
3113
  <div
2998
- class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg {currentStep === 'edit' ? 'overflow-hidden' : 'overflow-hidden'}"
3114
+ class="widget-details-left-content flex flex-col flex-1 min-h-0 relative rounded-b-lg overflow-hidden overflow-x-hidden"
2999
3115
  bind:this={contentContainerEl}
3000
3116
  >
3001
3117
  <div class="actions-panel widget-details-actions-panel relative flex flex-col flex-1 min-h-0">
3002
3118
 
3003
3119
  <!-- Keep all steps mounted — switching tabs must not destroy chat / PropsEditor state -->
3004
- <div class="widget-details-create-step flex flex-col flex-1 min-h-0 overflow-hidden gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
3120
+ <div class="widget-details-create-step flex flex-col flex-1 min-h-0 justify-start overflow-y-auto overflow-x-hidden scrollbar-thin gap-2 p-2 {currentStep === 'create' ? '' : 'hidden'}">
3005
3121
  <slot name="code-generation" />
3006
3122
  <slot name="publish-widget" />
3007
3123
  </div>
3008
- <div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-hidden {currentStep === 'edit' ? '' : 'hidden'}">
3124
+ <div class="widget-details-edit-step flex flex-col h-full min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin {currentStep === 'edit' ? '' : 'hidden'}">
3009
3125
  <slot name="composition-editor" />
3010
3126
  </div>
3011
3127
  <div class="widget-details-embed-step flex flex-col gap-2 p-2 {currentStep === 'embed' ? '' : 'hidden'}">
@@ -3139,6 +3255,7 @@
3139
3255
  {#if isGeneratingCode || previewLoading}
3140
3256
  <GenerateLoader
3141
3257
  title={isGeneratingCode ? 'Generating code…' : 'Building preview…'}
3258
+ subtitle=""
3142
3259
  size="md"
3143
3260
  />
3144
3261
  {:else if previewError === 'DRAFT_PANEL'}
@@ -3190,6 +3307,17 @@
3190
3307
  </div>
3191
3308
  {/if}
3192
3309
  </div>
3310
+ {:else if lastGenerationFailed}
3311
+ <div class="widget-preview-generation-error flex flex-col items-center justify-center text-center p-4">
3312
+ <div class="text-3xl mb-2">⚠️</div>
3313
+ <div class="font-medium text-sm text-red-600 mb-1">Code generation failed</div>
3314
+ <div class="text-xs max-w-[220px] text-gray-600 mb-3">{lastGenerationError || 'Fix the prompt or attachments, then retry.'}</div>
3315
+ <button
3316
+ type="button"
3317
+ onclick={() => { dispatch('generateNow', { widgetId: selectedWidgetId }); }}
3318
+ class="generate-now-btn px-3 py-1.5 text-xs rounded border border-green-500 text-green-700 bg-white hover:bg-green-50 cursor-pointer font-medium"
3319
+ >▶ Retry</button>
3320
+ </div>
3193
3321
  {:else if isLoadingDirectPreviewUrl || previewLoading}
3194
3322
  <GenerateLoader title="Building Preview…" subtitle="Compiling widget code…" size="md" />
3195
3323
  {:else}
@@ -3252,7 +3380,7 @@
3252
3380
  </div> <!-- end of widget-details-content-wrapper -->
3253
3381
 
3254
3382
  <!-- svelte-ignore a11y-no-static-element-interactions -->
3255
- <div class="absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-10 group" onmousedown={startDetailsResize} title="Resize panel">
3383
+ <div class="widget-details-resize-se absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'se')} title="Resize panel">
3256
3384
  <svg class="w-3 h-3 absolute bottom-0.5 right-0.5 text-gray-400 group-hover:text-gray-600 transition-colors" viewBox="0 0 6 6" fill="currentColor">
3257
3385
  <circle cx="5" cy="1" r="0.7"/>
3258
3386
  <circle cx="3" cy="3" r="0.7"/>
@@ -3262,6 +3390,25 @@
3262
3390
  <circle cx="5" cy="5" r="0.7"/>
3263
3391
  </svg>
3264
3392
  </div>
3393
+ {#if !fillViewport}
3394
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3395
+ <div class="widget-details-resize-sw absolute bottom-0 left-0 w-4 h-4 cursor-nesw-resize z-20 group" onmousedown={(e) => startDetailsResize(e, 'sw')} title="Resize panel">
3396
+ <svg class="w-3 h-3 absolute bottom-0.5 left-0.5 text-gray-400 group-hover:text-gray-600 transition-colors rotate-90" viewBox="0 0 6 6" fill="currentColor">
3397
+ <circle cx="5" cy="1" r="0.7"/>
3398
+ <circle cx="3" cy="3" r="0.7"/>
3399
+ <circle cx="5" cy="3" r="0.7"/>
3400
+ <circle cx="1" cy="5" r="0.7"/>
3401
+ <circle cx="3" cy="5" r="0.7"/>
3402
+ <circle cx="5" cy="5" r="0.7"/>
3403
+ </svg>
3404
+ </div>
3405
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3406
+ <div class="widget-details-resize-e absolute top-8 bottom-4 right-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'e')} title="Resize width"></div>
3407
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3408
+ <div class="widget-details-resize-w absolute top-8 bottom-4 left-0 w-1.5 cursor-ew-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 'w')} title="Resize width"></div>
3409
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
3410
+ <div class="widget-details-resize-s absolute bottom-0 left-4 right-4 h-1.5 cursor-ns-resize z-20 hover:bg-blue-400/30" onmousedown={(e) => startDetailsResize(e, 's')} title="Resize height"></div>
3411
+ {/if}
3265
3412
  </div>
3266
3413
  {/if}
3267
3414
 
@@ -32,6 +32,12 @@ export interface CreatorAppProps {
32
32
  * Omit for standalone widget-creator (falls back to VITE_DEBUG_MODE / dev).
33
33
  */
34
34
  initialDebugMode?: boolean;
35
+ /**
36
+ * Extra canvas tools (undo/frame/image/library, zoom 1:1/fit/shortcuts).
37
+ * Independent of debugMode — Debug is staging-only UI.
38
+ * Site owns the header toggle; embed uses CustomEvent after mount.
39
+ */
40
+ initialAdvancedFeatures?: boolean;
35
41
  /**
36
42
  * Host-controlled default coder model id (e.g. `deepseek-v4-flash`).
37
43
  * Site reads its own env and passes this — no creator-app.js rebuild, no API-gateway default for the UI.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/creator",
3
- "version": "0.3.50",
3
+ "version": "0.3.51",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@sveltejs/kit": "^2.20.0",