@popcraft/cli 0.13.2 → 0.15.0

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.
Files changed (2) hide show
  1. package/dist/popcraft.mjs +1170 -238
  2. package/package.json +2 -2
package/dist/popcraft.mjs CHANGED
@@ -32,9 +32,9 @@ var __commonJS = (cb, mod) => function __require2() {
32
32
  throw mod = 0, e;
33
33
  }
34
34
  };
35
- var __export = (target2, all3) => {
36
- for (var name2 in all3)
37
- __defProp(target2, name2, { get: all3[name2], enumerable: true });
35
+ var __export = (target2, all4) => {
36
+ for (var name2 in all4)
37
+ __defProp(target2, name2, { get: all4[name2], enumerable: true });
38
38
  };
39
39
  var __copyProps = (to, from2, except, desc2) => {
40
40
  if (from2 && typeof from2 === "object" || typeof from2 === "function") {
@@ -456,9 +456,9 @@ var init_groups = __esm({
456
456
  label: "Web",
457
457
  hint: "Pages and sections at every breakpoint",
458
458
  home: ["WEB", "KIT"],
459
- types: [t("web", "Pages", "WEB"), t("components", "Components", "WEB")],
459
+ types: [t("web", "Pages", "WEB"), t("storefront", "Storefronts", "WEB"), t("components", "Components", "WEB")],
460
460
  presets: ids((p2) => p2.category === "WEB" && !p2.id.startsWith("lottie-")),
461
- keywords: ["website", "landing", "site", "ui kit"]
461
+ keywords: ["website", "landing", "site", "ui kit", "ecommerce", "shop", "store"]
462
462
  },
463
463
  {
464
464
  id: "app",
@@ -1186,7 +1186,7 @@ function operations(spec = openapi) {
1186
1186
  }
1187
1187
  async function call(target2, method, path, opts = {}) {
1188
1188
  const qs = new URLSearchParams(opts.query ?? {}).toString();
1189
- const headers = { "user-agent": `popcraft-cli/${"0.13.2"}`, ...opts.headers };
1189
+ const headers = { "user-agent": `popcraft-cli/${"0.15.0"}`, ...opts.headers };
1190
1190
  if (target2.token) headers.authorization = `Bearer ${target2.token}`;
1191
1191
  if (opts.contentType) headers["content-type"] = opts.contentType;
1192
1192
  try {
@@ -1460,10 +1460,10 @@ function forgetToken(url4) {
1460
1460
  } catch {
1461
1461
  return false;
1462
1462
  }
1463
- const all3 = readCredentials();
1464
- if (!(url4 in all3)) return false;
1465
- delete all3[url4];
1466
- if (Object.keys(all3).length) writeCredentials(all3);
1463
+ const all4 = readCredentials();
1464
+ if (!(url4 in all4)) return false;
1465
+ delete all4[url4];
1466
+ if (Object.keys(all4).length) writeCredentials(all4);
1467
1467
  else rmSync(credentialsPath(), { force: true });
1468
1468
  return true;
1469
1469
  }
@@ -5928,7 +5928,7 @@ var init_types = __esm({
5928
5928
  "lib/document/types.ts"() {
5929
5929
  "use strict";
5930
5930
  CONTROL_KINDS = ["FORM", "INPUT", "TEXTAREA", "SELECT", "CHECKBOX", "RADIO", "BUTTON"];
5931
- INPUT_TYPES = ["text", "email", "number", "tel", "url", "search", "date", "password"];
5931
+ INPUT_TYPES = ["text", "email", "number", "tel", "url", "search", "date", "password", "hidden"];
5932
5932
  FIELD_TYPES = ["TEXT", "RICH_TEXT", "NUMBER", "BOOLEAN", "DATE", "COLOR", "MEDIA", "URL", "SLUG", "OPTION"];
5933
5933
  SEMANTIC_TAGS = ["div", "section", "header", "footer", "nav", "main", "aside", "article", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "button", "figure", "label"];
5934
5934
  INTERACTION_TRIGGERS = ["ON_CLICK", "ON_HOVER", "ON_PRESS", "ON_DRAG", "MOUSE_ENTER", "MOUSE_LEAVE", "AFTER_TIMEOUT", "KEY_DOWN", "ON_SCROLL"];
@@ -29073,10 +29073,10 @@ function paramsFromFlags(spec, flags) {
29073
29073
  if (!RESERVED.has(name2)) unknown2.push(name2);
29074
29074
  continue;
29075
29075
  }
29076
- const all3 = Array.isArray(value3) ? value3 : [value3];
29077
- const last3 = all3[all3.length - 1];
29076
+ const all4 = Array.isArray(value3) ? value3 : [value3];
29077
+ const last3 = all4[all4.length - 1];
29078
29078
  if (f2.type === "array") {
29079
- const list2 = all3.flatMap((v) => {
29079
+ const list2 = all4.flatMap((v) => {
29080
29080
  if (typeof v === "string" && v.trim().startsWith("[")) {
29081
29081
  try {
29082
29082
  return JSON.parse(v);
@@ -29504,8 +29504,8 @@ function parseNumberSkeleton(tokens) {
29504
29504
  result = {
29505
29505
  ...result,
29506
29506
  notation: "scientific",
29507
- ...token.options.reduce((all3, opt) => ({
29508
- ...all3,
29507
+ ...token.options.reduce((all4, opt) => ({
29508
+ ...all4,
29509
29509
  ...parseNotationOptions(opt)
29510
29510
  }), {})
29511
29511
  };
@@ -29514,8 +29514,8 @@ function parseNumberSkeleton(tokens) {
29514
29514
  result = {
29515
29515
  ...result,
29516
29516
  notation: "engineering",
29517
- ...token.options.reduce((all3, opt) => ({
29518
- ...all3,
29517
+ ...token.options.reduce((all4, opt) => ({
29518
+ ...all4,
29519
29519
  ...parseNotationOptions(opt)
29520
29520
  }), {})
29521
29521
  };
@@ -31663,11 +31663,11 @@ var init_icu_messageformat_parser = __esm({
31663
31663
  // node_modules/.pnpm/intl-messageformat@12.1.2/node_modules/intl-messageformat/index.js
31664
31664
  function mergeLiteral(parts) {
31665
31665
  if (parts.length < 2) return parts;
31666
- return parts.reduce((all3, part) => {
31667
- const lastPart = all3[all3.length - 1];
31668
- if (!lastPart || lastPart.type !== 0 || part.type !== 0) all3.push(part);
31666
+ return parts.reduce((all4, part) => {
31667
+ const lastPart = all4[all4.length - 1];
31668
+ if (!lastPart || lastPart.type !== 0 || part.type !== 0) all4.push(part);
31669
31669
  else lastPart.value += part.value;
31670
- return all3;
31670
+ return all4;
31671
31671
  }, []);
31672
31672
  }
31673
31673
  function isFormatXMLElementFn(el) {
@@ -31780,20 +31780,20 @@ function mergeConfig(c1, c2) {
31780
31780
  return {
31781
31781
  ...c1,
31782
31782
  ...c2,
31783
- ...Object.keys(c1).reduce((all3, k2) => {
31784
- all3[k2] = {
31783
+ ...Object.keys(c1).reduce((all4, k2) => {
31784
+ all4[k2] = {
31785
31785
  ...c1[k2],
31786
31786
  ...c2[k2]
31787
31787
  };
31788
- return all3;
31788
+ return all4;
31789
31789
  }, {})
31790
31790
  };
31791
31791
  }
31792
31792
  function mergeConfigs(defaultConfig, configs) {
31793
31793
  if (!configs) return defaultConfig;
31794
- return Object.keys(defaultConfig).reduce((all3, k2) => {
31795
- all3[k2] = mergeConfig(defaultConfig[k2], configs[k2]);
31796
- return all3;
31794
+ return Object.keys(defaultConfig).reduce((all4, k2) => {
31795
+ all4[k2] = mergeConfig(defaultConfig[k2], configs[k2]);
31796
+ return all4;
31797
31797
  }, { ...defaultConfig });
31798
31798
  }
31799
31799
  function createFastMemoizeCache(store6) {
@@ -31868,10 +31868,10 @@ var init_intl_messageformat = __esm({
31868
31868
  this.format = ((values3) => {
31869
31869
  const parts = this.formatToParts(values3);
31870
31870
  if (parts.length === 1) return parts[0].value;
31871
- const result = parts.reduce((all3, part) => {
31872
- if (!all3.length || part.type !== 0 || typeof all3[all3.length - 1] !== "string") all3.push(part.value);
31873
- else all3[all3.length - 1] += part.value;
31874
- return all3;
31871
+ const result = parts.reduce((all4, part) => {
31872
+ if (!all4.length || part.type !== 0 || typeof all4[all4.length - 1] !== "string") all4.push(part.value);
31873
+ else all4[all4.length - 1] += part.value;
31874
+ return all4;
31875
31875
  }, []);
31876
31876
  if (result.length <= 1) return result[0] || "";
31877
31877
  return result;
@@ -32502,6 +32502,7 @@ var init_en2 = __esm({
32502
32502
  "collections.no": "No",
32503
32503
  "collections.noRecords": "No records yet. Add one, or import a CSV.",
32504
32504
  "collections.noRepeat": "Does not repeat",
32505
+ "collections.offset": "Skip first",
32505
32506
  "collections.onePage": "One page",
32506
32507
  "collections.open": "{count, plural, =0 {Collections} one {Collections (#)} other {Collections (#)}}",
32507
32508
  "collections.ownOrder": "In the collection\u2019s order",
@@ -32510,10 +32511,18 @@ var init_en2 = __esm({
32510
32511
  "collections.perVisitorHint": "On the published site, a signed-in visitor sees and adds only their own records, kept in the site's database. The records here are samples the design is drawn with.",
32511
32512
  "collections.repeat": "Repeat its content for",
32512
32513
  "collections.repeatHint": "{count, plural, one {Shows # record on the page. The canvas shows the first.} other {Shows # records on the page. The canvas shows the first.}}",
32514
+ "collections.schema.commerce.cart": "The cart's totals",
32515
+ "collections.schema.commerce.cartLine": "The cart's lines",
32516
+ "collections.schema.commerce.collection": "Store categories",
32517
+ "collections.schema.commerce.menu": "The store's menu",
32518
+ "collections.schema.commerce.product": "Store products",
32513
32519
  "collections.shared": "They all see each other's",
32514
32520
  "collections.sharedHint": "Every signed-in visitor sees everyone's records, each with who added it. A visitor changes and deletes only their own.",
32515
32521
  "collections.sortBy": "Sort by",
32516
32522
  "collections.sortedBy": "Sorted by {name}",
32523
+ "collections.standsFor": "Stands for",
32524
+ "collections.standsForHint": "What the records are in a storefront: its fields become the store's, and exported as storefront components a list of it is a prop the store fills",
32525
+ "collections.standsForNothing": "Records of your own",
32517
32526
  "collections.title": "Collections",
32518
32527
  "collections.tokenHint": "Type this in a text to say the field",
32519
32528
  "collections.yes": "Yes",
@@ -32542,12 +32551,16 @@ var init_en2 = __esm({
32542
32551
  "command:export.pdf": "Export as PDF",
32543
32552
  "command:export.pngSequence": "Export animation as PNG sequence",
32544
32553
  "command:export.pptx": "Export as PowerPoint (.pptx)",
32554
+ "command:export.storefront": "Export storefront\u2026",
32555
+ "command:export.storefront#next-commerce": "Export storefront as Next.js Commerce components",
32556
+ "command:export.storefront#shopify-theme": "Export storefront as a Shopify theme",
32545
32557
  "command:export.web": "Export as web code\u2026",
32546
32558
  "command:export.webm": "Export animation as WebM",
32547
32559
  "command:file.duplicate": "Duplicate file",
32548
32560
  "command:generate.video": "Generate video",
32549
32561
  "command:history.redo": "Redo",
32550
32562
  "command:history.undo": "Undo",
32563
+ "command:integration.pullProducts": "Draw with my store's products",
32551
32564
  "command:media.download": "Download",
32552
32565
  "command:motion.addAudio": "Add audio",
32553
32566
  "command:motion.applyPreset": "Animate",
@@ -32898,17 +32911,23 @@ var init_en2 = __esm({
32898
32911
  "controlFields.required": "Required",
32899
32912
  "controlFields.reset": "Clears the form",
32900
32913
  "controlFields.sending": "Sending it",
32914
+ "controlFields.sendingAddToCart": "Adds the product to the cart",
32901
32915
  "controlFields.sendingChangePassword": "Changes the visitor's password",
32916
+ "controlFields.sendingCheckout": "Goes to checkout",
32902
32917
  "controlFields.sendingDelete": "Deletes its record",
32903
32918
  "controlFields.sendingDeleteAccount": "Deletes the visitor's account",
32904
32919
  "controlFields.sendingForgotPassword": "Emails a password reset link",
32920
+ "controlFields.sendingRemoveCartLine": "Removes a line from the cart",
32905
32921
  "controlFields.sendingResetPassword": "Sets a new password from a reset link",
32906
32922
  "controlFields.sendingSaves": "Saves what was entered",
32923
+ "controlFields.sendingSearch": "Searches the store",
32907
32924
  "controlFields.sendingSignIn": "Signs the visitor in",
32908
32925
  "controlFields.sendingSignOut": "Signs the visitor out",
32909
32926
  "controlFields.sendingSignUp": "Creates an account",
32927
+ "controlFields.sendingStore": "Storefront",
32910
32928
  "controlFields.sendingUpdate": "Changes its record",
32911
32929
  "controlFields.sendingUpdateAccount": "Changes the visitor's name",
32930
+ "controlFields.sendingUpdateCartLine": "Changes a cart line's quantity",
32912
32931
  "controlFields.stayHere": "Stay on the page",
32913
32932
  "controlFields.submit": "Sends the form",
32914
32933
  "controlFields.turnOn": "Turn on {name}",
@@ -33789,6 +33808,7 @@ var init_en2 = __esm({
33789
33808
  "integrationsDialog.adminsOnly": "Only owners and admins of this team connect and disconnect accounts.",
33790
33809
  "integrationsDialog.cancel": "Cancel",
33791
33810
  "integrationsDialog.capabilityAuth": "Sign-in",
33811
+ "integrationsDialog.capabilityCommerce": "Store",
33792
33812
  "integrationsDialog.capabilityDatabase": "Database",
33793
33813
  "integrationsDialog.capabilityDeploy": "Hosting",
33794
33814
  "integrationsDialog.capabilityRepo": "Code",
@@ -35680,6 +35700,7 @@ var init_en2 = __esm({
35680
35700
  "taskCluster.starting": "starting\u2026",
35681
35701
  "taskCluster.subagent": "subagent",
35682
35702
  "templateGallery.animated": "Animated",
35703
+ "templateGallery.exportsAsStorefront": "Exports as a storefront: {store}",
35683
35704
  "templateGallery.featured": "Featured",
35684
35705
  "templateGallery.featuredTitle": "Featured by PopCraft: shown first",
35685
35706
  "templateGallery.lottie": "Lottie",
@@ -35738,6 +35759,7 @@ var init_en2 = __esm({
35738
35759
  "templateType:resume": "Resume",
35739
35760
  "templateType:social": "Social",
35740
35761
  "templateType:sticker": "Sticker",
35762
+ "templateType:storefront": "Storefronts",
35741
35763
  "templateType:text-gif": "Text GIF",
35742
35764
  "templateType:web": "Pages",
35743
35765
  "textAnimatorPanel.afterTheFigureK": "After the figure (%, k, +\u2026)",
@@ -92127,6 +92149,39 @@ var init_editability = __esm({
92127
92149
  }
92128
92150
  });
92129
92151
 
92152
+ // lib/server/media-types.ts
92153
+ function sniffMime(bytes2, fallback2) {
92154
+ if (bytes2[0] === 137 && bytes2[1] === 80) return "image/png";
92155
+ if (bytes2[0] === 255 && bytes2[1] === 216) return "image/jpeg";
92156
+ if (ascii(bytes2, 0, "RIFF") && ascii(bytes2, 8, "WAVE")) return "audio/wav";
92157
+ if (bytes2[0] === 82 && bytes2[1] === 73 && bytes2[8] === 87) return "image/webp";
92158
+ if (ascii(bytes2, 0, "GIF8")) return "image/gif";
92159
+ if (ascii(bytes2, 4, "ftyp")) {
92160
+ const brand = String.fromCharCode(...bytes2.subarray(8, 12));
92161
+ if (brand === "avif" || brand === "avis") return "image/avif";
92162
+ if (brand === "qt ") return "video/quicktime";
92163
+ return "video/mp4";
92164
+ }
92165
+ if (bytes2[0] === 26 && bytes2[1] === 69 && bytes2[2] === 223 && bytes2[3] === 163) return "video/webm";
92166
+ if (ascii(bytes2, 0, "OggS")) return "audio/ogg";
92167
+ if (ascii(bytes2, 0, "ID3") || bytes2[0] === 255 && (bytes2[1] & 230) === 226) return "audio/mpeg";
92168
+ const head2 = new TextDecoder().decode(bytes2.slice(0, 512)).trimStart().toLowerCase();
92169
+ if (head2.startsWith("<svg") || head2.startsWith("<?xml") && head2.includes("<svg")) return "image/svg+xml";
92170
+ return fallback2;
92171
+ }
92172
+ var MAX_MEDIA_BYTES, ascii;
92173
+ var init_media_types = __esm({
92174
+ "lib/server/media-types.ts"() {
92175
+ "use strict";
92176
+ MAX_MEDIA_BYTES = 100 * 1024 * 1024;
92177
+ ascii = (bytes2, at3, s) => {
92178
+ if (bytes2.length < at3 + s.length) return false;
92179
+ for (let i2 = 0; i2 < s.length; i2++) if (bytes2[at3 + i2] !== s.charCodeAt(i2)) return false;
92180
+ return true;
92181
+ };
92182
+ }
92183
+ });
92184
+
92130
92185
  // packages/cli/src/shims/idb-keyval.ts
92131
92186
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "node:fs";
92132
92187
  import { join as join2 } from "node:path";
@@ -92565,7 +92620,8 @@ async function mediaToDataUrl(ref3) {
92565
92620
  const bytes2 = new Uint8Array(await blob.arrayBuffer());
92566
92621
  let bin = "";
92567
92622
  for (let i2 = 0; i2 < bytes2.length; i2 += 32768) bin += String.fromCharCode(...bytes2.subarray(i2, i2 + 32768));
92568
- return `data:${blob.type || "application/octet-stream"};base64,${btoa(bin)}`;
92623
+ const type = blob.type && blob.type !== "application/octet-stream" ? blob.type : sniffMime(bytes2, "application/octet-stream");
92624
+ return `data:${type};base64,${btoa(bin)}`;
92569
92625
  }
92570
92626
  function mapNodeImageUrls(node2, fn) {
92571
92627
  let out = null;
@@ -92688,6 +92744,7 @@ var key2, peaksKey, objectUrls, sourceKey, remoteResolver, MEDIA_FETCH_TIMEOUT_M
92688
92744
  var init_media_store = __esm({
92689
92745
  "lib/storage/media-store.ts"() {
92690
92746
  "use strict";
92747
+ init_media_types();
92691
92748
  init_idb();
92692
92749
  init_media_hash();
92693
92750
  init_types2();
@@ -96009,8 +96066,8 @@ function setOnsetMarkers(comp, times2) {
96009
96066
  return bare;
96010
96067
  }
96011
96068
  function cutToBeat(comp, opts = {}) {
96012
- const every2 = Math.max(1, Math.round(opts.every ?? 1)), minScene = Math.max(1, opts.minScene ?? 250);
96013
- const beats = onsetTimes(comp).filter((_, k2) => k2 % every2 === 0);
96069
+ const every3 = Math.max(1, Math.round(opts.every ?? 1)), minScene = Math.max(1, opts.minScene ?? 250);
96070
+ const beats = onsetTimes(comp).filter((_, k2) => k2 % every3 === 0);
96014
96071
  const cuts = [];
96015
96072
  let out = comp, last3 = -Infinity;
96016
96073
  for (let i2 = 0; i2 + 1 < out.clips.length; i2++) {
@@ -97170,9 +97227,9 @@ function applyActions(sm, values3, actions) {
97170
97227
  return { values: out, fired };
97171
97228
  }
97172
97229
  function nextTransition(sm, run4, fired) {
97173
- const all3 = sortedValues(sm.transitions);
97174
- const own2 = all3.filter((t3) => t3.from === run4.state);
97175
- const any4 = all3.filter((t3) => t3.from === ANY_STATE && t3.to !== run4.state);
97230
+ const all4 = sortedValues(sm.transitions);
97231
+ const own2 = all4.filter((t3) => t3.from === run4.state);
97232
+ const any4 = all4.filter((t3) => t3.from === ANY_STATE && t3.to !== run4.state);
97176
97233
  for (const t3 of [...own2, ...any4]) {
97177
97234
  if (!sm.states[t3.to]) continue;
97178
97235
  if ((t3.guards ?? []).every((g) => guardHolds(sm, g, run4.values, fired))) return t3;
@@ -239981,8 +240038,8 @@ function googleCssUrl(family, italic, weight, withAxes = true, wghtRange = "100.
239981
240038
  return `https://fonts.googleapis.com/css2?family=${fam}:${tags}@${tuple2}&display=swap`;
239982
240039
  }
239983
240040
  function pickWoff2FromCss(css, italic) {
239984
- const blocks = css.split("@font-face").slice(1).map((b, i2, all3) => {
239985
- const prev = i2 === 0 ? css.split("@font-face")[0] : all3[i2 - 1];
240041
+ const blocks = css.split("@font-face").slice(1).map((b, i2, all4) => {
240042
+ const prev = i2 === 0 ? css.split("@font-face")[0] : all4[i2 - 1];
239986
240043
  const subset = /\/\*\s*([\w-]+)\s*\*\/\s*$/.exec(prev)?.[1] ?? "";
239987
240044
  return { body: b, subset };
239988
240045
  });
@@ -248378,11 +248435,11 @@ function createPixelSlice(set4, get4) {
248378
248435
  const n = l?.parentId ? node2(l.parentId) : null;
248379
248436
  if (!l || !n) return;
248380
248437
  const fid = n.frames[clampFrame(n, frameIndex)].id;
248381
- const all3 = n.frames.map((f2) => f2.id);
248382
- const ids2 = frameIdsOf(l) ?? all3;
248438
+ const all4 = n.frames.map((f2) => f2.id);
248439
+ const ids2 = frameIdsOf(l) ?? all4;
248383
248440
  let next2 = ids2.includes(fid) ? ids2.filter((x2) => x2 !== fid) : [...ids2, fid];
248384
- next2 = all3.filter((x2) => next2.includes(x2));
248385
- commitDoc(updateNode(doc2, layerId2, { pixelFrameIds: next2.length === all3.length ? void 0 : next2 }), "Layer frames");
248441
+ next2 = all4.filter((x2) => next2.includes(x2));
248442
+ commitDoc(updateNode(doc2, layerId2, { pixelFrameIds: next2.length === all4.length ? void 0 : next2 }), "Layer frames");
248386
248443
  },
248387
248444
  /** Show a layer in every frame. */
248388
248445
  pixelShowLayerInAllFrames: (layerId2) => {
@@ -248444,9 +248501,9 @@ function createPixelSlice(set4, get4) {
248444
248501
  const count2 = n.pixelWidth * n.pixelHeight;
248445
248502
  let palette2 = n.palette;
248446
248503
  if (mode === "INDEXED" && palette2.length < 2) {
248447
- const all3 = new Uint32Array(count2 * n.frames.length);
248448
- n.frames.forEach((_, i2) => all3.set(frameRGBA(n, i2), i2 * count2));
248449
- palette2 = extractPalette(all3, 256).map(packedToHex);
248504
+ const all4 = new Uint32Array(count2 * n.frames.length);
248505
+ n.frames.forEach((_, i2) => all4.set(frameRGBA(n, i2), i2 * count2));
248506
+ palette2 = extractPalette(all4, 256).map(packedToHex);
248450
248507
  }
248451
248508
  const conv = (b64, cellCount) => encodeCels(convertCels(decodeCels(b64, cellCount), n.colorMode, mode, n.palette, palette2, n.transparentIndex));
248452
248509
  const patch = { colorMode: mode, palette: palette2 };
@@ -250007,6 +250064,7 @@ __export(apply_exports2, {
250007
250064
  insertFragment: () => insertFragment,
250008
250065
  insertRootGroups: () => insertRootGroups,
250009
250066
  insertRoots: () => insertRoots,
250067
+ ownBox: () => ownBox,
250010
250068
  variantOf: () => variantOf
250011
250069
  });
250012
250070
  function freshIdMap(t3) {
@@ -250076,10 +250134,10 @@ function insertRoots(doc2, t3, rootIds, parentId, ox, oy) {
250076
250134
  }
250077
250135
  function insertRootGroups(doc2, t3, groups, parentId) {
250078
250136
  const map2 = freshIdMap(t3);
250079
- const all3 = groups.flatMap((g) => g.rootIds);
250080
- let document2 = mergeLibraryNodes(mergeRecords(doc2, t3, map2, new Set(all3)), t3);
250137
+ const all4 = groups.flatMap((g) => g.rootIds);
250138
+ let document2 = mergeLibraryNodes(mergeRecords(doc2, t3, map2, new Set(all4)), t3);
250081
250139
  for (const g of groups) for (const rid of g.rootIds) document2 = addTree(document2, t3, map2, rid, parentId, g.ox, g.oy);
250082
- return { document: document2, rootIds: all3.map((r) => map2.get(r)) };
250140
+ return { document: document2, rootIds: all4.map((r) => map2.get(r)) };
250083
250141
  }
250084
250142
  function documentFromTemplate(t3, name2 = t3.meta.name, sizeId) {
250085
250143
  const chosen = variantOf(t3, sizeId);
@@ -250111,7 +250169,7 @@ function documentFromTemplate(t3, name2 = t3.meta.name, sizeId) {
250111
250169
  });
250112
250170
  return document2;
250113
250171
  }
250114
- var variantOf;
250172
+ var variantOf, ownBox;
250115
250173
  var init_apply2 = __esm({
250116
250174
  "lib/templates/apply.ts"() {
250117
250175
  "use strict";
@@ -250121,6 +250179,7 @@ var init_apply2 = __esm({
250121
250179
  init_page_presets();
250122
250180
  init_print_pages();
250123
250181
  variantOf = (t3, sizeId) => sizeId && t3.payload.variants.find((v) => v.sizeId === sizeId) || t3.payload.variants[0];
250182
+ ownBox = (t3, root2) => t3.meta.kind === "PRESENTATION" || t3.meta.kind === "WEB" && root2.height > root2.width * 1.5;
250124
250183
  }
250125
250184
  });
250126
250185
 
@@ -251022,39 +251081,6 @@ var init_collapse_duplicates = __esm({
251022
251081
  }
251023
251082
  });
251024
251083
 
251025
- // lib/server/media-types.ts
251026
- function sniffMime(bytes2, fallback2) {
251027
- if (bytes2[0] === 137 && bytes2[1] === 80) return "image/png";
251028
- if (bytes2[0] === 255 && bytes2[1] === 216) return "image/jpeg";
251029
- if (ascii(bytes2, 0, "RIFF") && ascii(bytes2, 8, "WAVE")) return "audio/wav";
251030
- if (bytes2[0] === 82 && bytes2[1] === 73 && bytes2[8] === 87) return "image/webp";
251031
- if (ascii(bytes2, 0, "GIF8")) return "image/gif";
251032
- if (ascii(bytes2, 4, "ftyp")) {
251033
- const brand = String.fromCharCode(...bytes2.subarray(8, 12));
251034
- if (brand === "avif" || brand === "avis") return "image/avif";
251035
- if (brand === "qt ") return "video/quicktime";
251036
- return "video/mp4";
251037
- }
251038
- if (bytes2[0] === 26 && bytes2[1] === 69 && bytes2[2] === 223 && bytes2[3] === 163) return "video/webm";
251039
- if (ascii(bytes2, 0, "OggS")) return "audio/ogg";
251040
- if (ascii(bytes2, 0, "ID3") || bytes2[0] === 255 && (bytes2[1] & 230) === 226) return "audio/mpeg";
251041
- const head2 = new TextDecoder().decode(bytes2.slice(0, 512)).trimStart().toLowerCase();
251042
- if (head2.startsWith("<svg") || head2.startsWith("<?xml") && head2.includes("<svg")) return "image/svg+xml";
251043
- return fallback2;
251044
- }
251045
- var MAX_MEDIA_BYTES, ascii;
251046
- var init_media_types = __esm({
251047
- "lib/server/media-types.ts"() {
251048
- "use strict";
251049
- MAX_MEDIA_BYTES = 100 * 1024 * 1024;
251050
- ascii = (bytes2, at3, s) => {
251051
- if (bytes2.length < at3 + s.length) return false;
251052
- for (let i2 = 0; i2 < s.length; i2++) if (bytes2[at3 + i2] !== s.charCodeAt(i2)) return false;
251053
- return true;
251054
- };
251055
- }
251056
- });
251057
-
251058
251084
  // lib/storage/document-library.ts
251059
251085
  var document_library_exports = {};
251060
251086
  __export(document_library_exports, {
@@ -251595,7 +251621,7 @@ function editCounter(node2, patch) {
251595
251621
  let motion = node2.motion;
251596
251622
  const track2 = motion?.tracks[COUNTER_PATH];
251597
251623
  if (track2?.keys.length && (patch.from !== void 0 || patch.to !== void 0)) {
251598
- const keys4 = track2.keys.map((k2, i2, all3) => i2 === 0 && patch.from !== void 0 ? { ...k2, v: next2.from } : i2 === all3.length - 1 && patch.to !== void 0 ? { ...k2, v: next2.to } : k2);
251624
+ const keys4 = track2.keys.map((k2, i2, all4) => i2 === 0 && patch.from !== void 0 ? { ...k2, v: next2.from } : i2 === all4.length - 1 && patch.to !== void 0 ? { ...k2, v: next2.to } : k2);
251599
251625
  motion = withTrack(motion, COUNTER_PATH, { ...track2, keys: keys4 });
251600
251626
  }
251601
251627
  return { ...withCounter(node2, next2), motion };
@@ -254192,10 +254218,12 @@ function createMotionSlice(set4, get4) {
254192
254218
  transport: DEFAULT_TRANSPORT,
254193
254219
  motionGesture: null,
254194
254220
  keySelection: [],
254195
- setMotionTarget: (target2) => {
254221
+ setMotionTarget: (target2, at3 = "start") => {
254196
254222
  const tr = get4().transport;
254197
254223
  if (sameTarget(tr.target, target2)) return;
254198
- set4({ transport: { ...tr, target: target2, time: 0, playing: false, playAnchor: null, fps: fpsOf(target2, tr.fps) }, keySelection: [] });
254224
+ const fid = at3 === "end" ? targetFrameId(target2) : null;
254225
+ const time4 = fid ? Math.max(0, timelineDuration(get4().document.nodes, fid) - 1) : 0;
254226
+ set4({ transport: { ...tr, target: target2, time: time4, playing: false, playAnchor: null, fps: fpsOf(target2, tr.fps) }, keySelection: [] });
254199
254227
  },
254200
254228
  editComposition: (id3, fn, description) => {
254201
254229
  const s = get4();
@@ -254467,9 +254495,9 @@ function breakpointsOf(doc2) {
254467
254495
  return [base, ...rest];
254468
254496
  }
254469
254497
  function chain2(doc2, id3) {
254470
- const all3 = breakpointsOf(doc2);
254471
- const at3 = all3.findIndex((b) => b.id === id3);
254472
- return at3 > 0 ? all3.slice(1, at3 + 1) : [];
254498
+ const all4 = breakpointsOf(doc2);
254499
+ const at3 = all4.findIndex((b) => b.id === id3);
254500
+ return at3 > 0 ? all4.slice(1, at3 + 1) : [];
254473
254501
  }
254474
254502
  function overridesAt(node2, doc2, id3) {
254475
254503
  if (!node2.responsive) return null;
@@ -254741,6 +254769,35 @@ var init_contract = __esm({
254741
254769
  });
254742
254770
 
254743
254771
  // lib/document/collections.ts
254772
+ var collections_exports = {};
254773
+ __export(collections_exports, {
254774
+ COLLECTION_LIMITS: () => COLLECTION_LIMITS,
254775
+ ITEM_RECORD: () => ITEM_RECORD,
254776
+ copyRecord: () => copyRecord,
254777
+ copySuffix: () => copySuffix,
254778
+ exprData: () => exprData,
254779
+ fieldKey: () => fieldKey,
254780
+ fieldText: () => fieldText,
254781
+ hasItemToken: () => hasItemToken,
254782
+ itemMark: () => itemMark,
254783
+ itemMarks: () => itemMarks,
254784
+ itemNode: () => itemNode,
254785
+ itemScope: () => itemScope,
254786
+ itemText: () => itemText,
254787
+ itemValues: () => itemValues,
254788
+ layerId: () => layerId,
254789
+ recordSlugs: () => recordSlugs,
254790
+ recordsIn: () => recordsIn,
254791
+ removeCollection: () => removeCollection,
254792
+ removeRecords: () => removeRecords,
254793
+ repeatRecords: () => repeatRecords,
254794
+ setCollection: () => setCollection,
254795
+ shownRecordOf: () => shownRecordOf,
254796
+ slugify: () => slugify2,
254797
+ templateRecord: () => templateRecord,
254798
+ upsertRecords: () => upsertRecords,
254799
+ withData: () => withData
254800
+ });
254744
254801
  function recordsIn(doc2, collectionId) {
254745
254802
  const c2 = doc2.collections?.[collectionId];
254746
254803
  if (!c2) return [];
@@ -255998,6 +256055,7 @@ function activeTeamWorkspaceId() {
255998
256055
  var STORAGE_KEY2, useHyperyTeam, useTeamPrompt;
255999
256056
  var init_team = __esm({
256000
256057
  "lib/client/cloud/team.ts"() {
256058
+ "use strict";
256001
256059
  init_esm();
256002
256060
  init_api4();
256003
256061
  init_session();
@@ -265427,12 +265485,12 @@ var init_client = __esm({
265427
265485
 
265428
265486
  // lib/collab/private.ts
265429
265487
  function privateNodeIds(nodes, userId) {
265430
- const all3 = allPrivateIds(nodes);
265431
- if (!userId) return all3;
265488
+ const all4 = allPrivateIds(nodes);
265489
+ if (!userId) return all4;
265432
265490
  if (nodes === privateIdsFor && userId === privateIdsOwner) return privateIdsCache;
265433
265491
  privateIdsFor = nodes;
265434
265492
  privateIdsOwner = userId;
265435
- privateIdsCache = all3.filter((id3) => nodes[id3].privateTo === userId);
265493
+ privateIdsCache = all4.filter((id3) => nodes[id3].privateTo === userId);
265436
265494
  return privateIdsCache;
265437
265495
  }
265438
265496
  function withoutPrivate(doc2) {
@@ -266147,7 +266205,7 @@ var init_vocabulary = __esm({
266147
266205
  TEMPLATE_CATEGORIES = Object.fromEntries(
266148
266206
  ["SOCIAL", "PRESENTATION", "WEB", "APP", "EMAIL", "BOARD", "CANVAS", "PRINT"].map((kind) => [kind, typesForKind(kind).map((t3) => t3.id)])
266149
266207
  );
266150
- FACET_KEYS = ["animated", "durationMs", "fps", "platforms", "formats", "useCases", "lottie", "threeD", "featured", "pinned"];
266208
+ FACET_KEYS = ["animated", "durationMs", "fps", "platforms", "formats", "useCases", "lottie", "threeD", "storefronts", "featured", "pinned"];
266151
266209
  }
266152
266210
  });
266153
266211
 
@@ -266213,9 +266271,9 @@ var require_gifenc = __commonJS({
266213
266271
  "node_modules/.pnpm/gifenc@1.0.3/node_modules/gifenc/dist/gifenc.js"(exports) {
266214
266272
  var __defProp2 = Object.defineProperty;
266215
266273
  var __markAsModule = (target2) => __defProp2(target2, "__esModule", { value: true });
266216
- var __export2 = (target2, all3) => {
266217
- for (var name2 in all3)
266218
- __defProp2(target2, name2, { get: all3[name2], enumerable: true });
266274
+ var __export2 = (target2, all4) => {
266275
+ for (var name2 in all4)
266276
+ __defProp2(target2, name2, { get: all4[name2], enumerable: true });
266219
266277
  };
266220
266278
  __markAsModule(exports);
266221
266279
  __export2(exports, {
@@ -290428,7 +290486,7 @@ var init_input_track = __esm({
290428
290486
  sortBy: queryA?.sortBy || queryB?.sortBy ? (track2) => {
290429
290487
  const resultA = queryA?.sortBy?.(track2) ?? [];
290430
290488
  const resultB = queryB?.sortBy?.(track2) ?? [];
290431
- const join12 = (resultA2, resultB2) => {
290489
+ const join13 = (resultA2, resultB2) => {
290432
290490
  return [
290433
290491
  ...Array.isArray(resultA2) ? resultA2 : [resultA2],
290434
290492
  ...Array.isArray(resultB2) ? resultB2 : [resultB2]
@@ -290436,10 +290494,10 @@ var init_input_track = __esm({
290436
290494
  };
290437
290495
  if (isThenable(resultA) || isThenable(resultB)) {
290438
290496
  return Promise.all([resultA, resultB]).then(([resultA2, resultB2]) => {
290439
- return join12(resultA2, resultB2);
290497
+ return join13(resultA2, resultB2);
290440
290498
  });
290441
290499
  } else {
290442
- return join12(resultA, resultB);
290500
+ return join13(resultA, resultB);
290443
290501
  }
290444
290502
  } : void 0
290445
290503
  };
@@ -303263,7 +303321,7 @@ function thumbnailRegion(t3, sizeId) {
303263
303321
  const sheets = v.rootIds.filter((id3) => !nodes[id3]?.print?.isMaster);
303264
303322
  const root2 = nodes[sheets[0] ?? v.rootIds[0]];
303265
303323
  const x2 = root2?.x ?? 0, y = root2?.y ?? 0;
303266
- const box2 = t3.meta.kind === "PRESENTATION" && root2 ? { width: root2.width, height: root2.height } : { width: v.width, height: v.height };
303324
+ const box2 = root2 && ownBox(t3, root2) ? { width: root2.width, height: root2.height } : { width: v.width, height: v.height };
303267
303325
  return { minX: x2, minY: y, maxX: x2 + box2.width, maxY: y + box2.height };
303268
303326
  }
303269
303327
  function flattenOnWhite(px5) {
@@ -304583,7 +304641,6 @@ function downloadBlob(blob, filename) {
304583
304641
  }
304584
304642
  var init_download = __esm({
304585
304643
  "lib/io/download.ts"() {
304586
- "use strict";
304587
304644
  }
304588
304645
  });
304589
304646
 
@@ -304801,7 +304858,6 @@ function creditLine(c2) {
304801
304858
  var MAX_PHOTO_PAGE, MAX_PHOTO_QUERY, UUID, isPhotoId, photoImagePath;
304802
304859
  var init_stock = __esm({
304803
304860
  "lib/photos/stock.ts"() {
304804
- "use strict";
304805
304861
  MAX_PHOTO_PAGE = 12;
304806
304862
  MAX_PHOTO_QUERY = 200;
304807
304863
  UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -307476,8 +307532,8 @@ function lineItems(node2, ctx2, acc) {
307476
307532
  const items = [];
307477
307533
  if (visual) {
307478
307534
  const cmds = visual.map((p2, i2) => ({ op: i2 ? "L" : "M", x: p2.x, y: p2.y }));
307479
- const all3 = (strokes.length ? strokes : [stroke3]).map(toIRStroke).filter((s) => !!s);
307480
- items.push({ t: "path", cmds, fillRule: "nonzero", fills: [], strokes: [...all3].reverse() });
307535
+ const all4 = (strokes.length ? strokes : [stroke3]).map(toIRStroke).filter((s) => !!s);
307536
+ items.push({ t: "path", cmds, fillRule: "nonzero", fills: [], strokes: [...all4].reverse() });
307481
307537
  }
307482
307538
  const headAlpha = strokeAlpha(stroke3);
307483
307539
  const head2 = (type, tip, angle2) => {
@@ -309814,6 +309870,7 @@ async function exportSettingFile(renderer, node2, setting, doc2, styles) {
309814
309870
  var MIME, DEFAULT_EXPORT, exportSettingsOf;
309815
309871
  var init_export = __esm({
309816
309872
  "lib/io/export.ts"() {
309873
+ "use strict";
309817
309874
  init_download();
309818
309875
  init_scene_graph();
309819
309876
  init_codecs();
@@ -309905,6 +309962,7 @@ async function selectionReferenceImages(doc2, selection, opts = {}) {
309905
309962
  var FIT_QUALITIES, FIT_SCALES, blobToDataUrl;
309906
309963
  var init_reference_images = __esm({
309907
309964
  "lib/ai/reference-images.ts"() {
309965
+ "use strict";
309908
309966
  init_export();
309909
309967
  init_selection_bounds();
309910
309968
  init_renderer_registry();
@@ -314584,6 +314642,27 @@ var init_vector = __esm({
314584
314642
  }
314585
314643
  });
314586
314644
 
314645
+ // lib/io/web/project/storefront-targets.ts
314646
+ var STOREFRONT_TARGET_INFO, STOREFRONT_TARGET_IDS;
314647
+ var init_storefront_targets = __esm({
314648
+ "lib/io/web/project/storefront-targets.ts"() {
314649
+ "use strict";
314650
+ STOREFRONT_TARGET_INFO = {
314651
+ "next-commerce": {
314652
+ label: "Next.js Commerce components",
314653
+ short: "Next.js Commerce",
314654
+ description: "React components a Next.js Commerce store renders with its own Shopify data and server actions."
314655
+ },
314656
+ "shopify-theme": {
314657
+ label: "Shopify theme",
314658
+ short: "Shopify",
314659
+ description: "An Online Store 2.0 theme to upload in Shopify: Online Store \u203A Themes \u203A Add theme \u203A Upload zip file."
314660
+ }
314661
+ };
314662
+ STOREFRONT_TARGET_IDS = Object.keys(STOREFRONT_TARGET_INFO);
314663
+ }
314664
+ });
314665
+
314587
314666
  // lib/motion/video-edit.ts
314588
314667
  function clipOr(doc2, id3, frameId) {
314589
314668
  const c2 = videoClipOf(doc2.nodes, id3, frameId, timelineDuration(doc2.nodes, frameId));
@@ -314818,8 +314897,8 @@ function splitAudioClip(holder, clipId, at3) {
314818
314897
  return { holder: withAudio(made.composition, placeAudio(placeAudio(others, left), right)), id: made.id };
314819
314898
  }
314820
314899
  function crossfadeAudio(holder, aId, bId, ms2, sourceMs) {
314821
- const all3 = holder.audio ?? [];
314822
- let a = all3.find((c2) => c2.id === aId), b = all3.find((c2) => c2.id === bId);
314900
+ const all4 = holder.audio ?? [];
314901
+ let a = all4.find((c2) => c2.id === aId), b = all4.find((c2) => c2.id === bId);
314823
314902
  if (!a || !b || a.id === b.id) throw new Error("Pick two different audio clips to crossfade.");
314824
314903
  if (b.start < a.start) [a, b] = [b, a];
314825
314904
  const d = Math.max(1, Math.round(ms2));
@@ -315670,17 +315749,17 @@ function exactKeys(src, grid, samples, present2) {
315670
315749
  }
315671
315750
  const b = keys4[j + 1];
315672
315751
  const eases = moving.map((d) => cpOf(d, j));
315673
- const all3 = (kind) => eases.every((e) => e.kind === kind);
315752
+ const all4 = (kind) => eases.every((e) => e.kind === kind);
315674
315753
  const cps = (sel) => src.dims.map((s, d) => s ? sel(eases[moving.indexOf(d)]) : LINEAR_CP);
315675
- if (all3("hold")) {
315754
+ if (all4("hold")) {
315676
315755
  push2(a.t, keyValue(j), "hold");
315677
315756
  continue;
315678
315757
  }
315679
- if (all3("curve")) {
315758
+ if (all4("curve")) {
315680
315759
  push2(a.t, keyValue(j), cps((e) => e.kind === "curve" ? e.cp : LINEAR_CP));
315681
315760
  continue;
315682
315761
  }
315683
- if (all3("halves")) {
315762
+ if (all4("halves")) {
315684
315763
  const va = keyValue(j), vb = keyValue(j + 1);
315685
315764
  push2(a.t, va, cps((e) => e.kind === "halves" ? e.first : LINEAR_CP));
315686
315765
  push2((a.t + b.t) / 2, va.map((v, d) => (v + vb[d]) / 2), cps((e) => e.kind === "halves" ? e.second : LINEAR_CP));
@@ -315869,12 +315948,12 @@ function uniqueName(name2, taken) {
315869
315948
  function lottieMarkers(timeline, range3) {
315870
315949
  const markers = [];
315871
315950
  const names = /* @__PURE__ */ new Map();
315872
- const all3 = [...timeline?.markers ?? []].sort((a, b) => a.t - b.t);
315951
+ const all4 = [...timeline?.markers ?? []].sort((a, b) => a.t - b.t);
315873
315952
  const taken = /* @__PURE__ */ new Set();
315874
315953
  const frames = (ms2) => (ms2 - range3.start) * range3.fps / 1e3;
315875
- all3.forEach((m, i2) => {
315954
+ all4.forEach((m, i2) => {
315876
315955
  if (m.t < range3.start || m.t > range3.end) return;
315877
- const next2 = all3.find((n) => n.t > m.t && n.kind !== "pause")?.t ?? range3.end;
315956
+ const next2 = all4.find((n) => n.t > m.t && n.kind !== "pause")?.t ?? range3.end;
315878
315957
  const cm = uniqueName(m.label || `Marker ${i2 + 1}`, taken);
315879
315958
  names.set(m.id, cm);
315880
315959
  markers.push({ tm: frames(m.t), cm, dr: Math.max(0, frames(Math.min(next2, range3.end)) - frames(m.t)) });
@@ -317976,7 +318055,8 @@ var init_naming = __esm({
317976
318055
  "use strict";
317977
318056
  classNameFor = (node2) => {
317978
318057
  const base = (node2.name || node2.type).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "node";
317979
- return `${base}-${node2.id.replace(/[^a-zA-Z0-9]/g, "").slice(-6)}`;
318058
+ const id3 = node2.id.includes("~__pcitem__") ? node2.id.split("~")[0] : node2.id;
318059
+ return `${base}-${id3.replace(/[^a-zA-Z0-9]/g, "").slice(-6)}`;
317980
318060
  };
317981
318061
  componentName = (name2) => (name2.replace(/[^a-zA-Z0-9]/g, "") || "Node").replace(/^[0-9]/, "N$&");
317982
318062
  fileBase = (name2) => (name2 || "export").replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-|-$/g, "") || "export";
@@ -317989,11 +318069,11 @@ function codeSyntaxFor(v, platform4) {
317989
318069
  if (explicit) return explicit;
317990
318070
  const parts = v.name.split("/").map((p2) => p2.trim()).filter(Boolean);
317991
318071
  if (platform4 === "WEB") return `--${parts.join("-").toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`;
317992
- const camel3 = parts.map((p2, i2) => {
318072
+ const camel4 = parts.map((p2, i2) => {
317993
318073
  const w = p2.replace(/[^A-Za-z0-9]+/g, " ").trim().split(/\s+/);
317994
318074
  return w.map((x2, j) => i2 === 0 && j === 0 ? x2.toLowerCase() : x2[0].toUpperCase() + x2.slice(1).toLowerCase()).join("");
317995
318075
  }).join("");
317996
- return platform4 === "iOS" ? `Tokens.${camel3}` : `Tokens.${camel3}`;
318076
+ return platform4 === "iOS" ? `Tokens.${camel4}` : `Tokens.${camel4}`;
317997
318077
  }
317998
318078
  var init_dev_mode = __esm({
317999
318079
  "lib/document/dev-mode.ts"() {
@@ -318130,10 +318210,12 @@ function serializeHTML(tree, opts = {}) {
318130
318210
  }
318131
318211
  } else {
318132
318212
  const cls = classNameFor(node2);
318213
+ const own2 = Object.fromEntries(Object.entries(style).filter(([, v]) => v.includes("{{")));
318214
+ const shared = Object.fromEntries(Object.entries(style).filter(([, v]) => !v.includes("{{")));
318133
318215
  rules.push(`.${cls} {
318134
- ${styleMapToCSSText(style, " ")}
318216
+ ${styleMapToCSSText(shared, " ")}
318135
318217
  }`);
318136
- attrs = ` class="${cls}"`;
318218
+ attrs = ` class="${cls}"${Object.keys(own2).length ? ` style="${styleMapToInline(own2)}"` : ""}`;
318137
318219
  if (el.keyframes) {
318138
318220
  if (!keyframes.includes(el.keyframes)) keyframes.push(el.keyframes);
318139
318221
  attrs += " data-motion-child";
@@ -318143,7 +318225,8 @@ ${styleMapToCSSText(style, " ")}
318143
318225
  triggered = true;
318144
318226
  }
318145
318227
  }
318146
- for (const [k2, v] of Object.entries(el.attrs)) attrs += ` ${k2}="${escapeHTML(v)}"`;
318228
+ const sends = node2.action ? opts.form?.(node2) : null;
318229
+ for (const [k2, v] of Object.entries({ ...el.attrs, ...sends?.attrs })) attrs += ` ${k2}="${escapeHTML(v)}"`;
318147
318230
  attrs += imageRoleAttrs(node2);
318148
318231
  if (opts.ids) attrs += ` data-pc-id="${escapeHTML(node2.id)}"`;
318149
318232
  if (machine && isRoot) attrs += ' data-sm-root=""';
@@ -318158,17 +318241,30 @@ ${indent}` : escapeHTML(el.input.value ?? "");
318158
318241
  const check2 = el.check ? `
318159
318242
  ${indent} <input${Object.entries(el.check.attrs).map(([k2, v]) => ` ${k2}="${escapeHTML(v)}"`).join("")} style="${CHECK_STYLE}" />` : "";
318160
318243
  if (el.html !== void 0) return `${indent}<${tag}${attrs}>${el.html}</${tag}>`;
318244
+ const words = el.runs ? el.runs.map(runMarkup).join("") : el.text !== void 0 ? escapeHTML(el.text) : null;
318245
+ if (words !== null && !el.picture && !node2.children.length && !node2.repeat && !check2 && !sends?.fields) return `${indent}<${tag}${attrs}>${words}</${tag}>`;
318161
318246
  const inner = [
318162
318247
  el.runs ? `
318163
318248
  ${indent} ${el.runs.map(runMarkup).join("")}` : el.text !== void 0 ? `
318164
318249
  ${indent} ${escapeHTML(el.text)}` : "",
318165
318250
  pictureMarkup(node2, el.picture, indent),
318166
- ...node2.children.map((c2) => `
318167
- ${emit8(c2, depth2 + 1, false)}`),
318251
+ ...sends?.fields ? [`
318252
+ ${indent} ${sends.fields}`] : [],
318253
+ ...listed(node2, depth2),
318168
318254
  check2
318169
318255
  ].join("");
318170
318256
  return inner ? `${indent}<${tag}${attrs}>${inner}
318171
318257
  ${indent}</${tag}>` : `${indent}<${tag}${attrs}></${tag}>`;
318258
+ };
318259
+ const listed = (node2, depth2) => {
318260
+ const loop = node2.repeat && opts.lists?.(node2);
318261
+ if (!loop || !node2.repeat) return node2.children.map((c2) => `
318262
+ ${emit8(c2, depth2 + 1, false)}`);
318263
+ const indent = " ".repeat(depth2 + 1);
318264
+ return [`
318265
+ ${indent}${loop.open}`, ...node2.repeat.template.map((c2) => `
318266
+ ${emit8(c2, depth2 + 2, false)}`), `
318267
+ ${indent}${loop.close}`];
318172
318268
  };
318173
318269
  const body = emit8(tree.root, opts.document ? 2 : 0, true);
318174
318270
  const fonts = opts.document ? fontsHref(tree) : null;
@@ -318398,6 +318494,7 @@ var init_tailwind = __esm({
318398
318494
  // lib/io/web/react.ts
318399
318495
  function serializeReact(tree, opts = {}) {
318400
318496
  const tailwind = opts.styling === "tailwind";
318497
+ const reader = opts.itemReader ?? "itemWords";
318401
318498
  const spec = interactiveSpec(tree);
318402
318499
  const machine = tree.stateMachine ?? null;
318403
318500
  const listened = machineLayers(machine);
@@ -318434,10 +318531,10 @@ ${pad}</>`;
318434
318531
  const fonted = opts.fontFamily && el.style["font-family"] ? { ...el.style, "font-family": opts.fontFamily(el.style["font-family"]) } : el.style;
318435
318532
  const style = node2.hidden && !bound?.visible && !el.pc ? { ...fonted, display: "none" } : fonted;
318436
318533
  if (el.keyframes && !keyframes.includes(el.keyframes)) keyframes.push(el.keyframes);
318437
- let attrs = tailwind && !el.pc ? ` className="${tailwindFor(style)}"` : ` style={{ ${[...styleMapToJSX(style).filter(([p2]) => !bound?.style[p2]).map(([p2, v]) => `${JSON.stringify(p2).replace(/^"([A-Za-z_$][\w$]*)"$/, "$1")}: '${v}'`), ...Object.entries(bound?.style ?? {}).map(([p2, e]) => `${p2}: ${e}`)].join(", ")} }${Object.keys(style).some((k2) => k2.startsWith("--")) ? " as CSSProperties" : ""}}`;
318438
- if (isRoot && (spec || machine || triggeredIn(node2))) attrs = ` ref={root}${attrs}`;
318439
318534
  const inItem = node2.id.includes(`~${ITEM_RECORD}`);
318440
- for (const [k2, v] of Object.entries(el.attrs)) attrs += inItem ? itemAttr(k2, v) : jsxAttr(k2, v);
318535
+ let attrs = tailwind && !el.pc ? ` className="${tailwindFor(style)}"` : ` style={{ ${[...styleMapToJSX(style).filter(([p2]) => !bound?.style[p2]).map(([p2, v]) => `${JSON.stringify(p2).replace(/^"([A-Za-z_$][\w$]*)"$/, "$1")}: ${inItem ? itemStyle(v, reader) : `'${v}'`}`), ...Object.entries(bound?.style ?? {}).map(([p2, e]) => `${p2}: ${e}`)].join(", ")} }${Object.keys(style).some((k2) => k2.startsWith("--")) ? " as CSSProperties" : ""}}`;
318536
+ if (isRoot && (spec || machine || triggeredIn(node2))) attrs = ` ref={root}${attrs}`;
318537
+ for (const [k2, v] of Object.entries(el.attrs)) attrs += inItem ? itemAttr(k2, v, reader) : jsxAttr(k2, v);
318441
318538
  if (opts.ids) attrs += inItem ? ` data-pc-id={${JSON.stringify(node2.id.split(`~${ITEM_RECORD}`)[0] + "~")} + item.id${node2.id.split(`~${ITEM_RECORD}`)[1] ? ` + ${JSON.stringify(node2.id.split(`~${ITEM_RECORD}`)[1])}` : ""}}` : ` data-pc-id=${JSON.stringify(node2.id)}`;
318442
318539
  const host = node2.id.includes("::") ? layerId(node2.id).split("::")[0] : void 0;
318443
318540
  const changeTo = (id3, target2) => {
@@ -318447,6 +318544,8 @@ ${pad}</>`;
318447
318544
  const handlers = opts.behaviour?.events(node2.id, { variant: chosen && layerId(node2.id) === chosen.instance ? node2.variant : void 0, changeTo }) ?? {};
318448
318545
  for (const [event, handler] of Object.entries(handlers)) attrs += ` ${event}={${inItem ? handler.split(ITEM_ID).join("item.id") : handler}}`;
318449
318546
  if (handlers.onClick && !NATIVELY_INTERACTIVE.has(tag)) attrs += ` role="button" tabIndex={0} onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click() } }}`;
318547
+ const sends = node2.action ? opts.formAction?.(node2) : null;
318548
+ if (sends) attrs += ` action={${sends}}`;
318450
318549
  if (el.keyframes) attrs += ' data-motion-child=""';
318451
318550
  if (el.motionTrigger) {
318452
318551
  attrs += ` data-motion="${el.motionTrigger}"`;
@@ -318455,7 +318554,7 @@ ${pad}</>`;
318455
318554
  if (el.pc) attrs += ` data-pc="${el.pc.uid}" data-layer=${JSON.stringify(el.pc.layer)}`;
318456
318555
  if (el.sm) attrs += ` data-sm=${JSON.stringify(el.sm)}`;
318457
318556
  if (el.input?.tag === "input") return `${pad}<input${attrs} />`;
318458
- const fieldValue = (v) => inItem ? itemAttr("value", v) : ` defaultValue=${JSON.stringify(v)}`;
318557
+ const fieldValue = (v) => inItem ? itemAttr("value", v, reader) : ` defaultValue=${JSON.stringify(v)}`;
318459
318558
  if (el.input?.tag === "textarea") return `${pad}<textarea${attrs}${el.input.value ? fieldValue(el.input.value) : ""} />`;
318460
318559
  if (el.input) return `${pad}<select${attrs}${el.input.value ? fieldValue(el.input.value) : ""}>
318461
318560
  ${(el.input.options ?? []).map((o) => `${pad} <option value=${JSON.stringify(o.value)}>${jsxText(o.label)}</option>`).join("\n")}
@@ -318466,14 +318565,15 @@ ${pad}</select>`;
318466
318565
  if (drawn2) inner.push(`${pad} ${drawn2}`);
318467
318566
  if (el.runs) inner.push(...el.runs.map((run4) => {
318468
318567
  const style2 = Object.keys(run4.style).length ? ` style={{ ${styleMapToJSX(run4.style).map(([p2, v]) => `${p2}: '${v}'`).join(", ")} }}` : "";
318469
- const words = inItem ? itemJsx(run4.text) : jsxText(run4.text);
318568
+ const words = inItem ? itemJsx(run4.text, reader) : jsxText(run4.text);
318470
318569
  if (run4.link) return `${pad} <a href=${JSON.stringify(run4.link)}${style2}>${words}</a>`;
318471
318570
  return style2 ? `${pad} <span${style2}>${words}</span>` : `${pad} ${words}`;
318472
318571
  }));
318473
318572
  else if (bound?.text) inner.push(`${pad} {${bound.text}}`);
318474
- else if (el.text !== void 0) inner.push(`${pad} ${inItem ? itemJsx(el.text) : jsxText(el.text)}`);
318573
+ else if (el.text !== void 0) inner.push(`${pad} ${inItem ? itemJsx(el.text, reader) : jsxText(el.text)}`);
318475
318574
  const picture2 = el.picture;
318476
318575
  if (picture2?.kind === "svg") inner.push(`${pad} <span dangerouslySetInnerHTML={{ __html: ${JSON.stringify(labelSVG(picture2.svg, node2))} }} />`);
318576
+ else if (picture2?.kind === "img" && inItem && itemMarks(picture2.src)) inner.push(`${pad} <img${itemAttr("src", picture2.src, reader)} width={${Math.round(node2.width)}} height={${Math.round(node2.height)}} alt=${JSON.stringify(altFor(node2))} style={{ objectFit: 'cover' }} />`);
318477
318577
  else if (picture2?.kind === "img") inner.push(`${pad} ${opts.image ? opts.image({ src: picture2.src, width: node2.width, height: node2.height, alt: altFor(node2) }) : `<img src="${picture2.src}" width={${node2.width}} height={${node2.height}} alt=${JSON.stringify(altFor(node2))} />`}`);
318478
318578
  else if (picture2) inner.push(`${pad} {/* ${picture2.reason}: render this node to ${picture2.format.toUpperCase()} */}`);
318479
318579
  const live2 = node2.island ? opts.island?.(node2) : null;
@@ -318511,7 +318611,7 @@ ${drawn}
318511
318611
  const live = !!spec || !!machine || triggered;
318512
318612
  const prelude = [...opts.prelude ?? [], ...states].map((l) => ` ${l}
318513
318613
  `).join("");
318514
- if (!live && !keyframes.length && !body.includes(" as CSSProperties}")) return `export function ${name2}() {
318614
+ if (!live && !keyframes.length && !body.includes(" as CSSProperties}")) return `export function ${name2}(${opts.props ?? ""}) {
318515
318615
  ${prelude} return (
318516
318616
  ${body}
318517
318617
  )
@@ -318544,7 +318644,7 @@ ${body}
318544
318644
  " return () => { io.disconnect(); el.removeEventListener('click', click); el.removeEventListener('mouseover', hover) }",
318545
318645
  " }, [])"
318546
318646
  );
318547
- return `${head2.join("\n")}export function ${name2}() {
318647
+ return `${head2.join("\n")}export function ${name2}(${opts.props ?? ""}) {
318548
318648
  ${prelude}${effects.join("\n")}${effects.length ? "\n" : ""} return (
318549
318649
  ${body}
318550
318650
  )
@@ -318555,7 +318655,7 @@ function elementType(node2) {
318555
318655
  const known = { div: "HTMLDivElement", section: "HTMLElement", main: "HTMLElement", header: "HTMLElement", footer: "HTMLElement", nav: "HTMLElement", article: "HTMLElement", aside: "HTMLElement", form: "HTMLFormElement", a: "HTMLAnchorElement", button: "HTMLButtonElement", ul: "HTMLUListElement", li: "HTMLLIElement" };
318556
318656
  return known[tag] ?? "HTMLElement";
318557
318657
  }
318558
- var jsxText, template, JSX_ATTR, NATIVELY_INTERACTIVE, BOOLEAN_ATTR, jsxAttr, itemJsx, itemAttr, ITEM_ID;
318658
+ var jsxText, template, JSX_ATTR, NATIVELY_INTERACTIVE, BOOLEAN_ATTR, jsxAttr, itemJsx, itemAttr, itemStyle, ITEM_ID;
318559
318659
  var init_react2 = __esm({
318560
318660
  "lib/io/web/react.ts"() {
318561
318661
  "use strict";
@@ -318574,33 +318674,173 @@ var init_react2 = __esm({
318574
318674
  NATIVELY_INTERACTIVE = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea", "summary", "label"]);
318575
318675
  BOOLEAN_ATTR = /* @__PURE__ */ new Set(["required", "checked", "disabled", "multiple", "readonly", "autofocus", "novalidate", "selected", "hidden", "open"]);
318576
318676
  jsxAttr = (k2, v) => BOOLEAN_ATTR.has(k2) ? v === "false" ? "" : ` ${JSX_ATTR[k2] ?? k2}` : ` ${JSX_ATTR[k2] ?? k2}=${JSON.stringify(v)}`;
318577
- itemJsx = (s) => {
318677
+ itemJsx = (s, reader = "itemWords") => {
318578
318678
  const parts = itemMarks(s);
318579
318679
  if (!parts) return jsxText(s);
318580
- return `{${parts.map((p2) => typeof p2 === "string" ? JSON.stringify(p2) : `itemWords(item, ${JSON.stringify(p2.key)})`).join(" + ")}}`;
318680
+ return `{${parts.map((p2) => typeof p2 === "string" ? JSON.stringify(p2) : `${reader}(item, ${JSON.stringify(p2.key)})`).join(" + ")}}`;
318581
318681
  };
318582
- itemAttr = (k2, v) => {
318682
+ itemAttr = (k2, v, reader = "itemWords") => {
318583
318683
  const parts = itemMarks(v);
318584
318684
  if (!parts || BOOLEAN_ATTR.has(k2)) return jsxAttr(k2, v);
318585
- return ` ${JSX_ATTR[k2] ?? k2}={${parts.map((p2) => typeof p2 === "string" ? JSON.stringify(p2) : `itemWords(item, ${JSON.stringify(p2.key)})`).join(" + ")}}`;
318685
+ return ` ${JSX_ATTR[k2] ?? k2}={${parts.map((p2) => typeof p2 === "string" ? JSON.stringify(p2) : `${reader}(item, ${JSON.stringify(p2.key)})`).join(" + ")}}`;
318686
+ };
318687
+ itemStyle = (v, reader) => {
318688
+ const parts = itemMarks(v);
318689
+ if (!parts) return `'${v}'`;
318690
+ return parts.map((p2) => typeof p2 === "string" ? JSON.stringify(p2.replace(/\\'/g, "'")) : `${reader}(item, ${JSON.stringify(p2.key)})`).join(" + ");
318586
318691
  };
318587
318692
  ITEM_ID = JSON.stringify(ITEM_RECORD);
318588
318693
  }
318589
318694
  });
318590
318695
 
318696
+ // packages/runtime/src/commerce.ts
318697
+ var commerce_exports = {};
318698
+ __export(commerce_exports, {
318699
+ COMMERCE_ACTIONS: () => COMMERCE_ACTIONS,
318700
+ COMMERCE_ACTION_PROP: () => COMMERCE_ACTION_PROP,
318701
+ COMMERCE_FIELDS: () => COMMERCE_FIELDS,
318702
+ COMMERCE_PROP: () => COMMERCE_PROP,
318703
+ COMMERCE_SCHEMAS: () => COMMERCE_SCHEMAS,
318704
+ commerceItem: () => commerceItem,
318705
+ commerceWords: () => commerceWords,
318706
+ formatMoney: () => formatMoney,
318707
+ isCommerceAction: () => isCommerceAction
318708
+ });
318709
+ function formatMoney(m, locale = "en-US") {
318710
+ if (!m) return "";
318711
+ try {
318712
+ return new Intl.NumberFormat(locale, { style: "currency", currency: m.currencyCode, currencyDisplay: "narrowSymbol" }).format(Number(m.amount));
318713
+ } catch {
318714
+ return `${m.amount} ${m.currencyCode}`;
318715
+ }
318716
+ }
318717
+ function commerceItem(schema, value3) {
318718
+ switch (schema) {
318719
+ case "commerce.product": {
318720
+ const p2 = value3;
318721
+ return { id: p2.id, values: {
318722
+ title: p2.title,
318723
+ handle: p2.handle,
318724
+ url: `/product/${p2.handle}`,
318725
+ description: p2.description,
318726
+ price: formatMoney(p2.priceRange?.minVariantPrice),
318727
+ max_price: formatMoney(p2.priceRange?.maxVariantPrice),
318728
+ image: p2.featuredImage?.url ?? "",
318729
+ image_alt: p2.featuredImage?.altText || p2.title,
318730
+ available: !!p2.availableForSale,
318731
+ variant_id: p2.variants?.find((v) => v.availableForSale)?.id ?? p2.variants?.[0]?.id ?? "",
318732
+ tags: (p2.tags ?? []).join(", ")
318733
+ } };
318734
+ }
318735
+ case "commerce.collection": {
318736
+ const c2 = value3;
318737
+ return { id: c2.handle || "all", values: { title: c2.title, handle: c2.handle, url: c2.path, description: c2.description } };
318738
+ }
318739
+ case "commerce.cart": {
318740
+ const c2 = value3;
318741
+ return { id: c2.id ?? "cart", values: {
318742
+ count: c2.totalQuantity,
318743
+ subtotal: formatMoney(c2.cost?.subtotalAmount),
318744
+ taxes: formatMoney(c2.cost?.totalTaxAmount),
318745
+ total: formatMoney(c2.cost?.totalAmount),
318746
+ checkout_url: c2.checkoutUrl
318747
+ } };
318748
+ }
318749
+ case "commerce.cartLine": {
318750
+ const l = value3;
318751
+ const m = l.merchandise;
318752
+ return { id: l.id ?? m.id, values: {
318753
+ title: m.product.title,
318754
+ variant: m.title === "Default Title" ? "" : m.title,
318755
+ url: `/product/${m.product.handle}`,
318756
+ image: m.product.featuredImage?.url ?? "",
318757
+ quantity: l.quantity,
318758
+ price: formatMoney(l.cost?.totalAmount),
318759
+ merchandise_id: m.id
318760
+ } };
318761
+ }
318762
+ case "commerce.menu": {
318763
+ const m = value3;
318764
+ return { id: m.path, values: { title: m.title, url: m.path } };
318765
+ }
318766
+ }
318767
+ }
318768
+ function commerceWords(item, key5) {
318769
+ if (key5 === "id") return item.id;
318770
+ const v = item.values[key5];
318771
+ return typeof v === "boolean" ? v ? "Yes" : "No" : v === void 0 ? "" : String(v);
318772
+ }
318773
+ var COMMERCE_SCHEMAS, COMMERCE_FIELDS, COMMERCE_PROP, COMMERCE_ACTIONS, COMMERCE_ACTION_PROP, isCommerceAction;
318774
+ var init_commerce = __esm({
318775
+ "packages/runtime/src/commerce.ts"() {
318776
+ "use strict";
318777
+ COMMERCE_SCHEMAS = ["commerce.product", "commerce.collection", "commerce.cart", "commerce.cartLine", "commerce.menu"];
318778
+ COMMERCE_FIELDS = {
318779
+ "commerce.product": [
318780
+ { name: "Title", type: "TEXT" },
318781
+ { name: "Handle", type: "TEXT" },
318782
+ { name: "URL", type: "URL" },
318783
+ { name: "Description", type: "TEXT" },
318784
+ { name: "Price", type: "TEXT" },
318785
+ { name: "Max price", type: "TEXT" },
318786
+ { name: "Image", type: "MEDIA" },
318787
+ { name: "Image alt", type: "TEXT" },
318788
+ { name: "Available", type: "BOOLEAN" },
318789
+ { name: "Variant ID", type: "TEXT" },
318790
+ { name: "Tags", type: "TEXT" }
318791
+ ],
318792
+ "commerce.collection": [{ name: "Title", type: "TEXT" }, { name: "Handle", type: "TEXT" }, { name: "URL", type: "URL" }, { name: "Description", type: "TEXT" }],
318793
+ "commerce.cart": [
318794
+ { name: "Count", type: "NUMBER" },
318795
+ { name: "Subtotal", type: "TEXT" },
318796
+ { name: "Taxes", type: "TEXT" },
318797
+ { name: "Total", type: "TEXT" },
318798
+ { name: "Checkout URL", type: "URL" }
318799
+ ],
318800
+ "commerce.cartLine": [
318801
+ { name: "Title", type: "TEXT" },
318802
+ { name: "Variant", type: "TEXT" },
318803
+ { name: "URL", type: "URL" },
318804
+ { name: "Image", type: "MEDIA" },
318805
+ { name: "Quantity", type: "NUMBER" },
318806
+ { name: "Price", type: "TEXT" },
318807
+ { name: "Merchandise ID", type: "TEXT" }
318808
+ ],
318809
+ "commerce.menu": [{ name: "Title", type: "TEXT" }, { name: "URL", type: "URL" }]
318810
+ };
318811
+ COMMERCE_PROP = {
318812
+ "commerce.product": { name: "products", type: "Product" },
318813
+ "commerce.collection": { name: "collections", type: "Collection" },
318814
+ "commerce.cart": { name: "cart", type: "Cart", one: true },
318815
+ "commerce.cartLine": { name: "lines", type: "CartItem" },
318816
+ "commerce.menu": { name: "menu", type: "Menu" }
318817
+ };
318818
+ COMMERCE_ACTIONS = ["addToCart", "updateCartLine", "removeCartLine", "checkout", "search"];
318819
+ COMMERCE_ACTION_PROP = {
318820
+ addToCart: "onAddToCart",
318821
+ updateCartLine: "onUpdateCartLine",
318822
+ removeCartLine: "onRemoveCartLine",
318823
+ checkout: "onCheckout",
318824
+ search: "onSearch"
318825
+ };
318826
+ isCommerceAction = (a) => typeof a === "string" && COMMERCE_ACTIONS.includes(a);
318827
+ }
318828
+ });
318829
+
318591
318830
  // lib/sites/auth.ts
318592
318831
  function usesSignIn(doc2) {
318593
318832
  if (Object.values(doc2.collections ?? {}).some((c2) => c2.perVisitor)) return true;
318594
318833
  if (Object.values(doc2.variables ?? {}).some((v) => v.name.startsWith("user/"))) return true;
318595
318834
  return Object.values(doc2.nodes).some((n) => {
318596
318835
  const o = n;
318597
- return !!o.route?.access || !!o.control?.formAction && o.control.formAction !== "deleteRecord" && o.control.formAction !== "updateRecord";
318836
+ return !!o.route?.access || !!o.control?.formAction && o.control.formAction !== "deleteRecord" && o.control.formAction !== "updateRecord" && !isCommerceAction(o.control.formAction);
318598
318837
  });
318599
318838
  }
318600
318839
  var init_auth = __esm({
318601
318840
  "lib/sites/auth.ts"() {
318602
318841
  "use strict";
318603
318842
  init_contract();
318843
+ init_commerce();
318604
318844
  }
318605
318845
  });
318606
318846
 
@@ -319282,7 +319522,7 @@ var init_package = __esm({
319282
319522
  "packages/runtime/package.json"() {
319283
319523
  package_default = {
319284
319524
  name: "@popcraft/runtime",
319285
- version: "0.13.2",
319525
+ version: "0.15.0",
319286
319526
  description: "What code exported from PopCraft shares: the meaning of expressions (operators and functions) and, as it grows, the parts of a design's behaviour that are common code rather than generated React.",
319287
319527
  license: "MIT",
319288
319528
  homepage: "https://popcraft.app/docs/exporting/nextjs",
@@ -319298,6 +319538,10 @@ var init_package = __esm({
319298
319538
  types: "./dist/index.d.ts",
319299
319539
  import: "./dist/index.js"
319300
319540
  },
319541
+ "./commerce": {
319542
+ types: "./dist/commerce.d.ts",
319543
+ import: "./dist/commerce.js"
319544
+ },
319301
319545
  "./server": {
319302
319546
  types: "./dist/server/index.d.ts",
319303
319547
  import: "./dist/server.js"
@@ -319877,10 +320121,11 @@ function cleanPath(raw) {
319877
320121
  function routesOf(screens) {
319878
320122
  const taken = /* @__PURE__ */ new Set();
319879
320123
  const own2 = /* @__PURE__ */ new Map();
320124
+ const claim2 = (s, path) => s.route?.collectionId ? `${path}/*` : path;
319880
320125
  for (const s of screens) {
319881
320126
  const path = s.route?.path ? cleanPath(s.route.path) : null;
319882
- if (path && !taken.has(path)) {
319883
- taken.add(path);
320127
+ if (path && !taken.has(claim2(s, path))) {
320128
+ taken.add(claim2(s, path));
319884
320129
  own2.set(s.id, path);
319885
320130
  }
319886
320131
  }
@@ -320395,6 +320640,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
320395
320640
  const anchored = anchorTargets(doc2);
320396
320641
  const pageItem = () => {
320397
320642
  const c2 = opts.item && doc2.collections?.[opts.item.collectionId];
320643
+ if (c2 && opts.item.recordId === ITEM_RECORD) return { collection: c2, record: templateRecord(c2), suffix: `~${ITEM_RECORD}` };
320398
320644
  const r = opts.item && doc2.records?.[opts.item.recordId];
320399
320645
  return c2 && r ? { collection: c2, record: r, suffix: "" } : null;
320400
320646
  };
@@ -320408,6 +320654,8 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
320408
320654
  const base = opts.routes?.[filled2.frameId];
320409
320655
  const of = dest?.route?.collectionId;
320410
320656
  if (of && base !== void 0) {
320657
+ const slugField = it?.record.id === ITEM_RECORD && it.collection.id === of ? it.collection.fields.find((f2) => f2.id === it.collection.slugFieldId) ?? it.collection.fields.find((f2) => f2.type === "SLUG" || f2.type === "TEXT") : void 0;
320658
+ if (slugField) return { link: filled2, href: `${base === "/" ? "" : base}/${itemMark(fieldKey(slugField.name))}`, screen: filled2.frameId };
320411
320659
  const slugs = recordSlugs(doc2, of);
320412
320660
  const rec = it?.collection.id === of ? it.record.id : slugs.keys().next().value;
320413
320661
  const slug3 = rec ? slugs.get(rec) : void 0;
@@ -320415,6 +320663,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
320415
320663
  }
320416
320664
  return { link: filled2, href: base ?? null, screen: filled2.frameId };
320417
320665
  }
320666
+ if (filled2.kind === "url" && it?.record.id === ITEM_RECORD && itemMarks(filled2.url)) return { link: filled2, href: filled2.url.trim() };
320418
320667
  return { link: filled2, href: hrefOf(filled2, doc2.nodes, opts.routes) };
320419
320668
  };
320420
320669
  const fields = /* @__PURE__ */ new Map();
@@ -320494,8 +320743,10 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
320494
320743
  const control = node2.control;
320495
320744
  if (control) {
320496
320745
  const named = control.name ? { name: control.name } : {};
320497
- if (control.kind === "FORM") web.tag = "form";
320498
- else if (control.kind === "BUTTON") {
320746
+ if (control.kind === "FORM") {
320747
+ web.tag = "form";
320748
+ if (isCommerceAction(control.formAction)) web.action = control.formAction;
320749
+ } else if (control.kind === "BUTTON") {
320499
320750
  web.tag = "button";
320500
320751
  web.attrs = { ...web.attrs, type: control.buttonType ?? "submit", ...named, ...control.name && control.value !== void 0 ? { value: item && hasItemToken(control.value) ? itemText(control.value, item.collection, item.record) : control.value } : {} };
320501
320752
  } else if (control.kind === "CHECKBOX" || control.kind === "RADIO") {
@@ -320624,7 +320875,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
320624
320875
  if (built) web.children.push(built);
320625
320876
  }
320626
320877
  }
320627
- if (collection.perVisitor && !outerItem) {
320878
+ if (collection.perVisitor && !outerItem || collection.schema && opts.listTemplates) {
320628
320879
  item = { collection, record: templateRecord(collection), suffix: `~${ITEM_RECORD}` };
320629
320880
  const template2 = [];
320630
320881
  for (const child of childrenOf(node2, doc2.nodes)) {
@@ -320683,6 +320934,7 @@ var init_tree = __esm({
320683
320934
  init_variables();
320684
320935
  init_scene3d();
320685
320936
  init_semantics();
320937
+ init_commerce();
320686
320938
  init_collections();
320687
320939
  init_props();
320688
320940
  init_native_registry();
@@ -333927,9 +334179,9 @@ var require_sourcemap_codec_umd = __commonJS({
333927
334179
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
333928
334180
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
333929
334181
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
333930
- var __export2 = (target2, all3) => {
333931
- for (var name2 in all3)
333932
- __defProp2(target2, name2, { get: all3[name2], enumerable: true });
334182
+ var __export2 = (target2, all4) => {
334183
+ for (var name2 in all4)
334184
+ __defProp2(target2, name2, { get: all4[name2], enumerable: true });
333933
334185
  };
333934
334186
  var __copyProps2 = (to, from2, except, desc2) => {
333935
334187
  if (from2 && typeof from2 === "object" || typeof from2 === "function") {
@@ -334614,9 +334866,9 @@ var require_trace_mapping_umd = __commonJS({
334614
334866
  var __commonJS2 = (cb, mod) => function __require2() {
334615
334867
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
334616
334868
  };
334617
- var __export2 = (target2, all3) => {
334618
- for (var name2 in all3)
334619
- __defProp2(target2, name2, { get: all3[name2], enumerable: true });
334869
+ var __export2 = (target2, all4) => {
334870
+ for (var name2 in all4)
334871
+ __defProp2(target2, name2, { get: all4[name2], enumerable: true });
334620
334872
  };
334621
334873
  var __copyProps2 = (to, from2, except, desc2) => {
334622
334874
  if (from2 && typeof from2 === "object" || typeof from2 === "function") {
@@ -335098,7 +335350,7 @@ var require_trace_mapping_umd = __commonJS({
335098
335350
  }
335099
335351
  return result;
335100
335352
  }
335101
- function generatedPosition(map2, source3, line, column, bias, all3) {
335353
+ function generatedPosition(map2, source3, line, column, bias, all4) {
335102
335354
  var _a4, _b2;
335103
335355
  line--;
335104
335356
  if (line < 0) throw new Error(LINE_GTR_ZERO);
@@ -335106,13 +335358,13 @@ var require_trace_mapping_umd = __commonJS({
335106
335358
  const { sources, resolvedSources } = map2;
335107
335359
  let sourceIndex2 = sources.indexOf(source3);
335108
335360
  if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source3);
335109
- if (sourceIndex2 === -1) return all3 ? [] : GMapping(null, null);
335361
+ if (sourceIndex2 === -1) return all4 ? [] : GMapping(null, null);
335110
335362
  const bySourceMemos = (_a4 = cast(map2))._bySourceMemos || (_a4._bySourceMemos = sources.map(memoizedState));
335111
335363
  const generated = (_b2 = cast(map2))._bySources || (_b2._bySources = buildBySources(decodedMappings(map2), bySourceMemos));
335112
335364
  const segments = generated[sourceIndex2][line];
335113
- if (segments == null) return all3 ? [] : GMapping(null, null);
335365
+ if (segments == null) return all4 ? [] : GMapping(null, null);
335114
335366
  const memo5 = bySourceMemos[sourceIndex2];
335115
- if (all3) return sliceGeneratedPositions(segments, memo5, line, column, bias);
335367
+ if (all4) return sliceGeneratedPositions(segments, memo5, line, column, bias);
335116
335368
  const index2 = traceSegmentInternal(segments, memo5, line, column, bias);
335117
335369
  if (index2 === -1) return GMapping(null, null);
335118
335370
  const segment = segments[index2];
@@ -335154,9 +335406,9 @@ var require_gen_mapping_umd = __commonJS({
335154
335406
  var __commonJS2 = (cb, mod) => function __require2() {
335155
335407
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
335156
335408
  };
335157
- var __export2 = (target2, all3) => {
335158
- for (var name2 in all3)
335159
- __defProp2(target2, name2, { get: all3[name2], enumerable: true });
335409
+ var __export2 = (target2, all4) => {
335410
+ for (var name2 in all4)
335411
+ __defProp2(target2, name2, { get: all4[name2], enumerable: true });
335160
335412
  };
335161
335413
  var __copyProps2 = (to, from2, except, desc2) => {
335162
335414
  if (from2 && typeof from2 === "object" || typeof from2 === "function") {
@@ -345221,8 +345473,8 @@ ${pad})`;
345221
345473
  return [`${pad}Box(`, `${pad} modifier = ${modifier}`, `${pad}) {`, ...kids, `${pad}}`].join("\n");
345222
345474
  };
345223
345475
  const body = emit8(tree.root, 1, true);
345224
- const all3 = [body, ...parts].join("\n");
345225
- const uses = (re) => re.test(all3);
345476
+ const all4 = [body, ...parts].join("\n");
345477
+ const uses = (re) => re.test(all4);
345226
345478
  const imports3 = [
345227
345479
  [/\.background\(/, "androidx.compose.foundation.background"],
345228
345480
  [/\.border\(/, "androidx.compose.foundation.border"],
@@ -345826,6 +346078,429 @@ var init_project = __esm({
345826
346078
  }
345827
346079
  });
345828
346080
 
346081
+ // lib/io/web/project/components.ts
346082
+ var components_exports = {};
346083
+ __export(components_exports, {
346084
+ componentFiles: () => componentFiles,
346085
+ componentMedia: () => componentMedia,
346086
+ componentScreens: () => componentScreens
346087
+ });
346088
+ function componentScreens(doc2, pageId2 = doc2.pages[0], nodeCap = 2e4) {
346089
+ const screens = screensOf(doc2, pageId2);
346090
+ const build3 = { target: "site", nodeCap, listTemplates: true, routes: Object.fromEntries(routesOf(screens)) };
346091
+ return screens.map((s) => {
346092
+ const of = s.route?.collectionId ? doc2.collections?.[s.route.collectionId] : void 0;
346093
+ const about = of?.schema ? of : void 0;
346094
+ const opts = about ? { ...build3, item: { collectionId: about.id, recordId: ITEM_RECORD } } : build3;
346095
+ const tree = buildPlainTree(doc2, s.id, opts);
346096
+ const media2 = mediaRules(doc2, s.id, tree.root, opts).map((r) => r.replace(/\[data-pc-id="([^"~]+)~[^"]+"\]/g, '[data-pc-id^="$1~"]'));
346097
+ return { id: s.id, name: s.name, tree: media2.length ? { ...tree, rules: [...tree.rules ?? [], ...media2] } : tree, ...about ? { about } : {} };
346098
+ });
346099
+ }
346100
+ function componentMedia(doc2, screens) {
346101
+ const text = JSON.stringify(screens.map((s) => s.tree)) + JSON.stringify(doc2.records ?? {});
346102
+ return [...new Set(text.match(MEDIA_REF2) ?? [])].sort();
346103
+ }
346104
+ function componentFiles(doc2, screens, o = {}) {
346105
+ const dir = (o.dir ?? "components/popcraft").replace(/\/$/, "");
346106
+ const media2 = [];
346107
+ const at3 = /* @__PURE__ */ new Map();
346108
+ for (const [ref3, m] of Object.entries(o.media ?? {})) {
346109
+ const name2 = `${ref3.slice("media://".length)}.${MEDIA_EXT[m.type] ?? "bin"}`;
346110
+ at3.set(ref3, `/popcraft/media/${name2}`);
346111
+ media2.push({ path: `public/popcraft/media/${name2}`, content: m.bytes });
346112
+ }
346113
+ const located = (v) => at3.size ? JSON.parse(JSON.stringify(v).replace(MEDIA_REF2, (ref3) => at3.get(ref3) ?? ref3)) : v;
346114
+ doc2 = { ...doc2, records: located(doc2.records) };
346115
+ const holder = { pages: screens.map((s) => ({ tree: located(s.tree) })) };
346116
+ const pictures = [...externalizeBitmaps(holder).map((f2) => ({ ...f2, path: `public/popcraft/${f2.path}` })), ...media2];
346117
+ const names = /* @__PURE__ */ new Set();
346118
+ const files = [];
346119
+ const index2 = [];
346120
+ screens.forEach((s, i2) => {
346121
+ const tree = { ...holder.pages[i2].tree, root: withAssetBase(holder.pages[i2].tree.root, "/popcraft/") };
346122
+ const base = (componentName(s.name) || "Screen").replace(/(page|screen)?$/i, "Page");
346123
+ let name2 = base;
346124
+ for (let n = 2; names.has(name2); n++) name2 = `${base}${n}`;
346125
+ names.add(name2);
346126
+ const props = [];
346127
+ const types = /* @__PURE__ */ new Set();
346128
+ const samples = [];
346129
+ const prelude = [];
346130
+ const listProp = /* @__PURE__ */ new Map();
346131
+ const propName2 = (base2) => {
346132
+ let p2 = base2;
346133
+ for (let n = 2; props.some((x2) => x2.name === p2) || p2 === "item"; n++) p2 = `${base2}${n}`;
346134
+ return p2;
346135
+ };
346136
+ if (s.about) {
346137
+ const schema = s.about.schema;
346138
+ const p2 = propName2(camel3(singular(COMMERCE_PROP[schema].name)));
346139
+ const type = COMMERCE_PROP[schema].type;
346140
+ types.add(type);
346141
+ props.push({ name: p2, type: `${type}`, doc: `The ${singular(s.about.name).toLowerCase()} this page is about; none: the design's first sample.` });
346142
+ samples.push(sampleConst(`SAMPLE_${p2.toUpperCase()}`, doc2, s.about, 1));
346143
+ prelude.push(`const item = ${p2} ? commerceItem(${JSON.stringify(schema)}, ${p2}) : SAMPLE_${p2.toUpperCase()}`);
346144
+ }
346145
+ for (const n of all3(tree.root)) {
346146
+ const c2 = n.repeat && doc2.collections?.[n.repeat.collectionId];
346147
+ if (!c2?.schema || listProp.has(c2.id)) continue;
346148
+ const { type, one } = COMMERCE_PROP[c2.schema];
346149
+ const p2 = propName2(one ? COMMERCE_PROP[c2.schema].name : camel3(c2.name));
346150
+ types.add(type);
346151
+ listProp.set(c2.id, p2);
346152
+ props.push({ name: p2, type: one ? type : `${type}[]`, doc: `${c2.name}; none: the design's samples.` });
346153
+ samples.push(sampleConst(`SAMPLE_${p2.toUpperCase()}`, doc2, c2));
346154
+ }
346155
+ const actions = [...new Set(all3(tree.root).map((n) => n.action).filter((a) => !!a))];
346156
+ for (const a of actions) props.push({ name: COMMERCE_ACTION_PROP[a], type: "(formData: FormData) => void | Promise<void>", doc: `What sending its ${a} form does (a server action).` });
346157
+ const body = serializeReact(tree, {
346158
+ ids: true,
346159
+ name: name2,
346160
+ styling: o.styling ?? "inline",
346161
+ prelude,
346162
+ itemReader: "commerceWords",
346163
+ props: props.length ? `{ ${props.map((p2) => p2.name).join(", ")} }: ${name2}Props = {}` : "",
346164
+ lists: (r) => {
346165
+ const p2 = listProp.get(r.collectionId);
346166
+ if (!p2) return null;
346167
+ const c2 = doc2.collections[r.collectionId];
346168
+ let out = COMMERCE_PROP[c2.schema].one ? `(${p2} ? [commerceItem(${JSON.stringify(c2.schema)}, ${p2})] : SAMPLE_${p2.toUpperCase()})` : `(${p2} ? ${p2}.map(v => commerceItem(${JSON.stringify(c2.schema)}, v)) : SAMPLE_${p2.toUpperCase()})`;
346169
+ if (r.offset || r.limit) out += `.slice(${r.offset ?? 0}${r.limit ? `, ${(r.offset ?? 0) + r.limit}` : ""})`;
346170
+ return out;
346171
+ },
346172
+ formAction: (n) => n.action ? COMMERCE_ACTION_PROP[n.action] : null
346173
+ });
346174
+ const runtime = [.../\bcommerceItem\(/.test(body) ? ["commerceItem"] : [], .../\bcommerceWords\(/.test(body) ? ["commerceWords"] : [], ...samples.length ? ["type CommerceItem"] : [], ...[...types].sort().map((t3) => `type ${t3}`)];
346175
+ const client = /\buse(Effect|Ref|State)\b/.test(body);
346176
+ const react = /<Fragment\b/.test(body) && !/import \{[^}]*\bFragment\b/.test(body) ? ["import { Fragment } from 'react'"] : [];
346177
+ const content = [
346178
+ ...client ? ["'use client'", ""] : [],
346179
+ `// ${s.name}, exported from PopCraft (${doc2.name}) as a component a Next.js Commerce store renders with its own data.`,
346180
+ "// Re-export it from PopCraft rather than editing it here: the design is the source.",
346181
+ "",
346182
+ ...react,
346183
+ ...runtime.length ? [`import { ${runtime.join(", ")} } from '@popcraft/runtime/commerce'`] : [],
346184
+ "",
346185
+ ...props.length ? [`export interface ${name2}Props {`, ...props.map((p2) => ` /** ${p2.doc} */
346186
+ ${p2.name}?: ${p2.type}`), "}", ""] : [],
346187
+ ...samples,
346188
+ body,
346189
+ ""
346190
+ ].join("\n");
346191
+ files.push({ path: `${dir}/${name2}.tsx`, content });
346192
+ index2.push(`export { ${name2}${props.length ? `, type ${name2}Props` : ""} } from './${name2}'`);
346193
+ });
346194
+ const tokens = screens.find((sc) => sc.tree.tokens)?.tree.tokens;
346195
+ files.push({ path: `${dir}/popcraft.css`, content: [
346196
+ `/* The variables of ${doc2.name}'s design, exported from PopCraft: what its components' styles name. */`,
346197
+ "*, *::before, *::after { box-sizing: border-box; }",
346198
+ ...tokens ? tokensCSS(tokens) : [],
346199
+ ""
346200
+ ].join("\n") });
346201
+ files.push({ path: `${dir}/index.ts`, content: [`// The components of ${doc2.name}, exported from PopCraft.`, "import './popcraft.css'", "", ...index2, ""].join("\n") });
346202
+ return [...files, ...pictures];
346203
+ }
346204
+ function sampleConst(name2, doc2, c2, one) {
346205
+ const rows = recordsIn(doc2, c2.id).map((r) => ({ id: r.id, values: Object.fromEntries(c2.fields.map((f2) => [fieldKey(f2.name), r.values[f2.id] ?? ""])) }));
346206
+ return one ? `const ${name2}: CommerceItem = ${JSON.stringify(rows[0] ?? { id: "sample", values: {} }, null, 2)}
346207
+ ` : `const ${name2}: CommerceItem[] = ${JSON.stringify(rows, null, 2)}
346208
+ `;
346209
+ }
346210
+ var all3, camel3, singular, MEDIA_REF2, MEDIA_EXT;
346211
+ var init_components = __esm({
346212
+ "lib/io/web/project/components.ts"() {
346213
+ init_commerce();
346214
+ init_collections();
346215
+ init_routes();
346216
+ init_naming();
346217
+ init_react2();
346218
+ init_tokens2();
346219
+ init_site();
346220
+ init_tree();
346221
+ init_assets();
346222
+ all3 = (n, out = []) => {
346223
+ out.push(n);
346224
+ n.children.forEach((c2) => all3(c2, out));
346225
+ n.repeat?.template.forEach((c2) => all3(c2, out));
346226
+ return out;
346227
+ };
346228
+ camel3 = (s) => {
346229
+ const w = componentName(s.replace(/[^A-Za-z0-9]+/g, " ").replace(/\b\w/g, (c2) => c2.toUpperCase()));
346230
+ return w.charAt(0).toLowerCase() + w.slice(1);
346231
+ };
346232
+ singular = (s) => s.replace(/ies$/, "y").replace(/s$/, "");
346233
+ MEDIA_REF2 = /media:\/\/[A-Za-z0-9_-]+/g;
346234
+ MEDIA_EXT = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif", "image/avif": "avif", "image/svg+xml": "svg" };
346235
+ }
346236
+ });
346237
+
346238
+ // lib/io/web/project/shopify-theme.ts
346239
+ function kindOf(s, doc2, route2) {
346240
+ if (s.about?.schema === "commerce.product") return "product";
346241
+ if (s.about?.schema === "commerce.collection") return "collection";
346242
+ const nodes = every2(s.tree.root);
346243
+ if (nodes.some((n) => n.action === "search")) return "search";
346244
+ if (nodes.some((n) => n.repeat && doc2.collections?.[n.repeat.collectionId]?.schema === "commerce.cartLine")) return "cart";
346245
+ if (route2 === "/") return "index";
346246
+ return "page";
346247
+ }
346248
+ function liquidNode(node2, schema, v) {
346249
+ const fields = LIQUID[schema](v);
346250
+ const out = boundNode(node2, (key5) => key5 in fields ? `{{ ${fields[key5]} }}` : key5 === "id" ? `{{ ${v}.id }}` : "");
346251
+ const rename = (n) => ({ ...n, ...n.input?.attrs.name === "merchandiseId" ? { input: { ...n.input, attrs: { ...n.input.attrs, name: "id" } } } : {}, children: n.children.map(rename) });
346252
+ return rename(out);
346253
+ }
346254
+ function boundNode(node2, field) {
346255
+ const { children, repeat, variants, ...own2 } = node2;
346256
+ const out = JSON.parse(JSON.stringify(own2).replace(/\u2063([^\u2063]*)\u2063/g, (_m, key5) => field(key5)));
346257
+ const page = new RegExp(`^/[\\w-]+/${field("handle").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`);
346258
+ const href = (h) => field("handle") && page.test(h) ? field("url") : h;
346259
+ if (out.link?.href) out.link = { ...out.link, href: href(out.link.href) };
346260
+ if (out.attrs?.href) out.attrs = { ...out.attrs, href: href(out.attrs.href) };
346261
+ return { ...out, children: children.map((c2) => boundNode(c2, field)), ...repeat ? { repeat } : {}, ...variants ? { variants } : {} };
346262
+ }
346263
+ function shopifyThemeFiles(doc2, screens, o = {}) {
346264
+ const files = [];
346265
+ const name2 = o.name ?? doc2.name;
346266
+ const asset = /* @__PURE__ */ new Map();
346267
+ for (const [ref3, m] of Object.entries(o.media ?? {})) {
346268
+ const file2 = `popcraft-${ref3.slice("media://".length)}.${MEDIA_EXT2[m.type] ?? "bin"}`;
346269
+ asset.set(ref3, `{{ '${file2}' | asset_url }}`);
346270
+ files.push({ path: `assets/${file2}`, content: m.bytes });
346271
+ }
346272
+ const holder = { pages: screens.map((s) => ({ tree: s.tree })) };
346273
+ for (const f2 of externalizeBitmaps(holder)) {
346274
+ const file2 = `popcraft-${f2.path.replace(/^assets\//, "")}`;
346275
+ files.push({ path: `assets/${file2}`, content: f2.content });
346276
+ }
346277
+ const located = (v) => JSON.parse(JSON.stringify(v).replace(MEDIA_REF3, (ref3) => asset.get(ref3) ?? ref3).replace(/(["'(])assets\/(img-\d+\.\w+)/g, (_m, lead, f2) => `${lead}{{ 'popcraft-${f2}' | asset_url }}`));
346278
+ const schemaOf = (id3) => doc2.collections?.[id3]?.schema;
346279
+ const used = /* @__PURE__ */ new Set();
346280
+ const sections = [];
346281
+ for (const [i2, s] of screens.entries()) {
346282
+ const route2 = doc2.nodes?.[s.id] ? doc2.nodes[s.id].route?.path : void 0;
346283
+ let kind = kindOf(s, doc2, route2 ?? (i2 === 0 ? "/" : void 0));
346284
+ if (kind !== "page" && used.has(kind)) kind = "page";
346285
+ used.add(kind);
346286
+ const slug3 = fileBase(s.name).toLowerCase() || `page-${i2 + 1}`;
346287
+ let tree = located(holder.pages[i2].tree);
346288
+ if (s.about?.schema) tree = { ...tree, root: liquidNode(tree.root, s.about.schema, s.about.schema === "commerce.product" ? "product" : "collection") };
346289
+ const relist = (n) => {
346290
+ const schema = n.repeat && schemaOf(n.repeat.collectionId);
346291
+ const template3 = n.repeat && schema ? n.repeat.template.map((c2) => liquidNode(c2, schema, LOOP_VAR[schema])) : n.repeat?.template;
346292
+ return { ...n, children: n.children.map(relist), ...n.repeat ? { repeat: { ...n.repeat, template: (template3 ?? []).map(relist) } } : {} };
346293
+ };
346294
+ tree = { ...tree, root: relist(tree.root) };
346295
+ const source3 = kind === "collection" ? "collection.products" : kind === "search" ? "pc_results" : "collections.all.products";
346296
+ const html = serializeHTML(tree, {
346297
+ ids: true,
346298
+ lists: (n) => {
346299
+ const schema = n.repeat && schemaOf(n.repeat.collectionId);
346300
+ if (!schema || !n.repeat) return null;
346301
+ const range3 = `${n.repeat.limit ? ` limit: ${n.repeat.limit}` : ""}${n.repeat.offset ? ` offset: ${n.repeat.offset}` : ""}`;
346302
+ switch (schema) {
346303
+ case "commerce.product":
346304
+ return { open: `{% for p in ${source3}${range3} %}`, close: "{% endfor %}" };
346305
+ case "commerce.collection":
346306
+ return { open: `{% for c in collections${range3} %}{% if c.handle == 'all' or c.handle == 'frontpage' %}{% continue %}{% endif %}`, close: "{% endfor %}" };
346307
+ case "commerce.cartLine":
346308
+ return { open: `{% for line in cart.items${range3} %}`, close: "{% endfor %}" };
346309
+ case "commerce.menu":
346310
+ return { open: `{% for link in linklists['main-menu'].links${range3} %}`, close: "{% endfor %}" };
346311
+ case "commerce.cart":
346312
+ return { open: "", close: "" };
346313
+ }
346314
+ },
346315
+ form: (n) => {
346316
+ const f2 = n.action && FORMS[n.action];
346317
+ return f2 ? { attrs: { action: f2.action, method: f2.method, "accept-charset": "UTF-8" }, ...f2.fields ? { fields: f2.fields } : {} } : null;
346318
+ }
346319
+ });
346320
+ const prelude = kind === "search" ? "{%- liquid\n if search.performed\n assign pc_results = search.results | where: 'object_type', 'product'\n else\n assign pc_results = collections.all.products\n endif\n-%}\n" : "";
346321
+ const title = s.name;
346322
+ files.push({ path: `sections/popcraft-${slug3}.liquid`, content: `${prelude}${html}
346323
+
346324
+ {% schema %}
346325
+ ${JSON.stringify({ name: `${title} (PopCraft)`, settings: [] }, null, 2)}
346326
+ {% endschema %}
346327
+ ` });
346328
+ sections.push({ kind, slug: slug3, title });
346329
+ const template2 = kind === "page" ? `page.${slug3}` : kind;
346330
+ files.push({ path: `templates/${template2}.json`, content: `${JSON.stringify({ sections: { main: { type: `popcraft-${slug3}`, settings: {} } }, order: ["main"] }, null, 2)}
346331
+ ` });
346332
+ }
346333
+ const plain2 = (template2, title, body) => {
346334
+ files.push({ path: `sections/popcraft-${template2}.liquid`, content: `<div class="popcraft-plain">
346335
+ ${body}
346336
+ </div>
346337
+
346338
+ {% schema %}
346339
+ ${JSON.stringify({ name: `${title} (PopCraft)`, settings: [] }, null, 2)}
346340
+ {% endschema %}
346341
+ ` });
346342
+ files.push({ path: `templates/${template2}.json`, content: `${JSON.stringify({ sections: { main: { type: `popcraft-${template2}`, settings: {} } }, order: ["main"] }, null, 2)}
346343
+ ` });
346344
+ };
346345
+ const need = [
346346
+ ["index", "Home", "<h1>{{ shop.name }}</h1>"],
346347
+ ["product", "Product", `<h1>{{ product.title }}</h1>
346348
+ <p>{{ product.price | money }}</p>
346349
+ {% form 'product', product %}<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}"><button type="submit">Add to cart</button>{% endform %}`],
346350
+ ["collection", "Collection", '<h1>{{ collection.title }}</h1>\n {% for p in collection.products %}<a href="{{ p.url }}">{{ p.title }} \u2014 {{ p.price | money }}</a>{% endfor %}'],
346351
+ ["search", "Search", '<form action="/search"><input name="q" value="{{ search.terms | escape }}"><button>Search</button></form>\n {% for item in search.results %}<a href="{{ item.url }}">{{ item.title }}</a>{% endfor %}'],
346352
+ ["cart", "Cart", '<form action="/cart" method="post">{% for line in cart.items %}<p>{{ line.product.title }} \xD7 {{ line.quantity }}</p>{% endfor %}<button name="checkout">Check out</button></form>'],
346353
+ ["page", "Page", "<h1>{{ page.title }}</h1>\n {{ page.content }}"],
346354
+ ["404", "Not found", '<h1>Page not found</h1>\n <a href="{{ routes.root_url }}">Back to the shop</a>'],
346355
+ ["list-collections", "Collections", '{% for c in collections %}<a href="{{ c.url }}">{{ c.title }}</a>{% endfor %}'],
346356
+ ["blog", "Blog", '<h1>{{ blog.title }}</h1>\n {% for article in blog.articles %}<a href="{{ article.url }}">{{ article.title }}</a>{% endfor %}'],
346357
+ ["article", "Article", "<h1>{{ article.title }}</h1>\n {{ article.content }}"],
346358
+ ["password", "Password", `<h1>{{ shop.name }}</h1>
346359
+ <p>Opening soon.</p>
346360
+ {% form 'storefront_password' %}<input type="password" name="password"><button>Enter</button>{% endform %}`]
346361
+ ];
346362
+ for (const [template2, title, body] of need) if (!files.some((f2) => f2.path === `templates/${template2}.json`)) plain2(template2, title, body);
346363
+ files.push({ path: "templates/gift_card.liquid", content: "{% layout none %}\n<!doctype html>\n<html><head>{{ content_for_header }}</head><body><h1>{{ gift_card.balance | money }}</h1><p>{{ gift_card.code }}</p></body></html>\n" });
346364
+ const fonts = screens.map((s) => fontsHref(s.tree)).find(Boolean);
346365
+ files.push({ path: "layout/theme.liquid", content: [
346366
+ "<!doctype html>",
346367
+ '<html lang="{{ request.locale.iso_code }}">',
346368
+ "<head>",
346369
+ ' <meta charset="utf-8">',
346370
+ ' <meta name="viewport" content="width=device-width, initial-scale=1">',
346371
+ " <title>{{ page_title }}{% unless page_title contains shop.name %} \xB7 {{ shop.name }}{% endunless %}</title>",
346372
+ ' {% if page_description %}<meta name="description" content="{{ page_description | escape }}">{% endif %}',
346373
+ ' <link rel="canonical" href="{{ canonical_url }}">',
346374
+ ...fonts ? [` <link rel="stylesheet" href="${fonts}">`] : [],
346375
+ " <style>html, body { margin: 0; } *, *::before, *::after { box-sizing: border-box; } img { display: block; max-width: 100%; } .popcraft-plain { max-width: 960px; margin: 48px auto; padding: 0 20px; font-family: system-ui, sans-serif; }</style>",
346376
+ " {{ content_for_header }}",
346377
+ "</head>",
346378
+ "<body>",
346379
+ " {{ content_for_layout }}",
346380
+ "</body>",
346381
+ "</html>",
346382
+ ""
346383
+ ].join("\n") });
346384
+ files.push({ path: "config/settings_schema.json", content: `${JSON.stringify([{ name: "theme_info", theme_name: name2, theme_version: "1.0.0", theme_author: "PopCraft", theme_documentation_url: "https://popcraft.app/docs/exporting/commerce", theme_support_url: "https://popcraft.app/docs" }], null, 2)}
346385
+ ` });
346386
+ files.push({ path: "config/settings_data.json", content: `${JSON.stringify({ current: {} }, null, 2)}
346387
+ ` });
346388
+ files.push({ path: "locales/en.default.json", content: "{}\n" });
346389
+ return files;
346390
+ }
346391
+ var MEDIA_REF3, MEDIA_EXT2, LIQUID, LOOP_VAR, FORMS, every2;
346392
+ var init_shopify_theme = __esm({
346393
+ "lib/io/web/project/shopify-theme.ts"() {
346394
+ "use strict";
346395
+ init_fonts3();
346396
+ init_html();
346397
+ init_naming();
346398
+ init_assets();
346399
+ MEDIA_REF3 = /media:\/\/[A-Za-z0-9_-]+/g;
346400
+ MEDIA_EXT2 = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif", "image/avif": "avif", "image/svg+xml": "svg" };
346401
+ LIQUID = {
346402
+ "commerce.product": (v) => ({
346403
+ title: `${v}.title`,
346404
+ handle: `${v}.handle`,
346405
+ url: `${v}.url`,
346406
+ description: `${v}.description | strip_html | truncatewords: 60`,
346407
+ price: `${v}.price | money`,
346408
+ max_price: `${v}.price_max | money`,
346409
+ image: `${v}.featured_image | image_url: width: 1200`,
346410
+ image_alt: `${v}.featured_image.alt | escape`,
346411
+ available: `${v}.available`,
346412
+ variant_id: `${v}.selected_or_first_available_variant.id`,
346413
+ tags: `${v}.tags | join: ', '`
346414
+ }),
346415
+ "commerce.collection": (v) => ({ title: `${v}.title`, handle: `${v}.handle`, url: `${v}.url`, description: `${v}.description | strip_html` }),
346416
+ "commerce.cartLine": (v) => ({
346417
+ title: `${v}.product.title`,
346418
+ variant: `${v}.variant.title`,
346419
+ url: `${v}.url`,
346420
+ image: `${v}.image | image_url: width: 300`,
346421
+ quantity: `${v}.quantity`,
346422
+ price: `${v}.final_line_price | money`,
346423
+ merchandise_id: `${v}.key`
346424
+ }),
346425
+ "commerce.cart": () => ({ count: "cart.item_count", subtotal: "cart.items_subtotal_price | money", taxes: "'Calculated at checkout'", total: "cart.total_price | money", checkout_url: "routes.cart_url" }),
346426
+ "commerce.menu": (v) => ({ title: `${v}.title`, url: `${v}.url` })
346427
+ };
346428
+ LOOP_VAR = { "commerce.product": "p", "commerce.collection": "c", "commerce.cartLine": "line", "commerce.cart": "cart", "commerce.menu": "link" };
346429
+ FORMS = {
346430
+ addToCart: { action: "/cart/add", method: "post" },
346431
+ updateCartLine: { action: "/cart/change", method: "post" },
346432
+ removeCartLine: { action: "/cart/change", method: "post", fields: '<input type="hidden" name="quantity" value="0" />' },
346433
+ checkout: { action: "/cart", method: "post", fields: '<input type="hidden" name="checkout" value="1" />' },
346434
+ search: { action: "/search", method: "get" }
346435
+ };
346436
+ every2 = (n, out = []) => {
346437
+ out.push(n);
346438
+ n.children.forEach((c2) => every2(c2, out));
346439
+ n.repeat?.template.forEach((c2) => every2(c2, out));
346440
+ return out;
346441
+ };
346442
+ }
346443
+ });
346444
+
346445
+ // lib/io/web/project/storefront.ts
346446
+ var storefront_exports = {};
346447
+ __export(storefront_exports, {
346448
+ STOREFRONT_TARGETS: () => STOREFRONT_TARGETS,
346449
+ STOREFRONT_TARGET_IDS: () => STOREFRONT_TARGET_IDS,
346450
+ storefrontDesign: () => storefrontDesign
346451
+ });
346452
+ function storefrontDesign(doc2, screens) {
346453
+ const named = (id3) => doc2.collections?.[id3];
346454
+ const pages2 = screens.map((s) => {
346455
+ let root2 = s.tree.root;
346456
+ if (s.about) root2 = boundNode(root2, (key5) => `{{page.${key5}}}`);
346457
+ const relist = (n) => ({ ...n, children: n.children.map(relist), ...n.repeat ? { repeat: { ...n.repeat, template: n.repeat.template.map((c2) => relist(boundNode(c2, (key5) => `{{item.${key5}}}`))) } } : {} });
346458
+ const html = serializeHTML({ ...s.tree, root: relist(root2) }, {
346459
+ ids: true,
346460
+ lists: (n) => {
346461
+ const c2 = n.repeat && named(n.repeat.collectionId);
346462
+ if (!c2 || !n.repeat) return null;
346463
+ const range3 = `${n.repeat.limit ? ` limit="${n.repeat.limit}"` : ""}${n.repeat.offset ? ` offset="${n.repeat.offset}"` : ""}`;
346464
+ return { open: `<!-- pc:each list="${c2.name}" schema="${c2.schema ?? ""}" as="item"${range3} -->`, close: "<!-- pc:end -->" };
346465
+ },
346466
+ form: (n) => n.action ? { attrs: { "data-pc-action": n.action } } : null
346467
+ });
346468
+ const route2 = doc2.nodes[s.id]?.route?.path ?? null;
346469
+ return { name: s.name, route: route2, about: s.about?.schema ?? null, html };
346470
+ });
346471
+ const collections = Object.values(doc2.collections ?? {}).map((c2) => ({
346472
+ id: c2.id,
346473
+ name: c2.name,
346474
+ schema: c2.schema ?? null,
346475
+ fields: c2.fields.map((f2) => fieldKey(f2.name)),
346476
+ samples: recordsIn(doc2, c2.id).map((r) => Object.fromEntries(c2.fields.map((f2) => [fieldKey(f2.name), r.values[f2.id] ?? null])))
346477
+ }));
346478
+ return { name: doc2.name, pages: pages2, collections };
346479
+ }
346480
+ var STOREFRONT_TARGETS;
346481
+ var init_storefront = __esm({
346482
+ "lib/io/web/project/storefront.ts"() {
346483
+ init_components();
346484
+ init_shopify_theme();
346485
+ init_collections();
346486
+ init_html();
346487
+ init_storefront_targets();
346488
+ init_storefront_targets();
346489
+ STOREFRONT_TARGETS = {
346490
+ "next-commerce": {
346491
+ id: "next-commerce",
346492
+ ...STOREFRONT_TARGET_INFO["next-commerce"],
346493
+ files: (doc2, screens, o) => componentFiles(doc2, screens, { media: o.media, styling: o.styling })
346494
+ },
346495
+ "shopify-theme": {
346496
+ id: "shopify-theme",
346497
+ ...STOREFRONT_TARGET_INFO["shopify-theme"],
346498
+ files: (doc2, screens, o) => shopifyThemeFiles(doc2, screens, { media: o.media })
346499
+ }
346500
+ };
346501
+ }
346502
+ });
346503
+
345829
346504
  // lib/io/csv.ts
345830
346505
  function toCsv(rows, delimiter = ",") {
345831
346506
  return rows.map((r) => r.map((c2) => quote3(c2, delimiter)).join(delimiter)).join("\n");
@@ -346088,11 +346763,12 @@ async function exportSite(doc2, pageId2, packaging, styling = "css") {
346088
346763
  downloadBlob2(zipFiles2(files.map((f2) => ({ name: f2.path, bytes: bytesOf2(f2) }))), `${base}.zip`);
346089
346764
  return { summary: `Exported ${plural2(site.pages.length, "page")} as ${packaging === "next" ? "a Next.js project" : "a static site"} (${base}.zip)`, data: { filename: `${base}.zip`, packaging, pages: site.pages.map((p2) => p2.route), files: files.length } };
346090
346765
  }
346091
- var MAX_INLINE_BYTES, exportSvg, exportNodes, exportPdf, exportCopy, exportPages, exportPptx, animationParams, videoParams, gifParams, lottieParams, animationCommand, TARGET, exportMp4, exportWebm, exportGif, exportPngSequence, exportLottie2, EXPORT_NODE_CAP, exportWeb, exportBoard;
346766
+ var MAX_INLINE_BYTES, exportSvg, exportNodes, exportPdf, exportCopy, exportPages, exportPptx, animationParams, videoParams, gifParams, lottieParams, animationCommand, TARGET, exportMp4, exportWebm, exportGif, exportPngSequence, exportLottie2, EXPORT_NODE_CAP, exportWeb, exportStorefront, exportBoard;
346092
346767
  var init_export2 = __esm({
346093
346768
  "lib/commands/export.ts"() {
346094
346769
  "use strict";
346095
346770
  init_zod();
346771
+ init_storefront_targets();
346096
346772
  init_errors();
346097
346773
  init_types5();
346098
346774
  init_ui();
@@ -346493,6 +347169,58 @@ var init_export2 = __esm({
346493
347169
  return { summary: `Exported ${node2.name || "layer"} as ${filename}`, data: { filename, format, bytes: markup.length, truncated: tree.truncated } };
346494
347170
  }
346495
347171
  });
347172
+ exportStorefront = defineCommand({
347173
+ id: "export.storefront",
347174
+ title: "Export storefront",
347175
+ domain: "export",
347176
+ effect: "none",
347177
+ permission: "export",
347178
+ needs: ["canvas"],
347179
+ params: external_exports.object({
347180
+ target: external_exports.enum(STOREFRONT_TARGET_IDS).default("next-commerce").describe("next-commerce: React components a Next.js Commerce store renders with its own data and server actions. shopify-theme: an Online Store 2.0 theme (Liquid) to upload in the store's admin, running on Shopify with its own cart and checkout."),
347181
+ styling: external_exports.enum(["inline", "tailwind"]).optional().describe("next-commerce: style objects (default) or Tailwind CSS v4 classes."),
347182
+ dir: external_exports.string().max(200).optional().describe("next-commerce: where the components go in the store's project (default components/popcraft).")
347183
+ }).strict(),
347184
+ expose: {
347185
+ ui: {
347186
+ label: "Export storefront\u2026",
347187
+ keywords: ["commerce", "shopify", "theme", "liquid", "store", "shop", "next", "react", "components", "export"],
347188
+ variants: [
347189
+ { id: "next-commerce", label: "Export storefront as Next.js Commerce components", params: { target: "next-commerce" } },
347190
+ { id: "shopify-theme", label: "Export storefront as a Shopify theme", params: { target: "shopify-theme" } }
347191
+ ]
347192
+ },
347193
+ agent: { name: "export_storefront", readOnly: false, description: "Download a storefront design (a page whose lists and pages read collections with a commerce schema) as a store's project, in a zip. target next-commerce: a React component per screen, each list a typed prop of Next.js Commerce's own types, each storefront form (addToCart, updateCartLine, removeCartLine, checkout, search) an action prop for a server action. target shopify-theme: an Online Store 2.0 theme, each screen a section and template (home: index, a page about a product: product, about a category: collection, search, cart), each list a Liquid loop over the store's products, cart or menu, each form posting to Shopify's cart; uploaded in Online Store \u203A Themes. With no store data a Next.js Commerce component draws the design's samples." }
347194
+ },
347195
+ run: async (args2, ctx2) => {
347196
+ const doc2 = ctx2.doc();
347197
+ const [{ componentMedia: componentMedia2, componentScreens: componentScreens2 }, { STOREFRONT_TARGETS: STOREFRONT_TARGETS2 }, { bytesOf: bytesOf2 }, { fileBase: fileBase2, flatten: flatten3, resolveWebAssets: resolveWebAssets2 }, { rendererAssetResolvers: rendererAssetResolvers2 }, { zipFiles: zipFiles2 }, { downloadBlob: downloadBlob2 }, { loadMediaBlob: loadMediaBlob2 }] = await Promise.all([
347198
+ Promise.resolve().then(() => (init_components(), components_exports)),
347199
+ Promise.resolve().then(() => (init_storefront(), storefront_exports)),
347200
+ Promise.resolve().then(() => (init_project(), project_exports)),
347201
+ Promise.resolve().then(() => (init_web(), web_exports)),
347202
+ Promise.resolve().then(() => (init_resolvers(), resolvers_exports)),
347203
+ Promise.resolve().then(() => (init_zip_export(), zip_export_exports)),
347204
+ Promise.resolve().then(() => (init_download(), download_exports)),
347205
+ Promise.resolve().then(() => (init_media_store(), media_store_exports))
347206
+ ]);
347207
+ const target2 = STOREFRONT_TARGETS2[args2.target];
347208
+ const screens = componentScreens2(doc2, ctx2.pageId(), EXPORT_NODE_CAP);
347209
+ if (!screens.length) throw new ToolError("This page has no screens: a top-level frame is a page of the store.");
347210
+ const resolvers = rendererAssetResolvers2(doc2);
347211
+ for (const s of screens) if (flatten3(s.tree.root).some((n) => n.asset)) await resolveWebAssets2(s.tree, resolvers);
347212
+ const media2 = {};
347213
+ await Promise.all(componentMedia2(doc2, screens).map(async (ref3) => {
347214
+ const blob = await loadMediaBlob2(ref3).catch(() => null);
347215
+ if (blob) media2[ref3] = { bytes: new Uint8Array(await blob.arrayBuffer()), type: blob.type };
347216
+ }));
347217
+ const files = args2.target === "next-commerce" && args2.dir ? (await Promise.resolve().then(() => (init_components(), components_exports))).componentFiles(doc2, screens, { styling: args2.styling, dir: args2.dir, media: media2 }) : target2.files(doc2, screens, { media: media2, styling: args2.styling });
347218
+ const base = `${fileBase2(doc2.name)}-${args2.target}`;
347219
+ downloadBlob2(zipFiles2(files.map((f2) => ({ name: f2.path, bytes: bytesOf2(f2) }))), `${base}.zip`);
347220
+ const bound = screens.filter((s) => s.about || flatten3(s.tree.root).some((n) => n.repeat && doc2.collections?.[n.repeat.collectionId]?.schema)).length;
347221
+ return { summary: `Exported ${plural2(screens.length, "page")} (${bound} bound to store data) as ${target2.label} (${base}.zip)`, data: { filename: `${base}.zip`, target: args2.target, pages: screens.map((s) => s.name), files: files.length } };
347222
+ }
347223
+ });
346496
347224
  exportBoard = defineCommand({
346497
347225
  id: "export.board",
346498
347226
  title: "Export board",
@@ -347244,7 +347972,7 @@ function selectedImageTarget(doc2, selection) {
347244
347972
  }
347245
347973
  function withImage(node2, ref3) {
347246
347974
  if (node2.type === "IMAGE") return { imageUrl: ref3 };
347247
- const fills = (node2.fills ?? []).map((p2, i2, all3) => p2.type === "IMAGE" && all3.findIndex((q) => q.type === "IMAGE") === i2 ? { ...p2, imageUrl: ref3, imageHash: void 0 } : p2);
347975
+ const fills = (node2.fills ?? []).map((p2, i2, all4) => p2.type === "IMAGE" && all4.findIndex((q) => q.type === "IMAGE") === i2 ? { ...p2, imageUrl: ref3, imageHash: void 0 } : p2);
347248
347976
  return { fills };
347249
347977
  }
347250
347978
  function importSvgLayers(doc2, pageId2, svg, name2, cx, cy) {
@@ -348333,9 +349061,9 @@ var init_motion2 = __esm({
348333
349061
  },
348334
349062
  run: (args2, ctx2) => {
348335
349063
  const doc2 = ctx2.doc();
348336
- const all3 = Object.values(doc2.compositions ?? {});
349064
+ const all4 = Object.values(doc2.compositions ?? {});
348337
349065
  if (args2.id && !doc2.compositions?.[args2.id]) throw new ToolError(`There is no composition "${args2.id}".`);
348338
- const list2 = args2.id ? [doc2.compositions[args2.id]] : all3;
349066
+ const list2 = args2.id ? [doc2.compositions[args2.id]] : all4;
348339
349067
  const hex10 = (c2) => c2 ? "#" + [c2.r, c2.g, c2.b].map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("") : void 0;
348340
349068
  const data = list2.map((c2) => ({
348341
349069
  id: c2.id,
@@ -352289,7 +353017,6 @@ function publishSite(key5, files, opts = {}) {
352289
353017
  var siteStatus, setSiteVersion, renameSite;
352290
353018
  var init_sites = __esm({
352291
353019
  "lib/client/cloud/sites.ts"() {
352292
- "use strict";
352293
353020
  init_files();
352294
353021
  init_upload2();
352295
353022
  init_api4();
@@ -352417,7 +353144,8 @@ __export(integrations_exports, {
352417
353144
  listWorkspaceIntegrations: () => listWorkspaceIntegrations,
352418
353145
  removeDestination: () => removeDestination,
352419
353146
  setSignIn: () => setSignIn,
352420
- siteBackend: () => siteBackend
353147
+ siteBackend: () => siteBackend,
353148
+ storeProducts: () => storeProducts
352421
353149
  });
352422
353150
  function connectWithOAuth(workspaceId, provider2) {
352423
353151
  const popup = window.open("", "popcraft-integration", "popup,width=640,height=760");
@@ -352450,7 +353178,7 @@ function connectWithOAuth(workspaceId, provider2) {
352450
353178
  });
352451
353179
  });
352452
353180
  }
352453
- var integrationsWorkspaceId, listWorkspaceIntegrations, connectWithToken, disconnect, listResources, createResource, listDestinations, addDestination, removeDestination, deployDestination, siteBackend, attachDatabase, setSignIn, detachDatabase, listSubmissions, listCandidates, createDatabase, createDestination;
353181
+ var integrationsWorkspaceId, listWorkspaceIntegrations, connectWithToken, disconnect, listResources, createResource, listDestinations, addDestination, removeDestination, deployDestination, siteBackend, attachDatabase, setSignIn, detachDatabase, listSubmissions, listCandidates, createDatabase, createDestination, storeProducts;
352454
353182
  var init_integrations = __esm({
352455
353183
  "lib/client/cloud/integrations.ts"() {
352456
353184
  init_api4();
@@ -352473,6 +353201,7 @@ var init_integrations = __esm({
352473
353201
  listCandidates = async (workspaceId, capability2) => (await api(`/api/workspaces/${encodeURIComponent(workspaceId)}/integrations/candidates?capability=${capability2}`)).candidates;
352474
353202
  createDatabase = (key5, provider2, connectionId) => api(`/api/sites/${key5}/backend`, { method: "PUT", json: { create: true, ...provider2 ? { provider: provider2 } : {}, ...connectionId ? { connectionId } : {} } });
352475
353203
  createDestination = async (key5, capability2, provider2, connectionId) => (await api(`/api/sites/${key5}/destinations`, { method: "POST", json: { create: true, capability: capability2, ...provider2 ? { provider: provider2 } : {}, ...connectionId ? { connectionId } : {} } })).destination;
353204
+ storeProducts = async (connectionId, first2) => (await api(`/api/integrations/connections/${encodeURIComponent(connectionId)}/products?first=${first2}`)).products;
352476
353205
  }
352477
353206
  });
352478
353207
 
@@ -353542,11 +354271,11 @@ function allThemes(s) {
353542
354271
  return map2;
353543
354272
  }
353544
354273
  function pickableThemes(s) {
353545
- const all3 = allThemes(s);
354274
+ const all4 = allThemes(s);
353546
354275
  const builtinIds = new Set(BUILTIN_THEMES.map((t3) => t3.id));
353547
354276
  return [
353548
- ...PICKABLE_BUILTIN_IDS.map((id3) => all3.get(id3)),
353549
- ...[...all3.values()].filter((t3) => !builtinIds.has(t3.id))
354277
+ ...PICKABLE_BUILTIN_IDS.map((id3) => all4.get(id3)),
354278
+ ...[...all4.values()].filter((t3) => !builtinIds.has(t3.id))
353550
354279
  ];
353551
354280
  }
353552
354281
  function computeAppliedTheme(s) {
@@ -353585,15 +354314,15 @@ function usePickableThemes() {
353585
354314
  const userThemes = useThemeStore((s) => s.userThemes);
353586
354315
  const pluginThemes = useThemeStore((s) => s.pluginThemes);
353587
354316
  const localThemes = useThemeStore((s) => s.localThemes);
353588
- return (0, import_react8.useMemo)(() => pickableThemes({ userThemes, pluginThemes, localThemes }), [userThemes, pluginThemes, localThemes]);
354317
+ return (0, import_react9.useMemo)(() => pickableThemes({ userThemes, pluginThemes, localThemes }), [userThemes, pluginThemes, localThemes]);
353589
354318
  }
353590
- var import_react8, IDB_KEY, useThemeStore, cssString, defaultAccent, effectiveAccent;
354319
+ var import_react9, IDB_KEY, useThemeStore, cssString, defaultAccent, effectiveAccent;
353591
354320
  var init_theme_store = __esm({
353592
354321
  "lib/theme/theme-store.ts"() {
353593
354322
  init_contrast();
353594
354323
  init_idb();
353595
354324
  init_esm();
353596
- import_react8 = __toESM(require_react());
354325
+ import_react9 = __toESM(require_react());
353597
354326
  init_boot2();
353598
354327
  init_builtin();
353599
354328
  init_resolve();
@@ -354987,6 +355716,62 @@ before proposing anything, and recommend the least that covers it.
354987
355716
  }
354988
355717
  });
354989
355718
 
355719
+ // lib/agent/skills/builtin/storefront.ts
355720
+ var storefront;
355721
+ var init_storefront2 = __esm({
355722
+ "lib/agent/skills/builtin/storefront.ts"() {
355723
+ "use strict";
355724
+ storefront = {
355725
+ id: "storefront",
355726
+ name: "Design a storefront",
355727
+ description: "Bind a shop to a store's data and export it for Next.js Commerce or Shopify.",
355728
+ whenToUse: "the design should sell",
355729
+ tools: ["get_document_outline", "edit_collection", "edit_records", "set_repeat", "set_route", "set_control", "export_storefront", "list_integrations", "open_integrations", "pull_store_products", "ask_user", "manage_todos"],
355730
+ instructions: `# Design a storefront
355731
+
355732
+ PopCraft draws the shop; a Next.js Commerce store runs it. The store keeps Shopify, the cart, checkout and search;
355733
+ the design binds to the store's data through collections with a **commerce schema**, and is exported as React
355734
+ components the store renders with its products and server actions. Starting fresh, the Fieldwork and Kiln templates
355735
+ are whole stores to begin from.
355736
+
355737
+ ## Steps
355738
+
355739
+ 1. **Read the design.** \`get_document_outline\`: which screens are the store's pages (home, search, a product, the
355740
+ cart), where products are listed, where the cart and its totals are.
355741
+ 2. **The data.** \`edit_collection\` with \`schema\` and no fields makes a collection whose fields are the schema's:
355742
+ - \`commerce.product\` for products (title, handle, url, description, price, image, variant_id\u2026)
355743
+ - \`commerce.cartLine\` for the cart's lines, \`commerce.cart\` for its count and totals (one record)
355744
+ - \`commerce.menu\` for the store's menu, \`commerce.collection\` for its categories
355745
+ Add a few real sample records with \`edit_records\`: the canvas is drawn with them. When the user has a Shopify
355746
+ store, draw the design with its real products instead: \`list_integrations\` (capability commerce) for a connected
355747
+ store, then \`pull_store_products\`. You cannot connect a store yourself: say why it helps and \`open_integrations\`.
355748
+ 3. **The lists.** \`set_repeat\` on the frame that holds one card, over the collection; the card's texts say
355749
+ \`{{item.title}}\`, \`{{item.price}}\`, and its picture follows \`item_image\`. The cart's totals are a list of one
355750
+ (\`limit: 1\`) over the \`commerce.cart\` collection.
355751
+ 4. **A page per product.** \`set_route\` on the product screen with the product collection's id: the route is
355752
+ \`/product\` and each product's page is \`/product/<handle>\`. A card links to that screen.
355753
+ 5. **The forms.** \`set_control\` FORM with a storefront \`formAction\`:
355754
+ - \`addToCart\`: needs a hidden INPUT named \`merchandiseId\`, value \`{{item.variant_id}}\`
355755
+ - \`removeCartLine\` or \`updateCartLine\` inside a cart line: \`merchandiseId\` is \`{{item.merchandise_id}}\`
355756
+ - \`checkout\`
355757
+ - \`search\`: a field named \`q\`
355758
+ 6. **Export.** \`export_storefront\` writes the store's project, for the store the user runs:
355759
+ - \`target: 'next-commerce'\`: components, a prop per list (\`products\`, \`cartLines\`, \`cart\`, \`menu\`), \`product\` on a
355760
+ product's page, and \`onAddToCart\`, \`onCheckout\`\u2026 for the forms; the store passes its \`lib/shopify\` data and its
355761
+ server actions.
355762
+ - \`target: 'shopify-theme'\`: an Online Store 2.0 theme the user uploads in Shopify (Online Store \u203A Themes), running
355763
+ on Shopify with its own cart and checkout.
355764
+ Say which, and where it goes (/docs/exporting/commerce).
355765
+
355766
+ ## Rules
355767
+
355768
+ - Never invent a Shopify connection, an API key or an order flow in the design: the store owns those.
355769
+ - Product copy is real (a material, a size, what it is for), never lorem ipsum.
355770
+ - One schema per collection; two lists of one collection (featured, new) share one prop, each with its own limit.`
355771
+ };
355772
+ }
355773
+ });
355774
+
354990
355775
  // lib/agent/skills/builtin/index.ts
354991
355776
  var BUILTIN_SKILLS;
354992
355777
  var init_builtin2 = __esm({
@@ -355011,6 +355796,7 @@ var init_builtin2 = __esm({
355011
355796
  init_design_code();
355012
355797
  init_design_review();
355013
355798
  init_web_app();
355799
+ init_storefront2();
355014
355800
  BUILTIN_SKILLS = [
355015
355801
  fillContent,
355016
355802
  autoLayout,
@@ -355030,7 +355816,8 @@ var init_builtin2 = __esm({
355030
355816
  campaignPack,
355031
355817
  designCode,
355032
355818
  designReview,
355033
- webApp
355819
+ webApp,
355820
+ storefront
355034
355821
  ].map((s) => ({ ...s, source: "builtin" }));
355035
355822
  }
355036
355823
  });
@@ -357376,16 +358163,16 @@ var init_api5 = __esm({
357376
358163
  }
357377
358164
  });
357378
358165
 
357379
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/generated/runtime.js
358166
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/generated/runtime.js
357380
358167
  var MAIN_RUNTIME, UI_RUNTIME;
357381
358168
  var init_runtime4 = __esm({
357382
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/generated/runtime.js"() {
357383
- MAIN_RUNTIME = '"use strict";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: "rpc", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener("message", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== "popcraft-plugin-port" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === "result" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === "event" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/main.ts\n function clone(v) {\n return v === void 0 ? {} : JSON.parse(JSON.stringify(v));\n }\n function createMainApi(rpc = createRpcClient()) {\n const uiHandlers = [];\n let uiOnMessage = null;\n const uiQueue = [];\n function deliverUI(msg) {\n for (const cb of uiHandlers) {\n try {\n cb(msg);\n } catch (e) {\n console.error(e);\n }\n }\n if (typeof uiOnMessage === "function") {\n try {\n uiOnMessage(msg);\n } catch (e) {\n console.error(e);\n }\n }\n }\n function flushUI() {\n if (uiHandlers.length || typeof uiOnMessage === "function") uiQueue.splice(0).forEach(deliverUI);\n }\n rpc.on("uimessage", (msg) => {\n if (!uiHandlers.length && typeof uiOnMessage !== "function") uiQueue.push(msg);\n else deliverUI(msg);\n });\n let widgetDef = null;\n const widgetQueue = [];\n function renderWidget(state) {\n const handlers = {};\n function walk(el, path) {\n if (!el || typeof el !== "object") return el;\n const out = {};\n for (const k of Object.keys(el)) {\n const v = el[k];\n if (k === "children") {\n out.children = (Array.isArray(v) ? v : [v]).filter((c) => c !== null && c !== void 0 && c !== false).map((c, i) => walk(c, path + "." + i));\n } else if (k === "onClick" && typeof v === "function") {\n handlers[path] = v;\n out.onClick = path;\n } else {\n out[k] = v;\n }\n }\n return out;\n }\n return { tree: walk(widgetDef.render(state), "root"), handlers };\n }\n function initialWidgetState() {\n const s = widgetDef && widgetDef.initialState;\n return clone(typeof s === "function" ? s() : s);\n }\n function commitWidget(nodeId, state) {\n return rpc.call("widgetUpdate", { id: nodeId, state, tree: renderWidget(clone(state)).tree });\n }\n function handleWidgetClick(e) {\n Promise.resolve().then(() => {\n const state = clone(e.state);\n const rendered = renderWidget(state);\n const h = rendered.handlers[e.handler] || widgetDef.handlers && widgetDef.handlers[e.handler];\n if (typeof h !== "function") return;\n return Promise.resolve(h(state, { nodeId: e.nodeId })).then((ret) => commitWidget(e.nodeId, ret === void 0 ? state : ret));\n }).catch((err) => rpc.send({ kind: "error", message: String((err == null ? void 0 : err.message) || err) }));\n }\n rpc.on("widgetclick", (e) => {\n if (widgetDef) handleWidgetClick(e);\n else widgetQueue.push(e);\n });\n const popcraft = {\n /** Which menu command the user picked, or null when the plugin ran some other way. */\n command: null,\n /** Host import mode when launched as an importer, else null. */\n importMode: null,\n /** Pending import file when the host handed one over, else null. */\n importFile: null,\n /** Pending export request when the host started this plugin as an exporter, else null. */\n exportRequest: null,\n getSelection: () => rpc.call("getSelection"),\n setSelection: (ids) => rpc.call("setSelection", { ids }),\n getNode: (id) => rpc.call("getNode", { id }),\n findNodes: (query) => rpc.call("findNodes", query || {}),\n getCurrentPage: () => rpc.call("getCurrentPage"),\n createNode: (type, props) => rpc.call("createNode", { type, props: props || {} }),\n updateNode: (id, props) => rpc.call("updateNode", { id, props }),\n deleteNode: (id) => rpc.call("deleteNode", { id }),\n setText: (id, characters) => rpc.call("setText", { id, characters }),\n setTextPath: (opts) => rpc.call("setTextPath", opts || {}),\n flowTextIntoShape: (opts) => rpc.call("flowTextIntoShape", opts || {}),\n detachText: (opts) => rpc.call("detachText", opts || {}),\n setTextFit: (opts) => rpc.call("setTextFit", opts || {}),\n createPage: (name) => rpc.call("createPage", name != null ? { name: String(name) } : {}),\n setCurrentPage: (pageId) => rpc.call("setCurrentPage", { pageId }),\n createImage: (opts) => rpc.call("createImage", opts || {}),\n createDocument: (opts) => rpc.call("createDocument", opts || {}),\n getImportMode: () => rpc.call("getImportMode"),\n getImportFile: () => rpc.call("getImportFile"),\n getExportRequest: () => rpc.call("getExportRequest"),\n saveExport: (file) => rpc.call("saveExport", file || {}),\n getPresentationAudio: (opts) => rpc.call("getPresentationAudio", opts || {}),\n setPresentationAudioSettings: (opts) => rpc.call("setPresentationAudioSettings", opts || {}),\n setPresentationAudioClip: (opts) => rpc.call("setPresentationAudioClip", opts || {}),\n removePresentationAudioClip: (opts) => rpc.call("removePresentationAudioClip", opts || {}),\n setPresentationTiming: (opts) => rpc.call("setPresentationTiming", opts || {}),\n getPresentationMix: (opts) => rpc.call("getPresentationMix", opts || {}),\n requestPresentationAudio: (opts) => rpc.call("requestPresentationAudio", opts || {}),\n moveNodes: (opts) => rpc.call("moveNodes", opts || {}),\n resizeNodes: (opts) => rpc.call("resizeNodes", opts || {}),\n renameNodes: (opts) => rpc.call("renameNodes", opts || {}),\n reparentNodes: (opts) => rpc.call("reparentNodes", opts || {}),\n duplicateNodes: (opts) => rpc.call("duplicateNodes", opts || {}),\n groupNodes: (opts) => rpc.call("groupNodes", opts || {}),\n ungroupNodes: (opts) => rpc.call("ungroupNodes", opts || {}),\n alignNodes: (opts) => rpc.call("alignNodes", opts || {}),\n distributeNodes: (opts) => rpc.call("distributeNodes", opts || {}),\n flipNodes: (opts) => rpc.call("flipNodes", opts || {}),\n rotateNodes: (opts) => rpc.call("rotateNodes", opts || {}),\n reorderNodes: (opts) => rpc.call("reorderNodes", opts || {}),\n setLocked: (opts) => rpc.call("setLocked", opts || {}),\n setVisible: (opts) => rpc.call("setVisible", opts || {}),\n setAutoLayout: (opts) => rpc.call("setAutoLayout", opts || {}),\n importSvg: (opts) => rpc.call("importSvg", opts || {}),\n measureNodes: (opts) => rpc.call("measureNodes", opts || {}),\n setFills: (opts) => rpc.call("setFills", opts || {}),\n setStrokes: (opts) => rpc.call("setStrokes", opts || {}),\n setEffects: (opts) => rpc.call("setEffects", opts || {}),\n replaceColor: (opts) => rpc.call("replaceColor", opts || {}),\n setTypography: (opts) => rpc.call("setTypography", opts || {}),\n applyStyle: (opts) => rpc.call("applyStyle", opts || {}),\n createComponent: (opts) => rpc.call("createComponent", opts || {}),\n createInstance: (opts) => rpc.call("createInstance", opts || {}),\n renamePage: (opts) => rpc.call("renamePage", opts || {}),\n getDocumentOutline: (opts) => rpc.call("getDocumentOutline", opts || {}),\n setPageSetup: (opts) => rpc.call("setPageSetup", opts || {}),\n setShowAdvance: (opts) => rpc.call("setShowAdvance", opts || {}),\n booleanOperation: (opts) => rpc.call("booleanOperation", opts || {}),\n flattenNodes: (opts) => rpc.call("flattenNodes", opts || {}),\n outlineStroke: (opts) => rpc.call("outlineStroke", opts || {}),\n setMask: (opts) => rpc.call("setMask", opts || {}),\n setMaskType: (opts) => rpc.call("setMaskType", opts || {}),\n shapeBuilder: (opts) => rpc.call("shapeBuilder", opts || {}),\n setKeyframes: (opts) => rpc.call("setKeyframes", opts || {}),\n getAnimation: (opts) => rpc.call("getAnimation", opts || {}),\n getCompositions: (opts) => rpc.call("getCompositions", opts || {}),\n editBehaviours: (opts) => rpc.call("editBehaviours", opts || {}),\n setTimeline: (opts) => rpc.call("setTimeline", opts || {}),\n setLayerTiming: (opts) => rpc.call("setLayerTiming", opts || {}),\n editTextAnimation: (opts) => rpc.call("editTextAnimation", opts || {}),\n edit3DScene: (opts) => rpc.call("edit3DScene", opts || {}),\n setInstancePlayback: (opts) => rpc.call("setInstancePlayback", opts || {}),\n editComponentProperty: (opts) => rpc.call("editComponentProperty", opts || {}),\n setShaderFill: (opts) => rpc.call("setShaderFill", opts || {}),\n createComposition: (opts) => rpc.call("createComposition", opts || {}),\n editComposition: (opts) => rpc.call("editComposition", opts || {}),\n editStateMachine: (opts) => rpc.call("editStateMachine", opts || {}),\n getStateMachine: (opts) => rpc.call("getStateMachine", opts || {}),\n applyMotionPreset: (opts) => rpc.call("applyMotionPreset", opts || {}),\n staggerMotion: (opts) => rpc.call("staggerMotion", opts || {}),\n createMotionComponent: (opts) => rpc.call("createMotionComponent", opts || {}),\n addAudio: (opts) => rpc.call("addAudio", opts || {}),\n editFrameAudio: (opts) => rpc.call("editFrameAudio", opts || {}),\n editVideoClip: (opts) => rpc.call("editVideoClip", opts || {}),\n detectAudioOnsets: (opts) => rpc.call("detectAudioOnsets", opts || {}),\n cutToBeat: (opts) => rpc.call("cutToBeat", opts || {}),\n editPrototype: (opts) => rpc.call("editPrototype", opts || {}),\n importFile: (opts) => rpc.call("importFile", opts || {}),\n usePresentationFallbackAudio: (opts) => rpc.call("usePresentationFallbackAudio", opts || {}),\n getStyles: () => rpc.call("getStyles"),\n createPaintStyle: (name, paints) => rpc.call("createPaintStyle", { name, paints }),\n getVariables: () => rpc.call("getVariables"),\n setVariableValue: (variableId, modeId, value) => rpc.call("setVariableValue", { variableId, modeId, value }),\n exportNode: (id, options) => rpc.call("exportNode", { id, options: options || {} }),\n openExternal: (url) => rpc.call("openExternal", { url }),\n notify: (message, options) => rpc.call("notify", { message: String(message), error: !!(options && options.error) }),\n showUI: (options) => rpc.call("showUI", options || {}),\n hideUI: () => rpc.call("hideUI"),\n /** Ends the run. The optional message is shown as a notification. */\n closePlugin: (message) => rpc.call("closePlugin", { message }),\n storage: {\n get: (key) => rpc.call("storageGet", { key }),\n set: (key, value) => rpc.call("storageSet", { key, value })\n },\n ui: {\n get onmessage() {\n return uiOnMessage;\n },\n set onmessage(cb) {\n uiOnMessage = cb;\n flushUI();\n },\n postMessage: (msg) => rpc.send({ kind: "toUI", data: msg }),\n on: (cb) => {\n uiHandlers.push(cb);\n flushUI();\n }\n },\n widget: {\n register(def) {\n if (!def || typeof def.render !== "function") throw new Error("popcraft.widget.register needs a render(state) function");\n widgetDef = def;\n widgetQueue.splice(0).forEach(handleWidgetClick);\n },\n insert(options) {\n if (!widgetDef) return Promise.reject(new Error("Call popcraft.widget.register first"));\n const o = options || {};\n const state = o.state !== void 0 ? clone(o.state) : initialWidgetState();\n return rpc.call("widgetInsert", { state, tree: renderWidget(state).tree, x: o.x, y: o.y });\n },\n getState: (nodeId) => rpc.call("widgetGet", { id: nodeId }),\n setState(nodeId, state) {\n if (!widgetDef) return Promise.reject(new Error("Call popcraft.widget.register first"));\n return commitWidget(nodeId, clone(state));\n }\n },\n on: (event, cb) => rpc.on(event, cb),\n off: (event, cb) => rpc.off(event, cb)\n };\n rpc.on("init", (data) => {\n var _a, _b, _c;\n popcraft.command = data.command;\n popcraft.exportRequest = (_c = data.exportRequest) != null ? _c : null;\n popcraft.importMode = (_a = data.importMode) != null ? _a : null;\n popcraft.importFile = (_b = data.importFile) != null ? _b : null;\n });\n return popcraft;\n }\n function installMainApi() {\n const rpc = createRpcClient();\n const api = createMainApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n window.addEventListener("error", (e) => rpc.send({ kind: "error", message: String(e.message || e) }));\n window.addEventListener("unhandledrejection", (e) => rpc.send({ kind: "error", message: String(e.reason && e.reason.message || e.reason) }));\n return api;\n }\n\n // src/runtime/main.entry.ts\n installMainApi();\n})();';
358169
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/generated/runtime.js"() {
358170
+ MAIN_RUNTIME = '"use strict";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: "rpc", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener("message", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== "popcraft-plugin-port" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === "result" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === "event" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/main.ts\n function clone(v) {\n return v === void 0 ? {} : JSON.parse(JSON.stringify(v));\n }\n function createMainApi(rpc = createRpcClient()) {\n const uiHandlers = [];\n let uiOnMessage = null;\n const uiQueue = [];\n function deliverUI(msg) {\n for (const cb of uiHandlers) {\n try {\n cb(msg);\n } catch (e) {\n console.error(e);\n }\n }\n if (typeof uiOnMessage === "function") {\n try {\n uiOnMessage(msg);\n } catch (e) {\n console.error(e);\n }\n }\n }\n function flushUI() {\n if (uiHandlers.length || typeof uiOnMessage === "function") uiQueue.splice(0).forEach(deliverUI);\n }\n rpc.on("uimessage", (msg) => {\n if (!uiHandlers.length && typeof uiOnMessage !== "function") uiQueue.push(msg);\n else deliverUI(msg);\n });\n let widgetDef = null;\n const widgetQueue = [];\n function renderWidget(state) {\n const handlers = {};\n function walk(el, path) {\n if (!el || typeof el !== "object") return el;\n const out = {};\n for (const k of Object.keys(el)) {\n const v = el[k];\n if (k === "children") {\n out.children = (Array.isArray(v) ? v : [v]).filter((c) => c !== null && c !== void 0 && c !== false).map((c, i) => walk(c, path + "." + i));\n } else if (k === "onClick" && typeof v === "function") {\n handlers[path] = v;\n out.onClick = path;\n } else {\n out[k] = v;\n }\n }\n return out;\n }\n return { tree: walk(widgetDef.render(state), "root"), handlers };\n }\n function initialWidgetState() {\n const s = widgetDef && widgetDef.initialState;\n return clone(typeof s === "function" ? s() : s);\n }\n function commitWidget(nodeId, state) {\n return rpc.call("widgetUpdate", { id: nodeId, state, tree: renderWidget(clone(state)).tree });\n }\n function handleWidgetClick(e) {\n Promise.resolve().then(() => {\n const state = clone(e.state);\n const rendered = renderWidget(state);\n const h = rendered.handlers[e.handler] || widgetDef.handlers && widgetDef.handlers[e.handler];\n if (typeof h !== "function") return;\n return Promise.resolve(h(state, { nodeId: e.nodeId })).then((ret) => commitWidget(e.nodeId, ret === void 0 ? state : ret));\n }).catch((err) => rpc.send({ kind: "error", message: String((err == null ? void 0 : err.message) || err) }));\n }\n rpc.on("widgetclick", (e) => {\n if (widgetDef) handleWidgetClick(e);\n else widgetQueue.push(e);\n });\n const popcraft = {\n /** Which menu command the user picked, or null when the plugin ran some other way. */\n command: null,\n /** Host import mode when launched as an importer, else null. */\n importMode: null,\n /** Pending import file when the host handed one over, else null. */\n importFile: null,\n /** Pending export request when the host started this plugin as an exporter, else null. */\n exportRequest: null,\n getSelection: () => rpc.call("getSelection"),\n setSelection: (ids) => rpc.call("setSelection", { ids }),\n getNode: (id) => rpc.call("getNode", { id }),\n findNodes: (query) => rpc.call("findNodes", query || {}),\n getCurrentPage: () => rpc.call("getCurrentPage"),\n createNode: (type, props) => rpc.call("createNode", { type, props: props || {} }),\n updateNode: (id, props) => rpc.call("updateNode", { id, props }),\n deleteNode: (id) => rpc.call("deleteNode", { id }),\n setText: (id, characters) => rpc.call("setText", { id, characters }),\n setTextPath: (opts) => rpc.call("setTextPath", opts || {}),\n flowTextIntoShape: (opts) => rpc.call("flowTextIntoShape", opts || {}),\n detachText: (opts) => rpc.call("detachText", opts || {}),\n setTextFit: (opts) => rpc.call("setTextFit", opts || {}),\n createPage: (name) => rpc.call("createPage", name != null ? { name: String(name) } : {}),\n setCurrentPage: (pageId) => rpc.call("setCurrentPage", { pageId }),\n createImage: (opts) => rpc.call("createImage", opts || {}),\n createDocument: (opts) => rpc.call("createDocument", opts || {}),\n getImportMode: () => rpc.call("getImportMode"),\n getImportFile: () => rpc.call("getImportFile"),\n getStorefront: () => rpc.call("getStorefront"),\n getExportRequest: () => rpc.call("getExportRequest"),\n saveExport: (file) => rpc.call("saveExport", file || {}),\n getPresentationAudio: (opts) => rpc.call("getPresentationAudio", opts || {}),\n setPresentationAudioSettings: (opts) => rpc.call("setPresentationAudioSettings", opts || {}),\n setPresentationAudioClip: (opts) => rpc.call("setPresentationAudioClip", opts || {}),\n removePresentationAudioClip: (opts) => rpc.call("removePresentationAudioClip", opts || {}),\n setPresentationTiming: (opts) => rpc.call("setPresentationTiming", opts || {}),\n getPresentationMix: (opts) => rpc.call("getPresentationMix", opts || {}),\n requestPresentationAudio: (opts) => rpc.call("requestPresentationAudio", opts || {}),\n moveNodes: (opts) => rpc.call("moveNodes", opts || {}),\n resizeNodes: (opts) => rpc.call("resizeNodes", opts || {}),\n renameNodes: (opts) => rpc.call("renameNodes", opts || {}),\n reparentNodes: (opts) => rpc.call("reparentNodes", opts || {}),\n duplicateNodes: (opts) => rpc.call("duplicateNodes", opts || {}),\n groupNodes: (opts) => rpc.call("groupNodes", opts || {}),\n ungroupNodes: (opts) => rpc.call("ungroupNodes", opts || {}),\n alignNodes: (opts) => rpc.call("alignNodes", opts || {}),\n distributeNodes: (opts) => rpc.call("distributeNodes", opts || {}),\n flipNodes: (opts) => rpc.call("flipNodes", opts || {}),\n rotateNodes: (opts) => rpc.call("rotateNodes", opts || {}),\n reorderNodes: (opts) => rpc.call("reorderNodes", opts || {}),\n setLocked: (opts) => rpc.call("setLocked", opts || {}),\n setVisible: (opts) => rpc.call("setVisible", opts || {}),\n setAutoLayout: (opts) => rpc.call("setAutoLayout", opts || {}),\n importSvg: (opts) => rpc.call("importSvg", opts || {}),\n measureNodes: (opts) => rpc.call("measureNodes", opts || {}),\n setFills: (opts) => rpc.call("setFills", opts || {}),\n setStrokes: (opts) => rpc.call("setStrokes", opts || {}),\n setEffects: (opts) => rpc.call("setEffects", opts || {}),\n replaceColor: (opts) => rpc.call("replaceColor", opts || {}),\n setTypography: (opts) => rpc.call("setTypography", opts || {}),\n applyStyle: (opts) => rpc.call("applyStyle", opts || {}),\n createComponent: (opts) => rpc.call("createComponent", opts || {}),\n createInstance: (opts) => rpc.call("createInstance", opts || {}),\n renamePage: (opts) => rpc.call("renamePage", opts || {}),\n getDocumentOutline: (opts) => rpc.call("getDocumentOutline", opts || {}),\n setPageSetup: (opts) => rpc.call("setPageSetup", opts || {}),\n setShowAdvance: (opts) => rpc.call("setShowAdvance", opts || {}),\n booleanOperation: (opts) => rpc.call("booleanOperation", opts || {}),\n flattenNodes: (opts) => rpc.call("flattenNodes", opts || {}),\n outlineStroke: (opts) => rpc.call("outlineStroke", opts || {}),\n setMask: (opts) => rpc.call("setMask", opts || {}),\n setMaskType: (opts) => rpc.call("setMaskType", opts || {}),\n shapeBuilder: (opts) => rpc.call("shapeBuilder", opts || {}),\n setKeyframes: (opts) => rpc.call("setKeyframes", opts || {}),\n getAnimation: (opts) => rpc.call("getAnimation", opts || {}),\n getCompositions: (opts) => rpc.call("getCompositions", opts || {}),\n editBehaviours: (opts) => rpc.call("editBehaviours", opts || {}),\n setTimeline: (opts) => rpc.call("setTimeline", opts || {}),\n setLayerTiming: (opts) => rpc.call("setLayerTiming", opts || {}),\n editTextAnimation: (opts) => rpc.call("editTextAnimation", opts || {}),\n edit3DScene: (opts) => rpc.call("edit3DScene", opts || {}),\n setInstancePlayback: (opts) => rpc.call("setInstancePlayback", opts || {}),\n editComponentProperty: (opts) => rpc.call("editComponentProperty", opts || {}),\n setShaderFill: (opts) => rpc.call("setShaderFill", opts || {}),\n createComposition: (opts) => rpc.call("createComposition", opts || {}),\n editComposition: (opts) => rpc.call("editComposition", opts || {}),\n editStateMachine: (opts) => rpc.call("editStateMachine", opts || {}),\n getStateMachine: (opts) => rpc.call("getStateMachine", opts || {}),\n applyMotionPreset: (opts) => rpc.call("applyMotionPreset", opts || {}),\n staggerMotion: (opts) => rpc.call("staggerMotion", opts || {}),\n createMotionComponent: (opts) => rpc.call("createMotionComponent", opts || {}),\n addAudio: (opts) => rpc.call("addAudio", opts || {}),\n editFrameAudio: (opts) => rpc.call("editFrameAudio", opts || {}),\n editVideoClip: (opts) => rpc.call("editVideoClip", opts || {}),\n detectAudioOnsets: (opts) => rpc.call("detectAudioOnsets", opts || {}),\n cutToBeat: (opts) => rpc.call("cutToBeat", opts || {}),\n editPrototype: (opts) => rpc.call("editPrototype", opts || {}),\n importFile: (opts) => rpc.call("importFile", opts || {}),\n usePresentationFallbackAudio: (opts) => rpc.call("usePresentationFallbackAudio", opts || {}),\n getStyles: () => rpc.call("getStyles"),\n createPaintStyle: (name, paints) => rpc.call("createPaintStyle", { name, paints }),\n getVariables: () => rpc.call("getVariables"),\n setVariableValue: (variableId, modeId, value) => rpc.call("setVariableValue", { variableId, modeId, value }),\n exportNode: (id, options) => rpc.call("exportNode", { id, options: options || {} }),\n openExternal: (url) => rpc.call("openExternal", { url }),\n notify: (message, options) => rpc.call("notify", { message: String(message), error: !!(options && options.error) }),\n showUI: (options) => rpc.call("showUI", options || {}),\n hideUI: () => rpc.call("hideUI"),\n /** Ends the run. The optional message is shown as a notification. */\n closePlugin: (message) => rpc.call("closePlugin", { message }),\n storage: {\n get: (key) => rpc.call("storageGet", { key }),\n set: (key, value) => rpc.call("storageSet", { key, value })\n },\n ui: {\n get onmessage() {\n return uiOnMessage;\n },\n set onmessage(cb) {\n uiOnMessage = cb;\n flushUI();\n },\n postMessage: (msg) => rpc.send({ kind: "toUI", data: msg }),\n on: (cb) => {\n uiHandlers.push(cb);\n flushUI();\n }\n },\n widget: {\n register(def) {\n if (!def || typeof def.render !== "function") throw new Error("popcraft.widget.register needs a render(state) function");\n widgetDef = def;\n widgetQueue.splice(0).forEach(handleWidgetClick);\n },\n insert(options) {\n if (!widgetDef) return Promise.reject(new Error("Call popcraft.widget.register first"));\n const o = options || {};\n const state = o.state !== void 0 ? clone(o.state) : initialWidgetState();\n return rpc.call("widgetInsert", { state, tree: renderWidget(state).tree, x: o.x, y: o.y });\n },\n getState: (nodeId) => rpc.call("widgetGet", { id: nodeId }),\n setState(nodeId, state) {\n if (!widgetDef) return Promise.reject(new Error("Call popcraft.widget.register first"));\n return commitWidget(nodeId, clone(state));\n }\n },\n on: (event, cb) => rpc.on(event, cb),\n off: (event, cb) => rpc.off(event, cb)\n };\n rpc.on("init", (data) => {\n var _a, _b, _c;\n popcraft.command = data.command;\n popcraft.exportRequest = (_c = data.exportRequest) != null ? _c : null;\n popcraft.importMode = (_a = data.importMode) != null ? _a : null;\n popcraft.importFile = (_b = data.importFile) != null ? _b : null;\n });\n return popcraft;\n }\n function installMainApi() {\n const rpc = createRpcClient();\n const api = createMainApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n window.addEventListener("error", (e) => rpc.send({ kind: "error", message: String(e.message || e) }));\n window.addEventListener("unhandledrejection", (e) => rpc.send({ kind: "error", message: String(e.reason && e.reason.message || e.reason) }));\n return api;\n }\n\n // src/runtime/main.entry.ts\n installMainApi();\n})();';
357384
358171
  UI_RUNTIME = '"use strict";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: "rpc", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener("message", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== "popcraft-plugin-port" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === "result" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === "event" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/ui.ts\n function createUiApi(rpc = createRpcClient()) {\n const popcraft = {\n onmessage: null,\n postMessage: (msg) => rpc.send({ kind: "fromUI", data: msg }),\n close: () => rpc.send({ kind: "closeUI" })\n };\n rpc.on("message", (msg) => {\n if (typeof popcraft.onmessage === "function") popcraft.onmessage(msg);\n window.dispatchEvent(new MessageEvent("message", { data: { pluginMessage: msg } }));\n });\n return popcraft;\n }\n function installUiApi() {\n const rpc = createRpcClient();\n const api = createUiApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n return api;\n }\n\n // src/runtime/ui.entry.ts\n installUiApi();\n})();';
357385
358172
  }
357386
358173
  });
357387
358174
 
357388
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/host.js
358175
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/host.js
357389
358176
  function csp(network) {
357390
358177
  const connect2 = network ? "connect-src * data: blob:;" : "connect-src 'none';";
357391
358178
  const img = network ? "img-src * data: blob:;" : "img-src data: blob:;";
@@ -357413,40 +358200,40 @@ function buildUIDocument(uiHtml, network) {
357413
358200
  }
357414
358201
  var escapeScript;
357415
358202
  var init_host2 = __esm({
357416
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/host.js"() {
358203
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/host.js"() {
357417
358204
  init_runtime4();
357418
358205
  escapeScript = (code) => code.replace(/<\/script/gi, "<\\/script");
357419
358206
  }
357420
358207
  });
357421
358208
 
357422
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/runtime/rpc.js
358209
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/runtime/rpc.js
357423
358210
  var init_rpc = __esm({
357424
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/runtime/rpc.js"() {
358211
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/runtime/rpc.js"() {
357425
358212
  }
357426
358213
  });
357427
358214
 
357428
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/runtime/main.js
358215
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/runtime/main.js
357429
358216
  var init_main = __esm({
357430
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/runtime/main.js"() {
358217
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/runtime/main.js"() {
357431
358218
  init_rpc();
357432
358219
  }
357433
358220
  });
357434
358221
 
357435
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/types.js
358222
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/types.js
357436
358223
  var init_types9 = __esm({
357437
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/types.js"() {
358224
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/types.js"() {
357438
358225
  }
357439
358226
  });
357440
358227
 
357441
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/widget.js
358228
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/widget.js
357442
358229
  var init_widget = __esm({
357443
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/widget.js"() {
358230
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/widget.js"() {
357444
358231
  }
357445
358232
  });
357446
358233
 
357447
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/index.js
358234
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/index.js
357448
358235
  var init_dist = __esm({
357449
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=96b9dbf7c14e2f42dc018560c454dc18c0676d4092077efc38f3939519e5012c/node_modules/@popcraft/plugins/dist/index.js"() {
358236
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=997b73fa0364896bf8afb3420ac81e52de261b88cad7cac3e2e206341916c9a5/node_modules/@popcraft/plugins/dist/index.js"() {
357450
358237
  init_host2();
357451
358238
  init_main();
357452
358239
  init_runtime4();
@@ -357819,8 +358606,8 @@ var init_package2 = __esm({
357819
358606
 
357820
358607
  // lib/plugins/registry.ts
357821
358608
  async function listPlugins() {
357822
- const all3 = await values2(db2());
357823
- return all3.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
358609
+ const all4 = await values2(db2());
358610
+ return all4.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
357824
358611
  }
357825
358612
  async function getPlugin(id3) {
357826
358613
  return get2(id3, db2());
@@ -358628,9 +359415,9 @@ var init_import = __esm({
358628
359415
  return node2;
358629
359416
  }
358630
359417
  vectorFrom(b, name2) {
358631
- const all3 = this.bezierPoints(b);
358632
- const minX = Math.min(...all3.map((q) => q[0])), minY = Math.min(...all3.map((q) => q[1]));
358633
- const maxX = Math.max(...all3.map((q) => q[0])), maxY = Math.max(...all3.map((q) => q[1]));
359418
+ const all4 = this.bezierPoints(b);
359419
+ const minX = Math.min(...all4.map((q) => q[0])), minY = Math.min(...all4.map((q) => q[1]));
359420
+ const maxX = Math.max(...all4.map((q) => q[0])), maxY = Math.max(...all4.map((q) => q[1]));
358634
359421
  return createVector({ name: name2, x: minX, y: minY, width: Math.max(1e-3, maxX - minX), height: Math.max(1e-3, maxY - minY), paths: [this.pathFrom(b, minX, minY)], fills: [], strokes: [] });
358635
359422
  }
358636
359423
  /** Every vertex and control point of `b`, absolute. */
@@ -381124,8 +381911,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
381124
381911
  CurrentPointers.setTimeStamp(event.timeStamp);
381125
381912
  stopEvent(event);
381126
381913
  }
381127
- static _cleanup(all3) {
381128
- if (all3) {
381914
+ static _cleanup(all4) {
381915
+ if (all4) {
381129
381916
  this._currentDrawId = -1;
381130
381917
  this._currentParent = null;
381131
381918
  _DrawingEditor.#currentDraw = null;
@@ -387894,6 +388681,23 @@ var init_plugin = __esm({
387894
388681
  expose: { plugin: { method: "getImportFile", shape: (r) => r.data } },
387895
388682
  run: (_p, ctx2) => ({ summary: "Import file", data: hostEnv(ctx2).importFile ?? null })
387896
388683
  }),
388684
+ defineCommand({
388685
+ id: pluginCommandId("getStorefront"),
388686
+ title: "Get storefront design",
388687
+ domain: "export",
388688
+ params: empty,
388689
+ effect: "none",
388690
+ permission: "read",
388691
+ expose: { plugin: { method: "getStorefront", shape: (r) => r.data } },
388692
+ // A storefront design in a neutral form (lib/io/web/project/storefront.ts `storefrontDesign`): what an exporter
388693
+ // plugin turns into its own store's templates, as PopCraft turns it into a Next.js Commerce store or a Shopify theme.
388694
+ run: async (_p, ctx2) => {
388695
+ const [{ componentScreens: componentScreens2 }, { storefrontDesign: storefrontDesign2 }] = await Promise.all([Promise.resolve().then(() => (init_components(), components_exports)), Promise.resolve().then(() => (init_storefront(), storefront_exports))]);
388696
+ const doc2 = ctx2.doc();
388697
+ const design = storefrontDesign2(doc2, componentScreens2(doc2, ctx2.pageId()));
388698
+ return { summary: `Storefront: ${design.pages.length} pages`, data: design };
388699
+ }
388700
+ }),
387897
388701
  defineCommand({
387898
388702
  id: pluginCommandId("getExportRequest"),
387899
388703
  title: "Get export request",
@@ -388080,7 +388884,7 @@ function attached(done, ctx2) {
388080
388884
  ctx2.notice(`Database attached: ${where}`);
388081
388885
  return { summary: `Attached ${where} (${db3.connection.provider}, ${db3.connection.account}): the site's forms are saved there once it is published`, data: { provider: db3.connection.provider, account: db3.connection.account, target: db3.target } };
388082
388886
  }
388083
- var integrationList, integrationOpen, asPath2, integrationListResources, place3, integrationAddDestination, integrationDeploy, integrationAttachDatabase, integrationSubmissions, integrationSetSignIn, integrationConfirmCommands;
388887
+ var integrationList, integrationOpen, asPath2, integrationListResources, place3, integrationAddDestination, integrationDeploy, integrationAttachDatabase, integrationSubmissions, integrationSetSignIn, integrationConfirmCommands, integrationPullProducts;
388084
388888
  var init_integration = __esm({
388085
388889
  "lib/commands/integration.ts"() {
388086
388890
  "use strict";
@@ -388094,13 +388898,13 @@ var init_integration = __esm({
388094
388898
  effect: "none",
388095
388899
  permission: null,
388096
388900
  params: external_exports.object({
388097
- capability: external_exports.enum(["repo", "deploy", "database", "auth"]).optional().describe("Only the integrations that can do this: repo (push code to a repository), deploy (host the site), database (store what forms send and what collections hold), auth (sign-in for the site's visitors).")
388901
+ capability: external_exports.enum(["repo", "deploy", "database", "auth", "commerce"]).optional().describe("Only the integrations that can do this: repo (push code to a repository), deploy (host the site), database (store what forms send and what collections hold), auth (sign-in for the site's visitors), commerce (a store whose products a storefront design is drawn with).")
388098
388902
  }),
388099
388903
  expose: {
388100
388904
  agent: {
388101
388905
  name: "list_integrations",
388102
388906
  readOnly: true,
388103
- description: "The services this workspace can connect its own accounts to (GitHub, Vercel, Neon, Supabase), what each can do for a published site (repo, deploy, database, auth), which accounts are connected, and whether the user can connect more. `notYet` lists what an integration is meant to do and cannot yet: never promise those. Call it before recommending one, and before any step that needs a connection. You cannot connect an account yourself: when one is missing, say which integration fits and why, then call open_integrations so the user can connect it."
388907
+ description: "The services this workspace can connect its own accounts to (GitHub, Vercel, Neon, Supabase, Shopify), what each can do (repo, deploy, database, auth, commerce), which accounts are connected, and whether the user can connect more. `notYet` lists what an integration is meant to do and cannot yet: never promise those. Call it before recommending one, and before any step that needs a connection. You cannot connect an account yourself: when one is missing, say which integration fits and why, then call open_integrations so the user can connect it."
388104
388908
  }
388105
388909
  },
388106
388910
  run: async ({ capability: capability2 }) => {
@@ -388237,9 +389041,9 @@ var init_integration = __esm({
388237
389041
  run: async (p2, ctx2) => {
388238
389042
  const key5 = await openFile();
388239
389043
  const { deployDestination: deployDestination2, listDestinations: listDestinations2 } = await Promise.resolve().then(() => (init_integrations(), integrations_exports));
388240
- const all3 = await listDestinations2(key5);
388241
- const dest = p2.destinationId ? all3.find((d) => d.id === p2.destinationId) : all3.length === 1 ? all3[0] : void 0;
388242
- if (!dest) throw new CommandError(all3.length ? `Say which destination: ${all3.map((d) => `${d.id} (${d.target.map((t3) => t3.name).join(" / ")})`).join(", ")}.` : "This file has no destination yet. Add one first (add_site_destination).", "invalid_params");
389044
+ const all4 = await listDestinations2(key5);
389045
+ const dest = p2.destinationId ? all4.find((d) => d.id === p2.destinationId) : all4.length === 1 ? all4[0] : void 0;
389046
+ if (!dest) throw new CommandError(all4.length ? `Say which destination: ${all4.map((d) => `${d.id} (${d.target.map((t3) => t3.name).join(" / ")})`).join(", ")}.` : "This file has no destination yet. Add one first (add_site_destination).", "invalid_params");
388243
389047
  const done = await deployDestination2(key5, dest.id, p2.version);
388244
389048
  ctx2.notice(done.status === "building" ? `Deploying: ${done.url}` : `Sent: ${done.url}`);
388245
389049
  return { summary: `Version ${done.version} ${done.status === "building" ? "is deploying" : "was sent"}: ${done.url}`, data: { url: done.url, ref: done.ref, status: done.status, version: done.version, destinationId: dest.id } };
@@ -388335,6 +389139,56 @@ var init_integration = __esm({
388335
389139
  }
388336
389140
  });
388337
389141
  integrationConfirmCommands = [integrationAddDestination, integrationDeploy, integrationAttachDatabase, integrationSetSignIn];
389142
+ integrationPullProducts = defineCommand({
389143
+ id: "integration.pullProducts",
389144
+ title: "Draw the design with a store's products",
389145
+ domain: "integration",
389146
+ effect: "document",
389147
+ permission: "write",
389148
+ undoLabel: "Store products",
389149
+ params: external_exports.object({
389150
+ connectionId: external_exports.string().optional().describe("The connected store, from list_integrations (capability commerce). Omit when one store is connected."),
389151
+ collection: external_exports.string().optional().describe("The collection to fill, by id or name; it must stand for store products (schema commerce.product). Omit when the file has one."),
389152
+ limit: external_exports.number().int().min(1).max(100).default(24).describe("How many of the store's newest products.")
389153
+ }).strict(),
389154
+ expose: {
389155
+ ui: { label: "Draw with my store's products", keywords: ["shopify", "store", "products", "commerce", "shop", "catalogue", "import"] },
389156
+ agent: {
389157
+ name: "pull_store_products",
389158
+ description: "Replace the records of a storefront design's product collection (schema commerce.product) with a connected store's real products (Shopify): titles, prices, pictures, handles and variant ids, newest first. The design's lists and product pages then show them. It reads the store; it never changes it. Needs a Shopify connection (list_integrations, capability commerce); you cannot connect one yourself."
389159
+ }
389160
+ },
389161
+ run: async (args2, ctx2) => {
389162
+ const [{ cloudUser: cloudUser2 }, { integrationsWorkspaceId: integrationsWorkspaceId2, listWorkspaceIntegrations: listWorkspaceIntegrations2, storeProducts: storeProducts2 }, { commerceItem: commerceItem2, COMMERCE_FIELDS: COMMERCE_FIELDS2 }, { fieldKey: fieldKey2, removeRecords: removeRecords2, upsertRecords: upsertRecords2 }] = await Promise.all([
389163
+ Promise.resolve().then(() => (init_session(), session_exports)),
389164
+ Promise.resolve().then(() => (init_integrations(), integrations_exports)),
389165
+ Promise.resolve().then(() => (init_commerce(), commerce_exports)),
389166
+ Promise.resolve().then(() => (init_collections(), collections_exports))
389167
+ ]);
389168
+ if (!cloudUser2()) throw new CommandError("You are not signed in: a store is connected to your account's workspace.", "denied");
389169
+ const doc2 = ctx2.doc();
389170
+ const pick5 = Object.values(doc2.collections ?? {}).filter((c2) => c2.schema === "commerce.product" && (!args2.collection || c2.id === args2.collection || c2.name.toLowerCase() === args2.collection.toLowerCase()));
389171
+ if (!pick5.length) throw new CommandError(args2.collection ? `No store products collection "${args2.collection}".` : "This file has no collection that stands for store products: set one to Store products in Collections first.", "invalid_params");
389172
+ if (pick5.length > 1) throw new CommandError(`Name the collection: ${pick5.map((c2) => c2.name).join(", ")}.`, "invalid_params");
389173
+ const collection = pick5[0];
389174
+ let connectionId = args2.connectionId;
389175
+ if (!connectionId) {
389176
+ const workspaceId = integrationsWorkspaceId2();
389177
+ if (!workspaceId) throw new CommandError("Your workspaces have not loaded yet. Try again in a moment.", "failed");
389178
+ const stores2 = (await listWorkspaceIntegrations2(workspaceId)).integrations.filter((i2) => i2.manifest.capabilities.includes("commerce")).flatMap((i2) => i2.connections);
389179
+ if (!stores2.length) throw new CommandError("No store is connected. Connect Shopify in Integrations first.", "failed");
389180
+ if (stores2.length > 1) throw new CommandError(`Name the store: ${stores2.map((c2) => `${c2.account} (${c2.id})`).join(", ")}.`, "invalid_params");
389181
+ connectionId = stores2[0].id;
389182
+ }
389183
+ const products = await storeProducts2(connectionId, args2.limit);
389184
+ if (!products.length) throw new CommandError("The store has no products to show.", "failed");
389185
+ const keys4 = new Map(COMMERCE_FIELDS2["commerce.product"].map((f2) => [fieldKey2(f2.name), f2.name]));
389186
+ const rows = products.map((p2) => ({ values: Object.fromEntries(Object.entries(commerceItem2("commerce.product", p2).values).filter(([k2]) => keys4.has(k2)).map(([k2, v]) => [keys4.get(k2), v])) }));
389187
+ const cleared = removeRecords2(doc2, collection.order);
389188
+ ctx2.commit(upsertRecords2(cleared, collection.id, rows).document);
389189
+ return { summary: `Drew ${collection.name} with ${products.length} of the store's products`, data: { collection: collection.id, products: products.length } };
389190
+ }
389191
+ });
388338
389192
  }
388339
389193
  });
388340
389194
 
@@ -389339,6 +390193,7 @@ function designRecorder() {
389339
390193
  var installed;
389340
390194
  var init_recorders = __esm({
389341
390195
  "lib/agent/design-script/recorders.ts"() {
390196
+ "use strict";
389342
390197
  installed = null;
389343
390198
  }
389344
390199
  });
@@ -390854,8 +391709,8 @@ var init_app = __esm({
390854
391709
  s.setActiveBreakpoint(breakpoint2);
390855
391710
  const { breakpointsOf: breakpointsOf2 } = await Promise.resolve().then(() => (init_breakpoints(), breakpoints_exports));
390856
391711
  const shown2 = (await store5()).activeBreakpoint;
390857
- const all3 = breakpointsOf2(s.document);
390858
- return { summary: `Showing ${(all3.find((b) => b.id === shown2) ?? all3[0]).name}`, data: { breakpoint: shown2 ?? all3[0].id } };
391712
+ const all4 = breakpointsOf2(s.document);
391713
+ return { summary: `Showing ${(all4.find((b) => b.id === shown2) ?? all4[0]).name}`, data: { breakpoint: shown2 ?? all4[0].id } };
390859
391714
  }
390860
391715
  });
390861
391716
  appCommands = [modeSet, modeEdit, nodeRasterize, panelOpen, panelToggle, dialogOpen, aiOpen, docAction, toolSet, toolPlace, nodeOps, canvasToggle, viewSetBreakpoint, popcornOpen, pageGo];
@@ -391688,9 +392543,9 @@ var init_check = __esm({
391688
392543
  MAX_FRAME_PX = 8192;
391689
392544
  label2 = (t3) => `"${(t3.characters || t3.name).replace(/\s+/g, " ").slice(0, 40)}"`;
391690
392545
  presetOf = (doc2, w, h) => {
391691
- const all3 = PAGE_PRESETS.filter((p2) => p2.unit === "px" && p2.width === w && p2.height === h);
391692
- const web = all3.find((p2) => p2.category === "WEB");
391693
- return (doc2.settings?.kind === "WEB" || web && all3.length > 1 && w >= 1024 ? web : void 0)?.id ?? all3[0]?.id;
392546
+ const all4 = PAGE_PRESETS.filter((p2) => p2.unit === "px" && p2.width === w && p2.height === h);
392547
+ const web = all4.find((p2) => p2.category === "WEB");
392548
+ return (doc2.settings?.kind === "WEB" || web && all4.length > 1 && w >= 1024 ? web : void 0)?.id ?? all4[0]?.id;
391694
392549
  };
391695
392550
  }
391696
392551
  });
@@ -392485,6 +393340,7 @@ var init_site3 = __esm({
392485
393340
  init_controls();
392486
393341
  init_types();
392487
393342
  init_routes();
393343
+ init_commerce();
392488
393344
  init_doc_helpers();
392489
393345
  init_errors();
392490
393346
  init_types5();
@@ -392540,9 +393396,9 @@ var init_site3 = __esm({
392540
393396
  run: (args2, ctx2) => {
392541
393397
  const { doc: doc2, ids: ids2 } = resolveTargets(ctx2, args2.ids, "set_responsive");
392542
393398
  if (!args2.set && !args2.reset) throw new ToolError("set_responsive needs set or reset.");
392543
- const all3 = breakpointsOf(doc2);
392544
- if (!all3.some((b) => b.id === args2.breakpoint)) throw new ToolError(`No breakpoint "${args2.breakpoint}". This file has: ${all3.map((b) => b.id).join(", ")}.`);
392545
- if (isBaseBreakpoint(doc2, args2.breakpoint)) throw new ToolError(`"${args2.breakpoint}" is the base: what a layer has there is the layer itself. Use update_nodes, or name a narrower breakpoint (${all3.slice(1).map((b) => b.id).join(", ")}).`);
393399
+ const all4 = breakpointsOf(doc2);
393400
+ if (!all4.some((b) => b.id === args2.breakpoint)) throw new ToolError(`No breakpoint "${args2.breakpoint}". This file has: ${all4.map((b) => b.id).join(", ")}.`);
393401
+ if (isBaseBreakpoint(doc2, args2.breakpoint)) throw new ToolError(`"${args2.breakpoint}" is the base: what a layer has there is the layer itself. Use update_nodes, or name a narrower breakpoint (${all4.slice(1).map((b) => b.id).join(", ")}).`);
392546
393402
  const patches = /* @__PURE__ */ new Map();
392547
393403
  for (const id3 of ids2) {
392548
393404
  let node2 = doc2.nodes[id3];
@@ -392639,7 +393495,7 @@ var init_site3 = __esm({
392639
393495
  options: external_exports.array(external_exports.object({ value: external_exports.string().max(200), label: external_exports.string().max(200) })).max(200).optional().describe("SELECT: its choices."),
392640
393496
  buttonType: external_exports.enum(["submit", "reset", "button"]).optional().describe("BUTTON: what it does in a form (default submit)."),
392641
393497
  variableId: external_exports.string().nullable().optional().describe("The variable typing writes, so layers bound to it follow; null unbinds."),
392642
- formAction: external_exports.enum(["signUp", "signIn", "signOut", "updateAccount", "changePassword", "deleteAccount", "forgotPassword", "resetPassword", "deleteRecord", "updateRecord"]).nullable().optional().describe(`FORM: what sending does on a published site with sign-in. signUp: make an account from its fields named name, email and password. signIn: sign in with email and password. signOut: end the session. updateAccount: change the signed-in visitor's name (field name). changePassword: fields password (the current one) and new_password. deleteAccount: field password, then the visitor is signed out for good. forgotPassword: field email, emails a reset link that opens the site's /reset-password page; resetPassword (on that page): field new_password. deleteRecord: a form inside a repeated list's copy removes that copy's record from the visitor's own. updateRecord: a form inside a copy changes that copy's record \u2014 the fields it sends take their values, the rest stay; a BUTTON with a name and a value sends them with the form it sends ("Mark done" is a button named done with value Yes). null (default): save its values (to the visitor's own records when the form's collectionId is a perVisitor collection: each control's name is a field). onSubmit runs only when it worked; a refusal is put in the variable user/signInError.`),
393498
+ formAction: external_exports.enum(["signUp", "signIn", "signOut", "updateAccount", "changePassword", "deleteAccount", "forgotPassword", "resetPassword", "deleteRecord", "updateRecord", ...COMMERCE_ACTIONS]).nullable().optional().describe(`FORM: what sending does on a published site with sign-in. signUp: make an account from its fields named name, email and password. signIn: sign in with email and password. signOut: end the session. updateAccount: change the signed-in visitor's name (field name). changePassword: fields password (the current one) and new_password. deleteAccount: field password, then the visitor is signed out for good. forgotPassword: field email, emails a reset link that opens the site's /reset-password page; resetPassword (on that page): field new_password. deleteRecord: a form inside a repeated list's copy removes that copy's record from the visitor's own. updateRecord: a form inside a copy changes that copy's record \u2014 the fields it sends take their values, the rest stay; a BUTTON with a name and a value sends them with the form it sends ("Mark done" is a button named done with value Yes). null (default): save its values (to the visitor's own records when the form's collectionId is a perVisitor collection: each control's name is a field). onSubmit runs only when it worked; a refusal is put in the variable user/signInError. Storefront actions (for a design exported as components for a Next.js Commerce store): addToCart (a field or hidden input named merchandiseId, usually {{item.variant_id}}), updateCartLine (merchandiseId and quantity), removeCartLine (merchandiseId), checkout, search (a field named q); each becomes a prop of the exported component (onAddToCart, \u2026) its form sends to.`),
392643
393499
  collectionId: external_exports.string().nullable().optional().describe("FORM: the collection its values are kept in as a record (each control named as a field)."),
392644
393500
  onSubmit: external_exports.record(external_exports.string(), external_exports.unknown()).nullable().optional().describe('FORM: a prototype action run once it is sent, as edit_prototype takes them ({navigationType: "NAVIGATE", destinationId: <thank-you frame>}).')
392645
393501
  }).strict(),
@@ -392811,7 +393667,8 @@ var init_site3 = __esm({
392811
393667
  name: c2.name,
392812
393668
  records: c2.order.filter((id3) => doc2.records?.[id3]).length,
392813
393669
  fields: c2.fields.map((f2) => ({ id: f2.id, name: f2.name, key: fieldKey(f2.name), type: f2.type, ...f2.options ? { options: f2.options } : {} })),
392814
- slugField: c2.fields.find((f2) => f2.id === c2.slugFieldId)?.name ?? null
393670
+ slugField: c2.fields.find((f2) => f2.id === c2.slugFieldId)?.name ?? null,
393671
+ ...c2.schema ? { schema: c2.schema } : {}
392815
393672
  });
392816
393673
  dataEditCollection = defineCommand({
392817
393674
  id: "data.editCollection",
@@ -392832,6 +393689,7 @@ var init_site3 = __esm({
392832
393689
  slugField: external_exports.string().nullable().optional().describe("The field a record's page address is made from (by name); default: the first SLUG or TEXT field."),
392833
393690
  shared: external_exports.boolean().optional().describe("With perVisitor (turned on with it): every signed-in visitor sees everyone's records \u2014 a guest book, a team board. Each record says who made it (item_by) and whether it is the visitor's own (item_mine); only its maker changes or deletes it."),
392834
393691
  perVisitor: external_exports.boolean().optional().describe("Each signed-in visitor of the published site has their own records: a list repeated from it shows only theirs, and a form bound to it adds to theirs, kept in the site's database. The records in the file are samples the design is drawn with. Needs a database with sign-in attached."),
393692
+ schema: external_exports.enum(COMMERCE_SCHEMAS).nullable().optional().describe("What its records stand for in a storefront: commerce.product, commerce.collection, commerce.cart (one record: the cart count and totals, shown by a list of one), commerce.cartLine or commerce.menu. Its fields become the schema's (leave fields out) and its records are samples; exported as components, a list repeated from it is a typed prop (products, collections, cart, lines, menu) a Next.js Commerce store fills. null clears it."),
392835
393693
  remove: external_exports.boolean().optional().describe("Delete the collection, its records, and the repeaters and collection pages that used it.")
392836
393694
  }).strict(),
392837
393695
  expose: {
@@ -392850,6 +393708,7 @@ var init_site3 = __esm({
392850
393708
  return { summary: `Removed collection ${before.name}`, data: { removed: before.id } };
392851
393709
  }
392852
393710
  if (!before && !args2.name) throw new ToolError("A new collection needs a name.");
393711
+ if (args2.schema && !args2.fields) args2 = { ...args2, fields: COMMERCE_FIELDS[args2.schema].map((f2) => ({ name: f2.name, type: f2.type })) };
392853
393712
  if (!before && !args2.fields) throw new ToolError("A new collection needs fields.");
392854
393713
  if (!before && Object.keys(doc2.collections ?? {}).length >= COLLECTION_LIMITS.collections) throw new ToolError(`A file holds up to ${COLLECTION_LIMITS.collections} collections.`);
392855
393714
  const keys4 = (args2.fields ?? []).map((f2) => fieldKey(f2.name));
@@ -392870,6 +393729,11 @@ var init_site3 = __esm({
392870
393729
  void _was;
392871
393730
  next2 = { ...next2, collections: { ...next2.collections, [draft.id]: args2.shared ? { ...rest, shared: true, perVisitor: true } : rest } };
392872
393731
  }
393732
+ if (args2.schema !== void 0) {
393733
+ const { schema: _was, ...rest } = next2.collections[draft.id];
393734
+ void _was;
393735
+ next2 = { ...next2, collections: { ...next2.collections, [draft.id]: args2.schema ? { ...rest, schema: args2.schema } : rest } };
393736
+ }
392873
393737
  if (args2.perVisitor !== void 0) {
392874
393738
  const { perVisitor: _was, ...rest } = next2.collections[draft.id];
392875
393739
  void _was;
@@ -392953,11 +393817,11 @@ var init_site3 = __esm({
392953
393817
  return { summary: list2.length ? `${list2.length} collections: ${list2.map((c3) => c3.name).join(", ")}` : "No collections", data: { collections: list2 } };
392954
393818
  }
392955
393819
  const c2 = collectionOf(doc2, args2.collection);
392956
- const all3 = recordsIn(doc2, c2.id);
393820
+ const all4 = recordsIn(doc2, c2.id);
392957
393821
  const slugs = recordSlugs(doc2, c2.id);
392958
393822
  const from2 = args2.offset ?? 0;
392959
- const records = all3.slice(from2, from2 + (args2.limit ?? 50)).map((r) => ({ id: r.id, slug: slugs.get(r.id), values: Object.fromEntries(c2.fields.map((f2) => [fieldKey(f2.name), r.values[f2.id] ?? null])) }));
392960
- return { summary: `${c2.name}: ${records.length} of ${all3.length} records`, data: { collection: described2(c2, doc2), total: all3.length, records } };
393823
+ const records = all4.slice(from2, from2 + (args2.limit ?? 50)).map((r) => ({ id: r.id, slug: slugs.get(r.id), values: Object.fromEntries(c2.fields.map((f2) => [fieldKey(f2.name), r.values[f2.id] ?? null])) }));
393824
+ return { summary: `${c2.name}: ${records.length} of ${all4.length} records`, data: { collection: described2(c2, doc2), total: all4.length, records } };
392961
393825
  }
392962
393826
  });
392963
393827
  nodeSetRepeat = defineCommand({
@@ -394098,6 +394962,7 @@ var init_builtin4 = __esm({
394098
394962
  exportPages,
394099
394963
  exportPptx,
394100
394964
  exportWeb,
394965
+ exportStorefront,
394101
394966
  // state machines (lib/motion/state-machine.ts): interactive timelines (appended, as above)
394102
394967
  motionEditStateMachine,
394103
394968
  motionGetStateMachine,
@@ -394131,6 +394996,7 @@ var init_builtin4 = __esm({
394131
394996
  integrationOpen,
394132
394997
  integrationListResources,
394133
394998
  integrationSubmissions,
394999
+ integrationPullProducts,
394134
395000
  // a copy of the open file as a new file (lib/commands/file.ts) (appended, as above)
394135
395001
  fileDuplicate,
394136
395002
  // a whiteboard as one picture, or its stickies as Markdown / CSV (lib/popcorn/board-export.ts) (appended, as above)
@@ -399353,11 +400219,11 @@ function fileStorage(path) {
399353
400219
  return {
399354
400220
  get: async (key5) => read6()[key5],
399355
400221
  set: async (key5, value3) => {
399356
- const all3 = read6();
399357
- all3[key5] = value3;
400222
+ const all4 = read6();
400223
+ all4[key5] = value3;
399358
400224
  if (!path) return;
399359
400225
  mkdirSync3(dirname(path), { recursive: true });
399360
- writeFileSync3(path, JSON.stringify(all3));
400226
+ writeFileSync3(path, JSON.stringify(all4));
399361
400227
  }
399362
400228
  };
399363
400229
  }
@@ -399950,15 +400816,15 @@ function runCommandsList(positionals, flags, out) {
399950
400816
  out.line(commandHelp(command));
399951
400817
  return;
399952
400818
  }
399953
- const all3 = flagBool(flags, "all");
399954
- const rows = cliCommands().filter((c2) => all3 || ["local", "browser"].includes(availability2(c2)));
400819
+ const all4 = flagBool(flags, "all");
400820
+ const rows = cliCommands().filter((c2) => all4 || ["local", "browser"].includes(availability2(c2)));
399955
400821
  if (out.json) {
399956
400822
  out.result(`${rows.length} commands`, rows.map((c2) => ({ id: c2.id, title: c2.title, availability: availability2(c2), effect: c2.effect, flat: specOf(c2).flat })));
399957
400823
  return;
399958
400824
  }
399959
400825
  out.line(table([["COMMAND", "WHERE", "TITLE"], ...rows.map((c2) => [c2.id, WHERE[availability2(c2)], c2.title])]));
399960
400826
  out.line(`
399961
- ${rows.length} commands${all3 ? "" : " that run from the CLI (--all lists every command, with where the rest run)"}. \`popcraft commands <id>\` shows a command's flags.`);
400827
+ ${rows.length} commands${all4 ? "" : " that run from the CLI (--all lists every command, with where the rest run)"}. \`popcraft commands <id>\` shows a command's flags.`);
399962
400828
  }
399963
400829
  function suggest(key5) {
399964
400830
  const k2 = key5.toLowerCase().replace(/[^a-z]/g, "");
@@ -401056,6 +401922,64 @@ export default defineTemplate({
401056
401922
  }
401057
401923
  });
401058
401924
 
401925
+ // packages/cli/src/storefront.ts
401926
+ var storefront_exports2 = {};
401927
+ __export(storefront_exports2, {
401928
+ runStorefront: () => runStorefront
401929
+ });
401930
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
401931
+ import { dirname as dirname4, join as join11 } from "node:path";
401932
+ async function mediaFor(refs, file2, appUrl) {
401933
+ const out = {};
401934
+ await Promise.all(refs.map(async (ref3) => {
401935
+ const hash3 = ref3.slice("media://".length);
401936
+ const local = join11(dirname4(file2), "media", hash3);
401937
+ const bytes2 = existsSync12(local) ? new Uint8Array(readFileSync12(local)) : await fetch(`${appUrl}/templates/media/${hash3}`).then(async (r) => r.ok ? new Uint8Array(await r.arrayBuffer()) : null, () => null);
401938
+ if (bytes2) out[ref3] = { bytes: bytes2, type: sniff(bytes2) };
401939
+ }));
401940
+ return out;
401941
+ }
401942
+ async function runStorefront(rest, flags, out, appUrl) {
401943
+ const file2 = rest[0];
401944
+ if (!file2) throw usage("popcraft storefront <file.popcraft> [--target next-commerce|shopify-theme] [--out <dir>] [--page <id|name>] [--styling inline|tailwind] [--dir components/popcraft]");
401945
+ const target2 = flagString(flags, "target") ?? "next-commerce";
401946
+ if (!STOREFRONT_TARGET_IDS.includes(target2)) throw usage(`--target is one of ${STOREFRONT_TARGET_IDS.join(", ")}`);
401947
+ const doc2 = readPopcraft(file2);
401948
+ const ref3 = flagString(flags, "page");
401949
+ const pageId2 = ref3 ? resolvePage(doc2, ref3) : doc2.pages[0];
401950
+ if (!pageId2) throw usage(`No page ${ref3}`);
401951
+ const styling = flagString(flags, "styling");
401952
+ if (styling && styling !== "inline" && styling !== "tailwind") throw usage("--styling is inline or tailwind");
401953
+ const screens = componentScreens(doc2, pageId2);
401954
+ if (!screens.length) throw usage("That page has no screens: a top-level frame is a component.");
401955
+ const refs = componentMedia(doc2, screens);
401956
+ const media2 = await mediaFor(refs, file2, appUrl);
401957
+ const missing = refs.filter((r) => !media2[r]);
401958
+ if (missing.length) out.info(`${missing.length} picture(s) not found beside the file or on ${appUrl}; they keep their media:// address: ${missing.join(", ")}`);
401959
+ const dir = flagString(flags, "dir");
401960
+ const files = target2 === "next-commerce" && dir ? componentFiles(doc2, screens, { styling, dir, media: media2 }) : STOREFRONT_TARGETS[target2].files(doc2, screens, { styling, media: media2 });
401961
+ const root2 = flagString(flags, "out") ?? ".";
401962
+ for (const f2 of files) {
401963
+ const at3 = join11(root2, f2.path);
401964
+ mkdirSync8(dirname4(at3), { recursive: true });
401965
+ writeFileSync9(at3, f2.content);
401966
+ }
401967
+ out.result(`Wrote ${STOREFRONT_TARGETS[target2].label}: ${screens.length} page${screens.length === 1 ? "" : "s"} (${screens.map((s) => s.name).join(", ")}) and ${files.filter((f2) => /^(public|assets)\/.*\.(jpg|png|webp|gif|avif|svg)$/.test(f2.path)).length} picture(s) under ${root2}`, out.json ? { target: target2, pages: screens.map((s) => s.name), files: files.map((f2) => f2.path) } : void 0);
401968
+ return 0;
401969
+ }
401970
+ var sniff;
401971
+ var init_storefront3 = __esm({
401972
+ "packages/cli/src/storefront.ts"() {
401973
+ "use strict";
401974
+ init_components();
401975
+ init_storefront();
401976
+ init_args();
401977
+ init_headless2();
401978
+ init_io();
401979
+ sniff = (b) => b[0] === 255 && b[1] === 216 ? "image/jpeg" : b[0] === 137 && b[1] === 80 ? "image/png" : b[8] === 87 && b[9] === 69 ? "image/webp" : b[0] === 71 && b[1] === 73 ? "image/gif" : "application/octet-stream";
401980
+ }
401981
+ });
401982
+
401059
401983
  // lib/desktop/local-assets.ts
401060
401984
  function parsePluginFolder(manifestText, files) {
401061
401985
  let raw;
@@ -401091,36 +402015,36 @@ __export(plugins_exports, {
401091
402015
  runPluginHeadless: () => runPluginHeadless
401092
402016
  });
401093
402017
  import { spawn } from "node:child_process";
401094
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync12, statSync as statSync6, writeFileSync as writeFileSync9 } from "node:fs";
401095
- import { dirname as dirname4, extname as extname3, join as join11, resolve as resolve5, sep } from "node:path";
402018
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync13, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
402019
+ import { dirname as dirname5, extname as extname3, join as join12, resolve as resolve5, sep } from "node:path";
401096
402020
  function loadPlugin(ref3, root2 = desktopDataDir()) {
401097
402021
  let path = ref3;
401098
- if (!existsSync12(path)) {
402022
+ if (!existsSync13(path)) {
401099
402023
  const installed3 = listResources2("plugins", root2).find((r) => r.name === ref3);
401100
402024
  if (!installed3) throw usage(`No plugin "${ref3}": give its folder, .zip or .json, or the name of one in \`popcraft plugins list\`.`);
401101
- path = join11(root2, installed3.path);
402025
+ path = join12(root2, installed3.path);
401102
402026
  }
401103
402027
  if (statSync6(path).isDirectory()) {
401104
402028
  const dir = resolve5(path);
401105
- const manifest2 = join11(dir, "manifest.json");
401106
- if (!existsSync12(manifest2)) throw usage(`${path} has no manifest.json.`);
402029
+ const manifest2 = join12(dir, "manifest.json");
402030
+ if (!existsSync13(manifest2)) throw usage(`${path} has no manifest.json.`);
401107
402031
  const files = new Proxy({}, {
401108
402032
  get: (_, rel) => {
401109
402033
  if (typeof rel !== "string") return void 0;
401110
402034
  const file2 = resolve5(dir, rel);
401111
- return file2.startsWith(dir + sep) && existsSync12(file2) ? readFileSync12(file2, "utf8") : void 0;
402035
+ return file2.startsWith(dir + sep) && existsSync13(file2) ? readFileSync13(file2, "utf8") : void 0;
401112
402036
  }
401113
402037
  });
401114
- return parsePluginFolder(readFileSync12(manifest2, "utf8"), files);
402038
+ return parsePluginFolder(readFileSync13(manifest2, "utf8"), files);
401115
402039
  }
401116
- if (extname3(path).toLowerCase() === ".zip") return parsePluginZip(new Uint8Array(readFileSync12(path)));
401117
- return parsePluginBundle(readFileSync12(path, "utf8"));
402040
+ if (extname3(path).toLowerCase() === ".zip") return parsePluginZip(new Uint8Array(readFileSync13(path)));
402041
+ return parsePluginBundle(readFileSync13(path, "utf8"));
401118
402042
  }
401119
402043
  async function approvedPermissions(manifest2, dir) {
401120
402044
  const asked = manifest2.permissions;
401121
402045
  if (!asked.length) return [];
401122
- const file2 = dir ? join11(dir, "plugin-permissions.json") : null;
401123
- const approved = file2 && existsSync12(file2) ? JSON.parse(readFileSync12(file2, "utf8")) : {};
402046
+ const file2 = dir ? join12(dir, "plugin-permissions.json") : null;
402047
+ const approved = file2 && existsSync13(file2) ? JSON.parse(readFileSync13(file2, "utf8")) : {};
401124
402048
  if (asked.every((p2) => approved[manifest2.id]?.includes(p2))) return asked.slice();
401125
402049
  const answer2 = await askConfirmation({
401126
402050
  id: newConfirmId(),
@@ -401131,8 +402055,8 @@ async function approvedPermissions(manifest2, dir) {
401131
402055
  });
401132
402056
  if (!answer2.approved) throw new CliError(`${manifest2.name} was not run: ${answer2.reason}.`, 3);
401133
402057
  if (file2) {
401134
- mkdirSync8(dirname4(file2), { recursive: true });
401135
- writeFileSync9(file2, JSON.stringify({ ...approved, [manifest2.id]: asked }));
402058
+ mkdirSync9(dirname5(file2), { recursive: true });
402059
+ writeFileSync10(file2, JSON.stringify({ ...approved, [manifest2.id]: asked }));
401136
402060
  }
401137
402061
  return asked.slice();
401138
402062
  }
@@ -401145,8 +402069,8 @@ async function runPluginHeadless(plugin, opts) {
401145
402069
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
401146
402070
  const storageDir = opts.storageDir ?? null;
401147
402071
  const granted = await approvedPermissions(manifest2, storageDir);
401148
- const storageFile = storageDir ? join11(storageDir, "plugin-storage", `${manifest2.id}.json`) : null;
401149
- const storage = storageFile && existsSync12(storageFile) ? JSON.parse(readFileSync12(storageFile, "utf8")) : {};
402072
+ const storageFile = storageDir ? join12(storageDir, "plugin-storage", `${manifest2.id}.json`) : null;
402073
+ const storage = storageFile && existsSync13(storageFile) ? JSON.parse(readFileSync13(storageFile, "utf8")) : {};
401150
402074
  const errors2 = [];
401151
402075
  const app = (what) => new Error(`${manifest2.name} ${what}, which only works in the app: install it there with \`popcraft plugins install\`.`);
401152
402076
  let finish3;
@@ -401172,8 +402096,8 @@ async function runPluginHeadless(plugin, opts) {
401172
402096
  storageSet: async (key5, value3) => {
401173
402097
  storage[key5] = value3;
401174
402098
  if (!storageFile) return;
401175
- mkdirSync8(dirname4(storageFile), { recursive: true });
401176
- writeFileSync9(storageFile, JSON.stringify(storage));
402099
+ mkdirSync9(dirname5(storageFile), { recursive: true });
402100
+ writeFileSync10(storageFile, JSON.stringify(storage));
401177
402101
  },
401178
402102
  exportNode: async () => {
401179
402103
  throw app("renders with the canvas");
@@ -401226,7 +402150,7 @@ async function runPluginHeadless(plugin, opts) {
401226
402150
  async function runPluginCommand(args2, flags, out) {
401227
402151
  const [ref3, file2] = args2;
401228
402152
  if (!ref3 || !file2) throw usage("Usage: popcraft plugins run <plugin> <file.popcraft> [--command <id>] [--out <file>] [--dry-run] [--timeout <seconds>]");
401229
- if (!existsSync12(file2)) throw usage(`No such file: ${file2}`);
402153
+ if (!existsSync13(file2)) throw usage(`No such file: ${file2}`);
401230
402154
  const plugin = loadPlugin(ref3, flagString(flags, "dataDir") ?? desktopDataDir());
401231
402155
  openInStore(readPopcraft(file2));
401232
402156
  const before = currentDocument();
@@ -401356,7 +402280,7 @@ init_resources();
401356
402280
  init_desktop();
401357
402281
  init_hypery();
401358
402282
  import { createInterface } from "node:readline";
401359
- var VERSION2 = "0.13.2";
402283
+ var VERSION2 = "0.15.0";
401360
402284
  var HELP = `popcraft ${VERSION2} \u2014 the PopCraft command line
401361
402285
 
401362
402286
  Files (headless, on .popcraft files):
@@ -401398,6 +402322,10 @@ Templates (made as PopCraft's own are, with @popcraft/kit installed beside them)
401398
402322
  popcraft publish [dir] [--dry-run] [--changed-since <ref>] marketplace items under dir (default: .), templates
401399
402323
  with their media/ and pictures/
401400
402324
 
402325
+ Storefronts (one design, bound to a store's data, as a store's project):
402326
+ popcraft storefront <file> [--target next-commerce|shopify-theme] [--out <dir>] [--page <id|name>]
402327
+ [--styling inline|tailwind] [--dir components/popcraft] [--app-url <url>]
402328
+
401401
402329
  The desktop app's local folders:
401402
402330
  popcraft plugins|themes|fonts|skills|agents [list | install <path|url> | remove <name> | path]
401403
402331
  popcraft plugins run <plugin> <file> [--command <id>] a plugin's main script on the file, sandboxed (no panel)
@@ -401520,6 +402448,10 @@ async function main(argv) {
401520
402448
  const { runTemplate: runTemplate2 } = await Promise.resolve().then(() => (init_template(), template_exports));
401521
402449
  return runTemplate2(rest, flags, { out, appUrl: (flagString(flags, "appUrl") ?? process.env.POPCRAFT_APP_URL ?? url4).replace(/\/+$/, ""), target: target2 });
401522
402450
  }
402451
+ case "storefront": {
402452
+ const { runStorefront: runStorefront2 } = await Promise.resolve().then(() => (init_storefront3(), storefront_exports2));
402453
+ return runStorefront2(rest, flags, out, (flagString(flags, "appUrl") ?? process.env.POPCRAFT_APP_URL ?? url4).replace(/\/+$/, ""));
402454
+ }
401523
402455
  case "pictures": {
401524
402456
  const { runPictures: runPictures2 } = await Promise.resolve().then(() => (init_pictures(), pictures_exports));
401525
402457
  await runPictures2(rest[0] ?? ".", (flagString(flags, "appUrl") ?? process.env.POPCRAFT_APP_URL ?? url4).replace(/\/+$/, ""), out);