boxglow 0.6.7 → 0.6.8

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.
package/README.md CHANGED
@@ -11,6 +11,18 @@ Tasks are boxes with inputs and outputs; a box goes New (BlackBox: output decide
11
11
  - CLI for agents: `npx boxglow` (status / start / done / split / ask / answer ...)
12
12
  - License: MIT
13
13
 
14
+ ## What's new in 0.6.8
15
+
16
+ - **Find a box and zoom to it**: Ctrl+F (Cmd+F on a Mac) jumps to the search box from anywhere. Picking a result while zoomed out too far to read titles zooms until the whole box fits, and the new target button next to the box's ID in the side panel does the same on demand. A ring blinks around the box.
17
+ - **Very large plans are much faster**: in a tab with about 1,000 boxes, selecting a box took 12 seconds and switching to Edit about 2.5 minutes; they now take about 0.4 and 2 seconds. On a plan with about 1,200 boxes every edit takes about half a second (5 to 6 seconds before), and zooming is about three times lighter.
18
+
19
+ Details: [CHANGELOG.md](CHANGELOG.md). Update the CLI, `serve` and the VS Code extension (0.5.8) together.
20
+
21
+ ### 日本語: 0.6.8 の要点
22
+
23
+ - **箱を探して寄る**: Ctrl+F (Mac は Cmd+F) で、どこからでも検索欄へ移る。題名が読めないほど縮小しているときは、検索で選んだ箱全体が収まるまで寄る。右パネルの ID の横にある照準のボタンでも、選んだ箱へ寄る。寄った箱のまわりで輪が点滅する。
24
+ - **とても大きな計画が速くなった**: 箱が約 1000 個のタブで、箱を選ぶのに 12 秒、Edit への切り替えに約 2.5 分かかっていたのを、約 0.4 秒と約 2 秒にした。箱が約 1200 個の計画で、編集 1 回が約 0.5 秒 (以前は 5〜6 秒) になり、倍率を変えたときの重さも約 3 分の 1 になった。
25
+
14
26
  ## What's new in 0.6.7
15
27
 
16
28
  - **Large plans respond faster**: on a plan with about 480 boxes, opening the file, switching tabs, selecting a box, typing in the search box and editing now mostly take under a second (they took 1 to 7 seconds).
@@ -274,7 +286,7 @@ Writes become new versions on the server and reach the synced devices on their n
274
286
 
275
287
  ## VS Code extension
276
288
 
277
- Install **Boxglow** from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=sukima.boxglow-vscode): search for "Boxglow" in the Extensions view, or run `code --install-extension sukima.boxglow-vscode`. Keep the extension and the CLI on matching releases (extension 0.5.7 goes with boxglow 0.6.7).
289
+ Install **Boxglow** from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=sukima.boxglow-vscode): search for "Boxglow" in the Extensions view, or run `code --install-extension sukima.boxglow-vscode`. Keep the extension and the CLI on matching releases (extension 0.5.8 goes with boxglow 0.6.8).
278
290
 
279
291
  `vscode/` holds the extension's source. It opens `boxglow.json` as the same diagram inside the editor (custom editor "Boxglow"; right-click the file or use the editor title button / command *Boxglow: Open boxglow.json as a diagram*). The diagram follows the file when the CLI or an agent writes it, edits in the diagram are written back, and light / dark follow the VS Code theme. Deliverables, materials and relative links in Notes (Ctrl / Cmd + click) that point to a file open in the editor beside the diagram. To build it yourself: `npm run build:vscode` (bundles the web app into `vscode/media/`), then `cd vscode && npm run package` (`.vsix`, install via *Extensions: Install from VSIX...*).
280
292
 
