flowfig 0.6.0 → 0.8.0

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 (56) hide show
  1. package/README.md +28 -8
  2. package/dist/browser.d.ts +1 -3
  3. package/dist/browser.js +67 -59
  4. package/dist/check.d.ts +5 -3
  5. package/dist/check.js +24 -17
  6. package/dist/cli.js +43 -49
  7. package/dist/code.d.ts +26 -0
  8. package/dist/code.js +568 -0
  9. package/dist/diff.js +0 -4
  10. package/dist/draw.d.ts +1 -3
  11. package/dist/draw.js +3 -7
  12. package/dist/edges.d.ts +7 -0
  13. package/dist/edges.js +510 -0
  14. package/dist/geometry.d.ts +6 -8
  15. package/dist/geometry.js +75 -82
  16. package/dist/gif.d.ts +1 -2
  17. package/dist/gif.js +115 -42
  18. package/dist/guide.d.ts +1 -4
  19. package/dist/guide.js +10 -11
  20. package/dist/imports.d.ts +11 -0
  21. package/dist/imports.js +235 -0
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +20 -78
  24. package/dist/init.d.ts +3 -13
  25. package/dist/init.js +13 -18
  26. package/dist/load.d.ts +1 -3
  27. package/dist/load.js +2 -5
  28. package/dist/mcp.d.ts +1 -2
  29. package/dist/mcp.js +15 -15
  30. package/dist/model.d.ts +57 -109
  31. package/dist/model.js +20 -89
  32. package/dist/open.d.ts +2 -5
  33. package/dist/open.js +6 -11
  34. package/dist/page.d.ts +0 -1
  35. package/dist/page.js +1 -3
  36. package/dist/picker.d.ts +2 -4
  37. package/dist/picker.js +11 -14
  38. package/dist/png.js +9 -10
  39. package/dist/rail.d.ts +1 -3
  40. package/dist/rail.js +2 -16
  41. package/dist/scene.d.ts +8 -8
  42. package/dist/source.d.ts +1 -2
  43. package/dist/source.js +5 -5
  44. package/dist/svg.d.ts +5 -8
  45. package/dist/svg.js +50 -112
  46. package/dist/text.d.ts +0 -2
  47. package/dist/text.js +1 -4
  48. package/dist/type-index.d.ts +55 -0
  49. package/dist/type-index.js +383 -0
  50. package/dist/types.d.ts +32 -0
  51. package/dist/types.js +733 -0
  52. package/dist/verify.d.ts +26 -4
  53. package/dist/verify.js +68 -26
  54. package/dist/version.d.ts +1 -2
  55. package/dist/version.js +2 -2
  56. package/package.json +4 -2
package/dist/mcp.js CHANGED
@@ -1,13 +1,12 @@
1
- // The MCP server: the check, render, verify, diff and docs tools over stdio JSON-RPC, for an agent with no shell.
2
- // No SDK: the subset the clients need is small, and the package keeps its zero dependencies.
3
1
  import { mkdirSync, writeFileSync } from 'node:fs';
4
2
  import { dirname } from 'node:path';
5
3
  import { createInterface } from 'node:readline';
6
4
  import { diff, formatDiff } from './diff.js';
7
5
  import { GUIDE } from './guide.js';
8
6
  import { loadSpec, reportLines, sortFindings, svgWithSpec } from './load.js';
9
- import { check } from './svg.js';
10
- import { verify } from './verify.js';
7
+ import { check, render } from './svg.js';
8
+ import { checkRendered } from './check.js';
9
+ import { coverageLine, unsureLines, verifyReport } from './verify.js';
11
10
  import { VERSION } from './version.js';
12
11
  const specSchema = {
13
12
  description: 'The figure: the props object, { props }, or a path to a .json file or an SVG that flowfig wrote.',
@@ -55,9 +54,9 @@ export const TOOLS = [
55
54
  },
56
55
  ];
57
56
  class Bad extends Error {
58
- } // bad params: a JSON-RPC error, not a tool result
57
+ }
59
58
  class Fault extends Error {
60
- } // a path that cannot be read or written: a tool result with isError
59
+ }
61
60
  const ok = (text, isError = false) => isError ? { content: [{ type: 'text', text }], isError: true } : { content: [{ type: 'text', text }] };
62
61
  const spec = (v, name) => {
63
62
  if (typeof v !== 'string' && (typeof v !== 'object' || v === null))
@@ -66,7 +65,6 @@ const spec = (v, name) => {
66
65
  return loadSpec(v);
67
66
  }
68
67
  catch (e) {
69
- // The message of a path failure already starts with the path.
70
68
  throw typeof v === 'string' ? new Fault(e.message) : new Bad(`${name}: ${e.message}`);
71
69
  }
72
70
  };
