From 6ecf3e4c692d4b2f33c426264a6929064e26c197 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Sat, 22 Aug 2026 16:15:15 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Knoten-IDs=20(`#auth`)=20parsen=20?= =?UTF-8?q?=E2=80=94=20doppelte=20ID=20warnt=20(SPEC=20=C2=A71,=20D36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/DECISIONS.md | 42 +++++++++++++++++ docs/SPEC.md | 38 +++++++++++++--- docs/TASKS.md | 7 ++- docs/examples/werkbaum.werkbaum | 6 +-- frontend/CLAUDE.md | 9 +++- frontend/index.html | 1 + frontend/src/app.js | 37 +++++++++++---- frontend/src/parser.js | 23 ++++++++-- frontend/src/render.js | 6 ++- frontend/src/warnings.js | 5 +++ frontend/tests/ids.test.js | 80 +++++++++++++++++++++++++++++++++ 11 files changed, 229 insertions(+), 25 deletions(-) create mode 100644 frontend/tests/ids.test.js diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 81fa374..9b72d0f 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -1660,3 +1660,45 @@ kopierten Regeln — hätte jede künftige Layoutänderung doppelt pflegen lasse Im Modell ist `'xor'` ein eigener Gate-Wert (`gateOf`), damit die `mixedGate`-Warnung Mischungen mit `|` von selbst meldet; alle Disjunktiv-Abfragen prüfen `!== 'and'`. + +## D36 — Knoten-IDs (`#auth`) umgesetzt: eng gefasst, sichtbar nur im Tooltip +Der erste Baustein der Phase-4-Kette (ohne IDs keine Abhängigkeiten, ohne die +kein effektiver Status). Vier Festlegungen, die §11 offen ließ: + +**Zeichenmenge wie `@name`, nicht „whitespace-frei“.** §11 sagte „ein +whitespace-freier Bezeichner“; umgesetzt ist die engere Menge aus §7 +(Unicode-Buchstaben, Ziffern, `.`, `_`, `-`). Drei Gründe: Konsistenz mit den +beiden Nachbarn (`@name` heute, `&tag` reserviert mit derselben Menge, D34); +ein `#a/b` oder `#a:b` liefe sonst in dieselben Kollisionen, vor denen §11 bei +`:` und Pfaden gerade ausweicht; und enger → weiter ist später kompatibel +möglich, umgekehrt nicht. + +**Nur alleinstehend angesetzt** (`(^|\s)#…`, wie beim reservierten `&tag`): +„C#“ bleibt ein Label, und — entscheidend für den nächsten Schritt — die +Abhängigkeits-Schreibweise `:#a,#b` wird **nicht** als ID gefressen, weil dort +`:` bzw. `,` vor dem `#` steht. Die ID-Extraktion muss beim Bau der +Abhängigkeiten also nicht angefasst werden. + +**Das erste Token ist die ID, weitere bleiben im Label.** Die ID benennt genau +einen Knoten — mehr als eine pro Zeile ergibt keinen Sinn. Alles nach dem +ersten Treffer bleibt unangetastet stehen, denn dort wohnt die reservierte +Ticket-Referenz (`… #123 …`, §11): Sie soll sichtbar im Label bleiben, bis das +Taiga-Feature sie auflöst. Deshalb wurden auch die drei `#`-Vorkommen im +mitgelieferten Plan eingeklammert (`(#auth)`, `(#123)`) — als Erwähnungen sind +sie keine IDs, und `#123` wäre sonst doppelt vergeben gewesen (Zeile 25/162). + +**Sichtbar im Tooltip und `aria-label`, sonst nirgends.** Die ID gehört nicht +zum Label (sonst änderte das spätere Entfernen die Knoten-Identität der +„Was ist neu?“-Anzeige, D28). Ganz unsichtbar wäre aber nutzerfeindlich — +getippter Text verschwände spurlos. Der Tooltip zeigt `#id` als erste Zeile, +der Screenreader bekommt `a11yId`; ein eigenes Badge bekommt sie erst, wenn +etwas darauf zeigt (Querverbindungen, §11) — die Knoten-Ecken sind belegt +(D18). + +**Doppelte ID: Warnung an der späteren Zeile, mit Nennung der ersten.** Die +Meldung zeigt dorthin, wo man eingreifen muss, und `{firstLine}` erspart das +Suchen. Die spätere ID gilt trotzdem am Knoten (fehlertolerant wie §4); +welcher Knoten bei Verweisen „gewinnt“, entscheidet erst die +Abhängigkeits-Auflösung — dort ist die Warnung dann schon da. Eine Zeile, die +**nur** aus einer ID besteht, wird wie jede leere Zeile ignoriert und belegt +die ID nicht. diff --git a/docs/SPEC.md b/docs/SPEC.md index bf902db..4603227 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -19,8 +19,27 @@ dieser Reihenfolge (wichtig für Kollisionsfreiheit): 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. Fokusmarke: `!!!` als **alleinstehendes** Token (siehe unten). -7. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren. +6. Knoten-ID: das **erste** alleinstehend angesetzte `#name`-Token (siehe unten). +7. Fokusmarke: `!!!` als **alleinstehendes** Token (siehe unten). +8. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren. + +**Knoten-ID `#name`** — benennt einen Knoten im **ganzen Dokument** eindeutig; +sie ist die Adresse für Abhängigkeiten und Beschreibungsblöcke (§11). + +- Zeichenmenge wie bei `@name` (§7): Unicode-Buchstaben, Ziffern, `.`, `_`, `-`. + (Enger als das frühere „whitespace-frei“ aus §11 — Begründung: D36.) +- Erkannt nur **alleinstehend angesetzt** (`(^|\s)#…`): „C#“ bleibt damit ein + Label, und der für Abhängigkeiten reservierte Doppelpunkt `:#a,#b` (§11) + kollidiert nicht. +- Das **erste** solche Token der Zeile ist die ID; weitere `#`-Token bleiben im + Label stehen (dort liegt die reservierte Ticket-Referenz `#123`, §11). Eine + rein numerische ID ist zugleich die künftige Ticket-Referenz — oft ist die + Ticket-Nummer die natürliche Knoten-ID (D34). +- Die ID gehört **nicht** zum Label. Eine eigene Darstellung im Diagramm hat + sie (noch) nicht; sichtbar ist sie im Knoten-Tooltip und im `aria-label`. +- **Doppelte ID:** Warnung `duplicateId` mit beiden Zeilennummern; die spätere + ID gilt trotzdem am Knoten (fehlertolerant wie §4 — die Zeile geht nicht + verloren). **Fokusmarke `!!!`** — „schau hier hin": Der Knoten wird im Diagramm hervorgehoben und ins Bild geholt (§9). Gedacht für das gemeinsame Arbeiten an @@ -45,7 +64,13 @@ Referenz-Regex der Implementierung: ^([ \t]*)([-|+]|=(?=[ \t]))?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$ ``` -Für die Fokusmarke (Schritt 6): +Für die Knoten-ID (Schritt 6, nur der erste Treffer): + +``` +(^|\s)#([\p{L}\p{N}._-]+) +``` + +Für die Fokusmarke (Schritt 7): ``` (^|\s)!!!(?=\s|$) @@ -532,10 +557,9 @@ das auch. Begründung und Zusammenhang: D34. - `#123` — Referenz auf externe Tickets (geplant für Taiga-Integration). Ticket-Referenzen werden **so** notiert, weil es die etablierte Kurzschreibweise ist; sie haben unter den `#`-Verwendungen Vorrang. -- `#auth` — **Knoten-ID**: ein whitespace-freier Bezeichner, der einen Knoten im - ganzen Dokument eindeutig benennt. Ziel für Abhängigkeiten und - Beschreibungsblöcke (siehe unten). Zwei Knoten mit derselben ID sind ein - Fehler und bekommen eine Warnung mit Zeilennummer (§4). +- `#auth` — **Knoten-ID**: **umgesetzt**, Definition jetzt in §1 (Zeichenmenge, + Alleinstehend-Regel, Warnung `duplicateId`). Ziel für Abhängigkeiten und + Beschreibungsblöcke (siehe unten). Beide Rollen vertragen sich: Oft **ist** die Ticket-Nummer die natürliche Knoten-ID. Als Ticket-Link behandelt wird heuristisch das rein **numerische** diff --git a/docs/TASKS.md b/docs/TASKS.md index 3427ec0..8f60755 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -71,7 +71,12 @@ entscheiden, **bevor** Code entsteht. numerisch, oft zugleich die natürliche Knoten-ID; notfalls Präfix wie `#t123`); Schlagworte gehen auf `&tag` — niedrig priorisiert, gebaut erst mit dem ersten Konsumenten (D34-Nachtrag). -- [ ] Knoten-IDs parsen; doppelte ID → Warnung mit Zeilennummer. +- [x] Knoten-IDs parsen; doppelte ID → Warnung mit Zeilennummer. + → Umgesetzt (D36): Zeichenmenge wie `@name`, nur alleinstehend angesetzt + und nur das erste Token je Zeile (`:#a,#b` und Ticket-Erwähnungen bleiben + unberührt); Warnung `duplicateId` nennt beide Zeilen; sichtbar im + Tooltip + `aria-label`; SPEC-§11-Teil nach §1 überführt; + `tests/ids.test.js`. - [ ] Abhängigkeiten `:#a,#b` parsen; unbekannte ID → Warnung, Zyklen erlaubt. - [ ] Effektiven Status rechnen (intrinsisch + Abhängigkeiten); Darstellung entscheiden — die Knotenfarbe zeigt heute den intrinsischen Status. diff --git a/docs/examples/werkbaum.werkbaum b/docs/examples/werkbaum.werkbaum index 9e66acd..ea643ce 100644 --- a/docs/examples/werkbaum.werkbaum +++ b/docs/examples/werkbaum.werkbaum @@ -9,7 +9,7 @@ - [^] People tags, bare URLs, %% comments (XS) - [^] And/or decomposition (S) - [^] Optional nodes — neither required nor an alternative (S) - - [?] Node IDs, #auth (S) %% often just the ticket number, see SPEC §11 + - [x] Node IDs (#auth) (S) %% often just the ticket number, see SPEC §11 + [?] Free tags, &tag (M) %% only together with a consumer, see D34 - [?] A lens: highlight every node tagged &x (S) - [?] Dependencies across the tree, :#auth,#api (M) @@ -22,7 +22,7 @@ - [?] A short text right at the node (S) %% indentation already means hierarchy - [?] A long block at the end, addressed by its id (S) - [-] A separate storage format for the structure (L) %% the text is the format - - [ ] Ticket references #123 (S) + - [ ] Ticket references (#123) (S) - [?] Benefit per node, not only cost (M) | [?] Another sigil next to the size (S) | [?] Story points behind the T-shirt sizes (M) @@ -159,7 +159,7 @@ | [?] Run the one JS parser inside the IDE (M) - [?] Tracker integration (XL) | [?] Taiga (L) https://taiga.io - - [ ] Resolve #123 over the REST API (M) + - [ ] Resolve "#123" over the REST API (M) - [ ] Read title, link and status (S) - [ ] Map the workflow onto the states (S) - [?] Write the status back (M) diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index d81a661..c078e39 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -47,7 +47,8 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der Alternative-Rahmen grau (Basis-CSS `ul.or`). Kein Petrol im Diagramm mehr; `var(--or)` nur noch für UI-Akzente/Logo (SPEC §9, D15). - Extraktionsreihenfolge im Parser nicht umstellen: Kommentar → Zeichen/ - Status → URL → Größe → Tags (sonst kollidiert `@` in URLs). + Status → URL → Größe → Tags → Knoten-ID → Fokusmarke (sonst kollidieren + `@` und `#` in URLs). - Fehlertoleranz (SPEC §4): der Parser erfasst die Statusbox als *beliebiges* Einzelzeichen `\[([^\]])\]` und validiert gegen `STATUS_BY_CODE`; unbekannte Codes → `parse().warnings` als `{type:'unknownStatus', line, code}`, Knoten @@ -266,6 +267,12 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der passiert: Zähler stimmte, nichts leuchtete). Vorgehalten wird nur `freshPrevRoots` (Basis, einmal geparst). Basis je Dokument in `werkbaum-seen`, fortgeschrieben **erst beim Bestätigen** über `#freshBtn`. +- Knoten-IDs `#name` (SPEC §1/D36): nur **alleinstehend angesetzt** und nur + der **erste** Treffer der Zeile (kein `/g`!) — weitere `#`-Token bleiben im + Label (reservierte Ticket-Referenzen), und `:#a,#b` (künftige Abhängigkeiten) + darf nicht gefressen werden. Zeichenmenge wie `@name`. Doppelte ID → + `{type:'duplicateId', line, id, firstLine}`; die spätere gilt trotzdem. + Keine eigene Darstellung — nur Tooltip (erste Position) und `a11yId`. - XOR-Gruppen `=` (SPEC §3/D35): Der Parser setzt `type:'xor'` (nur mit folgendem Leerraum — `=SUMME(…)` bleibt Label); der Renderer gibt `