@liggi/figkit 0.3.0 → 0.5.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.
package/GUIDE.md CHANGED
@@ -5,7 +5,7 @@ work out coordinates or text widths: boxes size to their text, arrows route them
5
5
  measure the real result.
6
6
 
7
7
  ```
8
- npx figkit figure.html [--out figure.png]
8
+ npx @liggi/figkit figure.html [--out figure.png]
9
9
  ```
10
10
 
11
11
  The file is just the `<main>` below; no doctype or `<head>` needed. Without `--out` the PNG goes to a temporary folder; the command prints where.
@@ -28,6 +28,10 @@ the images and look, then embed the PNG: `![what it shows](/abs/path.png)`.
28
28
  A question about structure ("what are the entities?", "where does X live?", "how do these relate?", "what loads
29
29
  what?", "walk me through it") is answered with a picture first, not with numbered steps.
30
30
 
31
+ - "What happens when…?", "walk me through a run" and "who calls whom?" are a `sequence` spec (below): the steps in
32
+ order between the parts, not a numbered list or a table of steps.
33
+ - "What's the lifecycle of X?" and "what states can it be in?" are a `states` spec (below): the states one thing
34
+ moves through and what moves it on, not boxes and arrows in HTML.
31
35
  - "What are the entities and how do they relate?" is an `entities` spec (below), not boxes and arrows in HTML. It
32
36
  draws references: which record holds a pointer to which. The order work happens in (approve, then label, then
33
37
  train) is not a reference; it goes in prose, a list or a separate flow. A record that creates others still points
@@ -46,12 +50,12 @@ what?", "walk me through it") is answered with a picture first, not with numbere
46
50
 
47
51
  ## Charts and entities from a spec
48
52
 
49
- When the whole figure is one chart or one entities picture of a type below, write a JSON spec instead of HTML. You give the data; figkit
53
+ When the whole figure is one chart, one entities picture, one sequence or one lifecycle of a type below, write a JSON spec instead of HTML. You give the data; figkit
50
54
  does the layout, colours and labels. Use HTML (the rest of this guide) for anything else, including a chart that sits
51
55
  beside boxes or arrows.
52
56
 
53
57
  ```
54
- npx figkit chart.json [--out chart.png]
58
+ npx @liggi/figkit chart.json [--out chart.png]
55
59
  ```
56
60
 
57
61
  A spec that breaks a rule is refused with one line per problem; fix them and run it again. Then look at the images
@@ -74,6 +78,21 @@ and embed the PNG, as for any figure.
74
78
  "bars": [ { "label": "Current checkout", "value": 86 }, { "label": "New checkout", "value": 41, "tone": "good" } ] }
75
79
  ```
76
80
 
81
+ A bar chart that compares every bar with one level, such as production or the current version, takes `ref`: the
82
+ level's `value` and a `label` naming it in words ("Production (ash-prod-27b)", never a bare number). figkit draws it as
83
+ its own first row in the second colour, with a dashed line from its end down through every bar; bars above it are
84
+ accented and the rest are grey, and the key says what the colour means. The colours are figkit's, so a `tone` on a bar
85
+ is refused, and `note` is required so the reader knows what the level's number counts. With ranges, a bar counts as
86
+ above only when its whole range is above. `show` on the ref replaces its printed value, e.g. `"50%, a tie"`.
87
+
88
+ ```json
89
+ { "type": "bars", "title": "Two of the four new layouts beat the current page",
90
+ "note": "Share of visitors who finished checkout", "unit": "%",
91
+ "ref": { "label": "Current page", "value": 61 },
92
+ "bars": [ { "label": "One-page form", "value": 68 }, { "label": "Saved cards first", "value": 64 },
93
+ { "label": "Address lookup", "value": 60 }, { "label": "Guest by default", "value": 57 } ] }
94
+ ```
95
+
77
96
  `before-after`: one measure that changed, the two values side by side on one scale; `note` (required) says what
78
97
  the number counts. For several things changing,
79
98
  use `bars` or `dumbbell`.
@@ -146,7 +165,9 @@ different things on the same scale.
146
165
  ```
