A diagram is as wide as its layout, and scaled down to fit the column: a flowchart of eight steps left to right came out 1256px wide, which a phone shows at 350px, its labels about 4px tall. In a browser Pele's mount() would draw it again for the width it has; pages here have no script for that. So one wider than a phone (340px) is drawn a second time with Pele's maxWidth at that width, which wraps labels and turns a left-to-right flowchart top to bottom, and the figure holds both. A container query shows the second below 640px of column and the first above it. Each has its own ids, and the hidden one is display: none, so screen readers read one. Only diagrams that come out narrower get the second drawing.
- commit
- e7033c1310697bee8f81f25a583238f4be4bc6d2
- parent
- 3527968279601aec7e3a495aa3e7f40d26138530
- author
- cat <admin@dev.site>
- ci
- ✓ passed in 78s, signed by cat
- kind
- feat risk score 1.6 of 3, Jev's estimate
- review
- #15 Draw mermaid diagrams at build time, with Pele
ci attestation
forge-attestation 1 type ci commit e7033c1310697bee8f81f25a583238f4be4bc6d2 tree d535de1d124b7463a913bb1585774bb19bcd54e7 script .forge/ci aa9f4eac26627c6559e68cd4b5e5e76afb8e65e8 result pass exit 0 started 2026-10-19T03:17:10Z duration 78s runner (not shown here; refs/notes/ci has it) system Darwin 25.5.0 arm64 env host log sha256:6591ad79d13a69a079f3f8ac60fd2a8a9be8e29e15b1513ba6d880385883e768 262 lines |pushed wip/i18n |e99e6b9 running .forge/ci: greet.sh: say what it is | | sh: /bin/sh | | ok greet |e99e6b9 pass in 0s, signed |pushed main |remote: review #4: /dami/greet/reviews/4/ |pushed wip/i18n |22b1fb6 running .forge/ci: wave at the world | | sh: /bin/sh | | FAIL greet : got 'hello, world o/', want 'hello, world' |22b1fb6 fail in 0s, signed |forge: 1 commit(s) did not pass |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |ac576f3 running .forge/ci: update the test for the wave | | sh: /bin/sh | | ok greet |ac576f3 pass in 0s, signed |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |warning: You appear to have cloned an empty repository. |site home: deployed 944ecfc -> /sites/ana/dotfiles/home/ |created ana/notes |remote: review #1: /ana/notes/reviews/1/ |pushed ana/todo |landing #1 A todo list: ana/todo -> main |039c011 running .forge/ci: web, server and Python versions of greet | | sh: /bin/sh | | ok greet |039c011 pass in 0s, signed |pushed main |4d2f3bf running .forge/ci: messages: notes for the later regions | | sh: /bin/sh | | ok greet |4d2f3bf pass in 0s, signed |remote: review #7: /dami/greet/reviews/7/ |pushed dami/regions |created dami/forge |warning: You appear to have cloned an empty repository. |site guide: mkdir -p guide/fonts && cp site/assets/fonts/*.woff2 guide/fonts/ |site guide: deployed 8bfb5e6 -> /sites/dami/forge/guide/ |(node:47230) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/dotfiles in 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.MORAI6/wt/demo/root/.forge/www |(node:47357) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/notes in 1s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.MORAI6/wt/demo/root/.forge/www |(node:47498) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/forge in 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.MORAI6/wt/demo/root/.forge/www |(node:47632) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/greet in 2s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.MORAI6/wt/demo/root/.forge/www |(node:48300) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built - in 0s -> /var/folders/b4/mv8w4c8j0zj0_mldppxdbdw00000gn/T/forge-ci.MORAI6/wt/demo/root/.forge/www |234 of 234 pages render the same without 11ty |guide: 8 files, links ok |ok
Signature: good, cat
| M | README.md | 14 | ++++++++------ |
| M | lib/ | 17 | ++++++++++++++++- |
| M | site/ | 8 | +++++++- |
| M | test/ | 3 | +++ |
@@ -710,12 +710,14 @@ diagram script. `style.css` colours them through Pele's `--pele-*`
710710 variables, which follow light and dark mode without drawing them again.711711 There is no browser at build time to measure text in, so labels are sized712712 from a table of the sans font's character widths in `lib/diagrams.ts`713-(`lib/text-widths.sh` makes it from the font). A diagram that doesn't parse714-shows its source and the reason instead. Markdown is anyone's who can push715-a repo: Pele escapes a diagram's text and filters its links and styles,716-then the SVG goes through a sanitizer of its own that keeps only the717-shapes, text and links Pele draws with (no images, no `url()`), while raw718-SVG in the markdown itself is still dropped.713+(`lib/text-widths.sh` makes it from the font). One wider than a phone is714+drawn a second time, laid out for a phone's width (often top to bottom),715+and the stylesheet shows that one in a narrow column. A diagram that716+doesn't parse shows its source and the reason instead. Markdown is anyone's717+who can push a repo: Pele escapes a diagram's text and filters its links718+and styles, then the SVG goes through a sanitizer of its own that keeps719+only the shapes, text and links Pele draws with (no images, no `url()`),720+while raw SVG in the markdown itself is still dropped.719721 720722 ## The lake and insights721723 @@ -76,6 +76,12 @@ const SVG: sanitize.IOptions = {
7676 7777 const escape = (s: string): string => s.replace(/[&<>"]/g, (c) => `&${{ '&': 'amp', '<': 'lt', '>': 'gt', '"': 'quot' }[c]};`);7878 79+// The width a phone gives a diagram, about. One drawn wider than this gets80+// a second drawing laid out for it, often turned top to bottom, which the81+// stylesheet shows instead in a narrow column (Pele's mount() would draw it82+// again for the width; a page here has no script for that).83+const PHONE = 340;84+7985 // One diagram's HTML. `href` rewrites a link in it, as the markdown's own8086 // links are (relative ones resolve to forge pages).8187 export function diagram(src: string, href: (url: string) => string = (u) => u): string {@@ -85,7 +91,16 @@ export function diagram(src: string, href: (url: string) => string = (u) => u):
8591 const options = { measurer, images: false, linkSchemes: ['http', 'https', 'mailto'] };8692 let svg;8793 try {88- svg = render(src, { ...options, idPrefix }).svg;94+ const wide = render(src, { ...options, idPrefix });95+ svg = wide.svg;96+ if (wide.width > PHONE) {97+ try {98+ const narrow = render(src, { ...options, idPrefix: idPrefix + '-narrow', maxWidth: PHONE });99+ if (narrow.width < wide.width) svg += narrow.svg;100+ } catch {101+ // The wide one will do.102+ }103+ }89104 } catch (e) {90105 // Pele's message names the line, and can quote it: shown as text.91106 let why = 'Pele failed on it';@@ -464,7 +464,13 @@ details.att pre, details.howto pre { white-space: pre-wrap; overflow-wrap: anywh
464464 .viewer .markdown pre code { background: none; }465465 /* Diagrams, drawn at build time (lib/diagrams.ts): Pele's SVG takes its466466 colours and fonts from these, so it follows light and dark as it is. */467-.markdown .diagram { margin: var(--space-4) 0; text-align: center; }467+.markdown .diagram { margin: var(--space-4) 0; text-align: center; container-type: inline-size; }468+/* A wide diagram's second drawing, laid out for a phone, replaces it there. */469+.markdown .diagram > svg + svg { display: none; }470+@container (max-width: 640px) {471+ .markdown .diagram > svg:has(+ svg) { display: none; }472+ .markdown .diagram > svg + svg { display: inline; }473+}468474 .markdown .diagram-source { margin: var(--space-4) 0; }469475 .markdown .diagram-source figcaption { color: var(--muted); font-size: var(--text-sm); }470476 .pele {@@ -1,5 +1,6 @@
11 # A ```mermaid fence in markdown is drawn at build time: the page has the22 # diagram as inline SVG with its labels as text, and no script draws it.3+# One wider than a phone has a second drawing, laid out for one, after it.34 . "$(dirname "$0")/../lib.sh"45 user dami56 repo dami/greet@@ -29,6 +30,8 @@ has "$p" '>post-receive</text>'
2930 # Coloured by the stylesheet's --pele-* variables, not colours of its own.3031 has "$p" 'style="max-width:100%;height:auto;--_bg:var\(--pele-bg,'3132 has "$FORGE_WWW/assets/$(cd "$FORGE_WWW/assets" && ls style.*.css)" '--pele-fg: var\(--fg\)'33+# The wide one drawn twice, the small one once.34+eq "$(grep '<figure class="diagram">' "$p" | awk -F '<svg xmlns' '{ print NF - 1 }' | tr '\n' ' ')" '2 1 '3235 # A link preview's text skips the labels.3336 has "$p" '<meta property="og:description" content="greet Pushes become pages\.">'3437 # The scripts are the ones every page has; none is for diagrams.