Files
werkbaum/docs/SPEC.md
T
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

17 KiB
Raw Blame History

WBS-Notation Spezifikation

Textuelle Notation für Projektstrukturpläne (Work Breakdown Structure) mit Und/Oder-Zerlegung. Diese Datei ist die verbindliche Sprachdefinition. Syntaxänderungen werden zuerst hier dokumentiert, dann implementiert.

1. Zeilenformat

[Einrückung][Zeichen] [Statusbox] Label (Größe) URL @tag … %% Kommentar

Alle Bestandteile außer dem Label sind optional. Die Extraktion erfolgt in dieser Reihenfolge (wichtig für Kollisionsfreiheit):

  1. Kommentar entfernen: alles ab %% bis Zeilenende.
  2. Einrückung, Zeichen (- / |) und Statusbox […] per Zeilen-Regex.
  3. URL: erstes Token, das auf https?://\S+ passt (dadurch stören @ in URLs nicht).
  4. Größe: erstes (XS|S|M|L|XL|XXL), Groß-/Kleinschreibung egal.
  5. Tags: alle @name-Vorkommen.
  6. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren.

Referenz-Regex der Implementierung:

^([ \t]*)([-|])?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$

2. Hierarchie

  • Die Einrückung bestimmt die Ebene. Es gibt keine feste Schrittweite: Elternknoten ist die nächste vorangehende Zeile mit kleinerer Einrückungsbreite (Tab zählt als 2 Leerzeichen).
  • Zeilen ohne Zeichen (-/|) sind Wurzelknoten. Mehrere Wurzeln = mehrere Bäume nebeneinander.

3. Zerlegungsart (Gate)

Zeichen Bedeutung Semantik
- all of (Und-Zerlegung) Alle Teilpakete sind erforderlich.
| any of (Oder-Zerlegung) Mindestens eine Alternative wird gewählt.
  • Das Gate ist eine Eigenschaft der Geschwistergruppe; alle Geschwister sollen dasselbe Zeichen tragen.
  • Gemischte Geschwister: Darstellung nach dem ersten Kind, plus Warnung mit Zeilennummer.

4. Status

Codiert als Checkbox nach dem Zeichen (Erweiterung der Markdown-Task-Syntax):

