@popcraft/cli 0.20.3 → 0.21.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 +207 -44
  2. package/package.json +2 -2
package/dist/popcraft.mjs CHANGED
@@ -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.20.3"}`, ...opts.headers };
1189
+ const headers = { "user-agent": `popcraft-cli/${"0.21.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 {
@@ -5303,6 +5303,9 @@ var init_properties = __esm({
5303
5303
  // Fill
5304
5304
  itemProp(FILLS, "color", "Color", { valueType: "color", group: "Fill", control: color, essential: true, accepts: (it) => it.type === "SOLID" }),
5305
5305
  itemProp(FILLS, "opacity", "Opacity", { valueType: "number", group: "Fill", control: percent, accepts: () => true, defaultOf: () => 1 }),
5306
+ // A picture's address, swapped whole: what an IMAGE component property feeds (a product card's photo).
5307
+ itemProp(FILLS, "imageUrl", "Picture", { valueType: "string", group: "Fill", control: { kind: "text" }, accepts: (it) => it.type === "IMAGE" }),
5308
+ nodeProp("imageUrl", "Picture", { valueType: "string", group: "Fill", control: { kind: "text" }, appliesTo: (n) => n.type === "IMAGE" }),
5306
5309
  itemProp(FILLS, "color", "Color", { valueType: "color", group: "Fill", control: color, accepts: (it) => GRADIENTS.has(it.type), sub: { list: "gradientStops", label: (j) => `Stop ${j + 1}` } }),
5307
5310
  itemProp(FILLS, "position", "Position", { valueType: "number", group: "Fill", control: percent, accepts: (it) => GRADIENTS.has(it.type), sub: { list: "gradientStops", label: (j) => `Stop ${j + 1}` } }),
5308
5311
  shaderParamProp(),
@@ -32639,6 +32642,7 @@ var init_en2 = __esm({
32639
32642
  "command:node.align#right": "Align right",
32640
32643
  "command:node.align#top": "Align top",
32641
32644
  "command:node.applyAutoLayout": "Infer auto layout",
32645
+ "command:node.bindField": "Bind to a field",
32642
32646
  "command:node.delete": "Delete selection",
32643
32647
  "command:node.distribute": "Distribute",
32644
32648
  "command:node.distribute#horizontal": "Distribute horizontally",
@@ -32662,6 +32666,7 @@ var init_en2 = __esm({
32662
32666
  "command:node.setLock": "Lock",
32663
32667
  "command:node.setLock#lock": "Lock selection",
32664
32668
  "command:node.setLock#unlock": "Unlock selection",
32669
+ "command:node.setRecord": "Show a record",
32665
32670
  "command:node.setRepeat": "Repeat for each record",
32666
32671
  "command:node.setResponsive": "Breakpoint overrides",
32667
32672
  "command:node.setSemantics": "Web semantics",
@@ -32957,6 +32962,16 @@ var init_en2 = __esm({
32957
32962
  "cropToolbar.keepThisCropEnter": "Keep this crop (Enter)",
32958
32963
  "cropToolbar.putTheCropBackAs": "Put the crop back as it was (Esc)",
32959
32964
  "cropToolbar.ratio": "Ratio",
32965
+ "dataSection.collectionHint": "Which collection this layer shows a record of",
32966
+ "dataSection.fieldHint": "The field {name} shows",
32967
+ "dataSection.fromAround": "From around it: {name}",
32968
+ "dataSection.noRecord": "No record",
32969
+ "dataSection.notBound": "Not bound",
32970
+ "dataSection.pickFirst": "Pick a record above, or put this layer in a list, to bind it to fields.",
32971
+ "dataSection.picture": "Picture",
32972
+ "dataSection.recordHint": "Which record this layer shows",
32973
+ "dataSection.text": "Text",
32974
+ "dataSection.title": "Data",
32960
32975
  "deleteAccountDialog.cancel": "Cancel",
32961
32976
  "deleteAccountDialog.couldNotDelete": "Your account could not be deleted. Try again.",
32962
32977
  "deleteAccountDialog.deleteAccount": "Delete account",
@@ -33138,6 +33153,8 @@ var init_en2 = __esm({
33138
33153
  "designPanel.pairThePaddingLeftWith": "Pair the padding (left with right, top with bottom)",
33139
33154
  "designPanel.perspective": "Perspective",
33140
33155
  "designPanel.pick": "\u2014 Pick \u2014",
33156
+ "designPanel.pictureAddress": "The picture's address",
33157
+ "designPanel.pictureAddressPlaceholder": "https://\u2026",
33141
33158
  "designPanel.pixelCanvas": "Pixel canvas",
33142
33159
  "designPanel.placeholdersLikeTakeTheSelected": "Placeholders like '{{'Label'}}' take the selected instance's property values in the Dev panel.",
33143
33160
  "designPanel.pointCount": "Point count",
@@ -33737,9 +33754,9 @@ var init_en2 = __esm({
33737
33754
  "homeTeam.publicInMarketplace": "Public in the marketplace",
33738
33755
  "homeTeam.putOnline": "Put online",
33739
33756
  "homeTeam.reject": "Reject",
33740
- "homeTeam.rejectNote": "Why (for the publisher)",
33741
33757
  "homeTeam.rejected": "Rejected",
33742
33758
  "homeTeam.rejectedBecause": "Rejected: {note}",
33759
+ "homeTeam.rejectNote": "Why (for the publisher)",
33743
33760
  "homeTeam.removeConfirm": "Take {name} off the marketplace? A plugin is unlisted (those who installed it keep it); anything else is deleted.",
33744
33761
  "homeTeam.removeTitle": "Take {name} off the marketplace?",
33745
33762
  "homeTeam.review": "Review",
@@ -254923,14 +254940,24 @@ function repeatRecords(doc2, repeat, under) {
254923
254940
  return list2.slice(from2, repeat.limit !== void 0 ? from2 + Math.max(0, repeat.limit) : void 0);
254924
254941
  }
254925
254942
  function shownRecordOf(nodes, doc2, id3) {
254943
+ const own2 = pinnedRecord(doc2, nodes[id3]);
254944
+ if (own2) return own2;
254926
254945
  for (let at3 = nodes[id3]?.parentId, hops = 0; at3 && hops < 64; at3 = nodes[at3]?.parentId, hops++) {
254927
254946
  const frame2 = nodes[at3];
254947
+ const pinned = pinnedRecord(doc2, frame2);
254948
+ if (pinned) return pinned;
254928
254949
  if (frame2?.repeat) return repeatRecords(doc2, frame2.repeat)[0] ?? null;
254929
254950
  const of = frame2?.route?.collectionId;
254930
254951
  if (of && doc2.collections?.[of]) return repeatRecords(doc2, { collectionId: of })[0] ?? null;
254931
254952
  }
254932
254953
  return null;
254933
254954
  }
254955
+ function pinnedRecord(doc2, node2) {
254956
+ const pin = node2?.record;
254957
+ if (!pin) return null;
254958
+ const r = doc2.records?.[pin.recordId];
254959
+ return r && r.collectionId === pin.collectionId && doc2.collections?.[pin.collectionId] ? r : null;
254960
+ }
254934
254961
  function recordSlugs(doc2, collectionId) {
254935
254962
  const c2 = doc2.collections?.[collectionId];
254936
254963
  const out = /* @__PURE__ */ new Map();
@@ -254961,6 +254988,12 @@ function itemNode(node2, raw, collection, record2, scope) {
254961
254988
  if (bound) for (const [prop, src] of Object.entries(bound)) {
254962
254989
  const val = tryEvaluate(src, scope);
254963
254990
  if (val === void 0) continue;
254991
+ if (prop.startsWith(PROP_BINDING) && out.type === "INSTANCE") {
254992
+ const name2 = prop.slice(PROP_BINDING.length);
254993
+ const props = out.componentProperties;
254994
+ out = { ...out, componentProperties: { ...props, [name2]: typeof val === "boolean" ? val : fieldText(val) } };
254995
+ continue;
254996
+ }
254964
254997
  const value3 = prop === "imageUrl" ? String(val) : boundPropValue(prop, val);
254965
254998
  if (value3 !== void 0) out = { ...out, [prop]: value3 };
254966
254999
  }
@@ -255043,7 +255076,7 @@ function removeRecords(doc2, ids2) {
255043
255076
  const collections = Object.fromEntries(Object.entries(doc2.collections ?? {}).map(([id3, c2]) => [id3, c2.order.some((r) => gone.has(r)) ? { ...c2, order: c2.order.filter((r) => !gone.has(r)) } : c2]));
255044
255077
  return { ...doc2, records, collections };
255045
255078
  }
255046
- var COLLECTION_LIMITS, slugify2, dataMemo, withData, ITEM_RECORD, MARK, itemMark, templateRecord, compare, TOKEN2, hasItemToken, layerId, copySuffix, copyRecord, coerce3, newId;
255079
+ var COLLECTION_LIMITS, slugify2, dataMemo, withData, ITEM_RECORD, MARK, itemMark, templateRecord, compare, PROP_BINDING, TOKEN2, hasItemToken, layerId, copySuffix, copyRecord, coerce3, newId;
255047
255080
  var init_collections = __esm({
255048
255081
  "lib/document/collections.ts"() {
255049
255082
  "use strict";
@@ -255067,6 +255100,7 @@ var init_collections = __esm({
255067
255100
  values: Object.fromEntries(collection.fields.map((f2) => [f2.id, itemMark(fieldKey(f2.name))]))
255068
255101
  });
255069
255102
  compare = (a, b) => typeof a === "number" && typeof b === "number" ? a - b : String(a ?? "").localeCompare(String(b ?? ""), void 0, { numeric: true });
255103
+ PROP_BINDING = "prop:";
255070
255104
  TOKEN2 = /\{\{\s*item\.([A-Za-z0-9_]+)\s*\}\}/g;
255071
255105
  hasItemToken = (text) => !!text && text.includes("{{") && /\{\{\s*item\./.test(text);
255072
255106
  layerId = (id3) => {
@@ -256251,7 +256285,6 @@ function planEntryAction({ home }) {
256251
256285
  var STORAGE_KEY2, useHyperyTeam, TEAM_PROMPT_ENABLED, TEAM_PROMPTED_KEY, PROMPTED_KEY, useTeamPrompt, started;
256252
256286
  var init_team = __esm({
256253
256287
  "lib/client/cloud/team.ts"() {
256254
- "use strict";
256255
256288
  init_esm();
256256
256289
  init_api4();
256257
256290
  init_session();
@@ -304671,7 +304704,6 @@ async function dispatchShaped(key5, options) {
304671
304704
  var onViolation;
304672
304705
  var init_dispatch = __esm({
304673
304706
  "lib/commands/dispatch.ts"() {
304674
- "use strict";
304675
304707
  init_errors();
304676
304708
  init_registry();
304677
304709
  init_confirm();
@@ -305060,7 +305092,6 @@ function creditLine(c2) {
305060
305092
  var MAX_PHOTO_PAGE, MAX_PHOTO_QUERY, UUID, isPhotoId, photoImagePath;
305061
305093
  var init_stock = __esm({
305062
305094
  "lib/photos/stock.ts"() {
305063
- "use strict";
305064
305095
  MAX_PHOTO_PAGE = 12;
305065
305096
  MAX_PHOTO_QUERY = 200;
305066
305097
  UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -316779,7 +316810,29 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
316779
316810
  const stylesOf = (n) => styleMapFor(n, doc2.styles, styleOpts);
316780
316811
  let motion = null;
316781
316812
  const timeline = !opts.static && !start2 && first2.type === "FRAME" ? frameWebMotion(doc2, first2.id, (n) => stylesOf(prepared(n, doc2))) : null;
316813
+ let nodesNow = doc2.nodes;
316782
316814
  const build3 = (raw, parent, in3D2, fold) => {
316815
+ const pinned = pinnedRecord(doc2, raw);
316816
+ const outerItem = item, outerNodes = nodesNow;
316817
+ const pinnedTo = pinned ? doc2.collections[pinned.collectionId] : void 0;
316818
+ const holds2 = raw.type === "INSTANCE" || !!raw.childIds?.length;
316819
+ const slugField = holds2 && pinnedTo?.schema && opts.listTemplates && opts.target === "site" ? pinnedTo.fields.find((f2) => f2.id === pinnedTo.slugFieldId) ?? pinnedTo.fields.find((f2) => f2.type === "SLUG") : void 0;
316820
+ if (pinned && pinnedTo) item = slugField ? { collection: pinnedTo, record: templateRecord(pinnedTo), suffix: item?.suffix ?? "" } : { collection: pinnedTo, record: pinned, suffix: item?.suffix ?? "" };
316821
+ if (item && raw.type === "INSTANCE" && raw.boundExpressions) {
316822
+ const shown2 = itemised(raw, raw, item);
316823
+ if (shown2 !== raw) nodesNow = { ...nodesNow, [raw.id]: shown2 };
316824
+ }
316825
+ try {
316826
+ const web = buildLayer(raw, parent, in3D2, fold);
316827
+ if (!web || !pinned || !slugField) return web;
316828
+ const value3 = String(pinned.values[slugField.id] ?? "");
316829
+ return { ...web, children: [], repeat: { collectionId: pinnedTo.id, template: web.children, limit: 1, pick: { key: fieldKey(slugField.name), value: value3 } } };
316830
+ } finally {
316831
+ item = outerItem;
316832
+ nodesNow = outerNodes;
316833
+ }
316834
+ };
316835
+ const buildLayer = (raw, parent, in3D2, fold) => {
316783
316836
  if (emitted >= cap2) {
316784
316837
  truncated = true;
316785
316838
  return null;
@@ -316978,7 +317031,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
316978
317031
  try {
316979
317032
  for (const record2 of repeatRecords(doc2, repeat, under)) {
316980
317033
  item = { collection, record: record2, suffix: `${outerItem?.suffix ?? ""}~${record2.id}` };
316981
- for (const child of childrenOf(node2, doc2.nodes)) {
317034
+ for (const child of childrenOf(node2, nodesNow)) {
316982
317035
  const built = build3(child, node2, kids3D, kidFold);
316983
317036
  if (built) web.children.push(built);
316984
317037
  }
@@ -316986,7 +317039,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
316986
317039
  if (collection.perVisitor && !outerItem || collection.schema && opts.listTemplates) {
316987
317040
  item = { collection, record: templateRecord(collection), suffix: `~${ITEM_RECORD}` };
316988
317041
  const template2 = [];
316989
- for (const child of childrenOf(node2, doc2.nodes)) {
317042
+ for (const child of childrenOf(node2, nodesNow)) {
316990
317043
  const built = build3(child, node2, kids3D, kidFold);
316991
317044
  if (built) template2.push(built);
316992
317045
  }
@@ -317003,7 +317056,7 @@ function buildPlainTree(doc2, nodeId2, opts = {}, start2, hooks = {}) {
317003
317056
  } finally {
317004
317057
  item = outerItem;
317005
317058
  }
317006
- } else for (const child of childrenOf(node2, doc2.nodes)) {
317059
+ } else for (const child of childrenOf(node2, nodesNow)) {
317007
317060
  const built = build3(child, node2, kids3D, kidFold);
317008
317061
  if (built) web.children.push(built);
317009
317062
  }
@@ -320600,7 +320653,7 @@ var init_package = __esm({
320600
320653
  "packages/runtime/package.json"() {
320601
320654
  package_default = {
320602
320655
  name: "@popcraft/runtime",
320603
- version: "0.20.3",
320656
+ version: "0.21.0",
320604
320657
  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.",
320605
320658
  license: "MIT",
320606
320659
  homepage: "https://popcraft.app/docs/exporting/nextjs",
@@ -345259,7 +345312,6 @@ function exportsOf(source3) {
345259
345312
  var CODE_LIMITS, packageOf;
345260
345313
  var init_source2 = __esm({
345261
345314
  "lib/code/source.ts"() {
345262
- "use strict";
345263
345315
  CODE_LIMITS = { modules: 100, sourceLength: 2e5, packages: 12 };
345264
345316
  packageOf = (spec) => spec.split("/").slice(0, spec.startsWith("@") ? 2 : 1).join("/");
345265
345317
  }
@@ -345302,7 +345354,6 @@ function compileModule(module2) {
345302
345354
  var import_sucrase, BUILT_IN, memo4;
345303
345355
  var init_compile2 = __esm({
345304
345356
  "lib/code/compile.ts"() {
345305
- "use strict";
345306
345357
  import_sucrase = __toESM(require_dist2());
345307
345358
  init_source2();
345308
345359
  init_source2();
@@ -346270,7 +346321,8 @@ function componentPlan(doc2, screens) {
346270
346321
  props.push({ name: propName2(one ? COMMERCE_PROP[c2.schema].name : camel3(c2.name)), type: one ? type : `${type}[]`, doc: `${c2.name}; none: the design's samples.`, schema: c2.schema, collectionId: c2.id });
346271
346322
  }