147
166
 
148
167
  `heatmap`: a grid of values over two dimensions (day by hour, team by week), coloured from pale to dark blue: darker
149
- is always more. Up to 12 `rows` and 31 `columns`; `values` is one array per row. `note` (required) says what the
168
+ is always more. When any value is below zero (a change, a gap, a correlation), the grid is signed and the colour runs
169
+ from orange below zero through pale at zero to blue above, equally strong at equal distances from zero, with the
170
+ numbers printed with their sign; the scale shows 0 between the two halves. Numbers keep the decimal places you give. Up to 12 `rows` and 31 `columns`; `values` is one array per row. `note` (required) says what the
150
171
  colour counts, and the scale is drawn above the grid. Here the colour is the measure, so the one-accent rule moves:
151
172
  `mark` outlines the one cell to notice in the accent colour and names it beside the scale. Cells show their numbers
152
173
  when there is room. Columns label themselves at even steps, so give hours as "12am", "1am", … and they read 12am,
@@ -159,6 +180,96 @@ when there is room. Columns label themselves at even steps, so give hours as "12
159
180
  "mark": { "row": "Wed", "column": "12pm", "label": "Busiest: Wed noon" } }
160
181
  ```
161
182
 
183
+ ### How sure a number is
184
+
185
+ Most numbers are counted from a sample: 71% of 75 days is not 71% of 7,500. When the reader will compare such
186
+ numbers, draw how sure each one is.
187
+
188
+ Ranges on `bars`, `series` and `lines`: give each bar or point a `low` and a `high` around its value, and say once
189
+ in `range` what they are, in the reader's words ("likely range (95%)", "lowest to highest week"); it is printed under
190
+ the reading line beside the mark it describes. A bar steps back to a pale fill, its range is drawn across its end in
191
+ the bar's colour and the range's ends are printed after the value; a series or line gets a band. Accent the bar whose
192
+ range the title is about. Give every item a
193
+ range or none, since one without reads as certain. figkit refuses a range that does not contain its value, a range
194
+ with no `range` text, and a bar range below zero. `n` on a bar prints its sample size under its label, as "14 runs" when `each` names
195
+ the things counted (`"each": "runs"`), else "n = 14"; give every bar one or none. `ref` on a `series`,
196
+ `{ "y": 67, "label": "Jul to mid-Oct: 67%" }`, draws a dashed level to compare the line with, named in the key line. A percentage of fewer than 10 things is refused: show it as a count (`"show": "3 of 6"`) or draw `units`.
197
+
198
+ ```json
199
+ { "type": "bars", "title": "The new reply check catches more, but the ranges still overlap",
200
+ "note": "Share of unsafe replies caught, on the same test set", "unit": "%", "max": 100, "range": "likely range (95%)", "each": "replies",
201
+ "bars": [ { "label": "Current check", "value": 64, "low": 53, "high": 74, "n": 72 },
202
+ { "label": "New check", "value": 78, "low": 67, "high": 86, "n": 72, "tone": "accent" } ] }
203
+ ```
204
+
205
+ The title claims only what the ranges allow: when they overlap, "catches more" needs a "but".
206
+
207
+ `estimates`: signed results with their ranges, one row each around a zero line (a forest plot): a gap between two
208
+ groups, a change against control, a correlation. Use it when the sign is the point and each number has a range: "+7.4
209
+ (+2.7 to +12.4)". For numbers counted up from zero, use `bars`; for a move from one value to another, `dumbbell`. Every
210
+ row needs `low` and `high`, and `range` says once what they are. `note` (required) says what the number is and which
211
+ way round ("matched minus not matched"). `sides` names what each side of zero means, printed under the axis and meeting at zero; keep each to two or three words, or figkit reports it. figkit
212
+ chooses the colours: a row whose range clears zero is filled in the accent colour, one whose range includes zero is a
213
+ hollow grey dot, and the key says so; a `tone` is refused. With `"better": "higher"` or `"lower"`, a row clear of zero
214
+ is `good` or `bad` instead. Up to 12 rows; `n` and `each` as on `bars`. Numbers keep the decimal places you give, with
215
+ their sign. A range that does not contain its value, or a range of no width, is refused.
216
+
217
+ ```json
218
+ { "type": "estimates", "title": "Free delivery lifts orders in two regions; the north's range includes zero",
219
+ "note": "Change in orders per 100 visitors, free delivery minus paid", "range": "95% interval",
220
+ "sides": { "below": "Paid better", "above": "Free better" },
221
+ "rows": [ { "label": "South", "value": 2.4, "low": 1.1, "high": 3.7 },
222
+ { "label": "Midlands", "value": 1.8, "low": 0.6, "high": 3.0 },
223
+ { "label": "North", "value": 0.7, "low": -0.9, "high": 2.3 } ] }
224
+ ```
225
+
226
+ The title says a result only for rows whose range clears zero; a row whose range includes zero is "no clear change",
227
+ not a small gain.
228
+
229
+ `dots`: every value as a dot, for a sample small enough to look at one by one (up to 300 a group, 600 in all), in 1
230
+ to 6 groups on one axis. Use it instead of a bar of the average when the spread or a few odd values are the point.
231
+ Dots that would overlap stack upward and downward, so none hides another, and each group's count is printed. `note`
232
+ (required) says what one dot is. `each` names what a dot is, so counts read "50 runs". `summary` draws the `"median"` or `"mean"` as a line across each group, labelled;
233
+ `mark` rings one value and labels it; `zero` starts the axis at zero; `tone` on a group colours its dots.
234
+
235
+ ```json
236
+ { "type": "dots", "title": "Most builds finish in four minutes; a few take over ten",
237
+ "note": "One dot per build: minutes from push to result", "unit": " min", "summary": "median", "zero": true, "each": "builds",
238
+ "groups": [ { "label": "Main branch", "values": [3.8, 4.1, 3.6, 11.8, 4.0, 12.6, 4.2] },
239
+ { "label": "Pull requests", "values": [4.9, 5.2, 4.6, 5.8, 5.1], "tone": "accent" } ],
240
+ "mark": { "group": "Main branch", "value": 12.6, "label": "12.6 min, cache miss" } }
241
+ ```
242
+
243
+ `scatter`: two measures against each other, one dot per thing measured, up to 1000. It is its own type, not a mode
244
+ of `dots`, because it takes pairs and answers a different question: whether one measure moves with the other. `x`
245
+ and `y` each need a `label` saying what they measure, and take `unit`, `"log": true` for measures that span several
246
+ powers of ten (every value above zero; the axis runs to whole powers of ten), `zero`, and `time` (`"seconds"`,
247
+ `"minutes"`, `"hours"` or `"days"`, the unit the values are in) so ticks read 1 min, 1 h, 1 day, 1 wk.
248
+ `"medians": true` draws the median y of each band of x (each power of ten on a log axis, else fifths) in the accent
249
+ colour, with the middle half of the band's dots shaded behind it, for bands with 5 or more dots: the relation, or its
250
+ absence, drawn without fitting a model. `label` names a point beside it, at most 5; `tone` colours it.
251
+ figkit draws no fitted trend line: it claims more than dots do. Say the relation in the title only when the dots or
252
+ the medians show it, and "barely" when they mostly do not.
253
+
254
+ ```json
255
+ { "type": "scatter", "title": "Bigger pull requests wait longer for review",
256
+ "note": "One dot per merged pull request",
257
+ "x": { "label": "Lines changed", "log": true }, "y": { "label": "Time to first review", "time": "hours", "zero": true },
258
+ "points": [ { "x": 12, "y": 1.5 }, { "x": 120, "y": 5.5 }, { "x": 540, "y": 22, "label": "Search rewrite", "tone": "accent" } ] }
259
+ ```
260
+
261
+ `units`: a small count made concrete, "11 of 29" as 29 squares with 11 filled. `total` is at most 100: beyond that
262
+ the squares cannot be counted by eye, and a `split` bar of the shares reads better. Squares sit ten to a row and fill
263
+ column by column, so each part is one block from the left. The blocks show counts, not order: if when things happened
264
+ matters, draw it over time instead. Up to 3 `parts` fill squares in order (the first in the accent colour
265
+ unless it has a `tone`); `rest` (required) names the empty squares, so at least one is left. Counts are whole things.
266
+
267
+ ```json
268
+ { "type": "units", "title": "11 of the 29 flagged days were real problems",
269
+ "note": "One square per day the monitor flagged in October", "total": 29,
270
+ "parts": [ { "label": "real problems", "count": 11 } ], "rest": "false alarms" }
271
+ ```
272
+
162
273
  `entities`: what the things are and which points at which. 2 to 5 `records`, each with an `id` you use in `refs`,
163
274
  a `name` in the reader's words, and up to 4 `fields` that say what it holds (put a table name here only if the reader
164
275
  needs it). Each of up to 8 `refs` is a pointer the `from` record holds to the `to` record: `count` is how many `from`
@@ -183,11 +294,84 @@ in plain words, not in-house terms or button labels.
183
294
 
184
295
  More than 5 records is two figures: the core records, then one corner of it with the core record it hangs off.
185
296
 
186
- Flows (boxes and arrows) have no spec: write them in HTML, below.
297
+ `sequence`: who calls whom, in order. Use it when the question is "what happens when…", "walk me through a run",
298
+ "who starts what": the order is the point and more than one part takes part. A table of steps (step, who starts it,
299
+ who is involved) is this picture without the shape; draw it instead. A flow of boxes and arrows shows what connects
300
+ to what, not when; use it for structure. 2 to 5 `participants`, left to right in the order the reader meets them, each
301
+ an `id`, a `name` and an optional `kind` (where it runs: "GitHub Actions", "Cloud Run job"). 2 to 12 `messages` in
302
+ the order they happen, each from one participant to another with a `label` saying what is asked for or sent. figkit
303
+ numbers the steps, so your prose can say "step 4". `"reply": true` is the answer to an earlier call, drawn dashed;
304
+ leave replies out when nothing comes back that the reader needs. A message from a participant to itself is work it
305
+ does on its own. `note` adds one short line under a step: a condition ("only if the tests pass"), a wait ("up to an
306
+ hour"). `repeats` frames steps `from` to `to` (numbered from 1) that run more than once, with a `label` such as "For
307
+ each model". Accent one participant, or the steps the title is about, not both. The title claims only what the
308
+ steps show.
309
+
310
+ ```json
311
+ { "type": "sequence", "title": "A failed card charge is retried before the shopper hears anything",
312
+ "participants": [ { "id": "app", "name": "Shop app", "kind": "browser" }, { "id": "api", "name": "Checkout API" },
313
+ { "id": "pay", "name": "Card processor" } ],
314
+ "messages": [ { "from": "app", "to": "api", "label": "Place order" },
315
+ { "from": "api", "to": "pay", "label": "Charge the card", "tone": "accent" },
316
+ { "from": "pay", "to": "api", "label": "Approved or declined", "reply": true },
317
+ { "from": "api", "to": "app", "label": "Order number", "reply": true } ],
318
+ "repeats": [ { "from": 2, "to": 3, "label": "Up to 3 tries" } ] }
319
+ ```
320
+
321
+ More than 5 participants or 12 steps is two figures: the whole run with minor parts merged into one participant, then
322
+ the stage that needs detail. Leave out steps that only log or record what happened unless the question is about them.
323
+
324
+ `states`: the states one thing moves through and what moves it on: a pull request, an order, a ticket, a person
325
+ in a study. Use it for "what's the lifecycle of X?", "what states can it be in?", "what happens after it's
326
+ approved?": the question is about one thing and where it can go next. A `sequence` is about several parts taking turns;
327
+ a flow is about what connects to what. 2 to 7 `states`, each an `id`, a `name` and an optional one-line `note` (a rule
328
+ that holds there: "A restart needs a new review"). Mark the one state things begin in `"start": true`, and the states
329
+ they stop in `"end": true`: nothing ever leaves an end, so a mark that comes and goes on a state (the current
330
+ default, a pin) is that state's `note`, not an end. `entry` labels the way in ("Card created"). 1 to 10 `moves`, each `from` one state `to`
331
+ another with a `label` saying what makes the move happen. figkit draws the longest run from the start down one
332
+ column, the other states beside it, and a move back to an earlier state as a loop up the left side. `from` can be a
333
+ list of states for one move any of them can make (retire, cancel), drawn as one bracket beside them and one arrow; `back` names the same
334
+ move the other way (`"back": "Restore"`), drawn with a head at both ends.
335
+
336
+ Counts: `count` on every move is how many of the things made that move at least once, `each` names them
337
+ (`"people"`), and the start's `count` is how many entered. `stayed` on a state that is not an end is how many went no further
338
+ than it. `"shares": true` adds each move's share of those who
339
+ reached the state it leaves; a loop back brings nothing new into that base. figkit says what the counts mean under
340
+ the reading line, and refuses counts on only some moves, a move counted higher than reached the state it leaves,
341
+ moves out of a state passed through once adding up to more than reached it, and a share of fewer than 10.
342
+
343
+ It also refuses a lifecycle with no start or two, an end with a way out, a state nothing leaves that is not marked
344
+ end, a state the start cannot reach, a move to its own state (put that in the state's `note`), and two moves between
345
+ the same states the same way. Accent one state or the moves the title is about. The title claims only what the moves
346
+ show.
347
+
348
+ ```json
349
+ { "type": "states", "title": "An order waits after a declined card until a new card is added",
350
+ "entry": "Shopper checks out",
351
+ "states": [ {"id": "waiting", "name": "Awaiting payment", "start": true},
352
+ {"id": "declined", "name": "Card declined", "note": "Shopper emailed", "tone": "accent"},
353
+ {"id": "paid", "name": "Paid"},
354
+ {"id": "packed", "name": "Packed"},
355
+ {"id": "shipped", "name": "Shipped", "end": true},
356
+ {"id": "cancelled", "name": "Cancelled", "end": true} ],
357
+ "moves": [ {"from": "waiting", "to": "paid", "label": "Charge succeeds"},
358
+ {"from": "waiting", "to": "declined", "label": "Charge fails"},
359
+ {"from": "declined", "to": "waiting", "label": "New card added"},
360
+ {"from": "paid", "to": "packed", "label": "Warehouse picks"},
361
+ {"from": "packed", "to": "shipped", "label": "Carrier collects"},
362
+ {"from": ["paid", "packed"], "to": "cancelled", "label": "Shopper cancels"} ] }
363
+ ```
364
+
365
+ A states picture holds 7 states, not the 5 boxes of other figures: each state is a short name read in order down one
366
+ path, and an ordinary lifecycle is four or five steps plus one or two ways off it. More than 7 is two figures: the
367
+ main path with the side branches merged into one state, then the branch that needs detail.
368
+
369
+ Flows (boxes and arrows) have no spec: write them in HTML, below. A flow says what connects to what; when the order
370
+ things happen in is the point, use `sequence`; when one thing's states are the point, use `states`.
187
371
 
188
372
  - `title` (required) is the point, 14 words at most. `note` is the reading line under it; `source` a footer.
189
373
  - `"style": "report"` draws a light chart in one sans font, for a chart that sits in a page: a research report, a doc.
190
- It also draws a phone layout, `chart.phone.png`. Leave it out for the default dark figure for a dark chat.
374
+ It also draws a phone layout, `chart.phone.png`, as do `estimates`, `heatmap` and `bars` with a `ref` in either style. Leave it out for the default dark figure for a dark chat.
191
375
  - Keep `note` to what is measured, and "lower is better" where it applies; dates and method go in `source`.
192
376
  - `unit` is printed straight after each number. `show` on an item replaces its printed value, e.g. `"11 of 29"`.
193
377
  - `tone` is `accent` for what to notice, `good` or `bad` when it is a result; at most two tones in a chart. Leave the rest grey.
@@ -320,7 +504,8 @@ A few numbers compared:
320
504
  ```
