fix: eine einzelne Station ist ein gültiger Pfad (D38-Nachtrag 3)
drawCheapPath() stieg bei weniger als zwei Stationen ganz aus. Solange ein eingeklappter Knoten nur einen Zweig vertrat, fiel das nicht auf; klappt man den Wurzelknoten zu, bleibt genau eine sichtbare Station — und damit verschwand neben der Linie (richtig) auch der Stationspunkt (falsch), also der Pfad genau dort, wo die Faltung ihn am nötigsten vertreten muss. Die Zwei-Punkte-Schranke gehört an die Linie, nicht an die Zeichenfunktion; im Grafikexport dieselbe Trennung. Dabei gefunden: Der zugeklappte Wurzelknoten des Werkbaum-Plans ließ sich nicht wieder aufklappen. Dort ist der Zustand nicht in Marken ausdrückbar (das `<` in Zeile 160 holt seinen Teilbaum hervor), liegt also in der Sitzungs-Überlagerung. Beim Aufklappen ergab das einen unveränderten Text — replaceTextUndoable() meldete Erfolg, ohne Textänderung feuert aber kein input-Ereignis, also lief kein render(). Jetzt meldet sie „nichts geschrieben", und der Aufrufer zeichnet selbst neu. Außerdem: Faltmarken-Stellung auch in den Schreibregeln von llms.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
be729239ce
commit
9cbdf5ab58
@@ -2386,6 +2386,37 @@ Der Preis ist benannt und klein: Ein eingeklappter Zweig zeigt **eine** Station
|
|||||||
statt mehrerer. Das ist keine Ungenauigkeit, sondern die Aussage der Faltung —
|
statt mehrerer. Das ist keine Ungenauigkeit, sondern die Aussage der Faltung —
|
||||||
„hier drin liegt noch Pfad", genauer geht es ohne Aufklappen nicht.
|
„hier drin liegt noch Pfad", genauer geht es ohne Aufklappen nicht.
|
||||||
|
|
||||||
|
**Nachtrag 3 — eine einzelne Station ist ein gültiger Pfad.** Der Nachtrag
|
||||||
|
oben behob den Fall „eingeklappter Zweig wird übersprungen“ an der
|
||||||
|
Klassenvergabe (`cheapCls`) — und ließ dabei eine zweite, ältere Schranke
|
||||||
|
stehen: `drawCheapPath()` stieg bei weniger als **zwei** Stationen ganz aus.
|
||||||
|
Solange jeder eingeklappte Knoten nur *einen* Zweig vertrat, fiel das nicht
|
||||||
|
auf. Es fällt auf, sobald weit oben gefaltet wird: Klappt man den
|
||||||
|
**Wurzelknoten** des Werkbaum-Plans zu, bleibt genau eine sichtbare Station —
|
||||||
|
und damit verschwand nicht nur die Linie (richtig, durch einen Punkt führt
|
||||||
|
keine), sondern auch der **Stationspunkt** (falsch). Der Pfad war ausgerechnet
|
||||||
|
dort ganz weg, wo die Faltung ihn am nötigsten vertreten muss. Nachgemessen
|
||||||
|
vor der Korrektur: Knoten `root-node cheap cheap-leaf folded`, Stationspunkte
|
||||||
|
**0**; danach 1 Punkt, 0 Linien, im Grafikexport dasselbe (1 Kreis, keine
|
||||||
|
Pfadlinie).
|
||||||
|
|
||||||
|
Die Schranke gehört an die **Linie**, nicht an die Zeichenfunktion: `catmullRom`
|
||||||
|
braucht zwei Punkte, die Punkte brauchen einander nicht. Dieselbe Trennung im
|
||||||
|
Export (`cheapPts.length >= 2` nur noch für die Linie).
|
||||||
|
|
||||||
|
**Dabei gefunden: der zugeklappte Wurzelknoten ließ sich nicht wieder
|
||||||
|
aufklappen.** Ein eigener Fehler, nur über denselben Weg erreichbar. Im
|
||||||
|
Werkbaum-Plan ist „Wurzel eingeklappt“ **nicht in Marken ausdrückbar** — das
|
||||||
|
`<` in Zeile 160 holt seinen Teilbaum immer wieder hervor —, der Zustand liegt
|
||||||
|
also in der Sitzungs-Überlagerung (der dritte Fall aus Nachtrag 2). Beim
|
||||||
|
**Auf**klappen berechnete `writeFoldToText()` dann einen Text, der mit dem
|
||||||
|
vorhandenen identisch war: nichts zu schreiben. `replaceTextUndoable()` meldete
|
||||||
|
dafür Erfolg — aber ohne Textänderung feuert kein `input`-Ereignis, also lief
|
||||||
|
kein `render()`, und das Bild blieb stehen, wie es war. Jeder weitere Klick tat
|
||||||
|
dasselbe. Jetzt meldet die Funktion „nichts geschrieben“, und der Aufrufer
|
||||||
|
zeichnet selbst neu. Nachgemessen: 144 Knoten/69 Punkte → 1/1 → 144/69 → 1/1
|
||||||
|
über mehrere Klicks.
|
||||||
|
|
||||||
## D39 — Effektiver Status: die Farbe sagt die Wahrheit, die Marke die Box
|
## D39 — Effektiver Status: die Farbe sagt die Wahrheit, die Marke die Box
|
||||||
Mit den Abhängigkeiten (D37) gibt es zwei Aussagen je Knoten: was in der
|
Mit den Abhängigkeiten (D37) gibt es zwei Aussagen je Knoten: was in der
|
||||||
Statusbox steht (intrinsisch) und wie weit er wirklich ist (effektiv, §4).
|
Statusbox steht (intrinsisch) und wie weit er wirklich ist (effektiv, §4).
|
||||||
|
|||||||
+5
-1
@@ -710,7 +710,11 @@ fokussierten Knoten (WAI-ARIA-Baum-Idiom).
|
|||||||
Endknoten darunter. Das gilt auch, wenn der eingeklappte Knoten selbst nicht
|
Endknoten darunter. Das gilt auch, wenn der eingeklappte Knoten selbst nicht
|
||||||
gebraucht wird, sein Teilbaum aber schon (etwa ein per `:#…` gezogenes Ziel):
|
gebraucht wird, sein Teilbaum aber schon (etwa ein per `:#…` gezogenes Ziel):
|
||||||
Er ist dann der einzige sichtbare Griff auf nötige Arbeit und tritt deshalb
|
Er ist dann der einzige sichtbare Griff auf nötige Arbeit und tritt deshalb
|
||||||
auch nicht zurück.
|
auch nicht zurück. Bleibt so nur **eine** sichtbare Station übrig (etwa bei
|
||||||
|
eingeklapptem Wurzelknoten), entfällt die **Linie** — durch einen einzelnen
|
||||||
|
Punkt führt keine —, der **Stationspunkt bleibt**; sonst verschwände der
|
||||||
|
Pfad ausgerechnet dort ganz, wo der Baum am dichtesten gefaltet ist. Gilt
|
||||||
|
auch im Grafikexport.
|
||||||
- **Export und Druck folgen der sichtbaren Faltung** (dieselbe Regel wie beim
|
- **Export und Druck folgen der sichtbaren Faltung** (dieselbe Regel wie beim
|
||||||
„verworfene einblenden“-Filter): Verborgene Teilbäume fehlen, eingeklappte
|
„verworfene einblenden“-Filter): Verborgene Teilbäume fehlen, eingeklappte
|
||||||
Knoten behalten die Kennzeichnung „▸ n“ — das Bild behauptet damit keine
|
Knoten behalten die Kennzeichnung „▸ n“ — das Bild behauptet damit keine
|
||||||
|
|||||||
+9
-2
@@ -316,7 +316,10 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
|||||||
Pfad-Mitgliedschaft (ein per `:#…` gezogenes Ziel unter einem `+`-Knoten):
|
Pfad-Mitgliedschaft (ein per `:#…` gezogenes Ziel unter einem `+`-Knoten):
|
||||||
Dann ist er der einzige sichtbare Griff darauf und zählt als `cheap`.
|
Dann ist er der einzige sichtbare Griff darauf und zählt als `cheap`.
|
||||||
`extraCls()` muss dieselbe Eingeklappt-Bedingung bilden wie `itemHtml`
|
`extraCls()` muss dieselbe Eingeklappt-Bedingung bilden wie `itemHtml`
|
||||||
(nur mit sichtbaren Kindern).
|
(nur mit sichtbaren Kindern). **Eine einzelne Station ist ein gültiger Pfad**
|
||||||
|
(D38-Nachtrag 3): `drawCheapPath()` steigt nur bei **null** Stationen aus,
|
||||||
|
die Zwei-Punkte-Schranke gilt allein der Linie (`catmullRom`) — im Export
|
||||||
|
ebenso. Sonst verschwindet der Pfad beim eingeklappten Wurzelknoten ganz.
|
||||||
**Umklappen schreibt in den TEXT zurück** (`writeFoldToText`, D38-Nachtrag 2):
|
**Umklappen schreibt in den TEXT zurück** (`writeFoldToText`, D38-Nachtrag 2):
|
||||||
Die Ableitung Text → Zustand ist nicht umkehrbar (mehrere Markensätze ergeben
|
Die Ableitung Text → Zustand ist nicht umkehrbar (mehrere Markensätze ergeben
|
||||||
denselben Zustand; `<` faltet Knoten ohne eigene Marke), deshalb **minimal
|
denselben Zustand; `<` faltet Knoten ohne eigene Marke), deshalb **minimal
|
||||||
@@ -332,7 +335,11 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der
|
|||||||
Mobil die Tastatur hoch (`keyboardOnJump(true)`). Bei `src.readOnly` (Pad,
|
Mobil die Tastatur hoch (`keyboardOnJump(true)`). Bei `src.readOnly` (Pad,
|
||||||
D31) wird nicht geschrieben, dort trägt weiter `foldOverrides` — die
|
D31) wird nicht geschrieben, dort trägt weiter `foldOverrides` — die
|
||||||
Überlagerungen werden nach erfolgreichem Schreiben geleert, sonst maskieren
|
Überlagerungen werden nach erfolgreichem Schreiben geleert, sonst maskieren
|
||||||
sie den Text. Umklappen: Klick aufs
|
sie den Text. **`replaceTextUndoable()` meldet bei unveränderten Text
|
||||||
|
`false`** (D38-Nachtrag 3): Ohne Textänderung feuert kein `input`, also läuft
|
||||||
|
kein `render()` — der Aufrufer muss dann selbst zeichnen. Sonst bleibt das
|
||||||
|
Bild stehen (aufgefallen beim Aufklappen eines nur per Überlagerung
|
||||||
|
gefalteten Knotens: der Klick tat sichtbar nichts mehr). Umklappen: Klick aufs
|
||||||
`.fold`-Zeichen (preventDefault — es sitzt bei Link-Knoten im `<a>`) oder
|
`.fold`-Zeichen (preventDefault — es sitzt bei Link-Knoten im `<a>`) oder
|
||||||
←/→ am fokussierten Knoten; nach `render()` den Fokus per `data-line`
|
←/→ am fokussierten Knoten; nach `render()` den Fokus per `data-line`
|
||||||
wiederherstellen. Export/Druck: „▸ n" bleibt, das ▾ offener Knoten fällt weg
|
wiederherstellen. Export/Druck: „▸ n" bleibt, das ▾ offener Knoten fällt weg
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ One node per line. Everything except the label is optional.
|
|||||||
- Never mix `|` or `=` with other gates in one sibling group.
|
- Never mix `|` or `=` with other gates in one sibling group.
|
||||||
- Decompose everything `(M)` or larger.
|
- Decompose everything `(M)` or larger.
|
||||||
- Put a node ID in front of the title, separated by a colon: `#auth: Backend`.
|
- Put a node ID in front of the title, separated by a colon: `#auth: Backend`.
|
||||||
|
- Put a fold mark after the status box, right before the label:
|
||||||
|
`- [x] > Concept`. Before the box it would shift the box by one indent step.
|
||||||
- Give a node its own status only; express "blocked by" with `:#…`
|
- Give a node its own status only; express "blocked by" with `:#…`
|
||||||
dependencies instead of understating the status.
|
dependencies instead of understating the status.
|
||||||
- Mark nice-to-haves with `+` — otherwise they inflate the cheapest path.
|
- Mark nice-to-haves with `+` — otherwise they inflate the cheapest path.
|
||||||
|
|||||||
+26
-13
@@ -275,7 +275,12 @@ 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;
|
||||||
const leaves = [...out.querySelectorAll('.node.cheap-leaf')]; /* Dokument-Reihenfolge = Lese-Reihenfolge */
|
const leaves = [...out.querySelectorAll('.node.cheap-leaf')]; /* Dokument-Reihenfolge = Lese-Reihenfolge */
|
||||||
if(leaves.length < 2) return;
|
/* EINE Station ist ein gültiger Pfad: Ein eingeklappter Knoten vertritt
|
||||||
|
seinen ganzen Teilbaum (D38-Nachtrag), oben im Baum bleibt davon leicht
|
||||||
|
nur eine einzige sichtbare Station übrig. Die Linie entfällt dann (durch
|
||||||
|
einen Punkt führt keine), der Stationspunkt darf es nicht — sonst
|
||||||
|
verschwände der Pfad genau dort ganz, wo er am dichtesten gefaltet ist. */
|
||||||
|
if(!leaves.length) return;
|
||||||
const outRect = out.getBoundingClientRect();
|
const outRect = out.getBoundingClientRect();
|
||||||
const z = effZoom() || 1;
|
const z = effZoom() || 1;
|
||||||
if(!outRect.width || !outRect.height) return; /* Panel eingeklappt */
|
if(!outRect.width || !outRect.height) return; /* Panel eingeklappt */
|
||||||
@@ -284,16 +289,18 @@ function drawCheapPath(){
|
|||||||
return {x:(r.left + r.width/2 - outRect.left)/z, y:(r.top + r.height/2 - outRect.top)/z};
|
return {x:(r.left + r.width/2 - outRect.left)/z, y:(r.top + r.height/2 - outRect.top)/z};
|
||||||
});
|
});
|
||||||
const w = outRect.width/z, h = outRect.height/z;
|
const w = outRect.width/z, h = outRect.height/z;
|
||||||
const d = catmullRom(pts);
|
const d = pts.length > 1 ? catmullRom(pts) : null;
|
||||||
|
|
||||||
/* kräftige Linie HINTER die Knoten (als erstes Kind → hinterste Paint-Ebene) */
|
/* kräftige Linie HINTER die Knoten (als erstes Kind → hinterste Paint-Ebene) */
|
||||||
const back = overlaySvg('cheap-back', w, h);
|
if(d){
|
||||||
back.appendChild(svgEl('path', {class:'cheap-path', d}));
|
const back = overlaySvg('cheap-back', w, h);
|
||||||
out.insertBefore(back, out.firstChild);
|
back.appendChild(svgEl('path', {class:'cheap-path', d}));
|
||||||
|
out.insertBefore(back, out.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
/* davor: abgetönte Kopie (deutet den Verlauf über Knoten an) + Stationspunkte */
|
/* davor: abgetönte Kopie (deutet den Verlauf über Knoten an) + Stationspunkte */
|
||||||
const front = overlaySvg('cheap-front', w, h);
|
const front = overlaySvg('cheap-front', w, h);
|
||||||
front.appendChild(svgEl('path', {class:'cheap-path faint', d}));
|
if(d) front.appendChild(svgEl('path', {class:'cheap-path faint', d}));
|
||||||
pts.forEach(p => front.appendChild(
|
pts.forEach(p => front.appendChild(
|
||||||
svgEl('circle', {class:'cheap-dot', cx:p.x.toFixed(1), cy:p.y.toFixed(1), r:10})));
|
svgEl('circle', {class:'cheap-dot', cx:p.x.toFixed(1), cy:p.y.toFixed(1), r:10})));
|
||||||
out.appendChild(front);
|
out.appendChild(front);
|
||||||
@@ -574,12 +581,13 @@ function diagramToSvg(){
|
|||||||
parts.push(`<text x="${m.x.toFixed(1)}" y="${(m.y+3).toFixed(1)}" text-anchor="middle" fill="#6B7A8C" font-size="9" font-weight="600">1</text>`);
|
parts.push(`<text x="${m.x.toFixed(1)}" y="${(m.y+3).toFixed(1)}" text-anchor="middle" fill="#6B7A8C" font-size="9" font-weight="600">1</text>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* 3b) Günstigster-Pfad: abgetönte Kopie über den Knoten + Stationspunkte */
|
/* 3b) Günstigster-Pfad: abgetönte Kopie über den Knoten + Stationspunkte.
|
||||||
if(cheapPts.length >= 2){
|
Die Linie braucht zwei Punkte, die Stationen nicht — bei stark gefaltetem
|
||||||
parts.push(cheapLine('0.2'));
|
Baum bleibt leicht nur eine sichtbare Station übrig (siehe
|
||||||
cheapPts.forEach(p => parts.push(
|
`drawCheapPath`). */
|
||||||
`<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="10" fill="#0F766E" fill-opacity="0.2" stroke="#0F766E" stroke-opacity="0.35" stroke-width="1.5"/>`));
|
if(cheapPts.length >= 2) parts.push(cheapLine('0.2'));
|
||||||
}
|
cheapPts.forEach(p => parts.push(
|
||||||
|
`<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="10" fill="#0F766E" fill-opacity="0.2" stroke="#0F766E" stroke-opacity="0.35" stroke-width="1.5"/>`));
|
||||||
|
|
||||||
/* 4) Geister-Knoten „Untergliederung fehlt“ */
|
/* 4) Geister-Knoten „Untergliederung fehlt“ */
|
||||||
out.querySelectorAll('.ghost-node').forEach(g => {
|
out.querySelectorAll('.ghost-node').forEach(g => {
|
||||||
@@ -898,7 +906,12 @@ function foldStateMatches(txt, want){
|
|||||||
Stück zwischen gemeinsamem Anfang und Ende. */
|
Stück zwischen gemeinsamem Anfang und Ende. */
|
||||||
function replaceTextUndoable(neu){
|
function replaceTextUndoable(neu){
|
||||||
const alt = src.value;
|
const alt = src.value;
|
||||||
if(alt === neu) return true;
|
/* Nichts zu schreiben heißt: kein `input`-Ereignis, also auch kein render().
|
||||||
|
Deshalb false — der Aufrufer zeichnet dann selbst neu. Sonst bliebe das
|
||||||
|
Bild stehen, wie es war: Der Fall tritt auf, wenn die Marken den
|
||||||
|
gewünschten Zustand schon beschreiben (etwa beim Aufklappen eines Knotens,
|
||||||
|
dessen Faltung nur in der Sitzungs-Überlagerung stand). */
|
||||||
|
if(alt === neu) return false;
|
||||||
let s = 0;
|
let s = 0;
|
||||||
while(s < alt.length && s < neu.length && alt[s] === neu[s]) s++;
|
while(s < alt.length && s < neu.length && alt[s] === neu[s]) s++;
|
||||||
let e = 0;
|
let e = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user