346272
346323
  for (const a of new Set(all3(s.tree.root).map((n) => n.action).filter((a2) => !!a2))) props.push({ name: COMMERCE_ACTION_PROP[a], type: "(formData: FormData) => void | Promise<void>", doc: `What sending its ${a} form does (a server action).`, action: a });
346273
- return { screen: s, name: name2, props };
346324
+ const picks = [...new Set(all3(s.tree.root).filter((n) => n.repeat?.pick && doc2.collections?.[n.repeat.collectionId]?.schema === "commerce.product").map((n) => n.repeat.pick.value))].sort();
346325
+ return { screen: s, name: name2, props, ...picks.length ? { picks } : {} };
346274
346326
  });
346275
346327
  }
346276
346328
  function componentMedia(doc2, screens) {
@@ -346323,6 +346375,7 @@ function componentFiles(doc2, screens, o = {}) {
346323
346375
  if (!p2) return null;
346324
346376
  const c2 = doc2.collections[r.collectionId];
346325
346377
  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()})`;
346378
+ if (r.pick) return `((list) => [list.find(item => String(item.values[${JSON.stringify(r.pick.key)}] ?? "") === ${JSON.stringify(r.pick.value)}) ?? list[0]].filter((item): item is CommerceItem => !!item))(${out})`;
346326
346379
  if (r.offset || r.limit) out += `.slice(${r.offset ?? 0}${r.limit ? `, ${(r.offset ?? 0) + r.limit}` : ""})`;
346327
346380
  return out;
346328
346381
  },
@@ -346497,6 +346550,10 @@ function shopifyThemeFiles(doc2, screens, o = {}) {
346497
346550
  const schema = n.repeat && schemaOf(n.repeat.collectionId);
346498
346551
  if (!schema || !n.repeat) return null;
346499
346552
  const range3 = `${n.repeat.limit ? ` limit: ${n.repeat.limit}` : ""}${n.repeat.offset ? ` offset: ${n.repeat.offset}` : ""}`;
346553
+ if (n.repeat.pick && schema === "commerce.product") {
346554
+ const handle = n.repeat.pick.value.replace(/[^a-z0-9-]/gi, "");
346555
+ return { open: `{% assign p = all_products['${handle}'] %}{% if p == empty %}{% assign p = ${source3} | first %}{% endif %}{% if p %}`, close: "{% endif %}" };
346556
+ }
346500
346557
  switch (schema) {
346501
346558
  case "commerce.product":
346502
346559
  return { open: `{% for p in ${source3}${range3} %}`, close: "{% endfor %}" };
@@ -346879,14 +346936,16 @@ function pageSource(plan2, kind) {
346879
346936
  cart: 'getProducts({ sortKey: "CREATED_AT", reverse: true })',
346880
346937
  page: 'getProducts({ sortKey: "CREATED_AT", reverse: true })'
346881
346938
  };
346939
+ const picks = uses("products") ? plan2.picks ?? [] : [];
346940
+ const listed = picks.length ? `Promise.all([${productsFrom[kind]}, ...${JSON.stringify(picks)}.map((h) => getProduct(h))]).then(([list, ...picked]) => [...picked.filter((p): p is NonNullable<typeof p> => !!p), ...list])` : productsFrom[kind];
346882
346941
  const fetches = [
346883
346942
  ...kind === "collection" ? [["collection", "getCollection(handle)"]] : [],
346884
- ...uses("products") ? [["products", productsFrom[kind]]] : [],
346943
+ ...uses("products") ? [["products", listed]] : [],
346885
346944
  ...uses("categories") ? [["categories", "storeCategories()"]] : [],
346886
346945
  ["frame", "storeFrame()"]
346887
346946
  ];
346888
346947
  const shopify = [
346889
- ...kind === "product" ? ["getProduct"] : [],
346948
+ ...kind === "product" || picks.length ? ["getProduct"] : [],
346890
346949
  ...kind === "collection" ? ["getCollection"] : [],
346891
346950
  ...uses("products") ? [productsFrom[kind].slice(0, productsFrom[kind].indexOf("("))] : []
346892
346951
  ];
@@ -347033,7 +347092,8 @@ function storefrontDesign(doc2, screens) {
347033
347092
  const c2 = n.repeat && named(n.repeat.collectionId);
347034
347093
  if (!c2 || !n.repeat) return null;
347035
347094
  const range3 = `${n.repeat.limit ? ` limit="${n.repeat.limit}"` : ""}${n.repeat.offset ? ` offset="${n.repeat.offset}"` : ""}`;
347036
- return { open: `<!-- pc:each list="${c2.name}" schema="${c2.schema ?? ""}" as="item"${range3} -->`, close: "<!-- pc:end -->" };
347095
+ const pick5 = n.repeat.pick ? ` pick-${n.repeat.pick.key}="${n.repeat.pick.value.replace(/[&"<>]/g, (ch) => `&#${ch.charCodeAt(0)};`)}"` : "";
347096
+ return { open: `<!-- pc:each list="${c2.name}" schema="${c2.schema ?? ""}" as="item"${range3}${pick5} -->`, close: "<!-- pc:end -->" };
347037
347097
  },