321
505
 
322
506
  Grouped bars, several lines, small multiples and heat maps have specs (`groups`, `lines`, `panels`, `heatmap`); use
323
- them when the chart is the whole figure. Anything else (dots, intervals, distributions, a chart beside boxes) is
507
+ them when the chart is the whole figure. Dots, ranges, signed estimates, scatters and small counts have specs too
508
+ (`dots`, `estimates`, `scatter`, ranges on `bars` and `series`, `units`). Anything else (a distribution of thousands, a chart beside boxes) is
324
509
  Observable Plot, already loaded with d3:
325
510
 
326
511
  ```html
@@ -353,7 +538,8 @@ notice in `accent`, and label it directly on the chart; no legends. Start bar ax
353
538
 
354
539
  ## What the checks catch, and what they cannot
355
540
 
356
- Measured in the rendered page: text past its box or the frame, text too small, boxes and labels that overlap, arrows
541
+ Measured in the rendered page: text past its box or the frame, text too small, text too faint against what is drawn
542
+ behind it (a contrast under 3 to 1), boxes and labels that overlap, arrows
357
543
  through boxes, arrows crossing, edges that almost line up, too many words, boxes, arrows or colours, facts strung
358
544
  together with separators, a missing or long title.
359
545
 
package/README.md CHANGED
@@ -16,7 +16,7 @@ Node 20 or later, and a Chromium-based browser: figkit uses Chrome or Edge if on
16
16
 
17
17
  ```
