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
+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):
|
||||
Dann ist er der einzige sichtbare Griff darauf und zählt als `cheap`.
|
||||
`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):
|
||||
Die Ableitung Text → Zustand ist nicht umkehrbar (mehrere Markensätze ergeben
|
||||
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,
|
||||
D31) wird nicht geschrieben, dort trägt weiter `foldOverrides` — die
|
||||
Ü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
|
||||
←/→ am fokussierten Knoten; nach `render()` den Fokus per `data-line`
|
||||
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.
|
||||
- Decompose everything `(M)` or larger.
|
||||
- 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 `:#…`
|
||||
dependencies instead of understating the status.
|
||||
- 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());
|
||||
if(!cheapPathOn) return;
|
||||
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 z = effZoom() || 1;
|
||||
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};
|
||||
});
|
||||
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) */
|
||||
const back = overlaySvg('cheap-back', w, h);
|
||||
back.appendChild(svgEl('path', {class:'cheap-path', d}));
|
||||
out.insertBefore(back, out.firstChild);
|
||||
if(d){
|
||||
const back = overlaySvg('cheap-back', w, h);
|
||||
back.appendChild(svgEl('path', {class:'cheap-path', d}));
|
||||
out.insertBefore(back, out.firstChild);
|
||||
}
|
||||
|
||||
/* davor: abgetönte Kopie (deutet den Verlauf über Knoten an) + Stationspunkte */
|
||||
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(
|
||||
svgEl('circle', {class:'cheap-dot', cx:p.x.toFixed(1), cy:p.y.toFixed(1), r:10})));
|
||||
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>`);
|
||||
});
|
||||
|
||||
/* 3b) Günstigster-Pfad: abgetönte Kopie über den Knoten + Stationspunkte */
|
||||
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"/>`));
|
||||
}
|
||||
/* 3b) Günstigster-Pfad: abgetönte Kopie über den Knoten + Stationspunkte.
|
||||
Die Linie braucht zwei Punkte, die Stationen nicht — bei stark gefaltetem
|
||||
Baum bleibt leicht nur eine sichtbare Station übrig (siehe
|
||||
`drawCheapPath`). */
|
||||
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“ */
|
||||
out.querySelectorAll('.ghost-node').forEach(g => {
|
||||
@@ -898,7 +906,12 @@ function foldStateMatches(txt, want){
|
||||
Stück zwischen gemeinsamem Anfang und Ende. */
|
||||
function replaceTextUndoable(neu){
|
||||
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;
|
||||
while(s < alt.length && s < neu.length && alt[s] === neu[s]) s++;
|
||||
let e = 0;
|
||||
|
||||
Reference in New Issue
Block a user