@popcraft/cli 0.53.0 → 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 -173
  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.0"}`, ...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));
@@ -94509,6 +94509,7 @@ function hasMessage(key5) {
94509
94509
  var current2, loading, listeners, formats, asts;
94510
94510
  var init_runtime = __esm({
94511
94511
  "lib/i18n/runtime.ts"() {
94512
+ "use strict";
94512
94513
  init_intl_messageformat();
94513
94514
  init_icu_messageformat_parser();
94514
94515
  init_catalogs();
@@ -97385,7 +97386,6 @@ function selectionWorldBounds(nodes, ids2, scrollState) {
97385
97386
  }
97386
97387
  var init_selection_bounds = __esm({
97387
97388
  "lib/editor/selection-bounds.ts"() {
97388
- "use strict";
97389
97389
  init_scene_graph();
97390
97390
  init_transform();
97391
97391
  }
@@ -97577,7 +97577,6 @@ function relinkCopies(doc2, copies) {
97577
97577
  var TEXT_PATH_SOURCE_TYPES, isPathSource;
97578
97578
  var init_text_links = __esm({
97579
97579
  "lib/document/text-links.ts"() {
97580
- "use strict";
97581
97580
  init_transform();
97582
97581
  TEXT_PATH_SOURCE_TYPES = ["VECTOR", "ELLIPSE", "RECTANGLE", "POLYGON", "STAR", "LINE", "ARROW", "FRAME"];
97583
97582
  isPathSource = (n) => !!n && TEXT_PATH_SOURCE_TYPES.includes(n.type);
@@ -248026,7 +248025,6 @@ function repairCompositions(comps, nodes) {
248026
248025
  var isObj5, finite6, optFinite, isVec2, TIMELINE_COLOR_IDS, TIMELINE_COLORS, UNITS, ORDERS, PROP_KEYS, ANIMATOR_ID, MOTION_TYPES, TRANSITIONS, isFraction, MEDIA_HASH;
248027
248026
  var init_invariants = __esm({
248028
248027
  "lib/motion/invariants.ts"() {
248029
- "use strict";
248030
248028
  init_easing();
248031
248029
  init_properties();
248032
248030
  init_markers();
@@ -249149,7 +249147,6 @@ function beforeWrite() {
249149
249147
  var leave;
249150
249148
  var init_read_only = __esm({
249151
249149
  "lib/editor/modes/read-only.ts"() {
249152
- "use strict";
249153
249150
  leave = null;
249154
249151
  }
249155
249152
  });
@@ -251994,12 +251991,65 @@ var require_polygon_clipping_cjs = __commonJS({
251994
251991
  }
251995
251992
  });
251996
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
+
251997
252046
  // lib/engine/geometry/triangulate.ts
251998
- var import_polygon_clipping;
252047
+ var import_polygon_clipping2;
251999
252048
  var init_triangulate = __esm({
252000
252049
  "lib/engine/geometry/triangulate.ts"() {
252001
252050
  "use strict";
252002
- import_polygon_clipping = __toESM(require_polygon_clipping_cjs());
252051
+ import_polygon_clipping2 = __toESM(require_polygon_clipping_cjs());
252052
+ init_winding();
252003
252053
  }
252004
252054
  });
252005
252055
 
@@ -252023,7 +252073,7 @@ function computeBooleanMultiPoly(node2, allNodes) {
252023
252073
  const asPolys = rings.map((r) => [r]);
252024
252074
  let mp;
252025
252075
  try {
252026
- 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;
252027
252077
  } catch {
252028
252078
  mp = asPolys;
252029
252079
  }
@@ -252035,25 +252085,25 @@ function computeBooleanMultiPoly(node2, allNodes) {
252035
252085
  try {
252036
252086
  const rest = operands.slice(1);
252037
252087
  if (rest.length > 0) {
252038
- if (op4 === "SUBTRACT") result = import_polygon_clipping2.default.difference(operands[0], ...rest);
252039
- else if (op4 === "INTERSECT") result = import_polygon_clipping2.default.intersection(operands[0], ...rest);
252040
- else if (op4 === "EXCLUDE") result = import_polygon_clipping2.default.xor(operands[0], ...rest);
252041
- 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);
252042
252092
  }
252043
252093
  } catch {
252044
252094
  try {
252045
- result = import_polygon_clipping2.default.union(operands[0], ...operands.slice(1));
252095
+ result = import_polygon_clipping3.default.union(operands[0], ...operands.slice(1));
252046
252096
  } catch {
252047
252097
  result = operands[0];
252048
252098
  }
252049
252099
  }
252050
252100
  return result;
252051
252101
  }
252052
- var import_polygon_clipping2;
252102
+ var import_polygon_clipping3;
252053
252103
  var init_vector_geometry_cache = __esm({
252054
252104
  "lib/engine/vector-geometry-cache.ts"() {
252055
252105
  "use strict";
252056
- import_polygon_clipping2 = __toESM(require_polygon_clipping_cjs());
252106
+ import_polygon_clipping3 = __toESM(require_polygon_clipping_cjs());
252057
252107
  init_flatten();
252058
252108
  init_triangulate();
252059
252109
  init_shape_outline();
@@ -252072,11 +252122,11 @@ function arrangementFaces(shapes) {
252072
252122
  const next2 = [];
252073
252123
  let rest = shape2;
252074
252124
  for (const f2 of faces2) {
252075
- const inside2 = op2(import_polygon_clipping3.default.intersection, f2.mp, shape2);
252076
- if (solid(inside2)) next2.push({ mp: inside2, sources: [...f2.sources, i2] });
252077
- 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);
252078
252128
  if (solid(outside)) next2.push({ mp: outside, sources: f2.sources });
252079
- 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);
252080
252130
  }
252081
252131
  if (solid(rest)) next2.push({ mp: rest, sources: [i2] });
252082
252132
  faces2 = next2;
@@ -252085,15 +252135,15 @@ function arrangementFaces(shapes) {
252085
252135
  }
252086
252136
  function pointInMultiPoly(mp, x2, y) {
252087
252137
  for (const poly of mp) {
252088
- let inside2 = false;
252138
+ let inside3 = false;
252089
252139
  for (const ring of poly) {
252090
252140
  for (let i2 = 0, j = ring.length - 1; i2 < ring.length; j = i2++) {
252091
252141
  const [xi, yi] = ring[i2];
252092
252142
  const [xj, yj] = ring[j];
252093
- 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;
252094
252144
  }
252095
252145
  }
252096
- if (inside2) return true;
252146
+ if (inside3) return true;
252097
252147
  }
252098
252148
  return false;
252099
252149
  }
@@ -252105,16 +252155,16 @@ function unionFaces(faces2, picked) {
252105
252155
  const parts = picked.map((i2) => faces2[i2]?.mp).filter(solid);
252106
252156
  if (parts.length === 0) return [];
252107
252157
  try {
252108
- 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];
252109
252159
  } catch {
252110
252160
  return parts[0];
252111
252161
  }
252112
252162
  }
252113
- 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;
252114
252164
  var init_regions = __esm({
252115
252165
  "lib/engine/geometry/regions.ts"() {
252116
252166
  "use strict";
252117
- import_polygon_clipping3 = __toESM(require_polygon_clipping_cjs());
252167
+ import_polygon_clipping4 = __toESM(require_polygon_clipping_cjs());
252118
252168
  MAX_SHAPE_BUILDER_SHAPES = 12;
252119
252169
  area2 = (mp) => {
252120
252170
  let a = 0;
@@ -252216,7 +252266,7 @@ function bufferPolylines(lines2, width) {
252216
252266
  }
252217
252267
  if (pieces.length === 0) return [];
252218
252268
  try {
252219
- 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]];
252220
252270
  } catch {
252221
252271
  return pieces.map((p2) => [p2[0]]);
252222
252272
  }
@@ -252225,7 +252275,7 @@ function combineRings(rings, mode2) {
252225
252275
  const polys = rings.filter((r) => r.length >= 3).map((r) => [r]);
252226
252276
  if (polys.length === 0) return [];
252227
252277
  try {
252228
- 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]];
252229
252279
  } catch {
252230
252280
  return polys.map((p2) => [p2[0]]);
252231
252281
  }
@@ -252239,8 +252289,15 @@ function nodeWorldMultiPoly(doc2, id3) {
252239
252289
  return mp.map((poly) => poly.map((ring) => ring.map(([x2, y]) => place4(x2, y))));
252240
252290
  }
252241
252291
  const rings = nodeWorldRings(doc2, id3);
252242
- const mode2 = node2.type === "VECTOR" && node2.fillRule === "EVENODD" ? "xor" : "union";
252243
- 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");
252244
252301
  }
252245
252302
  function nodeAreaMultiPoly(doc2, id3) {
252246
252303
  if (!doc2.nodes[id3]) return [];
@@ -252250,13 +252307,23 @@ function nodeAreaMultiPoly(doc2, id3) {
252250
252307
  }
252251
252308
  function flattenNodesMultiPoly(doc2, ids2) {
252252
252309
  const operands = [];
252253
- for (const id3 of ids2) {
252254
- const mp = nodeAreaMultiPoly(doc2, id3);
252255
- if (mp.length > 0) operands.push(mp);
252256
- }
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);
252257
252324
  if (operands.length === 0) return [];
252258
252325
  try {
252259
- 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];
252260
252327
  } catch {
252261
252328
  return operands[0];
252262
252329
  }
@@ -252282,7 +252349,7 @@ function outlineStrokeMultiPoly(doc2, id3) {
252282
252349
  const buffer2 = bufferPolylines(lines2, width * 2);
252283
252350
  if (fill.length === 0 || buffer2.length === 0) return bufferPolylines(lines2, width);
252284
252351
  try {
252285
- 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);
252286
252353
  } catch {
252287
252354
  return bufferPolylines(lines2, width);
252288
252355
  }
@@ -252314,16 +252381,17 @@ function shapeBuilderFaces(doc2, ids2) {
252314
252381
  facesCache = { nodes: doc2.nodes, key: key5, faces: faces2 };
252315
252382
  return faces2;
252316
252383
  }
252317
- var import_polygon_clipping4, JOIN_CIRCLE_SEGMENTS, facesCache;
252384
+ var import_polygon_clipping5, JOIN_CIRCLE_SEGMENTS, facesCache;
252318
252385
  var init_vector_ops = __esm({
252319
252386
  "lib/engine/geometry/vector-ops.ts"() {
252320
252387
  "use strict";
252321
- import_polygon_clipping4 = __toESM(require_polygon_clipping_cjs());
252388
+ import_polygon_clipping5 = __toESM(require_polygon_clipping_cjs());
252322
252389
  init_scene_graph();
252323
252390
  init_transform();
252324
252391
  init_shape_outline();
252325
252392
  init_flatten();
252326
252393
  init_vector_geometry_cache();
252394
+ init_winding();
252327
252395
  init_regions();
252328
252396
  JOIN_CIRCLE_SEGMENTS = 16;
252329
252397
  facesCache = null;
@@ -252377,15 +252445,15 @@ var init_brush_along_path = __esm({
252377
252445
 
252378
252446
  // lib/engine/geometry/polygon.ts
252379
252447
  function pointInPolygon(p2, poly) {
252380
- let inside2 = false;
252448
+ let inside3 = false;
252381
252449
  for (let i2 = 0, j = poly.length - 1; i2 < poly.length; j = i2++) {
252382
252450
  const a = poly[i2], b = poly[j];
252383
252451
  if (a.y > p2.y !== b.y > p2.y) {
252384
252452
  const xCross = a.x + (p2.y - a.y) * (b.x - a.x) / (b.y - a.y);
252385
- if (p2.x < xCross) inside2 = !inside2;
252453
+ if (p2.x < xCross) inside3 = !inside3;
252386
252454
  }
252387
252455
  }
252388
- return inside2;
252456
+ return inside3;
252389
252457
  }
252390
252458
  var init_polygon = __esm({
252391
252459
  "lib/engine/geometry/polygon.ts"() {
@@ -252425,8 +252493,8 @@ function enclosedRegion(shapes, pt, view2) {
252425
252493
  }
252426
252494
  try {
252427
252495
  let region = rectMP(view2);
252428
- for (const c2 of containing) region = import_polygon_clipping5.default.intersection(region, c2);
252429
- 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);
252430
252498
  const hit = region.find((p2) => pointInPoly(pt, p2));
252431
252499
  if (!hit) return { ok: false, reason: "EMPTY" };
252432
252500
  if (!containing.length && touchesRect(hit, view2)) return { ok: false, reason: "OPEN" };
@@ -252477,11 +252545,11 @@ function findEnclosedRegion(doc2, pageId2, pt, view2) {
252477
252545
  walk4(pageId2);
252478
252546
  return enclosedRegion(shapes, pt, view2);
252479
252547
  }
252480
- var import_polygon_clipping5, ringPts, pointInMP, rectMP;
252548
+ var import_polygon_clipping6, ringPts, pointInMP, rectMP;
252481
252549
  var init_bucket = __esm({
252482
252550
  "lib/engine/geometry/bucket.ts"() {
252483
252551
  "use strict";
252484
- import_polygon_clipping5 = __toESM(require_polygon_clipping_cjs());
252552
+ import_polygon_clipping6 = __toESM(require_polygon_clipping_cjs());
252485
252553
  init_scene_graph();
252486
252554
  init_vector_ops();
252487
252555
  init_polygon();
@@ -252591,7 +252659,6 @@ function resizeWindow(doc2, target2, rect) {
252591
252659
  }
252592
252660
  var init_crop = __esm({
252593
252661
  "lib/editor/crop.ts"() {
252594
- "use strict";
252595
252662
  init_dist_node();
252596
252663
  init_scene_graph();
252597
252664
  init_photo_frames();
@@ -256124,7 +256191,6 @@ fn shade(uv: vec2<f32>, res: vec2<f32>, tm: f32) -> vec4<f32> {
256124
256191
  var SHADER_PRESETS, shaderPreset, legacyPresetOf, DEFAULT_SHADER_PRESET, DEFAULT_TEXTURE_PARAMS, f, vec4, NOISE_LIB;
256125
256192
  var init_shader_presets = __esm({
256126
256193
  "lib/engine/shader-presets.ts"() {
256127
- "use strict";
256128
256194
  init_shader_graph();
256129
256195
  init_shader_graph_presets();
256130
256196
  init_shader_presets_legacy();
@@ -257088,7 +257154,6 @@ function followPeers(peers) {
257088
257154
  var PALETTE, usePresence, awareness, CURSOR_SEND_MS, VIEWPORT_SEND_MS, cursorTimer, pendingCursor, sentCursor, EMOTE_MS, lastFollowCamera;
257089
257155
  var init_presence = __esm({
257090
257156
  "lib/collab/presence.ts"() {
257091
- "use strict";
257092
257157
  init_esm();
257093
257158
  init_editor_store();
257094
257159
  init_status_announcements();
@@ -257645,10 +257710,10 @@ function collapse(doc2) {
257645
257710
  else groups.set(h, [id3]);
257646
257711
  }
257647
257712
  const chosen = [];
257648
- const inside2 = /* @__PURE__ */ new Set();
257713
+ const inside3 = /* @__PURE__ */ new Set();
257649
257714
  const sorted = [...groups.values()].filter((g) => g.length >= MIN_COPIES).sort((a, b) => (size6.get(b[0]) ?? 0) - (size6.get(a[0]) ?? 0));
257650
257715
  const within3 = (id3) => {
257651
- 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;
257652
257717
  return false;
257653
257718
  };
257654
257719
  for (const g of sorted) {
@@ -257656,7 +257721,7 @@ function collapse(doc2) {
257656
257721
  const copies = g.filter((id3) => !within3(id3) && (id3 === rep || sameContents(nodes, rep, id3)));
257657
257722
  if (copies.length < MIN_COPIES || !copies.includes(rep)) continue;
257658
257723
  chosen.push(copies);
257659
- for (const id3 of copies) inside2.add(id3);
257724
+ for (const id3 of copies) inside3.add(id3);
257660
257725
  }
257661
257726
  if (!chosen.length) return doc2;
257662
257727
  const out = { ...nodes };
@@ -258001,7 +258066,6 @@ function onTabMessage(fn) {
258001
258066
  var EDITOR_HOME, localDocPath, cloudFilePath, CHANNEL2, channel2;
258002
258067
  var init_tab_mode = __esm({
258003
258068
  "lib/desktop/tab-mode.ts"() {
258004
- "use strict";
258005
258069
  EDITOR_HOME = "/home";
258006
258070
  localDocPath = (docId) => `${EDITOR_HOME}?doc=${encodeURIComponent(docId)}`;
258007
258071
  cloudFilePath = (key5) => `/pop/${encodeURIComponent(key5)}`;
@@ -258018,6 +258082,64 @@ function scaleVectorPaths(paths, sx, sy, rebuild) {
258018
258082
  return { ...path, points, data: rebuild(points, path.closed) };
258019
258083
  });
258020
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
+ }
258021
258143
  var scalePoint;
258022
258144
  var init_scale_geometry = __esm({
258023
258145
  "lib/document/scale-geometry.ts"() {
@@ -260624,7 +260746,6 @@ function retuneShaderAudio(node2, fillId) {
260624
260746
  }
260625
260747
  var init_shader_audio = __esm({
260626
260748
  "lib/motion/shader-audio.ts"() {
260627
- "use strict";
260628
260749
  init_shader_params();
260629
260750
  init_modifier_edit();
260630
260751
  init_keys();
@@ -262658,7 +262779,6 @@ function formatLimit(key5, max3) {
262658
262779
  var formatBytes;
262659
262780
  var init_limits2 = __esm({
262660
262781
  "lib/billing/limits.ts"() {
262661
- "use strict";
262662
262782
  init_format();
262663
262783
  init_plans();
262664
262784
  formatBytes = formatFileSize;
@@ -306875,7 +306995,6 @@ function decodeSource(key5, label3) {
306875
306995
  var DECODE_CACHE_BYTES, cache7, pending4, held, sizeOf, lengths, decodedLengths, DECODE_TIMEOUT_MS, decodedAudio, decodedSource, pcmOf;
306876
306996
  var init_audio_decode = __esm({
306877
306997
  "lib/motion/audio-decode.ts"() {
306878
- "use strict";
306879
306998
  init_media_store();
306880
306999
  init_audio_plan();
306881
307000
  init_time_stretch();
@@ -311656,6 +311775,7 @@ function startCloudSync() {
311656
311775
  var status, readOnlyDoc, activeLink, statusListeners, notice, urlArmed, opening, syncStatus, canEdit, uploaded, MEDIA_UPLOAD_PARALLEL, pushTimer, pushing, dirty, pendingMedia, collab, collabSession, collabAnonymous, uploadedThumbs, thumbCheckedAt, THUMB_EVERY_MS, THUMB_MIN_GAP_MS, putThumbnail, openingFiles, mediaHealed, started2;
311657
311776
  var init_sync2 = __esm({
311658
311777
  "lib/client/cloud/sync.ts"() {
311778
+ "use strict";
311659
311779
  init_paint_repair();
311660
311780
  init_migrate();
311661
311781
  init_view();
@@ -312926,7 +313046,6 @@ function applyTextPath(layout2, path, tp, rebase = true) {
312926
313046
  var defaultCircleRadius, ownsPath, mapPoint;
312927
313047
  var init_text_path = __esm({
312928
313048
  "lib/engine/text/text-path.ts"() {
312929
- "use strict";
312930
313049
  init_scene_graph();
312931
313050
  init_transform();
312932
313051
  init_shape_to_path();
@@ -313044,7 +313163,6 @@ function areaBounds(rings) {
313044
313163
  }
313045
313164
  var init_text_area = __esm({
313046
313165
  "lib/engine/text/text-area.ts"() {
313047
- "use strict";
313048
313166
  }
313049
313167
  });
313050
313168
 
@@ -315582,12 +315700,12 @@ function textStrokeSVG(node2) {
315582
315700
  const w = Array.isArray(stroke3.width) ? Math.max(...stroke3.width) : stroke3.width;
315583
315701
  if (!(w > 0)) return null;
315584
315702
  const behind = strokeBehindFill(node2) || stroke3.alignment === "OUTSIDE";
315585
- const inside2 = stroke3.alignment === "INSIDE";
315586
- if (inside2 && behind) return null;
315703
+ const inside3 = stroke3.alignment === "INSIDE";
315704
+ if (inside3 && behind) return null;
315587
315705
  const alpha2 = (stroke3.color.a ?? 1) * (stroke3.opacity ?? 1);
315588
315706
  const join13 = stroke3.join === "ROUND" ? "round" : stroke3.join === "BEVEL" ? "bevel" : "miter";
315589
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"' : ""}`;
315590
- return { attrs, behind, inside: inside2 };
315708
+ return { attrs, behind, inside: inside3 };
315591
315709
  }
315592
315710
  function textFillAttr(resolved, node2, ctx2) {
315593
315711
  const fill0 = resolved.fills?.[0];
@@ -316511,12 +316629,12 @@ Q
316511
316629
  };
316512
316630
  const paintStrokes = () => {
316513
316631
  for (const s of item.strokes) {
316514
- const inside2 = s.alignment === "INSIDE";
316515
- const wdt = inside2 ? s.width * 2 : s.width;
316632
+ const inside3 = s.alignment === "INSIDE";
316633
+ const wdt = inside3 ? s.width * 2 : s.width;
316516
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`);
316517
316635
  this.w(s.dash ? ` [${num7(s.dash[0])} ${num7(s.dash[1])}] 0 d
316518
316636
  ` : " [] 0 d\n");
316519
- if (inside2) this.w(`${ops2}${clipOp} n
316637
+ if (inside3) this.w(`${ops2}${clipOp} n
316520
316638
  `);
316521
316639
  this.w(`${ops2}S
316522
316640
  Q
@@ -316689,8 +316807,8 @@ function removeOverlaps(cmds) {
316689
316807
  break;
316690
316808
  }
316691
316809
  if (!touching) return cmds;
316692
- const pos = rings.filter((r) => signedArea(r) > 0).map((r) => [r]), neg = rings.filter((r) => signedArea(r) < 0).map((r) => [r]);
316693
- 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;
316694
316812
  let result;
316695
316813
  try {
316696
316814
  const a = pos.length ? clip.union(...pos) : [], b = neg.length ? clip.union(...neg) : [];
@@ -316803,12 +316921,12 @@ function buildCFF(name2, unitsPerEm, glyphs) {
316803
316921
  }
316804
316922
  return out;
316805
316923
  }
316806
- var import_polygon_clipping6, signedArea, bbox, intersects;
316924
+ var import_polygon_clipping7, signedArea2, bbox, intersects;
316807
316925
  var init_cff = __esm({
316808
316926
  "lib/io/pdf/cff.ts"() {
316809
316927
  "use strict";
316810
- import_polygon_clipping6 = __toESM(require_polygon_clipping_cjs());
316811
- 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) => {
316812
316930
  const [nx, ny] = p2[(i2 + 1) % p2.length];
316813
316931
  return a + x2 * ny - nx * y;
316814
316932
  }, 0) / 2;
@@ -318323,6 +318441,7 @@ function agentToolList() {
318323
318441
  var AGENT_CAPABILITIES, legacyCommandId, legacyTools, blobToDataUrl3, provided, legacyTool, legacyToolList;
318324
318442
  var init_agent = __esm({
318325
318443
  "lib/commands/agent.ts"() {
318444
+ "use strict";
318326
318445
  init_types5();
318327
318446
  init_registry();
318328
318447
  init_errors();
@@ -318360,6 +318479,7 @@ __export(tools_exports, {
318360
318479
  allTools: () => allTools,
318361
318480
  defineTool: () => defineTool,
318362
318481
  getTool: () => getTool,
318482
+ parseToolArgs: () => parseToolArgs,
318363
318483
  registerTools: () => registerTools,
318364
318484
  runTool: () => runTool,
318365
318485
  toolSpecs: () => toolSpecs
@@ -318380,14 +318500,53 @@ function toolSpecs(names) {
318380
318500
  function: { name: t3.name, description: t3.description, parameters: toolParameters(t3.parameters) }
318381
318501
  }));
318382
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
+ }
318383
318541
  async function runTool(name2, argsText, ctx2) {
318384
318542
  let raw = {};
318385
318543
  if (argsText.trim()) {
318386
- try {
318387
- raw = JSON.parse(argsText);
318388
- } catch {
318389
- 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");
318390
318548
  }
318549
+ raw = parsed2;
318391
318550
  }
318392
318551
  return dispatch2(name2, {
318393
318552
  surface: "agent",
@@ -319105,16 +319264,28 @@ function placeSvgLayers(doc2, parentId, svg, place4) {
319105
319264
  if (n.type === "IMAGE" && !doc2.nodes[n.id] && url4?.startsWith("data:")) nodes[n.id] = { ...n, imageUrl: place4.intern(url4) };
319106
319265
  }
319107
319266
  }
319108
- const minX = Math.min(...kids.map((k2) => k2.x)), minY = Math.min(...kids.map((k2) => k2.y));
319109
- const maxX = Math.max(...kids.map((k2) => k2.x + k2.width)), maxY = Math.max(...kids.map((k2) => k2.y + k2.height));
319110
- for (const k2 of kids) nodes[k2.id] = { ...nodes[k2.id], x: k2.x - minX, y: k2.y - minY };
319111
- const w = Math.max(1, maxX - minX), h = Math.max(1, maxY - minY);
319112
- 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 };
319113
319283
  return { doc: { ...next2, nodes }, id: frame3.id };
319114
319284
  }
319115
319285
  var MAX_SVG_LAYER_BYTES;
319116
319286
  var init_svg_place = __esm({
319117
319287
  "lib/io/svg-place.ts"() {
319288
+ init_scale_geometry();
319118
319289
  init_scene_graph();
319119
319290
  init_svg_import();
319120
319291
  MAX_SVG_LAYER_BYTES = 8 * 1024 * 1024;
@@ -319134,6 +319305,31 @@ function descendants(doc2, rootId, skipRoot = true) {
319134
319305
  }
319135
319306
  return out;
319136
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
+ }
319137
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;
319138
319334
  var init_node = __esm({
319139
319335
  "lib/commands/node.ts"() {
@@ -319146,6 +319342,7 @@ var init_node = __esm({
319146
319342
  init_scene_graph();
319147
319343
  init_types();
319148
319344
  init_errors();
319345
+ init_scale_geometry();
319149
319346
  init_plugin_shim();
319150
319347
  init_types5();
319151
319348
  init_ui();
@@ -319480,7 +319677,7 @@ var init_node = __esm({
319480
319677
  for (const [k2, v] of Object.entries(u.props)) {
319481
319678
  if (args2.strict && FORBIDDEN_PROPS.has(k2)) throw new ToolError(`update_nodes cannot change "${k2}". Use reparent, delete_nodes or create_node instead.`);
319482
319679
  if (FORBIDDEN_PROPS.has(k2) || v === void 0) continue;
319483
- patch2[k2] = v;
319680
+ patch2[k2] = k2 === "fills" || k2 === "strokes" ? normalizePaints(v, k2) : v;
319484
319681
  changedKeys.add(k2);
319485
319682
  }
319486
319683
  if (args2.strict && !Object.keys(patch2).length) throw new ToolError(`No properties given for "${u.id}".`);
@@ -319595,9 +319792,21 @@ var init_node = __esm({
319595
319792
  const n = doc2.nodes[id3];
319596
319793
  const w = args2.width ?? (args2.scale ? n.width * args2.scale : n.width);
319597
319794
  const h = args2.height ?? (args2.scale ? n.height * args2.scale : n.height);
319598
- 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
+ }
319599
319801
  }
319600
- 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);
319601
319810
  return { summary: `Resized ${plural2(ids2.length, "layer")}`, data: { ids: ids2 } };
319602
319811
  }
319603
319812
  });
@@ -319934,6 +320143,9 @@ var init_node = __esm({
319934
320143
  svg: external_exports.string().min(8).optional().describe("The full <svg>\u2026</svg> markup."),
319935
320144
  parentId: external_exports.string().optional(),
319936
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)."),
319937
320149
  name: external_exports.string().max(200).optional().describe("Name of that frame (with `at`)."),
319938
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.")
319939
320151
  }),
@@ -319941,7 +320153,7 @@ var init_node = __esm({
319941
320153
  plugin: { method: "importSvg" },
319942
320154
  agent: {
319943
320155
  name: "import_svg",
319944
- 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.'
319945
320157
  },
319946
320158
  // Paste, drop and File › Import: one undo step, and the ones that would not convert reported back so the
319947
320159
  // caller can place them as pictures instead (lib/editor/svg-paste.ts).
@@ -319959,7 +320171,7 @@ var init_node = __esm({
319959
320171
  const added2 = [];
319960
320172
  const failed2 = [];
319961
320173
  items.forEach((item, i2) => {
319962
- 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) });
319963
320175
  if (placed2) {
319964
320176
  next3 = placed2.doc;
319965
320177
  added2.push(placed2.id);
@@ -320954,7 +321166,8 @@ var init_paint = __esm({
320954
321166
  const rec = n;
320955
321167
  const patch2 = {};
320956
321168
  let hit = 0;
320957
- 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;
320958
321171
  if (paints?.length) {
320959
321172
  let changed = false;
320960
321173
  const next2 = paints.map((p2) => {
@@ -320981,7 +321194,10 @@ var init_paint = __esm({
320981
321194
  }
320982
321195
  return out;
320983
321196
  });
320984
- if (changed) patch2.fills = next2;
321197
+ if (changed) {
321198
+ patch2.fills = next2;
321199
+ if (rec.fillStyleId) patch2.fillStyleId = void 0;
321200
+ }
320985
321201
  }
320986
321202
  const strokes = rec.strokes;
320987
321203
  if (strokes?.length) {
@@ -320993,7 +321209,10 @@ var init_paint = __esm({
320993
321209
  hit++;
320994
321210
  return { ...s, color: c2 };
320995
321211
  });
320996
- if (changed) patch2.strokes = next2;
321212
+ if (changed) {
321213
+ patch2.strokes = next2;
321214
+ if (rec.strokeStyleId) patch2.strokeStyleId = void 0;
321215
+ }
320997
321216
  }
320998
321217
  const effects = effectsOf(n);
320999
321218
  if (effects.length) {
@@ -321024,7 +321243,7 @@ var init_paint = __esm({
321024
321243
  }
321025
321244
  }
321026
321245
  if (!swaps) throw new ToolError(`No ${args2.from} found under ${args2.rootId ?? "the selection"}. Check the color with get_node, or raise "tolerance".`);
321027
- ctx2.commit({ ...doc2, nodes });
321246
+ ctx2.commit(applyInvariants({ ...doc2, nodes }, touched));
321028
321247
  return { summary: `Replaced ${args2.from} with ${args2.to} in ${plural2(touched.length, "layer")} (${swaps} paints)`, data: { ids: touched.slice(0, 50), swaps } };
321029
321248
  }
321030
321249
  });
@@ -328477,7 +328696,7 @@ var init_package = __esm({
328477
328696
  "packages/runtime/package.json"() {
328478
328697
  package_default = {
328479
328698
  name: "@popcraft/runtime",
328480
- version: "0.53.0",
328699
+ version: "0.53.2",
328481
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.",
328482
328701
  license: "MIT",
328483
328702
  homepage: "https://popcraft.app/docs/exporting/nextjs",
@@ -353143,7 +353362,6 @@ function exportsOf(source3) {
353143
353362
  var CODE_LIMITS, packageOf;
353144
353363
  var init_source2 = __esm({
353145
353364
  "lib/code/source.ts"() {
353146
- "use strict";
353147
353365
  CODE_LIMITS = { modules: 100, sourceLength: 2e5, packages: 12 };
353148
353366
  packageOf = (spec) => spec.split("/").slice(0, spec.startsWith("@") ? 2 : 1).join("/");
353149
353367
  }
@@ -355192,7 +355410,7 @@ function reading(doc2, ids2) {
355192
355410
  }
355193
355411
  function boardColumns(doc2, roots, looseTitle, within3) {
355194
355412
  const areas = within3?.map((n) => boxOf(doc2, n));
355195
- const wanted = (p2) => !areas || areas.some((a) => inside(p2, a));
355413
+ const wanted = (p2) => !areas || areas.some((a) => inside2(p2, a));
355196
355414
  const cards = [];
355197
355415
  const boxes = [];
355198
355416
  const texts = [];
@@ -355223,7 +355441,7 @@ function boardColumns(doc2, roots, looseTitle, within3) {
355223
355441
  const band = b.box.minY + (b.box.maxY - b.box.minY) * 0.25;
355224
355442
  const top = texts.filter((t3) => {
355225
355443
  const c2 = centre(t3.box);
355226
- return inside(c2, b.box) && c2.y <= band;
355444
+ return inside2(c2, b.box) && c2.y <= band;
355227
355445
  }).sort((x2, y) => x2.box.minY - y.box.minY)[0];
355228
355446
  if (top) titles.set(b.node.id, { text: top.text, id: top.id });
355229
355447
  else if (!DEFAULT_NAMES.test(b.node.name.trim())) titles.set(b.node.id, { text: b.node.name });
@@ -355231,7 +355449,7 @@ function boardColumns(doc2, roots, looseTitle, within3) {
355231
355449
  const columnAt = (p2) => {
355232
355450
  let best = null;
355233
355451
  for (const b of boxes) {
355234
- if (!inside(p2, b.box)) continue;
355452
+ if (!inside2(p2, b.box)) continue;
355235
355453
  if (!best) {
355236
355454
  best = b;
355237
355455
  continue;
@@ -355274,7 +355492,7 @@ function boardCsv(columns) {
355274
355492
  const rows = Math.max(0, ...columns.map((c2) => c2.items.length));
355275
355493
  return toCsv([columns.map((c2) => c2.title), ...Array.from({ length: rows }, (_, i2) => columns.map((c2) => c2.items[i2] ?? ""))]);
355276
355494
  }
355277
- 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;
355278
355496
  var init_board_export = __esm({
355279
355497
  "lib/popcorn/board-export.ts"() {
355280
355498
  init_scene_graph();
@@ -355284,7 +355502,7 @@ var init_board_export = __esm({
355284
355502
  textOf = (n) => (n.text ?? "").replace(/\s+/g, " ").trim();
355285
355503
  boxOf = (doc2, n) => uprightBounds(getWorldTransform(doc2, n.id), n.width, n.height);
355286
355504
  centre = (b) => ({ x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 });
355287
- 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;
355288
355506
  area3 = (b) => (b.maxX - b.minX) * (b.maxY - b.minY);
355289
355507
  textLayer = (n) => n.type === "TEXT" ? (n.characters ?? "").replace(/\s+/g, " ").trim() : "";
355290
355508
  COLUMN_TYPES = /* @__PURE__ */ new Set(["FRAME", "SECTION", "GROUP", "RECTANGLE", "INSTANCE", "COMPONENT", "ELLIPSE"]);
@@ -358633,11 +358851,11 @@ function mapCompilerMessages(messages, offset, lines2) {
358633
358851
  for (const m of messages) {
358634
358852
  if (m.type !== "error" && m.type !== "warning") continue;
358635
358853
  const line = m.lineNum - offset;
358636
- const inside2 = m.lineNum > 0 && line >= 1 && line <= lines2;
358854
+ const inside3 = m.lineNum > 0 && line >= 1 && line <= lines2;
358637
358855
  out.push({
358638
- line: inside2 ? line : null,
358639
- column: inside2 && m.linePos > 0 ? m.linePos : null,
358640
- 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),
358641
358859
  severity: m.type === "error" ? "error" : "warning"
358642
358860
  });
358643
358861
  }
@@ -359152,9 +359370,9 @@ var init_motion_rich = __esm({
359152
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.");
359153
359371
  const layer2 = nodeOrThrow(doc2, op4.layer);
359154
359372
  const owner = target2.type === "COMPONENT_SET" ? target2.id : target2.id;
359155
- let inside2 = false;
359156
- for (let n = layer2; n; n = n.parentId ? doc2.nodes[n.parentId] : void 0) if (n.id === owner) inside2 = true;
359157
- 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}".`);
359158
359376
  const props = layerInputPatch(layer2, op4.name, op4.path);
359159
359377
  if (!props) throw new ToolError(`"${layer2.name}" has no ${op4.path}.`);
359160
359378
  commitUpdates(ctx2, doc2, /* @__PURE__ */ new Map([[layer2.id, props]]));
@@ -361284,7 +361502,14 @@ var init_media = __esm({
361284
361502
  },
361285
361503
  run: async (args2) => {
361286
361504
  const { listLibraryMedia: listLibraryMedia2 } = await Promise.resolve().then(() => (init_client5(), client_exports3));
361287
- 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);
361288
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 } };
361289
361514
  }
361290
361515
  });
@@ -401284,7 +401509,6 @@ function placeDesign(doc2, built, source3, size6, name2, at4) {
401284
401509
  var DESIGN_NAMESPACES, DERIVED2, templateOfBuild;
401285
401510
  var init_build3 = __esm({
401286
401511
  "lib/agent/design-script/build.ts"() {
401287
- "use strict";
401288
401512
  init_hash();
401289
401513
  init_scene_graph();
401290
401514
  init_libraries();
@@ -401314,7 +401538,6 @@ function designFrameOf(doc2, id3) {
401314
401538
  }
401315
401539
  var init_locate = __esm({
401316
401540
  "lib/agent/design-script/locate.ts"() {
401317
- "use strict";
401318
401541
  }
401319
401542
  });
401320
401543
 
@@ -401861,6 +402084,10 @@ async function drive(meta3, start2) {
401861
402084
  return [...notes, ...runtime.drainParentNotifications()];
401862
402085
  },
401863
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,
401864
402091
  maxRounds: TURN_MAX_ROUNDS,
401865
402092
  modelRetries: 3,
401866
402093
  isRecoverable: (err2) => !abort.signal.aborted && isInterruption(err2),
@@ -401986,7 +402213,7 @@ async function drive(meta3, start2) {
401986
402213
  useAgentStore.setState({ running: false, resuming: false, abort: null });
401987
402214
  }
401988
402215
  }
401989
- 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;
401990
402217
  var init_runner = __esm({
401991
402218
  "lib/agent/runner.ts"() {
401992
402219
  init_editor_store();
@@ -402012,6 +402239,7 @@ var init_runner = __esm({
402012
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"];
402013
402240
  MAX_HISTORY_MESSAGES = 24;
402014
402241
  MAX_PARALLEL_TOOLS = 4;
402242
+ MAX_TOOL_RESULT_CHARS = 48e3;
402015
402243
  TURN_MAX_ROUNDS = 500;
402016
402244
  registerTurnTools = (deps) => {
402017
402245
  registerBuiltinTools();
@@ -407377,7 +407605,6 @@ __export(editor_store_exports, {
407377
407605
  rebuildVectorData: () => rebuildVectorData,
407378
407606
  recoverPointsFromData: () => recoverPointsFromData,
407379
407607
  resetLibraryDocuments: () => resetLibraryDocuments,
407380
- scaleChildrenRecursively: () => scaleChildrenRecursively,
407381
407608
  tabPrefsOnly: () => tabPrefsOnly,
407382
407609
  useActiveTool: () => useActiveTool,
407383
407610
  useCamera: () => useCamera,
@@ -407550,37 +407777,6 @@ function stripImagePlaceholderFills(nodes, prevNodes, changed) {
407550
407777
  }
407551
407778
  return out ?? nodes;
407552
407779
  }
407553
- function rebuildVectorData(points, closed) {
407554
- if (points.length < 2) return [];
407555
- const data = [];
407556
- data.push("M", points[0].x, points[0].y);
407557
- for (let i2 = 1; i2 < points.length; i2++) {
407558
- const prev = points[i2 - 1];
407559
- const curr = points[i2];
407560
- if (prev.handleOut && curr.handleIn) {
407561
- data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407562
- } else if (prev.handleOut) {
407563
- data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
407564
- } else if (curr.handleIn) {
407565
- data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407566
- } else {
407567
- data.push("L", curr.x, curr.y);
407568
- }
407569
- }
407570
- if (closed) {
407571
- const prev = points[points.length - 1];
407572
- const curr = points[0];
407573
- if (prev.handleOut && curr.handleIn) {
407574
- data.push("C", prev.handleOut.x, prev.handleOut.y, curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407575
- } else if (prev.handleOut) {
407576
- data.push("Q", prev.handleOut.x, prev.handleOut.y, curr.x, curr.y);
407577
- } else if (curr.handleIn) {
407578
- data.push("Q", curr.handleIn.x, curr.handleIn.y, curr.x, curr.y);
407579
- }
407580
- data.push("Z");
407581
- }
407582
- return data;
407583
- }
407584
407780
  function fixCorruptedVectors(doc2) {
407585
407781
  let changed = false;
407586
407782
  const newNodes = { ...doc2.nodes };
@@ -407730,30 +407926,6 @@ function recoverPointsFromData(data) {
407730
407926
  }
407731
407927
  return points;
407732
407928
  }
407733
- function scaleChildrenRecursively(doc2, parentId, scaleX, scaleY) {
407734
- const parent = doc2.nodes[parentId];
407735
- if (!parent || !parent.childIds) return doc2;
407736
- const newDoc = { ...doc2, nodes: { ...doc2.nodes } };
407737
- const processNode = (nodeId2) => {
407738
- const node2 = newDoc.nodes[nodeId2];
407739
- if (!node2) return;
407740
- const updates = {
407741
- x: node2.x * scaleX,
407742
- y: node2.y * scaleY,
407743
- width: node2.width * scaleX,
407744
- height: node2.height * scaleY
407745
- };
407746
- if (node2.type === "TEXT") {
407747
- updates.fontSize = (node2.fontSize ?? 16) * Math.min(scaleX, scaleY);
407748
- }
407749
- newDoc.nodes[nodeId2] = { ...node2, ...updates };
407750
- if (node2.childIds) {
407751
- node2.childIds.forEach(processNode);
407752
- }
407753
- };
407754
- parent.childIds.forEach(processNode);
407755
- return newDoc;
407756
- }
407757
407929
  function placeInstance(document2, mainComponentId, pageId2, x2, y) {
407758
407930
  const main2 = document2.nodes[mainComponentId];
407759
407931
  if (!main2 || main2.type !== "COMPONENT") return null;
@@ -409360,10 +409532,10 @@ var init_editor_store = __esm({
409360
409532
  if ("childIds" in node2 && node2.childIds.length > 0) {
409361
409533
  newDoc = applyConstraints(newDoc, id3, node2.width, node2.height, newWidth, newHeight);
409362
409534
  }
409363
- if (node2.type === "GROUP" && node2.width > 0 && node2.height > 0) {
409364
- const scaleX = newWidth / node2.width;
409365
- const scaleY = newHeight / node2.height;
409366
- 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
+ }
409367
409539
  } else if (node2.type === "TABLE" && node2.width > 0 && node2.height > 0) {
409368
409540
  const table3 = node2;
409369
409541
  const totalW = (table3.colWidths || []).reduce((a, b) => a + b, 0) || 1;
@@ -410372,23 +410544,35 @@ var init_editor_store = __esm({
410372
410544
  const geo = multiPolyToVectorGeometry(mp);
410373
410545
  if (!geo) return;
410374
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);
410375
410558
  const vectorNode = createVector({
410376
410559
  ...geo,
410377
- 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 }],
410378
410561
  strokes: [],
410379
410562
  fillRule: "EVENODD",
410380
410563
  name: state.selection.length > 1 ? "Flattened" : `${first2?.name ?? "Vector"} (flattened)`
410381
410564
  });
410382
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
+ }
410383
410571
  state.selection.forEach((id3) => {
410384
410572
  if (doc2.nodes[id3]) doc2 = removeNode(doc2, id3);
410385
410573
  });
410386
- const command = createCommand("FLATTEN", "Flatten selection", documentBefore, doc2);
410387
- set4({
410388
- document: doc2,
410389
- history: pushCommand(state.history, command),
410390
- selection: [vectorNode.id]
410391
- });
410574
+ if (documentBefore !== doc2) get4().commitDocument(doc2, "Flatten selection");
410575
+ set4({ selection: [vectorNode.id] });
410392
410576
  },
410393
410577
  joinPaths: () => {
410394
410578
  const state = get4();
@@ -414100,7 +414284,7 @@ init_resources();
414100
414284
  init_desktop();
414101
414285
  init_hypery();
414102
414286
  import { createInterface } from "node:readline";
414103
- var VERSION2 = "0.53.0";
414287
+ var VERSION2 = "0.53.2";
414104
414288
  var HELP = `popcraft ${VERSION2} \u2014 the PopCraft command line
414105
414289
 
414106
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.0",
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.0"
37
+ "@popcraft/kit": "0.53.2"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node build.mjs",