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>
This commit is contained in:
mhoennig
2026-08-22 16:25:09 +02:00
co-authored by Claude Fable 5
parent 6ecf3e4c69
commit 3ba37fc5a0
12 changed files with 239 additions and 39 deletions
+25 -4
View File
@@ -26,15 +26,17 @@ const REALIZED = new Set(['arbeit', 'durchstich', 'fertig', 'prod']);
/* Parst den Notationstext zu { roots, warnings }.
Jeder Knoten: {label, type:'and'|'or'|'xor', optional, status, url, size,
tags, id, focus, children, line}.
tags, id, deps, focus, children, line}.
`deps` sind ID-Strings, keine Knoten-Referenzen — aufgelöst wird erst beim
Konsumenten (D37); der Parser prüft nur die Existenz (`unknownDep`).
`type` ist das Gate der Geschwistergruppe, `optional` (Zeichen `+`, SPEC §3)
eine Eigenschaft des einzelnen Knotens: er hängt an derselben Und-Zerlegung
(`type:'and'`), ist darin aber entbehrlich. Dadurch bleibt die
Gemischt-Warnung unverändert richtig — sie schlägt an, wenn `|` oder `=`
mit `-`/`+` (oder untereinander) gemischt wird.
Extraktionsreihenfolge (SPEC §1): Kommentar -> Zeichen/Status -> URL -> Größe
-> Tags -> Knoten-ID -> Fokusmarke -> Label. Hierarchie über Einrückungsbreite
(Tab = 2 Leerzeichen);
-> Tags -> Knoten-ID -> Abhängigkeiten -> Fokusmarke -> Label. Hierarchie
über Einrückungsbreite (Tab = 2 Leerzeichen);
Elternknoten ist die nächste vorangehende Zeile mit kleinerer Breite. */
export function parse(text){
const virtualRoot = {label:'', type:'and', children:[]};
@@ -68,6 +70,13 @@ export function parse(text){
`@name`; kein Lookbehind (Safari erst ab 16.4). */
let id = null;
rest = rest.replace(/(^|\s)#([\p{L}\p{N}._-]+)/u, (s, pre, g) => { id = g; return pre; });
/* Abhängigkeiten `:#a,#b` (SPEC §1, D37): EIN zusammenhängendes Token ohne
Leerraum, nur ALLEINSTEHEND ANGESETZT — eingeklammerte Erwähnungen wie
`(:#auth,#api)` bleiben damit Label (dieselbe Zitier-Konvention wie bei
der ID). Mehrere Token je Zeile werden zusammengeführt. */
const deps = [];
rest = rest.replace(/(^|\s):#([\p{L}\p{N}._-]+(?:,#[\p{L}\p{N}._-]+)*)/gu,
(s, pre, list) => { for(const p of list.split(',')) deps.push(p.replace(/^#/, '')); return pre; });
/* Fokusmarke `!!!` (SPEC §1) — nur ALLEINSTEHEND, damit „Achtung!!!" ein
gewöhnliches Label bleibt. Kein Lookbehind (Safari kennt es erst ab 16.4):
der führende Leerraum wird mitgefangen und wieder eingesetzt. */
@@ -93,11 +102,23 @@ export function parse(text){
while(stack.length > 1 && stack[stack.length-1].width >= width) stack.pop();
const parent = stack[stack.length-1].node;
const node = {label, type, optional, status, url, size, tags, id, focus, children:[], line:i+1};
const node = {label, type, optional, status, url, size, tags, id, deps, focus, children:[], line:i+1};
parent.children.push(node);
stack.push({node, width});
});
/* Unbekannte Abhängigkeits-IDs (SPEC §1): erst nach dem Einlesen prüfbar —
Vorwärts-Referenzen sind normal. Zyklen (auch auf sich selbst) werden
bewusst NICHT einmal erkannt: Sie sind zulässig und bedeuten „wird
gemeinsam fertig" (D34/D37) — eine Prüfung hätte keinen Abnehmer. */
(function checkDeps(nodes){
for(const n of nodes){
for(const d of n.deps)
if(!idLines.has(d)) warnings.push({type:'unknownDep', line:n.line, id:d});
checkDeps(n.children);
}
})(virtualRoot.children);
/* XOR-Regel (SPEC §3): In einer `=`-Gruppe darf genau EINE Alternative
realisiert sein. Jede weitere wird einzeln gemeldet — die Warnung zeigt so
auf die Zeile, die man ansehen muss, statt pauschal auf die Gruppe (D35).