347038
347098
  form: (n) => n.action ? { attrs: { "data-pc-action": n.action } } : null
347039
347099
  });
@@ -350976,7 +351036,7 @@ var init_motion_rich = __esm({
350976
351036
  return { summary: `Playback on ${plural2(patches.size, "instance")}`, data: { ids: [...patches.keys()] } };
350977
351037
  }
350978
351038
  });
350979
- PROP_TYPES = ["NUMBER", "COLOR", "MOTION", "BOOLEAN", "TEXT"];
351039
+ PROP_TYPES = ["NUMBER", "COLOR", "MOTION", "BOOLEAN", "TEXT", "IMAGE"];
350980
351040
  componentPropOp = external_exports.discriminatedUnion("op", [
350981
351041
  external_exports.object({
350982
351042
  op: external_exports.literal("add"),
@@ -350993,8 +351053,8 @@ var init_motion_rich = __esm({
350993
351053
  external_exports.object({
350994
351054
  op: external_exports.literal("bindLayer"),
350995
351055
  layer: external_exports.string().describe("A layer inside the component."),
350996
- name: external_exports.string().describe("A NUMBER or COLOR property."),
350997
- path: external_exports.string().max(300).nullable().describe("The layer property it feeds (a number path for NUMBER, a colour path for COLOR: fills[#0].color, opacity, text.counter \u2026); null unbinds.")
351056
+ name: external_exports.string().describe("A NUMBER, COLOR or IMAGE property."),
351057
+ path: external_exports.string().max(300).nullable().describe("The layer property it feeds (a number path for NUMBER, a colour path for COLOR: fills[#0].color, opacity, text.counter \u2026; a picture for IMAGE: imageUrl on an image layer, fills[#0].imageUrl on an image fill); null unbinds.")
350998
351058
  }).describe("Make a layer property an input: each instance's value of the property plays there (a keyed path too).")
350999
351059
  ]);
351000
351060
  motionEditComponentProperty = defineCommand({
@@ -351010,7 +351070,7 @@ var init_motion_rich = __esm({
351010
351070
  ui: { label: "Edit component property", palette: false },
351011
351071
  agent: {
351012
351072
  name: "edit_component_property",
351013
- description: `A component's properties, as its instances set them: add a NUMBER, COLOR, MOTION (one playback setting shown on every instance), BOOLEAN or TEXT property, remove one, change its default, or bind a layer property inside the component to a NUMBER / COLOR property (a layer input). Keys can also take their value from a property per instance (set_keyframes with bind), so "count to N" or "slide this far" is set per instance with set_instance_playback properties.`
351073
+ description: `A component's properties, as its instances set them: add a NUMBER, COLOR, MOTION (one playback setting shown on every instance), BOOLEAN, TEXT or IMAGE (a picture's address) property, remove one, change its default, or bind a layer property inside the component to a NUMBER / COLOR / IMAGE property (a layer input; IMAGE feeds a picture). An instance's TEXT and IMAGE properties can follow a record's field (bind_field). Keys can also take their value from a property per instance (set_keyframes with bind), so "count to N" or "slide this far" is set per instance with set_instance_playback properties.`
351014
351074
  }
351015
351075
  },
351016
351076
  run: (args2, ctx2) => {
@@ -351052,7 +351112,7 @@ var init_motion_rich = __esm({
351052
351112
  }
351053
351113
  case "bindLayer": {
351054
351114
  const def = has(op4.name);
351055
- if (def.type !== "NUMBER" && def.type !== "COLOR") throw new ToolError("Only a NUMBER or COLOR property feeds a layer input.");
351115
+ if (def.type !== "NUMBER" && def.type !== "COLOR" && def.type !== "IMAGE") throw new ToolError("Only a NUMBER, COLOR or IMAGE property feeds a layer input.");
351056
351116
  const layer2 = nodeOrThrow(doc2, op4.layer);
351057
351117
  const owner = target2.type === "COMPONENT_SET" ? target2.id : target2.id;
351058
351118
  let inside2 = false;
@@ -354042,7 +354102,6 @@ var init_publish = __esm({
354042
354102
  var storeOpsSeam;
354043
354103
  var init_seams = __esm({
354044
354104
  "lib/commands/seams.ts"() {
354045
- "use strict";
354046
354105
  storeOpsSeam = (st) => ({
354047
354106
  booleanOperation: (op4) => st().booleanOperation(op4),
354048
354107
  flatten: () => st().flattenSelection(),
@@ -356456,9 +356515,11 @@ are whole stores to begin from.
356456
356515
  - \`commerce.menu\` for the store's menu, \`commerce.collection\` for its categories
356457
356516
  Add a few real sample records with \`edit_records\`: the canvas is drawn with them. A store's real catalogue
356458
356517
  comes from the Ecommerce plugin (Marketplace \u203A Plugins): it fills these collections from Shopify.
356459
- 3. **The lists.** \`set_repeat\` on the frame that holds one card, over the collection; the card's texts say
356460
- \`{{item.title}}\`, \`{{item.price}}\`, and its picture follows \`item_image\`. The cart's totals are a list of one
356461
- (\`limit: 1\`) over the \`commerce.cart\` collection.
356518
+ 3. **The cards and lists.** Make the card a component with properties (\`edit_component_property\`: TEXT Title and
356519
+ Price, IMAGE Image fed to its picture with \`bindLayer\` path \`imageUrl\`). \`set_repeat\` on the frame that holds
356520
+ one instance, over the collection, and \`bind_field\` each property to a field (Title \u2190 title, Price \u2190 price,
356521
+ Image \u2190 image). One product in a hero or feature is \`set_record\` (by its handle), never a list of one with an
356522
+ offset. The cart's totals are a list of one (\`limit: 1\`) over the \`commerce.cart\` collection.
356462
356523
  4. **A page per product.** \`set_route\` on the product screen with the product collection's id: the route is
356463
356524
  \`/product\` and each product's page is \`/product/<handle>\`. A card links to that screen.
356464
356525
  5. **The forms.** \`set_control\` FORM with a storefront \`formAction\`:
@@ -358876,16 +358937,16 @@ var init_api5 = __esm({
358876
358937
  }
358877
358938
  });
358878
358939
 
358879
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/generated/runtime.js
358940
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/generated/runtime.js
358880
358941
  var MAIN_RUNTIME, UI_RUNTIME;
358881
358942
  var init_runtime4 = __esm({
358882
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/generated/runtime.js"() {
358883
- 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 (File \u2192 Export \u2192 this plugin\'s format), 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 /** Put TEXT layers on a path (another layer\'s outline, or their own arc, circle or wave), or tune one: brackets, alignment, placement, effect, overflow. */\n setTextPath: (opts) => rpc.call("setTextPath", opts || {}),\n /** Area type: flow a TEXT layer inside a closed shape, kept `inset` px in. */\n flowTextIntoShape: (opts) => rpc.call("flowTextIntoShape", opts || {}),\n /** Take TEXT layers off their path or out of their shape (or free every text linked to a path / shape). */\n detachText: (opts) => rpc.call("detachText", opts || {}),\n /** Auto-size TEXT layers\' type to their box, shape or path. */\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 /**\n * The open page as a storefront design in a neutral form, for an exporter that writes its own store\'s templates:\n * each page\'s HTML with the store\'s data marked (`{{page.title}}`, `{{item.price}}`, `<!-- pc:each \u2026 -->` lists,\n * `data-pc-action` forms), and its collections with their commerce schema and sample records.\n */\n getStorefront: () => rpc.call("getStorefront"),\n /**\n * A design\'s data (docs: Collections): a collection and its fields (`schema`: what it stands for, e.g.\n * `commerce.product`, whose fields are made for it), its records, and reading them.\n */\n editCollection: (opts) => rpc.call("editCollection", opts || {}),\n editRecords: (opts) => rpc.call("editRecords", opts || {}),\n getRecords: (opts) => rpc.call("getRecords", opts || {}),\n /** A layer as a web page\'s form control (a FORM\'s `formAction`, an INPUT\'s name and value), repeated per record, a page\'s address, its semantics. */\n setControl: (opts) => rpc.call("setControl", opts || {}),\n setRepeat: (opts) => rpc.call("setRepeat", opts || {}),\n setRoute: (opts) => rpc.call("setRoute", opts || {}),\n setSemantics: (opts) => rpc.call("setSemantics", opts || {}),\n /** The site\'s environment: public settings its host reads by name when it is deployed (never a secret: the file keeps them). */\n getSiteEnvironment: () => rpc.call("getSiteEnvironment"),\n setSiteEnvironment: (set) => rpc.call("setSiteEnvironment", { set }),\n /** What this plugin keeps with the open file (its settings for this design), under its own id; JSON only. */\n getFileData: (key) => rpc.call("getFileData", { key }),\n setFileData: (key, value) => rpc.call("setFileData", { key, value }),\n /** A store\'s project built from the open page, zipped (needs `export`): what an exporter saves with `saveExport`. */\n getStorefrontFiles: (target) => rpc.call("getStorefrontFiles", { target }),\n getExportRequest: () => rpc.call("getExportRequest"),\n /** Save the exporter\'s file (once per export request); the host enforces the contributed extension. */\n saveExport: (file) => rpc.call("saveExport", file || {}),\n /**\n * Presentation audio (docs/plugins/presentation-audio.md): the deck\'s slides, notes, narration and timing; the\n * soundtrack\'s settings, bed and stinger.\n */\n getPresentationAudio: (opts) => rpc.call("getPresentationAudio", opts || {}),\n /** Change the soundtrack\'s settings (voice, style, mood, loudness, beat alignment, levels) and re-time it. */\n setPresentationAudioSettings: (opts) => rpc.call("setPresentationAudioSettings", opts || {}),\n /** Put a sound in (a slide\'s narration, the bed or the stinger): bytes are normalised to the soundtrack\'s target and stored. */\n setPresentationAudioClip: (opts) => rpc.call("setPresentationAudioClip", opts || {}),\n removePresentationAudioClip: (opts) => rpc.call("removePresentationAudioClip", opts || {}),\n /** Re-time: fixed slide lengths (ms, null clears) and beat-aligned changes. */\n setPresentationTiming: (opts) => rpc.call("setPresentationTiming", opts || {}),\n /** The mix as the export carries it: loudness, true peak, and (wav: true) the 48 kHz stereo WAV. */\n getPresentationMix: (opts) => rpc.call("getPresentationMix", opts || {}),\n /** Ask the user to generate with AI (the panel opens with the request filled in; the user decides). */\n requestPresentationAudio: (opts) => rpc.call("requestPresentationAudio", opts || {}),\n /** Editor commands: each takes the arguments documented in docs/plugins/methods.md (the agent tool of the same purpose). */\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 usePresentationFallbackAudio: (opts) => rpc.call("usePresentationFallbackAudio", opts || {}),\n /** Bring a Lottie, PDF or PopCraft file into the open document (the `importFile` field is the file an importer was handed). */\n importDocumentFile: (opts) => rpc.call("importDocumentFile", 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.importMode = (_a = data.importMode) != null ? _a : null;\n popcraft.importFile = (_b = data.importFile) != null ? _b : null;\n popcraft.exportRequest = (_c = data.exportRequest) != null ? _c : 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})();';
358943
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/generated/runtime.js"() {
358944
+ 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 (File \u2192 Export \u2192 this plugin\'s format), 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 /** Put TEXT layers on a path (another layer\'s outline, or their own arc, circle or wave), or tune one: brackets, alignment, placement, effect, overflow. */\n setTextPath: (opts) => rpc.call("setTextPath", opts || {}),\n /** Area type: flow a TEXT layer inside a closed shape, kept `inset` px in. */\n flowTextIntoShape: (opts) => rpc.call("flowTextIntoShape", opts || {}),\n /** Take TEXT layers off their path or out of their shape (or free every text linked to a path / shape). */\n detachText: (opts) => rpc.call("detachText", opts || {}),\n /** Auto-size TEXT layers\' type to their box, shape or path. */\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 /**\n * The open page as a storefront design in a neutral form, for an exporter that writes its own store\'s templates:\n * each page\'s HTML with the store\'s data marked (`{{page.title}}`, `{{item.price}}`, `<!-- pc:each \u2026 -->` lists,\n * `data-pc-action` forms), and its collections with their commerce schema and sample records.\n */\n getStorefront: () => rpc.call("getStorefront"),\n /**\n * A design\'s data (docs: Collections): a collection and its fields (`schema`: what it stands for, e.g.\n * `commerce.product`, whose fields are made for it), its records, and reading them.\n */\n editCollection: (opts) => rpc.call("editCollection", opts || {}),\n editRecords: (opts) => rpc.call("editRecords", opts || {}),\n getRecords: (opts) => rpc.call("getRecords", opts || {}),\n /** A layer as a web page\'s form control (a FORM\'s `formAction`, an INPUT\'s name and value), repeated per record, a page\'s address, its semantics. */\n setControl: (opts) => rpc.call("setControl", opts || {}),\n setRepeat: (opts) => rpc.call("setRepeat", opts || {}),\n setRoute: (opts) => rpc.call("setRoute", opts || {}),\n setSemantics: (opts) => rpc.call("setSemantics", opts || {}),\n /** Which record layers are about (one product, picked from a collection), and which of its fields a layer\'s words, picture or an instance\'s properties follow. */\n setRecord: (opts) => rpc.call("setRecord", opts || {}),\n bindField: (opts) => rpc.call("bindField", opts || {}),\n /** The site\'s environment: public settings its host reads by name when it is deployed (never a secret: the file keeps them). */\n getSiteEnvironment: () => rpc.call("getSiteEnvironment"),\n setSiteEnvironment: (set) => rpc.call("setSiteEnvironment", { set }),\n /** What this plugin keeps with the open file (its settings for this design), under its own id; JSON only. */\n getFileData: (key) => rpc.call("getFileData", { key }),\n setFileData: (key, value) => rpc.call("setFileData", { key, value }),\n /** A store\'s project built from the open page, zipped (needs `export`): what an exporter saves with `saveExport`. */\n getStorefrontFiles: (target) => rpc.call("getStorefrontFiles", { target }),\n getExportRequest: () => rpc.call("getExportRequest"),\n /** Save the exporter\'s file (once per export request); the host enforces the contributed extension. */\n saveExport: (file) => rpc.call("saveExport", file || {}),\n /**\n * Presentation audio (docs/plugins/presentation-audio.md): the deck\'s slides, notes, narration and timing; the\n * soundtrack\'s settings, bed and stinger.\n */\n getPresentationAudio: (opts) => rpc.call("getPresentationAudio", opts || {}),\n /** Change the soundtrack\'s settings (voice, style, mood, loudness, beat alignment, levels) and re-time it. */\n setPresentationAudioSettings: (opts) => rpc.call("setPresentationAudioSettings", opts || {}),\n /** Put a sound in (a slide\'s narration, the bed or the stinger): bytes are normalised to the soundtrack\'s target and stored. */\n setPresentationAudioClip: (opts) => rpc.call("setPresentationAudioClip", opts || {}),\n removePresentationAudioClip: (opts) => rpc.call("removePresentationAudioClip", opts || {}),\n /** Re-time: fixed slide lengths (ms, null clears) and beat-aligned changes. */\n setPresentationTiming: (opts) => rpc.call("setPresentationTiming", opts || {}),\n /** The mix as the export carries it: loudness, true peak, and (wav: true) the 48 kHz stereo WAV. */\n getPresentationMix: (opts) => rpc.call("getPresentationMix", opts || {}),\n /** Ask the user to generate with AI (the panel opens with the request filled in; the user decides). */\n requestPresentationAudio: (opts) => rpc.call("requestPresentationAudio", opts || {}),\n /** Editor commands: each takes the arguments documented in docs/plugins/methods.md (the agent tool of the same purpose). */\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 usePresentationFallbackAudio: (opts) => rpc.call("usePresentationFallbackAudio", opts || {}),\n /** Bring a Lottie, PDF or PopCraft file into the open document (the `importFile` field is the file an importer was handed). */\n importDocumentFile: (opts) => rpc.call("importDocumentFile", 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.importMode = (_a = data.importMode) != null ? _a : null;\n popcraft.importFile = (_b = data.importFile) != null ? _b : null;\n popcraft.exportRequest = (_c = data.exportRequest) != null ? _c : 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})();';
358884
358945
  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})();';
358885
358946
  }
358886
358947
  });
358887
358948
 
358888
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/host.js
358949
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/host.js
358889
358950
  function csp(network) {
358890
358951
  const connect2 = network ? "connect-src * data: blob:;" : "connect-src 'none';";
358891
358952
  const img = network ? "img-src * data: blob:;" : "img-src data: blob:;";
@@ -358913,40 +358974,40 @@ function buildUIDocument(uiHtml, network) {
358913
358974
  }
358914
358975
  var escapeScript;
358915
358976
  var init_host2 = __esm({
358916
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/host.js"() {
358977
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/host.js"() {
358917
358978
  init_runtime4();
358918
358979
  escapeScript = (code) => code.replace(/<\/script/gi, "<\\/script");
358919
358980
  }
358920
358981
  });
358921
358982
 
358922
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/runtime/rpc.js
358983
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/runtime/rpc.js
358923
358984
  var init_rpc = __esm({
358924
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/runtime/rpc.js"() {
358985
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/runtime/rpc.js"() {
358925
358986
  }
358926
358987
  });
358927
358988
 
358928
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/runtime/main.js
358989
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/runtime/main.js
358929
358990
  var init_main = __esm({
358930
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/runtime/main.js"() {
358991
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/runtime/main.js"() {
358931
358992
  init_rpc();
358932
358993
  }
358933
358994
  });
358934
358995
 
358935
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/types.js
358996
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/types.js
358936
358997
  var init_types9 = __esm({
358937
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/types.js"() {
358998
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/types.js"() {
358938
358999
  }
358939
359000
  });
358940
359001
 
358941
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/widget.js
359002
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/widget.js
358942
359003
  var init_widget = __esm({
358943
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/widget.js"() {
359004
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/widget.js"() {
358944
359005
  }
358945
359006
  });
358946
359007
 
358947
- // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/index.js
359008
+ // node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/index.js
358948
359009
  var init_dist = __esm({
358949
- "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=e61849e2a6a59df95f0ebad36ab274c37a933fcf798d8b4dd6b3f0ffc625a1da/node_modules/@popcraft/plugins/dist/index.js"() {
359010
+ "node_modules/.pnpm/@popcraft+plugins@1.0.0_patch_hash=6826cb40a12857607fa1f7d7d102e5d8316bd483c1ff06865dac73139dfe709b/node_modules/@popcraft/plugins/dist/index.js"() {
358950
359011
  init_host2();
358951
359012
  init_main();
358952
359013
  init_runtime4();
@@ -390534,7 +390595,6 @@ function record(source, namespaces, globals, limits) {
390534
390595
  var HELPER_NAMES;
390535
390596
  var init_helper_names = __esm({
390536
390597
  "lib/agent/design-script/helper-names.ts"() {
390537
- "use strict";
390538
390598
  HELPER_NAMES = ["draw", "marks", "fit", "motion", "captions", "overlays", "texture", "pointer", "windows", "devices", "type", "beat", "web", "social", "video", "site", "effects"];
390539
390599
  }
390540
390600
  });
@@ -390917,7 +390977,6 @@ function designRecorder() {
390917
390977
  var installed;
390918
390978
  var init_recorders = __esm({
390919
390979
  "lib/agent/design-script/recorders.ts"() {
390920
- "use strict";
390921
390980
  installed = null;
390922
390981
  }
390923
390982
  });
@@ -394052,7 +394111,31 @@ function collectionOf(doc2, ref3) {
394052
394111
  if (!c2) throw new ToolError(`No collection "${ref3}". ${Object.values(doc2.collections ?? {}).length ? `There is: ${Object.values(doc2.collections).map((x2) => x2.name).join(", ")}.` : "Make one with edit_collection."}`);
394053
394112
  return c2;
394054
394113
  }
394055
- var sizing2, padding2, overrides, nodeSetResponsive, link, nodeSetSemantics, nodeSetControl, siteSetRoute, breakpoint, siteSetBreakpoints, siteSetGpuTransitions, ENV_NAME, RESERVED_ENV, siteSetEnvironment, siteGetEnvironment, siteGetBreakpoints, described2, dataEditCollection, dataEditRecords, dataGetRecords, nodeSetRepeat;
394114
+ function recordOf(doc2, c2, ref3) {
394115
+ const own2 = doc2.records?.[ref3];
394116
+ if (own2?.collectionId === c2.id) return own2;
394117
+ const slugs = recordSlugs(doc2, c2.id);
394118
+ const want = ref3.trim().toLowerCase();
394119
+ const found = recordsIn(doc2, c2.id).find((r) => slugs.get(r.id) === want || c2.fields.some((f2) => String(r.values[f2.id] ?? "").trim().toLowerCase() === want));
394120
+ if (!found) throw new ToolError(`${c2.name} has no record "${ref3}". Read them with get_records.`);
394121
+ return found;
394122
+ }
394123
+ function bindingKey(doc2, node2, property) {
394124
+ if (property === "text") {
394125
+ if (node2.type !== "TEXT") throw new ToolError(`"${node2.name}" is not a text.`);
394126
+ return "characters";
394127
+ }
394128
+ if (property === "picture") {
394129
+ if (node2.type !== "IMAGE") throw new ToolError(`"${node2.name}" is not an image layer: give its component an IMAGE property instead.`);
394130
+ return "imageUrl";
394131
+ }
394132
+ if (node2.type !== "INSTANCE") throw new ToolError(`"${node2.name}" has no property "${property}": say text, picture, or an instance's property.`);
394133
+ const defs = componentDefinitions(doc2.nodes, node2.mainComponentId);
394134
+ const def = defs[property];
394135
+ if (!def || !["TEXT", "IMAGE", "BOOLEAN"].includes(def.type)) throw new ToolError(`"${node2.name}" has no TEXT, IMAGE or BOOLEAN property "${property}". It has: ${Object.keys(defs).join(", ") || "none"}.`);
394136
+ return `${PROP_BINDING}${property}`;
394137
+ }
394138
+ var sizing2, padding2, overrides, nodeSetResponsive, link, nodeSetSemantics, nodeSetControl, siteSetRoute, breakpoint, siteSetBreakpoints, siteSetGpuTransitions, ENV_NAME, RESERVED_ENV, siteSetEnvironment, siteGetEnvironment, siteGetBreakpoints, described2, dataEditCollection, dataEditRecords, dataGetRecords, nodeSetRepeat, nodeSetRecord, nodeBindField, recordLabel;
394056
394139
  var init_site3 = __esm({
394057
394140
  "lib/commands/site.ts"() {
394058
394141
  "use strict";
@@ -394061,6 +394144,7 @@ var init_site3 = __esm({
394061
394144
  init_breakpoints();
394062
394145
  init_collections();
394063
394146
  init_semantics();
394147
+ init_instances();
394064
394148
  init_controls();
394065
394149
  init_types();
394066
394150
  init_routes();
@@ -394659,6 +394743,83 @@ var init_site3 = __esm({
394659
394743
  return { summary: c2 ? `Repeats for ${c2.name} (${shown2} records)` : "No longer repeats", data: { ids: ids2, collectionId: c2?.id ?? null } };
394660
394744
  }
394661
394745
  });
394746
+ nodeSetRecord = defineCommand({
394747
+ id: "node.setRecord",
394748
+ title: "Show a record",
394749
+ domain: "node",
394750
+ effect: "document",
394751
+ permission: "write",
394752
+ undoLabel: "Show a record",
394753
+ params: external_exports.object({
394754
+ ids: external_exports.array(external_exports.string()).min(1).max(50).optional().describe("Layers to edit (default: the selection)."),
394755
+ collection: external_exports.string().nullable().describe("The collection, by id or name; null: the layers show no record of their own again."),
394756
+ record: external_exports.string().optional().describe("The record, by id, page address, or the value of one of its fields (a product's handle or title).")
394757
+ }).strict(),
394758
+ expose: {
394759
+ plugin: { method: "setRecord" },
394760
+ ui: { label: "Show a record", keywords: ["record", "data", "bind", "product", "cms", "collection", "source"], enabled: needsSelection(1), palette: false },
394761
+ agent: {
394762
+ name: "set_record",
394763
+ description: "Make layers about one record of a collection (one product, one post): they and what they hold show its values wherever they follow a field \u2014 a text with {{item.<field key>}}, a picture or an instance property bound with bind_field. The nearest record wins: a layer's own, then a list it is in, then its page's."
394764
+ }
394765
+ },
394766
+ run: (args2, ctx2) => {
394767
+ const { doc: doc2, ids: ids2 } = resolveTargets(ctx2, args2.ids, "set_record");
394768
+ const c2 = args2.collection ? collectionOf(doc2, args2.collection) : null;
394769
+ if (c2 && !args2.record) throw new ToolError("Say which record.");
394770
+ const r = c2 ? recordOf(doc2, c2, args2.record) : null;
394771
+ const pin = c2 && r ? { collectionId: c2.id, recordId: r.id } : void 0;
394772
+ commitUpdates(ctx2, doc2, new Map(ids2.map((id3) => [id3, { record: pin }])));
394773
+ return { summary: pin ? `Shows ${c2.name}: ${recordLabel(c2, r)}` : "Shows no record of its own", data: { ids: ids2, record: pin ?? null } };
394774
+ }
394775
+ });
394776
+ nodeBindField = defineCommand({
394777
+ id: "node.bindField",
394778
+ title: "Bind to a field",
394779
+ domain: "node",
394780
+ effect: "document",
394781
+ permission: "write",
394782
+ undoLabel: "Bind to a field",
394783
+ params: external_exports.object({
394784
+ ids: external_exports.array(external_exports.string()).min(1).max(50).optional().describe("Layers to edit (default: the selection)."),
394785
+ property: external_exports.string().min(1).max(120).describe(`What follows the field: "text" (a text's words), "picture" (an image layer's picture), or the name of one of an instance's TEXT, IMAGE or BOOLEAN properties.`),
394786
+ field: external_exports.string().nullable().describe("The field, by name or key, of the record the layer is about (its own, its list's or its page's); null: it stops following one.")
394787
+ }).strict(),
394788
+ expose: {
394789
+ plugin: { method: "bindField" },
394790
+ ui: { label: "Bind to a field", keywords: ["bind", "field", "data", "source", "dynamic", "cms", "product"], enabled: needsSelection(1), palette: false },
394791
+ agent: {
394792
+ name: "bind_field",
394793
+ description: "Make a layer show a field of the record it is about (set_record, a list, or a collection page): a text's words, an image layer's picture, or an instance's TEXT / IMAGE / BOOLEAN property \u2014 so a product card component takes its title, price and photo from the record. null unbinds."
394794
+ }
394795
+ },
394796
+ run: (args2, ctx2) => {
394797
+ const { doc: doc2, ids: ids2 } = resolveTargets(ctx2, args2.ids, "bind_field");
394798
+ const updates = /* @__PURE__ */ new Map();
394799
+ for (const id3 of ids2) {
394800
+ const node2 = doc2.nodes[id3];
394801
+ const key5 = bindingKey(doc2, node2, args2.property);
394802
+ const bound = { ...node2.boundExpressions ?? {} };
394803
+ if (args2.field === null) delete bound[key5];
394804
+ else {
394805
+ const record2 = shownRecordOf(doc2.nodes, doc2, id3);
394806
+ const c2 = record2 && doc2.collections?.[record2.collectionId];
394807
+ if (!c2) throw new ToolError(`"${node2.name}" is about no record: pick one with set_record, or put it in a list (set_repeat).`);
394808
+ const want = fieldKey(args2.field);
394809
+ const f2 = c2.fields.find((x2) => x2.id === args2.field || fieldKey(x2.name) === want);
394810
+ if (!f2) throw new ToolError(`${c2.name} has no field "${args2.field}". It has: ${c2.fields.map((x2) => fieldKey(x2.name)).join(", ")}.`);
394811
+ bound[key5] = `item_${fieldKey(f2.name)}`;
394812
+ }
394813
+ updates.set(id3, { boundExpressions: Object.keys(bound).length ? bound : void 0 });
394814
+ }
394815
+ commitUpdates(ctx2, doc2, updates);
394816
+ return { summary: args2.field === null ? `${args2.property} follows no field` : `${args2.property} \u2190 ${args2.field}`, data: { ids: ids2 } };
394817
+ }
394818
+ });
394819
+ recordLabel = (c2, r) => {
394820
+ const f2 = c2.fields.find((x2) => x2.type === "TEXT") ?? c2.fields[0];
394821
+ return String((f2 && r.values[f2.id]) ?? r.id).slice(0, 80);
394822
+ };
394662
394823
  }
394663
394824
  });
394664
394825
 
@@ -395756,6 +395917,8 @@ var init_builtin4 = __esm({
395756
395917
  siteSetRoute,
395757
395918
  nodeSetControl,
395758
395919
  nodeSetRepeat,
395920
+ nodeSetRecord,
395921
+ nodeBindField,
395759
395922
  siteSetEnvironment,
395760
395923
  siteGetEnvironment,
395761
395924
  codeEditModule,
@@ -403062,7 +403225,7 @@ init_resources();
403062
403225
  init_desktop();
403063
403226
  init_hypery();
403064
403227
  import { createInterface } from "node:readline";
403065
- var VERSION2 = "0.20.3";
403228
+ var VERSION2 = "0.21.0";
403066
403229
  var HELP = `popcraft ${VERSION2} \u2014 the PopCraft command line
403067
403230
 
403068
403231
  Files (headless, on .popcraft files):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popcraft/cli",
3
- "version": "0.20.3",
3
+ "version": "0.21.0",
4
4
  "description": "The PopCraft command line: edit .popcraft files headlessly, call the REST API, publish to the marketplace and manage the desktop app's local plugins, themes, fonts, skills and agents.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://popcraft.app/docs/api/cli",
@@ -34,7 +34,7 @@
34
34
  "esbuild": "0.28.2"
35
35
  },
36
36
  "dependencies": {
37
- "@popcraft/kit": "0.20.3"
37
+ "@popcraft/kit": "0.21.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node build.mjs",