Commit Graph
100 Commits
Author SHA1 Message Date
mhoennigandClaude Fable 5 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>
2026-08-23 10:44:03 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 10:31:04 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 10:19:17 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 10:14:08 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 10:04:16 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 09:25:32 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 08:37:44 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 08:19:46 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 08:13:35 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 08:01:56 +02:00
mhoennigandClaude Fable 5 e223734d58 fix: Beschreibung und Kurz-Fakten im Tooltip trennen
Bisher hing alles an derselben ` · `-Aufzählung: Auf die letzte Zeile des
Fließtexts folgte unmittelbar `· #cms · geplant · Alt+Klick: …`. Der Übergang
war nicht zu sehen, obwohl dort die Art der Aussage wechselt — vorn ein Satz,
den jemand geschrieben hat, hinten Metadaten, die das Werkzeug kennt.

Jetzt: Leerzeile plus Trennstrich dazwischen. Der Strich ist aus `─` (U+2500)
gebaut, nicht aus Bindestrichen — das Box-Drawing-Zeichen stößt gapless
aneinander und liest sich als Linie, `---` liest sich als Text; mehr kann ein
`title` nicht, dort gibt es kein Markup.

Zwei Randbedingungen: Der Strich steht nur, wenn beide Teile da sind (ohne
Beschreibung bleibt der Tooltip unverändert), und mit 24 Zeichen ist er
schmaler als die Fakten-Zeile mit dem Sprung-Hinweis — er verbreitert den
Tooltip also nie.

Nicht in den aria-label: Ein Screenreader läse vierundzwanzig Striche einzeln
vor, und die Kommaliste dort ist ohnehin schon gegliedert.

2 neue Tests (199 gesamt), der bestehende Tooltip-Test nachgezogen.
SPEC §9 und D40 (Nachtrag) ebenso.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 07:54:12 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 07:50:34 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 07:41:43 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 07:31:45 +02:00
mhoennigandClaude Fable 5 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>
2026-08-23 07:24:04 +02:00
mhoennig 618149a36d docs: Werkbaum-Plan — 1 Knoten in Produktion
Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4
unterscheidet [x] fertig von [^] in Produktion (D30):

