@widgetic/creator 0.3.68 → 0.3.70

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.
@@ -5672,6 +5672,52 @@
5672
5672
  }
5673
5673
 
5674
5674
  // ─── Create new canvas ───────────────────────────────────────────────────────
5675
+ /** New-widget opens on a local canvas. The DB row is created on the first real edit. */
5676
+ let canvasRowDeferred = false;
5677
+ let canvasRowCreatePromise: Promise<string | null> | null = null;
5678
+
5679
+ async function ensureCanvasRowForFirstEdit(): Promise<string | null> {
5680
+ const existingId = typeof canvasId === 'string' ? canvasId.trim() : '';
5681
+ if (existingId) return existingId;
5682
+ if (canvasRowCreatePromise) return canvasRowCreatePromise;
5683
+ canvasRowCreatePromise = (async () => {
5684
+ if (!canvasesClient || !$userSession.user) {
5685
+ console.warn('[CreatorApp] First edit not persisted — canvas client is not ready');
5686
+ return null;
5687
+ }
5688
+ try {
5689
+ const usedNames = new Set(userCanvases.map((c) => (c.name || '').trim()).filter(Boolean));
5690
+ let canvasIndex = userCanvases.length + 1;
5691
+ let newName = `Canvas ${canvasIndex}`;
5692
+ while (usedNames.has(newName)) {
5693
+ canvasIndex += 1;
5694
+ newName = `Canvas ${canvasIndex}`;
5695
+ }
5696
+ const createdRaw = await canvasesClient.createCanvas({
5697
+ createCanvasRequest: { persistenceKey: newName }
5698
+ });
5699
+ const createdId = unwrapCreatedCanvasId(createdRaw);
5700
+ if (!createdId) {
5701
+ console.warn('[CreatorApp] First-edit canvas create response missing id');
5702
+ return null;
5703
+ }
5704
+ userCanvases = [{ id: createdId, name: newName, hasContent: true }, ...userCanvases];
5705
+ rememberCanvasId(createdId);
5706
+ activeCanvasName = newName;
5707
+ canvasRowDeferred = false;
5708
+ syncEmbeddedCanvasUrl(createdId);
5709
+ console.log('[CreatorApp] Canvas row created on first edit', createdId);
5710
+ return createdId;
5711
+ } catch (err) {
5712
+ console.error('[CreatorApp] Failed to create canvas on first edit:', err);
5713
+ return null;
5714
+ } finally {
5715
+ canvasRowCreatePromise = null;
5716
+ }
5717
+ })();
5718
+ return canvasRowCreatePromise;
5719
+ }
5720
+
5675
5721
  async function createNewCanvas(options: { skipSaveCurrent?: boolean } = {}): Promise<string | null> {
5676
5722
  if (!canvasesClient || !$userSession.user) {
5677
5723
  console.warn('[WidgetCreator] createNewCanvas skipped — missing client or user');
@@ -5776,24 +5822,10 @@
5776
5822
  selectedWidgetId = null;
5777
5823
  selectedWidget = null;
5778
5824
  beginBlankCanvasSession('site New widget');
5779
- // console.log('[CreatorApp] New widget: blank canvas only — widget record is created on Convert (F3)');
5780
- const clientsReady = await waitForAuthenticatedCanvasClient();
5781
- if (!clientsReady) {
5782
- console.error('[CreatorApp] Auth/clients not ready — blank canvas without new canvas row');
5783
- } else {
5784
- const newCanvasId = await createNewCanvas({ skipSaveCurrent: true });
5785
- if (!newCanvasId) {
5786
- showToast('error', 'Could not create a blank canvas', {
5787
- description: 'Try reloading the page to get a fresh canvas.',
5788
- duration: 8000
5789
- });
5790
- } else {
5791
- // Replace `new_widget=1` with the concrete canvas_id so a page refresh
5792
- // reloads THIS canvas (and its sketch) instead of re-running the
5793
- // new-widget bootstrap and creating yet another blank canvas row.
5794
- syncEmbeddedCanvasUrl(newCanvasId);
5795
- }
5796
- }
5825
+ rememberCanvasId(null);
5826
+ canvasRowDeferred = true;
5827
+ // No DB row yet. Autosave creates one on the first edit that has shapes.
5828
+ // An untouched new-widget refresh stays on new_widget=1 and writes nothing.
5797
5829
  // F3: New-widget flow starts with an EMPTY canvas — no widget record, no
5798
5830
  // WidgetShape, no auto-opened Widget Details. The widget record is created
5799
5831
  // only when the user sketches something and clicks "Convert to Widget".
@@ -7229,6 +7261,7 @@
7229
7261
  typeof crypto !== 'undefined' && crypto.randomUUID
7230
7262
  ? crypto.randomUUID()
7231
7263
  : `${Date.now()}-new-widget`;
7264
+ if (!canvasId) await ensureCanvasRowForFirstEdit();
7232
7265
  const response = await widgetsClient.createWidget(
7233
7266
  {
7234
7267
  newWidget: {
@@ -13923,6 +13956,13 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
13923
13956
  });
13924
13957
  }
13925
13958
 
13959
+ // A new-widget session has no row until the user actually adds something.
13960
+ // Empty autosaves (open, refresh, unmount of a blank canvas) stay local.
13961
+ if (canvasRowDeferred && !canvasId && objectCount > 0) {
13962
+ await ensureCanvasRowForFirstEdit();
13963
+ if (canvasId) saveCanvasState(canvasId, json);
13964
+ }
13965
+
13926
13966
  // Save to API via SDK with sequence race protection
13927
13967
  if (canvasId && canvasesClient) {
13928
13968
  const currentSeq = ++autosaveSequence;
@@ -14123,6 +14163,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
14123
14163
  ?? `ConvertedW-${Date.now()}`;
14124
14164
 
14125
14165
  authStatus = 'Creating widget from design...';
14166
+ if (!canvasId) await ensureCanvasRowForFirstEdit();
14126
14167
  const idempotencyKey = crypto?.randomUUID?.() ?? `${Date.now()}-convert`;
14127
14168
  const response = await widgetsClient.createWidget(
14128
14169
  {
@@ -146,20 +146,6 @@
146
146
  const VERSION_MENU_PAD_REM = 0.6;
147
147
  /** Menu shows at most 5 rows before scrolling. */
148
148
  const VERSION_MENU_MAX_ROWS = 5;
149
- /** Fabric corner handles sit on the shape corners and must stay clickable through the bar. */
150
- const HANDLE_NOTCH_PX = 14;
151
-
152
- function headerNotchClip(barWidth: number, shapeWidth: number): string {
153
- const notch = HANDLE_NOTCH_PX;
154
- const height = DRAG_BAR_H;
155
- const y = height - notch;
156
- const shapeRight = Math.min(barWidth, Math.max(shapeWidth, notch * 2));
157
- if (shapeRight >= barWidth - 1) {
158
- return `polygon(0px 0px, ${barWidth}px 0px, ${barWidth}px ${y}px, ${barWidth - notch}px ${y}px, ${barWidth - notch}px ${height}px, ${notch}px ${height}px, ${notch}px ${y}px, 0px ${y}px)`;
159
- }
160
- const rightInner = Math.max(notch + 1, shapeRight - notch);
161
- return `polygon(0px 0px, ${barWidth}px 0px, ${barWidth}px ${height}px, ${shapeRight}px ${height}px, ${shapeRight}px ${y}px, ${rightInner}px ${y}px, ${rightInner}px ${height}px, ${notch}px ${height}px, ${notch}px ${y}px, 0px ${y}px)`;
162
- }
163
149
 
164
150
  type ShapeRect = {
165
151
  widgetId: string;
@@ -701,14 +687,14 @@
701
687
  <div
702
688
  class="widget-shape-drag-bar pointer-events-auto absolute left-0 right-0 top-0 z-10 flex items-center gap-1 rounded-t-md border border-b-0 px-1.5 shadow-sm select-none [&_button:focus]:outline-none [&_button:focus]:shadow-none [&_button:focus-visible]:outline-none [&_button:focus-visible]:shadow-none
703
689
  {isPrimary ? 'bg-red-100' : 'bg-white'} {isSelected ? 'border-blue-400' : 'border-gray-300'}"
704
- style="height: {DRAG_BAR_H}px; cursor: grab; min-width: {showExpandedChrome ? MIN_EXPANDED_CHROME_WIDTH : 0}px; clip-path: {headerNotchClip(chromeWidth, overlay.width)};"
690
+ style="height: {DRAG_BAR_H}px; cursor: grab; min-width: {showExpandedChrome ? MIN_EXPANDED_CHROME_WIDTH : 0}px;"
705
691
  title="Drag widget"
706
692
  onpointerdown={(e) => startShapeDrag(e, overlay.widgetId)}
707
693
  >
708
694
  {#if isPrimary}
709
695
  <div
710
696
  class="widget-shape-primary-stroke pointer-events-none absolute z-20 bg-[#DC2626]"
711
- style="left: 0; width: {overlay.width}px; top: {DRAG_BAR_H - IFRAME_INSET_PX - 1}px; height: 2px; clip-path: inset(0 {HANDLE_NOTCH_PX}px 0 {HANDLE_NOTCH_PX}px);"
697
+ style="left: 0; width: {overlay.width}px; top: {DRAG_BAR_H - IFRAME_INSET_PX - 1}px; height: 2px;"
712
698
  ></div>
713
699
  {/if}
714
700
  <span class="widget-shape-drag-grip shrink-0 text-xs leading-none text-gray-400" aria-hidden="true">⠿</span>
@@ -132,27 +132,10 @@ export function installFreezeWatchdog() {
132
132
  URL.revokeObjectURL(url);
133
133
  let lastStatus = '';
134
134
  let pendingBreadcrumbs = [];
135
- let lastLongTaskSentAt = performance.now();
136
- function longTasksSince(since) {
137
- if (typeof performance === 'undefined' || typeof performance.getEntriesByType !== 'function')
138
- return [];
139
- const found = [];
140
- for (const entry of performance.getEntriesByType('longtask')) {
141
- const endedAt = entry.startTime + entry.duration;
142
- if (endedAt < since || entry.duration < BREADCRUMB_MIN_MS)
143
- continue;
144
- const attribution = entry.attribution?.[0];
145
- const source = attribution?.containerSrc || attribution?.containerName || attribution?.containerType;
146
- found.push({ label: 'longtask', duration: entry.duration, source });
147
- }
148
- return found;
149
- }
150
135
  worker.onmessage = (event) => {
151
136
  const data = event.data;
152
137
  if (data?.type === 'ping') {
153
- const breadcrumbs = pendingBreadcrumbs.concat(longTasksSince(lastLongTaskSentAt));
154
- lastLongTaskSentAt = performance.now();
155
- worker.postMessage({ type: 'pong', status: lastStatus, breadcrumbs });
138
+ worker.postMessage({ type: 'pong', status: lastStatus, breadcrumbs: pendingBreadcrumbs });
156
139
  pendingBreadcrumbs = [];
157
140
  }
158
141
  else if (data?.type === 'freeze-report' && data.message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/creator",
3
- "version": "0.3.68",
3
+ "version": "0.3.70",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@sveltejs/kit": "^2.20.0",