diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 28673c8..5dd0557 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,7 @@ reverse. ## 2026-08-24 +- Node titles wrap at thirty-two characters now instead of forty — long titles pulled the diagram wide - Fix: in the vertical layout, the line from a parent to one or two children was torn — the collector rail now always reaches the parent's stub - The legend shows the high-risk status `[!]` and mentions the size check - `llms.md` tabulates the size ranges and aligns its table columns diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index d0d9832..c1fd2da 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -4916,3 +4916,15 @@ Stub oben an (kein Zentrieren). Beides nachgemessen (Scanner: 0 Befunde in ist zoom-invariant, wie `--stem-x`). 405 Tests grün — die Regeln sind DOM-Geometrie und damit Browser-geprüft, nicht unit-testbar (dieselbe Grenze wie `alignStems()`, D29). + +**Nachtrag 3 zu D64 — die Umbruchgrenze sinkt von 40 auf 32 Zeichen.** +Nutzerwunsch nach dem Leben mit der 40er-Grenze: „einige Titel ziehen das +Diagramm doch arg in die Breite". Die 40 waren eine gesetzte Zahl, keine +hergeleitete — 32 ist es ebenso, nur mit Erfahrung dahinter. Die Änderung ist +genau der eine Parameter, für den `wrapLabel()` gebaut wurde: Default in +render.js, der `max-width`-Rückhalt im CSS (40ch → 32ch) und die +SPEC-§9-Zahl ziehen mit; Balance-Regel, Erste-Zeile-Verankerung und Export +bleiben unberührt. Sichtbare Folge: mehr Knoten brechen um (rund 100 Zeichen +ergeben jetzt vier statt drei Zeilen), dafür wird der breiteste Fächer +schmaler. Testerwartungen mit dem echten Algorithmus nachgerechnet statt +geschätzt. diff --git a/docs/SPEC.md b/docs/SPEC.md index 0796213..95639f4 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -499,7 +499,7 @@ Siehe D29. deterministisch auf Knotenmitte liegen (Offset 23 px = 5 px Listenabstand + halbe Knotenhöhe). Abzweige zielen auf den **Knoten**, nie auf die Mitte des Teilbaums. -- **Lange Labels brechen um** (D64): höchstens ~40 Zeichen je Zeile, die +- **Lange Labels brechen um** (D64): höchstens ~32 Zeichen je Zeile, die Zeichen **gleichmäßig** auf die Zeilen verteilt (nicht gierig — sonst stünde unter einer vollen Zeile ein einsames Wort). Die Umbrüche setzt der **Renderer**, deterministisch und nur an Leerzeichen; der Kasten schrumpft diff --git a/frontend/src/render.js b/frontend/src/render.js index 2efeff1..7dc1bed 100644 --- a/frontend/src/render.js +++ b/frontend/src/render.js @@ -55,7 +55,8 @@ function attr(s){ return esc(String(s)).replace(/"/g,'"'); } Beschreibung als data-Attribut wäre der einzige andere Weg gewesen. */ export const TIP_RULE = '─'.repeat(24); -/* Lange Labels umbrechen (SPEC §9/D64): höchstens ~40 Zeichen je Zeile, und +/* Lange Labels umbrechen (SPEC §9/D64): höchstens ~32 Zeichen je Zeile (D64, + verengt per D64-Nachtrag 3 — 40 zog das Diagramm zu breit), und die Zeichen GLEICHMÄSSIG auf die Zeilen verteilt — der gierige CSS-Umbruch machte aus 44 Zeichen eine volle Zeile plus ein einsames Wort. Bewusst im Renderer statt per `text-wrap:balance`: So schrumpft der Knotenkasten auf @@ -64,7 +65,7 @@ export const TIP_RULE = '─'.repeat(24); Regel ist headless testbar. Gebrochen wird nur an Leerzeichen; ein einzelnes Wort über der Grenze bleibt stehen (das CSS fängt es mit `max-width` + `overflow-wrap` ab). */ -export function wrapLabel(label, max = 40){ +export function wrapLabel(label, max = 32){ const text = String(label); if(text.length <= max) return [text]; const words = text.split(' '); diff --git a/frontend/src/style.css b/frontend/src/style.css index 9464514..aa13229 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -693,7 +693,7 @@ bleibt — der 23-px-Abzweig (§9) trifft bei mehrzeiligen Knoten deterministisch die Mitte der ERSTEN Zeile. */ white-space:pre-line; - max-width:40ch; + max-width:32ch; text-align:center; overflow-wrap:break-word; box-shadow:0 1px 2px rgba(36,52,71,.08); diff --git a/frontend/tests/wrap.test.js b/frontend/tests/wrap.test.js index 67f685b..e4486e1 100644 --- a/frontend/tests/wrap.test.js +++ b/frontend/tests/wrap.test.js @@ -1,4 +1,5 @@ -// Umbruch langer Labels (SPEC §9/D64): höchstens ~40 Zeichen je Zeile und +// Umbruch langer Labels (SPEC §9/D64, verengt auf 32 per Nachtrag 3): +// höchstens ~32 Zeichen je Zeile und // gleichmäßig verteilt — nicht gierig (volle Zeile plus einsames Wort). import { describe, it, expect } from 'vitest'; import { wrapLabel, renderTreeHtml } from '../src/render.js'; @@ -7,13 +8,13 @@ import { parse } from '../src/parser.js'; const t = key => key; describe('wrapLabel: balanciert statt gierig', () => { - it('bis 40 Zeichen bleibt es eine Zeile, unverändert', () => { - const s = 'Ein gewöhnlicher Knotentitel (39 Zeich.)'; - expect(s.length).toBe(40); + it('bis 32 Zeichen bleibt es eine Zeile, unverändert', () => { + const s = 'Ein gewöhnlicher Knotentitel, 32'; + expect(s.length).toBe(32); expect(wrapLabel(s)).toEqual([s]); }); - it('44 Zeichen werden zwei etwa gleiche Zeilen — nicht 40 plus Rest', () => { + it('44 Zeichen werden zwei etwa gleiche Zeilen — nicht 32 plus Rest', () => { const s = 'Update the agents version of the spec please'; expect(s.length).toBe(44); const lines = wrapLabel(s); @@ -29,19 +30,19 @@ describe('wrapLabel: balanciert statt gierig', () => { expect(wrapLabel(s).join(' ')).toBe(s); }); - it('keine Zeile über 40 Zeichen, solange die Wörter es hergeben', () => { + it('keine Zeile über 32 Zeichen, solange die Wörter es hergeben', () => { const s = 'A dependency needs its target id and nobody remembers a hundred and eighty of them by heart'; - for(const l of wrapLabel(s)) expect(l.length).toBeLessThanOrEqual(40); + for(const l of wrapLabel(s)) expect(l.length).toBeLessThanOrEqual(32); }); - it('rund 100 Zeichen ergeben drei gleichmäßige Zeilen', () => { + it('rund 100 Zeichen ergeben vier gleichmäßige Zeilen', () => { const s = 'The list at the caret offers the document ids filtered by what is typed with the node title as ctx'; expect(s.length).toBe(98); const lines = wrapLabel(s); - expect(lines.length).toBe(3); + expect(lines.length).toBe(4); for(const l of lines){ - expect(l.length).toBeGreaterThanOrEqual(25); - expect(l.length).toBeLessThanOrEqual(40); + expect(l.length).toBeGreaterThanOrEqual(17); + expect(l.length).toBeLessThanOrEqual(32); } });