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>
This commit is contained in:
mhoennig
2026-08-27 20:41:21 +02:00
co-authored by Claude Fable 5
parent e58f71bf90
commit 842e89795a
15 changed files with 776 additions and 33 deletions
+5
View File
@@ -19,6 +19,11 @@ reverse.
## 2026-08-27
- The node window shows the state of a ticket now: subject, status and assignee, read from Taiga through the backend proxy
- Taiga's status is shown next to the notation's own box (`In progress → [~]`) — a column name outside the five known ones stays unmapped and is shown as text
- Reading a ticket never changes the plan: no status is written back, and the text stays untouched
- The state is fetched once per ticket and session, and only when the node window stays open for a moment — a ↻ button fetches again
- Without being logged in nothing is fetched: the window offers a "fetch state" button that asks for the login first
- Ctrl+click a ticket ref like `#US-123` — in the text or on its node — opens the ticket in Taiga's web UI; the node window carries an open button, which is the way on touch
- The backend names the Taiga web address now (`WERKBAUM_TAIGA_WEB_URL`, reported by `/api/v1/info`) — without it, creating tickets still works and opening is simply absent
- A ticket ref like `#US-123` in a node title no longer breaks across two lines at its hyphen
+87
View File
@@ -8123,3 +8123,90 @@ gestapelten Prüfskript setzte der Sprung aus dem Vor-Schritt die Auswahl
asynchron neu und ließ den Tastaturweg scheinbar die falsche URL öffnen —
isoliert wiederholt stimmt sie; dieselbe Zustandsvermischung wie beim
Nachtrag-4-Bau.
**Nachtrag 6 — Ticket-Stand im Knoten-Fenster: gelesen, nie geschrieben
(2026-08-27).** `#trk.resolve` gebaut: Wo eine Ref steht und ein
`&taiga.<slug>` gilt, zeigt das Fenster Betreff, Status und Zuständigen des
Tickets (SPEC §9). Die Entscheidungen:
**Zwei benannte Lese-Endpunkte statt eines mit Typ-Parameter** —
`GET /taiga/userstories/{ref}` und `GET /taiga/tasks/{ref}`, je mit `?slug=`.
Das Präfix der Ref **trägt** den Typ (D91-Nachtrag 2), und Taiga hat für die
beiden Typen getrennte `by_ref`-Endpunkte; ein Enum-Parameter hätte dieselbe
Verzweigung nur einen Schritt später gemacht — und passt schlecht zu den
vorhandenen Namen (`/taiga/userstories` POST legt an, GET liest).
**Zwei Umläufe, nicht einer auf Verdacht.** Eine Ref ist nur **je Projekt**
eindeutig, `by_ref` filtert über die Projekt-**Id**. Der Proxy fragt deshalb
erst `/projects/by_slug`, dann `by_ref`. Der eine gesparte Umlauf über
`?project__slug=` wäre eine Wette auf eine Filter-Eigenheit gewesen, die
niemand hier gemessen hat — die beiden genommenen Endpunkte sind
dokumentiert. **Der Slug wird kodiert** in die Anfrage gesetzt: Er kommt vom
Client, und ein `&` darin hängte sonst einen weiteren Filter an (die kleine
Schwester der SSRF-Falle, wegen der die Basis-URL Server-Konfiguration ist).
**Die Abbildung Taiga-Status → Statusbox liegt im FRONTEND**, obwohl
`backend/CLAUDE.md` sie unter „Taiga-Mapping" führt: Die Statuscodes sind
Notations-Vokabular (SPEC §4), und das Backend parst die Notation nicht
(D14). Der Proxy reicht den **Namen** durch (`status_extra_info.name`), der
Editor bildet ab — headless in `taiga.js`, damit die Regel eine Zusicherung
hat. Vorgabe wie dort notiert: „New" `[ ]`, „In progress" `[~]`, „Ready for
test" `[/]`, „Done" `[x]`, „Archived" `[^]`; Groß-/Kleinschreibung und
Leerraum egal. **Unbekannte Namen bleiben unabgebildet** und stehen nur als
Text — Taiga-Workflows sind je Projekt frei benannt, und Raten wäre hier
besonders teuer: Der Knoten bekäme eine Statusaussage, die niemand gemacht
hat. Die in der Roadmap versprochene **Konfigurierbarkeit** ist damit
ausdrücklich noch offen; der Plan-Knoten `#trk.resolve.map` steht deshalb auf
`[/]`, nicht auf `[x]`.
**Gezeigt wird beides: Taigas Name UND die Box** (`In progress → [~]`). Die
Abbildung ist die Aussage — nur die Box zu zeigen verlöre, woher sie kommt,
nur den Namen zu zeigen verlöre den Bezug zur Notation.
**Gelesen, nie geschrieben.** Kein Zeichen wandert in den Text, keine
Statusbox ändert sich. Das Zurückschreiben ist ein eigener Knoten
(`#trk.write`) und braucht eigene Entscheidungen (wer gewinnt bei
Abweichung?). Wo Ticket und Knoten auseinanderlaufen, sieht man es jetzt —
die Frage stellt das Fenster, beantworten muss sie ein Mensch.
**Zwei Sparsamkeiten gegenüber der fremden Instanz.** Das Fenster öffnet beim
**Überfahren** (D57) und beim Tabben — ein Abruf je gestreiftem Knoten wäre
unhöflich. Also: geholt wird erst, wenn es **400 ms** stehen bleibt, und je
Ticket **einmal je Sitzung** (Cache); ein ↻-Knopf holt neu. Ohne Anmeldung
wird **gar nicht** automatisch geholt — dort steht der Knopf „Stand holen",
und der meldet bei Bedarf an: Ein Klick ist die ausdrückliche Absicht, ein
Zeiger über einem Knoten nicht.
**Der Anmelde-Dialog darf das Fenster nicht zumachen.** Er gehört zu einer
Aktion **aus** dem Fenster; schlösse der `pointerdown`-Wächter es (D52), fiele
die Antwort ins Leere und man müsste den Knoten erneut aufsuchen. Ausgenommen
ist deshalb `.tabmodal-overlay` — die Anlage-Aktion (D91-Nachtrag 4) schließt
das Fenster weiterhin selbst, dort ist es gewollt.
**Ohne Slug geschieht still nichts**, wie beim Öffnen (D91-Nachtrag 5) und
beim Abhängigkeits-Sprung (D67); ein Fehler dagegen steht als Zeile im
Fenster — ein Abruf, den jemand angefordert hat, darf nicht stumm scheitern.
Nicht im Grafikexport und nicht im Druck: Das Fenster ist Bedienhilfe.
**Nachgemessen** Ende-zu-Ende im Browser gegen das lokal laufende Backend mit
Taiga-Stub: Ohne Sitzung steht „Stand holen" und **kein** Abruf geht hinaus;
der Knopf meldet an (Dialog, Fenster bleibt offen) und zeigt danach
`In progress → [~]` mit Taigas eigenem Betreff und „Zuständig: Anna
Beispiel"; die Task zeigt `Ready for test → [/]`; ein Knoten ohne Ref zeigt
unverändert die Anlage-Knöpfe, eine Ref **ohne** Projekt-Zuordnung gar
nichts. Im Mitschnitt des Stubs: genau **zwei** Anfragen je Ticket
(`by_slug` + `by_ref`), **keine** beim kurzen Streifen eines Knotens,
**keine** beim zweiten Ansehen (Cache), und genau eine neue Runde auf ↻.
Die Farben der Box stammen aus §4 (gemessen: `#FADDE4`/`#D897A8` für
`arbeit`). Backend: 5 neue Client-Tests (zweistufiger Weg, eigener
Task-Endpunkt, fehlendes `extra_info` → leer statt geraten, 404 ohne zweiten
Umlauf, kodierter Slug) und 2 Ende-zu-Ende-Tests; Frontend 585 Tests
(9 neue). Gegenproben: Kodierung entfernt → genau die eine danach benannte
Zusicherung fällt, Normalisierung des Statusnamens entfernt → genau die drei.
**Werkzeuggrenze, benannt:** Die Browser-Fläche wurde in dieser Sitzung nicht
dargestellt (keine Frames, keine Screenshots, `getBoundingClientRect()`
durchweg 0) — geprüft ist deshalb über Ereignisse und **berechnete** Stile,
nicht am Bild. Dieselbe Sorte Grenze wie D57 (`focus()` ohne Fensterfokus
feuert keine Fokus-Ereignisse): Der Tastaturweg musste synthetisch angestoßen
werden.
+27
View File
@@ -489,6 +489,30 @@ durch eine echte Linie abgesetzt. Siehe D57.
Siehe D52.
**Ticket-Stand im Knoten-Fenster (§11).** Trägt die Zeile eine
Ticket-Referenz (`#US-123` / `#T-1234`) und ist ihr Teilbaum per
`&taiga.<slug>` (§1) einem Projekt zugeordnet, holt das Fenster den Stand des
Tickets und zeigt ihn über dem Öffnen-Knopf: **Betreff**, **Status** und, wenn
es einen gibt, den **Zuständigen**.
- Der Taiga-Status wird auf die Statusbox der Notation (§4) abgebildet und
**neben** seinem eigenen Namen gezeigt (`In progress → [~]`): „New“ `[ ]`,
„In progress“ `[~]`, „Ready for test“ `[/]`, „Done“ `[x]`, „Archived“ `[^]`;
Groß-/Kleinschreibung und Leerraum sind egal. Ein Name außerhalb dieser
Liste bleibt **unabgebildet** und steht nur als Text — geraten wird nicht.
- **Gelesen, nie geschrieben.** Der Notationstext bleibt unangetastet; die
Statusbox des Knotens ändert sich nicht, und die Abbildung sagt nichts über
Fortschritt (§4) oder Kosten (§5). Das Zurückschreiben ist reserviert (§11).
- Geholt wird erst, wenn das Fenster **kurz stehen bleibt** (nicht im
Vorüberfahren), und je Ticket **einmal je Sitzung** — ein ↻-Knopf im Fenster
holt neu. Ohne Anmeldung an der Instanz, ohne Projekt-Zuordnung oder ohne
konfiguriertes Backend geschieht still nichts; ein Fehler steht als Zeile im
Fenster.
- Reine Bedienhilfe wie das Fenster selbst: nicht im Grafikexport, nicht im
Druck.
Siehe D91-Nachtrag 6.
**Die Knotenfarbe zeigt den effektiven Status (§4)**, nicht den intrinsischen —
das Diagramm beantwortet „wie weit ist das wirklich?“. Wo der eigene Status
**weiter** ist als der effektive (der Knoten wird von Abhängigkeiten
@@ -1360,6 +1384,9 @@ wird sie von selbst die ID. Erstreckt sich ein Plan über **mehrere**
Taiga-Projekte, benennt das Schlagwort `&taiga.<slug>` (unten) das Projekt
je Teilbaum — eine Ref wird gegen das Projekt des nächsten Vorfahren mit so
einem Tag aufgelöst. Siehe D91-Nachträge 2 und 3.
Der **Stand** eines so bezeichneten Tickets (Betreff, Status, Zuständiger)
wird im Knoten-Fenster gezeigt (§9) — gelesen, nie geschrieben; das
**Zurückschreiben** des Status bleibt reserviert (unten).
Freie Schlagworte liegen **nicht** mehr auf `#` — siehe `&tag` unten; damit
ist die frühere Dreifach-Rolle von `#` aufgelöst (D34).
+7 -5
View File
@@ -201,9 +201,9 @@
| [?] #idea.drift.js: Run the one JS parser inside the IDE (M)
- [~] #trk: Tracker integration (XL) %% exactly one of these, hence =
= [~] #trk.taiga: Taiga (XL) https://taiga.io
- [ ] #trk.resolve: Resolve "#US-123" over the REST API (M)
- [ ] #trk.resolve.read: Read title, link and status (S)
- [ ] #trk.resolve.map: Map the workflow onto the states (S)
- [/] #trk.resolve: Resolve "#US-123" over the REST API (M)
- [x] #trk.resolve.read: Read title, link and status (S)
- [/] #trk.resolve.map: Map the workflow onto the states (S)
- [?] #trk.write: Write the status back (M) :#trk.resolve
- [^] #trk.create: Create tickets from nodes (XL)
- [^] #trk.create.proxy: Backend proxy with named endpoints (M)
@@ -1149,11 +1149,13 @@
state. Read-only, and already useful on its own.
#trk.resolve.read
Fetching the ticket and showing what it says.
Fetching the ticket and showing what it says: subject, status and assignee
in the node window, read through the backend proxy.
#trk.resolve.map
Translating the tracker's workflow onto the eight states of the notation.
Configurable, because no two projects use the same column names.
The default five are mapped, an unknown column name stays unmapped and is
shown as plain text; making the mapping configurable is still open.
#trk.write
The other direction: change a status in the plan and the ticket follows.