fix: vertikale Sammelleiste erreicht den Eltern-Stub wieder (D65)
Zwei Geometrie-Fehler bei Gruppen mit wenigen Kindern: :only-child schaltete die Leiste ab, obwohl Stub (50 %) und Abzweig (23 px) seit dem 20-px- Zusatzabstand nicht mehr zusammenfallen (6,6–15,8 px Lücke); und bei einem großen Teilbaum im letzten Kind endete die Leiste über dem Stub (bis 98 px). Fix: CSS-Verbinder 23px→50 % am Einzelkind, alignVRails() + --vrail-ext für das letzte Kind. Export und Kompakt-Modus waren nie betroffen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
94aa16efbd
commit
07e4116bd9
@@ -19,6 +19,7 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-24
|
## 2026-08-24
|
||||||
|
|
||||||
|
- 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
|
- The legend shows the high-risk status `[!]` and mentions the size check
|
||||||
- `llms.md` tabulates the size ranges and aligns its table columns
|
- `llms.md` tabulates the size ranges and aligns its table columns
|
||||||
- Long node titles wrap into evenly balanced lines of at most forty characters
|
- Long node titles wrap into evenly balanced lines of at most forty characters
|
||||||
|
|||||||
@@ -4842,3 +4842,77 @@ mitten im Wort, `\n` im Knotentext); die showIds-Tests sind auf die eigene
|
|||||||
Zeile umgeschrieben. Der neue Plan-Knoten `#ed.render.wrap (S)` kippte prompt
|
Zeile umgeschrieben. Der neue Plan-Knoten `#ed.render.wrap (S)` kippte prompt
|
||||||
die Größenprüfung des eigenen Plans (`#ed.render (M)` mit nun 4×S, D62) —
|
die Größenprüfung des eigenen Plans (`#ed.render (M)` mit nun 4×S, D62) —
|
||||||
ehrlich nachgezogen auf `(L)`, danach wieder 0 Warnungen.
|
ehrlich nachgezogen auf `(L)`, danach wieder 0 Warnungen.
|
||||||
|
|
||||||
|
## D65 — Abgerissene Linien im vertikalen Modus: Geometrie-Fehler, kein Rendering-Problem
|
||||||
|
Gemeldet: „Es geschieht immer wieder, dass die Verbindungslinien zu Sub-Knoten
|
||||||
|
nicht durchgängig sind, sondern Lücken haben — meistens fehlen in der
|
||||||
|
vertikalen Ansicht kurze vertikale Linien", mit der Frage, ob das stabil zu
|
||||||
|
fixen sei oder ein Umstieg auf Canvas-Rendering nötig würde. Der entscheidende
|
||||||
|
Hinweis kam nachgereicht: „meistens, wenn es nur einen einzigen oder zwei
|
||||||
|
Unterknoten gibt."
|
||||||
|
|
||||||
|
**Es ist kein Rundungs- oder Rendering-Problem, sondern ein deterministischer
|
||||||
|
Geometrie-Fehler** — gemessen statt geraten: Ein Scanner über die
|
||||||
|
Pseudo-Element-Geometrie aller 45 vertikalen all-of-Gruppen des mitgelieferten
|
||||||
|
Plans fand **8 kaputte**, alle mit 1–3 Kindern, alle exakt reproduzierbar.
|
||||||
|
Zwei Fehlerarten, eine gemeinsame Wurzel: **Der Eltern-Stub dockt bei 50 % der
|
||||||
|
Gruppenhöhe an** (`li.has-and{align-items:center}`, D9), **die Sammelleiste
|
||||||
|
endet aber am Abzweigpunkt des Rand-Kindes** — und nichts garantierte, dass
|
||||||
|
die 50 % dazwischen liegen.
|
||||||
|
|
||||||
|
- **Einziges Kind:** `li:only-child::after{border:0}` schaltete die Leiste ganz
|
||||||
|
ab — in der Annahme, Stub (50 %) und Kind-Abzweig (fest 23 px) fielen
|
||||||
|
zusammen. Das taten sie, solange das `<li>` symmetrisch gepolstert war
|
||||||
|
(5+5 px: Mitte 22,15 ≈ 23). Der **20-px-Zusatzabstand nach unten**
|
||||||
|
(D-Transponiert, gegen Badge/Tag-Überlappung) verschob die Mitte auf 29,65 —
|
||||||
|
**6,6 px Lücke**; mehrzeilige Knoten (D64) machten daraus **15,8 px**. Der
|
||||||
|
Fehler war also alt und wurde schrittweise sichtbarer — daher „immer wieder".
|
||||||
|
- **Letztes Kind mit großem Teilbaum:** Die Leiste läuft vom ersten bis zum
|
||||||
|
letzten Abzweig (je 23 px unter der Zellen-Oberkante). Trägt das letzte Kind
|
||||||
|
einen großen Teilbaum, liegt die Gruppen-**Mitte** unterhalb seines Abzweigs
|
||||||
|
— der Stub hing frei in der Luft (gemessen: 4,5 bis **98 px**).
|
||||||
|
|
||||||
|
**Fix in zwei Teilen, je auf dem billigsten tragfähigen Weg:**
|
||||||
|
|
||||||
|
- **Einziges Kind rein in CSS:** Bei `:only-child` ist die Gruppenhöhe die
|
||||||
|
Kindhöhe (`padding-top:0`), 50 % ist also im `<li>` ausdrückbar —
|
||||||
|
`top:23px; height:max(0px, calc(50% - 23px))` verbindet Abzweig und Stub
|
||||||
|
exakt. Für ein has-and-Einzelkind bleibt `border:0` (Abzweig liegt dort
|
||||||
|
selbst bei 50 %, beides fällt zusammen).
|
||||||
|
- **Letztes Kind per Messung:** Die Gruppenmitte relativ zum letzten `<li>`
|
||||||
|
kann CSS nicht ausdrücken — dieselbe Lage wie bei `--stem-x`
|
||||||
|
(D29-Nachtrag 2), also derselbe Griff: `alignVRails()` misst nach jedem
|
||||||
|
Rendern/Moduswechsel und setzt `--vrail-ext` (unskalierte px, durch
|
||||||
|
`effZoom()` zurückgerechnet); die CSS-Regel
|
||||||
|
`li:last-child:not(.has-and):not(:only-child)::after{height:var(--vrail-ext, 23px)}`
|
||||||
|
verlängert die Leiste bis zum Stub. **has-and-Letztkinder brauchen das
|
||||||
|
nie** — deren Abzweig liegt bei 50 % ihrer Zelle, und die Gruppenmitte kann
|
||||||
|
rechnerisch nie darunter liegen (H/2 > H − h/2 hieße h > H). Nach **oben**
|
||||||
|
kann die Mitte ebenfalls nie herausfallen (der erste Abzweig liegt höchstens
|
||||||
|
23 px unter dem Gruppenanfang, und H/2 ≥ 23 gilt ab 46 px Gruppenhöhe —
|
||||||
|
ein einzelner Knoten ist schon höher).
|
||||||
|
|
||||||
|
**Canvas (oder eine SVG-Volleinzeichnung) ist damit nicht nötig.** Die Frage
|
||||||
|
war berechtigt — viele einzeln positionierte Border-Segmente sind die
|
||||||
|
fehleranfälligere Bauart als ein durchgezogener Pfad —, aber der konkrete
|
||||||
|
Fehler lag in zwei falschen Annahmen der Geometrie, nicht im Mechanismus.
|
||||||
|
Ein Umstieg kostete die CSS-gestützte Selbstverständlichkeit von Fokus,
|
||||||
|
Hover, Zoom und Druck und müsste alle über D9–D64 austarierten Sonderfälle
|
||||||
|
(Treppe, only-child-Leiterstück, has-and-Zentrierung) neu beweisen. Sollte
|
||||||
|
je das **zweite** Phänomen auftreten — 1-px-Haarlinien an Segment-Stößen
|
||||||
|
unter `zoom` ≠ 1 —, wäre das ein eigener Fall mit eigenem Mittel
|
||||||
|
(Segmente an Stößen minimal überlappen lassen), kein Grund für einen Umbau.
|
||||||
|
|
||||||
|
**Export und Kompakt-Modus waren nie betroffen:** `diagramToSvg()` spannt die
|
||||||
|
Leiste seit jeher über die Kinder **und** die Elternmitte
|
||||||
|
(`kids.map(cy).concat(p.cy)`, D29-Nachtrag 4); im kompakten Modus dockt der
|
||||||
|
Stub oben an (kein Zentrieren). Beides nachgemessen (Scanner: 0 Befunde in
|
||||||
|
45 Kompakt-Gruppen; 0 verwaiste `--vrail-ext` nach Moduswechsel).
|
||||||
|
|
||||||
|
**Nachgemessen** nach dem Fix (mitgelieferter Plan, vertikal): 0 Befunde in
|
||||||
|
45 Gruppen; der Only-Child-Verbinder endet auf 0,0 px genau am Stub
|
||||||
|
(336,5 → 352,3 = Stub-Höhe); die 98-px-Lücke trägt jetzt eine
|
||||||
|
121-px-Verlängerung bis zum Stub; Zoom-Gegenprobe bei 0,9 sauber (die Variable
|
||||||
|
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).
|
||||||
|
|||||||
@@ -474,6 +474,18 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
|||||||
müssen **hinter** den first/last-Regeln stehen: Ein einziges Kind ist auch
|
müssen **hinter** den first/last-Regeln stehen: Ein einziges Kind ist auch
|
||||||
das letzte, und bei gleicher Spezifität gewinnt die spätere Regel — genau
|
das letzte, und bei gleicher Spezifität gewinnt die spätere Regel — genau
|
||||||
daran war `:only-child::after` jahrelang wirkungslos.
|
daran war `:only-child::after` jahrelang wirkungslos.
|
||||||
|
- `--vrail-ext` (D65): Im **vertikalen** Modus dockt der Eltern-Stub bei 50 %
|
||||||
|
der Gruppenhöhe an, die Sammelleiste endet aber am 23-px-Abzweig des letzten
|
||||||
|
Kindes — trägt das einen großen Teilbaum, liegt die Gruppenmitte darunter
|
||||||
|
und der Stub hinge in der Luft. `alignVRails()` (läuft an denselben drei
|
||||||
|
Stellen wie `alignStems()`) misst das und verlängert die Leiste per
|
||||||
|
`--vrail-ext`; das **einzige** Kind löst CSS allein
|
||||||
|
(`:only-child::after{top:23px;height:calc(50% - 23px)}` — dort ist
|
||||||
|
Gruppenhöhe == Kindhöhe). Kein `border:0` mehr am nicht-has-and-Einzelkind:
|
||||||
|
Genau das war die Lücke („kurze vertikale Linien fehlen"). has-and-Kinder
|
||||||
|
brauchen beides nie (Abzweig bei 50 % der Zelle). Wer an den
|
||||||
|
vertikalen first/last/only-Regeln dreht, prüft mit dem Geometrie-Scanner
|
||||||
|
(Pseudo-Element-Rects: Leisten-Segmente vs. Stub-Höhe) statt mit dem Auge.
|
||||||
- Kleiner Bildschirm: `body.mobile` (per `matchMedia`, ≤ 640 px) zeigt **genau
|
- Kleiner Bildschirm: `body.mobile` (per `matchMedia`, ≤ 640 px) zeigt **genau
|
||||||
einen** Bereich — `body.pane-diagram` bzw. `body.pane-text`, umgeschaltet über
|
einen** Bereich — `body.pane-diagram` bzw. `body.pane-text`, umgeschaltet über
|
||||||
je einen festen Knopf pro Titelzeile (`#paneToText`/`#paneToDiagram`,
|
je einen festen Knopf pro Titelzeile (`#paneToText`/`#paneToDiagram`,
|
||||||
|
|||||||
+29
-1
@@ -206,6 +206,7 @@ function render(){
|
|||||||
renderLineNos();
|
renderLineNos();
|
||||||
applyOptStairs(); /* muss vor dem Messen laufen — es verschiebt Knoten */
|
applyOptStairs(); /* muss vor dem Messen laufen — es verschiebt Knoten */
|
||||||
alignStems();
|
alignStems();
|
||||||
|
alignVRails();
|
||||||
drawCheapPath();
|
drawCheapPath();
|
||||||
/* Querverbindungen (D41) zeichnet highlightCurrentNode() unten mit —
|
/* Querverbindungen (D41) zeichnet highlightCurrentNode() unten mit —
|
||||||
es kennt die zweite Hälfte der Auswahl (Cursor-Zeile). */
|
es kennt die zweite Hälfte der Auswahl (Cursor-Zeile). */
|
||||||
@@ -332,6 +333,32 @@ function alignStems(){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sammelleisten-Verlängerung im vertikalen Modus (D65). Der Eltern-Stub dockt
|
||||||
|
bei 50 % der Gruppenhöhe an, die Leiste endet aber am 23-px-Abzweig des
|
||||||
|
letzten Kindes — trägt das einen großen Teilbaum, liegt die Gruppenmitte
|
||||||
|
TIEFER und der Stub hinge in der Luft (gemessen: 4,5 bis 98 px Lücke). CSS
|
||||||
|
kann die Gruppenmitte relativ zum letzten <li> nicht ausdrücken; wie bei
|
||||||
|
`alignStems()` misst deshalb JS und setzt `--vrail-ext` (unskalierte px,
|
||||||
|
durch effZoom() zurückgerechnet). Nur nicht-has-and-Letztkinder: Bei
|
||||||
|
has-and liegt der Abzweig bei 50 % der Zelle, und die Gruppenmitte liegt
|
||||||
|
beweisbar nie darunter. Nach oben kann die Mitte nie aus der Leiste fallen
|
||||||
|
(der erste Abzweig liegt höchstens 23 px unter dem Gruppenanfang). */
|
||||||
|
function alignVRails(){
|
||||||
|
out.querySelectorAll('ul.and>li').forEach(li => li.style.removeProperty('--vrail-ext'));
|
||||||
|
if(!out.classList.contains('vertical')) return;
|
||||||
|
const z = effZoom() || 1;
|
||||||
|
out.querySelectorAll('li.has-and>ul.and').forEach(ul => {
|
||||||
|
const kids = [...ul.children].filter(e => e.tagName === 'LI');
|
||||||
|
if(kids.length < 2) return; /* :only-child löst CSS allein (50 %) */
|
||||||
|
const last = kids[kids.length - 1];
|
||||||
|
if(last.classList.contains('has-and')) return;
|
||||||
|
const ur = ul.getBoundingClientRect(), lr = last.getBoundingClientRect();
|
||||||
|
if(!ur.height) return; /* Panel eingeklappt */
|
||||||
|
const ext = (ur.top + ur.height/2 - lr.top)/z;
|
||||||
|
if(ext > 23.5) last.style.setProperty('--vrail-ext', ext.toFixed(1) + 'px');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function drawCheapPath(){
|
function drawCheapPath(){
|
||||||
out.querySelectorAll('svg.cheap-overlay').forEach(e => e.remove());
|
out.querySelectorAll('svg.cheap-overlay').forEach(e => e.remove());
|
||||||
if(!cheapPathOn) return;
|
if(!cheapPathOn) return;
|
||||||
@@ -1752,6 +1779,7 @@ function applyLayout(mode){
|
|||||||
if(!isMobile()) applySplit(); /* Desktop: Preset neu setzen. Mobil: freie --drow-Aufteilung behalten */
|
if(!isMobile()) applySplit(); /* Desktop: Preset neu setzen. Mobil: freie --drow-Aufteilung behalten */
|
||||||
applyOptStairs(); /* Treppe gilt nur im Fächer — beim Moduswechsel bauen/auflösen */
|
applyOptStairs(); /* Treppe gilt nur im Fächer — beim Moduswechsel bauen/auflösen */
|
||||||
alignStems(); /* Stiel gilt nur im Fächer — beim Moduswechsel neu setzen/löschen */
|
alignStems(); /* Stiel gilt nur im Fächer — beim Moduswechsel neu setzen/löschen */
|
||||||
|
alignVRails(); /* Leisten-Verlängerung gilt nur vertikal — ebenso (D65) */
|
||||||
drawCheapPath(); /* Blatt-Positionen ändern sich mit dem Modus */
|
drawCheapPath(); /* Blatt-Positionen ändern sich mit dem Modus */
|
||||||
drawDepLinks(); /* Knoten-Positionen ebenso (D41) */
|
drawDepLinks(); /* Knoten-Positionen ebenso (D41) */
|
||||||
}
|
}
|
||||||
@@ -1805,7 +1833,7 @@ function setMobilePane(pane, save){
|
|||||||
Live-Geometrie zeichnet, muss deshalb nach dem Sichtbarwerden neu laufen —
|
Live-Geometrie zeichnet, muss deshalb nach dem Sichtbarwerden neu laufen —
|
||||||
im Diagramm dieselben vier Schritte wie beim Moduswechsel (applyLayout),
|
im Diagramm dieselben vier Schritte wie beim Moduswechsel (applyLayout),
|
||||||
im Editor der Zeilennummern-Streifen, der am Spiegel misst (D33). */
|
im Editor der Zeilennummern-Streifen, der am Spiegel misst (D33). */
|
||||||
if(pane === 'diagram'){ applyOptStairs(); alignStems(); drawCheapPath(); drawDepLinks(); }
|
if(pane === 'diagram'){ applyOptStairs(); alignStems(); alignVRails(); drawCheapPath(); drawDepLinks(); }
|
||||||
else renderLineNos();
|
else renderLineNos();
|
||||||
if(save) saveUI();
|
if(save) saveUI();
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -1317,7 +1317,19 @@
|
|||||||
Linien zu zentrierten Zwischenknoten ins Leere. Die Enden der Sammelleiste
|
Linien zu zentrierten Zwischenknoten ins Leere. Die Enden der Sammelleiste
|
||||||
folgen demselben Abzweigpunkt des jeweiligen Rand-Kindes. */
|
folgen demselben Abzweigpunkt des jeweiligen Rand-Kindes. */
|
||||||
.tree.vertical li.has-and>ul.and>li:first-child::after{top:23px;height:calc(100% - 23px)}
|
.tree.vertical li.has-and>ul.and>li:first-child::after{top:23px;height:calc(100% - 23px)}
|
||||||
.tree.vertical li.has-and>ul.and>li:only-child::after{border:0}
|
/* Einziges Kind: KEIN border:0 mehr (D65). Der Eltern-Stub dockt bei 50 % der
|
||||||
|
Gruppenhöhe an, der Kind-Abzweig liegt fest bei 23 px — seit dem
|
||||||
|
20-px-Zusatzabstand unten (und erst recht bei mehrzeiligen Knoten, D64)
|
||||||
|
fallen die beiden NICHT mehr zusammen. Das Verbindungsstück ist hier rein
|
||||||
|
in CSS ausdrückbar, weil Gruppenhöhe == Kindhöhe (ul hat padding-top:0). */
|
||||||
|
.tree.vertical li.has-and>ul.and>li:only-child::after{top:23px;height:max(0px, calc(50% - 23px))}
|
||||||
|
/* Letztes Kind mit großem Teilbaum (D65): Die Leiste endet am 23-px-Abzweig,
|
||||||
|
der Eltern-Stub dockt aber bei 50 % der GRUPPE an — liegt der tiefer
|
||||||
|
(großer Teilbaum im letzten Kind), hinge er in der Luft. `alignVRails()`
|
||||||
|
in app.js misst das und verlängert die Leiste per --vrail-ext bis zum
|
||||||
|
Stub. has-and-Kinder brauchen das nie (Abzweig bei 50 % der Zelle, und
|
||||||
|
50 % der Gruppe liegt beweisbar darüber). */
|
||||||
|
.tree.vertical li.has-and>ul.and>li:last-child:not(.has-and):not(:only-child)::after{height:var(--vrail-ext, 23px)}
|
||||||
.tree.vertical li.has-and>ul.and>li.has-and::before{top:50%}
|
.tree.vertical li.has-and>ul.and>li.has-and::before{top:50%}
|
||||||
.tree.vertical li.has-and>ul.and>li.has-and:first-child::after{top:50%;height:50%}
|
.tree.vertical li.has-and>ul.and>li.has-and:first-child::after{top:50%;height:50%}
|
||||||
.tree.vertical li.has-and>ul.and>li.has-and:last-child::after{top:0;height:50%}
|
.tree.vertical li.has-and>ul.and>li.has-and:last-child::after{top:0;height:50%}
|
||||||
|
|||||||
Reference in New Issue
Block a user