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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b7637d6393
commit
aa3e087ee6
@@ -734,3 +734,81 @@ stimmte, aber kein einziger Knoten leuchtete, weil `Set.has()` auf
|
||||
Objektidentität prüft und die gerenderten Knoten aus einem anderen Parse kamen.
|
||||
`render()` bildet die Menge daher bei jedem Durchlauf neu; vorgehalten wird nur
|
||||
der **geparste Basisbaum**.
|
||||
|
||||
## D29 — `+` für optionale Knoten: Zugaben sind weder Pflicht noch Alternative
|
||||
Die Notation kannte bisher nur zwei Beziehungen zwischen Geschwistern:
|
||||
erforderlich (`-`) und wählbar (`|`). Für ein einzelnes zusätzliches Feature,
|
||||
das weder nötig ist noch eine Alternative zu etwas anderem, passte keine von
|
||||
beiden. Man schrieb es als normales `-`-Kind — und log damit.
|
||||
|
||||
**Die Lücke hat einen Namen.** Feature-Modelle (FODA) unterscheiden seit den
|
||||
90ern *mandatory*, *optional* und *alternative*. Werkbaum hatte die erste und
|
||||
die dritte; `+` ergänzt die zweite. Mnemonik in der Reihe: `-` Teilpaket,
|
||||
`+` Zugabe, `|` Alternative. Deckt sich mit MoSCoW (Must / Could / Auswahl).
|
||||
|
||||
**Der eigentliche Anlass ist der günstigste Pfad (D18), nicht die Optik.**
|
||||
`markCheapest()` lief bei all-of über *alle* Kinder — jede Zugabe steckte damit
|
||||
im errechneten Minimum. Das Ergebnis war systematisch zu groß, und zwar umso
|
||||
mehr, je ehrlicher der Plan auch Kür notierte. `pathChildren()` filtert
|
||||
optionale Knoten jetzt mit heraus; da beide Nutzer (`cheapestCost`,
|
||||
`markCheapest`) über diese eine Funktion gehen, gilt das samt Teilbaum. Sichtbar
|
||||
wird es beim Vergleich von Alternativen: eine Alternative mit teurer Zugabe
|
||||
verlor vorher gegen eine schlichtere, obwohl die Zugabe gar nicht dazugehört.
|
||||
|
||||
**`+` gehört zum Knoten, nicht zur Gruppe** — anders als `-` und `|`. Der Parser
|
||||
setzt deshalb `optional:true` und lässt `type:'and'` stehen. Zwei Dinge fallen
|
||||
dadurch von selbst richtig aus: `gateOf()` bleibt unverändert, und die
|
||||
`mixedGate`-Warnung schlägt weiterhin genau dann an, wenn `|` mit `-`/`+`
|
||||
gemischt wird — `-` neben `+` ist erlaubt und still. Genau so soll es sein:
|
||||
„diese drei sind nötig, das hier wäre schön" ist der Normalfall, nicht der
|
||||
Fehlerfall. Die Regel dahinter: eine Gruppe ist entweder **konjunktiv**
|
||||
(`-`/`+` frei gemischt) oder **disjunktiv** (`|`).
|
||||
|
||||
**Darstellung: hohler Kreis am Abzweig, kein dritter Linienstil.** Erwogen und
|
||||
verworfen war eine **gepunktete** Abzweiglinie. Sie wäre pro Kind trivial zu
|
||||
setzen gewesen (den Abzweig zeichnet ohnehin ein `li`-Pseudoelement), kollidiert
|
||||
aber mit D15: Im **kompakten** Modus laufen beide Gates nach unten und werden
|
||||
*allein* über den Linienstil unterschieden. Ein dritter Stil müsste sich dort
|
||||
gegen „gestrichelt grau" behaupten — zu wenig Abstand für ein Merkmal, das man
|
||||
auf einen Blick lesen können muss. Der Kreis dagegen ist **orthogonal** zum
|
||||
Linienstil und lässt D15 unangetastet; er ist zudem die etablierte
|
||||
FODA-Konvention (gefüllter Punkt = erforderlich, hohler = optional).
|
||||
|
||||
Er sitzt **mittig auf der Knotenkante**, wo der Abzweig auftrifft, und
|
||||
unterbricht die Linie dort sichtbar. Grundfall im CSS ist die **gestapelte**
|
||||
Anordnung (links auf halber Höhe) — sie deckt vertikal, kompakt und die
|
||||
any-of-Gruppen ab; die **eine** Ausnahme ist der horizontale Fächer (oben
|
||||
mittig), die **eine** Rück-Ausnahme davon der gestapelte all-of-Teilbaum unter
|
||||
einer any-of-Gruppe (D18). Umgekehrt herum aufgezogen wären es vier Ausnahmen
|
||||
statt zwei. `.node::before`/`::after` waren beide frei; die `li`-Pseudoelemente
|
||||
sind von Abzweig und Sammelleiste belegt.
|
||||
|
||||
**Im Export** wird der Kreis **nach** den Knoten gezeichnet. Er liegt zur Hälfte
|
||||
außerhalb der Knotenbox — in der Zeichenreihenfolge der Linien (Schritt 1)
|
||||
hätte das Knoten-Rechteck ihn später halb überdeckt. Die Auftreffpunkte werden
|
||||
beim Linienzeichnen gesammelt und in einem eigenen Schritt 3a ausgegeben.
|
||||
|
||||
**Bekannte Schwäche:** Bei aktivem Günstigster-Pfad-Umschalter (Default an) wird
|
||||
der optionale Knoten ausgeblasst (`opacity:.32`) — und mit ihm sein Kreis, der
|
||||
die Erklärung *dafür* wäre. Undoen lässt sich das nicht: `opacity` am Elternteil
|
||||
schlägt auf jedes Kind durch, auch auf ein Pseudoelement. Bewusst in Kauf
|
||||
genommen, weil das Zurücktreten hier die *Hauptaussage* ist (dieselbe Logik wie
|
||||
bei nicht gewählten Alternativen) und Tooltip, `aria-label` und Legende die
|
||||
Begründung nachliefern. Bei ausgeschaltetem Umschalter steht der Kreis in voller
|
||||
Stärke.
|
||||
|
||||
**Verworfene Alternativen:**
|
||||
- **Den Status `[?]` (Idee) dafür nehmen** — falsche Achse. Status ist
|
||||
Fortschritt, `+` ist Notwendigkeit; eine Zugabe kann längst `[^]` sein (genau
|
||||
der Fall, der die Frage ausgelöst hat). SPEC §3 hält beide Achsen getrennt.
|
||||
- **`@optional` als Personen-Tag** — missbraucht §7 für etwas Strukturelles.
|
||||
- **`#optional` als Schlagwort** (§11 reserviert) — hätte keine Syntaxänderung
|
||||
gekostet, bringt aber weder Darstellung noch die Korrektur am Kostenmodell,
|
||||
also gerade das nicht, wofür sich der Aufwand lohnt.
|
||||
- **`*` statt `+`** — in Regex „null oder mehr" und damit nah an der Begründung
|
||||
von D1. Verworfen, weil `*` in Markdown zugleich Betonung auszeichnet und
|
||||
eher wie eine Fußnote gelesen wird; `+` liest sich als „Zugabe".
|
||||
|
||||
**Verhaltensänderung:** Ein `+` am Zeilenanfang ist jetzt ein Zeichen und
|
||||
gehört nicht mehr zum Label (`+ 5 % Puffer` ergibt das Label „5 % Puffer").
|
||||
Test-abgedeckt, damit es niemanden unbemerkt trifft.
|
||||
|
||||
+37
-10
@@ -14,7 +14,7 @@ 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.
|
||||
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.
|
||||
@@ -23,7 +23,7 @@ dieser Reihenfolge (wichtig für Kollisionsfreiheit):
|
||||
Referenz-Regex der Implementierung:
|
||||
|
||||
```
|
||||
^([ \t]*)([-|])?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$
|
||||
^([ \t]*)([-|+])?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$
|
||||
```
|
||||
|
||||
## 2. Hierarchie
|
||||
@@ -31,7 +31,7 @@ Referenz-Regex der Implementierung:
|
||||
- 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
|
||||
- Zeilen ohne Zeichen (`-`/`+`/`|`) sind Wurzelknoten. Mehrere Wurzeln = mehrere
|
||||
Bäume nebeneinander.
|
||||
|
||||
## 3. Zerlegungsart (Gate)
|
||||
@@ -39,12 +39,21 @@ Referenz-Regex der Implementierung:
|
||||
| Zeichen | Bedeutung | Semantik |
|
||||
|---|---|---|
|
||||
| `-` | all of (Und-Zerlegung) | Alle Teilpakete sind erforderlich. |
|
||||
| `+` | optional (Zugabe) | Einzelnes zusätzliches Teilpaket, nicht 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.
|
||||
- `-` und `|` sind Eigenschaften der **Geschwistergruppe**; `+` ist eine
|
||||
Eigenschaft des **einzelnen Knotens** (er hängt an derselben Und-Zerlegung,
|
||||
ist darin aber entbehrlich).
|
||||
- Daraus folgt die Mischregel: Eine Gruppe ist entweder **konjunktiv** — dann
|
||||
dürfen `-` und `+` frei nebeneinander stehen — oder **disjunktiv** (`|`).
|
||||
`|` mit `-`/`+` zu mischen ist ungültig: Darstellung nach dem **ersten** Kind,
|
||||
plus Warnung `mixedGate` mit Zeilennummer.
|
||||
- Ein `+`-Knoten zerlegt sich weiter wie jeder andere; das Gate seiner eigenen
|
||||
Kinder ist davon unabhängig. Optionalität vererbt sich nicht ausdrücklich —
|
||||
wer unter einem `+`-Knoten hängt, ist mit ihm zusammen entbehrlich.
|
||||
- `+` sagt nichts über den Fortschritt: Eine Zugabe kann längst `[^]` sein. Die
|
||||
beiden Achsen (Status §4, Notwendigkeit §3) sind unabhängig.
|
||||
|
||||
## 4. Status
|
||||
|
||||
@@ -122,6 +131,18 @@ nebeneinander (schmales Diagramm rechts).
|
||||
ist grau (`#6B7A8C`) — kein Petrol mehr im Diagramm. Der Modus ändert nur die
|
||||
**Anordnung**, nicht die Linienfarbe.
|
||||
|
||||
**Optionale Knoten (`+`, §3):** Sie hängen an der normalen all-of-Linie —
|
||||
Anordnung und Linienstil bleiben unverändert. Gekennzeichnet wird der Knoten
|
||||
selbst durch einen **kleinen hohlen Kreis** (weiß gefüllt, Rand in Tinte) genau
|
||||
dort, wo der Abzweig ihn trifft: in der horizontalen Fächer-Anordnung **oben
|
||||
mittig**, in den gestapelten Anordnungen (vertikal, kompakt, unterhalb einer
|
||||
any-of-Gruppe) **links auf halber Höhe**. Übernommen aus den Feature-Diagrammen
|
||||
(FODA: gefüllter Punkt = erforderlich, hohler Punkt = optional). Bewusst **kein
|
||||
weiterer Linienstil**: gestrichelt gehört der any-of-Zerlegung, und im kompakten
|
||||
Modus trägt allein der Linienstil die Gate-Codierung (D15) — ein dritter Stil
|
||||
wäre dort nicht mehr sicher unterscheidbar. Der Kreis erscheint auch im
|
||||
Grafikexport. Siehe D29.
|
||||
|
||||
### Horizontal (Normalmodus)
|
||||
- **all of:** Kinder nebeneinander, klassischer Organigramm-Fächer.
|
||||
- **any of:** Alternativen untereinander; gestrichelte graue Sammelleiste links
|
||||
@@ -190,13 +211,18 @@ werden die für die günstigste Realisierung **nötigen** Knoten:
|
||||
- **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.
|
||||
- **Optionale Knoten (`+`, §3) sind nie nötig** — sie zählen weder zu den
|
||||
Kosten ihres Elternknotens noch liegen sie auf dem Pfad, und der Teilbaum
|
||||
unter ihnen ebenso wenig. Genau dafür gibt es das Zeichen: Ohne `+` rechnet
|
||||
der günstigste Pfad jede Zugabe ins Minimum ein und überschätzt es.
|
||||
- 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
|
||||
any-of-Alternativen und optionale Knoten, je 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
|
||||
@@ -296,8 +322,8 @@ Das Diagramm wird aus der Live-Geometrie in ein eigenständiges SVG (nur Formen
|
||||
### 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
|
||||
(inkl. „(angenommen)“ beim impliziten M), Zuständige, ob der Knoten optional
|
||||
(§3) und ob er 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-/
|
||||
@@ -327,6 +353,7 @@ Druckdialog „an Seite anpassen“ bzw. Querformat wählen.
|
||||
- [~] Umsetzung (XL)
|
||||
- [/] Frontend (S) https://git.example.de/frontend @anna
|
||||
- [ ] Backend (L) @ben @carla
|
||||
+ [?] Dark Mode (S) %% Zugabe, nicht erforderlich
|
||||
- [ ] CMS-Anbindung (M)
|
||||
| [ ] WordPress
|
||||
| [?] Headless CMS
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- [^] T-shirt size and the "decompose from M" rule (S)
|
||||
- [^] People tags, bare URLs, %% comments (XS)
|
||||
- [^] And/or decomposition (S)
|
||||
- [^] Optional nodes — neither required nor an alternative (S)
|
||||
- [-] A separate storage format for the structure (L) %% the text is the format
|
||||
- [ ] Ticket references #123 (S)
|
||||
- [?] Benefit per node, not only cost (M)
|
||||
@@ -26,7 +27,7 @@
|
||||
- [^] Export (M)
|
||||
- [^] SVG and PNG download (S)
|
||||
- [^] PNG to the clipboard (S)
|
||||
- [^] Print stylesheet (XS)
|
||||
+ [^] Print stylesheet (XS)
|
||||
- [^] Nine interface languages (M)
|
||||
- [^] Translations (S)
|
||||
- [^] Default taken from the browser (XS)
|
||||
@@ -36,9 +37,9 @@
|
||||
- [^] Switcher in the editor title bar (S)
|
||||
- [^] Load a document from ?sourceUrl= (S)
|
||||
- [^] Jump between diagram and text (S)
|
||||
- [^] Show what is new since your last visit (S)
|
||||
+ [^] Show what is new since your last visit (S)
|
||||
- [ ] Open and save .werkbaum files (S)
|
||||
- [?] A pastel colour per person (S)
|
||||
+ [?] A pastel colour per person (S)
|
||||
- [?] Dates and milestones (M)
|
||||
| [?] An attribute in the line (S)
|
||||
| [?] A separate timeline view (L)
|
||||
|
||||
Reference in New Issue
Block a user