frontend: Treppe für aufeinanderfolgende optionale Endknoten
Im horizontalen Fächer kostet jedes optionale Geschwister eine eigene Spalte — Breite für gerade das, was am entbehrlichsten ist. Zwei oder mehr aufeinanderfolgende optionale ENDknoten werden deshalb als Kaskade gestapelt: erste Stufe am Stiel von oben wie ein gewöhnliches Kind, jede weitere eine Stufe tiefer und weiter rechts, an einem gestrichelten Winkel, der an der linken Kante der vorigen Stufe herabfällt und waagerecht in ihren Kreis einbiegt. Verworfen: die naheliegendere SENKRECHTE Spalte unter einem Anschlusspunkt. Schmaler und in einem Bruchteil der Zeit gebaut — sähe aber fast genau aus wie eine any-of-Gruppe (gestapelte Spalte an gestrichelter Leiste), unterschieden nur durch Tinte statt Grau. Genau diese Verwechslung zu vermeiden ist der Zweck von Kreis und Farbgebung. Ebenfalls verworfen: eine echte Diagonale — Rahmenkanten sind achsenparallel, sie bräuchte die SVG-Ebene und wäre eine zweite Zeichenebene neben allen anderen Linien, nachzuführen bei jedem Rendern, Moduswechsel und Zoom. Rechte Winkel geben denselben Kaskaden-Eindruck im vorhandenen Mechanismus. - Nur Endknoten: Der Platzgewinn entsteht gerade daraus, dass kein Teilbaum mitgestapelt wird — ein optionaler Knoten MIT Kindern spart nichts und behält seine Spalte. Technisch dasselbe: die Stufengeometrie setzt Zellenhöhe == Knotenhöhe voraus. Geprüft wird genau das (ein Element-Kind, und das ist der Knoten) — schließt auch den Geister-Knoten aus. - Gruppiert wird in app.js, nicht im Renderer. Die DOM-Ebene gibt es semantisch nicht und müsste in den drei übrigen Anordnungen (vertikal, kompakt, all-of unter any-of) wieder neutralisiert werden, jede mit hand-getunter Geometrie. `display:contents` löst das nicht: richtet die Boxen, aber die >-Selektoren jener Regeln greifen weiter auf dem DOM. applyOptStairs() baut die Gruppe nur im Fächer und löst sie beim Moduswechsel auf — dieselbe Kategorie wie alignStems() und drawCheapPath(); SPEC §9 bleibt für den RENDERER wörtlich wahr, Lese- und Fokusreihenfolge unberührt. - Der Export folgt der Kaskade. Erste Fassung reihte alle Stufen flach als Kinder ein und ließ den Export selbst routen — die Linie zur dritten Stufe lief dann HINTER der zweiten hindurch und las sich wie eine Eltern-Kind- Beziehung. Keine Schönheitsfrage, sondern eine falsche Aussage über die Struktur. Jetzt hängt nur die erste Stufe an der Sammelleiste. Nicht durch Tests gedeckt: applyOptStairs() arbeitet auf dem DOM, und für app.js gibt es keine Testumgebung (die Suite prüft die headless-Module). Verifiziert im Browser: Kaskade in horizontal (drei Stufen, Winkel treffen die Kreise), Moduswechsel horizontal↔kompakt↔vertikal mehrfach hin und zurück — Gruppe entsteht und löst sich rückstandsfrei auf, Knotenzahl (9) und Dokumentordnung in allen drei Modi unverändert, keine übrig gebliebenen --i-Variablen. SVG-Export gerendert geprüft: drei Kreise, Winkelkette statt kreuzender Linien. Vitest 60/60 (Renderer unverändert). Im mitgelieferten Werkbaum-Plan stehen die beiden Editor-Zugaben jetzt nebeneinander und zeigen die Treppe. Reihenfolgeänderung ist für „Was ist neu?" folgenlos — die Knoten-Identität ist der Label-Pfad (D28), test-abgedeckt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
091e8849d8
commit
56c18cb8cb
@@ -678,6 +678,46 @@
|
||||
left:-5px;top:50%;transform:translateY(-50%);
|
||||
}
|
||||
|
||||
/* --- TREPPE: aufeinanderfolgende optionale Endknoten (D29, Nachtrag 3) ---
|
||||
Nur im horizontalen Fächer, wo jedes Geschwister sonst eine eigene Spalte
|
||||
kostet. `applyOptStairs()` in app.js gruppiert sie in
|
||||
`li.opt-group > ul.opt-stair` und nummeriert die Stufen als `--i` (0-basiert).
|
||||
Geometrie: Stufe 0 hängt am Stiel von oben wie ein gewöhnliches Kind
|
||||
(`--stem-x` zielt auf sie). Jede weitere Stufe steht `--stair-step` weiter
|
||||
rechts und hängt an einem gestrichelten Winkel, der bündig an der linken
|
||||
Kante der VORIGEN Stufe herabfällt und waagerecht in den eigenen Kreis
|
||||
einbiegt. Das setzt voraus, dass die Zelle so hoch ist wie ihr Knoten —
|
||||
deshalb nur Endknoten. */
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair{
|
||||
--stair-step:26px; --stair-gap:12px;
|
||||
display:flex;flex-direction:column;align-items:flex-start;position:relative;
|
||||
}
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li{
|
||||
align-items:flex-start;
|
||||
padding:var(--stair-gap) 0 0 calc(var(--i) * var(--stair-step));
|
||||
}
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child{padding-top:0}
|
||||
/* Winkel zur vorigen Stufe: senkrecht herab, dann waagerecht in den Knoten.
|
||||
Der Knick liegt auf der linken Kante der vorigen Stufe. */
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before,
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
|
||||
content:'';position:absolute;
|
||||
left:calc((var(--i) - 1) * var(--stair-step));
|
||||
}
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
|
||||
top:0;height:calc(var(--stair-gap) + 16px);
|
||||
border-left:2px dashed var(--line);
|
||||
}
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before{
|
||||
top:calc(var(--stair-gap) + 16px);width:var(--stair-step);
|
||||
border-top:2px dashed var(--line);
|
||||
}
|
||||
/* Stufe 0 wird von oben getroffen (Kreis oben mittig), alle weiteren von
|
||||
links — das ist bereits der Grundfall von `.node.opt::before`. */
|
||||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child>.node.opt::before{
|
||||
left:50%;top:-5px;transform:translateX(-50%);
|
||||
}
|
||||
|
||||
/* ---------- Status (Pastell) ---------- */
|
||||
.node.st-idee {background:#EBEDEF;border-color:#A2ABB5;color:var(--ink)}
|
||||
.node.st-geplant {background:#EBE4F6;border-color:#A991D4;color:var(--ink)}
|
||||
|
||||
Reference in New Issue
Block a user