flowfig 0.8.0 → 0.8.1
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 +29 -17
- package/dist/check.js +64 -5
- package/dist/cli.js +13 -10
- package/dist/diff.js +12 -5
- package/dist/draw.js +1 -1
- package/dist/edges.js +53 -6
- package/dist/geometry.d.ts +11 -2
- package/dist/geometry.js +255 -67
- package/dist/guide.d.ts +22 -2
- package/dist/guide.js +282 -222
- package/dist/index.js +36 -28
- package/dist/load.d.ts +2 -0
- package/dist/load.js +24 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +16 -6
- package/dist/model.d.ts +8 -2
- package/dist/model.js +54 -11
- package/dist/scene.d.ts +2 -0
- package/dist/svg.js +49 -32
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/svg.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Wrapping is approximate: text is measured by character class, not by a browser.
|
|
2
|
-
import { route } from './geometry.js';
|
|
2
|
+
import { arcRoom, avoidOf, route } from './geometry.js';
|
|
3
3
|
import { foldedLabel, groupBox, layoutRail, railState, RAIL } from './rail.js';
|
|
4
4
|
import { textWidth, wrap } from './text.js';
|
|
5
5
|
import { checkRendered, planFor } from './check.js';
|
|
6
6
|
export { parseSource, links } from './source.js';
|
|
7
7
|
export { diff, formatDiff } from './diff.js';
|
|
8
|
-
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies,
|
|
8
|
+
import { ASYNC_TAG_W, BASE_RATE, CARD_LINE, CARD_PAD, CARD_WIDTH, DARK, EDGE_OFF, EDGE_ON, LIGHT, TONES, toneFill, toneTint, labelPillW, ON_ACCENT, str, edgeId, groupGap, isGroup, isRows, LANE_PAD, LANE_ROW_GAP, LANE_BLOCK_GAP, FRAME_SIDE, laneGutter as gutterOf, STUB_ROOM, laneBlock, laneEnd, tightCopies, LABEL_LINE, itemWidth, labelLines, fitCap, diamondLines, diamondRoom, isLanesLayout, timelineBeats, timelineLayout, TL_AXIS_H, TL_AXIS_W, TL_BAR_H, TL_ROW_GAP, toBeat, beatMs, STEP_HOLD_MS, playheadItem, loopStartItem, dateLabelRaised, labelSpan, outsideLabelRect, edgeTip, } from './model.js';
|
|
9
9
|
export { LIGHT, DARK } from './model.js';
|
|
10
10
|
const LINE = CARD_LINE, CARD_SIDE = 8, ROW_GAP = 4;
|
|
11
|
-
const
|
|
11
|
+
const SUB_LINE = 15;
|
|
12
12
|
const FRAME_TOP = 37, FRAME_BOTTOM = 18;
|
|
13
13
|
// The card-on fills approximate the player's 8 % accent tint.
|
|
14
14
|
const vars = (t, cardOn) => `--accent:${t.accent}; --fg:${t.fg}; --muted:${t.muted}; --bg:${t.bg}; --surface:${t.surface}; --border:${t.border}; --card-on:${cardOn}; --tint:color-mix(in srgb, var(--accent) 10%, var(--surface));`;
|
|
@@ -42,20 +42,18 @@ function layoutCard(c, width) {
|
|
|
42
42
|
function size(item, s) {
|
|
43
43
|
if (!isGroup(item)) {
|
|
44
44
|
const contents = s.cards.get(item.id);
|
|
45
|
-
const w =
|
|
45
|
+
const w = itemWidth(item, s.cards, s.fig.edges, s.cap);
|
|
46
46
|
const top = item.shape === 'store' ? 24 : 10;
|
|
47
47
|
const card = contents ? 8 + s.cardH.get(item.id) : 0;
|
|
48
|
-
const
|
|
48
|
+
const label = labelLines(item, w).length * LABEL_LINE;
|
|
49
|
+
const h = Math.max(top + label + (item.sub ? SUB_LINE : 0) + card + 10, s.minH(item.id));
|
|
49
50
|
if (item.shape !== 'decision')
|
|
50
51
|
return { w, h };
|
|
51
52
|
return { w, h: contents ? h + 24 : Math.max(diamondLines(item, w).h, s.minH(item.id) + 24) };
|
|
52
53
|
}
|
|
53
54
|
const kids = item.children.map((c) => size(c, s));
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const across = Math.max(...kids.map((k) => (item.direction === 'column' ? k.w : k.h)));
|
|
57
|
-
const inner = item.direction === 'column' ? { w: across, h: along } : { w: along, h: across };
|
|
58
|
-
return item.label != null ? { w: inner.w + FRAME_SIDE * 2, h: inner.h + FRAME_TOP + FRAME_BOTTOM } : inner;
|
|
55
|
+
const h = item.direction === 'column' ? kids.reduce((n, k) => n + k.h, 0) + s.gap(item) * (kids.length - 1) : Math.max(...kids.map((k) => k.h));
|
|
56
|
+
return { w: itemWidth(item, s.cards, s.fig.edges, s.cap), h: h + (item.label != null ? FRAME_TOP + FRAME_BOTTOM : 0) };
|
|
59
57
|
}
|
|
60
58
|
function placeLanes(fig, x, y, s, out) {
|
|
61
59
|
const lanes = fig.layout.children;
|
|
@@ -186,7 +184,16 @@ export function render(fig, opts = {}) {
|
|
|
186
184
|
const cardH = new Map();
|
|
187
185
|
const lanes = !tl && fig.lanes && isLanesLayout(fig.layout);
|
|
188
186
|
const plan = lanes ? planFor(fig, opts) : null;
|
|
189
|
-
const sizes = {
|
|
187
|
+
const sizes = {
|
|
188
|
+
cards,
|
|
189
|
+
cardH,
|
|
190
|
+
minH,
|
|
191
|
+
gap: (g) => groupGap(g, fig.edges),
|
|
192
|
+
fig,
|
|
193
|
+
plan,
|
|
194
|
+
tall: new Set(plan?.tall),
|
|
195
|
+
cap: fitCap(fig, opts.width ?? 830, pad),
|
|
196
|
+
};
|
|
190
197
|
for (const [id, contents] of cards) {
|
|
191
198
|
const width = CARD_WIDTH;
|
|
192
199
|
const widths = [width];
|
|
@@ -227,13 +234,10 @@ export function render(fig, opts = {}) {
|
|
|
227
234
|
around: e.around ?? (plan?.around.has(ids[i]) ? 'below' : undefined),
|
|
228
235
|
...(tl && { sides: ['r', 'l'], elbow: true }),
|
|
229
236
|
...(stubs.has(ids[i]) && { stub: stubs.get(ids[i]).map(labelPillW), bands: [bandOf(ids[i], e.from, 0), bandOf(ids[i], e.to, 1)] }),
|
|
230
|
-
...(
|
|
231
|
-
})), rects, tips, stubs.size
|
|
232
|
-
? placed.
|
|
233
|
-
: [
|
|
234
|
-
...placed.filter((p) => p.tl && !p.tl.labelInside).map((p) => outsideLabelRect(p, str(p.item.label))),
|
|
235
|
-
...placed.filter((p) => p.tl).map((p) => ({ x: p.x, y: p.y, w: p.w, h: p.h, box: true })),
|
|
236
|
-
], stubs.size ? { x: placed[0].x + gutter, y: placed[0].y, w: placed[0].w - gutter, h: placed[0].h } : undefined, lanes ? { bands: placed.filter((p) => p.lane), boxes: placed.filter((p) => !isGroup(p.item)) } : undefined);
|
|
237
|
+
...(!tl && e.label != null && { labelW: labelPillW(str(e.label)) }),
|
|
238
|
+
})), rects, tips, avoidOf(placed.filter((p) => !isGroup(p.item)), stubs.size
|
|
239
|
+
? placed.filter((p) => p.lane).map((p) => ({ x: p.x, y: p.y, w: gutter, h: p.h }))
|
|
240
|
+
: placed.filter((p) => p.tl && !p.tl.labelInside).map((p) => outsideLabelRect(p, str(p.item.label))), stubs.size > 0), stubs.size ? { x: placed[0].x + gutter, y: placed[0].y, w: placed[0].w - gutter, h: placed[0].h } : undefined, lanes ? { bands: placed.filter((p) => p.lane), boxes: placed.filter((p) => !isGroup(p.item)) } : undefined, pad);
|
|
237
241
|
};
|
|
238
242
|
let routed = go();
|
|
239
243
|
for (let pass = 0; plan && pass < 3; pass++) {
|
|
@@ -260,7 +264,11 @@ export function render(fig, opts = {}) {
|
|
|
260
264
|
const dl = dia ? diamondLines(p.item, p.w) : null;
|
|
261
265
|
const room = (far) => diamondRoom(p.w, p.h, far);
|
|
262
266
|
const flat = (p.item.shape === 'decision' ? p.w - 70 : p.w) - 16;
|
|
263
|
-
const texts =
|
|
267
|
+
const texts = labelLines(p.item, p.w).map((text) => ({
|
|
268
|
+
text,
|
|
269
|
+
fontSize: 14,
|
|
270
|
+
room: dl ? room(dl.label.far) : flat,
|
|
271
|
+
}));
|
|
264
272
|
if (dl)
|
|
265
273
|
for (const l of dl.subs)
|
|
266
274
|
texts.push({ text: l.text, fontSize: 12, room: room(l.far) });
|
|
@@ -443,6 +451,7 @@ export function render(fig, opts = {}) {
|
|
|
443
451
|
cls(p.lane && 'lane', anim(on, 'stroke: var(--accent)', 'stroke: var(--border)', 'n')) +
|
|
444
452
|
`/><text x="${n2(p.x + FRAME_SIDE)}" y="${n2(p.lane ? p.y + p.h / 2 + 4 : p.y + 20)}" class="frame">${esc(str(item.label).toUpperCase())}</text>`);
|
|
445
453
|
}
|
|
454
|
+
const tip = (inner) => (item.source ? `<g><title>${esc(item.source)}</title>${inner}</g>` : inner);
|
|
446
455
|
const bt = item.tone && TONES[item.tone];
|
|
447
456
|
const stroke = cls(boxAnim(item.id, true, bt));
|
|
448
457
|
if (p.tl) {
|
|
@@ -455,7 +464,7 @@ export function render(fig, opts = {}) {
|
|
|
455
464
|
? `<polygon points="${n2(cx)},${n2(p.y)} ${n2(p.x + p.w)},${n2(cy)} ${n2(cx)},${n2(p.y + p.h)} ${n2(p.x)},${n2(cy)}" fill="${fill0}" stroke="${stroke0}"${stroke}/>`
|
|
456
465
|
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="6" fill="${fill0}" stroke="${stroke0}"${stroke}/>`;
|
|
457
466
|
const tx = inside ? p.x + 8 : p.x + p.w + 6;
|
|
458
|
-
return shape + `<text x="${n2(tx)}" y="${n2(cy + 4.5)}" class="bar">${esc(label)}</text
|
|
467
|
+
return tip(shape + `<text x="${n2(tx)}" y="${n2(cy + 4.5)}" class="bar">${esc(label)}</text>`);
|
|
459
468
|
}
|
|
460
469
|
const rim = item.shape === 'store' ? cls(boxAnim(item.id, false, bt)) : '';
|
|
461
470
|
const fill0 = bt ? toneTint(bt, 'var(--bg)') : 'var(--bg)';
|
|
@@ -471,11 +480,14 @@ export function render(fig, opts = {}) {
|
|
|
471
480
|
? `<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(p.w)} 0 v ${n2(p.h - 24)} a ${n2(p.w / 2)} 12 0 0 1 ${n2(-p.w)} 0 z" fill="${fill0}" stroke="${stroke0}"${stroke}/>` +
|
|
472
481
|
`<path d="M${n2(p.x)} ${n2(p.y + 12)} a ${n2(p.w / 2)} 12 0 0 0 ${n2(p.w)} 0" fill="none" stroke="${stroke0}"${rim}/>`
|
|
473
482
|
: `<rect x="${n2(p.x)}" y="${n2(p.y)}" width="${n2(p.w)}" height="${n2(p.h)}" rx="10" fill="${fill0}" stroke="${stroke0}"${stroke}/>`;
|
|
474
|
-
const
|
|
483
|
+
const lines = labelLines(item, p.w);
|
|
484
|
+
const extra = (lines.length - 1) * LABEL_LINE;
|
|
485
|
+
const firstY = item.shape === 'store' || contents ? labelY : labelY - extra / 2;
|
|
486
|
+
const label = lines.map((l, i) => `<text x="${n2(cx)}" y="${n2(firstY + i * LABEL_LINE)}" class="label">${esc(l)}</text>`).join('');
|
|
475
487
|
const sub = dl
|
|
476
488
|
? dl.subs.map((l) => `<text x="${n2(cx)}" y="${n2(p.y + p.h / 2 + l.dy)}" class="sub">${esc(l.text)}</text>`).join('')
|
|
477
489
|
: item.sub
|
|
478
|
-
? `<text x="${n2(cx)}" y="${n2(
|
|
490
|
+
? `<text x="${n2(cx)}" y="${n2(firstY + extra + SUB_LINE)}" class="sub">${esc(str(item.sub))}</text>`
|
|
479
491
|
: '';
|
|
480
492
|
const dot = bt ?? 'var(--accent)';
|
|
481
493
|
const my = n2(p.y + p.h / 2);
|
|
@@ -484,7 +496,7 @@ export function render(fig, opts = {}) {
|
|
|
484
496
|
: item.mark === 'end'
|
|
485
497
|
? `<circle cx="${n2(p.x + p.w + 12)}" cy="${my}" r="6.25" fill="none" stroke="${dot}" stroke-width="1.5"/><circle cx="${n2(p.x + p.w + 12)}" cy="${my}" r="4" fill="${dot}"/>`
|
|
486
498
|
: '';
|
|
487
|
-
return shape + label + sub + mark + (contents ? card(p, cardTop, contents) : '');
|
|
499
|
+
return tip(shape + label + sub + mark + (contents ? card(p, cardTop, contents) : ''));
|
|
488
500
|
});
|
|
489
501
|
function card(p, top, contents) {
|
|
490
502
|
const id = p.item.id;
|
|
@@ -550,11 +562,13 @@ export function render(fig, opts = {}) {
|
|
|
550
562
|
const lit = cls(toned
|
|
551
563
|
? frames(on.map((x, i) => (x ? `stroke: ${col[i]}; stroke-width: ${EDGE_ON}` : off)), 'e', off)
|
|
552
564
|
: anim(on, `stroke: var(--accent); stroke-width: ${EDGE_ON}`, off, 'e'));
|
|
553
|
-
const
|
|
565
|
+
const tip = edgeTip(r.id, e.source, beats.flat());
|
|
566
|
+
const path0 = r.stub
|
|
554
567
|
? r.stub.parts
|
|
555
568
|
.map((d) => `<path d="${d}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}" marker-end="url(#arrow)"${lit}/>`)
|
|
556
569
|
.join('') + `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="none"/>`
|
|
557
570
|
: `<path id="p-${esc(r.id)}" d="${r.d}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}" marker-end="url(#arrow)"${lit}/>`;
|
|
571
|
+
const path = tip ? `<g><title>${esc(tip)}</title>${path0}</g>` : path0;
|
|
558
572
|
const pill = (x, y, lw, text) => `<rect x="${n2(x - lw / 2)}" y="${n2(y - 9)}" width="${n2(lw)}" height="18" rx="9" fill="var(--bg)" stroke="var(--border)"` +
|
|
559
573
|
cls(toned
|
|
560
574
|
? frames(on.map((x, i) => x ? `fill: ${tone[i] ? toneFill(col[i]) : col[i]}; stroke: ${col[i]}` : 'fill: var(--bg); stroke: var(--border)'), 'l', 'fill: var(--bg); stroke: var(--border)')
|
|
@@ -668,18 +682,18 @@ export function render(fig, opts = {}) {
|
|
|
668
682
|
}
|
|
669
683
|
}
|
|
670
684
|
const bounds = placed[0];
|
|
671
|
-
const
|
|
685
|
+
const [arcT, arcB, arcL, arcR] = ['above', 'below', 'left', 'right'].map((w) => arcRoom(routed, w));
|
|
672
686
|
const capLines = [...new Set(captions)].flatMap((c) => wrap(c, Math.max(560, bounds.w), 13.5).length);
|
|
673
|
-
const mapW = Math.max(bounds.w + pad * 2, 560);
|
|
687
|
+
const mapW = Math.max(bounds.w + pad * 2 + arcL + arcR, 560);
|
|
674
688
|
const rail = fig.rail ? layoutRail(fig, fig.rail === 'only' ? 560 : mapW) : null;
|
|
675
689
|
const only = fig.rail === 'only' && rail != null;
|
|
676
690
|
const capTop = rail ? 20 : 26;
|
|
677
691
|
const capH = steps.length ? capTop + 4 + Math.max(0, ...capLines) * 20 : 0;
|
|
678
692
|
const W = only ? Math.max(560, rail.width) : Math.max(mapW, rail?.width ?? 0);
|
|
679
|
-
const mapH = only ? 0 : bounds.h + pad * 2 +
|
|
693
|
+
const mapH = only ? 0 : bounds.h + pad * 2 + arcT + arcB;
|
|
680
694
|
const top = only ? pad : mapH + RAIL.gap;
|
|
681
695
|
const H = only ? pad + rail.height + capH : mapH + (rail ? RAIL.gap + rail.height : 0) + capH;
|
|
682
|
-
const shift = (W - (bounds.w + pad * 2)) / 2;
|
|
696
|
+
const shift = (W - (bounds.w + pad * 2 + arcL + arcR)) / 2 + arcL;
|
|
683
697
|
const railX = rail ? (W - rail.width) / 2 : 0;
|
|
684
698
|
const labels = rail || synthetic
|
|
685
699
|
? []
|
|
@@ -758,7 +772,8 @@ export function render(fig, opts = {}) {
|
|
|
758
772
|
? `<rect x="${n2(row.pill.x)}" y="${n2(ly - 9)}" width="${n2(row.pill.w)}" height="18" rx="9" fill="var(--bg)" stroke="var(--border)"${cls(anim(now, `fill: ${row.tone ? toneFill(col) : col}; stroke: ${col}`, 'fill: var(--bg); stroke: var(--border)', row.tone ? 'l' + col : 'l'))}/>` +
|
|
759
773
|
`<text x="${n2(row.pill.x + row.pill.w / 2)}" y="${n2(ly + 4)}"${cls('edgelabel', anim(now, `fill: ${ON_ACCENT}`, 'fill: var(--muted)', 'x'))}>${esc(row.text)}</text>`
|
|
760
774
|
: '';
|
|
761
|
-
|
|
775
|
+
const tip = edgeTip(row.edge, fig.edges[ids.indexOf(row.edge)].source, beats.flat());
|
|
776
|
+
parts.push(`<g${cls('railrow', moveRow(i))}>${tip ? `<title>${esc(tip)}</title>` : ''}<g${cls(anim(next, 'opacity: .45', 'opacity: 1', 'r'))}>` +
|
|
762
777
|
`<path d="M ${n2(x1)} ${n2(ly)} H ${n2(x2)}" fill="none" stroke="var(--muted)" stroke-width="${EDGE_OFF}"${row.async ? ' stroke-dasharray="4 3"' : ''} marker-end="url(#arrow)"${cls(lit)}/>` +
|
|
763
778
|
tag +
|
|
764
779
|
pill +
|
|
@@ -796,7 +811,7 @@ ${css.join('\n')}
|
|
|
796
811
|
<rect width="100%" height="100%" fill="var(--bg)"/>
|
|
797
812
|
${only
|
|
798
813
|
? ''
|
|
799
|
-
: `<g transform="translate(${n2(shift)} ${
|
|
814
|
+
: `<g transform="translate(${n2(shift)} ${arcT})">
|
|
800
815
|
${axisSvg}
|
|
801
816
|
${tl ? boxes.filter((b, i) => b && isGroup(placed[i].item)).join('\n') : ''}${lineSvg && `\n${lineSvg}`}
|
|
802
817
|
${tl ? edgeSvg.join('\n') : ''}
|
|
@@ -814,6 +829,7 @@ ${said.join('\n')}
|
|
|
814
829
|
fonts.push(12, 13);
|
|
815
830
|
const scene = {
|
|
816
831
|
width: n2(W),
|
|
832
|
+
...(!only && { area: { x: -shift, y: -arcT, w: W, h: mapH } }),
|
|
817
833
|
boxes: only ? [] : sceneBoxes,
|
|
818
834
|
edges: [
|
|
819
835
|
...(only ? [] : routed).flatMap((r) => {
|
|
@@ -827,14 +843,15 @@ ${said.join('\n')}
|
|
|
827
843
|
to: e.to,
|
|
828
844
|
curve: r.curve,
|
|
829
845
|
label: labelRects[r.id],
|
|
830
|
-
...(tl && { behind: true
|
|
846
|
+
...(tl && { behind: true }),
|
|
847
|
+
...(r.elbow && { elbow: r.elbow }),
|
|
831
848
|
},
|
|
832
849
|
];
|
|
833
850
|
}),
|
|
834
851
|
...(rail?.rows ?? []).flatMap((row, i) => {
|
|
835
852
|
if (row.kind !== 'message')
|
|
836
853
|
return [];
|
|
837
|
-
const dx = only ? railX : railX - shift, dy = only ? top : top -
|
|
854
|
+
const dx = only ? railX : railX - shift, dy = only ? top : top - arcT;
|
|
838
855
|
const y = dy + railState(rail, row.step)[i].y + RAIL.row / 2;
|
|
839
856
|
const [x1, x2] = [dx + rail.columns[row.from].x, dx + rail.columns[row.to].x];
|
|
840
857
|
const p = { x: x1, y }, q = { x: x2, y };
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.8.
|
|
1
|
+
export declare const VERSION = "0.8.1";
|
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
|
+
export const VERSION = '0.8.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowfig",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Your coding agent draws animated diagrams from your code, and flowfig checks them. One animated SVG for a GitHub README, PR or issue, and a React player. Set up Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, Windsurf and Kiro with npx flowfig init.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|