Code Key Name Bedeutung Hintergrund Rahmen
[?] idee Idee vage Idee #EBEDEF (grau) #A2ABB5
[ ] geplant geplant beschlossen, nichts investiert #EBE4F6 (flieder) #A991D4
[~] arbeit in Arbeit Kosten investiert, Risiko hoch #FADDE4 (rosé) #D897A8
[/] durchstich Durchstich funktionsbereit/vorführbar, Feinarbeiten offen #FBF2CE (pastellgelb) #D9BE63
[x] fertig fertig abgeschlossen #DCF1DE (pastellgrün) #86C293
[^] prod in Produktion deployed/live #DBEAF8 (pastellblau) #85ACD7
[-] verworfen verworfen bewusst nicht weiterverfolgt #F1F2F4, gestrichelter Rahmen, Text durchgestrichen #B3BAC2
[!] highrisk High Risk Aufwand noch unklar, hohes Risiko #FFE5CC (orange) #F97316
  • Ohne Statusbox: neutraler Knoten (weiß).
  • x auch als X zulässig.
  • High-Risk-Kennzeichnung: Der [!]-Knoten trägt zusätzlich zur orangen Färbung ein Warndreieck (⚠, in #F97316) als kleines Badge an der oberen linken Ecke — Tooltip „High Risk Aufwand noch unklar." Das Badge erscheint auch im Grafikexport. (Mnemonik: ! = Achtung. Ob [!] später zusätzlich zum Fertigstellungsstatus stehen darf, ist offen; vorerst ist es ein eigener Status wie die übrigen.)
  • Verworfene Knoten (inkl. Teilbaum) sind per Default ausgeblendet; Toggle „verworfene einblenden“ zeigt sie.
  • Fehlertoleranz: Ein unbekanntes Zeichen in der Statusbox (z. B. [z]) wird nicht verschluckt: Der Knoten erscheint neutral und es wird eine Warnung unknownStatus mit Zeilennummer gemeldet — die Zeile geht nicht verloren, Folgezeilen bleiben unberührt. (Eine mehrzeichige Klammer wie [xyz] ist keine Statusbox und bleibt Teil des Labels.)

5. Aufwand (T-Shirt-Größen)

  • Werte: XS < S < M < L < XL < XXL, notiert in Klammern, z. B. (L).
  • Untergliederungsregel: Ab (M) muss ein Element weiter zerlegt sein. Ein Element ≥ M ohne Kinder erhält einen Geister-Knoten an gestrichelter Linie darunter (in --warn, #B45309). Sein Label ist knapp „…“; die Erklärung („Ab Größe M sollte ein Element weiter untergliedert werden.“) steht im Tooltip — der ausgeschriebene Text machte die Zelle sonst breiter als der Knoten und verschob gestapelte Geschwister. Der angedeutete Unterpunkt genügt als Hinweis; eine zusätzliche Umrandung des Knotens gibt es nicht.
  • Ausnahme: verworfene Elemente lösen die Regel nie aus.
  • Anzeige: petrolfarbenes Badge (--or, #0F766E) mit weißer Schrift oben rechts an der Knoten-Ecke.
  • Ein nacktes https://…-Token macht den ganzen Knoten klickbar (neuer Tab, rel="noopener"); Kennzeichnung mit ↗ hinter dem Label.

7. Personen-Tags

  • @name mit name aus Unicode-Buchstaben, Ziffern, ., _, -.
  • Mehrere Tags pro Zeile möglich, Position im Text egal.
  • Anzeige: helle Pillen unten rechts an der Knoten-Ecke.

8. Kommentare

  • %% leitet einen Kommentar ein — ganze Zeile oder ab Zeilenmitte.
  • Konvention aus Mermaid übernommen; %%{ vermeiden (dort Direktiven-Syntax).

9. Darstellung

Drei Modi, im Editor umschaltbar über Icon-Buttons (Reihenfolge horizontal · kompakt · vertikal, je mit Tooltip). Der Modus wählt zugleich die Seitenanordnung: horizontal stellt Diagramm über den Editor (volle Breite), vertikal und kompakt stellen Editor und Diagramm nebeneinander (schmales Diagramm rechts).

Linienführung (in allen Modi gleich): all-of-Linien durchgezogen in Tinte (#41556E); any-of-Linien — Haupt-/Sammelleiste und Abzweige — durchgehend gestrichelt in Grau (#6B7A8C). Auch der Rahmen der Alternative-Knoten ist grau (#6B7A8C) — kein Petrol mehr im Diagramm. Der Modus ändert nur die Anordnung, nicht die Linienfarbe.

Horizontal (Normalmodus)

  • all of: Kinder nebeneinander, klassischer Organigramm-Fächer.
  • any of: Alternativen untereinander; gestrichelte graue Sammelleiste links unterhalb des Parents, gestrichelte graue Abzweige zu den Alternativen.

Vertikal (transponiert)

  • all of: exakter transponierter Organigramm-Fächer (horizontal um 90° gedreht): Der Parent sitzt vertikal mittig zu seiner Kindergruppe, die Linie tritt rechts auf halber Höhe aus (entspricht Richtung LR), eine vertikale Sammelleiste (von erster bis letzter Kindmitte) verteilt mit durchgezogenen Abzweigen; Kinder rechts untereinander.
  • any of: Austritt unten links, gestrichelte graue Abzweige.
  • Merkregel: Austrittsseite codiert das Gate (rechts = und, unten = oder), Linienstil bestätigt es.

Kompakt (transponiert, platzsparend)

  • Beide Gates laufen unten links aus dem Parent heraus, Kinder untereinander — kein Rechts-Fächer, dadurch minimale Breite.
  • Das Gate wird hier allein über den Linienstil codiert (siehe D15): und = durchgezogen (Tinte), oder = gestrichelt (Grau).

Geometrie-Invarianten

  • Knoten haben feste Zeilenhöhe (line-height: 1.3), damit Abzweige deterministisch auf Knotenmitte liegen (Offset 23 px = 5 px Listenabstand
    • halbe Knotenhöhe). Abzweige zielen auf den Knoten, nie auf die Mitte des Teilbaums.
  • In den transponierten Modi (vertikal, kompakt) stehen untereinander gestapelte Geschwister mit zusätzlichem Abstand nach unten (damit das Größen-Badge oben rechts nicht mit den Tags unten rechts des darüber liegenden Knotens überlappt). Der Abstand wird nur unterhalb ergänzt, der 23-px-Abzweig oben bleibt unverändert; die vertikal zentrierten all-of-Zwischenknoten bekommen ihn symmetrisch, damit ihr Abzweig (50 %-Höhe) weiterhin auf der Knotenmitte liegt.

Kleiner Bildschirm (mobil)

Ab schmaler Breite (≤ 640 px, z. B. Smartphone) werden Diagramm und Texteditor gestapelt und über den Splitter frei geteilt: Ziehen verteilt beliebig, jederzeit erneut verstellbar; die beiden Titelzeilen bleiben dabei immer stehen (jedes Panel schrumpft höchstens bis auf seine volle Titelzeile — die Zeilen-Minima entsprechen den gemessenen Kopfhöhen). Ein Antippen einer Titelzeile klappt dieses Panel ganz aus (das andere schrumpft auf seine Titelzeile). Es gibt hier kein diskretes Minimieren/Einrasten mehr (kein Snap, keine Min/Normal/Max-Buttons) — die Aufteilung ist durchgehend stufenlos. Voreinstellung ist Diagramm maximiert (Editor als Titelzeile). Die Legende bekommt hier einen eigenen Umschalter im Editor-Kopf (statt der dauerhaften „AGENDA“-Zeile), damit sie zugeklappt keinen Platz kostet. Der Modus-Wähler zeigt nur das aktive Icon und schaltet bei jedem Tippen reihum weiter (horizontal → kompakt → vertikal → …), spart also zwei Drittel der Breite. Die Sprachwahl zeigt eingeklappt nur die aktive Sprache; ein Tipp darauf klappt die volle Leiste als Overlay über die Kopfzeile auf (verdeckt die übrigen Bedienelemente, statt die Zeile zu verbreitern), nach der Auswahl klappt sie wieder auf die gewählte Sprache ein (Tipp daneben schließt ebenfalls). Die Download-Buttons (SVG/PNG mit Text-Label) verbergen sich hinter einem einzelnen Download-Icon, das die Formatwahl bei Bedarf als kleines Dropdown-Overlay unter der Kopfzeile zeigt — sonst bräche die Titelzeile um; „Kopieren" bleibt sichtbar. Der Untertitel wird auf eine garantiert einzeilige Kurzfassung reduziert. Voreinstellung auf kleinem Bildschirm ist Vollbild (siehe D17).

Günstigster Pfad (Kosten-Hervorhebung)

Ein Umschalter (Icon-Button im Diagramm-Kopf, Voreinstellung an, Zustand persistiert) hebt den kostengünstigsten Weg durch den Baum hervor. Ermittelt werden die für die günstigste Realisierung nötigen Knoten:

  • all of: alle Kinder sind nötig.
  • any of: nur die günstigste Alternative ist nötig. „Günstig" = kleinste rekursive Kosten (eigene T-Shirt-Größe plus — je Gate — Summe bzw. Minimum der Kinder). Bei Gleichstand gewinnt die erste Alternative.
  • Verworfene Knoten zählen nie mit (unabhängig vom „verworfene einblenden"- Filter).
  • Fehlende Größe wird als M gewertet (nur für diese Kostenschätzung; die SPEC-Semantik der Größen in §5 bleibt unberührt).

Darstellung per Inversion: nicht benötigte Knoten (nicht-gewählte any-of-Alternativen samt Teilbaum) treten zurück (blass, entsättigt); der günstige Pfad hebt sich dadurch von selbst ab — kein zusätzlicher Rahmen an den ohnehin dichten Knoten-Ecken. Wo die Größe implizit als M angenommen wird, zeigt der Knoten ein invertiertes Größen-Badge (weiß mit petrolfarbenem Rand/Text statt gefüllt) mit erläuterndem Tooltip.

Zusätzlich fädelt eine gestrichelte, geschwungene Petrol-Linie durch die Endknoten (Blätter) des Pfads (Katmull-Rom-Spline in Dokument-Reihenfolge, in allen Modi). Die kräftige Linie liegt hinter den Knoten (nur in den Lücken voll sichtbar), eine abgetönte Kopie davor deutet den Verlauf beim Durchschreiten eines Knotens nur schwach an. An jedem echten Endknoten sitzt ein großer, blasser Petrol-Stationspunkt (U-Bahn-Plan-Prinzip: der Knotentext bleibt lesbar) — nur durchquerte Fremd- oder Zwischenknoten tragen keinen Punkt, sodass eindeutig bleibt, welche Knoten auf dem Pfad enden. Linie, abgetönte Kopie und Punkte erscheinen bei aktivem Umschalter auch im Grafikexport. Siehe D18.

Diagramm aus einer URL laden (?sourceUrl=)

Der Editor kann den Notationstext aus einer externen Textdatei beziehen: …?sourceUrl=https://example.org/plan.txt (relative Angaben werden gegen die Seite aufgelöst; zugelassen sind nur http/https). Der Text wird als eigenes Dokument geführt, dessen Name die URL ist (vollständige URL im Tooltip); derselbe Link aktualisiert dieses Dokument, statt ein neues anzulegen. Ist der Parameter gesetzt, wird bei jedem Laden neu geholt — die URL ist die Quelle der Wahrheit, lokale Änderungen daran überleben ein Neuladen nicht. Scheitert das Laden (häufigster Fall: das Ziel sendet keinen Access-Control-Allow-Origin-Header, außerdem 404/Netzfehler), bleibt der bisherige Stand stehen und es erscheint eine Warnung. Siehe D23.

Sprung zwischen Diagramm und Text

Jeder Knoten kennt seine Zeilennummer im Notationstext; beide Richtungen sind verknüpft (siehe D25):

  • Diagramm → Text: Alt+Klick auf einen Knoten markiert die zugehörige Zeile im Texteditor (ganze Zeile ausgewählt, in Sicht gescrollt, Fokus im Textfeld). Tastatur: Alt+Enter am fokussierten Knoten. Auf Touch-Geräten: langer Druck — nach ≥ 500 ms zeigt der Knoten einen Petrol-Ring („scharf"), der Sprung erfolgt beim Loslassen; ein Wischen bricht ab. Ist das Editor-Panel zugeklappt, öffnet der Sprung es zuerst.
  • Text → Diagramm: Der Knoten der Cursor-Zeile wird im Diagramm hervorgehoben (weißer Halo + Ring in Tinte) und beim Zeilenwechsel ins Bild gescrollt. Zeilen ohne Knoten (Kommentar, Leerzeile, ausgeblendetes verworfenes Element) heben nichts hervor.
  • Der einfache Klick bleibt unverändert der Link (§6): Ein Knoten mit URL öffnet sie weiterhin im neuen Tab. Deshalb Alt und nicht der einfache Klick.
  • Auffindbarkeit: Solange Alt gedrückt ist, zeigen alle Knoten den Sprung-Cursor und der Knoten unter dem Zeiger einen Petrol-Ring — die Geste ist im Moment des Ausprobierens sichtbar, auch auf verlinkten Knoten. Dazu nennt der Knoten-Tooltip die Geste, und die aufklappbare Legende schließt mit einer Bedienungs-Zeile ab.
  • Die Hervorhebung ist eine reine Editierhilfe: nicht im Grafikexport, nicht im Druck.

Grafikexport des Diagramms

Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen

  • Text, keine externen Ressourcen) nachgezeichnet. Zwei Icon-Schaltflächen:
  • Kopieren — als PNG in die Zwischenablage. Es werden zwei Flavors geschrieben: image/png (das eigentliche Bild) und text/html mit eingebettetem PNG. Fällt der Bild-Clipboard ganz aus (fehlende ClipboardItem-Unterstützung), wird der SVG-Quelltext kopiert.
  • Herunterladen — als Datei, zwei Schaltflächen mit Format-Label: SVG (werkbaum-diagramm.svg, Vektor) und PNG (werkbaum-diagramm.png, Raster). Der Datei-Weg ist der verlässliche Weg für Programme, die das Browser-Bild-Clipboard nicht erkennen. Manche Programme lesen auch das SVG nicht (z. B. LibreOffice) — dafür gibt es die PNG-Datei, die überall per „Bild einfügen“ importierbar ist.
  • Übernommen werden Knotenfarben (Status §4), Größen-Badge, Tags und der Geister-Knoten; die Verbindungslinien werden je Gate neu gezogen (und = durchgezogen Tinte, oder = gestrichelt Grau) und treffen die Knoten unabhängig vom Darstellungsmodus.
  • Es wird genau die sichtbare Struktur exportiert (der „verworfene einblenden“-Filter wirkt auch hier).

Barrierefreiheit

Die visuell codierten Knoten-Eigenschaften werden für Screenreader in einem sprechenden aria-label je Knoten zusammengefasst — Label, Status, Aufwand (inkl. „(angenommen)“ beim impliziten M), Zuständige und ob der Knoten verlinkt ist —, alles in der aktuellen UI-Sprache. Die rein visuellen Beiwerke (Größen-Badge, Tags, ↗-Pfeil) sind aria-hidden, damit sie nicht kryptisch doppelt vorgelesen werden. Alle Knoten sind fokussierbar (tabindex="0" bzw. der Link selbst); die Fokusreihenfolge entspricht der Dokument-/ Lesereihenfolge (Wurzeln, dann Kinder in Tiefe). Ein sichtbarer :focus-visible-Rahmen (Petrol) zeigt den Tastaturfokus. Der Warnungsbereich ist eine Live-Region (role="status", aria-live="polite"), sodass neue Warnungen (z. B. gemischte Gates) angesagt werden.

Druck

Ein @media print-Stylesheet reduziert die Seite auf das Diagramm: Kopfzeile, Editor-Panel, Splitter, sämtliche Bedienelemente (Diagramm-Kopf), Warnungen und Footer werden ausgeblendet; das Diagramm füllt die Seite und darf über mehrere Seiten laufen. Die Statusfarben werden bewusst mitgedruckt (print-color-adjust: exact), Knoten brechen nicht über den Seitenrand (break-inside: avoid), und der Günstigster-Pfad-Overlay wird mitgedruckt. Sehr breite Bäume laufen bei 100 % über die Seitenbreite hinaus — dann im Druckdialog „an Seite anpassen“ bzw. Querformat wählen.

10. Beispiel (kanonisch)

%% Projektstruktur  Stand Sprint 14
[~] Website-Relaunch (XL) https://wiki.example.de/relaunch
  - [x] Konzeption (M)
    - [x] Zielgruppenanalyse (S)
    - [x] Sitemap (XS)
  - [~] Umsetzung (XL)
    - [/] Frontend (S) https://git.example.de/frontend @anna
    - [ ] Backend (L) @ben @carla
    - [ ] CMS-Anbindung (M)
      | [ ] WordPress
      | [?] Headless CMS
      | [-] Eigenentwicklung  %% Aufwand zu hoch
  - [?] Hosting (M)
    | Cloud
    | On-Premise

11. Reservierte Erweiterungen (noch nicht implementiert)

  • #123 — Referenz auf externe Tickets (geplant für Taiga-Integration).
  • #tag — freie Schlagworte (deshalb # nicht anderweitig verwenden).

12. Dateiendung

  • Notationstexte tragen die Endung .werkbaum, Kodierung UTF-8, Zeilenende LF. Beispiele: docs/examples/*.werkbaum.
  • Die Endung ist Konvention, kein Vertrag: Der Parser sieht nur Text, und das Laden per ?sourceUrl= (§9) wertet weder Endung noch Content-Type aus. .txt und endungslose Dateien bleiben damit gültig.
  • Es gibt keinen registrierten MIME-Typ; wer selbst ausliefert, nimmt text/plain; charset=utf-8 (dann zeigt der Browser die Datei an, statt sie herunterzuladen). Siehe D24.