@popcraft/cli 0.53.1 → 0.53.2

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 +357 -165
  2. package/package.json +2 -2
package/dist/popcraft.mjs CHANGED
@@ -1196,7 +1196,7 @@ function operations(spec = openapi) {
1196
1196
  }
1197
1197
  async function call(target2, method, path, opts = {}) {
1198
1198
  const qs = new URLSearchParams(opts.query ?? {}).toString();
1199
- const headers = { "user-agent": `popcraft-cli/${"0.53.1"}`, ...opts.headers };
1199
+ const headers = { "user-agent": `popcraft-cli/${"0.53.2"}`, ...opts.headers };
1200
1200
  if (target2.token) headers.authorization = `Bearer ${target2.token}`;
1201
1201
  if (opts.contentType) headers["content-type"] = opts.contentType;
1202
1202
  try {
@@ -6786,8 +6786,8 @@ function currentPrintPage(doc2, pageId2, o = {}) {
6786
6786
  }
6787
6787
  const c2 = o.centre;
6788
6788
  if (c2) {
6789
- const inside2 = pages2.find((p2) => c2.x >= p2.x && c2.x <= p2.x + p2.width && c2.y >= p2.y && c2.y <= p2.y + p2.height);
6790
- if (inside2) return inside2;
6789
+ const inside3 = pages2.find((p2) => c2.x >= p2.x && c2.x <= p2.x + p2.width && c2.y >= p2.y && c2.y <= p2.y + p2.height);
6790
+ if (inside3) return inside3;
6791
6791
  let best = pages2[0], bestD = Infinity;
6792
6792
  for (const p2 of pages2) {
6793
6793
  const dx = Math.max(p2.x - c2.x, 0, c2.x - (p2.x + p2.width));
@@ -96212,7 +96212,6 @@ function repairDocumentPaints(doc2) {
96212
96212
  var broken;
96213
96213
  var init_paint_repair = __esm({
96214
96214
  "lib/document/paint-repair.ts"() {
96215
- "use strict";
96216
96215
  init_shader_graph();
96217
96216
  broken = (p2) => {
96218
96217
  if (!p2 || typeof p2 !== "object") return false;
@@ -96870,6 +96869,7 @@ function releaseMediaMemory(hash3) {
96870
96869
  var key2, peaksKey, objectUrls, sourceKey, remoteResolver, MEDIA_FETCH_TIMEOUT_MS, SHIPPED_MEDIA_PATH, notShipped, MEDIA_SCHEME, isImageDataUrl, memory, pendingLoads, sourceAliases, PAINT_KEYS, URL_KEYS;
96871
96870
  var init_media_store = __esm({
96872
96871
  "lib/storage/media-store.ts"() {
96872
+ "use strict";
96873
96873
  init_media_types();
96874
96874
  init_idb();
96875
96875
  init_media_hash();
@@ -251991,12 +251991,65 @@ var require_polygon_clipping_cjs = __commonJS({
251991
251991
  }
251992
251992
  });
251993
251993
 
251994
+ // lib/engine/geometry/winding.ts
251995
+ function interiorPoint(r) {
251996
+ let cx = 0, cy = 0;
251997
+ for (const [x2, y] of r) {
251998
+ cx += x2;
251999
+ cy += y;
252000
+ }
252001
+ const centroid2 = [cx / r.length, cy / r.length];
252002
+ if (inside(centroid2, r)) return centroid2;
252003
+ const [a, b, c2] = [r[0], r[1], r[2] ?? r[0]];
252004
+ return [(a[0] + b[0] + c2[0]) / 3, (a[1] + b[1] + c2[1]) / 3];
252005
+ }
252006
+ function nonzeroMultiPoly(rings) {
252007
+ const usable = rings.filter((r) => r.length >= 3);
252008
+ if (!usable.length) return [];
252009
+ const polys = usable.map((r) => [r]);
252010
+ if (usable.length === 1) return import_polygon_clipping.default.union(polys[0]);
252011
+ const areas = usable.map(signedArea);
252012
+ const add3 = [], sub2 = [];
252013
+ usable.forEach((r, i2) => {
252014
+ const p2 = interiorPoint(r);
252015
+ let w = Math.sign(areas[i2]);
252016
+ usable.forEach((o, j) => {
252017
+ if (j !== i2 && Math.abs(areas[j]) > Math.abs(areas[i2]) && inside(p2, o)) w += Math.sign(areas[j]);
252018
+ });
252019
+ (w === 0 ? sub2 : add3).push(polys[i2]);
252020
+ });
252021
+ if (!add3.length) return [];
252022
+ const filled2 = import_polygon_clipping.default.union(add3[0], ...add3.slice(1));
252023
+ return sub2.length ? import_polygon_clipping.default.difference(filled2, ...sub2) : filled2;
252024
+ }
252025
+ var import_polygon_clipping, signedArea, inside;
252026
+ var init_winding = __esm({
252027
+ "lib/engine/geometry/winding.ts"() {
252028
+ "use strict";
252029
+ import_polygon_clipping = __toESM(require_polygon_clipping_cjs());
252030
+ signedArea = (r) => {
252031
+ let a = 0;
252032
+ for (let i2 = 0, j = r.length - 1; i2 < r.length; j = i2++) a += (r[j][0] - r[i2][0]) * (r[j][1] + r[i2][1]);
252033
+ return a / 2;
252034
+ };
252035
+ inside = ([x2, y], r) => {
252036
+ let c2 = false;
252037
+ for (let i2 = 0, j = r.length - 1; i2 < r.length; j = i2++) {
252038
+ const [xi, yi] = r[i2], [xj, yj] = r[j];
252039
+ if (yi > y !== yj > y && x2 < (xj - xi) * (y - yi) / (yj - yi) + xi) c2 = !c2;
252040
+ }
252041
+ return c2;
252042
+ };
252043
+ }
252044
+ });
252045
+
251994
252046
  // lib/engine/geometry/triangulate.ts
251995
- var import_polygon_clipping;
252047
+ var import_polygon_clipping2;
251996
252048
  var init_triangulate = __esm({
251997
252049
  "lib/engine/geometry/triangulate.ts"() {
251998
252050
  "use strict";
251999
- import_polygon_clipping = __toESM(require_polygon_clipping_cjs());
252051
+ import_polygon_clipping2 = __toESM(require_polygon_clipping_cjs());
252052
+ init_winding();
252000
252053
  }
252001
252054
  });
252002
252055
 
@@ -252020,7 +252073,7 @@ function computeBooleanMultiPoly(node2, allNodes) {
252020
252073
  const asPolys = rings.map((r) => [r]);
252021
252074
  let mp;
252022
252075
  try {
252023
- mp = asPolys.length > 1 ? import_polygon_clipping2.default.union(asPolys[0], ...asPolys.slice(1)) : asPolys;
252076
+ mp = asPolys.length > 1 ? import_polygon_clipping3.default.union(asPolys[0], ...asPolys.slice(1)) : asPolys;
252024
252077
  } catch {
252025
252078
  mp = asPolys;
252026
252079
  }
@@ -252032,25 +252085,25 @@ function computeBooleanMultiPoly(node2, allNodes) {
252032
252085
  try {
252033
252086
  const rest = operands.slice(1);
252034
252087
  if (rest.length > 0) {
252035
- if (op4 === "SUBTRACT") result = import_polygon_clipping2.default.difference(operands[0], ...rest);
252036
- else if (op4 === "INTERSECT") result = import_polygon_clipping2.default.intersection(operands[0], ...rest);
252037
- else if (op4 === "EXCLUDE") result = import_polygon_clipping2.default.xor(operands[0], ...rest);
252038
- else result = import_polygon_clipping2.default.union(operands[0], ...rest);
252088
+ if (op4 === "SUBTRACT") result = import_polygon_clipping3.default.difference(operands[0], ...rest);
252089
+ else if (op4 === "INTERSECT") result = import_polygon_clipping3.default.intersection(operands[0], ...rest);
252090
+ else if (op4 === "EXCLUDE") result = import_polygon_clipping3.default.xor(operands[0], ...rest);
252091
+ else result = import_polygon_clipping3.default.union(operands[0], ...rest);
252039
252092
  }
252040
252093
  } catch {
252041
252094
  try {
252042
- result = import_polygon_clipping2.default.union(operands[0], ...operands.slice(1));
252095
+ result = import_polygon_clipping3.default.union(operands[0], ...operands.slice(1));
252043
252096
  } catch {
252044
252097
  result = operands[0];
252045
252098
  }
252046
252099
  }
252047
252100
  return result;
252048
252101
  }
252049
- var import_polygon_clipping2;
252102
+ var import_polygon_clipping3;
252050
252103
  var init_vector_geometry_cache = __esm({
252051
252104
  "lib/engine/vector-geometry-cache.ts"() {
252052
252105
  "use strict";
252053
- import_polygon_clipping2 = __toESM(require_polygon_clipping_cjs());
252106
+ import_polygon_clipping3 = __toESM(require_polygon_clipping_cjs());
252054
252107
  init_flatten();
252055
252108
  init_triangulate();
252056
252109
  init_shape_outline();
@@ -252069,11 +252122,11 @@ function arrangementFaces(shapes) {
252069
252122
  const next2 = [];
252070
252123
  let rest = shape2;
252071
252124
  for (const f2 of faces2) {
252072
- const inside2 = op2(import_polygon_clipping3.default.intersection, f2.mp, shape2);
252073
- if (solid(inside2)) next2.push({ mp: inside2, sources: [...f2.sources, i2] });
252074
- const outside = op2(import_polygon_clipping3.default.difference, f2.mp, shape2);
252125
+ const inside3 = op2(import_polygon_clipping4.default.intersection, f2.mp, shape2);
252126
+ if (solid(inside3)) next2.push({ mp: inside3, sources: [...f2.sources, i2] });
252127
+ const outside = op2(import_polygon_clipping4.default.difference, f2.mp, shape2);
252075
252128
  if (solid(outside)) next2.push({ mp: outside, sources: f2.sources });
252076
- if (solid(inside2)) rest = op2(import_polygon_clipping3.default.difference, rest, f2.mp);
252129
+ if (solid(inside3)) rest = op2(import_polygon_clipping4.default.difference, rest, f2.mp);
252077
252130
  }
252078
252131
  if (solid(rest)) next2.push({ mp: rest, sources: [i2] });
252079
252132
  faces2 = next2;
@@ -252082,15 +252135,15 @@ function arrangementFaces(shapes) {
252082
252135
  }
252083
252136
  function pointInMultiPoly(mp, x2, y) {
252084
252137
  for (const poly of mp) {
252085
- let inside2 = false;
252138
+ let inside3 = false;
252086
252139
  for (const ring of poly) {
252087
252140
  for (let i2 = 0, j = ring.length - 1; i2 < ring.length; j = i2++) {
252088
252141
  const [xi, yi] = ring[i2];
252089
252142
  const [xj, yj] = ring[j];
252090
- if (yi > y !== yj > y && x2 < (xj - xi) * (y - yi) / (yj - yi) + xi) inside2 = !inside2;
252143
+ if (yi > y !== yj > y && x2 < (xj - xi) * (y - yi) / (yj - yi) + xi) inside3 = !inside3;
252091
252144
  }
252092
252145
  }
252093
- if (inside2) return true;
252146
+ if (inside3) return true;
252094
252147
  }
252095
252148
  return false;
252096
252149
  }
@@ -252102,16 +252155,16 @@ function unionFaces(faces2, picked) {
252102
252155
  const parts = picked.map((i2) => faces2[i2]?.mp).filter(solid);
252103
252156
  if (parts.length === 0) return [];
252104
252157
  try {
252105
- return parts.length > 1 ? import_polygon_clipping3.default.union(parts[0], ...parts.slice(1)) : parts[0];
252158
+ return parts.length > 1 ? import_polygon_clipping4.default.union(parts[0], ...parts.slice(1)) : parts[0];
252106
252159
  } catch {
252107
252160
  return parts[0];
252108
252161
  }
252109
252162
  }
252110
- var import_polygon_clipping3, MAX_SHAPE_BUILDER_SHAPES, area2, MIN_FACE_AREA, solid, op2;
252163
+ var import_polygon_clipping4, MAX_SHAPE_BUILDER_SHAPES, area2, MIN_FACE_AREA, solid, op2;
252111
252164
  var init_regions = __esm({
252112
252165
  "lib/engine/geometry/regions.ts"() {
252113
252166
  "use strict";
252114
- import_polygon_clipping3 = __toESM(require_polygon_clipping_cjs());
252167
+ import_polygon_clipping4 = __toESM(require_polygon_clipping_cjs());
252115
252168
  MAX_SHAPE_BUILDER_SHAPES = 12;
252116
252169
  area2 = (mp) => {
252117
252170
  let a = 0;
@@ -252213,7 +252266,7 @@ function bufferPolylines(lines2, width) {
252213
252266
  }
252214
252267
  if (pieces.length === 0) return [];
252215
252268
  try {
252216
- return pieces.length > 1 ? import_polygon_clipping4.default.union(pieces[0], ...pieces.slice(1)) : [pieces[0]];
252269
+ return pieces.length > 1 ? import_polygon_clipping5.default.union(pieces[0], ...pieces.slice(1)) : [pieces[0]];
252217
252270
  } catch {
252218
252271
  return pieces.map((p2) => [p2[0]]);
252219
252272
  }
@@ -252222,7 +252275,7 @@ function combineRings(rings, mode2) {
252222
252275
  const polys = rings.filter((r) => r.length >= 3).map((r) => [r]);
252223
252276
  if (polys.length === 0) return [];
252224
252277
  try {
252225
- return polys.length > 1 ? import_polygon_clipping4.default[mode2](polys[0], ...polys.slice(1)) : [polys[0]];
252278
+ return polys.length > 1 ? import_polygon_clipping5.default[mode2](polys[0], ...polys.slice(1)) : [polys[0]];
252226
252279
  } catch {
252227
252280
  return polys.map((p2) => [p2[0]]);
252228
252281
  }
@@ -252236,8 +252289,15 @@ function nodeWorldMultiPoly(doc2, id3) {
252236
252289
  return mp.map((poly) => poly.map((ring) => ring.map(([x2, y]) => place4(x2, y))));
252237
252290
  }
252238
252291
  const rings = nodeWorldRings(doc2, id3);
252239
- const mode2 = node2.type === "VECTOR" && node2.fillRule === "EVENODD" ? "xor" : "union";
252240
- return combineRings(rings, mode2);
252292
+ if (node2.type === "VECTOR") {
252293
+ if (node2.fillRule === "EVENODD") return combineRings(rings, "xor");
252294
+ try {
252295
+ return nonzeroMultiPoly(rings);
252296
+ } catch {
252297
+ return combineRings(rings, "union");
252298
+ }
252299
+ }
252300
+ return combineRings(rings, "union");
252241
252301
  }
252242
252302
  function nodeAreaMultiPoly(doc2, id3) {
252243
252303
  if (!doc2.nodes[id3]) return [];
@@ -252247,13 +252307,23 @@ function nodeAreaMultiPoly(doc2, id3) {
252247
252307
  }
252248
252308
  function flattenNodesMultiPoly(doc2, ids2) {
252249
252309
  const operands = [];
252250
- for (const id3 of ids2) {
252251
- const mp = nodeAreaMultiPoly(doc2, id3);
252252
- if (mp.length > 0) operands.push(mp);
252253
- }
252310
+ const visit2 = (id3) => {
252311
+ const n = doc2.nodes[id3];
252312
+ if (!n || n.visible === false) return;
252313
+ if (n.type !== "GROUP") {
252314
+ const mp = n.type === "FRAME" || n.type === "COMPONENT" || n.type === "INSTANCE" || n.type === "SECTION" ? (n.fills ?? []).some((f2) => f2.visible !== false) ? nodeWorldMultiPoly(doc2, id3) : [] : nodeAreaMultiPoly(doc2, id3);
252315
+ if (mp.length > 0) operands.push(mp);
252316
+ }
252317
+ if (n.type !== "BOOLEAN") for (const c2 of n.childIds ?? []) visit2(c2);
252318
+ };
252319
+ ids2.forEach(visit2);
252320
+ return unionAll(operands);
252321
+ }
252322
+ function unionAll(mps) {
252323
+ const operands = mps.filter((mp) => mp.length > 0);
252254
252324
  if (operands.length === 0) return [];
252255
252325
  try {
252256
- return operands.length > 1 ? import_polygon_clipping4.default.union(operands[0], ...operands.slice(1)) : operands[0];
252326
+ return operands.length > 1 ? import_polygon_clipping5.default.union(operands[0], ...operands.slice(1)) : operands[0];
252257
252327
  } catch {
252258
252328
  return operands[0];
252259
252329
  }
@@ -252279,7 +252349,7 @@ function outlineStrokeMultiPoly(doc2, id3) {
252279
252349
  const buffer2 = bufferPolylines(lines2, width * 2);
252280
252350
  if (fill.length === 0 || buffer2.length === 0) return bufferPolylines(lines2, width);
252281
252351
  try {
252282
- return alignment === "INSIDE" ? import_polygon_clipping4.default.intersection(buffer2, fill) : import_polygon_clipping4.default.difference(buffer2, fill);
252352
+ return alignment === "INSIDE" ? import_polygon_clipping5.default.intersection(buffer2, fill) : import_polygon_clipping5.default.difference(buffer2, fill);
252283
252353
  } catch {
252284
252354
  return bufferPolylines(lines2, width);
252285
252355
  }
@@ -252311,16 +252381,17 @@ function shapeBuilderFaces(doc2, ids2) {
252311
252381
  facesCache = { nodes: doc2.nodes, key: key5, faces: faces2 };
252312
252382
  return faces2;
252313
252383
  }
252314
- var import_polygon_clipping4, JOIN_CIRCLE_SEGMENTS, facesCache;
252384
+ var import_polygon_clipping5, JOIN_CIRCLE_SEGMENTS, facesCache;
252315
252385
  var init_vector_ops = __esm({
252316
252386
  "lib/engine/geometry/vector-ops.ts"() {
252317
252387
  "use strict";
252318
- import_polygon_clipping4 = __toESM(require_polygon_clipping_cjs());
252388
+ import_polygon_clipping5 = __toESM(require_polygon_clipping_cjs());
252319
252389
  init_scene_graph();
252320
252390
  init_transform();
252321
252391
  init_shape_outline();
252322
252392
  init_flatten();
252323
252393
  init_vector_geometry_cache();
252394
+ init_winding();
252324
252395
  init_regions();
252325
252396
  JOIN_CIRCLE_SEGMENTS = 16;
252326
252397
  facesCache = null;
@@ -252374,15 +252445,15 @@ var init_brush_along_path = __esm({
252374
252445
 
252375
252446
  // lib/engine/geometry/polygon.ts
252376
252447
  function pointInPolygon(p2, poly) {
252377
- let inside2 = false;
252448
+ let inside3 = false;
252378
252449
  for (let i2 = 0, j = poly.length - 1; i2 < poly.length; j = i2++) {
252379
252450
  const a = poly[i2], b = poly[j];
252380
252451
  if (a.y > p2.y !== b.y > p2.y) {
252381
252452
  const xCross = a.x + (p2.y - a.y) * (b.x - a.x) / (b.y - a.y);
252382
- if (p2.x < xCross) inside2 = !inside2;
252453
+ if (p2.x < xCross) inside3 = !inside3;
252383
252454
  }
252384
252455
  }
252385
- return inside2;
252456
+ return inside3;
252386
252457
  }
252387
252458
  var init_polygon = __esm({
252388
252459
  "lib/engine/geometry/polygon.ts"() {
@@ -252422,8 +252493,8 @@ function enclosedRegion(shapes, pt, view2) {
252422
252493
  }
252423
252494
  try {
252424
252495
  let region = rectMP(view2);
252425
- for (const c2 of containing) region = import_polygon_clipping5.default.intersection(region, c2);
252426
- if (subtract.length) region = import_polygon_clipping5.default.difference(region, ...subtract);
252496
+ for (const c2 of containing) region = import_polygon_clipping6.default.intersection(region, c2);
252497
+ if (subtract.length) region = import_polygon_clipping6.default.difference(region, ...subtract);
252427
252498
  const hit = region.find((p2) => pointInPoly(pt, p2));
252428
252499
  if (!hit) return { ok: false, reason: "EMPTY" };
252429
252500
  if (!containing.length && touchesRect(hit, view2)) return { ok: false, reason: "OPEN" };
@@ -252474,11 +252545,11 @@ function findEnclosedRegion(doc2, pageId2, pt, view2) {
252474
252545
  walk4(pageId2);
252475
252546
  return enclosedRegion(shapes, pt, view2);
252476
252547
  }
252477
- var import_polygon_clipping5, ringPts, pointInMP, rectMP;
252548
+ var import_polygon_clipping6, ringPts, pointInMP, rectMP;
252478
252549
  var init_bucket = __esm({
252479
252550
  "lib/engine/geometry/bucket.ts"() {
252480
252551
  "use strict";
252481
- import_polygon_clipping5 = __toESM(require_polygon_clipping_cjs());
252552
+ import_polygon_clipping6 = __toESM(require_polygon_clipping_cjs());
252482
252553
  init_scene_graph();
252483
252554
  init_vector_ops();
252484
252555
  init_polygon();
@@ -257639,10 +257710,10 @@ function collapse(doc2) {
257639
257710
  else groups.set(h, [id3]);
257640
257711
  }
257641
257712
  const chosen = [];
257642
- const inside2 = /* @__PURE__ */ new Set();
257713
+ const inside3 = /* @__PURE__ */ new Set();
257643
257714
  const sorted = [...groups.values()].filter((g) => g.length >= MIN_COPIES).sort((a, b) => (size6.get(b[0]) ?? 0) - (size6.get(a[0]) ?? 0));
257644
257715
  const within3 = (id3) => {
257645
- for (let p2 = nodes[id3]?.parentId, guard2 = 0; p2 && guard2 < 200; p2 = nodes[p2]?.parentId, guard2++) if (inside2.has(p2)) return true;
257716
+ for (let p2 = nodes[id3]?.parentId, guard2 = 0; p2 && guard2 < 200; p2 = nodes[p2]?.parentId, guard2++) if (inside3.has(p2)) return true;
257646
257717
  return false;
257647
257718
  };
257648
257719
  for (const g of sorted) {
@@ -257650,7 +257721,7 @@ function collapse(doc2) {
257650
257721
  const copies = g.filter((id3) => !within3(id3) && (id3 === rep || sameContents(nodes, rep, id3)));
257651
257722
  if (copies.length < MIN_COPIES || !copies.includes(rep)) continue;
257652
257723
  chosen.push(copies);
257653
- for (const id3 of copies) inside2.add(id3);
257724
+ for (const id3 of copies) inside3.add(id3);
257654
257725
  }
257655
257726
  if (!chosen.length) return doc2;
257656
257727
  const out = { ...nodes };
@@ -257995,7 +258066,6 @@ function onTabMessage(fn) {
257995
258066
  var EDITOR_HOME, localDocPath, cloudFilePath, CHANNEL2, channel2;
257996
258067
  var init_tab_mode = __esm({
257997
258068
  "lib/desktop/tab-mode.ts"() {
257998
- "use strict";
257999
258069
  EDITOR_HOME = "/home";
258000
258070
  localDocPath = (docId) => `${EDITOR_HOME}?doc=${encodeURIComponent(docId)}`;
258001
258071
  cloudFilePath = (key5) => `/pop/${encodeURIComponent(key5)}`;
@@ -258012,6 +258082,64 @@ function scaleVectorPaths(paths, sx, sy, rebuild) {
258012
258082
  return { ...path, points, data: rebuild(points, path.closed) };
258013
258083
  });
258014
258084
  }
258085
+ function scaledChildProps(orig, sx, sy, rebuild) {
258086
+ return {
258087
+ x: orig.x * sx,
258088
+ y: orig.y * sy,
258089
+ width: orig.width * sx,
258090
+ height: orig.height * sy,
258091
+ ...orig.type === "VECTOR" ? { paths: scaleVectorPaths(orig.paths, sx, sy, rebuild) } : {}
258092
+ };
258093
+ }
258094
+ function rebuildVectorData(points, closed) {
258095
+ if (points.length < 2) return [];
258096
+ const data = [];
258097
+ data.push("M", points[0].x, points[0].y);
258098
+ for (let i2 = 1; i2 < points.length; i2++) {
258099
+ const prev = points[i2 - 1];
258100
+ const curr = points[i2];
258101
+ if (prev.handleOut && curr.handleIn) {
258102
+ data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
258103
+ } else if (prev.handleOut) {
258104
+ data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
258105
+ } else if (curr.handleIn) {
258106
+ data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
258107
+ } else {
258108
+ data.push("L", curr.x, curr.y);
258109
+ }
258110
+ }
258111
+ if (closed) {
258112
+ const prev = points[points.length - 1];
258113
+ const curr = points[0];
258114
+ if (prev.handleOut && curr.handleIn) {
258115
+ data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
258116
+ } else if (prev.handleOut) {
258117
+ data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
258118
+ } else if (curr.handleIn) {
258119
+ data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
258120
+ }
258121
+ data.push("Z");
258122
+ }
258123
+ return data;
258124
+ }
258125
+ function scaleSubtreePatches(nodes, id3, sx, sy, includeSelf = true) {
258126
+ const out = /* @__PURE__ */ new Map();
258127
+ const root2 = nodes[id3];
258128
+ if (!root2 || sx === 1 && sy === 1) return out;
258129
+ if (includeSelf && root2.type === "VECTOR") out.set(id3, { paths: scaleVectorPaths(root2.paths, sx, sy, rebuildVectorData) });
258130
+ const walk4 = (parent) => {
258131
+ for (const cid of parent.childIds ?? []) {
258132
+ const c2 = nodes[cid];
258133
+ if (!c2) continue;
258134
+ const patch2 = { ...scaledChildProps(c2, sx, sy, rebuildVectorData) };
258135
+ if (c2.type === "TEXT") patch2.fontSize = (c2.fontSize ?? 16) * Math.min(sx, sy);
258136
+ out.set(cid, patch2);
258137
+ walk4(c2);
258138
+ }
258139
+ };
258140
+ if (root2.type === "GROUP" || root2.type === "BOOLEAN" || !includeSelf) walk4(root2);
258141
+ return out;
258142
+ }
258015
258143
  var scalePoint;
258016
258144
  var init_scale_geometry = __esm({
258017
258145
  "lib/document/scale-geometry.ts"() {
@@ -262651,7 +262779,6 @@ function formatLimit(key5, max3) {
262651
262779
  var formatBytes;
262652
262780
  var init_limits2 = __esm({
262653
262781
  "lib/billing/limits.ts"() {
262654
- "use strict";
262655
262782
  init_format();
262656
262783
  init_plans();
262657
262784
  formatBytes = formatFileSize;
@@ -310254,6 +310381,7 @@ function importSVGFile(file2, document2, parentId) {
310254
310381
  var ENTITIES, decode3, I, mul2, apply2, scaleOf, NUM2, nums, near, NAMED, INHERITED, PRESENTATION, len2, CAP, JOIN, SKIPPED, refId;
310255
310382
  var init_svg_import = __esm({
310256
310383
  "lib/io/svg-import.ts"() {
310384
+ "use strict";
310257
310385
  init_dist_node();
310258
310386
  init_types();
310259
310387
  ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
@@ -311857,7 +311985,6 @@ async function dispatchShaped(key5, options) {
311857
311985
  var onViolation;
311858
311986
  var init_dispatch = __esm({
311859
311987
  "lib/commands/dispatch.ts"() {
311860
- "use strict";
311861
311988
  init_errors();
311862
311989
  init_registry();
311863
311990
  init_confirm();
@@ -312919,7 +313046,6 @@ function applyTextPath(layout2, path, tp, rebase = true) {
312919
313046
  var defaultCircleRadius, ownsPath, mapPoint;
312920
313047
  var init_text_path = __esm({
312921
313048
  "lib/engine/text/text-path.ts"() {
312922
- "use strict";
312923
313049
  init_scene_graph();
312924
313050
  init_transform();
312925
313051
  init_shape_to_path();
@@ -315574,12 +315700,12 @@ function textStrokeSVG(node2) {
315574
315700
  const w = Array.isArray(stroke3.width) ? Math.max(...stroke3.width) : stroke3.width;
315575
315701
  if (!(w > 0)) return null;
315576
315702
  const behind = strokeBehindFill(node2) || stroke3.alignment === "OUTSIDE";
315577
- const inside2 = stroke3.alignment === "INSIDE";
315578
- if (inside2 && behind) return null;
315703
+ const inside3 = stroke3.alignment === "INSIDE";
315704
+ if (inside3 && behind) return null;
315579
315705
  const alpha2 = (stroke3.color.a ?? 1) * (stroke3.opacity ?? 1);
315580
315706
  const join13 = stroke3.join === "ROUND" ? "round" : stroke3.join === "BEVEL" ? "bevel" : "miter";
315581
315707
  const attrs = `stroke="${colorToSVG(stroke3.color)}"${alpha2 < 1 ? ` stroke-opacity="${fmt2(alpha2)}"` : ""} stroke-width="${fmt2(stroke3.alignment === "CENTER" ? w : w * 2)}" stroke-linejoin="${join13}"${behind ? ' paint-order="stroke fill"' : ""}`;
315582
- return { attrs, behind, inside: inside2 };
315708
+ return { attrs, behind, inside: inside3 };
315583
315709
  }
315584
315710
  function textFillAttr(resolved, node2, ctx2) {
315585
315711
  const fill0 = resolved.fills?.[0];
@@ -316503,12 +316629,12 @@ Q
316503
316629
  };
316504
316630
  const paintStrokes = () => {
316505
316631
  for (const s of item.strokes) {
316506
- const inside2 = s.alignment === "INSIDE";
316507
- const wdt = inside2 ? s.width * 2 : s.width;
316632
+ const inside3 = s.alignment === "INSIDE";
316633
+ const wdt = inside3 ? s.width * 2 : s.width;
316508
316634
  this.w(`q /${this.gs(1, opacity * s.alpha, blend2)} gs ${this.res.color.stroke(s.color)} ${num7(wdt)} w ${CAP2[s.cap]} J ${JOIN2[s.join]} j`);
316509
316635
  this.w(s.dash ? ` [${num7(s.dash[0])} ${num7(s.dash[1])}] 0 d
316510
316636
  ` : " [] 0 d\n");
316511
- if (inside2) this.w(`${ops2}${clipOp} n
316637
+ if (inside3) this.w(`${ops2}${clipOp} n
316512
316638
  `);
316513
316639
  this.w(`${ops2}S
316514
316640
  Q
@@ -316681,8 +316807,8 @@ function removeOverlaps(cmds) {
316681
316807
  break;
316682
316808
  }
316683
316809
  if (!touching) return cmds;
316684
- const pos = rings.filter((r) => signedArea(r) > 0).map((r) => [r]), neg = rings.filter((r) => signedArea(r) < 0).map((r) => [r]);
316685
- const clip = import_polygon_clipping6.default;
316810
+ const pos = rings.filter((r) => signedArea2(r) > 0).map((r) => [r]), neg = rings.filter((r) => signedArea2(r) < 0).map((r) => [r]);
316811
+ const clip = import_polygon_clipping7.default;
316686
316812
  let result;
316687
316813
  try {
316688
316814
  const a = pos.length ? clip.union(...pos) : [], b = neg.length ? clip.union(...neg) : [];
@@ -316795,12 +316921,12 @@ function buildCFF(name2, unitsPerEm, glyphs) {
316795
316921
  }
316796
316922
  return out;
316797
316923
  }
316798
- var import_polygon_clipping6, signedArea, bbox, intersects;
316924
+ var import_polygon_clipping7, signedArea2, bbox, intersects;
316799
316925
  var init_cff = __esm({
316800
316926
  "lib/io/pdf/cff.ts"() {
316801
316927
  "use strict";
316802
- import_polygon_clipping6 = __toESM(require_polygon_clipping_cjs());
316803
- signedArea = (p2) => p2.reduce((a, [x2, y], i2) => {
316928
+ import_polygon_clipping7 = __toESM(require_polygon_clipping_cjs());
316929
+ signedArea2 = (p2) => p2.reduce((a, [x2, y], i2) => {
316804
316930
  const [nx, ny] = p2[(i2 + 1) % p2.length];
316805
316931
  return a + x2 * ny - nx * y;
316806
316932
  }, 0) / 2;
@@ -317473,7 +317599,6 @@ async function selectionReferenceImages(doc2, selection, opts = {}) {
317473
317599
  var FIT_QUALITIES, FIT_SCALES, blobToDataUrl;
317474
317600
  var init_reference_images = __esm({
317475
317601
  "lib/ai/reference-images.ts"() {
317476
- "use strict";
317477
317602
  init_export();
317478
317603
  init_selection_bounds();
317479
317604
  init_renderer_registry();
@@ -318354,6 +318479,7 @@ __export(tools_exports, {
318354
318479
  allTools: () => allTools,
318355
318480
  defineTool: () => defineTool,
318356
318481
  getTool: () => getTool,
318482
+ parseToolArgs: () => parseToolArgs,
318357
318483
  registerTools: () => registerTools,
318358
318484
  runTool: () => runTool,
318359
318485
  toolSpecs: () => toolSpecs
@@ -318374,14 +318500,53 @@ function toolSpecs(names) {
318374
318500
  function: { name: t3.name, description: t3.description, parameters: toolParameters(t3.parameters) }
318375
318501
  }));
318376
318502
  }
318503
+ function parseToolArgs(text) {
318504
+ try {
318505
+ return JSON.parse(text);
318506
+ } catch {
318507
+ }
318508
+ let depth = 0, inStr = false, esc3 = false;
318509
+ const stack = [];
318510
+ for (let i2 = 0; i2 < text.length; i2++) {
318511
+ const c2 = text[i2];
318512
+ if (inStr) {
318513
+ if (esc3) esc3 = false;
318514
+ else if (c2 === "\\") esc3 = true;
318515
+ else if (c2 === '"') inStr = false;
318516
+ continue;
318517
+ }
318518
+ if (c2 === '"') inStr = true;
318519
+ else if (c2 === "{" || c2 === "[") {
318520
+ stack.push(c2 === "{" ? "}" : "]");
318521
+ depth++;
318522
+ } else if (c2 === "}" || c2 === "]") {
318523
+ stack.pop();
318524
+ depth--;
318525
+ if (depth === 0) {
318526
+ try {
318527
+ return JSON.parse(text.slice(0, i2 + 1));
318528
+ } catch {
318529
+ return void 0;
318530
+ }
318531
+ }
318532
+ }
318533
+ }
318534
+ if (!stack.length) return void 0;
318535
+ try {
318536
+ return JSON.parse(text + (inStr ? '"' : "") + stack.reverse().join(""));
318537
+ } catch {
318538
+ return void 0;
318539
+ }
318540
+ }
318377
318541
  async function runTool(name2, argsText, ctx2) {
318378
318542
  let raw = {};
318379
318543
  if (argsText.trim()) {
318380
- try {
318381
- raw = JSON.parse(argsText);
318382
- } catch {
318383
- throw new ToolError(`The arguments for ${name2} were not valid JSON.`, "invalid_params");
318544
+ const parsed2 = parseToolArgs(argsText);
318545
+ if (parsed2 === void 0) {
318546
+ console.warn(`[agent] unparseable arguments for ${name2}:`, argsText.slice(0, 500));
318547
+ throw new ToolError(`The arguments for ${name2} were not valid JSON. Send one JSON object.`, "invalid_params");
318384
318548
  }
318549
+ raw = parsed2;
318385
318550
  }
318386
318551
  return dispatch2(name2, {
318387
318552
  surface: "agent",
@@ -319099,16 +319264,28 @@ function placeSvgLayers(doc2, parentId, svg, place4) {
319099
319264
  if (n.type === "IMAGE" && !doc2.nodes[n.id] && url4?.startsWith("data:")) nodes[n.id] = { ...n, imageUrl: place4.intern(url4) };
319100
319265
  }
319101
319266
  }
319102
- const minX = Math.min(...kids.map((k2) => k2.x)), minY = Math.min(...kids.map((k2) => k2.y));
319103
- const maxX = Math.max(...kids.map((k2) => k2.x + k2.width)), maxY = Math.max(...kids.map((k2) => k2.y + k2.height));
319104
- for (const k2 of kids) nodes[k2.id] = { ...nodes[k2.id], x: k2.x - minX, y: k2.y - minY };
319105
- const w = Math.max(1, maxX - minX), h = Math.max(1, maxY - minY);
319106
- nodes[frame3.id] = { ...nodes[frame3.id], x: Math.round(place4.cx - w / 2), y: Math.round(place4.cy - h / 2), width: w, height: h };
319267
+ const minX = Math.min(...kids.map((k3) => k3.x)), minY = Math.min(...kids.map((k3) => k3.y));
319268
+ const maxX = Math.max(...kids.map((k3) => k3.x + k3.width)), maxY = Math.max(...kids.map((k3) => k3.y + k3.height));
319269
+ for (const k3 of kids) nodes[k3.id] = { ...nodes[k3.id], x: k3.x - minX, y: k3.y - minY };
319270
+ let w = Math.max(1, maxX - minX), h = Math.max(1, maxY - minY);
319271
+ const f2 = place4.fit;
319272
+ const k2 = f2 && (f2.width || f2.height) ? Math.min(f2.width ? f2.width / w : Infinity, f2.height ? f2.height / h : Infinity) : 1;
319273
+ if (k2 !== 1 && Number.isFinite(k2) && k2 > 0) {
319274
+ const wrapper = { childIds: kids.map((c2) => c2.id) };
319275
+ for (const [id3, patch2] of scaleSubtreePatches({ ...nodes, [frame3.id]: { ...nodes[frame3.id], ...wrapper } }, frame3.id, k2, k2, false)) {
319276
+ nodes[id3] = { ...nodes[id3], ...patch2 };
319277
+ }
319278
+ w *= k2;
319279
+ h *= k2;
319280
+ }
319281
+ const topLeft = place4.anchor === "top-left";
319282
+ nodes[frame3.id] = { ...nodes[frame3.id], x: Math.round(topLeft ? place4.cx : place4.cx - w / 2), y: Math.round(topLeft ? place4.cy : place4.cy - h / 2), width: w, height: h };
319107
319283
  return { doc: { ...next2, nodes }, id: frame3.id };
319108
319284
  }
319109
319285
  var MAX_SVG_LAYER_BYTES;
319110
319286
  var init_svg_place = __esm({
319111
319287
  "lib/io/svg-place.ts"() {
319288
+ init_scale_geometry();
319112
319289
  init_scene_graph();
319113
319290
  init_svg_import();
319114
319291
  MAX_SVG_LAYER_BYTES = 8 * 1024 * 1024;
@@ -319128,6 +319305,31 @@ function descendants(doc2, rootId, skipRoot = true) {
319128
319305
  }
319129
319306
  return out;
319130
319307
  }
319308
+ function normalizeColor(c2, key5) {
319309
+ if (typeof c2 === "string") return parseHex(c2);
319310
+ const o = c2;
319311
+ const ch = o && ["r", "g", "b"].map((k2) => o[k2]);
319312
+ if (!ch || !ch.every((v) => typeof v === "number" && Number.isFinite(v))) {
319313
+ throw new ToolError(`${key5} has a colour without numeric r, g and b. Give 0\u20131 channels, or use set_fills with a hex colour.`);
319314
+ }
319315
+ const nums2 = ch;
319316
+ const scale = nums2.some((v) => v > 1) ? 255 : 1;
319317
+ const a = typeof o.a === "number" && Number.isFinite(o.a) ? o.a > 1 ? o.a / 255 : o.a : 1;
319318
+ return createColor(nums2[0] / scale, nums2[1] / scale, nums2[2] / scale, a);
319319
+ }
319320
+ function normalizePaints(v, key5) {
319321
+ if (!Array.isArray(v)) throw new ToolError(`${key5} must be an array of paints.`);
319322
+ return v.map((p2) => {
319323
+ const paint = p2;
319324
+ if (!paint || typeof paint !== "object") throw new ToolError(`${key5} must be an array of paint objects.`);
319325
+ const out = { ...paint };
319326
+ if ("color" in paint) out.color = normalizeColor(paint.color, key5);
319327
+ if (Array.isArray(paint.gradientStops)) {
319328
+ out.gradientStops = paint.gradientStops.map((s) => ({ ...s, color: normalizeColor(s.color, key5) }));
319329
+ }
319330
+ return out;
319331
+ });
319332
+ }
319131
319333
  var nodeFind, nodeGet, nodeMeasure, docOutline, CREATABLE, nodeCreate, FORBIDDEN_PROPS, nodeUpdate, nodeDelete, nodeMove, nodeResize, nodeReparent, nodeGroup, nodeUngroup, nodeOrder, nodeAlign, nodeDistribute, nodeRename, nodeDuplicate, nodeImportSvg, nodeApplyAutoLayout, nodeSetLock, nodeSetVisibility, nodeFlip, nodeRotate;
319132
319334
  var init_node = __esm({
319133
319335
  "lib/commands/node.ts"() {
@@ -319140,6 +319342,7 @@ var init_node = __esm({
319140
319342
  init_scene_graph();
319141
319343
  init_types();
319142
319344
  init_errors();
319345
+ init_scale_geometry();
319143
319346
  init_plugin_shim();
319144
319347
  init_types5();
319145
319348
  init_ui();
@@ -319474,7 +319677,7 @@ var init_node = __esm({
319474
319677
  for (const [k2, v] of Object.entries(u.props)) {
319475
319678
  if (args2.strict && FORBIDDEN_PROPS.has(k2)) throw new ToolError(`update_nodes cannot change "${k2}". Use reparent, delete_nodes or create_node instead.`);
319476
319679
  if (FORBIDDEN_PROPS.has(k2) || v === void 0) continue;
319477
- patch2[k2] = v;
319680
+ patch2[k2] = k2 === "fills" || k2 === "strokes" ? normalizePaints(v, k2) : v;
319478
319681
  changedKeys.add(k2);
319479
319682
  }
319480
319683
  if (args2.strict && !Object.keys(patch2).length) throw new ToolError(`No properties given for "${u.id}".`);
@@ -319589,9 +319792,21 @@ var init_node = __esm({
319589
319792
  const n = doc2.nodes[id3];
319590
319793
  const w = args2.width ?? (args2.scale ? n.width * args2.scale : n.width);
319591
319794
  const h = args2.height ?? (args2.scale ? n.height * args2.scale : n.height);
319592
- patches.set(id3, { width: Math.max(AI_LIMITS.minSize, w), height: Math.max(AI_LIMITS.minSize, h) });
319795
+ const width = Math.max(AI_LIMITS.minSize, w), height = Math.max(AI_LIMITS.minSize, h);
319796
+ patches.set(id3, { width, height });
319797
+ if (n.width <= 0 || n.height <= 0) continue;
319798
+ for (const [pid, patch2] of scaleSubtreePatches(doc2.nodes, id3, width / n.width, height / n.height)) {
319799
+ patches.set(pid, { ...patches.get(pid), ...patch2 });
319800
+ }
319593
319801
  }
319594
- commitUpdates(ctx2, doc2, patches);
319802
+ let next2 = doc2;
319803
+ for (const id3 of ids2) {
319804
+ const n = doc2.nodes[id3], p2 = patches.get(id3);
319805
+ if (n.type === "FRAME" || n.type === "COMPONENT" || n.type === "SECTION") {
319806
+ next2 = applyConstraints(next2, id3, n.width, n.height, p2.width, p2.height);
319807
+ }
319808
+ }
319809
+ commitUpdates(ctx2, next2, patches);
319595
319810
  return { summary: `Resized ${plural2(ids2.length, "layer")}`, data: { ids: ids2 } };
319596
319811
  }
319597
319812
  });
@@ -319928,6 +320143,9 @@ var init_node = __esm({
319928
320143
  svg: external_exports.string().min(8).optional().describe("The full <svg>\u2026</svg> markup."),
319929
320144
  parentId: external_exports.string().optional(),
319930
320145
  at: external_exports.object({ x: external_exports.number(), y: external_exports.number() }).optional().describe("Place the layers in one frame centred on this point (parent coordinates) instead of at the SVG's own coordinates."),
320146
+ anchor: external_exports.enum(["center", "top-left"]).optional().describe("Whether `at` is the frame's centre (default) or its top-left corner."),
320147
+ width: external_exports.number().positive().optional().describe("With `at`: scale the artwork to fit this width (keeps proportions)."),
320148
+ height: external_exports.number().positive().optional().describe("With `at`: scale the artwork to fit this height (keeps proportions)."),
319931
320149
  name: external_exports.string().max(200).optional().describe("Name of that frame (with `at`)."),
319932
320150
  more: external_exports.array(external_exports.object({ svg: external_exports.string().min(8), name: external_exports.string().max(200).optional() })).max(50).optional().describe("Further SVGs placed in the same step (with `at`), each 24 px further down and right.")
319933
320151
  }),
@@ -319935,7 +320153,7 @@ var init_node = __esm({
319935
320153
  plugin: { method: "importSvg" },
319936
320154
  agent: {
319937
320155
  name: "import_svg",
319938
- description: "Import SVG markup as editable layers under a parent (default: the current page). Pass `at` to place them in one frame centred on a point. Returns the ids of the new top-level layers."
320156
+ description: 'Import SVG markup as editable layers under a parent (default: the current page). Pass `at` (in the PARENT\'s coordinates) to place them in one frame centred on that point, or with its top-left there when anchor is "top-left"; add width and/or height to scale the artwork to fit that box. Returns the ids of the new top-level layers.'
319939
320157
  },
319940
320158
  // Paste, drop and File › Import: one undo step, and the ones that would not convert reported back so the
319941
320159
  // caller can place them as pictures instead (lib/editor/svg-paste.ts).
@@ -319953,7 +320171,7 @@ var init_node = __esm({
319953
320171
  const added2 = [];
319954
320172
  const failed2 = [];
319955
320173
  items.forEach((item, i2) => {
319956
- const placed2 = placeSvgLayers2(next3, parentId, item.svg, { name: item.name || "SVG", cx: args2.at.x + i2 * 24, cy: args2.at.y + i2 * 24, intern: (url4) => internDataUrl2(url4) });
320174
+ const placed2 = placeSvgLayers2(next3, parentId, item.svg, { name: item.name || "SVG", cx: args2.at.x + i2 * 24, cy: args2.at.y + i2 * 24, anchor: args2.anchor, fit: { width: args2.width, height: args2.height }, intern: (url4) => internDataUrl2(url4) });
319957
320175
  if (placed2) {
319958
320176
  next3 = placed2.doc;
319959
320177
  added2.push(placed2.id);
@@ -320948,7 +321166,8 @@ var init_paint = __esm({
320948
321166
  const rec = n;
320949
321167
  const patch2 = {};
320950
321168
  let hit = 0;
320951
- const paints = rec.fills;
321169
+ const fillStyle = rec.fillStyleId ? doc2.styles?.[rec.fillStyleId] : void 0;
321170
+ const paints = fillStyle?.type === "PAINT" ? fillStyle.paints : rec.fills;
320952
321171
  if (paints?.length) {
320953
321172
  let changed = false;
320954
321173
  const next2 = paints.map((p2) => {
@@ -320975,7 +321194,10 @@ var init_paint = __esm({
320975
321194
  }
320976
321195
  return out;
320977
321196
  });
320978
- if (changed) patch2.fills = next2;
321197
+ if (changed) {
321198
+ patch2.fills = next2;
321199
+ if (rec.fillStyleId) patch2.fillStyleId = void 0;
321200
+ }
320979
321201
  }
320980
321202
  const strokes = rec.strokes;
320981
321203
  if (strokes?.length) {
@@ -320987,7 +321209,10 @@ var init_paint = __esm({
320987
321209
  hit++;
320988
321210
  return { ...s, color: c2 };
320989
321211
  });
320990
- if (changed) patch2.strokes = next2;
321212
+ if (changed) {
321213
+ patch2.strokes = next2;
321214
+ if (rec.strokeStyleId) patch2.strokeStyleId = void 0;
321215
+ }
320991
321216
  }
320992
321217
  const effects = effectsOf(n);
320993
321218
  if (effects.length) {
@@ -321018,7 +321243,7 @@ var init_paint = __esm({
321018
321243
  }
321019
321244
  }
321020
321245
  if (!swaps) throw new ToolError(`No ${args2.from} found under ${args2.rootId ?? "the selection"}. Check the color with get_node, or raise "tolerance".`);
321021
- ctx2.commit({ ...doc2, nodes });
321246
+ ctx2.commit(applyInvariants({ ...doc2, nodes }, touched));
321022
321247
  return { summary: `Replaced ${args2.from} with ${args2.to} in ${plural2(touched.length, "layer")} (${swaps} paints)`, data: { ids: touched.slice(0, 50), swaps } };
321023
321248
  }
321024
321249
  });
@@ -328471,7 +328696,7 @@ var init_package = __esm({
328471
328696
  "packages/runtime/package.json"() {
328472
328697
  package_default = {
328473
328698
  name: "@popcraft/runtime",
328474
- version: "0.53.1",
328699
+ version: "0.53.2",
328475
328700
  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.",
328476
328701
  license: "MIT",
328477
328702
  homepage: "https://popcraft.app/docs/exporting/nextjs",
@@ -353179,6 +353404,7 @@ function compileModule(module2) {
353179
353404
  var import_sucrase, BUILT_IN, memo4;
353180
353405
  var init_compile2 = __esm({
353181
353406
  "lib/code/compile.ts"() {
353407
+ "use strict";
353182
353408
  import_sucrase = __toESM(require_dist2());
353183
353409
  init_source2();
353184
353410
  init_source2();
@@ -355184,7 +355410,7 @@ function reading(doc2, ids2) {
355184
355410
  }
355185
355411
  function boardColumns(doc2, roots, looseTitle, within3) {
355186
355412
  const areas = within3?.map((n) => boxOf(doc2, n));
355187
- const wanted = (p2) => !areas || areas.some((a) => inside(p2, a));
355413
+ const wanted = (p2) => !areas || areas.some((a) => inside2(p2, a));
355188
355414
  const cards = [];
355189
355415
  const boxes = [];
355190
355416
  const texts = [];
@@ -355215,7 +355441,7 @@ function boardColumns(doc2, roots, looseTitle, within3) {
355215
355441
  const band = b.box.minY + (b.box.maxY - b.box.minY) * 0.25;
355216
355442
  const top = texts.filter((t3) => {
355217
355443
  const c2 = centre(t3.box);
355218
- return inside(c2, b.box) && c2.y <= band;
355444
+ return inside2(c2, b.box) && c2.y <= band;
355219
355445
  }).sort((x2, y) => x2.box.minY - y.box.minY)[0];
355220
355446
  if (top) titles.set(b.node.id, { text: top.text, id: top.id });
355221
355447
  else if (!DEFAULT_NAMES.test(b.node.name.trim())) titles.set(b.node.id, { text: b.node.name });
@@ -355223,7 +355449,7 @@ function boardColumns(doc2, roots, looseTitle, within3) {
355223
355449
  const columnAt = (p2) => {
355224
355450
  let best = null;
355225
355451
  for (const b of boxes) {
355226
- if (!inside(p2, b.box)) continue;
355452
+ if (!inside2(p2, b.box)) continue;
355227
355453
  if (!best) {
355228
355454
  best = b;
355229
355455
  continue;
@@ -355266,7 +355492,7 @@ function boardCsv(columns) {
355266
355492
  const rows = Math.max(0, ...columns.map((c2) => c2.items.length));
355267
355493
  return toCsv([columns.map((c2) => c2.title), ...Array.from({ length: rows }, (_, i2) => columns.map((c2) => c2.items[i2] ?? ""))]);
355268
355494
  }
355269
- var MARGIN, MAX_BOARD_EXPORT_PX, textOf, boxOf, centre, inside, area3, textLayer, COLUMN_TYPES;
355495
+ var MARGIN, MAX_BOARD_EXPORT_PX, textOf, boxOf, centre, inside2, area3, textLayer, COLUMN_TYPES;
355270
355496
  var init_board_export = __esm({
355271
355497
  "lib/popcorn/board-export.ts"() {
355272
355498
  init_scene_graph();
@@ -355276,7 +355502,7 @@ var init_board_export = __esm({
355276
355502
  textOf = (n) => (n.text ?? "").replace(/\s+/g, " ").trim();
355277
355503
  boxOf = (doc2, n) => uprightBounds(getWorldTransform(doc2, n.id), n.width, n.height);
355278
355504
  centre = (b) => ({ x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 });
355279
- inside = (p2, b) => p2.x >= b.minX && p2.x <= b.maxX && p2.y >= b.minY && p2.y <= b.maxY;
355505
+ inside2 = (p2, b) => p2.x >= b.minX && p2.x <= b.maxX && p2.y >= b.minY && p2.y <= b.maxY;
355280
355506
  area3 = (b) => (b.maxX - b.minX) * (b.maxY - b.minY);
355281
355507
  textLayer = (n) => n.type === "TEXT" ? (n.characters ?? "").replace(/\s+/g, " ").trim() : "";
355282
355508
  COLUMN_TYPES = /* @__PURE__ */ new Set(["FRAME", "SECTION", "GROUP", "RECTANGLE", "INSTANCE", "COMPONENT", "ELLIPSE"]);
@@ -358625,11 +358851,11 @@ function mapCompilerMessages(messages, offset, lines2) {
358625
358851
  for (const m of messages) {
358626
358852
  if (m.type !== "error" && m.type !== "warning") continue;
358627
358853
  const line = m.lineNum - offset;
358628
- const inside2 = m.lineNum > 0 && line >= 1 && line <= lines2;
358854
+ const inside3 = m.lineNum > 0 && line >= 1 && line <= lines2;
358629
358855
  out.push({
358630
- line: inside2 ? line : null,
358631
- column: inside2 && m.linePos > 0 ? m.linePos : null,
358632
- message: inside2 ? m.message : harnessMessage(m.message),
358856
+ line: inside3 ? line : null,
358857
+ column: inside3 && m.linePos > 0 ? m.linePos : null,
358858
+ message: inside3 ? m.message : harnessMessage(m.message),
358633
358859
  severity: m.type === "error" ? "error" : "warning"
358634
358860
  });
358635
358861
  }
@@ -359144,9 +359370,9 @@ var init_motion_rich = __esm({
359144
359370
  if (def.type !== "NUMBER" && def.type !== "COLOR" && def.type !== "IMAGE") throw new ToolError("Only a NUMBER, COLOR or IMAGE property feeds a layer input.");
359145
359371
  const layer2 = nodeOrThrow(doc2, op4.layer);
359146
359372
  const owner = target2.type === "COMPONENT_SET" ? target2.id : target2.id;
359147
- let inside2 = false;
359148
- for (let n = layer2; n; n = n.parentId ? doc2.nodes[n.parentId] : void 0) if (n.id === owner) inside2 = true;
359149
- if (!inside2 || layer2.id === owner) throw new ToolError(`"${layer2.name}" is not a layer inside "${target2.name}".`);
359373
+ let inside3 = false;
359374
+ for (let n = layer2; n; n = n.parentId ? doc2.nodes[n.parentId] : void 0) if (n.id === owner) inside3 = true;
359375
+ if (!inside3 || layer2.id === owner) throw new ToolError(`"${layer2.name}" is not a layer inside "${target2.name}".`);
359150
359376
  const props = layerInputPatch(layer2, op4.name, op4.path);
359151
359377
  if (!props) throw new ToolError(`"${layer2.name}" has no ${op4.path}.`);
359152
359378
  commitUpdates(ctx2, doc2, /* @__PURE__ */ new Map([[layer2.id, props]]));
@@ -361276,7 +361502,14 @@ var init_media = __esm({
361276
361502
  },
361277
361503
  run: async (args2) => {
361278
361504
  const { listLibraryMedia: listLibraryMedia2 } = await Promise.resolve().then(() => (init_client5(), client_exports3));
361279
- const page = await listLibraryMedia2(args2);
361505
+ const q = { ...args2 };
361506
+ if (!q.workspace) {
361507
+ const { syncStatus: syncStatus2 } = await Promise.resolve().then(() => (init_sync2(), sync_exports2));
361508
+ const link2 = syncStatus2().link;
361509
+ if (!link2) throw new ToolError("This document is not saved to the cloud, so it has no team media library. Pass workspace.");
361510
+ q.workspace = (await listLibraryMedia2({ file: link2.key, limit: 1 })).workspaceId;
361511
+ }
361512
+ const page = await listLibraryMedia2(q);
361280
361513
  return { summary: `${page.items.length} media item${page.items.length === 1 ? "" : "s"}${page.nextCursor ? " (more)" : ""}`, data: { workspaceId: page.workspaceId, items: page.items.map(brief3), nextCursor: page.nextCursor } };
361281
361514
  }
361282
361515
  });
@@ -362221,7 +362454,6 @@ var init_delete_current_file = __esm({
362221
362454
  var storeOpsSeam;
362222
362455
  var init_seams = __esm({
362223
362456
  "lib/commands/seams.ts"() {
362224
- "use strict";
362225
362457
  storeOpsSeam = (st) => ({
362226
362458
  booleanOperation: (op4) => st().booleanOperation(op4),
362227
362459
  flatten: () => st().flattenSelection(),
@@ -401852,6 +402084,10 @@ async function drive(meta3, start2) {
401852
402084
  return [...notes, ...runtime.drainParentNotifications()];
401853
402085
  },
401854
402086
  maxParallel: MAX_PARALLEL_TOOLS,
402087
+ // The loop's default cut every result at 8000 characters AFTER the tool had honoured its own limit, so
402088
+ // export_svg with maxChars 40000 still came back without its last paths and the model could not
402089
+ // rebuild the artwork. Tools bound their own output; this is only the backstop.
402090
+ maxResultChars: MAX_TOOL_RESULT_CHARS,
401855
402091
  maxRounds: TURN_MAX_ROUNDS,
401856
402092
  modelRetries: 3,
401857
402093
  isRecoverable: (err2) => !abort.signal.aborted && isInterruption(err2),
@@ -401977,7 +402213,7 @@ async function drive(meta3, start2) {
401977
402213
  useAgentStore.setState({ running: false, resuming: false, abort: null });
401978
402214
  }
401979
402215
  }
401980
- var ALWAYS_ALLOWED, MAX_HISTORY_MESSAGES, MAX_PARALLEL_TOOLS, TURN_MAX_ROUNDS, registerTurnTools, specsFor, loopTools, runs, agentRuns, setAgentRuns, RESUME_WINDOW_MS, MAX_INTERRUPTIONS, SUSPEND_RESTART_MS, STOPPED_MESSAGE;
402216
+ var ALWAYS_ALLOWED, MAX_HISTORY_MESSAGES, MAX_PARALLEL_TOOLS, MAX_TOOL_RESULT_CHARS, TURN_MAX_ROUNDS, registerTurnTools, specsFor, loopTools, runs, agentRuns, setAgentRuns, RESUME_WINDOW_MS, MAX_INTERRUPTIONS, SUSPEND_RESTART_MS, STOPPED_MESSAGE;
401981
402217
  var init_runner = __esm({
401982
402218
  "lib/agent/runner.ts"() {
401983
402219
  init_editor_store();
@@ -402003,6 +402239,7 @@ var init_runner = __esm({
402003
402239
  ALWAYS_ALLOWED = ["list_skills", "read_skill", "manage_todos", "notify", "get_selection", "task", "await_job", "send_message", "stop_agent", "list_agents", "list_profiles", "read_profile", "ask_user"];
402004
402240
  MAX_HISTORY_MESSAGES = 24;
402005
402241
  MAX_PARALLEL_TOOLS = 4;
402242
+ MAX_TOOL_RESULT_CHARS = 48e3;
402006
402243
  TURN_MAX_ROUNDS = 500;
402007
402244
  registerTurnTools = (deps) => {
402008
402245
  registerBuiltinTools();
@@ -406487,7 +406724,6 @@ function registerBuiltinCommands() {
406487
406724
  var builtinCommands, uiOnlyCommands;
406488
406725
  var init_builtin4 = __esm({
406489
406726
  "lib/commands/builtin.ts"() {
406490
- "use strict";
406491
406727
  init_registry();
406492
406728
  init_node();
406493
406729
  init_node();
@@ -407369,7 +407605,6 @@ __export(editor_store_exports, {
407369
407605
  rebuildVectorData: () => rebuildVectorData,
407370
407606
  recoverPointsFromData: () => recoverPointsFromData,
407371
407607
  resetLibraryDocuments: () => resetLibraryDocuments,
407372
- scaleChildrenRecursively: () => scaleChildrenRecursively,
407373
407608
  tabPrefsOnly: () => tabPrefsOnly,
407374
407609
  useActiveTool: () => useActiveTool,
407375
407610
  useCamera: () => useCamera,
@@ -407542,37 +407777,6 @@ function stripImagePlaceholderFills(nodes, prevNodes, changed) {
407542
407777
  }
407543
407778
  return out ?? nodes;
407544
407779
  }
407545
- function rebuildVectorData(points, closed) {
407546
- if (points.length < 2) return [];
407547
- const data = [];
407548
- data.push("M", points[0].x, points[0].y);
407549
- for (let i2 = 1; i2 < points.length; i2++) {
407550
- const prev = points[i2 - 1];
407551
- const curr = points[i2];
407552
- if (prev.handleOut && curr.handleIn) {
407553
- data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407554
- } else if (prev.handleOut) {
407555
- data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
407556
- } else if (curr.handleIn) {
407557
- data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407558
- } else {
407559
- data.push("L", curr.x, curr.y);
407560
- }
407561
- }
407562
- if (closed) {
407563
- const prev = points[points.length - 1];
407564
- const curr = points[0];
407565
- if (prev.handleOut && curr.handleIn) {
407566
- data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407567
- } else if (prev.handleOut) {
407568
- data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
407569
- } else if (curr.handleIn) {
407570
- data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407571
- }
407572
- data.push("Z");
407573
- }
407574
- return data;
407575
- }
407576
407780
  function fixCorruptedVectors(doc2) {
407577
407781
  let changed = false;
407578
407782
  const newNodes = { ...doc2.nodes };
@@ -407722,30 +407926,6 @@ function recoverPointsFromData(data) {
407722
407926
  }
407723
407927
  return points;
407724
407928
  }
407725
- function scaleChildrenRecursively(doc2, parentId, scaleX, scaleY) {
407726
- const parent = doc2.nodes[parentId];
407727
- if (!parent || !parent.childIds) return doc2;
407728
- const newDoc = { ...doc2, nodes: { ...doc2.nodes } };
407729
- const processNode = (nodeId2) => {
407730
- const node2 = newDoc.nodes[nodeId2];
407731
- if (!node2) return;
407732
- const updates = {
407733
- x: node2.x * scaleX,
407734
- y: node2.y * scaleY,
407735
- width: node2.width * scaleX,
407736
- height: node2.height * scaleY
407737
- };
407738
- if (node2.type === "TEXT") {
407739
- updates.fontSize = (node2.fontSize ?? 16) * Math.min(scaleX, scaleY);
407740
- }
407741
- newDoc.nodes[nodeId2] = { ...node2, ...updates };
407742
- if (node2.childIds) {
407743
- node2.childIds.forEach(processNode);
407744
- }
407745
- };
407746
- parent.childIds.forEach(processNode);
407747
- return newDoc;
407748
- }
407749
407929
  function placeInstance(document2, mainComponentId, pageId2, x2, y) {
407750
407930
  const main2 = document2.nodes[mainComponentId];
407751
407931
  if (!main2 || main2.type !== "COMPONENT") return null;
@@ -409352,10 +409532,10 @@ var init_editor_store = __esm({
409352
409532
  if ("childIds" in node2 && node2.childIds.length > 0) {
409353
409533
  newDoc = applyConstraints(newDoc, id3, node2.width, node2.height, newWidth, newHeight);
409354
409534
  }
409355
- if (node2.type === "GROUP" && node2.width > 0 && node2.height > 0) {
409356
- const scaleX = newWidth / node2.width;
409357
- const scaleY = newHeight / node2.height;
409358
- newDoc = scaleChildrenRecursively(newDoc, id3, scaleX, scaleY);
409535
+ if ((node2.type === "GROUP" || node2.type === "VECTOR") && node2.width > 0 && node2.height > 0) {
409536
+ for (const [pid, patch2] of scaleSubtreePatches(newDoc.nodes, id3, newWidth / node2.width, newHeight / node2.height)) {
409537
+ newDoc = updateNode(newDoc, pid, patch2);
409538
+ }
409359
409539
  } else if (node2.type === "TABLE" && node2.width > 0 && node2.height > 0) {
409360
409540
  const table3 = node2;
409361
409541
  const totalW = (table3.colWidths || []).reduce((a, b) => a + b, 0) || 1;
@@ -410364,23 +410544,35 @@ var init_editor_store = __esm({
410364
410544
  const geo = multiPolyToVectorGeometry(mp);
410365
410545
  if (!geo) return;
410366
410546
  const first2 = doc2.nodes[state.selection[0]];
410547
+ const firstFills = (id3) => {
410548
+ const n = doc2.nodes[id3];
410549
+ if (!n) return void 0;
410550
+ if (n.fills?.length && n.type !== "GROUP") return n.fills;
410551
+ for (const c2 of n.childIds ?? []) {
410552
+ const f2 = firstFills(c2);
410553
+ if (f2) return f2;
410554
+ }
410555
+ return void 0;
410556
+ };
410557
+ const fills = state.selection.map(firstFills).find(Boolean);
410367
410558
  const vectorNode = createVector({
410368
410559
  ...geo,
410369
- fills: first2?.fills?.length ? first2.fills : [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.85, a: 1 }, visible: true }],
410560
+ fills: fills ?? [{ type: "SOLID", color: { r: 0.85, g: 0.85, b: 0.85, a: 1 }, visible: true }],
410370
410561
  strokes: [],
410371
410562
  fillRule: "EVENODD",
410372
410563
  name: state.selection.length > 1 ? "Flattened" : `${first2?.name ?? "Vector"} (flattened)`
410373
410564
  });
410374
410565
  doc2 = addNode(doc2, vectorNode, state.currentPageId);
410566
+ const parentId = first2?.parentId;
410567
+ if (parentId && parentId !== state.currentPageId && doc2.nodes[parentId]) {
410568
+ const placed2 = placementUnder(doc2, vectorNode.id, parentId);
410569
+ if (placed2) doc2 = reparentNode(updateNode(doc2, vectorNode.id, placed2), vectorNode.id, parentId);
410570
+ }
410375
410571
  state.selection.forEach((id3) => {
410376
410572
  if (doc2.nodes[id3]) doc2 = removeNode(doc2, id3);
410377
410573
  });
410378
- const command = createCommand("FLATTEN", "Flatten selection", documentBefore, doc2);
410379
- set4({
410380
- document: doc2,
410381
- history: pushCommand(state.history, command),
410382
- selection: [vectorNode.id]
410383
- });
410574
+ if (documentBefore !== doc2) get4().commitDocument(doc2, "Flatten selection");
410575
+ set4({ selection: [vectorNode.id] });
410384
410576
  },
410385
410577
  joinPaths: () => {
410386
410578
  const state = get4();
@@ -414092,7 +414284,7 @@ init_resources();
414092
414284
  init_desktop();
414093
414285
  init_hypery();
414094
414286
  import { createInterface } from "node:readline";
414095
- var VERSION2 = "0.53.1";
414287
+ var VERSION2 = "0.53.2";
414096
414288
  var HELP = `popcraft ${VERSION2} \u2014 the PopCraft command line
414097
414289
 
414098
414290
  Files (headless, on .popcraft files):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popcraft/cli",
3
- "version": "0.53.1",
3
+ "version": "0.53.2",
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.53.1"
37
+ "@popcraft/kit": "0.53.2"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node build.mjs",