defeaf84d057b691a1b0738c0e59407ffbc34992
75
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
549c12191e |
feat: Strg+Klick auf :#id springt zur Zeile der referenzierten ID (D67)
Strg/Cmd+Klick auf eine Abhängigkeits-ID im Textfeld — auch jede ID der Liste und die Kopf-Form #auth:#db — markiert die Zeile, die die ID vergibt; Tastatur-Pendant Strg+Enter. Erste Vergabe gewinnt (D36/D39), unbekannte IDs tun still nichts. Headless als depIdAt/idLine in autocomplete.js, 16 Tests; Legenden-Zeile hint_jump in allen neun Sprachen erweitert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
07e4116bd9 |
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> |
||
|
|
52232f2c20 |
feat: Labels brechen balanciert um, ID-Zeile über dem Titel, Falt-Chip — D64
Lange Knotentitel brechen bei ~40 Zeichen um, gleichmäßig auf die Zeilen verteilt — vom Renderer (wrapLabel, headless getestet) statt text-wrap:balance, damit der Kasten auf die längste balancierte Zeile schrumpft. Der 23-px-Abzweig ankert an der ersten Zeile; der Optional-Kreis sitzt fest auf der Abzweighöhe. Der Grafikexport misst die gerenderten Zeilen (labelLines) und gibt je Zeile ein <text> aus. Dazu: die eingeblendete ID (D56) steht in einer eigenen Zeile über dem Titel, und das Falt-Zeichen ist ein gerahmter 19×16-px-Chip mit gedeckelter Höhe. Plan: #ed.render.wrap, #ed.render M→L (D62-Prüfung schlug korrekt an). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1debb7d9da |
feat: ID-Vorschläge beim Tippen von Abhängigkeiten (:#) — D63
Wer :# tippt, bekommt die vergebenen IDs als Liste an der Schreibmarke: Präfix- vor Teilstring-Treffern, Knotentitel als Kontext, schon gelistete und die eigene ID ausgenommen. ↑/↓ wählt, Enter/Tab übernimmt (undo-fähig), Esc schließt; Live-Region für Screenreader. Eingabehilfe wie die Kurzform (D55) — der Parser sieht nie etwas davon. Regeln headless in autocomplete.js (20 Tests), app.js verdrahtet Popup, Tasten und Einfügen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a60c466d82 |
fix: Linie zum einzigen Kind riss ab (D29-Nachtrag 4)
Der Stiel aus dem Elternknoten sitzt bei 50 % der Gruppe, der Stiel zum
Kind bei `--stem-x` (der gemessenen Knotenmitte, Nachtrag 2). Für
`li:only-child` ist die waagerechte Sammelleiste abgeschaltet — richtig,
solange beide zusammenfallen, und das tun sie nur, solange der Knoten in
seiner Zelle zentriert steht. Bei einer Treppe darunter sind es gemessen
8,5 px Versatz, bei einem `li.has-or` als einzigem Kind 41,8 px.
Jetzt verbindet ein Leiterstück genau zwischen 50 % und `--stem-x` die
beiden — dieselbe L-Form wie bei einem äußeren Kind einer größeren
Gruppe; ohne `--stem-x` ist es 0 px breit und unsichtbar.
Dabei aufgefallen: `:only-child::after{border-left}` stand vor
`:last-child::after{border:0 none}` und war bei gleicher Spezifität seit
jeher wirkungslos. Der ganze Block steht nun dahinter.
Kein Rückschritt von D61 — mit ausgeschaltetem Pfad derselbe Versatz;
die Zugabe steht seither nur in voller Farbe statt blass da. Der
Grafikexport war nie betroffen (die Leiste schließt dort die Elternmitte
ein).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
36154f6f68 |
feat: Neuigkeiten — der Stern wandert in die Kopfzeile und bekommt ein Popup
Der „Was ist neu?"-Knopf stand im Diagramm-Kopf und war verborgen, solange es nichts gab — er konnte also nur etwas über das offene Dokument sagen, und meistens sagte er gar nichts. Jetzt steht er permanent in der oberen Bedienleiste und öffnet ein Popup mit der Chronik der letzten Tage; je Tag führt ein Link die betroffenen Knoten im Diagramm vor (dieselbe gelbe Kranz-Ansicht wie D28, nur mit einer anderen Frage). Zwei Quellen, jede in ihrer Rolle: docs/CHANGELOG.md sagt, WAS geschehen ist (englisch — das Popup ist Oberfläche in neun Sprachen, die Commit-Betreffs sind deutsch); die git-Historie des mitgelieferten Plans sagt, WELCHE Knoten sich bewegt haben. Beides wird zur Bauzeit eingelesen (Vite-Plugin), zur Laufzeit lädt Werkbaum weiterhin nichts nach. Bernstein heißt ungesehen, Petrol heißt „wird gerade vorgeführt". Der Besuchsvergleich (D28) steht als abgesetzter Abschnitt zuoberst im Popup und trägt den „gesehen"-Knopf, den vorher der Knopf selbst war. SPEC §9 (Neuigkeiten) und D58; 20 neue Tests, davon einer auf der ausgelieferten CHANGELOG.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e937e1bbbb |
refactor(snapshots): Regeln nach snapshots.js, 28 Tests
D54-Nachtrag 2 endete mit „nicht durch Tests gedeckt ... das ist der Preis
dafür, dass die Zustandslogik im UI-Modul sitzt". Der Preis war zu hoch: Der
Fehler kam bis in Produktion, und ein Test hätte ihn in einer Zeile
gefunden.
Geschnitten nach dem Vorbild von remote.js (D31) — die entscheidbare Hälfte
heraus, die I/O bleibt:
- snapshots.js: addSnapshot (wann entsteht ein Stand), dropOldestSnap /
persistSnaps (was fliegt bei Platzmangel), parseSnaps (was darf gelesen
werden), snapLabel. Speicher als {setItem, removeItem} und Uhr als Zahl
hereingereicht — genau die Abhängigkeiten, an denen die Prüfbarkeit
vorher scheiterte.
- app.js: -63/+14 Zeilen, snapshotNow() schrumpft auf sechs.
Die Gegenprobe zählt, nicht die Zahl der Tests: Baut man exakt den
ausgelieferten Fehler wieder ein (`manual ? null : base` -> `base`), fällt
GENAU die eine Zusicherung, die nach ihm benannt ist; die übrigen 27 bleiben
grün.
Zwei bewusste Verhaltensänderungen: parseSnaps wirft formfremde Einträge weg
(Liste kein Array, t keine Zahl, text kein String), und ein Array im
Speicher ergibt {} statt eines halb benutzbaren Zustands.
Verdrahtung bleibt Browser-Sache und wurde nachgemessen: Knopf ohne Eingabe
-> 1 Stand; Takt (2 s) bei bloß angesehenem Dokument -> 0, nach Änderung
-> 1, weitere Takte -> 1.
D54-Nachtrag 3 + Faustregel in frontend/CLAUDE.md. 271 Tests grün (243+28).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
cb966e4e20 |
feat(editor): Warn-Zeilennummer trägt ihre Meldung als Tooltip
Der Streifen sagte bisher nur, DASS eine Zeile eine Warnung hat; welche, stand allein unter dem Diagramm — man sah die orange Zahl und suchte dann in der Liste die passende Zeilennummer. - warnings.js: `warningText()` neben `formatWarning()`, beide aus demselben `switch` (`build(w, t, esc)`) — der Warnungsbereich ist HTML, ein `title` ist Klartext. Ohne die Trennung stünde dort „Drag & Drop“. - app.js: `lineNoWarn` ist jetzt Map Zeile -> Text; mehrere Warnungen einer Zeile stehen im Tooltip untereinander. Titel verschwinden mit der Warnung. - style.css: Der Kasten der Warn-Zahl reicht über die ganze Streifenbreite (Ziffern bleiben punktgenau stehen, nachgemessen 21,14 px), `cursor:help`. Nachgemessen (4 Warnungen in 3 Zeilen): Tooltips wortgleich mit dem Warnungsbereich, `&`/`"` unverfälscht, keine Zahl ohne Warnung mit `title`; nach dem Beheben verschwinden Klasse und Titel mit. Werkbaum-Plan: 0. SPEC §9 (Zeilennummern) + D33-Nachtrag; Plan: #ed.lineno.tip [x]. 243 Tests grün (3 neue). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6935b4882d |
fix: Tab rückt Zeilen ein und zerstört kein Undo mehr (D53)
Zwei gemeldete Fehler, eine Zeile:
src.value = value.slice(0, s) + ' ' + value.slice(eEnd);
1. Sie schneidet den markierten Bereich heraus — mehrere markierte
Zeilen wurden also durch zwei Leerzeichen ERSETZT. Ohne Auswahl fügt
derselbe Ausdruck nur ein, deshalb fiel es nie auf.
2. `src.value =` löscht die Undo-Historie des Textfelds vollständig
(D38-Nachtrag 2 hatte das gemessen, aber nur beim Zurückschreiben der
Faltung beherzigt). Betroffen war damit alles davor Getippte. Erneut
gemessen, alte Zeile nachgestellt: erstes `undo` ändert nichts,
zweites liefert `false` — Stapel leer. Das ist die Antwort auf „wann
geht Undo kaputt": bei jedem Tab-Druck. Die übrigen drei
`src.value =` laden ein anderes Dokument, dorthin gibt es nichts
zurückzunehmen.
Neue Regel: ohne Auswahl zwei Leerzeichen an der Marke (Shift+Tab nimmt
sie zurück und zieht die Marke mit), mit Auswahl wird jede berührte
ZEILE ein-/ausgerückt. Verworfen war die VS-Code-Feinregel „nur bei
mehrzeiliger Auswahl einrücken": Wer eine ganze Zeile markiert, meint
auch dann Einrücken, und die einfache Regel kann nie Text löschen.
Geschrieben wird über execCommand('insertText') — ein Undo-Schritt.
Nebenbefund mitbehoben: Tab war eine Tastenfalle (WCAG 2.1.2). Esc hebt
sie für den nächsten Tastendruck auf.
Nachgemessen mit echten Tastendrücken: drei markierte Zeilen, Tab -> alle
drei von 2 auf 4 Leerzeichen, nichts gelöscht, Block bleibt ausgewählt;
Shift+Tab zurück; tippen + Tab + zweimal undo -> erst der Einzug, dann
das Getippte. Werkzeuggrenze notiert: synthetisches ctrl+z löst kein
natives Undo aus, geprüft wird mit execCommand('undo').
240 Tests grün.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b2248c45bc |
feat: Auf Touch öffnet der einfache Tipp das Knoten-Fenster (D52)
Ein `title` braucht einen Zeiger — auf dem Telefon war die Beschreibung (D40) damit gar nicht zu sehen, und seit D48 hängt an jedem der 172 Plan-Knoten eine. Der einfache Tipp zeigt jetzt denselben Inhalt als Fenster. Die eine echte Frage war der Link: Ein Knoten mit URL belegt den einfachen Klick vollständig (SPEC §6), ein Tipp kann nicht beides tun. Entschieden: Auf Touch öffnet der Tipp immer das Fenster, der Link steht darin als Knopf. Damit hat die Geste dort eine Bedeutung, und die Tooltips sind auf allen Knoten erreichbar. SPEC §6 bekommt dafür eine Touch-Ausnahme. Es hängt an den Touch-Ereignissen, nicht an einer Media Query — ein Gerät mit beidem verhält sich dadurch ohne Sonderfall richtig: Finger öffnet das Fenster, Maus öffnet den Link und zeigt den Tooltip. Die drei Touch-Gesten unterscheiden sich am vorhandenen Zustand, ohne neues Merkerfeld (armedEl gesetzt ⇒ Sprung, Timer läuft ⇒ Tipp, beides weg ⇒ gescrollt). Ausgenommen ist das Falt-Zeichen: Das Fenster braucht preventDefault(), das sonst den Falt-Klick verschluckt. Das Fenster nutzt, was ein `title` nicht kann: echte Trennlinie statt 24 `─` (der title wird an genau diesem Strich zerlegt, TIP_RULE exportiert — ein zweites data-Attribut hätte 20 kB DOM-Text verdoppelt), Absätze aus Leerzeilen statt harter Quell-Umbrüche, Knöpfe. Der Sprung-Hinweis nennt hier den langen Druck (neuer i18n-Schlüssel jumpHintTouch, plus tipClose und tipOpenLink, je 9 Sprachen). position:fixed auf <body>: in #out erbte es dessen zoom und würde von dessen overflow beschnitten (D50). Zu macht es alles, was seine Aussage hinfällig macht — Tipp daneben, zweiter Tipp, Esc, ×, Diagramm-Scroll, Bereichswechsel, Sprung in den Text, render(). Nachgemessen (375x812): Wurzelknoten 336x220 px ganz im Bild, ein Absatz ohne harte Umbrüche, Fakten mit Langdruck-Hinweis, Link-Knopf, Ring am Knoten, URL nicht geöffnet; Knoten ohne Beschreibung 39 px ohne Trennstrich; Spitze 0 px von der Knotenmitte. Echte Wischgeste (scrollLeft 0 -> 300) schließt. Schreibtisch unverändert: Klick öffnet den Link, Fenster bleibt zu. Plan: 150 Knoten, 0 Warnungen, 24 Stationen. 240 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b6e83b4f2a |
fix: Erledigte Knoten treten nie zurück (D46-Nachtrag)
Gemeldet: manche Knoten in Produktion werden grau dargestellt, etwa `#ed.fresh`. Ursache war nicht der Status, sondern die Pfad-Inversion: Der Knoten ist eine Zugabe (`+`) und liegt damit per D29 nie auf dem günstigsten Pfad — `.cheap-on .node:not(.cheap)` blasste ihn aus (Deckkraft 0,32, Sättigung 0,4; Pastellblau liest sich so als Grau). D46 hatte „erledigt tritt nicht zurück" bereits entschieden, die Regel hing aber an `cheapCls()` und griff nur für Knoten AUF dem Pfad. Durch das Raster fielen die fertige Zugabe und die fertige, nicht gewählte Alternative. Jetzt gilt sie unabhängig vom Pfad; Blässe heißt damit einheitlich „hier ist nichts getan und wird auch nichts getan". Umgesetzt als eigene Klasse `done` per `isDone(n)` in render.js, nicht über `st-fertig`/`st-prod`: Die Statusklasse trägt den EFFEKTIVEN Status (D39), die Ausnahme muss dem intrinsischen folgen — dieselbe Linie wie bei D35, D28 und der Kostenrechnung aus D46. Gefärbt wird unverändert nach dem effektiven Status. Nachgemessen (Werkbaum-Plan, 149 sichtbare Knoten, Pfad an): vorher blass und fertig waren genau drei Knoten (#ed.fresh, #ed.export.print, #ed.closure.greedy) — jetzt alle drei opacity 1, filter none, Füllung rgb(219,234,248) wie die fertigen Knoten auf dem Pfad. Weiterhin blass: 31 Knoten, darunter kein einziger fertiger. Der Grafikexport hatte den Fehler nie (er liest backgroundColor, nicht opacity). 240 Tests grün (4 neue in tests/frontier.test.js). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ec338eb558 |
fix: Dokumenten- und Download-Menü klappen auf Mobil wieder auf (D50)
Gemeldet: „Dokumente aufklappen/auswählen funktioniert in Mobilansicht nicht." Regression aus D47, einen Tag alt. Ursache: Das dort eingeführte `overflow-x:auto` macht aus der Titelzeile einen Scroll-Container — und beide Aufklapp-Menüs hängen als absolut positionierte Kinder genau darin (`#docMenu` im Editor-Kopf, `.dlmenu` im Diagramm-Kopf). Übersehen war dabei eine CSS-Regel: `overflow-x` auf etwas anderes als `visible` hebt ein `visible` der ANDEREN Achse auf `auto`. Geklippt wurde also nicht seitlich, sondern nach unten — dorthin, wo die Menüs aufklappen. Und ein Scroll-Container klippt, ob er überläuft oder nicht: Der Fehler traf jede Mobilbreite, nicht nur die schmalen, für die die Regel gedacht war. Nachgestellt bei 375 px: Das Dokumenten-Menü ist 153 px hoch und beginnt 156 px unterhalb der Kopfunterkante (clientHeight 43, scrollHeight 200); `elementFromPoint` an seiner Stelle liefert das Textfeld. Beim Download-Menü dasselbe. Das Menü war also nicht schwer zu treffen, sondern gar nicht da. Behoben durch Wegnahme: kein `overflow` mehr an der Zeile. An seine Stelle tritt Umbrechen, aber nur unter 360 px, wo neun Fingerziele arithmetisch nicht nebeneinander passen; darüber bleibt `nowrap`. Erwogen und verworfen war, die Menüs per `position:fixed` aus dem Container zu heben — JS-Geometrie für etwas, das CSS allein konnte, und der Preis stand in keinem Verhältnis: Das `overflow` diente nur Breiten unter 360 px, kaputt waren die Menüs überall. `flex:0 0 auto` gegen den zerdrückten Modus-Wähler bleibt. Nachgemessen — und zwar die Bedienung, nicht die Geometrie, denn genau darin war der Fehler unsichtbar (die Zeile war weiter 49 px hoch, alle Knöpfe an ihrem Platz): Bei 375 px und 320 px trifft `elementFromPoint` im geöffneten Menü `.docitem` bzw. `.dlmenu`, und ein Klick wechselt das Dokument wirklich (Werkbaum -> Example: Titel, `werkbaum-active`, neu gebauter Baum). Kopfzeile 375 px eine Reihe zu 49 px, 320 px zwei Reihen zu 78 px mit allen neun Elementen vollständig sichtbar. Desktop unverändert. 236 Tests grün. D47 bekommt den Verweis, dass sein `overflow` zurückgenommen ist; gelöscht wird dort nichts. frontend/CLAUDE.md warnt jetzt vor `overflow` an Leisten, die Menüs tragen — samt dem Hinweis, dass man das nicht an Maßen sieht. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ecc1b632f6 |
feat: von Station zu Station des günstigsten Pfads (D47)
Mit dem status-bewussten Pfad (D46) zeigt das Diagramm die offene Front — im mitgelieferten Plan 24 Stationen, verteilt über einen Baum von über 20 000 px Breite. Sie zu sehen war damit gelöst, sie abzugehen nicht. Der neue Knopf im Diagramm-Kopf holt sie der Reihe nach in die Mitte: erster Druck die erste (was als Nächstes dran ist), jeder weitere die nächste, nach der letzten wieder die erste. Der Sprung fasst nur das Diagramm an — zentrieren, hervorheben, Tastaturfokus, also buchstäblich die Behandlung des ausdrücklichen Alt+Klicks (D25) samt Puls und hervorgehobenen Abhängigkeits-Kanten. In den Text führt von dort Alt+Enter, dieselbe Geste wie überall. Ohne eigenen Zustand: Fortgesetzt wird am gerade hervorgehobenen Knoten. Ein gemerkter Index wäre schlechter — der Baum wird bei jedem Tastendruck neu gebaut, die Stationsliste ändert sich unter ihm. Verborgen, solange es keine Station gibt (Pfad aus, oder alles erledigt); der Tooltip nennt die Anzahl. i18n `leanNextTooltip` in neun Sprachen. Die Mobil-Kopfzeile trug den neunten Knopf nicht mehr — D17-Nachtrag 5 hatte sie gerade erst auf acht zurechtgemessen. Nachgemessen: bei 375 px reicht es nach Lücke 8->6 px und Innenabstand 10->8 px weiterhin (345 px Inhalt, 359 px Platz, 14 px Luft); bei 320 px passen neun Fingerziele arithmetisch nicht, und unter 29 px zu gehen hat D17-Nachtrag 5 abgelehnt. Die Zeile schiebt dort deshalb (`overflow-x:auto`, Balken ausgeblendet) — 41 px Schiebeweg, Kopfhöhe unverändert 49 px, alle neun Elemente in voller Größe erreichbar. Dabei ein älterer, stiller Fehler gefunden: Bei Platzmangel schrumpfte der Modus-Wähler — das einzige Element ohne feste Größe — auf einen 2-px-Strich zusammen, während sein Icon 38 px breit darüber hinausragte; er sah aus wie ein Trennstrich und war unbenutzbar. `flex:0 0 auto` an allen Kindern der Zeile stellt das ab. 236 Tests grün. SPEC §9 und D47 halten es fest; der Plan bekommt den Knoten als `[x]` (noch nicht deployt), frontend/CLAUDE.md die Kopfzeilen-Stolperfalle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f1ac57c931 |
fix: Update-Hinweis vergleicht gegen den laufenden Build (D45)
Der Hinweis erschien oft für genau die Fassung, die gerade geladen war. Zwei Fehler mit derselben Wurzel — die Prüfung verglich nie mit dem laufenden Stand: - Relais statt Vergleich: der abgerufene Inhalts-Hash wurde gegen den ZULETZT ABGERUFENEN gehalten (localStorage), nicht gegen den laufenden. Beim Deploy antworten CDN-Knoten unterschiedlich; jeder Wechsel schlug an, und der Schlüssel wird von allen Tabs geteilt. - Klebendes Flag: `werkbaum-update-available` entfernte nur „Jetzt laden". Nach F5 oder „Später" blieb es stehen, und der Load-Check zeigte das Banner bei jedem weiteren Laden erneut. Verglichen wird jetzt der Commit aus dem Footer-Versionslink der laufenden Seite gegen den der abgerufenen HTML (D16 spritzt ihn in beiden Deploy-Wegen ein). Kein localStorage dazwischen; beim Laden wird nichts gemeldet, die Prüfung zwei Sekunden später holt es nach. Eine spätere „aktuell"-Meldung sammelt Banner und Footer-Symbol wieder ein. Rückfall auf den Inhalts-Hash für Builds ohne Marker (Dev-Server), Vergleichsstand dort der erste Abruf der Seiten-Sitzung. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
579dfbf789 |
fix: Diagramm-Titelzeile bricht auf Mobil nicht mehr um (D17-Nachtrag 5)
Mit dem achten Knopf (Falt-Umschalter, D44) ging die Zeile auf drei Reihen und 86 statt 49 px — und fraß die Fläche, für die es den Ein-Bereich-Modus gibt. Die Knöpfe waren nicht das Problem: Ihre Breiten summieren sich bei 375 px auf 243 px. Die Abstände brachten den Rest — gap:14px mal sieben plus 2×16 px Innenabstand sind mehr als ein Drittel des Bildschirms. Auf Mobil deshalb gap:8px und padding-inline:10px, dazu flex-wrap:nowrap als Riegel: Ohne ihn wäre der Umbruch nur hinausgeschoben und käme beim nächsten Knopf unbemerkt zurück. Die Knöpfe selbst bleiben bei 29 px (Fingergröße). Nachgemessen mit allen acht Bedienelementen — der freshBtn ist der, der die Zeile kippen ließ, weil er nur bei fremden Dokumenten mit Neuigkeiten erscheint: bei 375 px eine Reihe (49 px), bei 320 px eine Reihe mit 10 px Luft. Desktop bleibt bei 14 px unverändert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
417006960f |
change: Falt-Voreinstellung wird ein Umschalter, Schwelle ab M abwärts (D44)
Statt eines Aufklapp-Menüs mit zwei Einträgen jetzt ein Umschalter neben „verworfene einblenden" und „günstigster Pfad". Die beiden Einträge waren nie unabhängige Befehle, sondern die zwei Stellungen einer Sache — jeder beschreibt einen vollständigen Faltzustand, zusammen decken sie ihn ab. Die Schwelle schließt M jetzt ein (atMostM statt belowM): Ein M-Paket ist selbst noch die Einheit, die man als Ganzes plant, seine Zerlegung ist schon das Innere. Offen bleiben L, XL, XXL — und Knoten ohne Größenangabe. Der Zustand des Knopfes wird bei jedem Rendern am Baum abgelesen, nicht gemerkt: Klappt jemand danach einen Knoten von Hand um, springt er von selbst heraus, statt etwas zu behaupten, das nicht mehr stimmt. Deshalb auch nicht persistiert — der Faltzustand steht im Text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9bbe7d78cc |
feat: Falt-Voreinstellungen im Diagramm-Kopf (D44)
Ein Menü mit zwei Einträgen: alle aufklappen, unter Größe M zuklappen. Das Kriterium ist die Größe, nicht die Tiefe. Die Ebene ist eine Eigenschaft der Schreibweise — ein fein zerlegter Zweig verschwände bei derselben Zahl früher als ein grob notierter. Die T-Shirt-Größe sagt etwas über die Arbeit, und ab M muss ohnehin zerlegt sein (SPEC §5): „unter M" ist genau die Menge, die keine Zerlegung mehr braucht. Ohne Größenangabe wird nicht zugeklappt. Der günstigste Pfad wertet fehlende Größen als M (D18), aber das ist eine Kostenannahme, keine Aussage des Autors. Beide Einträge beschreiben einen vollständigen Zustand — sonst hinge die Wirkung vom Vorzustand ab. Rückgängig nimmt den ganzen Vorgang in einem Schritt zurück. Umgesetzt über denselben Weg wie das einzelne Umklappen; der Voll-Rewrite ist dafür aus writeFoldToText() in writeAllFoldMarks() gewandert. Die Rückfall-Kette gilt unverändert: Im Werkbaum-Plan ist der Zustand wegen einer Fokusmarke nicht in Marken ausdrückbar, dort trägt die Sitzungs-Überlagerung. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7e2c7cd3e0 |
fix: vordere Zeichenebenen liegen wirklich vorn (D25-Nachtrag 3)
Der aktive Knoten deckte seinen eigenen Stationspunkt zu. Ursache ist der z-index:3 der Cursor-Zeile (D25-Nachtrag 1) — dort richtig begründet, aber mit einer zweiten Wirkung, die nicht mitgedacht wurde: Er hebt den Knoten über jede Ebene ohne eigene Stapelposition, und svg.cheap-front hatte keine. Betroffen war nicht nur die Cursor-Zeile: Die Strahlenkränze tragen aus demselben Grund z-index:2 (D28/D32), und svg.dep-front hing mit dran — die hervorgehobenen Abhängigkeits-Kanten gehören zum ausgewählten Knoten und endeten deshalb unter ihm. Behoben an der Ebene, nicht am Knoten: dep-front 4, cheap-front 5. Die hinteren Ebenen bleiben ohne z-index, dort trägt die DOM-Reihenfolge. Export und Druck waren nie betroffen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9cbdf5ab58 |
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> |
||
|
|
be729239ce |
notation: Faltmarke rückt hinter die Statusbox (D34-Nachtrag 2)
`- > [x] Backend` wird zu `- [x] > Backend`. Die alte Stellung verschob die Statusbox um genau eine Einrückungsstufe — die Box einer gefalteten Zeile stand damit in der Spalte der Boxen ihrer eigenen Kinder. Vor dem Label kostet die Verschiebung nichts, weil Labels ohnehin ausgefranst sind. Nebengewinn: Die Regel wird einfacher. Statt „zwischen Zeichen und Statusbox, bei Wurzelknoten am Zeilenanfang" heißt sie jetzt ausnahmslos „unmittelbar vor dem Label"; für Zeilen ohne Statusbox ändert sich nichts. Die alte Stellung wird weiter gelesen (Pads und ?sourceUrl=-Quellen lassen sich nicht migrieren), aber nie mehr geschrieben — setFoldMark() löst sie in die neue auf. SPEC §1 hält zusätzlich fest, dass `<` gelesen, aber nie erzeugt wird. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
321bdc9dfa |
feat: Umklappen im Diagramm schreibt die Faltmarke in den Text zurück
Erster Schritt der Schreibrichtung Diagramm -> Text. Damit ist der Text auch für
die Faltung die eine Quelle der Wahrheit (D14): Was man sieht, steht
geschrieben, und ein Neuladen stellt es wieder her. Kehrt die bisherige
Festlegung aus D34/D38 um („die Marken bestimmen nur den Anfangszustand").
Verfahren: minimal patchen, dann nachrechnen. Die Ableitung Text -> Zustand ist
nicht umkehrbar — mehrere Markensätze ergeben denselben Zustand, und `<` faltet
Knoten, die gar keine Marke tragen. Statt zu invertieren wird der Kandidat
befragt: `>` an die eine Zeile, initialCollapsed() darauf, mit dem Soll
vergleichen. Passt es, bleibt alles andere unangetastet — so überleben von Hand
gesetzte `<`. Passt es nicht, werden alle Marken neu gesetzt (das `<` löst sich
dabei auf). Passt auch das nicht (ein `!!!` holt seinen Knoten immer wieder
hervor), wird gar nicht geschrieben: Ein Text, der etwas anderes sagt als das
Bild, wäre schlimmer als eine fehlende Marke. initialCollapsed() bleibt die
einzige Stelle, die die Marken versteht.
Undo-fähig über execCommand('insertText') — gemessen: value= UND setRangeText
machen Strg+Z wirkungslos, wer tippt und dann faltet käme an sein Getipptes
nicht mehr heran. Falten ist damit ein eigener Undo-Schritt (geprüft: einmal
Strg+Z nimmt die Faltung, ein zweites das Getippte).
Zwei gemessene Fallen: Ein Textfeld mit display:none-Vorfahr nimmt kein
execCommand an (liefert false, obwohl activeElement es meldet) — auf Mobil ist
der Editor beim Falten genau das, deshalb schaltet ihn `writing-fold` für die
Dauer des synchronen Schreibens aus dem Bild heraus sichtbar. Und der nötige
Fokus zöge dort die Tastatur hoch, dagegen inputmode="none" wie beim Sprung
(D25); danach geht der Fokus an den Knoten zurück.
Bei schreibgeschütztem Text (Pad, D31) wird nicht geschrieben — dort trägt
weiter die Sitzungs-Überlagerung. Schreibmarke und Scrollstand bleiben erhalten.
Im Browser geprüft: `<` bleibt stehen, wenn es noch stimmt, und löst sich auf,
wenn nicht; Falten bei verborgenem Editor auf Mobil; Pad-Fall sitzungsweise;
Hin- und Herklappen führt zeichengenau zum Ausgangstext zurück.
7 neue Tests für setFoldMark (215 gesamt). SPEC §1/§9, D38 (Nachtrag 2) und
frontend/CLAUDE.md nachgezogen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
38104a4c5d |
fix: eingeklappter Knoten vertritt seinen Teilbaum auf dem günstigsten Pfad
D38 hielt fest, ein eingeklappter Zweig dürfe Pfadknoten verbergen, „die Inversion an den sichtbaren Knoten bleibt richtig". Zu kurz gedacht: Die LINIE blieb eben nicht richtig. Sie fädelt durch die cheap-leaf-Knoten im DOM; ein eingeklappter Pfadknoten ist zwar `cheap`, aber kein Blatt (seine Pfad-Kinder existieren im Modell) — also bekam er keine Station, und die Linie umging den ganzen Zweig. Im Beispiel nachgemessen: „Concept" war `node cheap folded`, verbarg zwei Pfadknoten, trug keinen Punkt; die Linie begann erst bei „PWA". Das Bild behauptete damit, in diesem Zweig sei nichts zu tun — eine Aussage über den Plan, nicht über die Ansicht. Genau die Grenze, die D38 sonst zieht. Regel: Eingeklappt überspringt cheapCls() die Blatt-Prüfung und fragt den Teilbaum (hidesCheap). Liegt darin etwas auf dem Pfad, ist der Knoten dessen tiefste noch sichtbare Station. Beim Aufklappen geben die Kinder sie zurück — gemessen 6 -> 7 -> 6 Stationen beim Hin- und Herklappen. Auch ohne eigene Pfad-Mitgliedschaft: Ein per :#… gezogenes Ziel kann unter einem +-Knoten liegen, der selbst nicht gebraucht wird (D42). Eingeklappt ist dieser der einzige sichtbare Griff auf nötige Arbeit — er wird Station und zählt als cheap, tritt also nicht zurück. Ihn auszublassen wäre die Lüge. 4 neue Tests (208 gesamt). SPEC §9, D38 (Nachtrag) und frontend/CLAUDE.md nachgezogen; die alte „hinnehmbar"-Stelle in D38 ist als falsch markiert statt gelöscht. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6463a76e1e |
fix: Bildschirmtastatur quetscht den Editor nicht mehr zusammen
Die eigentliche Ursache der Meldung — meine erste Diagnose (Titelzeilen-Tipp im
alten Splitter-Modell) war für 1.1.39 richtig, aber nicht das, was der Nutzer
auf dem Pages-Build sah.
setAppHeight() schreibt visualViewport.height nach --app-height, und
body{height:var(--app-height)} macht daraus die Höhe der ganzen Seite. Genau
diesen Wert verkleinert die Bildschirmtastatur — dafür ist visualViewport
gemacht. Nachgemessen linear: Textfeldhöhe = --app-height minus ~206 px feste
Aufbauten. 812 px -> 606 px Text, 440 -> 234, 260 -> 54 px, also drei Zeilen.
Das ist das gemeldete „komplett zusammengeklappt".
Sichtbar ausschließlich auf echten Geräten: In der Emulation gibt es keine
Tastatur, deshalb liefen alle drei Reproduktionsversuche vorher ins Leere.
Unterschieden wird am Fokus, nicht an der Größe: Tastatur und überlagernde
Browserleiste (Brave, der ursprüngliche Anlass des Mechanismus) erzeugen
dieselbe Signatur — vv.height fällt, innerHeight bleibt. Die Tastatur steht
aber nur, wenn ein editierbares Feld den Fokus hat. Solange das so ist, bleibt
die letzte tastaturfreie Höhe stehen; die Seite behält ihre Größe und der
Browser schiebt den Ausschnitt zur Schreibmarke.
Mit nachgebildetem visualViewport geprüft: mit Fokus im Textfeld 812/606 px
(unverändert), ohne Fokus 440/234 px — die Brave-Leiste wirkt also weiter.
orientationchange durchbricht die Sperre (sonst behielte die Seite beim Drehen
während des Tippens das alte Hochformat); der focusout-Timer ruft
() => setAppHeight(), sonst reichte er ein wahres `force` durch.
D17 (Nachtrag 4) und frontend/CLAUDE.md nachgezogen, samt der Lehre: Ein
„nicht reproduzierbar" aus dem Emulator ist bei geräteabhängigen Meldungen
kein Befund (vgl. D25 zu synthetischen TouchEvents).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
1bda54ae59 |
fix: Umschalter navigiert zum Knoten der Cursor-Zeile (Touch-Weg Text -> Diagramm)
Zum gemeldeten Fehler „Cursor in den Text setzen klappt den Editor zusammen": Auf der deployten Instanz (1.1.39, noch das alte Splitter-Modell) nachgestellt — ein Tipp auf die Titelzeile des Diagramms, ein 49-px-Streifen unmittelbar über dem Text, schrumpfte den Editor von 594 px auf 44 px. Beim Zielen auf die oberen Textzeilen leicht zu treffen. Das Ein-Bereich-Modell nimmt dem die Grundlage: kein Splitter, kein Titelzeilen-Tippen, den Bereich wechselt allein der Umschalter. Im aktuellen Stand nicht mehr auslösbar (Maus, emulierter Touch und synthetische Touch-Folge je ohne Bereichswechsel). Der zweite Teil der Meldung deckte eine echte Lücke auf: Für Text -> Diagramm gibt es nur Alt+Klick/Alt+Enter — und Alt gibt es auf dem Telefon nicht. Die Gegenrichtung hat dort ihren langen Druck, diese hatte nichts. Ein langer Druck IM TEXTFELD verbietet sich allerdings: Dort gehört er dem Betriebssystem (Wort markieren, Auswahlgriffe). D25 konnte sich die Geste im Diagramm nehmen, weil es da nichts zu markieren gibt; in einem editierbaren Feld ist sie die Bedien-Grundlage zum Bearbeiten — sie zu überschreiben löste ein Navigationsproblem auf Kosten des Bearbeitens, in einer Meldung, deren Kern gerade lautet, dass Bearbeiten nicht bestraft werden darf. Stattdessen trägt der Umschalter die Geste nebenbei: Wer aus dem Text ins Diagramm wechselt, will bei dem nachsehen, woran er geschrieben hat — der Knopf wird in genau diesem Moment ohnehin gedrückt. Er zentriert und markiert den Knoten der Cursor-Zeile (focusNodeOfCaret, identisch zum Alt+Klick am Schreibtisch); auf einer Zeile ohne Knoten wird nur umgeschaltet. SPEC §9 und D17 (Nachtrag 3) nachgezogen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
24a70ca49e |
feat: Cursor in einer Beschreibung wählt ihren Knoten aus
Bisher fiel die Hervorhebung weg, sobald der Cursor von der Knotenzeile auf die `"`-Zeile darunter rückte: Die trägt keinen Knoten, also fand die Zeilensuche nichts. Falsche Auskunft — sie trägt keinen EIGENEN Knoten, gehört aber zu einem, und wer in ihr schreibt, arbeitet an genau diesem. Gilt für beide Formen, nicht nur die Kurzform: Die Zuordnung entsteht an derselben Stelle im Parser, an der auch die `---`-Blöcke landen, und dort trägt die Begründung sogar weiter — der Langtext steht am Dateiende, weit weg von seinem Knoten. Zugeordnet werden Textzeilen, Block-Kopfzeile (sie nennt den Knoten) und Leerzeilen innerhalb eines Blocks; nicht der `---`-Trenner und nichts unter unbekannter ID. `node.descLines` entsteht im Parser, der Renderer gibt sie als `data-desc-lines="3 4 5"` aus, app.js trifft sie per `[data-desc-lines~="N"]`. Bewusst getrennt von `descLines` (dem Text): Dort fallen Leerzeilen zu einem Absatztrenner zusammen und Blocktext unter unbekannter ID kommt gar nicht an. Beide Richtungen gehen jetzt über eine Auflösung (`nodeOfLine()`) — sonst wäre der ausdrückliche Alt+Klick aus einer Beschreibung heraus stumm geblieben, und das ist gerade der Ort, an dem man ihn braucht. 5 neue Tests (204 gesamt). SPEC §9 und D40 (Nachtrag 2) nachgezogen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
037db30d96 |
feat: Debug-Kasten minimierbar; Mobil-Inhalte ~25 % kleiner
Debug-Panel: Ein Klick entfernte es bisher ganz — was nichts half, weil der 15-Sekunden-Takt es sofort wieder aufbaute; auf dem Telefon verdeckte es damit dauerhaft die untere rechte Ecke. Jetzt schaltet der Klick zwischen Kasten und einem 26-px-Icon (⟳) unten rechts um. Der Zustand liegt im localStorage, nicht am Element: Das Panel wird bei jedem Takt neu bespielt, ein Zustand am DOM-Knoten wäre beim nächsten Tick weg. Über einen echten Intervall-Durchlauf geprüft. Der aufgeklappte Kasten bekommt white-space:pre-wrap — die mit \n gefügten Zeilen liefen bisher zu einem Absatz zusammen. Mobil ~25 % kleiner: MOBILE_ZOOM=0.75 als Faktor AUF den Nutzer-Zoom, nicht als neuer Anfangswert — sonst wäre die Verkleinerung nach dem ersten Zoomen weg und „Zurücksetzen" führte auf eine Größe, die auf dem Telefon zu groß ist. Die Anzeige nennt den effektiven Wert (75 %), sie soll beschreiben was man sieht. Die drei Messstellen rechnen jetzt effZoom() zurück; nachgemessen bei 0,75 liegen alle fünf Stationspunkte exakt auf ihren Blattknoten (0 px). Der Text kann kein zoom bekommen — Zeilennummern-Streifen und Spiegel messen am Textfeld (D33) und die Streifenbreite rechnet in ch. Also font-size .64rem an Textfeld und Streifen gemeinsam, Innenabstand im selben Verhältnis mit. 27 statt 19 Zeilen im Bild. Dabei aufgefallen: Der Grafikexport war schon immer zoom-abhängig falsch — die SVG-Schriftgrößen sind feste Zahlen, die Kästen gemessen. Fiel nie auf, weil 100 % der Normalfall war; mit dem Mobil-Faktor wäre es der Regelfall geworden. diagramToSvg() stellt den Zoom für die Messung jetzt kurz auf 1 (Knoten auf dem Schirm 143 px, im SVG 189,7 px — die unskalierte Größe). Desktop unverändert nachgeprüft (Zoom 1, 100 %, 13,6 px). Prod-Build enthält das Debug-Panel weiterhin gar nicht. SPEC §9, D17 (Nachtrag 2) und frontend/CLAUDE.md nachgezogen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ec086eb526 |
feat: Mobil genau ein Bereich, Umschalter oben links statt Splitter
Auf 375 px ist für zwei Bereiche kein Platz. Die stufenlose Aufteilung (D17) hat das nur verwaltet: Jede Stellung war ein Kompromiss, in dem beide Bereiche zu klein waren, und sinnvoll waren praktisch nur die Extreme — also genau das, was ein Umschalter direkt anbietet. Der Splitter kostete dabei dauerhaft eine zweite Titelzeile plus Griff. Jetzt: Diagramm ODER Text über die ganze Fläche, oben links ein Knopf zum Umschalten. Er zeigt das Ziel, nicht den Zustand — anders als der Modus-Wähler daneben, der drei Zustände hat; hier sieht man am ganzen Bildschirm, welcher gilt. Umgesetzt als zwei feste Knöpfe, je einer pro Titelzeile, die daher kein Icon wechseln müssen. Der Bereichstitel „DIAGRAMM" entfällt, der Dokumenten-Wähler steht neben dem Umschalter (er gehört zum Textbereich, D22), die übrigen Aktions-Knöpfe werden mitgeschaltet und bleiben komprimiert. Falle beim Bauen: Ein display:none-Panel misst sich zu null, und render() läuft bei jedem Tastendruck im Textbereich. Gemessen nach einer Eingabe bei verborgenem Diagramm: Pfad-Linie weg, null Stationspunkte, kein --stem-x. setMobilePane() zeichnet deshalb beim Umschalten neu (zum Diagramm dieselben vier Schritte wie applyLayout, zum Text renderLineNos) — danach wieder 48 px Pfadlänge, 5 Punkte, --stem-x 67,4px. Die Sprünge (D25) holen den nötigen Bereich selbst nach vorn. Zustand in werkbaum-ui gemerkt. Entfallen: --drow auf Mobil, --pmin-d/-e, syncPanelMins, setMobileDrow, Titelzeilen-Tippen. Desktop unverändert (nachgeprüft). SPEC §9, D17 (Nachtrag) und frontend/CLAUDE.md nachgezogen; paneToText/ paneToDiagram in allen neun Sprachen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ad2f3bab14 |
feat: Knoten-ID vor dem Titel mit optionalem Doppelpunkt; Alt+Klick pulst mit
Übliche Schreibweise ist jetzt `#auth: Backend` — die ID benennt den Knoten, der Titel erläutert sie; vorn hat sie eine feste Stelle, an der das Auge sie sucht. Der Doppelpunkt ist optional und reines Trennzeichen: Er gehört weder zur ID noch zum Label und fällt beim Parsen weg, sonst wären `#auth` und `#auth:` zwei verschiedene Adressen. Geschluckt wird er nur mit folgendem Leerraum oder Zeilenende (Leerraum-Regel wie bei `=`, `>`/`<`, `"`). Damit bleibt `#auth:#db` ID plus Abhängigkeit, und ein Doppelpunkt im Titel bleibt stehen. Die ID-Erkennung selbst ist unverändert — die Doppelpunkt-Gruppe ist optional; ein verlangtes `(?=\s|$)` hinter der ID hätte bestehende Zeilen umgedeutet. Der Block-Kopf im Beschreibungsteil nimmt den Doppelpunkt ebenfalls an. Alle mitgelieferten Beispiele umgestellt (docs/examples/, INITIAL, llms.md); SPEC §10 hat keine IDs. Legenden-Zeile hint_id in allen neun Sprachen. Dazu: Alt+Klick im Textfeld zeigt jetzt dieselbe Hervorhebung wie der Zeilenwechsel, Puls eingeschlossen — bisher unterdrückte focusNodeOfCaret() ihn, um doppeltes Scrollen zu vermeiden, und ausgerechnet die ausdrückliche Geste kam stiller an als das beiläufige Tippen. Getrennt über den neuen Parameter highlightCurrentNode(moved, scroll): 'nearest' | 'center' | false. 7 neue Parser-Tests (197 gesamt). SPEC §1, D36 (Nachtrag), D25 (Nachtrag 2) und llms.md nachgezogen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
099ee87bc1 |
feat: Cursor-Zeile hebt sich aus der Ebene, plus Puls beim Zeilenwechsel
Die Hervorhebung der Cursor-Zeile (D25) war zu leise. Sie benutzte denselben Kanal, den schon jeder Knoten belegt — jeder Status hat einen Rahmen (SPEC §4); ein weiterer Ring darunter ist ein Unterschied im Grad, nicht in der Art. Dazu kommt jetzt Tiefe: Schlagschatten + scale(1.04). Der einzige Kanal, den im Diagramm noch nichts belegt, also ohne Konflikt mit einer Farbcodierung. Um die Mitte skaliert, damit die Knotenmitte erhalten bleibt (alignStems, Stationspunkte messen genau die — nachgemessen dx=dy=0). Beim Zeilenwechsel meldet sie sich zusätzlich mit einem einmaligen Puls (Hüpfer + auslaufender Ring auf ::after); kein Blinken, prefers-reduced-motion schaltet ihn ab. Behoben dabei: Die Pfad-Inversion (D18) blasste den Ring auf 0,32 aus — die Ausnahme, die .fresh (D28) und .focusmark (D32) längst haben, fehlte für .current als einziger, obwohl der Fall der häufigste ist (Umschalter default an, im Beispiel 7 von 18 Knoten betroffen). diagramToSvg() muss die Erhebung abschalten (Klasse `exporting`): Der Export zieht die Live-Geometrie per getBoundingClientRect() nach, anders als beim box-shadow schlägt eine Vergrößerung dort durch. Nachgemessen: 155,6 statt 161,9 px. Im Druck fällt beides ebenso weg. SPEC §9 und D25 (Nachtrag) nachgezogen; llms.md unberührt (Notation, keine Editor-UI, D43). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ea2c7eef4a |
feat: „Original wiederherstellen" im Dokumenten-Menü (D22-Nachtrag)
Auf der Prod-Instanz gab es keinen Weg, ein bearbeitetes mitgeliefertes Dokument wieder auf den Auslieferungsstand zu bringen — der Reset ist ein Debug-Knopf, und das Fingerabdruck-Nachziehen (D27) fasst bearbeitete Texte nie an. Stilles Überschreiben beim Laden wäre überraschender Datenverlust (verworfen); stattdessen ein Menü-Eintrag, sichtbar nur für mitgelieferte Dokumente und nur bei Abweichung vom Auslieferungsstand, mit Rückfrage. Stellt Text und Name wieder her, verwirft Falt-Eingriffe. i18n in allen 9 Sprachen; im Browser durchgespielt (erscheint nach Edit, stellt her, verschwindet wieder). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4706541517 |
fix: promote-shipped.sh kennt den umbenannten Plan und die neue Syntax
Der Rename example-werkbaum.werkbaum → werkbaum.werkbaum (
|
||
|
|
c6f42f8da1 |
docs: Beispiel-Dokument zeigt die neuen Features (INITIAL, D27-Nachtrag)
Das mitgelieferte „Example" führt jetzt alles vor: eingeklapptes Concept-Kapitel (>) mit "-Hinweis am Wurzelknoten, Knoten-ID #cms mit Langbeschreibung hinter ---, eine Abhängigkeit samt bewusster Diskrepanz (Landing page [x], effektiv geplant — Farbe + [x]-Marke + gepunktete Kante), eine Zugabe (+ Dark mode) und die Hosting-Wahl als =-Gruppe. Damit Bestandsnutzer die neue Fassung sehen, zieht seedShippedDocs das Beispiel jetzt wie das Werkbaum-Dokument per Fingerabdruck nach (werkbaum-seeded-example, D27-Nachtrag) — nur bei unverändertem Text, der Reset setzt beide Merker. 21 Knoten, 0 Warnungen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1cd1d30d1b |
fix: Querverbindungen gepunktet — zurückhaltender (D41-Nachtrag)
Nutzer-Feedback: Die durchgezogene Kurve konkurrierte trotz Blässe noch mit dem Baum. Basis-Kanten jetzt gepunktet (runde Punkte, .1/5) — damit dreifach von den Baumlinien unterschieden (Krümmung, Blässe, Punktierung). Der D29-Einwand gegen einen dritten Linienstil galt den Baum-Abzweigen im kompakten Modus, nicht der eigenen gekrümmten Ebene. Die Fokus-Hervorhebung bleibt durchgezogen in Tinte; Export identisch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
afe536e33c |
feat: Querverbindungen der Abhängigkeiten — gekrümmt, blassgrau, Pfeil aufs Gebrauchte (SPEC §9, D41)
Eigene Zeichenebene wie der Pfad-Spline (Overlay-SVGs, /zoom): Kanten aus den neuen data-id/data-deps-Attributen des Renderers, dünn und blassgrau HINTER den Knoten — das Unterscheidungsmerkmal zu den orthogonalen Baumlinien ist die Krümmung, keine neue Farbe (die Palette ist vergeben, Rot bewusst nicht). Pfeilspitze zeigt auf das Gebrauchte, Endpunkte auf den Knotenkanten. Die Kanten des ausgewählten Knotens (Tastaturfokus, sonst Cursor-Zeile) liegen hervorgehoben in Tinte auf einer vorderen Ebene. Kanten zu verborgenen Zielen entfallen; erste ID-Vergabe gewinnt; Selbst-Abhängigkeiten zeichnen nichts. Basis-Kanten auch im SVG-Export (Schritt 1a) und im Druck, die Hervorhebung nicht. Browser-geprüft in allen drei Modi; Renderer-Attribute testgedeckt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
15c3e85e26 |
feat: Effektiver Status — Knotenfarbe zeigt ihn, Diskrepanz-Marke unten links (SPEC §4/§9, D39)
Jeder Status bekommt einen Fortschritts-Rang entlang der Ergebnis-Skala (D5); effektiv ist das Minimum über die Abhängigkeits-Hülle (Fixpunkt — Zyklen teilen ihr Minimum, wörtlich das „wird gemeinsam fertig" aus D34). Neutral und [-] zählen als 0, [!] als 1; bei doppelter ID gilt die erste Vergabe. Entschieden (Nutzer): Die Knotenfarbe zeigt den effektiven Status — das Diagramm beantwortet „wie weit ist das wirklich?". Wo der eigene Status weiter ist, sitzt unten links die eigene Statusbox als Marke in den eigenen §4-Farben (auch im Export/Druck); Tooltip und a11yEffective benennen beides. XOR-„realisiert" und „Was ist neu?" bleiben bewusst intrinsisch. i18n in allen 9 Sprachen; 12 neue Tests (tests/effective.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cbe798d746 |
feat: Faltmarken (>/<) — Teilbäume ein-/ausklappbar (SPEC §1/§9, D38)
Parser liest die Marke zwischen Zeichen und Statusbox (Leerraum-Regel, `- >Achtung` bleibt Label); den Anfangszustand rechnet initialCollapsed() in model.js: `>` klappt ein, `<` (und die Fokusmarke !!!) holt seinen Teilbaum hervor, indem die Faltung die Pfad-Ebenen hinunterwandert — Geschwister stehen als einzelne eingeklappte Knoten da, jede Kante bleibt eine echte. Im Diagramm: Falt-Zeichen ▾/„▸ n" vor dem Label (n = alle verborgenen Knoten), Klick oder ←/→ am fokussierten Knoten klappt um; Eingriffe je Label-Pfad, nur für die Sitzung, Dokumentwechsel setzt zurück. Eingeklappte Kinder werden nicht gerendert (Export, Messungen, Pfadlinie automatisch konsistent), ihre Warnungen aber weiter gemeldet. Export/Druck: „▸ n" bleibt, das ▾ offener Knoten fällt weg. Legende + a11y in allen 9 Sprachen; SPEC-§11-Abschnitt in §1/§9 überführt; 15 neue Tests (tests/fold.test.js), Snapshots aktualisiert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ba37fc5a0 |
feat: Abhängigkeiten (:#a,#b) parsen — unbekannte ID warnt, Zyklen zulässig (SPEC §1, D37)
Extraktionsschritt 7: alle alleinstehend angesetzten `:#a,#b`-Token — EIN zusammenhängendes Token ohne Leerraum, mehrere je Zeile werden zusammengeführt; eingeklammerte Erwähnungen wie `(:#auth,#api)` bleiben Label (Zitier-Konvention wie `(#auth)`, deshalb enger als die frühere §11-Formulierung). `deps` sind ID-Strings am Knoten; der Parser prüft nur Existenz (Warnung unknownDep, Vorwärts-Referenzen normal), Zyklen werden bewusst nicht einmal erkannt. Sichtbar als `→ #a, #b` im Tooltip und als a11yDeps; Legendenzeile + Warntext in allen 9 Sprachen. Im mitgelieferten Plan: Zyklen-legal und unknown-id-Warnung auf [x], Abhängigkeits-Knoten auf [~]. 12 neue Tests (tests/deps.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6ecf3e4c69 |
feat: Knoten-IDs (#auth) parsen — doppelte ID warnt (SPEC §1, D36)
Extraktionsschritt 6 (nach den Tags): das erste alleinstehend angesetzte `#name`-Token wird die Knoten-ID (Zeichenmenge wie `@name`); weitere `#`-Token bleiben im Label (reservierte Ticket-Referenzen), `:#a,#b` und `C#` werden nicht gefressen. Doppelte ID → Warnung duplicateId an der späteren Zeile mit Nennung der ersten; die spätere gilt trotzdem. Sichtbar im Tooltip (erste Position) und als a11yId im aria-label — noch keine eigene Diagramm-Darstellung. Legendenzeile + Warntext in allen 9 Sprachen; die drei `#`-Erwähnungen im mitgelieferten Plan sind eingeklammert, damit sie Erwähnungen bleiben. 12 neue Tests (tests/ids.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d276c94840 |
feat: XOR-Gruppen (=) — genau eine Alternative (SPEC §3, D35)
Parser erkennt `=` als disjunktives Gate (nur mit folgendem Leerraum — `=SUMME(…)` bleibt Label); Mischungen melden mixedGate wie gehabt. Die XOR-Regel warnt je WEITERER realisierter Alternative (xorConflict mit Zeilennummer; realisiert = [~]/[/]/[x]/[^], siehe D35). Darstellung erbt die komplette any-of-Geometrie (ul class="or xor"), ergänzt um eine „1"-Plakette am Austritt der Sammelleiste — auch im Grafikexport. Legende (+ hint_root) und Warntext in allen 9 Sprachen; SPEC §11-Eintrag in §1/§3/§9 überführt; 13 neue Tests (tests/xor.test.js). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
837922aaa7 |
frontend: Zeilennummern im Texteditor + Alt+Klick auch im Text
Zwei Lücken, die beim Arbeiten am Text auffallen: **Zeilennummern (D33).** Die Warnungen nennen Zeilennummern („Zeile 12: …"), das Textfeld zeigte keine — man musste abzählen. Der Streifen links vom Textfeld hebt zusätzlich die Cursor-Zeile und Zeilen mit Warnung hervor. Die Zahlen stehen auf **gemessenen** Höhen (Spiegel-`div` aus D25, ein Marker je Zeile), nicht auf „Zeilenhöhe × n": Der Text bricht weich um, eine lange Zeile belegt mehrere Bildzeilen und behält doch eine Nummer. Bei 375 px Breite brechen 134 der 143 Zeilen um — gerechnet liefe der Streifen sofort davon. Dabei fiel ein älterer Fehler im Spiegel auf: `width = src.clientWidth` bei `content-box` machte ihn um genau die 32 px Innenabstand zu breit, er brach später um als das Textfeld. Mit `box-sizing:border-box` stimmen die Umbrüche nachweislich überein (170 Bildzeilen beiderseits) — das behebt nebenbei die Zielgenauigkeit des Sprungs zu langen Zeilen (D25). **Alt+Klick im Textfeld (Nachtrag zu D25).** Die Rückrichtung lief bisher allein über die Cursor-Zeile — und die scrollt bewusst nur `nearest` und nur beim Zeilenwechsel, sonst ruckelte das Diagramm beim Tippen. Als „zeig mir das" taugt sie damit nicht. Alt+Klick im Text (Tastatur Alt+Enter) zentriert den Knoten jetzt und gibt ihm den Fokus: derselbe Modifier in beide Richtungen, und mit Fokus wird daraus ein vollständiger Hin- und Rückweg. Die Legenden-Zeile nennt beide Richtungen (kein zehnter i18n-Schlüssel), 9 Sprachen nachgezogen. SPEC §9 ergänzt, D33 angelegt, Plan-Dokument und frontend/CLAUDE.md nachgeführt. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
953277ab83 |
frontend: Fokusmarke bekommt einen eigenen Strahlenkranz in Petrol
Der mit der Cursor-Zeile geteilte Ring war zu leise — „das erkennt man zu wenig" (Nutzer). Damit fällt auch die in D32 als Preis notierte Zweideutigkeit weg: Sie war das Zugeständnis für die Sparsamkeit, und wenn die Sparsamkeit nicht funktioniert, ist das Zugeständnis nicht mehr zu rechtfertigen. Gebaut wie der gelbe Kranz aus D28 (Schein nach außen, Füllung bleibt dem Status, kein Blinken, `z-index:2` gegen das Abschneiden durch später gemalte Geschwister) — aber in PETROL, nicht in Gelb. Die Frage stand ausdrücklich im Raum; die Antwort ist nein, weil die beiden Verschiedenes bedeuten und gleichzeitig auftreten: Gelb ist eine stehende Tatsache über den Plan („seit deinem letzten Besuch live gegangen"), Petrol ein Zuruf („schau jetzt hierhin"). Im Pad-Betrieb ist die Kombination der Normalfall — man zeigt auf das, was sich geändert hat. Wären beide gelb, könnte ein Knoten nicht beides sagen. Der Schein ist helles Teal (#14B8A6), der Ring dunkles Petrol. Die erste Fassung nahm für beides `--or` und wirkte trotz identischer Geometrie leiser als das Gelb. Der Grund ist nicht Geschmack: Ein dunkler Schein auf weißem Grund liest sich als Schatten, ein heller als Licht. Gegen die Stationspunkte des günstigsten Pfads (D18) geprüft statt angenommen — zwei Marken gezielt auf Pfad-Endknoten (mit Punkt), eine daneben (ohne): Der Kranz sitzt eng am Knoten und hat einen harten Ring, der Stationspunkt ist ein großer blasser Fleck ohne Rand. Verwechseln kann man das nicht. Kombinationen ausbuchstabiert, weil sie real vorkommen: `focusmark.current` (Tinte innen, Teal außen) und `fresh.focusmark` (Petrol-Ring innen, Gelb außen — zwei Scheine nach außen gehen nicht). Letztere steht nach der ersten: gleiche Spezifität, und bei allen drei sollen Zuruf und Neuheit gewinnen. SPEC §9 und D32 nachgezogen. |
||
|
|
6917b420d8 |
docs: eingebettetes Pad — Update-Poller als Ursache ausgeschlossen (D31)
Gemeldet: Das eingebettete Etherpad lässt sich nach einer Weile nicht mehr bearbeiten; Verdacht auf den Update-Poller. Der ist es nicht, und das ist belegt statt vermutet: - `checkForUpdates()` holt `location.href`, also den Werkbaum-Origin — es kann die Pad-Drosselung gar nicht auslösen. - Es lädt die Seite nicht neu; `checkAndShowUpdateNotification()` und `showUpdateDebug()` hängen nur `position:fixed`-Elemente an `<body>`. Kein Neuaufbau eines Containers um den Rahmen — der WÜRDE ihn neu laden, weil ein Umhängen im DOM jeden `<iframe>` neu lädt. - Gemessen über ~6 Minuten: window-Marker überlebt (kein Seiten-Reload), `load`-Zähler am Rahmen bleibt 0 (kein Rahmen-Reload), Sichtbarkeit durchgehend `visible`. Reproduziert wurde der Fehler nicht. Nach sechs Minuten lief die Verbindung noch — geprüft ohne Tippen, indem das Pad von außen geändert wurde: der Rahmen übernahm die Änderung sofort. Etherpads eigene Meldungen sind von außen nicht lesbar (fremdstämmiger Rahmen, eigener Konsolen-Kontext). Als Verdacht festgehalten, ausdrücklich unbewiesen: das `SameSite=Lax`-Cookie. Die erste Verbindung gelingt, aber beim Wiederaufbau nach einem Abbruch fehlt die Autoren-Identität — sichtbar, aber nicht mehr beschreibbar. Passt zu „nach einer Weile" und dazu, dass es im eigenen Tab nicht auftritt. Der entscheidende Test gehört dem, der es sieht: Pad im eigenen Tab öffnen. Geht es dort, ist es der Dritt-Kontext (nur serverseitig zu beheben). Klemmt es dort auch, liegt es am Pad. Behelf ist schon eingebaut: Einmal durch den Ansichts-Wähler schalten lädt den Rahmen neu (über `about:blank`). Bewusst NICHT in den Neu-laden-Knopf gelegt — der zieht nach dem Tippen Spiegel und Diagramm nach, und ein Rahmen-Reload dabei kostete jedes Mal die Schreibmarke im Pad. |
||
|
|
816a77d9e7 |
notation: !!! als Fokusmarke — ein geteilter Zeigefinger im Text
Beim gemeinsamen Arbeiten an einem Pad (D31) fehlte das Naheliegendste: „schau mal hier". Etherpad zeigt die Cursor der anderen im Pad, aber Werkbaum kommt nicht an sie heran — ein fremdstämmiger Rahmen gibt keinen DOM-Zugriff, der Klartext-Export überträgt nur Text. Eine Marke IM Text ist damit nicht der Notbehelf, sondern der einzige Weg. Und sie hat etwas, das ein Cursor nicht hat: alle sehen dieselbe Stelle. Erkannt wird `!!!` nur ALLEINSTEHEND (`(^|\s)!!!(?=\s|$)`), sonst verlöre jedes „Achtung!!!" seine Ausrufezeichen und niemand fände den Grund. Bewusst ohne Lookbehind — Safari kennt es erst ab 16.4, und die Notation soll nicht an einer Browserversion hängen. Position in der Extraktion (SPEC §1): nach den Tags; der Kommentar fällt vorher weg, eine Marke hinter `%%` wirkt also nicht. Darstellung ist DIESELBE wie die der Cursor-Zeile (D25), nicht eine vierte: neben Fokusrahmen, Strahlenkranz (D28) und Cursor-Ring wäre ein weiterer Ring ein Zeichen zu viel — es gibt nur einen Begriff „hier schauen". Preis: Im Bild ist nicht zu unterscheiden, ob die eigene Cursor-Zeile oder eine fremde Marke leuchtet; für Screenreader benennt `a11yFocusMark` es ausdrücklich. Ins Bild geholt wird nur bei ÄNDERUNG der Marke, Schlüssel ist der Label-Text statt der Zeilennummer (Umsortieren im Pad soll nicht neu scrollen). Ohne diese Bedingung zöge jeder Neubau den Blick zurück und man käme nicht weg. Beides geprüft: gleiche Marke -> Scrollstand bleibt 0, Marke gewandert -> springt auf 242 und markiert den neuen Knoten. Beim Hinsehen gefunden: Die Pfad-Inversion verschluckte den Zeigefinger. Der erste markierte Knoten im Test war eine nicht gewählte any-of-Alternative („Headless CMS") — `opacity:.32; saturate(.4)`, der Ring praktisch unsichtbar. Der Zeiger zeigte auf nichts. Dieselbe Ausnahme wie für D28 behebt es, und hier ist sie zwingender: „Sollten wir diese verworfene Alternative noch einmal ansehen?" ist einer der wahrscheinlichsten Gründe, überhaupt zu zeigen. Anders als beim optionalen Knoten (D29), wo das Zurücktreten die Aussage IST. Nicht im Druck und nicht im Grafikexport (wie die Cursor-Zeile): Die Marke sagt „schau jetzt hierhin", nicht „so ist der Plan". Nicht ins kanonische Beispiel (SPEC §10) aufgenommen — das ist zugleich Test-Fixture, ein dauerhafter Zeigefinger darin wäre eine Aussage, die niemand gemacht hat. Verworfen war `%%!` (Kommentar-Variante, bliebe harmlos liegen und könnte die Begründung tragen) — der Nutzer wählte `!!!`, weil es im Moment des Zeigens schnell zu tippen ist, und genau dann will man nicht über Syntax nachdenken. Der Preis steht in D32: die Marke bleibt sichtbar stehen, bis jemand sie löscht; Werkbaum kann sie nicht entfernen (kein Schreibzugriff auf das Pad). SPEC §1/§9 zuerst, dann D32, dann Code. 15 neue Tests (86 -> 101), Legende und i18n in allen 9 Sprachen. |
||
|
|
0a08706877 |
frontend: Pad einbetten — drei Ansichten mit Splitter, Spiegel bleibt
Das Einbetten ist möglich, ohne D25 zu opfern. Nachgemessen zuerst, ob der
Server es überhaupt zulässt: pad.hostsharing.net sendet kein
`X-Frame-Options` und keine CSP mit `frame-ancestors`, und im Versuch baute das
Pad in einem fremdstämmigen Rahmen seine inneren Editorframes auf
(`contentWindow.length === 2` = ace_outer/ace_inner), samt Werkzeugleiste,
heiler Einrückung und lebender Socket-Verbindung.
Einbetten heißt aber nicht Ersetzen: Ein cross-origin-iframe gibt keinen
DOM-Zugriff, ein ersetztes Textfeld kostete also beide Richtungen von D25
(Alt+Klick -> Zeile, Cursor-Zeile -> Knoten). Deshalb drei Ansichten, reihum
über einen Wähler in der Editor-Titelzeile:
- Pad und Text, geteilt durch einen eigenen Splitter (Mechanik und Idiom vom
Legenden-Splitter aus D26: von der Spiegelseite gezogen, Doppelklick setzt
zurück, `--pcol`/`--prow` getrennt gehalten und persistiert). Der Spiegel darf
schmal werden und trägt weiter die Sprünge — die arbeiten auf unserem
eigenen `<textarea>`.
- nur Pad — Spiegel ausgeblendet; `revealEditor()` schaltet bei einem Sprung
auf „beide", sonst zeigte der Sprung ins Nichts.
- nur Text — wie bisher, kein Rahmen.
Ein Wähler statt dreier Knöpfe, weil die Titelzeile schon Dokument, Pad, Neu
laden, Kopieren, Legende und Fensterknöpfe trägt (mobil dreifach eng, D17) —
derselbe Reihum-Griff wie beim Modus-Wähler dort. Der Zustand steckt im Symbol
(geteilt / links gefüllt / rechts gefüllt), der Tooltip nennt ihn im Klartext.
Der Rahmen bekommt nur sichtbar ein `src`, sonst `about:blank`. Das ist kein
Geiz um Bytes: Ein geladenes Pad verbindet sich per Socket und macht dich in
dessen Anwesenden-Liste sichtbar. „Nur Text" ist damit die Ansicht, die nichts
von dir verrät.
Zwei Dinge, die beim Prüfen auffielen und behoben sind:
- Eine überzählige `}` ließ das Modul gar nicht laden. Die Messung sah trotzdem
plausibel aus, weil die abgefragten Werte die statischen HTML-Vorgaben waren —
seither prüft jede Messung zuerst, ob überhaupt Knoten gerendert sind, und
`node --check` läuft vor dem Blick in den Browser.
- In „nur Pad" war der Spiegel weiterhin sichtbar (sogar breiter als das Pad):
die Regel `.src-area.pv-pad>textarea{display:none}` fehlte.
Bleibender Preis: `Set-Cookie: token=…; SameSite=Lax`. Das Cookie ist Etherpads
Autoren-Identität und wird im fremdstämmigen Rahmen nicht mitgesendet — man ist
bei jedem Laden ein neuer Autor. Nur serverseitig zu beheben
(`cookie.sameSite: "None"`), nicht in Werkbaum. Steht in D31.
Auf kleinem Bildschirm nachgemessen statt geschätzt: bei 375x812 bleiben Pad
317 px und Spiegel 180 px, per Splitter verschiebbar — brauchbar, also keine
Sonderregel für Mobil.
SPEC §9 und D31 nachgezogen; 5 neue i18n-Schlüssel in allen 9 Sprachen.
|
||
|
|
793561c873 |
frontend: Pad auf Knopfdruck holen statt zu takten — Etherpad drosselt
Der Hintergrund-Takt aus dem letzten Commit funktionierte in der Praxis nicht:
im Netzwerk-Mitschnitt stapelten sich Anfragen und wurden abgebrochen
("cancelled"). Die Ursache ist nicht Langsamkeit, sondern ein Rate Limit —
Etherpads `importExportRateLimiting` ist serienmäßig an und lässt 10 Abrufe je
90 s und IP zu; der Takt wollte 36. Jenseits davon antwortet die Gegenseite
nicht mit 429, sondern hält die Verbindung ohne Antwort offen. Nachgemessen:
12:48:12 Abbruch nach 25 s (0 Bytes)
Abbruch nach 25 s (0 Bytes)
12:50:23 HTTP 200 nach 0,436 s <- nach ~40 s Pause
Zwei Minuten totgestellt, dann sofort in 0,4 s da. Gegen eine Drosselung kann
ein Takt nicht gewinnen, er erzeugt sie. Also ein Knopf. Damit entfallen
Stabilitätstakt, Sichtbarkeits-Wächter, Anti-Stapel-Riegel und Wiederanlauf.
Der Knopf greift zudem gut mit "Was ist neu?" (D28) zusammen: drücken, und was
seither in Produktion ging, leuchtet auf.
Verhindern UND melden, beides:
- Mindestabstand 10 s zwischen zwei Abrufen. Das ergibt höchstens 9 je 90 s und
bleibt damit beweisbar unter Etherpads Budget — die Drosselung wird gar nicht
erst ausgelöst. Nach einem Abbruch sind wir schon drüber: dann das ganze
Fenster (90 s) abwarten statt weiter dagegen zu rennen.
- Der Knopf bleibt in der Sperre **klickbar** (nur `aria-disabled`, nicht
`disabled`): Ein Klick meldet den Grund und nennt die Grenze samt Restzeit.
Die Meldung landet in `#warn`, das eine Live-Region ist, wird also angesagt.
Ein `disabled` erklärt nichts.
- Eigener Warnungstyp `sourceTimeout` für den Abbruch: `sourceLoad` zeigt auf
CORS und schickte hier auf die falsche Fährte.
- `setPadBusy()` dreht das Symbol während des Abrufs — das können bis zu 20 s
sein, und ohne Zeichen wirkt der Knopf kaputt.
Zwei Fehler des Vorgänger-Commits fielen dabei auf:
- `padSource` und der Takt standen HINTER dem `await` des ersten Abrufs. Ein
einziger Fehlschlag — bei dieser Gegenseite der Normalfall — ließ das Dokument
tot liegen, ohne Knopf und ohne Wiederversuch, bis zum Neuladen der Seite.
Genau das war die vom Nutzer gemeldete Fehlermeldung.
- Kein Erfolg räumte die Warnung des ersten Versuchs weg; sie hätte gestanden,
während der Text längst da war.
Dazu: `deleteDoc()` beendet die Pad-Quelle, sonst legte ein späterer Abruf das
gelöschte Dokument wieder an und aktivierte es auch gleich.
Nebenbefunde, beide nachgemessen und in D31 festgehalten:
- Das *Tippen* im Etherpad-Editor war der letzte offene Punkt. Der Nutzer hat
` - [ ] Layout` eingerückt eingetippt; der Export gibt es zeichengenau
zurück (vier echte Leerzeichen, kein Tab, kein Listen-Markup).
- Einbetten verweigert der Server nicht: kein `X-Frame-Options`, keine CSP mit
`frame-ancestors`, und im Versuch baute das Pad in einem fremdstämmigen Rahmen
seine Editorframes auf. Preis ist `SameSite=Lax` am Autoren-Cookie (jedes
Laden ein neuer Autor; nur serverseitig zu beheben) und der Verlust beider
Richtungen von D25, falls das Textpanel ersetzt wird.
86 Tests (85 -> 86); Schreibschutz zeigt den Text jetzt in Tinte statt grau —
hier wird gelesen, der Plantext ist der Hauptinhalt.
|
||
|
|
3310cab7be |
frontend: ?etherpad= — Echtzeit-Zusammenarbeit über ein geliehenes Pad
Werkbaum hat kein Backend, und das eigentlich Schwere an gemeinsamem Bearbeiten ist das Zusammenführen gleichzeitiger Änderungen — im Plan als `[!] Merging simultaneous edits` markiert. Etherpad hat das gelöst. Also geliehen statt nachgebaut: das Pad ist die Schreibfläche, Werkbaum die Ansicht. Nachgemessen an pad.hostsharing.net, bevor irgendwas gebaut wurde: - der Klartext-Export sendet `Access-Control-Allow-Origin: *`, - das kanonische Beispiel (SPEC §10) kommt byte-identisch zurück, - der HTML-Export zeigt kein Listen-Markup — Etherpad deutet `-` nicht zur Aufzählung um und behält die führenden Leerzeichen. Das war das Risiko, das die Idee hätte erledigen können. Nicht geprüft ist das Tippen im Etherpad-Editor selbst (Tab-Einrückung, mögliches Auto-Bullet); dafür braucht es einen echten Browser. Eigener Parameter statt `?sourceUrl=`: Die URL, die ein Mensch in der Hand hat, ist die Pad-URL — `/export/txt` hängt Werkbaum selbst an. Vor allem aber lizenziert der eigene Parameter das andere Verhalten, sodass D23 unangetastet bleibt: `sourceUrl` heißt weiter „statische Datei, einmal pro Laden geholt", bestehende Links bekommen kein Polling. Name und id sind die vollständige Pad-URL (Pad-Namen sind nur pro Instanz eindeutig). Textfeld schreibgeschützt, Knopf öffnet das Pad: ohne das verschwände getippter Text beim nächsten Abruf. Schrift bleibt Tinte statt grau — hier wird gelesen, der Plantext ist der Hauptinhalt. Drei Riegel im Takt, jeder aus einem echten Fehler: - `padBusy` — im Netzwerk-Mitschnitt stapelten sich die Abrufe, weil die Gegenseite langsamer war als der Takt; eine spät eintreffende alte Antwort hätte neueren Text überschrieben, - Abbruch nach 10 s — sonst bliebe der Riegel bei hängender Gegenseite für immer zu, - `visibilityState` + `visibilitychange` — nicht im Hintergrund abrufen, aber bei Rückkehr sofort. Der Stabilitätstakt übernimmt erst beim zweiten gleichen Abruf, sonst sieht man die anderen mitten im Tippen. Die Normalisierung der Pad-Adresse liegt headless in `remote.js`, damit sie testbar ist (23 neue Tests, 60 -> 83). Im Vorschau-Browser meldet `visibilityState` „hidden" und HMR lädt bei jeder Quelländerung neu — ein Reload sieht wie eine geglückte Übernahme aus; nachgewiesen wurde die Übernahme deshalb mit einem Marker auf `window`, der einen Reload nicht überlebt. SPEC §9 zuerst, dann D31, dann Code. Der Plan bekommt den Knoten nach D30 mit `[x]`, nicht `[^]`. |
||
|
|
56c18cb8cb |
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> |
||
|
|
091e8849d8 |
frontend: optionale Abzweige gestrichelt + Stiel trifft die Knotenmitte
Zwei Nachbesserungen aus dem ersten Blick auf `+` (D29). **Gestrichelter Abzweig.** Der hohle Kreis allein war zu leise. Der Einwand gegen einen dritten Linienstil bleibt richtig, greift aber nicht so weit wie gedacht: Gestrichelt wird NUR der Abzweig zum Knoten (nicht die Sammelleiste — die gehört der ganzen Gruppe) und zwar in TINTE, während die any-of-Linien gestrichelt in Grau sind. Entscheidend ist, dass beide sich nie am selben Verteiler treffen können: | darf nach SPEC §3 nicht mit -/+ gemischt werden. Der Kreis bleibt zusätzlich — er sagt, WELCHER Knoten gemeint ist. Umgesetzt an den vorhandenen Pseudoelementen, ohne neue Zeichenebene: im Fächer der senkrechte Stiel (border-left von ::after, beim letzten Kind border-right von ::before), gestapelt der waagerechte Ast (border-top von ::before); die jeweils andere Kante ist die Leiste und bleibt durchgezogen. Dafür braucht auch das <li> die Klasse `opt` — den Abzweig zeichnet es, nicht der Knoten. Der SVG-Export zieht mit (dash || isOpt). **Stiel trifft die Knotenmitte auch waagerecht (`--stem-x`).** Dabei fiel ein älterer Fehler auf, gemeldet als „die Linie zu Wahl trifft den Knoten nicht": Im horizontalen Fächer lief der Stiel zu einem Knoten mit any-of-Kindern neben dem Knoten vorbei (gemessen 13,4 px). Dieselbe Verwechslung, die D10 senkrecht schon behoben hat, nur in der anderen Achse — der Stiel saß bei 50 % der ZELLE, und das ist nur die Knotenmitte, solange der Knoten darin zentriert steht. `li.has-or` ist aber flex-start (der Knoten steht links, damit die any-of-Sammelleiste unter ihm aufsetzt), und die Zelle ist so breit wie der Teilbaum. Rein in CSS nicht lösbar: gebraucht wird die Knotenbreite, und kein Selektor macht sie einer Elternregel zugänglich (Anchor Positioning ist Chrome-only). `alignStems()` misst deshalb nach jedem render() und in applyLayout() die Knotenmitte der betroffenen Zellen und setzt `--stem-x`; die Pseudoelemente rechnen über left:var(--stem-x, 50%) bzw. right:calc(100% - var(--stem-x, 50%)). Der Rückfallwert 50 % hält alle übrigen Zellen ohne Messung richtig, die transponierten Modi setzen left/right ohnehin fest. Messwerte werden wie in drawCheapPath() durch `zoom` zurückgerechnet. Verifiziert: Vitest 60/60 (2 neue Tests: `opt` auch am <li> neben dem Gate der eigenen Kinder; <li> ohne Attribut, wenn weder Kinder noch optional). Im Browser gemessen: Abweichung Stiel↔Knotenmitte bei „Wahl" 13,4 px → 0,0 px, alle übrigen Zellen unverändert 0,0 px ohne gesetzte Variable. Angesehen in horizontal und kompakt: gestrichelter Ast in Tinte zum Kreis, Sammelleiste durchgezogen, deutlich unterscheidbar vom gestrichelt-grauen any-of-Ast daneben. SVG-Export gerendert geprüft: 2 von 9 Tinte-Linien gestrichelt, beide Kreise am Auftreffpunkt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
aa3e087ee6 |
notation: + für optionale Knoten — Zugaben statt Pflicht oder Alternative
Die Notation kannte nur „erforderlich" (-) und „wählbar" (|). Ein einzelnes zusätzliches Feature — weder nötig noch Alternative zu etwas anderem — musste als normales -Kind notiert werden und log damit. Feature-Modelle (FODA) unterscheiden seit den 90ern mandatory/optional/alternative; `+` ergänzt die fehlende zweite Beziehung. Mnemonik: `-` Teilpaket, `+` Zugabe, `|` Alternative. Anlass ist nicht die Optik, sondern der günstigste Pfad (D18): markCheapest() lief bei all-of über ALLE Kinder, jede Zugabe steckte also im errechneten Minimum — systematisch zu groß, und zwar umso mehr, je ehrlicher ein Plan auch die Kür notiert. Sichtbar wird es beim Alternativenvergleich: eine Alternative mit teurer Zugabe verlor gegen eine schlichtere, obwohl die Zugabe gar nicht dazugehört. - Parser setzt `optional:true` und lässt `type:'and'` stehen — `+` gehört zum Knoten, nicht zur Gruppe. Dadurch bleiben gateOf() und die mixedGate-Warnung unverändert richtig: sie meldet weiter genau dann, wenn | mit -/+ gemischt wird. `-` neben `+` ist erlaubt und still — „diese drei sind nötig, das hier wäre schön" ist der Normalfall, nicht der Fehlerfall. - Aus dem Pfad fallen optionale Knoten über pathChildren() heraus, die eine Stelle, die cheapestCost() und markCheapest() gemeinsam nutzen — deshalb wirkt es samt Teilbaum. - Darstellung: hohler Kreis mittig auf der Knotenkante, wo der Abzweig auftrifft (FODA-Konvention). Bewusst KEIN dritter Linienstil: im kompakten Modus codiert allein der Stil das Gate (D15), gepunktet müsste sich dort gegen gestrichelt-grau behaupten. Der Kreis ist orthogonal dazu. CSS-Grundfall ist gestapelt (links/50 %), Ausnahme der horizontale Fächer (oben/50 %), Rück-Ausnahme der gestapelte all-of-Teilbaum unter any-of (D18) — andersherum wären es vier Ausnahmen statt zwei. - SVG-Export zeichnet den Kreis NACH den Knoten (optMarks, Schritt 3a): er liegt halb außerhalb der Box und würde sonst vom Knoten-Rechteck überdeckt. - Legende, Knoten-Tooltip und aria-label in allen neun Sprachen; hint_root formuliert die neue Mischregel. Bekannte Schwäche, bewusst in Kauf genommen: Bei aktivem Pfad-Umschalter wird der optionale Knoten ausgeblasst (opacity:.32) — und mit ihm sein Kreis, der die Erklärung dafür wäre. `opacity` am Elternteil schlägt auf Pseudoelemente durch, das lässt sich nicht zurücknehmen. Das Zurücktreten ist hier die Hauptaussage (wie bei nicht gewählten Alternativen), Tooltip/aria/Legende liefern die Begründung nach. Verhaltensänderung: `+` am Zeilenanfang ist jetzt ein Zeichen und nicht mehr Teil des Labels (`+ 5 % Puffer` ergibt „5 % Puffer"). Test-abgedeckt. SPEC §1/§3/§9/§10 zuerst, dann Code (CLAUDE). Das kanonische Beispiel in §10 enthält jetzt eine `+`-Zeile und ist mit der Test-Fixture wieder deckungsgleich. Der mitgelieferte Werkbaum-Plan markiert Drucklayout, „Was ist neu?" und die Personenfarben als Zugaben — „Was ist neu?" war der Auslöser der Frage. Verifiziert: 12 neue Tests (Parser setzt optional/type; Status/Größe/Tags/URL am +-Knoten; führendes + wird verbraucht; Pfad lässt Zugabe samt Teilbaum aus; Kosten des Elternknotens ohne Zugabe; Alternativenvergleich ohne Zugaben; optionale Knoten bleiben sichtbar; opt-Klasse; keine Warnung bei -/+; Warnung bei |/+; aria-label). Vitest 58/58, Snapshot zeigt `node opt` OHNE `cheap`. Im Browser in allen drei Modi angesehen: Kreis sitzt in horizontal oben mittig, in vertikal und kompakt links auf halber Höhe, jeweils genau auf dem Ende des Abzweigs; SVG-Export enthält beide Kreise an denselben Punkten (gerendert geprüft, nicht nur im Quelltext). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7a52277b84 |
frontend: „Was ist neu?" — neu in Produktion mit gelbem Strahlenkranz
Dokumente von außen (mitgeliefert D27, ?sourceUrl= D23) ändern sich, ohne dass der Betrachter es merkt. Sie zeigen jetzt, was sich seit seinem letzten Besuch getan hat. - „Neu" heißt bewusst NICHT „Zeile hinzugefügt", sondern **neu in Produktion**: ein Knoten trägt jetzt [^] und tat es in der zuletzt gesehenen Fassung nicht. Ein Zeilendiff meldete vor allem Rauschen; die Nachricht, die zählt, ist was live gegangen ist. Nebeneffekt: es leuchtet eine Handvoll Knoten, nicht dreißig. - Basis ist die zuletzt GESEHENE Fassung je Dokument (`werkbaum-seen`), nicht die letzte Auslieferung — wer Fassungen überspringt, sieht alles seither. Fortgeschrieben wird erst beim Bestätigen, sonst wäre die Meldung nach einem Neuladen weg, bevor sie jemand bemerkt. Beim Erstkontakt leuchtet nichts. - Knoten-Identität ist der Label-Pfad, nicht die Zeilennummer: Umeinrücken und Umsortieren erzeugen keine Falschmeldungen; gleichnamige Geschwister per Index; Umbenennen gilt als neuer Knoten (der Text ist der Vertrag, D14). - Darstellung: gelber Strahlenkranz nach außen, kein Blinken (WCAG 2.2.2/2.3.1; Blinken zöge dauerhaft den Blick statt einmal zu melden). Außen, weil die Füllung dem Status gehört (SPEC §4). Zusammen mit der Cursor-Zeile: Tinte innen, Gelb außen. Nicht im Druck und nicht im Grafikexport — die Markierung hängt am persönlichen Besuchsstand. - Knopf im Diagramm-Kopf nur, wenn es etwas gibt; nennt die Anzahl, Klick bestätigt. Kein Dauer-Umschalter. - Bei selbst bearbeitetem mitgeliefertem Dokument wird nichts hervorgehoben — dort fehlt die saubere Vergleichsbasis (D27). Fehler beim Bauen, der zuerst durchrutschte: Die Menge wurde beim Laden aus einem eigenen Parse-Durchlauf berechnet. Der Zähler stimmte, aber kein Knoten leuchtete — `Set.has()` prüft Objektidentität, und die gerenderten Knoten kamen aus einem anderen Parse. `render()` bildet die Menge jetzt bei jedem Durchlauf aus den gerade geparsten Wurzeln; vorgehalten wird nur der geparste Basisbaum. Verifiziert: 9 neue Modelltests (Statuswechsel, neuer [^]-Knoten, nicht-[^] ignoriert, unverändert ignoriert, Erstkontakt leer, Umsortieren/Tabs ohne Falschmeldung, gleiches Label unter verschiedenen Eltern, gleichnamige Geschwister, verworfene). Im Browser: Erstkontakt setzt nur die Basis (Knopf versteckt); zurückgedrehte Basis lässt genau die erwarteten Knoten leuchten (Zähler 3, dann 1); Bestätigen räumt auf und überlebt das Neuladen; ohne Bestätigen bleibt die Meldung über ein Neuladen stehen. Vitest 46/46. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |