ssh git@grit.deadkey.org man

Commit c55e090 h

Pat Ambrosio · 26-08-28 10:07

Stop a small diagram stretching to fill the modal

Changes: 3 files +60 -0

Changes (Side-by-Side)

share/jenny/layout/page.sh +21 -0

diagram_script() {
206 // a read-only SVGAnimatedString, so assigning to it silently 206 // a read-only SVGAnimatedString, so assigning to it silently
207 // does nothing and the sizing rule never matches. 207 // does nothing and the sizing rule never matches.
208 clone.setAttribute('class', 'diagram-modal-svg'); 208 clone.setAttribute('class', 'diagram-modal-svg');
209 // Filling the viewport is right for a dense chart and wrong for
210 // a small one: a three-node diagram blown up to 1400px is all
211 // gaps and oversized text. Cap the enlargement at twice natural
212 // size, which is enough that anything around 700px wide still
213 // fills a typical screen while a small diagram stays legible.
214 // Natural width comes from the diagram itself -- mermaid writes
215 // it as an inline max-width, with the viewBox as a fallback --
216 // so the ceiling is per-diagram and nothing here is tuned to
217 // any particular chart.
218 var natural = parseFloat(svg.style.maxWidth);
219 if (!natural) {
220 var vb = (svg.getAttribute('viewBox') || '').split(/[ ,]+/);
221 natural = parseFloat(vb[2]);
222 }
223 // setProperty with 'important' because the stylesheet rule below
224 // is itself !important (it has to be, to beat mermaid's own
225 // inline max-width). A plain inline value would lose to it and
226 // this cap would silently do nothing.
227 if (natural > 0) {
228 clone.style.setProperty('max-width', (natural * 2) + 'px', 'important');
229 }
209 body.appendChild(clone); 230 body.appendChild(clone);
210 } 231 }
211 232

share/jenny/layout/post.sh +21 -0

diagram_script() {
122 // a read-only SVGAnimatedString, so assigning to it silently 122 // a read-only SVGAnimatedString, so assigning to it silently
123 // does nothing and the sizing rule never matches. 123 // does nothing and the sizing rule never matches.
124 clone.setAttribute('class', 'diagram-modal-svg'); 124 clone.setAttribute('class', 'diagram-modal-svg');
125 // Filling the viewport is right for a dense chart and wrong for
126 // a small one: a three-node diagram blown up to 1400px is all
127 // gaps and oversized text. Cap the enlargement at twice natural
128 // size, which is enough that anything around 700px wide still
129 // fills a typical screen while a small diagram stays legible.
130 // Natural width comes from the diagram itself -- mermaid writes
131 // it as an inline max-width, with the viewBox as a fallback --
132 // so the ceiling is per-diagram and nothing here is tuned to
133 // any particular chart.
134 var natural = parseFloat(svg.style.maxWidth);
135 if (!natural) {
136 var vb = (svg.getAttribute('viewBox') || '').split(/[ ,]+/);
137 natural = parseFloat(vb[2]);
138 }
139 // setProperty with 'important' because the stylesheet rule below
140 // is itself !important (it has to be, to beat mermaid's own
141 // inline max-width). A plain inline value would lose to it and
142 // this cap would silently do nothing.
143 if (natural > 0) {
144 clone.style.setProperty('max-width', (natural * 2) + 'px', 'important');
145 }
125 body.appendChild(clone); 146 body.appendChild(clone);
126 } 147 }
127 148

test/regressions.sh +18 -0

it "the modal content box fills the viewport"
2673 assert_file_contains "$dm_post" "width: 100%; height: 100%; overflow: auto; display: flex; }" 2673 assert_file_contains "$dm_post" "width: 100%; height: 100%; overflow: auto; display: flex; }"
2674 assert_file_not_contains "$dm_post" "max-width: min(95vw, 95vh)" 2674 assert_file_not_contains "$dm_post" "max-width: min(95vw, 95vh)"
2675 2675
2676 it "enlargement is capped at twice the diagram's natural size"
2677 # Filling the viewport suits a dense chart and ruins a small one -- a
2678 # three-node diagram at 1400px is all gaps and oversized text. The ceiling is
2679 # read from the diagram itself, so it holds for any chart rather than being
2680 # tuned to the ones in this repo.
2681 assert_file_contains "$dm_post" "(natural * 2) + 'px'"
2682
2683 it "the natural width falls back to the viewBox"
2684 # mermaid writes an inline max-width, but a diagram without one still carries
2685 # its dimensions in the viewBox.
2686 assert_file_contains "$dm_post" "svg.getAttribute('viewBox')"
2687
2688 it "the cap is set with important priority"
2689 # The stylesheet rule is !important (it must be, to beat mermaid's own inline
2690 # max-width). A plain inline value would lose to it and the cap would silently
2691 # do nothing -- measured: 100% wins over a non-important inline 600px.
2692 assert_file_contains "$dm_post" "setProperty('max-width', (natural * 2) + 'px', 'important')"
2693
2676 it "the diagram overrides mermaid's inline max-width" 2694 it "the diagram overrides mermaid's inline max-width"
2677 # mermaid writes style="max-width: {natural}px" directly on the <svg>. An 2695 # mermaid writes style="max-width: {natural}px" directly on the <svg>. An
2678 # inline style beats any selector, so without !important the diagram keeps its 2696 # inline style beats any selector, so without !important the diagram keeps its

Raw