18
18
  npm install @liggi/figkit
19
- npx figkit guide # prints GUIDE.md, the rules for writing figures
19
+ npx @liggi/figkit guide # prints GUIDE.md, the rules for writing figures
20
20
  ```
21
21
 
22
22
  ## A figure from HTML
@@ -39,7 +39,7 @@ Save this as `order.html`:
39
39
  ```
40
40
 
41
41
  ```
42
- npx figkit order.html --out order.png
42
+ npx @liggi/figkit order.html --out order.png
43
43
  ```
44
44
 
45
45
  ![An order is checked before it is charged: Basket, then Fraud check, then Charge the card](docs/readme-flow.png)
@@ -47,7 +47,7 @@ npx figkit order.html --out order.png
47
47
  ## A chart from a spec
48
48
 
49
49
  For one chart of a known type (`split`, `bars`, `before-after`, `dumbbell`, `series`, `groups`, `lines`, `panels`,
50
- `heatmap`), or an `entities` picture of records and the references between them, give the data and figkit does the
50
+ `heatmap`, `dots`, `scatter`, `units`), or an `entities` picture of records and the references between them, give the data and figkit does the
51
51
  rest, including the layout. Add `"style": "report"` for a light chart that sits in a page. Save this as `checkout.json`:
52
52
 
