diff --git a/provider/src/pacta_provider/webdocs.py b/provider/src/pacta_provider/webdocs.py
index 7a9f017..634b5a0 100644
--- a/provider/src/pacta_provider/webdocs.py
+++ b/provider/src/pacta_provider/webdocs.py
@@ -50,6 +50,13 @@ def _leaf_ok(entry: LogEntry) -> bool:
)
+def _leaf_short(component: str) -> str:
+ """Compact display name for a leaf box at small spans."""
+ return (component.replace("-ed25519-verified", "")
+ .replace("ltl-accumulator-verified", "accum")
+ .replace("fips205-slhdsa-verified", "slh-dsa"))
+
+
def _svg_tree(entries: list[LogEntry], root_hex: str, signing_backend: str, head_label: str = "Ed25519") -> str:
"""The accumulator, drawn from its real leaves."""
if not entries:
@@ -77,12 +84,22 @@ def _svg_tree(entries: list[LogEntry], root_hex: str, signing_backend: str, head
ok = _leaf_ok(entry)
component = (((entry.leaf.get("attestation") or {}).get("subject")) or {}).get("component", "?")
fill, stroke = ("#e2f2e9", "#1e7f4f") if ok else ("#f4f4f6", "#8a93a0")
- out.append(f'')
- out.append(f'leaf {node_index}')
- short = escape(str(component).replace("-ed25519-verified", ""))
+ # Boxes must FIT the per-leaf span at any tree size (the
+ # 2026-08-16 lesson: fixed 112px boxes shingled at 19
+ # leaves). Rich boxes while they fit, compact ones after.
+ box_w = min(112.0, span * 0.94)
+ compact = box_w < 100
+ short = escape(_leaf_short(str(component)))
label = short if ok else f"{short} ✗"
- out.append(f'{label}')
- out.append(f'{node.hex()[:10]}…')
+ if compact:
+ out.append(f'')
+ out.append(f'leaf {node_index}')
+ out.append(f'{label}')
+ else:
+ out.append(f'')
+ out.append(f'leaf {node_index}')
+ out.append(f'{label}')
+ out.append(f'{node.hex()[:10]}…')
else:
is_root = level_index == len(levels) - 1
out.append(f'')
@@ -91,11 +108,19 @@ def _svg_tree(entries: list[LogEntry], root_hex: str, signing_backend: str, head
for child in (2 * node_index, 2 * node_index + 1):
if (level_index - 1, child) in positions:
cx, cy = positions[(level_index - 1, child)]
- out.append(f'')
+ leaf_top = 18 if len(entries) > 9 else 22
+ out.append(f'')
root_x, root_y = positions[(len(levels) - 1, 0)]
- out.append(f'')
- out.append(f'Signed Tree Head — {escape(head_label)}({root_hex[:12]}…)')
- out.append(f'signed by: {escape(signing_backend)} (verify path attested; signing itself not proven)')
+ # The head box sizes itself to its longest line (the 2026-08-16
+ # lesson: a fixed 380px box let a growing caption spill both sides).
+ title = f"Signed Tree Head — {head_label}({root_hex[:12]}…)"
+ line2 = f"signed by: {signing_backend}"
+ line3 = "(verify path attested; signing itself not proven)"
+ head_w = max(len(title) * 7.0, len(line2) * 5.3, len(line3) * 5.3) + 28
+ out.append(f'')
+ out.append(f'{escape(title)}')
+ out.append(f'{escape(line2)}')
+ out.append(f'{escape(line3)}')
out.append(f'')
out.append("")
return "".join(out)
diff --git a/tests/test_web_and_witness.py b/tests/test_web_and_witness.py
index 27870a5..0c18907 100644
--- a/tests/test_web_and_witness.py
+++ b/tests/test_web_and_witness.py
@@ -181,3 +181,32 @@ def test_webdocs_source_carries_no_stale_paper_claims():
# first-use glosses the page promised: STH and axiom cones
assert "Signed Tree Head (STH)" in text
assert "axiom cones (the exact set of assumptions" in text
+
+
+def test_svg_tree_boxes_never_overlap_or_spill():
+ # Regression for 2026-08-16: fixed-width leaf boxes shingled once the
+ # log outgrew the 8-leaf design, and a fixed head box let its caption
+ # spill. Render the tree at several sizes and assert geometry.
+ import re
+
+ from pacta_provider.webdocs import _svg_tree
+
+ class _E:
+ def __init__(self, i):
+ self.leaf_hash = f"{i:02x}" * 32
+ self.leaf = {"attestation": {"subject": {"component": "betrusted-ed25519-verified"},
+ "certificates": [{"status": "proven", "axiom_status": "clean"}]}}
+
+ for n in (8, 19, 33):
+ svg = _svg_tree([_E(i) for i in range(n)], "ab" * 32, "verified-dalek-serial")
+ rects = [(float(m.group(1)), float(m.group(2)), float(m.group(3)))
+ for m in re.finditer(r'([^<]+)', svg).group(1)
+ assert len(title) * 7.0 <= float(head.group(1)), "head title spills"