commit c55e0909941894f3b0b4f0badaa7dc24cda0dedb
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date:   Fri Aug 28 10:07:28 2026 +0800

    Stop a small diagram stretching to fill the modal
    
    Filling the viewport is right for a dense chart and wrong for a small
    one. A three-node legend blown up to 1400px is all gaps and oversized
    text -- the modal made it worse than the page did.
    
    The ceiling is twice the diagram's own natural size, read at open time
    from mermaid's inline max-width with the viewBox as a fallback. Nothing
    here knows what a chart looks like, so it holds for a gantt or a sequence
    diagram as much as for the flowcharts in this repo:
    
        natural   cap      on a 1440 screen
          120px   240px    240px, not stretched
          300px   600px    600px, not stretched
          900px   1800px   fills the screen
         2400px   4800px   fills the screen
    
    Twice is where the two behaviours meet: anything from about 700px up
    still reaches the edge of a typical screen, so nothing dense loses the
    full-viewport view it just gained, while everything smaller stops before
    it looks silly. Half again is too tight -- a 900px chart would stop short
    of the screen. Two and a half lets a small one sprawl again.
    
    The cap has to be set with important priority. The stylesheet rule is
    itself !important, which it must be to beat the inline max-width mermaid
    writes on the svg, and a plain inline value loses to it: measured, 100%
    wins over an inline 600px and the cap does nothing at all. Two competing
    !important declarations is not pretty, but an inline style is only
    beatable by one, and mermaid gets there first.
    
    A diagram carrying neither an inline width nor a viewBox sets no cap and
    falls through to the stylesheet, rather than writing NaNpx.

diff --git a/share/jenny/layout/page.sh b/share/jenny/layout/page.sh
index 040778b..e420807 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -206,6 +206,27 @@ diagram_script() {
 	              // a read-only SVGAnimatedString, so assigning to it silently
 	              // does nothing and the sizing rule never matches.
 	              clone.setAttribute('class', 'diagram-modal-svg');
+	              // Filling the viewport is right for a dense chart and wrong for
+	              // a small one: a three-node diagram blown up to 1400px is all
+	              // gaps and oversized text. Cap the enlargement at twice natural
+	              // size, which is enough that anything around 700px wide still
+	              // fills a typical screen while a small diagram stays legible.
+	              // Natural width comes from the diagram itself -- mermaid writes
+	              // it as an inline max-width, with the viewBox as a fallback --
+	              // so the ceiling is per-diagram and nothing here is tuned to
+	              // any particular chart.
+	              var natural = parseFloat(svg.style.maxWidth);
+	              if (!natural) {
+	                var vb = (svg.getAttribute('viewBox') || '').split(/[ ,]+/);
+	                natural = parseFloat(vb[2]);
+	              }
+	              // setProperty with 'important' because the stylesheet rule below
+	              // is itself !important (it has to be, to beat mermaid's own
+	              // inline max-width). A plain inline value would lose to it and
+	              // this cap would silently do nothing.
+	              if (natural > 0) {
+	                clone.style.setProperty('max-width', (natural * 2) + 'px', 'important');
+	              }
 	              body.appendChild(clone);
 	            }
 
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index ba0be6a..3fd51b1 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -122,6 +122,27 @@ diagram_script() {
 	              // a read-only SVGAnimatedString, so assigning to it silently
 	              // does nothing and the sizing rule never matches.
 	              clone.setAttribute('class', 'diagram-modal-svg');
+	              // Filling the viewport is right for a dense chart and wrong for
+	              // a small one: a three-node diagram blown up to 1400px is all
+	              // gaps and oversized text. Cap the enlargement at twice natural
+	              // size, which is enough that anything around 700px wide still
+	              // fills a typical screen while a small diagram stays legible.
+	              // Natural width comes from the diagram itself -- mermaid writes
+	              // it as an inline max-width, with the viewBox as a fallback --
+	              // so the ceiling is per-diagram and nothing here is tuned to
+	              // any particular chart.
+	              var natural = parseFloat(svg.style.maxWidth);
+	              if (!natural) {
+	                var vb = (svg.getAttribute('viewBox') || '').split(/[ ,]+/);
+	                natural = parseFloat(vb[2]);
+	              }
+	              // setProperty with 'important' because the stylesheet rule below
+	              // is itself !important (it has to be, to beat mermaid's own
+	              // inline max-width). A plain inline value would lose to it and
+	              // this cap would silently do nothing.
+	              if (natural > 0) {
+	                clone.style.setProperty('max-width', (natural * 2) + 'px', 'important');
+	              }
 	              body.appendChild(clone);
 	            }
 
diff --git a/test/regressions.sh b/test/regressions.sh
index 714fc4f..dd1de6b 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -2673,6 +2673,24 @@ it "the modal content box fills the viewport"
 assert_file_contains "$dm_post" "width: 100%; height: 100%; overflow: auto; display: flex; }"
 assert_file_not_contains "$dm_post" "max-width: min(95vw, 95vh)"
 
+it "enlargement is capped at twice the diagram's natural size"
+# Filling the viewport suits a dense chart and ruins a small one -- a
+# three-node diagram at 1400px is all gaps and oversized text. The ceiling is
+# read from the diagram itself, so it holds for any chart rather than being
+# tuned to the ones in this repo.
+assert_file_contains "$dm_post" "(natural * 2) + 'px'"
+
+it "the natural width falls back to the viewBox"
+# mermaid writes an inline max-width, but a diagram without one still carries
+# its dimensions in the viewBox.
+assert_file_contains "$dm_post" "svg.getAttribute('viewBox')"
+
+it "the cap is set with important priority"
+# The stylesheet rule is !important (it must be, to beat mermaid's own inline
+# max-width). A plain inline value would lose to it and the cap would silently
+# do nothing -- measured: 100% wins over a non-important inline 600px.
+assert_file_contains "$dm_post" "setProperty('max-width', (natural * 2) + 'px', 'important')"
+
 it "the diagram overrides mermaid's inline max-width"
 # mermaid writes style="max-width: {natural}px" directly on the <svg>. An
 # inline style beats any selector, so without !important the diagram keeps its