@@ -332,7 +344,7 @@ Expanded boxes (containers) are drawn as frames: the shallower the level, the th
332
344
  - Readiness: "the inputs are in" and "the work is concrete enough to start" are judged separately. An output carries a planned deliverable (`expect`, e.g. `file:src/auth/callback.ts`; `note` for an investigation) next to the real deliverables, and the acceptance criteria live in the scope. `boxglow context` prints the `readiness` (ready / needs detail / waiting, with reasons and the next commands), candidates are grouped the same way, and the canvas, side panel and In charge table show "needs detail". `start` warns on such a box (`policy --unprepared reject` refuses AI starts; a reason does not bypass it), and `boxglow claim <block>` holds a box for planning without starting it. `split` takes `expect` / `acceptance` per child (only for the ones you start now) and `parentMakes` for outputs the parent makes itself.
333
345
  - Branches: a branch box (+ Block → Branch, or `boxglow branch "<title>" --options "A|B"`) shows a fork that is not decided yet (an existing box can be turned into one from the Actions row on its More tab, or `boxglow branch --box <block>`). Each option is a path (output). Boxes on the paths wait for the answer; once you pick a path (Choose a path at the top of the side panel, or `boxglow answer`), the paths not chosen are skipped (faded, left out of progress, next candidates and lists). A branch is drawn as a card with cut corners and an "IF" diamond on its edge. Where paths come together, a merge part drawn like an OR gate (+ Block → Merge, or `boxglow join`) passes the work on as soon as any one path arrives.
334
346
  - Edit / View: a top-bar toggle; in View mode drag-and-drop editing and the Delete key are off. View lays boxes out in dependency order as numbered stages (left to right, or top to bottom with the vertical view in the zoom panel); Edit shows and edits the layout saved in the file. You cannot zoom out further than the whole diagram (Fit), and selecting a box or opening a panel keeps your zoom and position.
335
- - Short IDs: every box gets `B1`, `B2`, ... shown on the box; search by ID or title from the top bar (`/`), and use the ID in the CLI.
347
+ - Short IDs: every box gets `B1`, `B2`, ... shown on the box; search by ID or title from the top bar (Ctrl+F or `/`), and use the ID in the CLI. The target button next to the ID in the side panel zooms to the box.
336
348
  - Schedule: start date, due date, estimate and actual hours per box; overdue boxes are flagged.
337
349
  - One source of truth: an input's description is the description of the output it is connected to (edit it there); only unconnected inputs carry their own text.
338
350
  - Progress: set a percentage per box (slider in the inspector or `boxglow set <block> --progress 60`); parents average their children, WhiteBox counts as 100.
