From 33443016ce37b883694fbd8191c0ae19ba6675e3 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Tue, 25 Aug 2026 17:19:32 +0200 Subject: [PATCH] feat: Querverbindungen folgen der Faltung + Schalter; Falt-Knopf mit vier Voreinstellungen (D75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Abhängigkeits-Kanten enden am nächsten sichtbaren Vorfahren, für Quelle wie Ziel; zusammengefallene Kanten entfallen bzw. werden dedupliziert (data-sub-ids/data-sub-deps am eingeklappten Vertreter, SPEC §9) - Umschalter für die Querverbindungen neben dem Günstigster-Pfad-Knopf, Voreinstellung an, persistiert; Export und Druck folgen ihm - Der Falt-Knopf schaltet durch: M und kleiner zu → alles abseits des Pfads zu → alles zu → alles offen; Icon/Tooltip zeigen den nächsten Schritt, Handfaltung setzt die Position zurück (presetFoldSet, model.js) Co-Authored-By: Claude Fable 5 --- docs/CHANGELOG.md | 3 + docs/DECISIONS.md | 84 ++++++++++++++ docs/SPEC.md | 59 +++++++--- docs/examples/werkbaum.werkbaum | 22 +++- frontend/index.html | 18 ++- frontend/src/app.js | 193 ++++++++++++++++++++++++-------- frontend/src/model.js | 25 +++++ frontend/src/render.js | 35 +++++- frontend/src/style.css | 11 ++ frontend/tests/fold.test.js | 82 +++++++++++++- 10 files changed, 452 insertions(+), 80 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7b5d6bc..d2331f0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,9 @@ reverse. ## 2026-08-25 +- The fold button cycles through four presets now: size M and smaller, everything off the cheapest path, everything, all open +- A button next to the cheapest-path toggle switches the dependency cross links on and off +- Dependency links follow the folding: an edge to a hidden node now ends at its first visible ancestor, for source and target alike - In Brave, the file notice names the flag that enables the File System Access API by hand - Fix: the file notice and the legend blamed "Chromium", but Brave is Chromium without the File System Access API — they now name the feature and example browsers - Browsers without the File System Access API (Firefox, Safari) explain themselves once: files open as copies and saving downloads a new file diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 0877c33..736fc6f 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -5660,3 +5660,87 @@ in den Übersetzungen — eine Stelle statt neun, an denen sie vertippt sein kann. **Nachgemessen** im Dev-Server (Stufe-1-Pfad erzwungen, Brave per `navigator.brave`-Stub zur Laufzeit): mit Stub trägt das Banner die zweite Zeile samt Mono-Code der Adresse, ohne Stub fehlt beides. + +## D75 — Querverbindungen folgen der Faltung und bekommen einen Schalter; der Falt-Knopf schaltet vier Voreinstellungen durch +Drei zusammenhängende Nutzerwünsche an derselben Stelle des Diagramms, in +einem Zug gebaut. + +**1. Abhängigkeits-Kanten enden am nächsten sichtbaren Vorfahren.** D41 ließ +Kanten zu eingeklappten Knoten schlicht entfallen — gerade in einem dicht +gefalteten Plan verschwand damit die Aussage „dieser Zweig braucht jenen" +genau dann, wenn man sie am nötigsten hat. Die Regel gab es längst: **Der +eingeklappte Knoten vertritt seinen Teilbaum** — für die Pfad-Station, den +„▸ n"-Zähler und die Cursor-Zeile (D38-Nachträge). Jetzt gilt sie auch für +die Querverbindungen, für **Quelle wie Ziel** (Nutzer-Vorgabe). Fallen beide +Endpunkte in denselben sichtbaren Knoten, entfällt die Kante (sie sagte +nichts mehr); mehrere so zusammengefallene Kanten desselben Paars werden +**eine**. Kanten zu ausgeblendeten **verworfenen** Knoten entfallen +weiterhin — dieselbe Grenze wie bei der Cursor-Zeile (D38-Nachtrag 4): +Faltung ist Ansicht, der Verworfen-Filter ist eine Aussage über den Plan. + +**Umgesetzt im Renderer, nicht in einer App-Nebenrechnung:** `walkFolded()` +läuft ohnehin durch jeden verborgenen Teilbaum (Warnungen, „▸ n") und sammelt +jetzt dessen IDs und Abhängigkeiten mit; der eingeklappte Knoten trägt sie +als `data-sub-ids`/`data-sub-deps` — getrennt von den eigenen Attributen, +damit die Bedeutung ablesbar bleibt, und headless testbar (Hausregel +D54-Nachtrag 3). `depEdges()` in app.js löst dann nur noch auf: Die +Sub-IDs stehen in DFS- und damit Dokumentreihenfolge, „erste Vergabe +gewinnt" (D36) gilt so auch über die Faltgrenze hinweg. Der Grafikexport +nutzt dasselbe `depEdges()` und folgt ohne Zusatzcode. + +**2. Ein Umschalter für die Querverbindungen**, neben dem +Günstigster-Pfad-Knopf (Nutzer-Vorgabe): Voreinstellung an, persistiert in +`werkbaum-ui` wie die Nachbarn (D22). Export und Druck folgen ihm wie den +übrigen Ansichts-Filtern (die D38/D44/D56-Linie: das Bild zeigt, was +sichtbar ist). Der Klick zeichnet nur die Overlays neu statt zu rendern — +am Baum ändert sich nichts. + +**3. Der Falt-Knopf wird ein Durchschalter mit vier Voreinstellungen** +(Nutzer-Vorgabe, Reihenfolge wie gewünscht): **(1)** ab Größe M abwärts zu +(die D44-Regel, unverändert), **(2)** alles zu, durch dessen Teilbaum der +günstigste Pfad nicht läuft — weder der Knoten selbst noch ein Unterknoten +liegt darauf; sichtbar bleibt genau der Pfad, alles Übrige steht als je ein +eingeklappter Knoten da —, **(3)** alles zu, **(4)** alles offen, dann +wieder von vorn. Jede Stufe beschreibt einen **vollständigen** Faltzustand +(die D44-Eigenschaft bleibt: zweimal Drücken derselben Stufe ergäbe +dasselbe), geschrieben wird über denselben Weg wie bisher — ein +Undo-Schritt je Stufe, beim Pad trägt die Sitzungs-Überlagerung. + +- **Der Knopf zeigt den NÄCHSTEN Schritt** (Icon per `data-next`, Tooltip + aus vier neuen i18n-Schlüsseln × 9 Sprachen; `foldSmallTooltip` entfällt). + Das ist die D17-Logik des Bereichs-Umschalters: Ein Knopf, der den + Zustand zeigt, den man vor sich hat, sagt nichts — einer, der das Ziel + zeigt, sagt, was passiert. Mit vier Stufen ist er zudem kein Umschalter + mehr, `aria-pressed` entfällt. +- **Die Reihum-Position wird nicht gemerkt, sondern geprüft** — die + D44-Fortschreibung für vier Stufen: `render()` rechnet nach, ob der Baum + noch die zuletzt hergestellte Stufe beschreibt (`presetFoldSet` in + model.js, headless getestet); wenn nicht — Handfaltung, Textänderung, + Dokumentwechsel —, beginnt der nächste Druck wieder bei 1. Ein reines + Ablesen ohne Position (D44) trägt bei vier Stufen nicht mehr: Ein voll + offener Baum kann zugleich Stufe 4 und einer leeren Stufe 1 entsprechen — + die Mehrdeutigkeit ist den Stufen inhärent, die geprüfte Position löst + sie deterministisch. +- **Stufe 2 rechnet den Pfad auch bei ausgeschaltetem Pfad-Umschalter** — + die Voreinstellung fragt nach dem Pfad, nicht nach seiner Anzeige; die + Rechnung ist dieselbe, die bei eingeschaltetem Pfad ohnehin je Tastendruck + läuft (D42). + +**Preis in der Kopfzeile, benannt:** Der Querverbindungs-Knopf ist das elfte +Element. Auf dem Telefon (375 px) bricht die Zeile damit regulär in zwei +Reihen à 78 px — genau der Umbruch, den D50/D56 unterhalb von 440 px +vorsehen; am Schreibtisch bleibt sie einreihig (gemessen 44 px bei 800 px). + +**Nachgemessen** im Browser an einem Wegwerf-Dokument (10 Knoten, 3 Kanten, +über die echte UI angelegt und gelöscht): Mittel eingeklappt → Kante endet +am Vertreter (`data-sub-deps="z1"`), Ziel-Seite ebenso; beide Quellen unter +einem Vorfahren eingeklappt → **eine** Kante statt zwei (3 → 2); Wurzel zu → +0 Kanten. Durchschalter: small → path → closed → open → wieder small, je mit +korrekter Faltmenge und Tooltip; Handfaltung danach setzt auf small zurück; +`path` faltet bei ausgeschaltetem Pfad-Umschalter identisch; Undo nimmt eine +Stufe in einem Zug zurück (4 Marken → 1 → 4). Umschalter: aus → 0 Overlays +und 0 Kanten im exportierten SVG, an → 3/3; `depLinks` überlebt den Reload. +480 → 487 Tests, davon 7 neue in `tests/fold.test.js` (presetFoldSet-Modi +inkl. des per `:#…` gezogenen Ziels unter einer Zugabe; `data-sub-*` in +Dokumentreihenfolge, dedupliziert, nie an offenen Knoten, nie für +ausgeblendete verworfene). diff --git a/docs/SPEC.md b/docs/SPEC.md index 61a37c2..70b566a 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1083,19 +1083,32 @@ fokussierten Knoten (WAI-ARIA-Baum-Idiom). weil eine Fokusmarke `!!!` ihren Knoten immer wieder hervorholt): Dann wird lieber nichts geschrieben, als einen Text zu hinterlassen, der etwas anderes sagt als das Bild. -- **Für den ganzen Baum** gibt es im Diagramm-Kopf einen **Umschalter**: - gedrückt ist alles **ab Größe M abwärts** zugeklappt, nicht gedrückt ist der - ganze Baum **offen**. Betroffen ist jeder Knoten mit Kindern, dessen - **angegebene** Größe (§5) `M` oder kleiner ist — offen bleiben `L`, `XL`, - `XXL`. Ein Knoten **ohne** Größenangabe wird nicht zugeklappt: Der günstigste - Pfad rechnet fehlende Größen zwar als `M` (unten), das ist aber eine - Kostenannahme und keine Aussage des Autors. -- Beide Stellungen beschreiben einen **vollständigen** Faltzustand — was der - Umschalter nicht zuklappt, ist danach offen. Sein Zustand wird **nicht - gemerkt, sondern am Baum abgelesen**: Wer danach einen Knoten von Hand - umklappt, sieht ihn von selbst herausspringen. Geschrieben wird wie beim - einzelnen Umklappen — ein einziger Rückgängig-Schritt für den ganzen - Vorgang. Siehe D44. +- **Für den ganzen Baum** gibt es im Diagramm-Kopf einen **Durchschalter**: + Jeder Druck stellt die **nächste** von vier Voreinstellungen her, reihum — + 1. **ab Größe M abwärts zugeklappt**: jeder Knoten mit Kindern, dessen + **angegebene** Größe (§5) `M` oder kleiner ist — offen bleiben `L`, + `XL`, `XXL`. Ein Knoten **ohne** Größenangabe wird nicht zugeklappt: + Der günstigste Pfad schätzt fehlende Größen zwar (unten), das ist aber + eine Kostenannahme und keine Aussage des Autors. + 2. **alles abseits des günstigsten Pfads zugeklappt**: jeder Knoten, durch + dessen Teilbaum der günstigste Pfad (§9) **nicht** läuft — weder er + selbst noch ein Unterknoten liegt auf ihm. Der Pfad bleibt vollständig + sichtbar, alles Übrige steht als je ein eingeklappter Knoten da. + Gerechnet wird dafür auch bei **ausgeschaltetem** Pfad-Umschalter — die + Voreinstellung fragt nach dem Pfad, nicht nach seiner Anzeige. + 3. **alles zugeklappt**: jeder Knoten mit Kindern. + 4. **alles offen**. + + Der Knopf zeigt als Icon und Tooltip den **nächsten** Schritt — also was + Drücken tun wird, nicht was gerade gilt: Den Zustand hat man ohnehin vor + sich (dieselbe Logik wie beim Bereichs-Umschalter auf kleinem Bildschirm). +- Jede Voreinstellung beschreibt einen **vollständigen** Faltzustand — was + sie nicht zuklappt, ist danach offen. Die Reihum-Position wird **nicht + gemerkt, sondern am Baum geprüft**: Beschreibt der Baum nicht mehr den + zuletzt hergestellten Schritt (jemand hat von Hand umgeklappt oder den + Text geändert), beginnt der nächste Druck wieder bei 1. Geschrieben wird + wie beim einzelnen Umklappen — ein einziger Rückgängig-Schritt je + Voreinstellung. Siehe D44, D75. - Faltung ist **reine Ansicht**: Warnungen aus eingeklappten Teilbäumen werden weiter gemeldet (sie gelten dem Text), und der günstigste Pfad rechnet unverändert über den ganzen Baum. @@ -1139,11 +1152,21 @@ Strichen, kein gefülltes Dreieck). Der Baum trägt weiterhin die Hauptaussage. - Die Kanten des **ausgewählten** Knotens — Tastaturfokus im Diagramm, sonst der Knoten der Cursor-Zeile — sind in Tinte hervorgehoben, ein- wie ausgehende; sie liegen über den übrigen Kanten, aber unter jedem Knoten. -- Kanten zu gerade nicht sichtbaren Knoten (eingeklappt §9, verworfen - ausgeblendet §4) entfallen; bei doppelter ID zielt die Kante auf die erste - Vergabe (D36/D39). -- Die Basis-Kanten erscheinen auch im **Grafikexport** und im **Druck**; die - Hervorhebung ist Interaktion und erscheint nicht. Siehe D41. +- **Eingeklappte Knoten vertreten ihre Teilbäume auch hier** (§9, Falten): + Liegt ein Endpunkt — Quelle wie Ziel — in einem eingeklappten Teilbaum, + endet die Kante am nächsten **sichtbaren Vorfahren**. Fallen beide + Endpunkte in denselben sichtbaren Knoten zusammen, entfällt die Kante (sie + sagte nichts mehr); mehrere so zusammengefallene Kanten desselben Paars + werden zu **einer**. Kanten zu **ausgeblendeten verworfenen** Knoten (§4) + entfallen weiterhin — der Verworfen-Filter ist eine Aussage über den Plan, + keine Ansicht. Bei doppelter ID zielt die Kante auf die erste Vergabe + (D36/D39). +- **Ein Umschalter im Diagramm-Kopf** (neben dem günstigsten Pfad) blendet + die Querverbindungen ein und aus; Voreinstellung **an**, Zustand gemerkt. +- Die Basis-Kanten erscheinen auch im **Grafikexport** und im **Druck**, + sofern der Umschalter an ist (beide folgen ihm wie den übrigen + Ansichts-Filtern); die Hervorhebung ist Interaktion und erscheint nicht. + Siehe D41, D75. ### Grafikexport des Diagramms Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index 77ac315..efc6f41 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -42,6 +42,8 @@ - [^] #ed.deplink: Cross links for dependencies (M) - [^] #ed.deplink.calm: Visually secondary — the tree carries the message (S) - [^] #ed.deplink.focus: Highlight what a selected node needs and feeds (S) + - [x] #ed.deplink.fold: A hidden endpoint falls to its visible ancestor (S) %% source and target alike, see D75 + - [x] #ed.deplink.toggle: A switch hides the cross links (XS) - [^] #ed.path: Cheapest path (XL) - [^] #ed.path.cost: Cost model, a size prices its subtree (S) - [^] #ed.path.est: Missing size estimated from the sub-packages (S) %% see D66 @@ -83,7 +85,7 @@ - [^] #ed.fold.marks: Initial state from the text: > collapses, < re-opens (S) - [^] #ed.fold.click: Fold and unfold in the diagram (S) - [^] #ed.fold.write: Folding writes the mark back into the text (S) %% one undo step - - [^] #ed.fold.preset: One switch folds everything from size M downward (S) %% see D44 + - [x] #ed.fold.preset: The fold button cycles through four presets (S) %% M-and-smaller, off-path, all, none — see D44/D75 - [^] #ed.snaps: Earlier states of a document, every ten minutes (S) %% only when something changed - [^] #ed.snaps.manual: Save a state by hand, before a larger change (XS) %% ten minutes is the wrong beat for that moment - [^] #ed.files: Open and save .werkbaum files (S) @@ -397,6 +399,16 @@ The selected node — keyboard focus in the diagram, otherwise the node of the caret line — pulls its incoming and outgoing edges forward in ink. +#ed.deplink.fold + When an endpoint sits in a folded subtree, the edge ends at the nearest + visible ancestor instead of vanishing — the collapsed node stands in for + its subtree here too, for source and target alike. Edges whose two ends + fall into the same visible node disappear, duplicates merge into one. + +#ed.deplink.toggle + A toggle next to the cheapest-path button hides the cross links entirely; + export and print follow it like the other view filters. + #ed.path Highlight the cheapest way through the tree: with and, everything; with or, the cheapest alternative. This is the feature the whole notation exists for. @@ -590,9 +602,11 @@ restores exactly what you were looking at. #ed.fold.preset - One switch puts the whole tree at working height: everything of size M and - smaller closed, L and above open. Size rather than depth — depth only says - how finely somebody happened to write it down. + One button cycles the whole tree through four complete fold states: size M + and smaller closed, everything off the cheapest path closed, everything + closed, everything open. Icon and tooltip show the next step; a manual fold + restarts the cycle. Size rather than depth — depth only says how finely + somebody happened to write it down. #ed.snaps A safety net against mishaps: the text is put aside every ten minutes, but diff --git a/frontend/index.html b/frontend/index.html index 2dc86d5..677eb6d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -196,6 +196,11 @@ + + @@ -205,10 +210,15 @@ - -