Commit Graph
129 Commits
Author SHA1 Message Date
mhoennigandClaude Opus 5 2a58951138 fix(i18n): die Zwei-Fenster-Warnung nennt die zuletzt gespeicherte Änderung, nicht den letzten Tastendruck
Nutzer-Rückfrage: „Was für ein letzter Tastendruck?" — die Formulierung
liest sich, als zähle nur Tippen. Tut es nicht: Umklappen im Diagramm
schreibt die Faltmarke in den Text (D38-Nachtrag 2) und damit in den
Speicher, nimmt also am selben Wettlauf teil. Nachgemessen mit zwei
Fenstern: A klappt zu, im Speicher steht `- > Ast A (M)`, B's Editor und
Diagramm bleiben unverändert — und B's nächster Tastendruck überschreibt
den Speicher, A's Faltung ist weg.

Beide Texte in allen neun Sprachen umformuliert (tabConflictWarn,
docLockText); der Dialogtext nennt das Falten ausdrücklich. SPEC §9 sagt
jetzt dazu, dass das andere Fenster nichts nachzieht und dass ein
Dokumentwechsel dort nicht mitwandert — `werkbaum-active` entscheidet erst
wieder, was ein Neustart öffnet (auch das eine Nutzer-Rückfrage,
nachgemessen). D94-Nachtrag 2 und CHANGELOG nachgezogen.

Benannt, nicht behoben: Wer „trotzdem hier bearbeiten" gewählt hat, behält
die Warnung, auch wenn das andere Fenster das Dokument längst verlassen
hat — dieser Ausweg fordert die Sperre bewusst nicht mehr an.

666 Tests grün; die neuen Texte im laufenden Editor nachgesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:21:29 +02:00
mhoennigandClaude Opus 5 94ee0f20e5 docs(spec): §9 — der Zeilenumbruch trennte live: von seinem Bindestrich
Der Absatz zum Mehr-Fenster-Betrieb brach nach dem Code-Span um, sodass
"`live:`\n-Dokumente" gerendert als „live: -Dokumente" dastand — der
Bindestrich sah aus wie ein Gedankenstrich am Zeilenanfang. Nur neu
umbrochen, kein Wort geändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:08:17 +02:00
mhoennigandClaude Opus 5 caaf7c15e1 feat(editor): Mehr-Fenster-Betrieb gebaut — Ablageschema v3, Tombstones, docsync, Sperre je Dokument (RFC 002, D94)
Kein Fenster schreibt mehr den Schlüssel eines anderen: Text, Meta und
frühere Stände liegen je Dokument unter eigenen Schlüsseln, der Index ist
nur noch Reihenfolge-Hinweis und löscht nie, Löschungen hinterlassen einen
Tombstone (7 Tage). Der Voll-Flush weicht dem Dirty-Flush. Damit sind
Befund 2 und 3 des RFC weg — verschiedene Dokumente in zwei Fenstern
kollidieren gar nicht mehr. docsync.js zieht Liste, Namen, Tombstones und
den Stände-Cache aus dem storage-Ereignis nach; am aktiven Dokument die
drei Fälle umbenannt / anderswo gelöscht (behalten, bis getippt wird) /
fremd geschrieben. Der modale D89-Dialog samt Präsenz-Kanal ist ersatzlos
raus; den einen Verlustfall — dasselbe nicht-`live:`-Dokument in beiden
Fenstern vorn — findet eine Sperre per Web Locks, und das zweite Fenster
bekommt drei Auswege statt „trotzdem fortfahren".

Im Browser mit zwei echten Tabs nachgemessen (RFC §10): Fälle 1–8 grün,
Fall 1 im Vor-v3-Build gegengeprüft und dort nachweislich rot. Fall 9
deckte auf, dass reviveGoneDoc() den Index-Hinweis nicht mitschrieb — ein
durch Tippen wiederbelebtes Dokument hing bis zum nächsten Flush allein an
seinen eigenen Schlüsseln und wäre bei einem Rückbau auf einen Build vor
v3 still verlorengegangen; behoben, Gegenprobe per Mutation gezogen.
Headless 666 Tests grün. Offen bleibt die Handarbeit: PWA neben Tab,
Firefox, Safari, ein Browser ohne Locks-API.

SPEC §9, D94, D89-Nachtrag, RFC (Status, §10, Revisionsgeschichte),
CHANGELOG und Plan-Knoten #ed.docs.windows nachgezogen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:07:20 +02:00
mhoennigandClaude Fable 5 aa6ca85bdf fix(diagram): umgeklappter Knoten behält seine Position im Bild — Scrollstand zieht nach (D38-Nachtrag 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 20:21:03 +02:00
mhoennigandClaude Fable 5 0fe4c07851 feat(diagram): Knoten-Fenster öffnet nur noch auf Klick, nicht mehr bei Hover (D92, SPEC §6/§9)
Der einfache Klick toggelt das Fenster überall — wie der Tipp auf Touch
(D52); der Link eines Knotens ist damit auch am Zeiger der ↗-Knopf im
Fenster, Enter und Strg+Klick öffnen die URL weiterhin direkt. Tastatur-
fokus öffnet nur noch echten (:focus-visible), focusout schließt nicht in
das Fenster hinein, und die Fokus-Rückgabe nach dem Falten ruft das
Fenster ausdrücklich nie.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 16:43:01 +02:00
mhoennigandClaude Opus 5 1a33ebdac8 feat(taiga): Ticket verknüpfen — URL/Ref-Feld mit Betreff-Bestätigung (D91-Nachtrag 11)
Ein Knopf im Knoten-Fenster bindet ein bestehendes Taiga-Ticket an den
Knoten: Die eingefügte Ticket-URL trägt Slug, Typ und Nummer selbst; eine
nackte Nummer wird per Probe Story-dann-Task aufgelöst. Verknüpft wird erst
nach Anzeige des Betreffs; geschrieben werden Ref-Token und ggf.
&taiga.<slug> in einem Undo-Schritt, der geholte Stand füllt den
Ticket-Cache. Eine URL einer fremden Instanz wird benannt statt verknüpft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 12:48:32 +02:00
mhoennigandClaude Opus 5 a1e4a3bbab feat(taiga): Bulk-Abfrage und Abweichungs-Marke — das Diagramm zeigt, wo Ticket und Plan auseinanderlaufen (D91-Nachtrag 10, SPEC §9)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:44:33 +02:00
mhoennigandClaude Fable 5 323e8fe0ba feat(taiga): Status zurückschreiben — zwei Knöpfe, niemand gewinnt von selbst (D91-Nachtrag 8, SPEC §9)
Weicht der Ticket-Status von der Statusbox ab, markiert das Knoten-Fenster
die Abweichung und bietet beide Richtungen ausdrücklich an.

- „nach Taiga schreiben": Spalte des Projekts suchen (Taiga schreibt nach Id,
  die Namen sind je Projekt frei) und mit der zuletzt GELESENEN `version`
  patchen — hat jemand dazwischen geändert, lehnt Taiga ab und der Text steht
  im Fenster, statt dass etwas überschrieben wird.
