flowfig 0.8.2 → 0.8.3

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
@@ -65,7 +65,7 @@ jobs:
65
65
  - uses: actions/setup-node@v4
66
66
  with:
67
67
  node-version: 22
68
- - uses: iamalvisng/flowfig@v0.8.2
68
+ - uses: iamalvisng/flowfig@v0.8.3
69
69
  with:
70
70
  figures: 'docs/**/*.svg' # default **/*.svg
71
71
  ```
@@ -478,7 +478,7 @@ jobs:
478
478
  - uses: actions/setup-node@v4
479
479
  with:
480
480
  node-version: 22
481
- - uses: iamalvisng/flowfig@v0.8.2
481
+ - uses: iamalvisng/flowfig@v0.8.3
482
482
  with:
483
483
  figures: 'docs/**/*.svg' # default **/*.svg
484
484
  ```
package/dist/code.d.ts CHANGED
@@ -18,7 +18,7 @@ export declare function matchClose(code: string, open: number): number;
18
18
  export declare function pyBody(code: string, start: number): Extent;
19
19
  export declare function outside(root: string, path: string): boolean;
20
20
  export declare function codeFile(root: string, path: string, read?: Read, cache?: Map<string, CodeFile | null>): CodeFile | null;
21
- export declare function locate(file: CodeFile, symbol: string): {
21
+ export declare const locate: (file: CodeFile, symbol: string) => {
22
22
  start: number;
23
23
  end: number;
24
24
  } | null;
package/dist/code.js CHANGED
@@ -511,7 +511,7 @@ export function codeFile(root, path, read = readFile, cache = new Map()) {
511
511
  cache.set(full, file);
512
512
  return file;
513
513
  }
514
- export function locate(file, symbol) {
514
+ function locateDefinition(file, symbol) {
515
515
  const { code, lang } = file;
516
516
  const parts = symbol.split('.');
517
517
  if (!parts.every((p) => /^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(p)))
@@ -565,4 +565,33 @@ export function locate(file, symbol) {
565
565
  }
566
566
  return null;
567
567
  }
568
+ function enumMember(file, symbol) {
569
+ const { code, lang } = file;
570
+ const [owner, name, ...rest] = symbol.split('.');
571
+ if (!name || rest.length || lang === 'go' || lang === 'py')
572
+ return null;
573
+ const head = new RegExp(`\\benum\\s+${esc(owner)}${E}[^;{}]*\\{`, 'u').exec(code);
574
+ if (!head)
575
+ return null;
576
+ const open = head.index + head[0].length - 1;
577
+ const close = matchClose(code, open);
578
+ let from = open + 1;
579
+ for (let i = from, d = 0; i <= close; i++) {
580
+ const c = code[i];
581
+ if ('{(['.includes(c))
582
+ d++;
583
+ else if (')]'.includes(c) || (c === '}' && i < close))
584
+ d--;
585
+ else if (d > 0 || (c !== ',' && c !== ';' && i < close))
586
+ continue;
587
+ const seg = code.slice(from, i).replace(/^(\s|@\w+(\([^)]*\))?|#\[[^\]]*\]|\[[^\]]*\])+/, '');
588
+ if (new RegExp(`^${esc(name)}${E}`, 'u').test(seg))
589
+ return { start: i - seg.length, end: i };
590
+ if (c === ';')
591
+ return null;
592
+ from = i + 1;
593
+ }
594
+ return null;
595
+ }
596
+ export const locate = (file, symbol) => locateDefinition(file, symbol) ?? enumMember(file, symbol);
568
597
  export const isDefined = (file, symbol) => locate(file, symbol) != null;
package/dist/geometry.js CHANGED
@@ -1,4 +1,4 @@
1
- import { STUB } from './model.js';
1
+ import { EDGE_ON, STUB } from './model.js';
2
2
  export const avoidOf = (boxes, extra, lanes) => [
3
3
  ...extra,
4
4
  ...boxes.map((r) => (lanes ? r : { ...r, box: true })),
@@ -78,6 +78,8 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
78
78
  if (around && blocked(around, arc)) {
79
79
  const fits = Array.from({ length: ARC_STEPS + 1 }, (_, k) => [around, outward(around, k)]);
80
80
  arc = fits.find(([way, at]) => !blocked(way, at))?.[1] ?? arc;
81
+ if (blocked(around, arc))
82
+ around = undefined;
81
83
  }
82
84
  if (!around && !e.sides && !e.stub && !e.elbow && past.length) {
83
85
  const ways = stacked ? ['left', 'right'] : ['above', 'below'];
@@ -177,6 +179,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
177
179
  const all = Object.values(rects);
178
180
  const [x0, y0] = [Math.min(...all.map((r) => r.x)), Math.min(...all.map((r) => r.y))];
179
181
  const [x1, y1] = [Math.max(...all.map((r) => r.x + r.w)), Math.max(...all.map((r) => r.y + r.h))];
182
+ const room = (d) => (d > 0 ? d + EDGE_ON / 2 : 0);
180
183
  const outside = (way, { x, y }) => (way === 'above' ? y0 - y : way === 'below' ? y - y1 : way === 'left' ? x0 - x : x - x1);
181
184
  const roomOf = (way, at, curve, labelW) => Math.max(ARC - 6, lanes || !labelW ? outside(way, { x: at, y: at }) - 6 : outside(way, bezier(curve, 0.5)) + (upright(way) ? labelW / 2 : 9));
182
185
  const one = (p) => {
@@ -335,21 +338,24 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
335
338
  if (e.x - s.x < 16 && s.y === e.y) {
336
339
  return { id: p.id, d: `M ${s.x} ${s.y} H ${e.x}`, mid: { x: (s.x + e.x) / 2, y: s.y }, curve: [s, s, e, e], elbow: [s, e] };
337
340
  }
341
+ const own = (r) => [p.a, p.b].some((q) => same(q, r));
342
+ const all = avoid.filter((r) => !own(r));
343
+ const boxes = all.filter((r) => r.box);
344
+ const vClear = (x, ya, yb, m = 2) => !all.some((r) => x > r.x - m && x < r.x + r.w + m && Math.max(ya, yb) > r.y && Math.min(ya, yb) < r.y + r.h);
345
+ const hClear = (y, xa, xb, list = boxes, m = 2) => !list.some((r) => y > r.y - m && y < r.y + r.h + m && Math.max(xa, xb) > r.x && Math.min(xa, xb) < r.x + r.w);
338
346
  if (e.x - s.x < 16) {
339
- const mx = s.x + 8, ex = e.x - 8, my = (s.y + e.y) / 2;
347
+ const mx = s.x + 8, ex = e.x - 8, mid = (s.y + e.y) / 2;
348
+ const ys = Array.from({ length: Math.floor((y1 - y0 + 24) / 4) + 1 }, (_, i) => y0 - 12 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
349
+ const my = ys.find((y) => vClear(mx, s.y, y) && hClear(y, ex, mx, all) && vClear(ex, y, e.y)) ?? mid;
340
350
  return {
341
351
  id: p.id,
342
352
  d: `M ${s.x} ${s.y} H ${mx} V ${my} H ${ex} V ${e.y} H ${e.x}`,
343
353
  mid: { x: (mx + ex) / 2, y: my },
344
354
  curve: [s, { x: mx, y: my }, { x: ex, y: my }, e],
345
355
  elbow: [s, { x: mx, y: s.y }, { x: mx, y: my }, { x: ex, y: my }, { x: ex, y: e.y }, e],
356
+ bleed: { above: room(y0 - my), below: room(my - y1) },
346
357
  };
347
358
  }
348
- const own = (r) => [p.a, p.b].some((q) => same(q, r));
349
- const all = avoid.filter((r) => !own(r));
350
- const boxes = all.filter((r) => r.box);
351
- const vClear = (x, ya, yb, m = 2) => !all.some((r) => x > r.x - m && x < r.x + r.w + m && Math.max(ya, yb) > r.y && Math.min(ya, yb) < r.y + r.h);
352
- const hClear = (y, xa, xb, list = boxes, m = 2) => !list.some((r) => y > r.y - m && y < r.y + r.h + m && Math.max(xa, xb) > r.x && Math.min(xa, xb) < r.x + r.w);
353
359
  const mid = (s.x + e.x) / 2;
354
360
  const gap = Array.from({ length: Math.floor((e.x - s.x - 16) / 4) + 1 }, (_, i) => s.x + 8 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
355
361
  const mx = [mid, e.x - 8, s.x + 8, ...gap].find((x) => vClear(x, s.y, e.y) && hClear(s.y, s.x, x) && hClear(e.y, x, e.x));
@@ -384,10 +390,10 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
384
390
  curve: [s, { x, y }, { x: ex, y }, e],
385
391
  elbow: [s, { x, y: s.y }, { x, y }, { x: ex, y }, { x: ex, y: e.y }, e],
386
392
  bleed: {
387
- left: Math.max(0, x0 - Math.min(x, ex)),
388
- right: Math.max(0, Math.max(x, ex) - x1),
389
- above: Math.max(0, y0 - y),
390
- below: Math.max(0, y - y1),
393
+ left: room(x0 - Math.min(x, ex)),
394
+ right: room(Math.max(x, ex) - x1),
395
+ above: room(y0 - y),
396
+ below: room(y - y1),
391
397
  },
392
398
  };
393
399
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.8.2";
1
+ export declare const VERSION = "0.8.3";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // The release list bumps this with package.json.
2
- export const VERSION = '0.8.2';
2
+ export const VERSION = '0.8.3';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowfig",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Your coding agent draws animated diagrams from your code, and flowfig checks each box and arrow against that code. CI fails when a diagram drifts from the code. The output is one animated SVG with no script, for a README, PR or issue. Works with Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, Windsurf and Kiro.",
5
5
  "license": "MIT",
6
6
  "repository": {