package/bin/boxglow.js CHANGED
@@ -970,6 +970,8 @@ var inspector = {
970
970
  "\u300C{name}\u300D\u3092\u90E8\u54C1\u3068\u3057\u3066\u4FDD\u5B58\u3057\u307E\u3057\u305F (\u2630 \u306E\u300C\u90E8\u54C1\u300D\u304B\u3089\u633F\u5165)": 'Saved "{name}" as a part (insert it from Parts in \u2630)',
971
971
  "\u77ED\u3044 ID (\u62BC\u3059\u3068\u30B3\u30D4\u30FC\u3002\u691C\u7D22\u3084 CLI \u3067\u4F7F\u3048\u307E\u3059)": "Short ID (click to copy; works in search and the CLI)",
972
972
  "ID {key} \u3092\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F": "Copied ID {key}",
973
+ "\u56F3\u3067\u3053\u306E\u7BB1\u306B\u5BC4\u308B (\u7BB1\u5168\u4F53\u304C\u753B\u9762\u306B\u53CE\u307E\u308B\u4F4D\u7F6E\u3078)": "Zoom to this box on the canvas (fit the whole box)",
974
+ "\u56F3\u3067\u3053\u306E\u7BB1\u306B\u5BC4\u308B": "Zoom to this box",
973
975
  "Title (\u4F55\u3092\u4F5C\u308B\u304B)": "Title (what to make)",
974
976
  "\u72B6\u614B\u30FB\u9032\u6357\u30FB\u6D3B\u52D5\u30FB\u5224\u65AD": "Status, progress, activity, decisions",
975
977
  "\u5165\u529B\u3068\u51FA\u529B (\u6210\u679C\u7269)": "Inputs and outputs (deliverables)",
@@ -1261,6 +1263,8 @@ var app = {
1261
1263
  "\u524A\u9664": "Delete",
1262
1264
  "\u5143\u306B\u623B\u3059 / \u3084\u308A\u76F4\u3059": "Undo / Redo",
1263
1265
  "\u9078\u629E\u3092\u89E3\u9664": "Clear selection",
1266
+ "\u7BB1\u3092\u63A2\u3059": "Find a box",
1267
+ "(\u53F3\u30D1\u30CD\u30EB\u306E\u7167\u6E96\u306E\u30DC\u30BF\u30F3\u3067\u3001\u305D\u306E\u7BB1\u306B\u5BC4\u308B)": "(the target button in the side panel zooms to the box)",
1264
1268
  "\u30DC\u30C3\u30AF\u30B9\u306F\u300C\u5165\u529B\u304B\u3089\u51FA\u529B\u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002\u51FA\u529B\u3092\u5148\u306B\u6C7A\u3081\u3001\u5927\u304D\u306A\u30DC\u30C3\u30AF\u30B9\u306F\u300C\u5206\u89E3\u3059\u308B\u300D\u3067\u4E2D\u306B\u30DC\u30C3\u30AF\u30B9\u3092\u7F6E\u304F\u3002\u4F9B\u7D66\u5143\u306E\u7121\u3044\u5165\u529B\u306F\u5DE6\u7AEF\u306E\u5165\u529B\u307E\u3067\u81EA\u52D5\u3067\u70B9\u7DDA\u304C\u4F38\u3073\u308B\u3002": "A box is a task that turns inputs into an output. Decide the output first; break big boxes down by putting boxes inside. Inputs with no source get a dotted line from the left edge automatically.",
1265
1269
  "Boxglow \u3067\u958B\u304F": "Open in Boxglow",
1266
1270
  "\u30D3\u30EB\u30C9\u65E5\u6642 (\u65E5\u672C\u6642\u9593)\u3002\u53E4\u3044\u5834\u5408\u306F\u518D\u8AAD\u307F\u8FBC\u307F (Ctrl+F5) \u3057\u3066\u304F\u3060\u3055\u3044": "Build time (JST). If it looks old, reload with Ctrl+F5",
@@ -1275,6 +1279,7 @@ var app = {
1275
1279
  "Edit \u30E2\u30FC\u30C9: \u30C9\u30E9\u30C3\u30B0\u3067\u79FB\u52D5\u30FB\u7D50\u7DDA\u30FB\u968E\u5C64\u79FB\u52D5\u304C\u3067\u304D\u307E\u3059\u3002\u62BC\u3059\u3068 View (\u95B2\u89A7) \u306B": "Edit mode: drag to move, wire and re-parent. Click to switch to View",
1276
1280
  "View \u30E2\u30FC\u30C9: \u30C9\u30E9\u30C3\u30B0\u3067\u306E\u7DE8\u96C6\u306F\u52B9\u304D\u307E\u305B\u3093\u3002\u62BC\u3059\u3068 Edit \u306B": "View mode: dragging does not edit. Click to switch to Edit",
1277
1281
  "Search ID / \u984C\u540D": "Search ID / title",
1282
+ "ID \u304B\u984C\u540D\u3067\u7BB1\u3092\u63A2\u3059 (Ctrl+F \u307E\u305F\u306F /)": "Find a box by ID or title (Ctrl+F or /)",
1278
1283
  "\u898B\u3064\u304B\u308A\u307E\u305B\u3093": "No matches",
1279
1284
  "\u30D6\u30ED\u30C3\u30AF\u3092\u8FFD\u52A0 (N)\u3002\u30DC\u30C3\u30AF\u30B9\u3092\u9078\u3093\u3067\u3044\u308C\u3070\u305D\u306E\u4E2D\u306B\u3001\u9078\u3093\u3067\u3044\u306A\u3051\u308C\u3070\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u4E2D\u306B": "Add a box (N): inside the selected box, or in the project if nothing is selected",
1280
1285
  "Auto Layout: \u4F9D\u5B58\u95A2\u4FC2\u3067\u4E26\u3079\u76F4\u3059 (\u5927\u9805\u76EE\u306F\u7573\u3093\u3060\u524D\u63D0)": "Auto Layout: arrange by dependencies (top-level boxes assumed collapsed)",
@@ -3083,9 +3088,9 @@ function customAlphabet(alphabet, defaultSize = 21) {
3083
3088
  while (accepted < target) {
3084
3089
  fillRandom(randomBytes);
3085
3090
  for (let i = 0; i < randomBytes.length; i++) {
3086
- let index2 = randomBytes[i] & mask;
3087
- if (index2 < alphabetLen) {
3088
- buffer[accepted++] = charCodes[index2];
3091
+ let index = randomBytes[i] & mask;
3092
+ if (index < alphabetLen) {
3093
+ buffer[accepted++] = charCodes[index];
3089
3094
  if (accepted === target) break;
3090
3095
  }
3091
3096
  }
@@ -3238,11 +3243,86 @@ function createArtifact(title, url = "") {
3238
3243
  function createGitArtifact(title, git2) {
3239
3244
  return { id: newId(), title, url: git2.url ?? "", kind: "git", note: "", repo: git2.repo, path: git2.path, commit: git2.commit, blob: git2.blob, state: "ok", checkedAt: now() };
3240
3245
  }
3241
- var index = null;
3246
+ var indexes = [];
3242
3247
  var endpointKey = (ep) => `${ep.portId}
3243
3248
  ${ep.side}`;
3249
+ function buildIndex(p, opts) {
3250
+ const portsByBlock = /* @__PURE__ */ new Map();
3251
+ for (const q of Object.values(p.ports)) {
3252
+ const list2 = portsByBlock.get(q.blockId);
3253
+ if (list2) list2.push(q);
3254
+ else portsByBlock.set(q.blockId, [q]);
3255
+ }
3256
+ const kidsByParent = /* @__PURE__ */ new Map();
3257
+ const kidIds = opts.kidIds ? /* @__PURE__ */ new Map() : null;
3258
+ if (opts.children !== false) {
3259
+ for (const b of Object.values(p.blocks)) {
3260
+ if (b.parentId === null) continue;
3261
+ const list2 = kidsByParent.get(b.parentId);
3262
+ if (list2) list2.push(b);
3263
+ else kidsByParent.set(b.parentId, [b]);
3264
+ if (kidIds) {
3265
+ const ids = kidIds.get(b.parentId);
3266
+ if (ids) ids.push(b.id);
3267
+ else kidIds.set(b.parentId, [b.id]);
3268
+ }
3269
+ }
3270
+ }
3271
+ const edgesTo = /* @__PURE__ */ new Map();
3272
+ const edgesFrom = /* @__PURE__ */ new Map();
3273
+ if (opts.edges) {
3274
+ for (const e of Object.values(p.edges)) {
3275
+ const to = endpointKey(e.to), from = endpointKey(e.from);
3276
+ const tl = edgesTo.get(to);
3277
+ if (tl) tl.push(e);
3278
+ else edgesTo.set(to, [e]);
3279
+ const fl = edgesFrom.get(from);
3280
+ if (fl) fl.push(e);
3281
+ else edgesFrom.set(from, [e]);
3282
+ }
3283
+ }
3284
+ return { ports: p.ports, portsByBlock, blocks: opts.children !== false ? p.blocks : null, kidsByParent, kidIds, edges: opts.edges ? p.edges : null, edgesTo, edgesFrom, memo: opts.memo ? /* @__PURE__ */ new Map() : null };
3285
+ }
3286
+ function usingIndex(ix, fn) {
3287
+ indexes.push(ix);
3288
+ try {
3289
+ return fn();
3290
+ } finally {
3291
+ indexes.pop();
3292
+ }
3293
+ }
3294
+ var VIEW_INDEX = /* @__PURE__ */ new WeakMap();
3295
+ function withViewIndex(p, fn) {
3296
+ let ix = VIEW_INDEX.get(p);
3297
+ if (!ix) {
3298
+ ix = buildIndex(p, { children: true, edges: true, memo: true });
3299
+ VIEW_INDEX.set(p, ix);
3300
+ }
3301
+ return usingIndex(ix, fn);
3302
+ }
3303
+ function withMoveIndex(p, fn) {
3304
+ return usingIndex(buildIndex(p, { children: true, edges: true, kidIds: true }), fn);
3305
+ }
3306
+ function viewMemo(p, name) {
3307
+ for (let i = indexes.length - 1; i >= 0; i--) {
3308
+ const ix = indexes[i];
3309
+ if (ix.memo && ix.ports === p.ports && ix.blocks === p.blocks && ix.edges === p.edges) {
3310
+ let m = ix.memo.get(name);
3311
+ if (!m) {
3312
+ m = /* @__PURE__ */ new Map();
3313
+ ix.memo.set(name, m);
3314
+ }
3315
+ return m;
3316
+ }
3317
+ }
3318
+ return null;
3319
+ }
3244
3320
  function childrenOf(p, blockId) {
3245
- if (index && index.blocks === p.blocks) return (index.kidsByParent.get(blockId) ?? []).slice();
3321
+ for (let i = indexes.length - 1; i >= 0; i--) {
3322
+ const ix = indexes[i];
3323
+ if (ix.blocks === p.blocks) return (ix.kidsByParent.get(blockId) ?? []).slice();
3324
+ if (ix.kidIds && ix.ports === p.ports && ix.edges === p.edges) return (ix.kidIds.get(blockId) ?? []).map((id) => p.blocks[id]).filter((b) => !!b);
3325
+ }
3246
3326
  return Object.values(p.blocks).filter((b) => b.parentId === blockId);
3247
3327
  }
3248
3328
  function descendantsOf(p, blockId) {
@@ -3269,19 +3349,20 @@ function ancestorsOf(p, blockId) {
3269
3349
  return out2;
3270
3350
  }
3271
3351
  function portsOf(p, blockId, direction) {
3272
- if (index && index.ports === p.ports) {
3273
- const list2 = index.portsByBlock.get(blockId) ?? [];
3352
+ for (let i = indexes.length - 1; i >= 0; i--) {
3353
+ if (indexes[i].ports !== p.ports) continue;
3354
+ const list2 = indexes[i].portsByBlock.get(blockId) ?? [];
3274
3355
  return direction === void 0 ? list2.slice() : list2.filter((q) => q.direction === direction);
3275
3356
  }
3276
3357
  return Object.values(p.ports).filter((q) => q.blockId === blockId && (direction === void 0 || q.direction === direction));
3277
3358
  }
3278
3359
  var sameEndpoint = (a, b) => a.portId === b.portId && a.side === b.side;
3279
3360
  function incomingEdges(p, ep) {
3280
- if (index && index.edges === p.edges) return (index.edgesTo.get(endpointKey(ep)) ?? []).slice();
3361
+ for (let i = indexes.length - 1; i >= 0; i--) if (indexes[i].edges === p.edges) return (indexes[i].edgesTo.get(endpointKey(ep)) ?? []).slice();
3281
3362
  return Object.values(p.edges).filter((e) => sameEndpoint(e.to, ep));
3282
3363
  }
3283
3364
  function outgoingEdges(p, ep) {
3284
- if (index && index.edges === p.edges) return (index.edgesFrom.get(endpointKey(ep)) ?? []).slice();
3365
+ for (let i = indexes.length - 1; i >= 0; i--) if (indexes[i].edges === p.edges) return (indexes[i].edgesFrom.get(endpointKey(ep)) ?? []).slice();
3285
3366
  return Object.values(p.edges).filter((e) => sameEndpoint(e.from, ep));
3286
3367
  }
3287
3368
  function scopeOf(p, ep) {
@@ -6312,7 +6393,7 @@ function resolveToken(server, allowEnvironmentToken = true) {
6312
6393
  import { hostname as hostname2 } from "node:os";
6313
6394
 
6314
6395
  // package.json
6315
- var version = "0.6.7";
6396
+ var version = "0.6.8";
6316
6397
 
6317
6398
  // src/model/version.ts
6318
6399
  var APP_VERSION = version;
@@ -13972,39 +14053,10 @@ function metaRowWidth(opts) {
13972
14053
  w += opts.keyLen * 7 + 8;
13973
14054
  return w;
13974
14055
  }
13975
- function taskCardPorts(p, blockId, direction) {
13976
- const ports = portsOf(p, blockId, direction);
13977
- if (ports.length < 2) return ports;
13978
- const absoluteY = (id) => {
13979
- let y = 0, current3 = id;
13980
- while (current3 && current3 !== ROOT_ID) {
13981
- const b = p.blocks[current3];
13982
- if (!b) break;
13983
- y += b.position.y;
13984
- current3 = b.parentId;
13985
- }
13986
- return y;
13987
- };
13988
- const rows = ports.map((port2, index2) => {
13989
- const edges = direction === "in" ? incomingEdges(p, { portId: port2.id, side: "outer" }) : outgoingEdges(p, { portId: port2.id, side: "outer" });
13990
- const peerY = (portId) => {
13991
- const peer = p.ports[portId];
13992
- if (!peer) return void 0;
13993
- const inside = isExpanded(p, peer.blockId) ? (peer.direction === "out" ? incomingEdges(p, { portId, side: "inner" }).map((e) => e.from.portId) : outgoingEdges(p, { portId, side: "inner" }).map((e) => e.to.portId)).map((id) => p.ports[id]?.blockId).filter((id) => !!id && id !== peer.blockId) : [];
13994
- return inside.length ? inside.map(absoluteY).reduce((a, b) => a + b, 0) / inside.length : absoluteY(peer.blockId);
13995
- };
13996
- const ys = edges.map((e) => peerY(direction === "in" ? e.from.portId : e.to.portId)).filter((y) => y !== void 0);
13997
- return { port: port2, index: index2, y: ys.length ? ys.reduce((a, b) => a + b, 0) / ys.length : Infinity };
13998
- });
13999
- return rows.sort((a, b) => (a.y === b.y ? 0 : a.y - b.y) || a.index - b.index).map((row) => row.port);
14000
- }
14001
14056
  var MERGE_W = 136;
14002
14057
  var MERGE_MIN_H = 80;
14003
14058
  var MERGE_ROW = 32;
14004
- function taskCardLayout(p, blockId) {
14005
- const b = p.blocks[blockId];
14006
- const ins = taskCardPorts(p, blockId, "in");
14007
- const outs = taskCardPorts(p, blockId, "out");
14059
+ function cardLayoutOf(b, ins, outs) {
14008
14060
  if (b?.merge) {
14009
14061
  const height = Math.max(MERGE_MIN_H, ins.length * MERGE_ROW + 16);
14010
14062
  const step = (height - 16) / Math.max(1, ins.length);
@@ -14030,12 +14082,43 @@ function taskCardLayout(p, blockId) {
14030
14082
  const outputs = rows(outs);
14031
14083
  return { width, height: cursor + 8, headerH, metaH, inputs, outputs };
14032
14084
  }
14085
+ function cardSize(p, blockId) {
14086
+ const l = cardLayoutOf(p.blocks[blockId], portsOf(p, blockId, "in"), portsOf(p, blockId, "out"));
14087
+ return { width: l.width, height: l.height, headerH: l.headerH };
14088
+ }
14033
14089
  function isExpanded(p, blockId) {
14034
14090
  const b = p.blocks[blockId];
14035
14091
  return !!b && !b.collapsed && childrenOf(p, blockId).length > 0;
14036
14092
  }
14037
14093
  function blockSize(p, blockId) {
14038
- if (!isExpanded(p, blockId)) return taskCardLayout(p, blockId);
14094
+ const memo = viewMemo(p, `blockSize:${getLang()}`);
14095
+ if (!memo) return blockSizeWith(p, blockId, blockSize);
14096
+ let hit = memo.get(blockId);
14097
+ if (!hit) {
14098
+ hit = blockSizeWith(p, blockId, blockSize);
14099
+ memo.set(blockId, hit);
14100
+ }
14101
+ return hit;
14102
+ }
14103
+ function memoBlockSize() {
14104
+ const memo = /* @__PURE__ */ new WeakMap();
14105
+ const size = (p, blockId) => {
14106
+ let m = memo.get(p);
14107
+ if (!m) {
14108
+ m = /* @__PURE__ */ new Map();
14109
+ memo.set(p, m);
14110
+ }
14111
+ let hit = m.get(blockId);
14112
+ if (!hit) {
14113
+ hit = blockSizeWith(p, blockId, size);
14114
+ m.set(blockId, hit);
14115
+ }
14116
+ return hit;
14117
+ };
14118
+ return size;
14119
+ }
14120
+ function blockSizeWith(p, blockId, kidSize) {
14121
+ if (!isExpanded(p, blockId)) return cardSize(p, blockId);
14039
14122
  const b = p.blocks[blockId];
14040
14123
  const insP = portsOf(p, blockId, "in");
14041
14124
  const outsP = portsOf(p, blockId, "out");
@@ -14071,7 +14154,7 @@ function blockSize(p, blockId) {
14071
14154
  let right = 0;
14072
14155
  let bottom = 0;
14073
14156
  for (const c of kids) {
14074
- const s = blockSize(p, c.id);
14157
+ const s = kidSize(p, c.id);
14075
14158
  right = Math.max(right, c.position.x + s.width);
14076
14159
  bottom = Math.max(bottom, c.position.y + s.height);
14077
14160
  }
@@ -14087,6 +14170,14 @@ function terminalHeight(p, which) {
14087
14170
  return HEADER_H + Math.max(n, 1) * ROW_H + PAD_BOTTOM;
14088
14171
  }
14089
14172
 
14173
+ // src/model/overlaps.ts
14174
+ function resolveEditOverlaps(before, after) {
14175
+ return withViewIndex(before, () => withMoveIndex(after, () => resolveChangedOverlaps(before, after, memoBlockSize())));
14176
+ }
14177
+ function resolveShownOverlaps(p) {
14178
+ return withMoveIndex(p, () => resolveAllOverlaps(p, memoBlockSize()));
14179
+ }
14180
+
14090
14181
  // cli/main.ts
14091
14182
  import { dirname as dirname11, join as join13, resolve as resolve7 } from "node:path";
14092
14183
 
@@ -14631,7 +14722,7 @@ function save(path, p) {
14631
14722
  let text2 = toJSON(normalized) + "\n";
14632
14723
  const warnings = [];
14633
14724
  const revision = planIO().commit(path, text2, expected, { prepare: (current3) => {
14634
- const next = current3 ? resolveChangedOverlaps(fromJSON(current3), normalized, blockSize) : resolveAllOverlaps(normalized, blockSize);
14725
+ const next = current3 ? resolveEditOverlaps(fromJSON(current3), normalized) : resolveShownOverlaps(normalized);
14635
14726
  text2 = prepareClaimSave(current3, toJSON(next) + "\n", claimRequest, (warning) => warnings.push(warning));
14636
14727
  return text2;
14637
14728
  } });