@@ -75,7 +73,6 @@ const need = (args, tool, keys) => {
75
73
  if (args[k] == null)
76
74
  throw new Bad(`${tool}: ${k} is required`);
77
75
  };
78
- /** A present argument must have its type; a number must be positive. */
79
76
  const types = (args, tool, want) => {
80
77
  for (const [k, t] of Object.entries(want)) {
81
78
  const v = args[k];
@@ -94,13 +91,14 @@ function run(name, args) {
94
91
  types(args, name, { width: 'number', minText: 'number', out: 'string', strict: 'boolean' });
95
92
  const props = spec(args.spec, name);
96
93
  const opts = { width: args.width, minText: args.minText };
97
- const findings = sortFindings(check(props, opts), args.strict === true);
94
+ const rendered = render(props, opts);
95
+ const findings = sortFindings(checkRendered(props, opts, rendered.scene), args.strict === true);
98
96
  const errors = findings.some((f) => f.severity === 'error');
99
97
  const lines = reportLines(props, findings);
100
98
  if (name === 'check' || errors)
101
99
  return ok(lines.join('\n'), errors);
102
100
  const out = String(args.out);
103
- const svg = svgWithSpec(props, opts);
101
+ const svg = svgWithSpec(props, opts, rendered.svg);
104
102
  try {
105
103
  mkdirSync(dirname(out), { recursive: true });
106
104
  writeFileSync(out, svg);
@@ -117,6 +115,7 @@ function run(name, args) {
117
115
  types(args, name, { root: 'string', strict: 'boolean' });
118
116
  const root = args.root == null ? process.cwd() : String(args.root);
119
117
  const lines = [];
118
+ const counts = [];
120
119
  let errors = 0;
121
120
  for (const p of args.paths) {
122
121
  let props;
@@ -130,13 +129,15 @@ function run(name, args) {
130
129
  lines.push(`${'error'.padEnd(8)} ${'missing-file'.padEnd(18)} ${e.message}`);
131
130
  continue;
132
131
  }
133
- const findings = sortFindings([...check(props), ...verify(props, { root })], args.strict === true);
132
+ const report = verifyReport(props, { root });
133
+ counts.push(coverageLine(p, report.coverage), ...unsureLines(report.coverage));
134
+ const findings = sortFindings([...check(props), ...report.findings], args.strict === true);
134
135
  errors += findings.filter((f) => f.severity === 'error').length;
135
136
  lines.push(...findings.map((f) => `${f.severity.padEnd(8)} ${f.rule.padEnd(18)} ${p}: ${f.message}`));
136
137
  }
137
138
  const n = (k, w) => `${k} ${w}${k === 1 ? '' : 's'}`;
138
139
  const total = lines.length;
139
- lines.push(`${n(errors, 'error')}, ${n(total - errors, 'warning')}`);
140
+ lines.push(`${n(errors, 'error')}, ${n(total - errors, 'warning')}`, ...counts);
140
141
  return ok(lines.join('\n'), errors > 0);
141
142
  }
142
143
  if (name === 'diff') {
@@ -147,7 +148,7 @@ function run(name, args) {
147
148
  }
148
149
  const reply = (id, result) => ({ jsonrpc: '2.0', id, result });
149
150
  const fail = (id, code, message) => ({ jsonrpc: '2.0', id, error: { code, message } });
150
- /** One JSON-RPC message in, one response out; `undefined` for a notification. Pure, so the tests call it directly. */
151
+ /** Returns `undefined` for a notification. */
151
152
  export function handle(message) {
152
153
  if (typeof message !== 'object' || message === null)
153
154
  return fail(null, -32600, 'invalid request');
@@ -156,7 +157,7 @@ export function handle(message) {
156
157
  if (typeof method !== 'string')
157
158
  return notification ? undefined : fail(id, -32600, 'invalid request');
158
159
  if (notification)
159
- return undefined; // a notification gets no reply, and a call sent as one does not run
160
+ return undefined;
160
161
  const info = { name: 'flowfig', version: VERSION };
161
162
  try {
162
163
  switch (method) {
@@ -195,7 +196,6 @@ export function handle(message) {
195
196
  return fail(id, -32603, e.message);
196
197
  }
197
198
  }
198
- /** Read one message per line from `input`, write one response per line to `output`. Returns when `input` ends. */
199
199
  export async function serve(input, output) {
200
200
  const rl = createInterface({ input, crlfDelay: Infinity });
201
201
  for await (const line of rl) {
package/dist/model.d.ts CHANGED
@@ -1,41 +1,41 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { Rect } from './geometry.ts';
3
- /** A box in the figure. A box with a `show` in some step becomes a content card: it grows to fit the most any step puts in it. */
3
+ /** A box. A box with `show` in a step becomes a content card. */
4
4
  export type FigNode = {
5
- /** Name that edges, steps and beats use to point at this box. Must be unique in the figure. */
5
+ /** Name that edges, steps and beats use for this box. Unique in the figure. */
6
6
  id: string;
7
7
  /** The title in the box. */
8
8
  label: ReactNode;
9
9
  /** A smaller line under the label. */
10
10
  sub?: ReactNode;
11
- /** `box` is a rounded box, `decision` a diamond, `store` a database cylinder for data at rest. Default: `box`. */
11
+ /** `decision` is a diamond, `store` a cylinder for data at rest. Default: `box`. */
12
12
  shape?: 'box' | 'decision' | 'store';
13
- /** The least number of text lines a content card keeps. The card still grows to fit its content. */
13
+ /** The least text lines a content card keeps. It still grows to fit. */
14
14
  lines?: number;
15
15
  /** Width in px. Overrides the width the layout picks. */
16
16
  width?: number;
17
- /** In a `lanes` figure: the time column of the box, 0-based. Default: the box's first appearance in the steps. */
17
+ /** In `lanes`: the 0-based time column. Default: the first appearance in the steps. */
18
18
  at?: number;
19
- /** In a `timeline` figure: the start of the item, or the date of a milestone, as YYYY-MM-DD. */
19
+ /** In `timeline`: the start date, or the milestone date, as YYYY-MM-DD. */
20
20
  from?: string;
21
- /** In a `timeline` figure: the last day of the item, as YYYY-MM-DD. The day counts. Without it the box is a milestone. */
21
+ /** In `timeline`: the last day, as YYYY-MM-DD. No `to` makes a milestone. */
22
22
  to?: string;
23
- /** A permanent state color for the box, such as a failing part. It gives a 1 px border and a light tint. An arrival with no hop tone uses it too. */
23
+ /** A permanent state color. An arriving hop with no tone uses it too. */
24
24
  tone?: FigTone;
25
- /** A lifecycle mark: `start` draws a filled dot before the box, `end` a ringed dot after it. No layout change. */
25
+ /** `start` draws a filled dot before the box, `end` a ringed dot after it. */
26
26
  mark?: 'start' | 'end';
27
- /** The code this draws, `path` or `path#symbol`, relative to the repo root. `flowfig verify` checks it. */
27
+ /** Code this draws: `path` or `path#symbol` from the repo root. Checked by `flowfig verify`. */
28
28
  source?: string;
29
29
  };
30
- /** A frame that lays out its children in a row or a column. Groups can hold groups. */
30
+ /** A frame that lays out its children in a row or a column. */
31
31
  export type FigGroup = {
32
32
  /** Name that edges can point at, to reach the group as a whole. */
33
33
  id?: string;
34
- /** The title on the frame. A group with a label is drawn as a frame around its children. */
34
+ /** The title on the frame. A labeled group is drawn as a frame. */
35
35
  label?: ReactNode;
36
36
  /** `row` puts the children side by side, `column` stacks them. Default: `row`. */
37
37
  direction?: 'row' | 'column';
38
- /** Space between children in px. Default: 28 for a column. For a row, wide enough for the widest edge label between two children (at least 56). */
38
+ /** Space between children in px. Default: 28 for a column, fitted for a row. */
39
39
  gap?: number;
40
40
  /** Where children line up across the group's direction. Default: center (columns stretch). */
41
41
  align?: 'start' | 'center' | 'end';
@@ -54,10 +54,12 @@ export type FigEdge = {
54
54
  label?: ReactNode;
55
55
  /** Routes the edge over or under the boxes in between (loops, skip-ahead edges). */
56
56
  around?: 'above' | 'below';
57
- /** Draws the edge only while a step uses it. Use it for long edges that would cut across everything. */
57
+ /** Draws the edge only while a step uses it. */
58
58
  quiet?: boolean;
59
- /** The code this draws, `path` or `path#symbol`, relative to the repo root. `flowfig verify` checks it. */
59
+ /** Code this draws: `path` or `path#symbol` from the repo root. Checked by `flowfig verify`. */
60
60
  source?: string;
61
+ /** The route, queue, topic or table that carries this edge. Checked by `flowfig verify`. */
62
+ via?: string;
61
63
  };
62
64
  /** One packet on one edge. A string is the edge `id`. */
63
65
  export type FigHop = string | {
@@ -67,12 +69,14 @@ export type FigHop = string | {
67
69
  back?: boolean;
68
70
  /** A small card that rides along with the packet. */
69
71
  data?: ReactNode;
70
- /** Colors the packet, its data card, the edge and the box it arrives at, for this beat. The trail after the beat stays the accent. Default: the accent. */
72
+ /** Colors the packet, data card, edge and arrival box for this beat. Default: the accent. */
71
73
  tone?: FigTone;
72
- /** Marks a message that does not wait for an answer. The rail draws it dashed, with an `async` tag. Default: `false`. */
74
+ /** Marks a message that needs no reply. The rail draws it dashed. Default: `false`. */
73
75
  async?: boolean;
74
- /** The code this draws, `path` or `path#symbol`, relative to the repo root. `flowfig verify` checks it. */
76
+ /** Code this draws: `path` or `path#symbol` from the repo root. Checked by `flowfig verify`. */
75
77
  source?: string;
78
+ /** The route, queue, topic or table that carries this edge. Checked by `flowfig verify`. */
79
+ via?: string;
76
80
  };
77
81
  /** One moment of a step. No edges means a pause. */
78
82
  export type FigBeat = {
@@ -80,13 +84,13 @@ export type FigBeat = {
80
84
  edges?: FigHop | FigHop[];
81
85
  /** The line of narration for this beat. */
82
86
  say?: ReactNode;
83
- /** Fills the content card of each named box. The card keeps it until the step ends. */
87
+ /** Fills the content card of each named box until the step ends. */
84
88
  show?: Record<string, FigContent>;
85
89
  /** Ids of boxes to highlight for this beat, without a packet or a content card. */
86
90
  light?: string[];
87
- /** Ids of boxes that take the active look for this beat: a 2 px border, a tint and a glow. The boxes in `light` keep the trail look. */
91
+ /** Ids of boxes that take the active look in this beat. */
88
92
  focus?: string[];
89
- /** How long the beat lasts in ms. The packet crosses in `speed`; the rest is a hold. An explicit `ms` wins as written. Default: `speed` plus the time to read `say`. */
93
+ /** Beat length in ms. Default: `speed` plus the time to read `say`. */
90
94
  ms?: number;
91
95
  };
92
96
  /** One line of a content card. */
@@ -104,7 +108,7 @@ export type FigRow = {
104
108
  /** Draws the text in a monospace font. Default: `false`. */
105
109
  mono?: boolean;
106
110
  };
107
- /** The color of a row tag, a hop or a box. Each tone is a fixed color. */
111
+ /** The color of a row tag, a hop or a box. */
108
112
  export type FigTone = 'blue' | 'purple' | 'green' | 'orange' | 'red' | 'gray';
109
113
  /** What a content card shows: rows, or anything React can render. */
110
114
  export type FigContent = FigRow[] | ReactNode;
@@ -114,12 +118,12 @@ export type FigStep = {
114
118
  label: ReactNode;
115
119
  /** The line shown under the figure while no beat of the step has a `say`. */
116
120
  caption?: ReactNode;
117
- /** The beats in play order. A plain hop or hop array is a beat with just edges. */
121
+ /** The beats in play order. A bare hop or hop array is one beat. */
118
122
  flow: (FigHop | FigHop[] | FigBeat)[];
119
123
  /** Ids of boxes to highlight for the whole step. */
120
124
  nodes?: string[];
121
125
  };
122
- /** Colors for the figure. Each key sets one color, and a color you leave out keeps the built-in value. */
126
+ /** Colors for the figure. A key you leave out keeps the built-in color. */
123
127
  export type FigTheme = Partial<Record<'accent' | 'fg' | 'muted' | 'bg' | 'surface' | 'border' | 'font', string>>;
124
128
  /** The spec of one figure. Give it to `Flow` (React) or to `toSvg` (SVG string). */
125
129
  export type FlowProps = {
@@ -129,21 +133,21 @@ export type FlowProps = {
129
133
  edges: FigEdge[];
130
134
  /** The stories the figure tells. With no steps, the figure is a still map. */
131
135
  steps?: FigStep[];
132
- /** Colors. Can also be set from CSS with --fig-accent, --fig-bg, ... on `.flowfig`. `font` defaults to `inherit`. */
136
+ /** Colors. CSS `--fig-accent`, `--fig-bg` on `.flowfig` also set them. `font` defaults to `inherit`. */
133
137
  theme?: FigTheme;
134
138
  /** Milliseconds a packet takes to cross one edge. Default: 900. */
135
139
  speed?: number;
136
140
  /** Starts playing when the figure mounts. Default: `true`. Only the React player reads it. */
137
141
  autoplay?: boolean;
138
- /** Development aid: run the `flowfig check` rules on what the browser drew, and print the faults with console.warn. Default: `false`. */
142
+ /** Dev aid: run the `flowfig check` rules on the drawn figure, then console.warn faults. */
139
143
  check?: boolean;
140
- /** Draw the steps as a lifeline rail under the map: one row for each message, with its payload. `'only'` draws the rail without the map. Default: `false`. */
144
+ /** Draws the steps as a lifeline rail under the map. `'only'` draws no map. */
141
145
  rail?: boolean | 'only';
142
- /** Draw the layout as swimlanes: a `column` group of labeled groups, one per role, with the boxes in time order left to right. Default: off. */
146
+ /** Draws the layout as swimlanes: a `column` group of labeled groups, one per role. */
143
147
  lanes?: true;
144
- /** Draw the layout as a timeline: one labeled group per track, with the boxes placed by their `from` and `to` dates. Default: off. */
148
+ /** Draws a timeline: one labeled group per track, boxes placed by `from` and `to`. */
145
149
  timeline?: true;
146
- /** In a `timeline` figure: the date of the fixed today marker, as YYYY-MM-DD. With no `today`, the figure has no marker. */
150
+ /** In `timeline`: the date of the today marker, as YYYY-MM-DD. */
147
151
  today?: string;
148
152
  };
149
153
  /** A figure file's default export: a title, a source note and the props. */
@@ -152,7 +156,7 @@ export type Figure = {
152
156
  source?: string;
153
157
  props: FlowProps;
154
158
  };
155
- /** A beat with its hops spelled out: every hop has an edge id and a direction. */
159
+ /** A beat with every hop spelled out: an edge id and a direction. */
156
160
  export type Beat = Omit<FigBeat, 'edges'> & {
157
161
  hops: {
158
162
  edge: string;
@@ -161,33 +165,23 @@ export type Beat = Omit<FigBeat, 'edges'> & {
161
165
  tone?: FigTone;
162
166
  async?: boolean;
163
167
  source?: string;
168
+ via?: string;
164
169
  }[];
165
170
  };
166
171
  export declare const toBeat: (b: FigHop | FigHop[] | FigBeat) => Beat;
167
- /** A React node can be an object too, so a row is told apart by its `text` key. */
168
172
  export declare const isRows: (c: FigContent) => c is FigRow[];
169
173
  export declare const edgeId: (e: FigEdge) => string;
170
174
  export declare const isGroup: (x: FigNode | FigGroup) => x is FigGroup;
171
175
  export declare const decisions: (g: FigGroup) => string[];
172
- /** The width of a content card in px. Both renderers use it, so a figure lays out the same way in each. */
173
176
  export declare const CARD_WIDTH = 166;
174
- /** The player runs 1.25 times faster than the timings in a figure: figure timings are written to be read slowly. The SVG matches it. */
175
177
  export declare const BASE_RATE = 1.25;
176
- /** The height of one text line in a content card, and the padding above and below the lines. */
177
178
  export declare const CARD_LINE = 15, CARD_PAD = 6;
178
- /** The fixed color of each row tone. */
179
179
  export declare const TONES: Record<FigTone, string>;
180
- /** A tone as a fill under white text: darker, so the text keeps its contrast. */
181
180
  export declare const toneFill: (c: string) => string;
182
- /** The light tint of a tone over a background: the fill of a toned box. */
183
181
  export declare const toneTint: (c: string, bg: string, pct?: number) => string;
184
- /** The width of an edge-label pill in px: the label text plus its padding. The rail uses it for a payload, so both look the same. */
185
182
  export declare const labelPillW: (label: string) => number;
186
- /** The width of the `async` tag in px. */
187
183
  export declare const ASYNC_TAG_W: number;
188
- /** The stroke width in px of an edge that a packet crosses now, and of an idle edge. */
189
184
  export declare const EDGE_ON = 2, EDGE_OFF = 1.25;
190
- /** The built-in colors. Both renderers and `flowfig check` read these, so they cannot drift apart. */
191
185
  export declare const LIGHT: {
192
186
  accent: string;
193
187
  fg: string;
@@ -204,27 +198,20 @@ export declare const DARK: {
204
198
  surface: string;
205
199
  border: string;
206
200
  };
207
- /** Text on an accent fill: the data chips and a lit edge label. */
208
201
  export declare const ON_ACCENT = "#ffffff";
209
- /** Only text survives outside React: a React element has no string form. */
210
202
  export declare const str: (n: unknown) => string;
211
- /** The time a reader needs for a caption line: 400 ms plus 240 ms per word. The result is 0 for no text. */
212
203
  export declare const readMs: (say: unknown) => number;
213
- /** How long a beat lasts. An explicit `ms` wins as written. Else the beat lasts `speed` plus the time to read `say`. */
214
204
  export declare const beatMs: (b: Beat, speed: number) => number;
215
- /** The item the playhead rests on at beat `i`: the first dated id in the latest focus up to `i`, or undefined for home. */
216
205
  export declare const playheadItem: <T extends {
217
206
  id: string;
218
207
  }>(items: T[], beats: {
219
208
  focus?: string[];
220
209
  }[], i: number) => T | undefined;
221
- /** The item the playhead rests on when the loop starts: the playhead item of the first beat of the first step. */
222
210
  export declare const loopStartItem: <T extends {
223
211
  id: string;
224
212
  }>(items: T[], steps: {
225
213
  flow: FigStep['flow'];
226
214
  }[]) => T | undefined;
227
- /** The box of a timeline label that sits beside its bar, in the bar's coordinates. */
228
215
  export declare const outsideLabelRect: (bar: {
229
216
  x: number;
230
217
  y: number;
@@ -236,38 +223,22 @@ export declare const outsideLabelRect: (bar: {
236
223
  w: number;
237
224
  h: number;
238
225
  };
239
- /**
240
- * The time span in which the playhead label shows its date. The label waits for the line to arrive: it stays hidden for
241
- * the ramp (the move), so it never shows a date at a place that is not that date, and it never meets "today" on the way.
242
- */
243
226
  export declare const labelSpan: (t0: number, end: number, ramp: number) => [number, number];
244
- /** True if a playhead date label at x would overlap the "today" label, so it moves to the top row. Widths are in px. */
245
227
  export declare const dateLabelRaised: (x: number, today: number | null | undefined, w: number, todayW: number) => boolean;
246
- /** The hold at the end of each step, before the next step starts. */
247
228
  export declare const STEP_HOLD_MS = 2000;
248
229
  export declare const nodes: (g: FigGroup) => FigNode[];
249
- /** Lane layout constants, shared by both renderers: the gap between time columns, the padding inside a lane, and the gap between lanes. */
250
230
  export declare const LANE_GAP = 56, LANE_PAD = 24, LANE_ROW_GAP = 20;
251
- /** True if the layout can draw as lanes: a `column` group whose every child is a labeled group with boxes only. */
252
231
  export declare const isLanesLayout: (g: FigGroup) => boolean;
253
- /** True if `at` is a usable time column: an integer of 0 or more. */
254
232
  export declare const validAt: (at: unknown) => at is number;
255
- /** The time column of each box in a lanes figure: its first appearance in the steps (from, then to; a back hop to, then from),
256
- * then the boxes no step touches in layout order. `at` overrides. Two boxes may share a column. */
233
+ /** The time column of each box: first use in the steps, then layout order. `at` wins. */
257
234
  export declare function laneColumns(fig: FlowProps): Map<string, number>;
258
- /** The side of an SVG frame, and the width limits of a box with no card. The lane split measures with them. */
259
235
  export declare const FRAME_SIDE = 18, NODE_MIN_W = 100, NODE_MAX_W = 190;
260
- /** The gap between two blocks of wrapped lanes. */
261
236
  export declare const LANE_BLOCK_GAP = 40;
262
- /** The length of a stub line between a box and its pill, the clear space past a pill, and the room a lane grows by for a pill. */
263
237
  export declare const STUB = 12, STUB_CLEAR = 8, STUB_ROOM = 30;
264
238
  /** The width of a box as the SVG draws it, before a diamond adds its 70 px. */
265
239
  export declare function nodeWidth(item: FigNode, carded: boolean): number;
266
- /** The room a diamond of size w by h gives a text line whose far edge sits `far` px from the center: the width of the outline there, less 8 px. */
267
240
  export declare const diamondRoom: (w: number, h: number, far: number) => number;
268
- /** The lines of a diamond with no card. The sub wraps to the room the outline gives its last line; the diamond grows taller by 15 px
269
- * for each line until the lines fit. `dy` is the baseline of each line below the center, as the SVG draws it: the label sits 2 px
270
- * above the center over a one-line sub, and 5 px below it with no sub. `far` is the distance from the center to the far edge of the text. */
241
+ /** The lines of a diamond with no card. The diamond grows 15 px for each sub line that wraps. */
271
242
  export declare function diamondLines(item: FigNode, w: number): {
272
243
  h: number;
273
244
  label: {
@@ -280,58 +251,46 @@ export declare function diamondLines(item: FigNode, w: number): {
280
251
  far: number;
281
252
  }[];
282
253
  };
283
- /** The lane copies (`laneIndex@block`) that hold a stub end with no place clear of every edge path. A renderer grows each by
284
- * STUB_ROOM and routes again: the pill then fits under or over its box, inside the band. Both renderers call it with their routes. */
254
+ /** The lane copies whose stub end has no clear place. The renderer grows each by STUB_ROOM. */
285
255
  export declare function tightCopies(lanes: FigGroup[], ends: Map<string, [number, number]>, edges: FlowProps['edges'], routed: {
286
256
  id: string;
287
257
  stub?: {
288
258
  tight: [boolean, boolean];
289
259
  };
290
260
  }[]): Set<string>;
291
- /** The width of the label gutter of lanes and of a timeline: the widest track label plus the frame sides. */
292
261
  export declare const laneGutter: (lanes: FigGroup[]) => number;
293
- /** How a lanes figure wraps. Both renderers lay out with it, so the player shows the blocks and stubs of the SVG. */
262
+ /** How a lanes figure wraps. Both renderers lay out with it. */
294
263
  export type LanePlan = {
295
264
  /** The time column of each box. */
296
265
  cols: Map<string, number>;
297
- /** The first column of each block, then the column count: block k holds columns starts[k] to starts[k + 1] - 1. */
266
+ /** The first column of each block, then the column count. */
298
267
  starts: number[];
299
268
  /** The most columns in one block. */
300
269
  per: number;
301
- /** The blocks that hold a box, in order. A block with no box is not drawn. A figure with no box keeps block 0. */
270
+ /** The blocks that hold a box, in order. An empty figure keeps block 0. */
302
271
  blocks: number[];
303
272
  /** The width of the label gutter, the same in every block. */
304
273
  gutter: number;
305
- /** The space after each column: the gap to the next column of its block, or the right margin of the band for the last one. */
274
+ /** The space after each column: the gap to the next, or the band margin. */
306
275
  gaps: number[];
307
276
  /** The room before the first column of each block, after the gutter. */
308
277
  lead: number[];
309
- /** The block of the source and of the target of each edge, by edge id. A lane end names the copy of the lane in that block. */
278
+ /** The block of each edge source and target, by edge id. */
310
279
  ends: Map<string, [number, number]>;
311
- /** The edges whose ends sit in two blocks, by edge id: the pill texts at the source and at the target, then a shorter source
312
- * text without the edge label, if the edge has one. */
280
+ /** The pill texts of an edge across two blocks: source, target, then a short source. */
313
281
  stubs: Map<string, string[]>;
314
- /** Edges between two boxes of one lane and one block, with a box of that lane between them and no `around` of their own.
315
- * A straight route would cross that box, so the renderers route them around below. */
282
+ /** Same-lane, same-block edges with a box between them and no `around`. They route below. */
316
283
  around: Set<string>;
317
- /** The lane copies (`laneIndex@block`) that grow by STUB_ROOM at the bottom: a stub pill there has no room beside its box. */
284
+ /** The lane copies (`laneIndex@block`) that grow by STUB_ROOM: a stub pill has no room. */
318
285
  tall: Set<string>;
319
- /** Edges with a lane end that has no block on the side of the other end. They use the nearest block; `check` reports them. */
286
+ /** Edges with a lane end in no block on the other side. `check` reports them. */
320
287
  lost: string[];
321
288
  };
322
- /** The block of a time column, from the block starts of a plan. */
323
289
  export declare const laneBlock: (starts: number[], c: number) => number;
324
- /** The pill texts of a cross-block edge: "label → Target" at the source, "from Source" at the target, and "→ Target", a
325
- * shorter source text for a pill that has no place for the long one. */
326
290
  export declare const stubTexts: (label: unknown, from: unknown, to: unknown) => string[];
327
291
  /**
328
- * The wrap of a lanes figure. If all the time columns do not fit `width` with text at `minText` or more, the lanes wrap like a
329
- * line of text: block 1 holds columns 0..n-1, block 2 holds n..2n-1. n is the largest count that fits; it is 1 at least.
330
- * The text test is the one `check` runs: the smallest text (11 px, or 10.5 px with a mono card row) times width / figure width.
331
- * If the wrap cannot make the text readable, the lanes do not wrap: the smallest font is under `minText`, or `floor` (the least
332
- * width of the rest of the figure, such as the rail) is already too wide.
333
- * The blocks share the columns as evenly as they can, and a block holds 2 columns at least: if 2 do not fit, the lanes keep
334
- * one block. An edge between two blocks becomes two stubs with pills. A pill takes no room of its own, so n depends on the boxes.
292
+ * The wrap of a lanes figure: it wraps like text when the time columns do not fit `width`.
293
+ * The block count depends on the boxes only, not on pills.
335
294
  */
336
295
  export declare function lanePlan(fig: FlowProps, { width, minText, padding, floor }?: {
337
296
  floor?: number | undefined;
@@ -339,16 +298,9 @@ export declare function lanePlan(fig: FlowProps, { width, minText, padding, floo
339
298
  padding?: number | undefined;
340
299
  width?: number | undefined;
341
300
  }): LanePlan;
342
- /**
343
- * The rect id that an edge end routes to in wrapped lanes. A box keeps its id. A lane has one band per block, so a lane end names
344
- * the band in the block from the plan (`band` finds it). A stub end at a lane is a point inside that band: the source at the band
345
- * end, past the room for its pill, and the target at the first column of the block. The function adds the rect it names to `rects`.
346
- */
301
+ /** The rect id that an edge end routes to in wrapped lanes. A stub end is a point in the band. */
347
302
  export declare function laneEnd(plan: LanePlan, edge: string, id: string, start: boolean, band: (lane: string, block: number) => Rect | undefined, rects: Record<string, Rect>): string;
348
- /** Timeline constants, shared by both renderers: the bar height, the gap between rows, the least bar width, the milestone width,
349
- * the axis strip height and the axis width of the SVG. */
350
303
  export declare const TL_BAR_H = 28, TL_ROW_GAP = 8, TL_MIN_BAR = 24, TL_DIAMOND = 14, TL_AXIS_H = 28, TL_AXIS_W = 640;
351
- /** The days since 1970-01-01 (UTC) of a YYYY-MM-DD date. It is null for any other text and for a date that does not exist. */
352
304
  export declare const dayOf: (iso: string) => number | null;
353
305
  export type TimelineItem = {
354
306
  id: string;
@@ -362,7 +314,7 @@ export type TimelineItem = {
362
314
  date: string;
363
315
  };
364
316
  export type TimelineLayout = {
365
- /** The range in days, rounded out to whole weeks: the Monday of the first week and the Sunday of the last week. */
317
+ /** The range in days, rounded out to whole Monday-to-Sunday weeks. */
366
318
  start: number;
367
319
  end: number;
368
320
  /** The week ticks (`W41`) or the month ticks (`Oct`). */
@@ -370,21 +322,20 @@ export type TimelineLayout = {
370
322
  x: number;
371
323
  label: string;
372
324
  }[];
373
- /** Bars and milestones. `x` and `w` are px inside the axis; `row` is the stack row inside the track. */
325
+ /** Bars and milestones. `x` and `w` are px in the axis. */
374
326
  items: TimelineItem[];
375
327
  /** The row count of each track. */
376
328
  rows: number[];
377
- /** The x of the today marker. It is null when the figure has no valid `today`. */
329
+ /** The x of the today marker, or null with no valid `today`. */
378
330
  today: number | null;
379
- /** The x and the d MMM date of the last day: the playhead rests there in the step hold. The x is null when the figure has no dated item. */
331
+ /** The x and date of the last day. Null x: no dated item. */
380
332
  last: number | null;
381
333
  lastDate: string;
382
334
  };
383
335
  /** Where each dated item, tick and the today line sit on the axis, `axisWidth` px wide. Both renderers call it. */
384
336
  export declare function timelineLayout(fig: FlowProps, axisWidth: number): TimelineLayout;
385
- /** One step, "timeline", with one beat for each dated item in date order. The beat lights the item and says "label, d MMM to d MMM" (a milestone: "label, d MMM"), then " · sub". */
337
+ /** One step with one beat for each dated item, in date order. */
386
338
  export declare function timelineBeats(fig: FlowProps): FigStep[];
387
- /** The part counts of a spec, for the line that `flowfig check` prints. An agent copies the line into its reply. */
388
339
  export declare const counts: (p: FlowProps) => {
389
340
  boxes: number;
390
341
  groups: number;
@@ -392,8 +343,5 @@ export declare const counts: (p: FlowProps) => {
392
343
  steps: number;
393
344
  messages: number;
394
345
  };
395
- /**
396
- * A group's gap. A row's default gap grows to hold the widest label of an edge between two of its children, with 8px each side:
397
- * the label sits at the middle of that gap. A gap the author sets stays as set; `flowfig check` reports it if it is too narrow.
398
- */
346
+ /** A row's default gap grows to hold its widest edge label, plus 8 px each side. */
399
347
  export declare function groupGap(g: FigGroup, edges: FigEdge[]): number;