- „aus Taiga übernehmen": `setStatusBox()` schreibt die Box in die Textzeile,
  undo-fähig wie jede andere Änderung.
- Schreibbar sind nur die fünf abgebildeten Zustände; `[?]`, `[!]`, `[-]` und
  der neutrale Knoten lassen das Ticket unangetastet — mit Begründung im
  Fenster.
- Proxy: zwei Spaltenlisten (`/taiga/{userstory,task}-statuses?slug=`) und
  zwei Schreib-Endpunkte (`PATCH …/{ref}/status?slug=`); die Zielspalte wählt
  der Editor, das Backend parst die Notation nicht (D14).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:18:52 +02:00
mhoennigandClaude Fable 5 4e6f98bbee docs: Status-Rückschreiben festgelegt — zwei Knöpfe statt einer Richtung, nur die fünf abgebildeten Zustände (D91-Nachtrag 7, SPEC §11)
Die offene Frage von #trk.write ist entschieden, gebaut ist nichts: Weicht
der Ticket-Status von der Statusbox ab, wird die Abweichung markiert und mit
zwei ausdrücklichen Aktionen angeboten — nichts geschieht von selbst.
Verworfen sind „der Text gewinnt" (schreibt selbsttätig in ein fremdes,
geteiltes System) und „Taiga gewinnt" (schriebe fortlaufend im Notationstext,
gegen D14); „Zeitstempel entscheidet" ist ohne erfundene Metadaten gar nicht
baubar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:00:20 +02:00
mhoennigandClaude Fable 5 842e89795a feat(taiga): Ticket-Stand im Knoten-Fenster — gelesen, nie geschrieben (D91-Nachtrag 6, SPEC §9)
Wo eine Ref steht und ein `&taiga.<slug>` gilt, zeigt das Knoten-Fenster
Betreff, Status und Zuständigen des Tickets; Taigas Statusname steht neben
der Statusbox der Notation (`In progress → [~]`).

- Proxy: zwei benannte Lese-Endpunkte (`GET /taiga/userstories/{ref}` und
  `…/tasks/{ref}`, je `?slug=`) — das Präfix der Ref trägt den Typ, Taiga
  hat getrennte `by_ref`-Endpunkte. Erst `/projects/by_slug`, dann `by_ref`
  (eine Ref ist nur je Projekt eindeutig); der Slug wird kodiert angehängt.
- Die Abbildung Status → Statusbox liegt im Editor (`mapTaigaStatus`,
  headless): Statuscodes sind Notation, das Backend parst sie nicht (D14).
  Unbekannte Namen bleiben unabgebildet — Raten hieße, dem Knoten eine
  Aussage zu geben, die niemand gemacht hat.
- Geholt wird erst nach 400 ms Verweilen und je Ticket einmal je Sitzung
  (↻ holt neu); ohne Anmeldung gar nicht — der Knopf meldet erst an.
