feat: Label-Umbruch bei 32 statt 40 Zeichen (D64-Nachtrag 3)

Lange Titel zogen das Diagramm zu breit. Ein Parameter in wrapLabel(),
der 32ch-Rückhalt im CSS und SPEC §9 ziehen mit; Balance-Regel und
Erste-Zeile-Verankerung unverändert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-24 18:36:52 +02:00
co-authored by Claude Fable 5
parent 07e4116bd9
commit ebc5be407e
6 changed files with 30 additions and 15 deletions
+1
View File
@@ -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
+12
View File
@@ -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.
+1 -1
View File
@@ -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
+3 -2
View File
@@ -55,7 +55,8 @@ function attr(s){ return esc(String(s)).replace(/"/g,'&quot;'); }
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(' ');
+1 -1
View File
@@ -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);
+12 -11
View File
@@ -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);
}
});