- llms.md, the notation explained to AI agents
2026-08-22 21:07:05 +02:00
mhoennigandClaude Fable 5 fd324d8828 docs: Ticket-Kennungen sind auch alphanumerisch — Muster statt Numerik-Heuristik (D34-Nachtrag)
Taiga schreibt #US-123 für eine User Story, Jira #ABC-123 — die frühere
Heuristik „rein numerisch = Ticket" trägt nicht. Als Ticket-Link erkannt
wird künftig, was dem konfigurierbaren Muster des angebundenen Trackers
entspricht (Festlegung beim Taiga-Spike); die Zeichenmenge der Knoten-ID
deckt solche Kennungen schon ab, am Parser ändert sich nichts. SPEC
§1/§11, llms.md und Plan nachgezogen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:06:42 +02:00
mhoennigandClaude Fable 5 01b836a4d4 fix: llms.md statt llms.txt, Footer-Link zwischen Versionsnummer und Copyright (D43-Nachtrag)
.md ist die zum Inhalt ehrliche Endung (es IST Markdown); Link rückt zur
Werkzeug-Ecke des Footers. Pipelines, SPEC §13, CLAUDE.md und Plan
nachgezogen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:02:36 +02:00
mhoennigandClaude Fable 5 10c5ffb931 feat: llms.txt — die Notation für KI-Agenten, unter der Site-Wurzel (SPEC §13, D43)
Englische Markdown-Kurzfassung der Notation (Syntax + Semantik: Gates,
Status, Ränge, Extraktionsreihenfolge, Abhängigkeits- und
Beschreibungsregeln, Schreib-Faustregeln, vollständiges Beispiel), damit
Agenten Werkbaum lesen und schreiben können. Name und Ort folgen der
llms.txt-Konvention; Quelle in frontend/public/ (Dev-Server und dist/
gratis), Pages-Workflow und deploy-prod.sh kopieren sie mit je einer
Zeile, Footer verlinkt den Dateinamen (keine i18n nötig, Tooltip DE·EN).
Die SPEC bleibt normativ — Hausregel jetzt: SPEC zuerst, dann Code, dann
llms.txt nachziehen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 20:58:52 +02:00
mhoennigandClaude Fable 5 c136a2c3d1 fix: „Original wiederherstellen" immer zeigen, wenn ein mitgeliefertes Dokument aktiv ist
Ganz versteckt war der Eintrag nicht auffindbar (Nutzer: „ich sehe
keinen Reset-Button") — jetzt erscheint er, sobald Example oder Werkbaum
aktiv ist, und ist nur ausgegraut, solange das Dokument dem
Auslieferungsstand entspricht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 20:18:34 +02:00
mhoennig c83e5ab6d6 docs: Werkbaum-Plan — 1 Knoten in Produktion
Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4
unterscheidet [x] fertig von [^] in Produktion (D30):

- Restore a shipped document from the menu
2026-08-22 20:09:16 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 20:04:10 +02:00
mhoennig 7eebc985b1 docs: Werkbaum-Plan — 24 Knoten in Produktion
Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4
unterscheidet [x] fertig von [^] in Produktion (D30):

- Node IDs (#auth) (S)  %% often just the ticket number, see SPEC §11
- Dependencies across the tree, (:#auth,#api)
- They constrain the status, not when work may start
- Effective status: intrinsic, plus what it depends on
- Cycles are legal — they mean "finished together"
- An id with no node behind it is a warning
- XOR, = — exactly one alternative, not at least one
- Node descriptions, shown as a tooltip
- A short text right at the node (S)  %% a " line below the node
- A long block at the end, addressed by its id (S)  %% behind a --- separator
- Cross links for dependencies
- Visually secondary — the tree carries the message
- Highlight what a selected node needs and feeds
- Count the whole dependency closure #closure
- Shared dependencies counted only once
- The cheapest alternative is no longer the local one (M)  %% see D34, D42
- Search exactly, the trees are small enough
- Enumerate the coupled any-of choices
- Cost is the size of the union
- Stay greedy, but say so (XS)  %% built as the named fallback
- Line numbers in the text editor (XS)  %% the warnings name them
- Collapsible subtrees
- Initial state from the text: > collapses, < re-opens
- Fold and unfold in the diagram, independently of the text
2026-08-22 19:50:56 +02:00
mhoennigandClaude Fable 5 4706541517 fix: promote-shipped.sh kennt den umbenannten Plan und die neue Syntax
Der Rename example-werkbaum.werkbaum → werkbaum.werkbaum (82f34b2) hatte
das Skript nicht erreicht — der Deploy brach mit „Plan nicht gefunden"
ab. Dazu die Statusbox-Erkennung um das =-Gate und die Faltmarken >/<
erweitert (SPEC §1), damit auch `- > [x] …`-Zeilen befördert werden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:50:56 +02:00
mhoennigandClaude Fable 5 5911609d2e fix: Abhängigkeits-Punktierung enger (.1/3 statt .1/5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:40:24 +02:00
mhoennigandClaude Fable 5 a8c5e54f11 fix: Abhängigkeits-Pfeilspitzen offen und grau — kein gefülltes Dreieck (D41-Nachtrag)
Das gefüllte Dreieck stach als einziger satter Fleck aus der gepunkteten
grauen Linie heraus. Jetzt ein offener Winkel aus zwei dünnen Strichen
im selben Grau und Gewicht wie die Linie; die Fokus-Hervorhebung bekommt
denselben Winkel in Tinte. Export identisch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:37:58 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 19:34:00 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 19:18:10 +02:00
mhoennigandClaude Fable 5 d2ab30b63d docs: Werkbaum-Plan nutzt die neuen Features selbst (D24: lebende Projektübersicht)
IDs (#scaffold, #docs, #resolve, #closure) und sechs echte
Abhängigkeiten — alles Serverseitige zeigt auf :#scaffold und
demonstriert damit die geteilte Closure; die Tracker-Wahl ist jetzt eine
=-Gruppe (genau einer), 'Update by itself' und das IntelliJ-Kapitel
starten per > eingeklappt, < holt die Zweiter-Parser-Warnung hervor;
eine "-Kurzbeschreibung am Collaboration-Knoten und zwei Langtexte
(#closure, #scaffold) hinter ---. Die in D40-D42 gebauten Knoten stehen
auf [x]. Parst mit 165 Knoten ohne Warnung, Pfad exakt, keine
Status-Diskrepanzen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:13:02 +02:00
mhoennigandClaude Fable 5 a918c75286 feat: Günstigster Pfad auf der Dependency Closure — exakt über gekoppelte Gruppen (SPEC §9, D42)
Jeder nötige Knoten zieht seine :#…-Ziele samt Realisierung in die
nötige Menge; gemeinsam Gebrauchtes zählt über die Vereinigung nur
einmal. Verfahren gewählt und benannt (D34): erschöpfende Suche, aber
nur über die GEKOPPELTEN Gruppen (Teilbaum enthält Abhängigkeiten oder
wird gebraucht) — ungekoppelte wählen lokal wie bisher, ohne
Abhängigkeiten bleibt es bei genau einer Auswertung zum alten Preis.
Lexikografischer Odometer, strikt < : bei Gleichstand die erste
Alternative. Oberhalb des Suchlimits (20 000) gierig mit zeilenloser
Warnung cheapApprox statt stillschweigender Pseudo-Optimalität.
Abhängigkeiten ziehen auch optionale und nicht gewählte Ziele (ohne
deren Vorfahren), verworfene nie. Neue API computeCheapPlan {set,exact};
markCheapest entfällt. 8 neue Tests (tests/closure.test.js).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:10:42 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 19:05:33 +02:00
mhoennigandClaude Fable 5 a42710b38f feat: Knotenbeschreibungen — "-Zeilen und ----Beschreibungsteil (SPEC §1/§9, D40)
Parser: `"`-Zeilen (Leerraum-Regel, nur ohne Zerlegungszeichen) hängen am
vorangehenden Knoten; hinter einem `---`-Trenner eröffnen #id-Zeilen
Blöcke, deren eingerückte Zeilen der Text sind (Leerzeilen = Absätze,
weitere Trenner bedeutungslos). Freitext ohne §1-Extraktion, nur %% fällt
weiter weg. Warnungen: unknownDesc (ID ohne Knoten, Blocktext wird still
geschluckt) und descStray je verwaister Zeile — ein versehentlicher
Trenner meldet die verschluckten Knotenzeilen zeilengenau. Anzeige im
Tooltip (Text zuerst, mehrzeilig) + aria-label, ”-Marke am Knoten
(nicht im Export — eine Marke ohne Ziel wäre Rauschen). i18n in allen
9 Sprachen; SPEC-§11-Abschnitt in §1/§9 überführt; 16 neue Tests
(tests/desc.test.js).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 19:01:02 +02:00
mhoennigandClaude Fable 5 c4abd24ce8 docs: Knotenbeschreibungen entschieden — "-Zeilen und ----Beschreibungsteil (D34-Nachtrag)
Kurzform (Nutzer-Entscheidung): eine neue Zeile mit `"` unter dem Knoten
— eigenes Zeichen statt Einrückung, weil Einrückung Hierarchie bedeutet;
Leerraum-Regel wie bei =, >/<, damit "Zitat"-Labels unberührt bleiben.
Langform (Nutzer-Entscheidung): hinter einem `---`-Trenner nach
YAML-/Frontmatter-Vorbild eröffnen ID-Zeilen (#auth) Blöcke, deren
eingerückte Zeilen der Text sind — kein Schlusszaun, der vergessen werden
könnte; die Wurzelknoten-Regel endet am Trenner, darum braucht dort keine
Zeile ein Zeichen. Fehlertoleranz: verwaiste/uneingerückte Zeilen im
Beschreibungsteil warnen zeilengenau — ein versehentlicher Trenner macht
sich laut bemerkbar. SPEC §11, TASKS, ROADMAP und Plan nachgezogen; damit
ist die letzte offene Schreibweise der fünf D34-Erweiterungen fest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 18:33:23 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 17:15:04 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 16:47:34 +02:00
mhoennigandClaude Fable 5 c778e3e267 docs: Faltmarken entschieden — hinter dem Gate, Export folgt der Faltung (D34-Nachtrag)
Stellung: `- > [x] Backend` — die Marke steht hinter dem Zerlegungszeichen,
damit die Gate-Spalte bündig bleibt (an ihr liest das Auge die Hierarchie
ab); `>` am Zeilenanfang wäre zudem die Markdown-Blockquote-Konvention.
Wurzelknoten ohne Zeichen tragen die Marke am Zeilenanfang.

Grafikexport und Druck folgen der sichtbar eingeklappten Struktur (wie der
Verworfene-Filter, SPEC §9); eingeklappte Knoten werden gekennzeichnet,
damit das Bild nicht stillschweigend Vollständigkeit behauptet — anders als
Cursor-Ring/Kranz/Fokusmarke ist die Faltung eine Aussage über das Bild,
nicht über den Betrachter. Genaue Form der Kennzeichnung beim Bauen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 16:26:40 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 16:25:09 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 16:15:15 +02:00
mhoennigandClaude Fable 5 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>
2026-08-22 16:08:37 +02:00
mhoennigandClaude Fable 5 2754fa40f6 docs: XOR-Zeichen entschieden — = statt x (D34-Nachtrag)
SPEC §11: Zeichen festgelegt (disjunktiv wie `|`, Mischung → mixedGate,
Leerraum-Regel gegen Labels wie =SUMME(…)); offen bleibt nur noch die
Kennzeichnung der Gruppe im Diagramm. D34-Nachtrag begründet die Wahl und
hält die verworfenen Kandidaten fest (x, ^, |1, /, ⊕, °).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 15:01:34 +02:00
mhoennigandClaude Fable 5 477ef6c812 docs: Schlagworte auf &tag umreserviert, #-Doppelrolle aufgelöst (D34)
Tickets haben auf `#` Vorrang (`#123` als etablierte Kurzschreibweise, oft
zugleich die natürliche Knoten-ID; notfalls Präfix wie `#t123`). Freie
Schlagworte verlassen `#` und gehen auf `&tag` — als niedrig priorisierte
Idee, ausdrücklich ungebaut: Ohne Konsumenten (Filter-Linse, Taiga-Label-Sync)
sind Tags nur Kommentare mit Extra-Syntax; gebaut erst zusammen mit dem ersten.

Zeichenwahl nach den D32-Kriterien: `&` ist auf DE- wie US-Layout eine
Shift-Taste, nie ein Dead-Key; „R&D" und „Drag & Drop" bleiben durch die
Alleinstehend-Regel (wie `!!!`) Labels. Verworfen: `~` (Dead-Key auf Mac-DE,
Zirka-Kollision), `$`, `%`, `:`, `§`, `*`.

Damit ist der offene Punkt aus D34 durch Wegfall statt Trennregel geschlossen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 12:15:22 +02:00
mhoennigandClaude Opus 5 90230c4dd3 docs: Geplante Erweiterungen (IDs, Abhängigkeiten, XOR, Falten, Beschreibungen)
Die fünf geplanten Notations-Erweiterungen für vollständiges Lean-Pathfinding
sind in die Doku eingearbeitet — Schreibweisen samt offener Punkte in SPEC §11,
Begründung und Folgen in D34, Aufgaben in Phase 4, und als Knoten im
mitgelieferten Werkbaum-Plan.

Drei Kollisionen mit Bestehendem sind dabei benannt statt stillschweigend
mitentschieden: `#` trägt jetzt drei Bedeutungen (Ticket, Schlagwort, Knoten-ID),
`x` für XOR liest sich neben `[x]` schlecht, und kurze Knotenbeschreibungen
können keine eingerückte Folgezeile sein, weil Einrückung Hierarchie bedeutet.

Die weitestreichende Folge betrifft D18: Mit Dependency Closure — gemeinsame
Abhängigkeiten nur einmal gezählt — ist die günstigste Alternative nicht mehr
lokal entscheidbar; die gierige Wahl je Gruppe ist nicht länger optimal.

LEAN-PATHFINDING.md schlug für Abhängigkeiten noch `→ Feature` vor (Verweis auf
den Titel, rote Linien); das ist mit `:#id` überholt und korrigiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 10:04:50 +02:00
mhoennig a698dc31bd docs: Add market analysis and features for Lean Pathfinding to documentation 2026-08-16 12:15:13 +02:00
mhoennig 82f34b2c09 rename: example-werkbaum.werkbaum zu werkbaum.werkbaum. 2026-08-16 12:05:01 +02:00
mhoennigandClaude 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>
2026-08-05 13:57:47 +02:00
mhoennig 400bdbdd35 docs: IntelliJ-Plugin als Idee in den Plan
Nutzer-Idee: ein IntelliJ-IDEA-Plugin — Rendern über Mermaid als erster Schritt,
ein vollwertiger Editor als das eigentlich Reizvolle.

Zwei Dinge sind beim Aufschreiben aufgefallen und stehen deshalb im Plan:

- Der billige Weg über Mermaid trägt nur, wenn Werkbaum in Mermaid SELBST
  landet. Die IDE bringt ihr eigenes Mermaid mit, eine selbstgehostete
  Diagrammart lädt sie nicht — das steht als [-] unter dem Mermaid-Zweig, damit
  niemand später darauf baut. Damit hängt der IntelliJ-Weg an der teureren der
  beiden Mermaid-Alternativen (Pull Request statt eigener Seite).
- Ein eigener Lexer/Parser im Plugin wäre GENAU die zweite Grammatik, vor der
  D14 warnt. Der Knoten trägt deshalb [!] und zerlegt sich in die beiden Auswege:
  gemeinsame Fixtures oder den einen JS-Parser in der IDE laufen lassen.

Der Editor selbst ist ein `+`-Knoten: Das Anzeigen ist der Kern, der
vollwertige Editor die Zugabe. Genau die Unterscheidung, für die D29 das Zeichen
eingeführt hat — und dadurch fällt er aus dem günstigsten Pfad heraus, statt ihn
zu verteuern.

Geprüft: 139 Knoten, keine Warnungen, keine Geister-Knoten (alle M+ sind
zerlegt).
2026-07-30 17:10:17 +02:00
mhoennig f61c043f92 docs: Korrektur — Websockets gehen bei Hostsharing sehr wohl
Der vorige Commit behauptete, der Websocket-Weg sei bei Hostsharing die offene
Frage. Das war ein Fehlschluss: Ich hatte den dokumentierten NODE-Weg
recherchiert (Apache + mod_passenger, das kann tatsächlich keine Websockets) und
daraus auf die Plattform geschlossen. Für einen Dienst wie Etherpad nimmt man
aber gar nicht Passenger, sondern „eigener Daemon + Reverse Proxy" — und genau
das ist dokumentiert.

Belege: Hostsharing hat eine eigene Feature-Seite „Reverse Proxy für
WebSockets", und das Wiki zeigt es an Mattermost vor — .htaccess in htdocs-ssl,
`RewriteCond %{HTTP:UPGRADE} ^WebSocket$` und `RewriteRule .*
ws://127.0.0.1:<port>%{REQUEST_URI} [proxy]`, dazu ein von Hostsharing
zugewiesener Daemon-Port und eine systemd-User-Unit für den Neustart. Matrix
Synapse steht ebenfalls im Wiki. Es hat also längst jemand zum Laufen gebracht.

Der Plan sagt das jetzt richtig: aus dem [!]-Knoten mit Ausweich-Alternativen
werden zwei gewöhnliche Aufgaben (Reverse Proxy, systemd-Unit). Übrig bleibt als
Risiko nur, was wirklich eines ist — das dauerhafte Patchen eines öffentlich
erreichbaren Dienstes.

Geprüft: 120 Knoten, keine Warnungen.
2026-07-30 16:45:24 +02:00
mhoennig 6b35cf4277 docs: Recherche Hostsharing — eigenes Etherpad ist machbar, aber ohne Websockets
Frage: Lässt sich Etherpad in einem Managed Webspace auf einem eigenen Managed
Server betreiben? Recherchiert, mit einem Befund, der die Sache anders kippt als
erwartet.

Machbar, ja:
- Hostsharing erlaubt ausdrücklich eigene Serverdienste (Daemons) im Managed
  Webspace gegen ein RAM-Kontingent, und Installationen im Userland —
  Programmiersprachen, Webserver, Datenbanken.
- Node.js wird ausdrücklich unterstützt.

Der Haken sitzt beim Transport: Der dokumentierte Node-Weg läuft über Apache
mit mod_passenger, und Passenger unterstützt Websockets auf Apache NICHT (nur
Nginx und Passenger Standalone, sagt Phusion selbst). Socket.io fällt dann auf
Long Polling zurück. Root gibt es auf einem Managed Server nicht; ob die
Apache-Vhost-Konfiguration ein Upgrade zu einem eigenen Port durchreicht, ist
die offene Frage — deshalb steht der Knoten auf [!].

Entscheidend ist aber, dass unsere Anforderung davon gar nicht abhängt: Die
2 Sekunden brauchen keine Websockets, sondern die Hoheit über die
Konfiguration — Export-Rate-Limit oder ein postMessage-Plugin. Beides ist auf
einer eigenen Instanz reine Einstellungssache, unabhängig vom Transport. Ein
Etherpad, das intern nur pollt, liefert uns trotzdem alles, was wir wollten.

Geprüft: 121 Knoten, keine Warnungen.
2026-07-30 16:40:26 +02:00
mhoennig f830b4450a docs: rephrase node description for collaboration in example plan 2026-07-30 15:36:53 +02:00
mhoennig f3dead402c docs: eigene Etherpad-Instanz als Alternative in den Plan
Nutzer-Idee: ein eigenes Etherpad unter javagil.de betreiben, statt den Admin
der Genossenschafts-Instanz um Konfigurationsänderungen zu bitten.

Die Alternative beherrscht mehrere der übrigen, deshalb steht sie zuoberst in
der Gruppe: Auf einer eigenen Instanz sind Rate Limit, `cookie.sameSite`,
Plugins und socket.io-CORS allesamt selbst gesetzt. Schon das Anheben des
Export-Limits — eine Zeile Konfiguration — würde die 2-Sekunden-Anforderung
erfüllen; ein Plugin wäre der elegantere Weg. `javagil.de` steht wie
`hostsharing.net` nicht auf der Public Suffix List, `pad.javagil.de` und
`werkbaum.javagil.de` wären also same-site, womit der Cookie-Ärger im
eingebetteten Rahmen ohnehin entfällt.

Die Kosten stehen als Kinder darunter statt im Kommentar, weil sie echte
Arbeit sind: Node ≥ 18.18.2 und eine richtige Datenbank (Etherpads
Flatfile-Speicher ist ausdrücklich nicht für Produktion), ein Proxy, der das
Websocket-Upgrade durchreicht, TLS. Das Patchen eines öffentlich erreichbaren
Dienstes bekommt `[!]`: Der Aufwand ist unklar UND unbefristet, und das ist bei
dieser Alternative der eigentliche Preis — nicht die Installation.

Geprüft: 118 Knoten, keine Warnungen.
2026-07-30 15:29:01 +02:00
mhoennig f3d3cc3afb docs: Recherche zur automatischen Aktualisierung — Alternativen in den Plan
Wunsch: Ansicht aktualisiert sich selbst, höchstens ~2 s Verzögerung, möglichst
über Etherpads Websockets. Recherchiert und gemessen, nicht geschätzt.

Kernbeobachtung, die alles erklärt: Der einzige Endpunkt, den wir fremdstämmig
lesen dürfen, ist genau der gedrosselte. `/export/txt` sendet
`Access-Control-Allow-Origin: *` und ist auf 10 Abrufe je 90 s und IP begrenzt
(Etherpad-Voreinstellung, in settings.json.template bestätigt); die Pad-Seite
`/p/<pad>` enthält den vollen Text in `clientVars`, sendet aber KEINEN
CORS-Header — der naheliegende Umweg ist also versperrt.

Der vorgeschlagene Websocket-Weg ebenfalls, und das ist gemessen: Ein
`wss://…/socket.io/?EIO=4&transport=websocket` scheitert mit Code 1006 ohne
`open`, während ein fremder Echo-Server aus derselben Seite sauber öffnet — die
Umgebung kann WebSockets, dieser Server nimmt uns nur nicht. Das eingebettete
Pad verbindet sich dagegen problemlos; der Unterschied ist der Origin. Damit
scheitert der Weg nicht an unserer Bereitschaft, eine Abhängigkeit aufzunehmen,
sondern an der Gegenseite — die Easysync-Frage stellt sich gar nicht mehr.

Fund der Suche: Etherpad-Plugins reden per postMessage mit der einbettenden
Seite. `ep_iframeinsert` schickt periodisch den ganzen Pad-Text ans
Elternfenster (und nimmt insert-Befehle an, könnte also sogar Schreiben
erlauben). Das ist der einzige Weg, der live ist, kein Polling braucht, das
Rate Limit nicht berührt und uns nur einen message-Zuhörer kostet — keine neue
Laufzeit-Abhängigkeit. Preis: Installation auf der Instanz.

Damit hängt alles an einer Frage: Kommt man an die Konfiguration der
Pad-Instanz? Alle wirksamen Wege sind Server-Änderungen. Ohne Zugriff bleibt
Polling im Budget — höchstens einmal je 9 s, geteilt über alle Betrachter
hinter derselben IP.

Im Plan steht das als any-of-Gruppe unter „Update by itself"; die beiden
gemessenen Sackgassen als [-] mit dem Messergebnis im Kommentar, damit niemand
sie erneut aufmacht. Der günstigste Pfad wählt dort von selbst die
Rate-Limit-Anhebung. Geprüft: 107 Knoten, keine Warnungen.
2026-07-30 14:36:11 +02:00
mhoennig c4f83a371b docs: README — Beispiel-Links auf die stabile Instanz, Vorbehalt entfällt
Beide READMEs behaupteten noch, die stabile Instanz könne `?sourceUrl=` erst
„ab ihrem nächsten Produktions-Deploy". Der ist gelaufen (1.1.2), also ist der
Satz falsch.

Statt ihn umzuformulieren fällt er weg — er war ohnehin nur die Entschuldigung
dafür, dass die sechs Beispiel-Links je README auf den latest build zeigten,
weil `?sourceUrl=` auf prod fehlte. Jetzt zeigen sie auf werkbaum.javagil.de,
also dorthin, wohin der Kopf des README ohnehin als Erstes weist. Verloren geht
dabei nichts: Dass es die Pages-Instanz als jeweils neuesten Build gibt, steht
unverändert in Zeile 9.

Stichprobe: einer der umgestellten Links liefert HTTP 200, und die Rohdatei
sendet `access-control-allow-origin: *` samt `text/plain` — der Link
funktioniert also nicht nur formal.
2026-07-30 14:14:34 +02:00
mhoennig fdde15cef3 docs: Werkbaum-Plan — 9 Knoten in Produktion
Beim Deploy der stabilen Instanz gehen diese Knoten live; SPEC §4
unterscheidet [x] fertig von [^] in Produktion (D30):

- Optional nodes — neither required nor an alternative
- Show what is new since your last visit
- Watch a shared Etherpad — it merges, we render
- Load the pad's plain-text export
- Reload on demand (XS)  %% a timer only trips Etherpad's rate limit
- Embed the pad, keep the text mirror beside it
- Three views: pad and text, pad only, text only
- Splitter between pad and mirror
- A shared pointer: !!! focuses a node for everyone
2026-07-30 14:06:40 +02:00
mhoennig 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.
2026-07-30 13:51:36 +02:00
mhoennig 1a82771239 VERSION 1.1 — die Notation ist zweimal gewachsen
Bewusster Bump-Commit nach D16. Anlass ist nicht die Zahl der Commits (98 seit
1.0), sondern dass die Sprache selbst zugelegt hat:

- `+` für optionale Knoten (D29) — Zugaben sind weder Pflicht noch Alternative,
  und der günstigste Pfad rechnete sie vorher fälschlich mit.
- `!!!` als Fokusmarke (D32) — ein geteilter Zeigefinger im Text.

Dazu der ganze Weg zur Zusammenarbeit ohne eigenes Backend: `?etherpad=`,
eingebettetes Pad mit drei Ansichten (D31).

Die Micro-Stelle beginnt damit wieder bei 0 (D16).
2026-07-30 13:46:49 +02:00
mhoennig 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.
2026-07-30 13:38:19 +02:00
mhoennig 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.
2026-07-30 13:30:50 +02:00
mhoennig 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.
2026-07-30 13:14:40 +02:00
mhoennig 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.
2026-07-30 12:59:14 +02:00
mhoennig 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 `[^]`.
2026-07-30 12:04:22 +02:00
mhoennigandClaude Opus 4.8 cfbbfab54b deploy: Beförderung gegen Ausweitung sichern (Legende, fremde Beispiele)
Der Umfang war schon richtig — genau eine Datei, kein Glob —, aber als bloße
Zusage zu schwach. `[x]` steht im Repo an Stellen, wo eine Beförderung falsch
bis unsinnig wäre, und der Schaden wäre still:

- Die Legende („Agenda") zeigt `[x] fertig` als ANSCHAUUNGSMATERIAL für die
  Notation (index.html, chip('fertig','[x]') in app.js). Daraus würde
  „[^] fertig" — Unsinn, den beim Durchsehen eines Diffs niemand bemerkt.
- Das mitgelieferte „Example"-Dokument (INITIAL in app.js) und die übrigen
  docs/examples/*.werkbaum sind erfunden.
- SPEC §10 (kanonisches Beispiel, zugleich Test-Fixture) und die Checkboxen in
  docs/TASKS.md.

Deshalb jetzt eine Laufzeitsicherung statt einer Absichtserklärung: Der Lauf
vergleicht `git status` vor und nach dem Schreiben und bricht ab, sobald mehr
als die Plandatei NEU geändert ist — Plandatei zurückgesetzt, nichts committet.
Der Vergleich ist gegen den Vorher-Zustand gebildet, damit anderweitig
schmutzige Dateien im Arbeitsbaum keinen Fehlalarm auslösen. Dazu ein
UMFANG-Absatz im Skriptkopf, der die drei Fallen benennt und ausdrücklich sagt:
nicht auf ein Muster erweitern.

Verifiziert im Wegwerf-Worktree: Normalfall befördert die zwei Knoten und
committet; ein absichtlich auf example-plan-0.werkbaum ausgeweiteter sed bricht
mit Exit 1 ab, listet beide betroffenen Dateien, setzt die Plandatei zurück und
committet nichts; danach läuft der Normalfall unverändert durch.

(Beim ersten Testlauf griff der Wächter scheinbar nicht — Ursache war der Test,
nicht das Skript: `git reset --hard` hatte die eingespielte Skriptfassung durch
die committete, noch ungesicherte ersetzt.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 12:42:54 +02:00
mhoennigandClaude Opus 4.8 6836352e84 deploy: „fertig" wird erst beim Deploy „in Produktion" (D30)
Der mitgelieferte Werkbaum-Plan behauptete [^] für Funktionen, die nur auf der
automatisch deployten Pages-Instanz lagen, nicht auf werkbaum.javagil.de
(manueller Deploy, D16). Ausgerechnet das Dokument, das den Stand beschreiben
soll, war ungenau — und „Was ist neu?" (D28) meldete Dinge als live, die es
dort nicht waren.

Die Unterscheidung gibt es längst: SPEC §4 trennt [x] fertig von [^] in
Produktion. Der Plan hat sie für sich selbst nie benutzt. Konvention ab jetzt:
beim Mergen [x], der Deploy befördert — nur er weiß, wann die Aussage wahr wird.

- scripts/promote-shipped.sh schreibt Statusboxen am Zeilenanfang von [x] auf
  [^] und hält das als eigenen Commit fest (-n zeigt nur, -y ohne Rückfrage).
  Bricht ab, wenn die Plandatei uncommittete Änderungen hat; committet nur
  diesen einen Pfad; pusht nicht.
- deploy-prod.sh ruft es als Schritt 0 auf (--no-promote schaltet es ab) und
  warnt, wenn HEAD noch nicht auf origin liegt — der Footer-Versionslink zeigt
  sonst auf einen Commit, den GitHub nicht kennt.

Warum ein Commit und kein Rewrite beim Bauen: Ein Rewrite macht GENAU EINE
Installation ehrlich; Pages untertriebe dauerhaft und die Neu-Anzeige wäre dort
für immer stumm. Der Commit wird von beiden Pipelines gesehen (Pages beim Push,
prod beim rsync), das Artefakt bleibt inhaltsgleich mit dem Repo — die
vorhandenen sed-Regeln (D16) fassen nur Pfade und Version an, Infrastruktur,
kein Rewrite dessen, was das Dokument aussagt. Außerdem Präzedenzfall D16:
VERSION per bewusstem Bump-Commit, „vollständig aus dem Repo reproduzierbar".

Einmalige Nachholung, exakt statt geschätzt: Der Footer der stabilen Instanz
verlinkt den deployten Commit (4061362); alles danach ist dort nicht drin. Es
sind GENAU ZWEI Knoten — „Optional nodes" (D29) und „Show what is new since
your last visit" (D28) —, nicht das Dutzend, das ich vorher grob geschätzt
hatte. Beide stehen jetzt auf [x] und leuchten beim nächsten Prod-Deploy als
neu auf. Eine Demotion [^]→[x] löst kein Falschleuchten aus: freshProdSet
meldet nur Knoten, die JETZT [^] sind (test-abgedeckt).

Umfang: nur example-werkbaum.werkbaum. Die übrigen Beispieldateien sind
erfunden und sagen nichts über ein Deployment aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 12:38:21 +02:00
mhoennigandClaude Opus 4.8 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>
2026-07-27 21:25:22 +02:00
mhoennigandClaude Opus 4.8 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>
2026-07-27 21:11:20 +02:00
mhoennigandClaude Opus 4.8 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>
2026-07-27 20:58:52 +02:00
mhoennigandClaude Opus 4.8 b7637d6393 docs: Werkbaum-Plan — Dokumentenverwaltung und zwei neue Funktionen auf [^]
Der Plan hinkte dem Stand hinterher: „Several documents in one editor" stand
noch auf [x], obwohl es samt Kindern längst deployt ist, und zwei seither
ausgelieferte Funktionen fehlten ganz.

- Several documents in one editor + Switcher + ?sourceUrl= von [x] auf [^].
- Neu aufgenommen, beide live: „Jump between diagram and text" (D25) und
  „Show what is new since your last visit" (D28).

Damit hat „Was ist neu?" zugleich wieder etwas zu zeigen — Unit tests war
bestätigt und damit verbraucht.

Verifiziert gegen das gebaute dist-Artefakt mit nachgestelltem Leser-Zustand
(Basis und Dokumenttext = Vorstand 744bb0b): Der Text wird nachgezogen, der
Knopf zeigt 5, und genau die fünf erwarteten Knoten leuchten — darunter zwei
waagerechte Nachbarn, deren Kränze sich dank z-index nicht mehr gegenseitig
abschneiden. Plan parst ohne Warnung (96 Knoten). Vitest 46/46.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 20:28:11 +02:00
mhoennigandClaude Opus 4.8 744bb0bcf3 frontend: Strahlenkranz robust machen (Stapelordnung + Pfad-Inversion)
Gemeldet von der deployten Seite: „nur die linke Hälfte gelb, kein Rahmen
drum herum". Zwei Ursachen gefunden — beide betreffen den Kranz, weil er
AUSSERHALB der Knotenbox liegt und damit fremden Malvorgängen ausgeliefert ist:

- Fehlende Stapelordnung: Jedes später kommende Geschwister malt seinen
  undurchsichtigen Hintergrund über den Schein und schneidet ihn einseitig ab —
  übrig bleibt eine gelbe Kante, die wie ein halber Hintergrund aussieht statt
  wie ein Rahmen. Die Geschwister-Abstände (gemessen 24–25 px) liegen nur knapp
  über der Reichweite des Scheins (Blur 16 + Spread 5 = 21 px); bei Zoom,
  längeren Labels oder engeren Layouts reicht das nicht. Jetzt `z-index:2`.
- Pfad-Inversion dimmte die Meldung weg: `.cheap-on .node:not(.cheap)` setzt
  `opacity:.32; filter:saturate(.4)` und traf damit auch den gelben Kranz
  (nachgemessen: Deckkraft 0,32 ⇒ praktisch unsichtbar). Gerade bei einer nicht
  gewählten Alternative ist „das ist jetzt live" aber die interessantere
  Nachricht. Neue Regel hebt beides für `.fresh` auf.

Verifiziert gegen das gebaute dist-Artefakt (dasselbe, das deployt wird):
z-index 2 gesetzt; ein nicht-günstiger Knoten mit .fresh hat wieder
opacity 1 / filter none bei unverändertem Kranz; im kompakten Modus liegt
kein Geschwister mehr über dem Schein. Vitest 46/46.

Nicht reproduziert: die exakte Beschreibung (gelb nur hinter der linken
Texthälfte) trat bei mir weder im Dev-Server noch im dist-Artefakt auf, in
keinem der drei Modi. Die beiden behobenen Defekte erklären sie plausibel,
bewiesen ist das nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 19:51:11 +02:00
mhoennigandClaude Opus 4.8 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>
2026-07-27 15:18:20 +02:00
mhoennigandClaude Opus 4.8 68abdc6625 docs+frontend: Gemeinsam arbeiten in Roadmap und Werkbaum-Plan
- docs/ROADMAP.md: neuer Abschnitt „Gemeinsam an einem Diagramm arbeiten" in
  drei Stufen — Lesen teilen (?sourceUrl=, vorhanden), asynchron über Git
  (heute schon möglich, weil der Plan Text ist: git diff/blame sind lesbar;
  Ausbau = Backend committet jede Änderung), live tippen (Transport ist der
  kleinere Teil, das Zusammenführen gleichzeitiger Änderungen der eigentliche;
  Text-CRDT passt wegen D14 ohne eigene Merge-Logik). Notiert auch, warum Git
  direkt aus dem Browser kein Live-Sync ist (Sync auf Zuruf, CORS-Proxy nötig,
  Konfliktmarker mitten in der Notation) und was vor einer Entscheidung offen
  ist (erste Laufzeit-Abhängigkeit, Commit-Granularität, Rechte).
- docs/examples/example-werkbaum.werkbaum: passender Zweig „Working together
  on one plan" (94 Knoten, parst ohne Warnung).

Dabei aufgefallen: Das mitgelieferte Dokument wurde nur EINMAL angelegt — die
Ergänzung hätte niemanden erreicht, der die Seite schon geöffnet hatte.
`werkbaum-seeded` hält deshalb statt '1' den Fingerabdruck (FNV-1a) der
ausgelieferten Fassung; bei neuer Fassung wird der Text nachgezogen, aber nur
wenn der Nutzer ihn nicht bearbeitet hat. Gelöscht bleibt gelöscht.
`resetToDefaults()` setzt den Merker mit.

Verifiziert im Browser, vier Fälle: alte unveränderte Fassung wird nachgezogen
(98 Zeilen, neuer Zweig da); selbst geänderter Text bleibt unangetastet;
gelöschtes Dokument kehrt nicht zurück; frischer Zustand zeigt die neue
Fassung (89 sichtbare Knoten, keine Warnung). Vitest 37/37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:19:49 +02:00
mhoennigandClaude Opus 4.8 e160015fc7 frontend: Mitgeliefertes Dokument heißt „Werkbaum" (Tippfehler korrigiert)
„Werkbank" war ein Vertipper, gemeint war der Projektname.

- WERKBAUM_NAME auf „Werkbaum" gesetzt.
- `loadDocs()` zieht den Namen bei bereits angelegten Dokumenten nach — aber
  nur, solange er unverändert der ausgelieferte „Werkbank" ist. Eine eigene
  Umbenennung des Nutzers bleibt stehen; ein fremdes Dokument, das zufällig
  „Werkbank" heißt, wird nicht angefasst (die Regel greift über die
  reservierte id, nicht über den Namen).

Verifiziert im Browser: Bestandszustand mit drei Dokumenten (Example,
id=werkbaum/„Werkbank", eigenes d9/„Werkbank") -> nach dem Laden ist nur
id=werkbaum umbenannt, d9 bleibt unberührt; frischer Zustand zeigt „Example"
und „Werkbaum" im Wähler, Wechsel rendert 75 Knoten ohne Warnung.
Vitest 37/37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:10:06 +02:00
mhoennigandClaude Opus 4.8 6fb45ff37a frontend: „Werkbank" — Werkbaums eigener Plan als mitgeliefertes Dokument
Der interessanteste Beispielbaum war nur über einen ?sourceUrl=-Link
erreichbar. Er steht jetzt direkt im Dokument-Wähler.

- Eine Quelle, keine Kopie: `?raw`-Import aus
  docs/examples/example-werkbaum.werkbaum — derselben Datei, die auch der
  ?sourceUrl=-Link lädt. Vite bettet sie in die eine Ausgabedatei ein (D19),
  nichts wird nachgeladen (D20). Die Beispieldatei ist damit Build-Eingabe.
- `seedShippedDocs()` legt das Dokument genau EINMAL an — auch für
  Bestandsnutzer mit vorhandener Dokumentenliste — und merkt sich das in
  `werkbaum-seeded`. Ohne Merker gäbe es nur schlechte Alternativen: entweder
  bekämen bestehende Nutzer es nie, oder ein bewusst gelöschtes Dokument
  kehrte bei jedem Laden zurück. Das aktive Dokument bleibt unverändert.
- Fester, nicht lokalisierter Name wie „Example" (D22). Der Reset setzt jetzt
  beide mitgelieferten Dokumente zurück; eigene bleiben unangetastet.

Verifiziert im Browser: frischer Zustand -> Example + Werkbank, aktiv bleibt
Example; Wechsel auf Werkbank rendert 75 Knoten ohne Warnung; Bestandsnutzer
mit eigener Liste bekommt Werkbank ergänzt, ohne aus seinem Dokument gerissen
zu werden; nach Löschen kehrt es nicht zurück. Build bleibt eine einzelne
Datei (361 kB), Vitest 37/37.

Offen (in D27 notiert): der Text wird nach dem Anlegen nicht mehr
aktualisiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:06:58 +02:00
mhoennigandClaude Opus 4.8 6888762ac8 frontend: Legende scrollbar + Splitter zwischen Editor und Legende
Die Legende wurde abgeschnitten statt zu scrollen — obwohl `.hint` seit jeher
`flex:1 1 auto; min-height:0; overflow:auto` trägt.

Ursache: Chrome legt den Inhalt eines <details> in das Pseudo-Element
`::details-content`. Damit war `.hint` kein Flex-Kind von `.agenda` mehr, die
Begrenzung griff nicht und der Kasten wuchs auf seine Inhaltshöhe (gemessen
585 px in einem 282 px hohen Container) und wurde geclippt. Eine Regel für
`::details-content` wirkte im Test, ist aber Chrome-eigen — Firefox und Safari
kennen das Pseudo-Element nicht.

- Die Legende ist deshalb kein <details> mehr, sondern `div.agenda` +
  `button.agenda-summary`; der Zustand hängt an der Klasse `open`
  (`aria-expanded` am Button, `#legendBtn` und Splitter-Sichtbarkeit über
  `setAgendaOpen()` synchron). `.hint` ist wieder echtes Flex-Kind und scrollt
  in allen Browsern.
- Neuer Splitter `#hintGutter` im selben Idiom wie der große (Pointer-Capture,
  Doppelklick setzt zurück): horizontal nebeneinander -> Breite `--hcol`,
  gestapelt (side/mobil) -> Höhe `--hrow`. Beide getrennt gehalten und in
  `werkbaum-ui` persistiert (samt Auf-/Zu-Zustand), damit ein Moduswechsel die
  jeweils andere Aufteilung nicht zerstört. Grenzen 90 px bis 85 %.
- Die 85-%-Obergrenze steht zusätzlich als max-width/max-height im CSS: die
  gespeicherte Größe ist ein fester px-Wert und würde den Editor sonst auf 0
  drücken, wenn das Panel später schrumpft (großer Splitter, Drehung,
  Bildschirmtastatur). Verifiziert mit --hrow:9999px -> gekappt bei 85 %.
- Nebenbei: der große Splitter war als einziges Bedienelement nicht lokalisiert
  (fest deutsche title/aria-label). Beide teilen sich nun `gutterTooltip`,
  dazu `gutterAria`/`hintGutterAria` — 9 Sprachen.

Verifiziert im Browser: Legende scrollt (247 px Fenster, 607 px Inhalt);
Ziehen in beiden Ausrichtungen inkl. Unter-/Obergrenze und Doppelklick-Reset;
`--hcol` und `--hrow` überleben den Moduswechsel; nach Neuladen sind
Auf-Zustand, Breite und beide Variablen wiederhergestellt; auf Mobil ist die
Summary-Zeile aus und der Kopf-Knopf steuert. Vitest 37/37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:02:56 +02:00
mhoennigandClaude Opus 4.8 4061362922 frontend: Sprung holt keine Bildschirmtastatur mehr herauf
Nach dem Sprung erschien auf Touch-Geräten sofort die virtuelle Tastatur und
nahm den halben Bildschirm — auch bei angeschlossener Bluetooth-Tastatur.

Das ist kein Fehler der App: Eine Webseite erfährt nichts über verbundene
Tastaturen, sie fordert nur Fokus an; alles Weitere entscheidet das OS (Android
hat dafür den Schalter „Bildschirmtastatur anzeigen" unter Physische Tastatur,
vielerorts an). Statt darauf zu verweisen, fordert der Sprung sie erst gar
nicht an:

- `jumpToLine()` setzt vor dem Fokussieren `inputmode="none"` — das unterdrückt
  nur die VIRTUELLE Tastatur, Hardware-Tastaturen tippen unverändert weiter.
- Die Sperre fällt, sobald der Nutzer das Textfeld selbst antippt
  (`pointerdown` läuft vor dem Fokus). Der Sprung ist damit „hinschauen", der
  erste Tipp ins Textfeld „bearbeiten".
- `newDoc()` hebt die Sperre ausdrücklich auf — bei einem neuen, leeren
  Dokument ist Tippen gemeint.

Verifiziert im Browser: Sprung setzt inputmode=none bei Fokus im Textfeld und
korrekter Zeilenmarkierung; Feld bleibt editierbar (nicht readOnly/disabled);
eigenes pointerdown entfernt das Attribut; erneuter Sprung setzt es wieder;
„Neues Dokument" fokussiert ohne Sperre. Vitest 37/37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:51:29 +02:00
mhoennigandClaude Opus 4.8 bbdecce75a frontend: Touch-Langdruck springt beim Loslassen (Fokus blieb sonst nicht)
Auf echten Touch-Geräten flackerte die Markierung im Editor nach dem langen
Druck nur kurz auf und der Fokus fiel sofort wieder heraus.

Ursache: Der Sprung lief im 500-ms-Timer, und `focus()` aus einem
Timer-Callback gilt in mobilen Browsern nicht als Nutzergeste — ein Textfeld
darf sich so nicht selbst fokussieren (sonst könnte jede Seite ungefragt die
Bildschirmtastatur aufziehen). `touchend` ist eine Nutzergeste.

- Der Timer setzt jetzt nur noch die Rückmeldung: der Zielknoten bekommt die
  Klasse `armed` mit demselben Petrol-Ring wie unter dem Alt-Zeiger.
  `jumpToLine()` läuft im touchend-Handler; Wischen bricht weiterhin ab.
- Zweiter Beitrag zum selben Symptom: die native Textauswahl bzw. das
  Link-Callout des Browsers startet bei derselben Druckdauer und riss die
  Auswahl an sich. Drei Schichten dagegen, weil keine allein überall wirkt:
  contextmenu-preventDefault während des Drucks (überall), `user-select:none`
  an .node für grobe Zeiger (@media hover:none and pointer:coarse) und
  `-webkit-touch-callout:none` (nur iOS; Chrome kennt es nicht mehr). Die
  frühere Regel `a.node{-webkit-touch-callout:none}` geht darin auf.

Verifiziert (synthetische TouchEvents): während des Drucks nur `armed` +
Petrol-Ring, Auswahl noch unverändert, Fokus noch nicht im Textfeld; beim
Loslassen Sprung, Fokus TEXTAREA, Klick unterdrückt; Wischen und kurzer Druck
springen nicht und lassen den Klick durch. Ring greift auch unterhalb einer
any-of-Gruppe. Vitest 37/37.

Einschränkung: Weder die Nutzergesten-Regel noch die nativen Langdruck-Gesten
lassen sich synthetisch auslösen — der eigentliche Beweis ist ein Test auf
echter Hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:41:48 +02:00
mhoennigandClaude Opus 4.8 c496850b88 frontend: Alt-Sprung auffindbar machen (Cursor-Rückmeldung + Legenden-Zeile)
Eine Modifier-Geste, die niemand kennt, ist keine Funktion. Zwei Maßnahmen:

- Solange Alt gedrückt ist, trägt #out die Klasse `alt`: alle Knoten zeigen
  `cursor:alias`, der Knoten unter dem Zeiger einen Petrol-Ring. Das wirkt
  auch auf verlinkten Knoten, wo der einfache Klick dem Link gehört und es
  sonst gar keine Rückmeldung gäbe. Der `blur`-Handler ist Pflicht — bei
  Alt+Tab kommt kein keyup mehr an, der Modus bliebe hängen.
- Die aufklappbare Legende endet mit einer abgesetzten Bedienungs-Zeile
  (`hint_jump` in 9 Sprachen, `.hint-op` mit gestricheltem Trenner), damit
  sie ihre Rolle als Notations-Legende behält.

Verworfen (in D25 samt Begründung festgehalten): einfacher Klick springt auf
Knoten ohne URL (~97 % der Knoten der Beispieldateien) — beiläufig auffindbar,
aber dieselbe Geste täte je nach Knoten Verschiedenes; sowie ein einmaliger
Hinweis nach dem ersten Sprung.

Verifiziert im Browser: ohne Alt Cursor auto bzw. pointer (Link), mit Alt
überall `alias`, nach Loslassen zurück, nach blur ohne keyup (Alt+Tab)
zurückgesetzt; Legenden-Zeile sichtbar und abgesetzt. Vitest 37/37.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:26:43 +02:00
mhoennigandClaude Opus 4.8 1f6988b047 frontend: Sprung zwischen Diagramm und Text (beide Richtungen)
Knoten und Notationszeile sind nun verknüpft. Der Parser hängte die
Zeilennummer ohnehin schon an jeden Knoten — sie wandert jetzt als data-line
ins Markup.

- Diagramm -> Text: Alt+Klick markiert die ganze Zeile im Editor (Fokus,
  in Sicht gescrollt); Tastatur Alt+Enter, Touch langer Druck (500 ms,
  Wischen bricht ab). Ein zugeklapptes Editor-Panel wird zuerst geöffnet.
- Text -> Diagramm: der Knoten der Cursor-Zeile bekommt weißen Halo +
  Tinte-Ring und wird beim Zeilenwechsel ins Bild gescrollt.
- Alt statt einfachem Klick, weil ein Knoten mit URL als <a> den ganzen
  Kasten belegt (SPEC §6 bleibt unverändert). Der Handler ruft
  preventDefault() — sonst lädt Alt+Klick auf einen Link das Ziel herunter.
  Neuer Tooltip-Hinweis an jedem Knoten (i18n `jumpHint`, 9 Sprachen).
- Gescrollt wird über einen Spiegel-div, nicht über Zeilenhöhe x n: lange
  Zeilen brechen weich um, die naive Rechnung lag im Test bei 60 Zeilen um
  bis zu 525 px daneben.

Beim Verifizieren gefunden und behoben: `ul.or .node{box-shadow:none}` ist
spezifischer als `.node.current` und schluckte den Ring überall unterhalb
einer any-of-Gruppe — die Regel braucht den #out-Präfix.

Verifiziert im Browser (Alt+Klick auf <a>- und div-Knoten, Klick aufs
Größen-Badge, Klick/Enter ohne Alt unverändert, Alt+Enter, kurzer/langer
Druck und Wischen auf Mobil, Panel-Aufklappen aus dem minimierten Zustand,
Zeile nach Sprung immer im sichtbaren Band, Kommentar-/verworfene Zeilen
heben nichts hervor). Vitest 37/37 (3 neue data-line-Tests, Snapshots
aktualisiert). Dokumentiert als D25 + SPEC §9 + frontend/CLAUDE.md + README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:14:10 +02:00
mhoennigandClaude Opus 4.8 cdf843e2e3 docs: Eigene Endung .werkbaum + fünf Beispieldateien unter docs/examples/
Mit nur einem Beispiel ließ sich das Umschalten zwischen Dokumenten (D22)
nicht zeigen — jeder ?sourceUrl=-Link legt genau ein Dokument an (D23), erst
mehrere nacheinander geöffnete füllen den Wähler.

- Neue Endung `.werkbaum` (D24, SPEC §12): macht Notationstexte zuordenbar
  (spätere Editor-Zuordnung, Syntax-Highlighting, Öffnen/Speichern). Konvention,
  kein Vertrag — `?sourceUrl=` wertet weder Endung noch Content-Type aus, `.txt`
  bleibt gültig. `.gitattributes` führt `*.werkbaum` als Text (LF).
- docs/example-plan.txt -> docs/examples/example-plan-0.werkbaum (Inhalt bis
  auf die Kopfzeile unverändert); README-Links nachgezogen.
- Neu, je einen anderen Aspekt zeigend:
  example-plan-1 (Küche: kein Software-Plan, alternativenlastig — günstigster
  Pfad), example-plan-2 (Konferenz: breiter Fächer, viele Beteiligte),
  example-plan-3 (drei Wurzeln = drei Bäume nebeneinander, dazu ein Knoten
  ohne Größe für das implizite-M-Badge),
  example-werkbaum (Werkbaum selbst: Bestand + mögliche Weiterentwicklung,
  destilliert aus ROADMAP/TASKS/DECISIONS).
- README.md / README.de.md: Tabelle mit den fünf Live-Links samt Hinweis, dass
  sie nacheinander geöffnet den Dokument-Wähler füllen.

Verifiziert: alle fünf parsen headless ohne Warnungen und ohne gemischte Gates
(19/26/43/28/79 Knoten, 3 Wurzeln in Beispiel 3); Vitest 34/34 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 12:43:43 +02:00
mhoennigandClaude Opus 4.8 4d0fb9edd7 docs: Beispiel-Link auf den latest build (stabile Instanz hat sourceUrl noch nicht)
Der Live-Beispiel-Link zeigte auf werkbaum.javagil.de — die stabile Instanz
wird manuell deployt (scripts/deploy-prod.sh) und kennt ?sourceUrl= noch nicht
(verifiziert: 0 Treffer im ausgelieferten HTML). Der Parameter waere dort
stillschweigend ignoriert worden. Der Link zeigt nun auf die automatisch aus
main deployte Pages-Instanz, die das Feature hat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 12:41:28 +02:00
mhoennigandClaude Opus 4.8 ca98f0a055 docs: Beispiel-Plan als ladbare Textdatei + ?sourceUrl=-Abschnitt im README
Für ?sourceUrl= (D23) fehlte bislang eine ladbare Beispieldatei — im Repo lagen
nur Markdown-Dokumente (rendern als Chaos) und VERSION.

- docs/example-plan.txt: englischer Beispielplan, der die Notation vorführt —
  alle acht Status (inkl. [!] mit Warndreieck und einer verworfenen
  Alternative), beide Gates, Größen, Tags, URLs, Kommentare sowie zwei
  Geister-Knoten für die Untergliederungspflicht ab M.
- README.md / README.de.md: neuer Abschnitt „Loading a diagram from a URL" /
  „Diagramm aus einer URL laden" mit klickbarem Live-Beispiel, dem Hinweis auf
  URL-als-Dokumentname und der CORS-Einschränkung.

Verifiziert: Die Datei parst ohne Warnungen (18 Knoten, 7 sichtbare Status,
1 Warndreieck, 2 Geister-Knoten).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 12:40:11 +02:00
mhoennigandClaude Opus 4.8 1557a7ab0d frontend: Notationstext per ?sourceUrl= von einer URL laden
Der Editor kann den Text aus einer externen Textdatei beziehen:
  …/index.html?sourceUrl=https://example.org/plan.txt

- Das Geladene wird als eigenes Dokument (D22) geführt, dessen Name die URL
  ist; die id leitet sich aus der URL ab, derselbe Link aktualisiert also
  dieses Dokument, statt bei jedem Aufruf ein neues anzulegen. Eigene
  Dokumente bleiben unberührt. Die volle URL steht zusätzlich im Tooltip
  (der Name wird in der Titelzeile elidiert).
- Ist der Parameter gesetzt, wird bei jedem Laden neu geholt — die URL ist
  die Quelle der Wahrheit.
- Nur http(s) (relative Angaben werden aufgelöst), credentials:'omit';
  file:/data:/javascript: werden abgewiesen. Fremder Text ist ungefährlich:
  Labels werden escaped, der Parser erkennt als Link nur https?:// (SPEC §1).
- Fehler (CORS, 404, Netz) landen als zeilenlose Warnung `sourceLoad` im
  Warnbereich und nennen CORS ausdrücklich als wahrscheinliche Ursache; der
  bisherige Stand bleibt stehen. i18n in allen 9 Sprachen.
- Nebenbei: checkForUpdates() baut die Cache-Buster-URL nun über
  URL/searchParams — mit vorhandenem Query-String hätte '?t=' ein zweites
  '?' erzeugt.

Verifiziert headless (jsdom gegen den gebauten Bundle: Erfolg, CORS-Fehler,
404, ohne Parameter, verbotenes Schema) und im Browser gegen
raw.githubusercontent.com. Dokumentiert als D23 + SPEC §9 + frontend/CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 11:23:40 +02:00
mhoennigandClaude Opus 4.8 64e4e60b53 frontend: Dokument-Wähler — Beispiel englisch, Inline-Umbenennen, gezielter Reset
Behebt drei Punkte am Mehrfach-Dokument-Feature (D22):

- Beispiel-Dokument heißt jetzt fest „Example" (englisch, nicht mehr
  lokalisiert „Beispiel") und trägt die reservierte id `example`. Alt-Zustände
  (zufällige id, „Beispiel") werden beim Laden adoptiert, sofern der Text noch
  unverändert ist (=== INITIAL) — echte Inhalte nie.
- Umbenennen läuft inline im Menü (Textfeld: Enter bestätigt, Esc bricht ab,
  Blur bestätigt) statt über window.prompt, das in manchen Browser-Kontexten
  unterdrückt ist („Umbenennen funktioniert nicht").
- Reset (Debug-Knopf, non-prod) setzt nur noch das Beispiel-Dokument auf
  INITIAL/„Example" zurück und verwirft Ansichts-/Sprach-/Update-Flags; alle
  anderen Dokumente bleiben erhalten (vorher wurden pauschal alle werkbaum-*-
  Schlüssel gelöscht).

i18n-Keys docDefaultName/docRenamePrompt entfallen (nicht mehr gebraucht).
D22 + frontend/CLAUDE.md nachgezogen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:01:43 +02:00
mhoennigandClaude Opus 4.8 36d79b82c4 frontend: Mehrere umschaltbare Dokumente (Wähler in der Editor-Titelzeile)
Der Editor kann jetzt mehrere Notationstexte halten und zwischen ihnen
umschalten. Der Name des aktiven Dokuments ersetzt die feste Beschriftung
„Struktur (Text)" und öffnet als Dropdown den Wähler mit Wechseln, Neu,
Umbenennen und Löschen.

- Datenmodell (noch kein Backend): Dokumente als [{id,name,text}] in
  localStorage (werkbaum-docs), aktives per id in werkbaum-active; der
  aktive Text bleibt zusätzlich in werkbaum-src gespiegelt.
- Migration verlustfrei: fehlt werkbaum-docs, wird der bestehende Einzeltext
  (oder INITIAL) in ein Dokument gepackt. Reset und Löschen des letzten
  Dokuments säen das Beispiel neu.
- Jedes Dokument ist nur Text + Name (kein Strukturformat, D14) —
  vorwärtskompatibel zum geplanten Backend (D13). Ansichts-State (Modus,
  Zoom, Aufteilung) bleibt global.
- i18n in allen 9 Sprachen; Doc-Namen bleiben Nutzerdaten (nicht übersetzt).
- Dokumentiert als ADR D22 (DECISIONS.md) + frontend/CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:48:31 +02:00
mhoennigandClaude Opus 4.8 5a0a06b185 frontend: Beispiel-PSP durchgehend englisch (Web+Nativ → Web+Native)
Letzter deutscher Rest im INITIAL-Beispiel entfernt, damit das
Standard-Diagramm ein breiteres Publikum anspricht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:08:26 +02:00
mhoennigandClaude Opus 4.8 6aee82949b ci: Build-Node von 20 (EOL) auf 24 (aktueller Active LTS) heben
Node 20 ist seit ~April 2026 End-of-Life. Node 24 "Krypton" ist der aktuelle
Active LTS und passt zur node24-Laufzeit der Actions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:01:39 +02:00
mhoennigandClaude Opus 4.8 c2181e3252 ci: Actions auf node24-Majors heben (Node-20-Deprecation)
GitHub erzwingt node24 für node20-Actions (Deprecation vom 2025-09-19). Die fünf
Pages-Actions auf ihren jeweils ersten node24-Major gehoben:
checkout v4->v5, setup-node v4->v5, configure-pages v5->v6,
upload-pages-artifact v3->v5, deploy-pages v4->v5.

Der Build-`node-version: 20` (für npm ci/test/build) ist davon unberührt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 11:00:12 +02:00
mhoennigandClaude Opus 4.8 17d354059e frontend: Update-Erkennung über vollen Content-Hash statt 300-Zeichen-Fenster
Die Erkennung hashte nur die ersten + letzten 300 Zeichen der Seite. Da der
Editor eine self-contained Datei ist (D19), liegen der gesamte App-Code UND die
Footer-Version als inline-Bundle in der Mitte — außerhalb des abgetasteten
Fensters. Anfang/Ende sind Build-übergreifend identisch, daher wurde eine neue
Version nie erkannt (nur die ETag änderte sich -> "Metadaten geändert, aber
Inhalt gleich").

- hashContent() (cyrb53) über den GESAMTEN Text als alleinige Wahrheit.
- ETag/Last-Modified-Vergleich entfernt: GitHub Pages liefert je Cache-Knoten
  wechselnde ETags für identischen Inhalt und löste damit die irreführende
  Metadaten-Meldung aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:52:16 +02:00
mhoennigandClaude Opus 4.8 29e144eddf frontend: UI-Default-Sprache aus der Browsersprache (Fallback DE)
Ohne gespeicherte werkbaum-lang wählt detectLang() die erste vom Browser
gemeldete Sprache (navigator.languages), für die eine Übersetzung existiert
(Primär-Subtag; de-AT->de, zh-Hans->zh), sonst Deutsch. Greift auch nach dem
Reset. Deutsch bleibt Quellsprache. Als D21 dokumentiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:15:23 +02:00
mhoennigandClaude Opus 4.8 095dbfb63e frontend: High-Risk-Warndreieck + Prod-Gating der Test-Hilfen
- [!]-Knoten trägt jetzt ein Warndreieck (⚠) an der oberen linken Ecke,
  zusätzlich zur orangen Färbung — im Diagramm, im SVG/PNG-Export und
  barrierefrei (aria-hidden, Status steckt im aria-label). Tooltip in allen
  9 Sprachen.
- SPEC §4: [!]/highrisk-Status nachgetragen (war beim ersten Hinzufügen
  übersprungen), inkl. Warndreieck-Kennzeichnung.
- Beispiel-PSP: Backend als High-Risk ([!]) markiert.
- Debug-Panel + Reset-Button nur außerhalb des Prod-Builds (isProdBuild via
  VITE_BUILD_BADGE); im build:prod eliminiert esbuild den Zweig als toten
  Code. Update-Check + Banner bleiben in allen Builds aktiv.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:00:51 +02:00
mhoennigandClaude Haiku 4.5 90b5deb45b fix: Move reset button to header next to fullscreen
Reset-Button gehört in den Header neben dem Fullscreen-Button,
nicht ins Debug-Panel.

Changes:
- Reset-Button (🔄) oben rechts neben Fullscreen-Toggle
- Benutzt gleiche Styling-Klasse (fsbtn) für Konsistenz
- Title: \"App auf Defaults zurücksetzen (für Testing)\"
- Debug-Panel vereinfacht (nur Log-Text, kein Button mehr)

Intuitivere Platzierung für Testing-Feature.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 09:22:22 +02:00
mhoennigandClaude Haiku 4.5 f284e36458 improvement: Add reset button to debug panel
Reset-Button gehört ins Debug-Panel (Testing-Feature):

Changes:
- Debug-Panel erweitert mit flexiblem Layout (flex-column)
- 🔄 Reset-Button hinzugefügt (klickbar, hover-Effekt)
- ✕ Close-Button für Panel (zum Schließen)
- Bessere Styling: halbtransparente Buttons, klare Hover-States

Reset ist nur während Testing nötig — gehört ins Debug-Panel,
nicht in die Haupt-UI. Das 📦-Icon im Footer bleibt für
verfügbare Updates.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:57:30 +02:00
mhoennigandClaude Haiku 4.5 0a8bab24fb refactor: Move update notification icon to footer
Statt Reset-Button im Footer:
- 📦-Symbol rechts neben Version (nur wenn Update verfügbar)
- Klick zeigt die Update-Benachrichtigung wieder an
- Hilft, wenn man den Banner auf \"Später\" verschoben hat

Features:
- Icon nur sichtbar wenn werkbaum-update-available Flag gesetzt
- Hover-Effekt: Icon wird größer (scale 1.2)
- Tooltip erklärt Funktion
- Auto-Update nach Benachrichtigung

Reset-Button entfernt (Nutzer wünscht sich das oben neben Fullscreen,
wenn überhaupt benötigt).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:47:36 +02:00
mhoennigandClaude Haiku 4.5 ceabc34bbe improvement: Make reset button more visible in footer
Reset-Button war zu unauffällig (nur 🔄-Symbol).

Jetzt prominenter:
- Label: \"🔄 Reset\" statt nur 🔄
- Border: dünne Linie um den Button
- Hover-Effekt: Farbe ändert sich zu Petrol (#0F766E)
- Cursor zeigt es ist klickbar

Button sitzt im Footer zwischen Versionsnummer und Links.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:44:35 +02:00
mhoennigandClaude Haiku 4.5 b94f58fcd5 improvement: Clarify update detection logging
Besseres Logging zum Verständnis von Cache-Verhalten:

- \"✓ Alles aktuell\" = Content + Headers gleich
- \"⚙ Metadaten geändert, aber Inhalt gleich\" = Headers unterschiedlich,
  aber HTML-Inhalt identisch (nur Build-Metadaten/ETag geändert)
- \" NEUE VERSION ERKANNT!\" = echter neuer Inhalt entdeckt

Hilft zu verstehen, ob GitHub Pages cacht oder ob wirklich
eine neue Version deployed wurde.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:37:39 +02:00
mhoennigandClaude Haiku 4.5 3bc8f984a6 fix: Simplify update detection – back to GET with timestamp
HEAD-Request hat auf GitHub Pages zu Netzwerkfehlern geführt.
Vereinfachter Ansatz:

- Zurück zu einfachem GET-Request (statt HEAD)
- Cache-Busting via Timestamp: location.href + '?t=' + Date.now()
- ETag/Last-Modified Vergleich + Content-Hash Fallback
- Besseres Error-Logging: unterscheidet "Netzwerk/CORS" von anderen Fehlern

Robuster auf instabilen Netzwerk-Bedingungen (Smartphone).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:32:06 +02:00
mhoennigandClaude Haiku 4.5 e7db4d53d5 feat: Add reset-to-defaults button for testing
Neuer Button im Footer (🔄) zum Zurücksetzen aller Einstellungen:

Features:
- Löscht alle werkbaum-* localStorage-Einträge (Editor, Einstellungen, Update-Flags)
- Confirm-Dialog mit Warnung (Editor-Inhalt geht verloren)
- Reagiert auf alle Bildschirmgrößen
- Nach Confirm: kurze Verzögerung (Logging sichtbar), dann auto-reload

Hilfreich zum Testen:
- Update-Detection testen (Editor-Inhalt neu setzen)
- High-Risk-Status sehen (mit Reset auf Defaults und neuem Beispiel)
- Alle Einstellungen auf Default zurückbringen

Der Button sitzt unauffällig im Footer als 🔄-Symbol.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:25:06 +02:00
mhoennigandClaude Haiku 4.5 d5ef3ff800 fix: Clear update flag before reload when user clicks "Jetzt laden"
Problem: Nach Klick auf "Jetzt laden" wird die Seite neu geladen,
aber das werkbaum-update-available Flag bleibt in localStorage.
Beim Laden der neuen Version wird der alte Check noch vorhanden,
also wird der Banner wieder angezeigt (auch wenn die neue Version
schon da ist).

Lösung: Vor window.location.reload() das Flag löschen.
Damit nach dem Reload die neue Version sauber geladen wird.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:13:14 +02:00
mhoennigandClaude Haiku 4.5 e9519ed7ae fix: Improve update detection with ETag/Last-Modified headers
Robustere Update-Detection für unzuverlässige Netzwerk-Bedingungen:

Neuer Ansatz (statt nur Content-Hash):
1. HEAD-Request prüft ETag + Last-Modified Headers
   - Effizient: Kein großer HTML-Download nötig
   - Verlässlich: Standard HTTP-Cache-Header
2. Wenn Headers gleich → "✓ Alles aktuell" (schnell raus)
3. Nur bei Header-Änderung → volles HTML fetchen + Content-Hash-Vergleich
4. Speichert ETag + Last-Modified für nächste Prüfung

Besseres Logging:
- " NEUE VERSION ERKANNT!" (highlight)
- "⚠ " + Fehlerdetails (was genau fehlgeschlagen ist)
- "✓ Erste Prüfung – Hash gespeichert"
- "✓ Headers änderten sich, aber Content gleich"

Das verhindert:
- Falsche "alles aktuell" wenn Fetch fehlschlägt
- Mehrfaches Fetchen der großen HTML
- "Failed to Fetch" auf unstabilen Verbindungen

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 08:08:24 +02:00
mhoennigandClaude Haiku 4.5 68322b13d9 feat: Add High Risk status [!] for uncertain effort estimation
Neuer Status-Code [!] für Items mit unbekannter Größe/Aufwand:

Parser:
- STATUS_BY_CODE erweitert: '!' -> {key:'highrisk', name:'High Risk – Aufwand unklar'}

Style:
- .node.st-highrisk: Pastellorange (#FFE5CC) mit orange Rahmen (#F97316)
- .chip.st-highrisk: Gleiche Farben für Agenda-Beispiele

i18n (alle 9 Sprachen):
- DE: "High Risk"
- EN: "high risk"
- ES: "alto riesgo"
- FR: "risque élevé"
- PL: "wysokie ryzyko"
- RU: "высокий риск"
- HI: "उच्च जोखिम"
- ZH: "高风险"
- JA: "高リスク"

Tests:
- parser.test.js aktualisiert: 8 Status-Codes (waren 7)

Damit können Items mit ungeklärtem Aufwand im Lean-Pathfinding-Prozess
gekennzeichnet werden – wichtig für Risk-Flagging in Phase 1.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 07:52:02 +02:00
mhoennigandClaude Haiku 4.5 0454f0f50a fix: Add visible debug panel for update detection
Neue Debug-Features für Fehlersuche ohne Dev-Konsole:

- Update-Check alle 15 Sekunden (statt 60, für schnelleres Feedback beim Test)
- Logging in localStorage mit Zeitstempel
- Sichtbares Debug-Panel unten rechts (schwarz, Petrol-Rand)
  * Zeigt die letzten 6 Update-Check-Einträge
  * Klick zum Schließen
  * Hilft ohne Browser Dev-Tools zu sehen, was passiert

Logging zeigt:
-  Neue Version erkannt!
- ✓ Alles aktuell
- ⚠ Fehler: ... (falls Fetch fehlschlägt)

So kann Update-Detection auch auf Smartphone (ohne Dev-Konsole) debugged werden.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 07:40:31 +02:00
mhoennigandClaude Haiku 4.5 ae207789ef docs: Update subtitle short form – concise for mobile
Kurzversionen angepasst für kleine Bildschirme:
- DE: „PSP-Editor mit Lean Pathfinding"
- EN: „WBS editor with Lean Pathfinding"
- ES: „Editor EDT con Lean Pathfinding"
- FR: „Éditeur WBS avec Lean Pathfinding"
- PL: „Edytor WBS z Lean Pathfinding"
- RU: „Редактор СДР с Lean Pathfinding"
- HI: „WBS संपादक Lean Pathfinding के साथ"
- ZH: „WBS 编辑器和 Lean Pathfinding"
- JA: „WBS エディター & Lean Pathfinding"

Testcommit für Update-Detection.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-23 07:28:26 +02:00