53
53
  ```json
@@ -59,7 +59,7 @@ rest, including the layout. Add `"style": "report"` for a light chart that sits
59
59
  ```
60
60
 
61
61
  ```
62
- npx figkit checkout.json --out checkout.png
62
+ npx @liggi/figkit checkout.json --out checkout.png
63
63
  ```
64
64
 
65
65
  ![Bar chart: the current checkout takes 86 seconds, the new one 41](docs/readme-bars.png)
package/bin/fig.mjs CHANGED
@@ -46,7 +46,7 @@ if (/\.json$/i.test(src)) {
46
46
  console.log(`This chart spec has ${spec.problems.length} problem${spec.problems.length === 1 ? '' : 's'}:\n${spec.problems.map((p) => ` - ${p}`).join('\n')}`);
47
47
  process.exit(1);
48
48
  }
49
- const cls = [spec.spec.type === 'entities' ? 'entities-fig' : 'chart', spec.spec.type === 'panels' ? 'panels-fig' : '', spec.spec.style ?? ''].filter(Boolean).join(' ');
49
+ const cls = [spec.spec.type === 'entities' ? 'entities-fig' : spec.spec.type === 'sequence' ? 'sequence-fig' : spec.spec.type === 'states' ? 'states-fig' : 'chart', spec.spec.type === 'panels' ? 'panels-fig' : '', spec.spec.style ?? ''].filter(Boolean).join(' ');
50
50
  body = `<main class="fig ${cls}"><script type="application/json" id="figkit-spec">${JSON.stringify(spec.spec).replace(/</g, '\\u003c')}</script></main>`;