- Nichts wird geschrieben: kein Text, keine Statusbox (das bleibt
  `#trk.write`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 20:41:21 +02:00
mhoennigandClaude Fable 5 a22c320909 feat(editor): Strg+Klick öffnet eine Ticket-Ref in Taiga (D91, SPEC §9)
Im Text (freistehendes #US-123/#T-1234, D67-Ausschlüsse) wie auf dem
Knoten; das Knoten-Fenster trägt an Refs einen Öffnen-Knopf — der Weg
auf Touch. Adresse aus taigaWeb (/info) + &taiga.slug-Vererbung; in
einem :#…-Token gewinnt weiter der Abhängigkeits-Sprung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:24:20 +02:00
mhoennigandClaude Fable 5 83eb6ff77f fix(diagram): Ticket-Refs im Label brechen nicht mehr am Bindestrich um (SPEC §9)
Der Browser bricht eine zu breit geratene Zeile an jedem Bindestrich —
aus #US-123 wurde #US-/123 über zwei Zeilen. Eine tref-Spanne mit
white-space:nowrap hält das Token zusammen; labelLines() (Export) misst
ihre Textknoten unverändert mit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:14:27 +02:00
mhoennigandClaude Fable 5 5a18505571 feat(notation): Schlagworte &tag gebaut — taiga.<slug> benennt das Projekt je Teilbaum, vererbt (SPEC §1, D91)
SPEC zuerst: §1 Schritt 5b samt Block und Referenz-Regex, §11 auf
'umgesetzt' umgestellt; llms.md nachgezogen. Parser extrahiert
alleinstehende &-Token als node.marks (Zitier-Konvention gilt),
taigaSlugs() in model.js vererbt das taiga.*-Schlagwort wie die
@-Tags beim Engpass. Sichtbar im Tooltip und aria-label (a11yMarks,
9 Sprachen). Plan-Labels mit &-Erwähnungen eingeklammert; 12 neue
Tests, Gegenprobe: ohne Alleinstehend-Anker fallen genau die zwei
Zitier-Tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 16:20:03 +02:00
mhoennigandClaude Fable 5 c712e81970 docs: Mehrprojekt-Pläne — &taiga.slug erbt je Teilbaum, erster &tag-Konsument (D91-Nachtrag 3, SPEC §11)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 14:48:03 +02:00
mhoennigandClaude Fable 5 4c98ec6603 docs: Ticket-Ref zusätzlich zur Knoten-ID, Präfixe US-/T- festgelegt (D91-Nachtrag 2, SPEC §11)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 14:37:08 +02:00
mhoennigandClaude Fable 5 e3742feffb feat(diagram): Unassigned-Pille zeigt das Sprach-Kürzel — de N.N., en TBD; Langform im Tooltip (D87-Nachtrag)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 13:52:39 +02:00
mhoennigandClaude Fable 5 f45ca44d3c feat(kopf): Dokumentart grau hinter dem Namens-Chip — Mitgeliefert/Lokal/Geteilt samt Host (D90)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 13:03:53 +02:00
mhoennigandClaude Fable 5 408e23f25b feat(live): vier Netze gegen stillen Verlust — Zwei-Fenster-Modal, Wachhund, Rettungs-Sicherungen, lokale Stände für Server-Dokumente (D89)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 12:51:11 +02:00
mhoennigandClaude Fable 5 22769cf3b6 docs: Nutzen je Knoten festgelegt — (M/9) im Größen-Token, lohnendster Pfad (SPEC §11)
Geplant, nicht gebaut: Ziffer 0-9 im Größen-Token (0 = Vorbedingung),
reine Autoren-Aussage ohne Aggregation; der Pfad-Knopf wird ein
Durchschalter aus -> günstigster -> lohnendster. Plan-Knoten
#not.benefit (XOR-Gruppe aufgelöst) und #ed.path.worth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 11:03:36 +02:00
mhoennigandClaude Fable 5 deec39e82a feat(diagram): Personen-Leiste mit Personen-Linse und Belastungsanzeige (D87)
Je @Person eine Pille mit Anteil an der offenen Pfad-Arbeit (D71-Maß),
dazu ein Eintrag für Unzugewiesenes — die Anteile summieren auf 100 %.
Ein Tipp filtert das Diagramm auf die Knoten der Person: alles andere
faltet, ihre Pakete stehen als ▸ n, die Pillen färben petrol. Die Linse
ist rein Ansicht und Sitzungssache — in den Text wird nichts geschrieben,
in geteilten Dokumenten ginge der persönliche Filter sonst an alle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 10:38:53 +02:00
mhoennigandClaude Fable 5 4be6ecb984 feat: Dokumenten-Menü neu — Brotkrume im App-Kopf, Stand-Knöpfe im Editor-Kopf (D81)
Der Dokument-Wähler steht jetzt als gerahmter Chip "Werkbaum › Name" im
App-Kopf und ist damit auch auf dem Telefon aus beiden Bereichen erreichbar;
sein Name übernimmt dort die Untertitel-Zeile. Das Menü gruppiert nach
Dokumentart (docKind, headless getestet), trägt Umbenennen/Löschen/
Wiederherstellen als Symbole je Zeile und scrollt als Ganzes, statt auf
kleinen Bildschirmen abgeschnitten zu werden. Die Editor-Titelzeile heißt
wieder "Text-Editor" und trägt die Stand-Funktionen des aktiven Dokuments:
Speichern (Strg+S), Stand sichern, frühere Stände, Neu laden (Original/URL/
Datei) und den neuen kurzen Teilen-Knopf (vorher "Auf den Server legen" im
Menü). subtitleShort ist ersatzlos ausgebaut.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 22:21:11 +02:00
mhoennigandClaude Opus 5 fdca934cf0 feat: Adresszeile folgt dem Dokument, Live-Sitzung ebenso (D80)
Wer bei offenem `?live=` umschaltet, behielt die alte Adresse — optisch
falsch, und ein Neuladen holte das falsche Dokument zurück. Der Parameter
gehört jetzt zum aktiven Dokument, für `?live=` wie für `?sourceUrl=`;
`?etherpad=` wird nur noch weggeräumt. Fremde Parameter (`?server=`)
bleiben wörtlich stehen, damit die URL lesbar bleibt.

Dabei gefunden: Die Live-Sitzung lief weiter, während ein anderes Dokument
vorn stand — `setLiveText()` schrieb fremde Änderungen in dessen Text. Die
Sitzung gehört jetzt dem sichtbaren Dokument: Umschalten beendet sie,
Umschalten auf ein Server-Dokument nimmt sie auf. Was noch im Debounce
steckt, geht vorher raus, und `pushLive()` hält seine Sitzung fest statt
anzunehmen, dass sich über ein `await` hinweg nichts ändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 21:19:29 +02:00
mhoennig 627f6feca8 perf(live): Debounce 600 ms, Sync-Versionen nur noch 5 Minuten (D79)
Gemeldet: ~3 s Verzoegerung zwischen zwei Browsern. Zerlegt statt geraten -
von 1,73 s gemessenem Weg A->B entfallen 1,67 s auf die Wartezeit vor dem
Senden. Alles andere sind zusammen ~70 ms.

Zwei Verdaechtige sind freigesprochen: Der Server weckt den wartenden Feed
39 ms nach dem PATCH (isoliert per curl, ohne Browser), und der Apache der
produktiven Instanz haelt den Long-Poll die vollen 25 s durch und schliesst
sauber mit 204 - kein Fenster ohne offenen Feed, kein 5-Sekunden-Fehlerpfad.
Produktiv kommen ~130 ms Rundlauf je Anfrage dazu.

Der Debounce bleibt ein Debounce (kein Takt): Wer durchtippt, erzeugt
weiterhin keine Version. Der Grund fuer die 1,5 s stammte aus der
Rate-Limit-Disziplin des Etherpad-Konzepts - und Etherpad ist ausgebaut (D78).

Die Aufbewahrung zahlt die haeufigeren Pushes: Jede Version speichert den
ganzen Text, und die Frist entscheidet einzig, ob ein zurueckgefallener Client
ein Diff oder den Volltext bekommt. Nutzersichtbar sind die Meilensteine, und
die werden nie verdichtet; zurueckfallen kann nur ein ruhender Feed
(Hintergrund-Tab). Zusammen sinkt die Spitze je aktiv getipptem Dokument von
115 MB auf 24 MB (49-kB-Plan, Dauertippen).

Dabei gefunden: Eine Schreibpause laenger als die Frist war mit einer Stunde
der Ausnahmefall und ist mit fuenf Minuten der Normalfall. Dass die letzte
Sync-Version davor nicht verlorengeht, haengt allein daran, dass
recordHistory() zuerst befoerdert und danach verdichtet - sonst loeschte die
Verdichtung genau den Stand, den die Befoerderung gleich zum Meilenstein
gemacht haette. Die Reihenfolge hat jetzt eine Zusicherung; vertauscht faellt
genau der danach benannte Test.

Werkzeuggrenze notiert: Der Automatisierungs-Browser zeigt seine Flaeche nicht
an, Chrome drosselt Timer verborgener Seiten auf 1 Hz (gemessen: ein blanker
setTimeout(600) feuert nach 999-1053 ms). Ein Sub-Sekunden-Debounce ist dort
grundsaetzlich nicht messbar.

501 Frontend-Tests, 139 Backend-Tests.
2026-08-26 20:53:02 +02:00
mhoennig bfacbe96e5 feat: Etherpad-Anbindung ausgebaut (D78)
D31 lieh sich fuer die Echtzeit-Zusammenarbeit ein Etherpad: Das Pad war die
Schreibflaeche, Werkbaum die Ansicht. Richtig fuer den Zeitpunkt - die schwere
Arbeit war dort getan, und Werkbaum hatte kein Backend. Jetzt hat es eins
(D76), das dasselbe besser und im Editor selbst kann.

D31 bleibt stehen: Was dort gemessen wurde, ist der Grund, warum die Anbindung
nie gut wurde - Export-Drosselung (10 je 90 s), SameSite=Lax beim
Autoren-Cookie, abgelehnter Socket-Upgrade. Uebrig blieb ein Neu-laden-Knopf
und ein schreibgeschuetztes Textfeld.

Ein alter ?etherpad=-Link meldet sich, statt still nichts zu tun: Der Parameter
bleibt erkannt und ergibt die zeilenlose Warnung padGone, die auf ?live= zeigt
(neun Sprachen). Geholt wird nichts - nachgemessen geht keine Anfrage mehr an
den Pad-Host. Vorhandene Pad-Dokumente bleiben liegen und werden dabei besser:
ohne Schreibschutz sind sie bearbeitbar und sammeln fruehere Staende.

Mit ausgebaut, weil sie nur der Anbindung dienten: remote.js samt Tests, der
Ansichts-Waehler und sein Splitter, der Neu-laden-Knopf samt
Drosselungs-Zaehler, die Warnungen sourceTimeout/padRateLimit, elf i18n-
Schluessel x 9 Sprachen - und src.readOnly ganz, denn nur Pad-Dokumente haben
es gesetzt (mit ihm updateSnapBtn und sieben tote Waechter). Auch #srcArea
faellt weg: den Kasten gab es nur, damit Pad-Rahmen und Spiegel sich einen
Bereich teilen. ?sourceUrl= bleibt unangetastet - beide teilten sich einen
Fetch-Pfad, das war die Stelle, an der nichts verrutschen durfte.

Im Plan wird aus dem #col.pad-Zweig (14 Knoten samt der ganzen
"Update by itself"-Gruppe) ein verworfener Knoten mit Begruendung; der
gemeinsame Zeigefinger wandert als #col.point heraus, denn !!! gilt jedem
geteilten Dokument. 196 Knoten, 0 Warnungen.

Nebengewinn: Der README bekommt endlich den Abschnitt zu ?live= - den gab es
nie, die Einstiegs-Beschreibung stand weiter beim Pad.

Nachgemessen im Browser: ?etherpad= zeigt die Warnung ohne Netz-Anfrage,
?sourceUrl= laedt unveraendert und ist beschreibbar, Textfeld und
Zahlenstreifen liegen buendig, Legenden-Splitter teilt wie zuvor (954+10+300),
Falten und Staende funktionieren. 501 Tests, 946 Zeilen weniger.
2026-08-26 20:31:33 +02:00
mhoennigandClaude Opus 5 08965157a1 feat(frontend): gemeinsam am Server-Dokument arbeiten (?live=, Schritt 6)
Der Editor fuehrt ein Dokument des Backends: laden, nach 1,5 s Ruhe das Diff
schicken, ueber einen offenen Abruf fremde Aenderungen einspielen — ohne
Neuladen, mit mitwandernder Schreibmarke. Dazu CORS im Backend; ohne das
blockiert der Browser jeden Aufruf.

Zwei Fehler hat erst der Live-Test gegen das laufende Backend gefunden, beide
an der Naht zwischen Modul und Verdrahtung (D54-Nachtrag 3):

Der Konflikt entstand nie. Mit laufendem Feed zieht die Schattenkopie staendig
nach, die eigene Basis ist also nie veraltet — der Server haette nie 409
geantwortet, und die fremde Zeile waere stillschweigend ueberschrieben worden.
Der Client prueft die Ueberschneidung jetzt selbst gegen den ungesendeten
Text; den kennt der Server nicht.

Die Nummer begann nach jedem Neuladen wieder bei 1, waehrend die Kennung
blieb — der Server hielt die erste echte Aenderung fuer eine Wiederholung und
tat nichts. Beides liegt jetzt im sessionStorage: je Tab, ueberlebt Neuladen.
Je Tab ist zugleich die richtige Aussage, zwei Tabs sind zwei Schreiber.

Nachgemessen im Browser: fremde Aenderung erscheint ohne Neuladen, Getipptes
erreicht den Server, beide Konflikt-Knoepfe tun was sie sagen, und die
Schreibmarke steht nach zwei fremd eingefuegten Zeilen darueber unveraendert
bei Zeile+2, Spalte 8. 518 Frontend-Tests, 135 im Backend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 17:48:17 +02:00
mhoennigandClaude Fable 5 33443016ce feat: Querverbindungen folgen der Faltung + Schalter; Falt-Knopf mit vier Voreinstellungen (D75)
- Abhängigkeits-Kanten enden am nächsten sichtbaren Vorfahren, für Quelle
  wie Ziel; zusammengefallene Kanten entfallen bzw. werden dedupliziert
  (data-sub-ids/data-sub-deps am eingeklappten Vertreter, SPEC §9)
- Umschalter für die Querverbindungen neben dem Günstigster-Pfad-Knopf,
  Voreinstellung an, persistiert; Export und Druck folgen ihm
- Der Falt-Knopf schaltet durch: M und kleiner zu → alles abseits des
  Pfads zu → alles zu → alles offen; Icon/Tooltip zeigen den nächsten
  Schritt, Handfaltung setzt die Position zurück (presetFoldSet, model.js)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 17:19:32 +02:00
mhoennigandClaude Fable 5 35b72fc178 feat: Zuständigen-Engpass — Warnung, wenn eine Person mehr als die Hälfte des Pfads trägt (D71)
Trägt ein @name mehr als die Hälfte der offenen Arbeit des günstigsten
Pfads (Marginalkosten-Maß, D69) und tragen mindestens zwei Personen Last
auf der offenen Front, meldet die zeilenlose Warnung assigneeOverload die
mögliche Engstelle; die Pillen der Person wechseln an offenen Pfad-Knoten
auf die Warnfarbe. Knoten ohne Tags erben für die Rechnung vom nächsten
getaggten Vorfahren, mehrere Tags teilen sich den Beitrag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:29:52 +02:00
mhoennigandClaude Fable 5 0de7f5f24e feat: Die geschätzte Größe bepreist den Rest — Erledigtes fällt aus der Schätzung (D70)
Die D66-Schätzung war status-blind: Ein größenloses Paket mit [x] (L) und
[ ] (S) wurde als L geschätzt, obwohl nur S offen ist. Erledigte Kinder
fallen jetzt heraus, eine erledigte realisierte Alternative stellt ihre
Gruppe fertig, und sind alle benannten Kinder erledigt, gilt XS (der eigene
Rest — M-Rückfall erhöhte den Preis beim Fertigwerden). Angegebene Größen
bleiben unangetastet (D69).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 11:04:26 +02:00
mhoennigandClaude Fable 5 4aaa19becd feat: Die Größe bepreist den ganzen Teilbaum — Kinder zählen nicht obendrauf (D69)
Der Preis eines Knotens ist seine angegebene (oder D66-geschätzte) Größe;
die frühere Rekursion (eigene Größe plus Summe/Minimum der Kinder) zählte
doppelt und bestrafte sorgfältig zerlegte Pakete — eine zerlegte
S-Alternative verlor gegen ein grobes L. Das Vereinigungs-Maß der
Closure-Suche (D42) zählt je Knoten nur noch die Marge über seine nötigen
Teilpakete hinaus. Mitgelieferter Plan wählt identisch (per Vergleich
gemessen); SPEC §9 und llms.md nachgezogen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:45:12 +02:00
mhoennigandClaude Fable 5 59da485af6 feat: Größe ist das letzte alleinstehende (L)-Token der Zeile (D68)
Erkannt nur alleinstehend angesetzt (wie #id und :#…) — damit greifen die
Zitier-Konventionen "(L)" und ((L)) von selbst, und Backend(L) bleibt
Label. Das letzte Token gewinnt statt des ersten: Die Größe steht hinter
dem Titel, frühere Vorkommen sind Text. SPEC §1/§5 und llms.md nachgezogen;
mitgelieferte Beispiele unverändert (Parse-Vergleich alt/neu).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:19:52 +02:00
mhoennigandClaude Fable 5 549c12191e feat: Strg+Klick auf :#id springt zur Zeile der referenzierten ID (D67)
Strg/Cmd+Klick auf eine Abhängigkeits-ID im Textfeld — auch jede ID der
Liste und die Kopf-Form #auth:#db — markiert die Zeile, die die ID vergibt;
Tastatur-Pendant Strg+Enter. Erste Vergabe gewinnt (D36/D39), unbekannte IDs
tun still nichts. Headless als depIdAt/idLine in autocomplete.js, 16 Tests;
Legenden-Zeile hint_jump in allen neun Sprachen erweitert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 08:59:58 +02:00
mhoennigandClaude Fable 5 bb01045488 feat: Fehlende Größe aus den Teilpaketen geschätzt statt pauschal M (D66)
Mindestens die größte Größe der zählenden Kinder, ab drei Kindern dieser
Größe eine Stufe mehr (Deckel XXL); disjunktiv die kleinste Alternative,
rekursiv über größenlose Kinder, Blatt bleibt M. Badge, Tooltip und
aria-label zeigen die geschätzte Größe („mindestens {size} angenommen").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 19:59:13 +02:00
mhoennigandClaude Fable 5 ebc5be407e feat: Label-Umbruch bei 32 statt 40 Zeichen (D64-Nachtrag 3)
Lange Titel zogen das Diagramm zu breit. Ein Parameter in wrapLabel(),
der 32ch-Rückhalt im CSS und SPEC §9 ziehen mit; Balance-Regel und
Erste-Zeile-Verankerung unverändert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 18:36:52 +02:00
mhoennigandClaude Fable 5 52232f2c20 feat: Labels brechen balanciert um, ID-Zeile über dem Titel, Falt-Chip — D64
Lange Knotentitel brechen bei ~40 Zeichen um, gleichmäßig auf die Zeilen
verteilt — vom Renderer (wrapLabel, headless getestet) statt text-wrap:balance,
damit der Kasten auf die längste balancierte Zeile schrumpft. Der
23-px-Abzweig ankert an der ersten Zeile; der Optional-Kreis sitzt fest auf
der Abzweighöhe. Der Grafikexport misst die gerenderten Zeilen (labelLines)
und gibt je Zeile ein <text> aus. Dazu: die eingeblendete ID (D56) steht in
einer eigenen Zeile über dem Titel, und das Falt-Zeichen ist ein gerahmter
19×16-px-Chip mit gedeckelter Höhe. Plan: #ed.render.wrap, #ed.render M→L
(D62-Prüfung schlug korrekt an).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:45:11 +02:00
mhoennigandClaude Fable 5 1debb7d9da feat: ID-Vorschläge beim Tippen von Abhängigkeiten (:#) — D63
Wer :# tippt, bekommt die vergebenen IDs als Liste an der Schreibmarke:
Präfix- vor Teilstring-Treffern, Knotentitel als Kontext, schon gelistete
und die eigene ID ausgenommen. ↑/↓ wählt, Enter/Tab übernimmt (undo-fähig),
Esc schließt; Live-Region für Screenreader. Eingabehilfe wie die Kurzform
(D55) — der Parser sieht nie etwas davon. Regeln headless in autocomplete.js
(20 Tests), app.js verdrahtet Popup, Tasten und Einfügen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:13:18 +02:00
mhoennigandClaude Fable 5 9cb6ec1197 feat: eingeklappter Vorfahr vertritt die Cursor-Zeile im Diagramm (D38-Nachtrag 4)
Liegt der Knoten der Cursor-Zeile in einem eingeklappten Teilbaum, hebt
nodeOfLine() den nächsten sichtbaren Vorfahren hervor — Cursor-Mitlaufen,
Alt+Klick, Alt+Enter und der Mobil-Umschalter erben es über die eine
Auflösungsstelle. Ausgeblendete verworfene Elemente heben weiterhin nichts
hervor (SPEC §9). Zeilen-Map als lineTargets() headless in model.js, 5 neue
Tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 16:20:48 +02:00
mhoennigandClaude Fable 5 5b37c117e1 feat: Größen-Konflikt — Teilpakete müssen zur angegebenen Größe passen (D62)
Größen zählen für diese eine Prüfung als Bereiche (Untergrenzen verdoppeln
sich, XXL nach oben offen); Konflikt erst, wenn selbst die günstigste Lesart
der Kinder die großzügigste des Elternknotens erreicht. Warnung sizeConflict
an der Elternzeile, Badge in --warn, Grund in Tooltip und aria-label. Nichts
wird automatisch korrigiert. Nebenbefund behoben: der Grafikexport zeichnete
das Größen-Badge mit festen Farben — das invertierte implizite M stand seit
jeher gefüllt im Bild. Die acht berechtigten Konflikte im eigenen Plan sind
nachgezogen. 372 Tests, 16 neue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 15:55:16 +02:00
mhoennigandClaude Opus 5 6d58164577 fix: Abhängigkeits-Kanten laufen hinter den Knoten (D41-Nachtrag 2)
Die Basis-Kanten lagen schon richtig; „drüber gemalt" wurde allein die
Hervorhebung des ausgewählten Knotens (`svg.dep-front`, z-index 4). Eine
durchgezogene Linie quer über einen fremden Knoten durchstreicht dessen
Titel — und beim Tippen wechselt die Hervorhebung mit jeder Zeile.

`dep-front` entfällt; hervorgehobene Kanten hängen zuletzt in derselben
hinteren Ebene, liegen damit über den übrigen Kanten und unter jedem
Knoten. Export und Druck waren nie betroffen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:19:35 +02:00
mhoennigandClaude Opus 5 4900eb3428 feat: ID-Kurzform wird beim Doppelpunkt aufgelöst (D55-Nachtrag)
`#.kc` wird jetzt aufgelöst, sobald ein Doppelpunkt die ID abschließt —
beim Tippen des Doppelpunkts ebenso wie beim nächsten Tastendruck in
einer Zeile, die ihn schon trägt. Ohne Doppelpunkt bleibt es beim
Verlassen der Zeile.

Der Cursor allein löst weiterhin nichts aus: Angefasst wird nur die
Zeile, in der auch getippt wurde (D55/D27).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 14:52:29 +02:00
mhoennigandClaude Opus 5 36b78efa53 lean-path: Angefangenes liegt auf dem Pfad (SPEC §9, D61)
Eine Zugabe (`+`) fällt aus dem günstigsten Pfad — außer sie ist
realisiert und noch nicht erledigt (`[~]`/`[/]`). Angefangene Arbeit ist
die offene Front, und seit D46 zeigt der Pfad genau die; bisher führte
der Von-Station-zu-Station-Knopf nie dorthin, wo gearbeitet wird.
Erledigte Zugaben bleiben draußen: dort ist nichts mehr zu tun, und was
darunter offen blieb, ist mit ihnen zusammen entbehrlich (§3).

Dabei die fehlende Hälfte von D46 gefunden: SPEC §9 sagt seit jeher
„eine bereits realisierte Alternative gewinnt", umgesetzt war das aber
nur über die Kosten — und die sind allein bei `[x]`/`[^]` null. Ein
`[~]`-Zweig verlor damit gegen eine unangetastete billigere, obwohl die
XOR-Regel gerade ihn als die realisierte Alternative ausweist.
`chosenPool()` schränkt die Wahlmenge auf die realisierten ein; unter
mehreren entscheiden wieder die Kosten. Nebengewinn: eine entschiedene
Gruppe ist keine freie Variable mehr und verkleinert die Suche (D42).

10 neue Tests; Gegenprobe geführt (jede Regel einzeln herausgenommen
lässt genau die nach ihr benannten Zusicherungen fallen). Der
mitgelieferte Plan ändert sich nicht — er hat keine angefangene Zugabe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 14:11:38 +02:00
mhoennigandClaude Opus 5 3018db9f61 notation: ohne Titel vertritt die Knoten-ID ihn (SPEC §1, D60)
`- #US-123` ergab bisher nichts — leeres Label, Zeile ignoriert, ID nicht
vergeben. Wo die Kennung schon der Name ist (Ticket-Referenzen, §11), war das
verkehrt herum: Wer den Titel nicht danebenschrieb, verlor den ganzen Knoten.
Jetzt wird `#id` das Label, mit Doppelpunkt geschrieben wie ohne.

Das `#` bleibt im Label — es sagt „hier steht die Adresse, weil es keinen
Titel gibt". Der `#`-Umschalter setzt bei so einem Knoten nichts davor
(sonst `#US-123: #US-123`), und Tooltip wie aria-label lassen die ID weg, die
schon im Titel steht.

Verhaltensänderung: Die ID ist damit vergeben — `- #auth` gefolgt von
`- [ ] Echt #auth` gibt jetzt zwei Knoten und eine duplicateId-Warnung.

SPEC §1, llms.md, D60; 6 neue Tests, der alte auf die neue Regel umgeschrieben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:31:39 +02:00
mhoennigandClaude Opus 5 410dacdc6c notation: \ am Zeilenende setzt die Zeile fort (SPEC §1, D59)
Eine Zeile trägt alles auf einmal — im mitgelieferten Plan ist die längste
122 Zeichen lang, und seit das Textfeld nicht mehr umbricht (D49) muss man
dafür schieben. Ein `\` am Zeilenende verteilt sie jetzt auf mehrere
Textzeilen, ohne dass ein neuer Knoten entsteht.

Leerraum davor ist Pflicht (`… \`, nicht `…\`) — bewusst gegen die
Shell-Gewohnheit: Ohne die Regel verschluckt ein Label wie `C:\temp\` stumm
den folgenden Knoten, mit ihr bekommt man nur keine Fortsetzung, was man
sofort sieht. Der laute Fehler ist der bessere.

Umgesetzt als Vor-Durchlauf `logicalLines()`: Die Folgezeile ist Teil der
Zeile, bevor irgendetwas aus ihr gelesen wird — die Extraktionsreihenfolge
aus §1 bleibt unangetastet. Alles gehört zur ersten Zeile: Einrückung,
Warnungs-Zeilennummer, Rückschreiber; der Cursor in der Fortsetzung wählt
ihren Knoten aus, wie bei Beschreibungszeilen.

SPEC §1/§9, llms.md, Legende in neun Sprachen, D59; 20 neue Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:17:26 +02:00
mhoennigandClaude Opus 5 257aab08dd feat(news): übersetzter Hinweis auf die englischen Notizen, optisch zurückgenommen
Wer die Oberfläche auf Japanisch stehen hat, sah einen japanischen Rahmen um
englische Sätze — das kann man für eine fehlende Übersetzung halten. Ein Satz
oben im Popup sagt jetzt, dass es Absicht ist, in allen neun Sprachen; klein
und grau, damit er hinter dem Inhalt zurücksteht, zu dem er gehört. Bei
englischer Oberfläche entfällt er.

SPEC §9 und D58-Nachtrag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:56:16 +02:00
mhoennigandClaude Opus 5 36154f6f68 feat: Neuigkeiten — der Stern wandert in die Kopfzeile und bekommt ein Popup
Der „Was ist neu?"-Knopf stand im Diagramm-Kopf und war verborgen, solange es
nichts gab — er konnte also nur etwas über das offene Dokument sagen, und
meistens sagte er gar nichts. Jetzt steht er permanent in der oberen
Bedienleiste und öffnet ein Popup mit der Chronik der letzten Tage; je Tag
führt ein Link die betroffenen Knoten im Diagramm vor (dieselbe gelbe
Kranz-Ansicht wie D28, nur mit einer anderen Frage).

Zwei Quellen, jede in ihrer Rolle: docs/CHANGELOG.md sagt, WAS geschehen ist
(englisch — das Popup ist Oberfläche in neun Sprachen, die Commit-Betreffs
sind deutsch); die git-Historie des mitgelieferten Plans sagt, WELCHE Knoten
sich bewegt haben. Beides wird zur Bauzeit eingelesen (Vite-Plugin), zur
Laufzeit lädt Werkbaum weiterhin nichts nach.

Bernstein heißt ungesehen, Petrol heißt „wird gerade vorgeführt". Der
Besuchsvergleich (D28) steht als abgesetzter Abschnitt zuoberst im Popup und
trägt den „gesehen"-Knopf, den vorher der Knopf selbst war.

SPEC §9 (Neuigkeiten) und D58; 20 neue Tests, davon einer auf der
ausgelieferten CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:50:13 +02:00
mhoennigandClaude Opus 5 71bfb62b35 feat(editor): Warndreieck als Zeiger über fehlerhaften Zeilennummern
Das `?` von `cursor:help` sagte nur „hier gibt es etwas zu lesen". Über der
orange gefärbten Zahl einer Warn-Zeile steht jetzt dasselbe ⚠, das im
Warnungsbereich vor jeder Meldung steht — der Zeiger sagt dieselbe Sache wie
das Ziel, auf das er zeigt.

Der Pfeil bleibt daneben stehen (sonst zeigte der Zeiger auf nichts), Spitze
auf dem Brennpunkt; als data:-URI im Stylesheet, `help` als Rückfall.

SPEC §9 (Zeilennummern) und D33-Nachtrag 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:20:08 +02:00
mhoennigandClaude Opus 5 3fbc772d81 feat(diagram): das Knoten-Fenster löst den nativen Tooltip überall ab
Das Fenster aus D52 war für Touch gebaut — damit standen zwei Darstellungen
desselben Inhalts nebeneinander, und ausgerechnet die schlechtere bekam der
Zeiger. Jetzt zeigt es überall: am Zeiger nach 350 ms beim Überfahren, bei
Tastaturfokus sofort, auf Touch unverändert beim Tipp.

Was ein `title` prinzipiell nicht kann:
- keine Absätze (die Beschreibungen im Plan brechen bei ~76 Zeichen, das sah
  ausgefranst aus),
- keine Linie (der Trennstrich war aus 24 `─` GEMALT, D40-Nachtrag),
- nichts bei Tastaturfokus — wer mit Tab durch das Diagramm geht, sah bisher
  keine Beschreibung. Nachgemessen mit ECHTEM Tastendruck: Fokus von
  #not.line auf #not.status, Fenster folgt, 10 px unter dem Knoten, Ring am
  fokussierten Knoten.

- Inhalt zieht von `title` nach `data-tip` (sonst zeigte der Browser seinen
  eigenen zusätzlich) — dasselbe Attribut unter anderem Namen, keine
  Verdopplung der 20 kB DOM-Text.
- Die letzten drei nativen Tooltips IN Knoten mussten weichen: Das
  Warndreieck war redundant (Statusname steht in den Fakten), die Annahme
  „Größe fehlt, gilt als M" wandert in die Faktenzeile. Nachgemessen: 0 von
  161 Knoten und 0 Kinder tragen noch einen `title`.
- Absicht-Erkennung: 350 ms Verzögerung, beim Wechsel zwischen Knoten ohne
  Warten, 120 ms Nachfrist beim Verlassen (der Weg ins Fenster führt über
  einen Zwischenraum).
- `aria-hidden` am Fenster: Der Inhalt steht schon im aria-label, sonst würde
  er doppelt vorgelesen; × und ↗ deshalb `tabindex="-1"`.

Werkzeuggrenze notiert: `element.focus()` feuert ohne Fensterfokus keine
Fokus-Ereignisse (`document.hasFocus()` false) — der erste Prüflauf zeigte
fälschlich „öffnet nichts".

SPEC §9 + D57; Plan: #ed.tipwindow [x] (161 Knoten, 0 Warnungen).
293 Tests grün, 5 Snapshots/Zusicherungen auf `data-tip` nachgezogen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:07:00 +02:00
mhoennigandClaude Opus 5 f0fe877d45 feat(diagram): #-Umschalter blendet die Knoten-IDs vor dem Titel ein
Die ID ist die Adresse eines Knotens — Ziel der Abhängigkeiten, Schlüssel der
Beschreibungsblöcke —, stand im Diagramm aber nur im Tooltip (D36). Jetzt
umschaltbar und geschrieben wie im Text: `#some.id: Titel`.

- Als RENDERER-Option, nicht per CSS versteckt: `diagramToSvg()` zieht den
  Knotentext aus `textContent`, ein `display:none` hätte die IDs trotzdem in
  jeden Export geschrieben. Nachgemessen im SVG: `>#not.line: Line format<`
  mit Umschalter, `>Line format<` ohne.
- Zurückgenommen dargestellt (Mono wie im Textfeld, --muted), `aria-hidden` —
  der Screenreader bekommt die ID über a11yId (D36). Zustand persistiert.

Das zehnte Bedienelement hat die Kopfzeile gekippt, zweimal dieselbe Rechnung
wie D17-Nachtrag 5:
- Schreibtisch: 738 nötig, 728 da -> 82 statt 49 px. Nicht die Knöpfe (566 px
  für elf Elemente), sondern zehn Lücken à 14 px = 140 px. `gap:10` gibt
  40 px zurück; danach 698 von 728 und wieder 49 px mit UND ohne Knopf.
- Telefon (375 px): alle Ziele schon auf 29 px, Lücken auf 6 — ein zehntes
  passt arithmetisch nicht (386 gegen 359). D50s Umbruch-Schwelle wandert von
  360 auf 440 px. Greift nur mit dem „Was ist neu?"-Knopf: mit allen zehn
  78 px/zwei Reihen, ohne ihn 49 px/eine Reihe.

SPEC §9 + D56; Plan: #ed.showids [x] (160 Knoten, 0 Warnungen).
293 Tests grün (288+5); i18n `idsTooltip` in 9 Sprachen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:55:04 +02:00
mhoennigandClaude Opus 5 285d86a2e1 feat(editor): ID-Kurzschreibweise #.kc als Eingabehilfe
Gefragt war, ob `#.kc` unter `#prod-stage` als Notation `#prod-stage.kc`
bedeuten könnte. Als Notation wäre es falsch, und das zeigt der eigene Plan:
181 IDs im Baumteil, davon 50 (28 %), deren gepunktetes Präfix gar nicht die
Eltern-ID ist (`#ed.parser` hängt unter `#ed.live`) — die Punkte benennen den
BEREICH, nicht den Pfad, genau wie D48 es vorsieht. Dazu: der
Beschreibungsteil hinter `---` hat keinen Baum (zwei Schreibweisen für eine
ID), Einrücken änderte die Identität und bräche `:#…`-Verweise, und aus einer
Konvention würde Grammatik. Gewinn wären 802 Zeichen in 40 kB.

Also als Eingabehilfe: getippt wird `#.kc`, beim Verlassen der Zeile steht
`#prod-stage.kc` im Text — derselbe Griff, mit dem das Umklappen im Diagramm
seine Faltmarke zurückschreibt (D38-Nachtrag 2). Die Datei bleibt eindeutig,
durchsuchbar und umsortierbar; llms.md ist unberührt, weil sich an der
Notation nichts ändert.

- parser.js: `expandShortIds(text)` neben `setFoldMark`, 17 Tests.
- app.js: löst beim Zeilenwechsel auf, aber nur in der Zeile, in der auch
  getippt wurde — ein bloß durchgeklicktes fremdes Dokument bleibt
  zeichengenau stehen (sonst fiele es aus dem Nachziehen, D27).

Falle, zum zweiten Mal dieselbe: `execCommand` verweigert re-entrant im
`input`-Ereignis den Dienst, `replaceTextUndoable` fällt dann auf
`src.value =` zurück und löscht die Undo-Historie. Gemessen: erstes
Rückgängig ohne Wirkung, jedes weitere `false`. Jetzt einen Zug später
(`setTimeout 0`) und nur mit Fokus im Textfeld; danach nimmt ein Rückgängig
genau die Auflösung zurück.

SPEC §9 + D55; Plan: #ed.shortid [x] (159 Knoten, 0 Warnungen).
288 Tests grün (271+17).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:47:16 +02:00
mhoennigandClaude Opus 5 48d3174684 feat(site): llms.txt als Wegweiser + Charset-Fix für llms.md
Anlass war die Frage, ob `llms.md` ein guter Name ist. Zwei Befunde:

1. Der Zweck der Konvention ist ein anderer, als D43 annahm. llmstxt.org
   über die eigene Datei: „a markdown file that provides brief background
   information and guidance, along with links to markdown files providing
   more detailed information“ — ein Index, kein Inhalt. Der 211-Zeilen-
   Leitfaden ist genau eine jener verlinkten Dateien; `llms.md` ist damit
   der richtige Name, es fehlte der Wegweiser davor.

2. `llms.md` kam auf der stabilen Instanz falsch kodiert an: Apache kennt
   `.md` nicht und sendet GAR KEINEN Content-Type, der Browser rät
   windows-1252. Gemessen: characterSet=windows-1252, aus „notation —
   guide“ wurde „notation â€" guide“, 31 Zeilen betroffen. GitHub Pages
   liefert dieselbe Datei korrekt als text/markdown; charset=utf-8 aus.

- frontend/public/llms.txt: Index nach der Konvention (Titel, Blockquote,
  Notation in Kurzform, ## Docs, ## Optional). Rein ASCII — er ist die
  Datei, die ein fremder Agent ungefragt abruft, und soll auch dort
  ankommen, wo ein Server die Kodierung verschweigt. Alle 5 Links: 200.
- scripts/prod.htaccess: AddType für .md/.txt/.werkbaum, von
  deploy-prod.sh als .htaccess gespiegelt. Nicht in public/ — dort landete
  es wirkungslos im Pages-Artefakt. Rückweg bei 500 steht in der Datei.
- Beide Deploy-Wege kopieren llms.txt mit.

SPEC §13 + D43-Nachtrag 2 (mit Richtigstellung der D43-Annahme);
Plan: #not.llms.index [x]. 243 Tests grün, Plan 157 Knoten, 0 Warnungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:01:51 +02:00
mhoennigandClaude Opus 5 cb966e4e20 feat(editor): Warn-Zeilennummer trägt ihre Meldung als Tooltip
Der Streifen sagte bisher nur, DASS eine Zeile eine Warnung hat; welche,
stand allein unter dem Diagramm — man sah die orange Zahl und suchte dann
in der Liste die passende Zeilennummer.

- warnings.js: `warningText()` neben `formatWarning()`, beide aus demselben
  `switch` (`build(w, t, esc)`) — der Warnungsbereich ist HTML, ein `title`
  ist Klartext. Ohne die Trennung stünde dort „Drag &amp; Drop“.
- app.js: `lineNoWarn` ist jetzt Map Zeile -> Text; mehrere Warnungen einer
  Zeile stehen im Tooltip untereinander. Titel verschwinden mit der Warnung.
- style.css: Der Kasten der Warn-Zahl reicht über die ganze Streifenbreite
  (Ziffern bleiben punktgenau stehen, nachgemessen 21,14 px), `cursor:help`.

Nachgemessen (4 Warnungen in 3 Zeilen): Tooltips wortgleich mit dem
Warnungsbereich, `&`/`"` unverfälscht, keine Zahl ohne Warnung mit `title`;
nach dem Beheben verschwinden Klasse und Titel mit. Werkbaum-Plan: 0.

SPEC §9 (Zeilennummern) + D33-Nachtrag; Plan: #ed.lineno.tip [x].
243 Tests grün (3 neue).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 10:41:05 +02:00