51
51
  } else if (!/class="[^"]*\bfig\b/.test(body)) body = `<main class="fig">\n${body}\n</main>`;
52
52
  await ensureBuilt();
@@ -101,7 +101,7 @@ try {
101
101
  if (phone) await page.addInitScript(() => { window.__figPhone = true; });
102
102
  await page.goto(pathToFileURL(wrapped).href);
103
103
  await page.waitForFunction('window.__figDone === true', null, { timeout: 30000 });
104
- const r = await page.evaluate(() => ({ problems: window.fig.problems, info: window.fig.info, sides: !!document.querySelector('.fig .sides, .fig .entities, .fig .panels, .fig.report'), entities: !!document.querySelector('.fig .entities'), panels: !!document.querySelector('.fig .panels'), stack: !!document.querySelector('.fig .sides') }));
104
+ const r = await page.evaluate(() => ({ problems: window.fig.problems, info: window.fig.info, sides: !!document.querySelector('.fig .sides, .fig .entities, .fig .sequence, .fig .states, .fig .panels, .fig .estimates, .fig .heatmap, .fig fig-bars.has-ref, .fig.report'), states: !!document.querySelector('.fig .states'), entities: !!document.querySelector('.fig .entities'), sequence: !!document.querySelector('.fig .sequence'), panels: !!document.querySelector('.fig .panels'), stack: !!document.querySelector('.fig .sides') }));
105
105
  await page.locator('.fig').first().screenshot({ path: shot });
106
106
  await page.close();
107
107
  return { ...r, problems: [...errors.map((e) => `Script error: ${e}`), ...r.problems] };
@@ -116,7 +116,7 @@ try {
116
116
 
117
117
  const { info } = desk;
118
118
  const lines = [`Figure: ${png} (${info.width * 2}×${info.height * 2})`];
119
- if (mob) lines.push(`Phone layout: ${phonePng} (${mob.info.width * 2}×${mob.info.height * 2}), ${desk.entities ? 'the records in two columns' : desk.panels ? 'the panels stacked' : desk.stack ? 'the sides stacked' : 'at phone width'}, for a viewer to show on a phone`);
119
+ if (mob) lines.push(`Phone layout: ${phonePng} (${mob.info.width * 2}×${mob.info.height * 2}), ${desk.entities ? 'the records in two columns' : desk.sequence ? 'the participants narrower' : desk.states ? 'the states narrower' : desk.panels ? 'the panels stacked' : desk.stack ? 'the sides stacked' : 'at phone width'}, for a viewer to show on a phone`);
120
120
  lines.push(`Phone inline: ${inline} (preview only)`);
121
121
  const ph = (mob ?? desk).info.phone;
122
122
  const sizes = [['the title', ph?.title], ['headings', ph?.heading], ['body', ph?.body]].filter(([, v]) => v != null).map(([k, v]) => `${k} ${v}px`);