feat(taiga): Bulk-Abfrage und Abweichungs-Marke — das Diagramm zeigt, wo Ticket und Plan auseinanderlaufen (D91-Nachtrag 10, SPEC §9)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-28 11:44:33 +02:00
co-authored by Claude Opus 5
parent e77a4d1ce4
commit a1e4a3bbab
17 changed files with 663 additions and 7 deletions
+4
View File
@@ -19,6 +19,10 @@ reverse.
## 2026-08-28
- One bulk request reads the status of all referenced tickets: the proxy fans the refs out right next to the Taiga instance, so the cost scales with the refs in the plan — not with a project that holds thousands of tickets
- With a Taiga login, diverging tickets show up in the diagram: a ref whose ticket status no longer matches the node's box turns amber — as its own little badge where the ref is the node id and thus not part of the title
- The bulk result pre-fills the node window's ticket cache, so hovering a ticket node usually needs no request of its own any more
- The divergence mark is session knowledge and stays out of the SVG export and of print — what you share is the plan, not your fetch state
- The create buttons in the node window are recut: one "create story" button always opens the dialog with project choice and sub-package checkboxes — unchecking covers the story-only case
- A "create task" button appears where an ancestor node already carries a story: the task lands in that story without any dialog — project and story follow from the tree, only an error gets a surface
+85
View File
@@ -8386,3 +8386,88 @@ benannte Test.
Seite nicht (Klicks schon: das DOM ist geteilt, die JS-Objekte nicht). Eine
Rückfrage lässt sich dort also nicht wegstubben; Aufräumen, das durch eine
Rückfrage führt, geht stattdessen über die Ablage selbst (D83-Schema).
**Nachtrag 10 — Bulk-Abfrage und Abweichungs-Marke: das Diagramm zeigt, wo
Ticket und Plan auseinanderlaufen (2026-08-28).** Nutzerwunsch in zwei
Schritten: erst die Frage nach einer Bulk-Abfrage („Einzelabfrage wäre
wahrscheinlich zu langsam"), dann — auf den Befund, dass die Taiga-Projekte
**Tausende** Stories und Tasks haben — die nach einem Vorfilter. Die
Antwort auf beide ist dieselbe: **Der Filter sind die Refs im Plan, und
angewandt wird er im Proxy.**
- **`GET /taiga/tickets?slug=…&refs=US-1,T-2,…`** — EIN Aufruf vom Browser,
der Proxy fächert in `by_ref`-Einzelabfragen auf: **parallel** (virtuelle
Threads, D76-Nachtrag 5) mit **gedeckelter Nebenläufigkeit** (Semaphore,
6 gleichzeitig — Höflichkeit gegenüber der fremden Instanz). Die teure
Strecke Browser→Backend (~130 ms, gemessen) fällt einmal an; Backend und
Taiga-API sitzen beim selben Hoster, dort kosten die Einzelabfragen
Millisekunden. Die Kosten skalieren mit den Refs im Plan (Dutzende), nie
mit dem Projekt (Tausende) — die zuerst erwogene **Projekt-Volliste ist
damit verworfen**: Sie holte Megabytes, um fast alles wegzuwerfen, und
ließe Taiga je Anfrage eine große Liste rechnen.
- **Ein Endpunkt für beide Typen:** Das Präfix jeder Ref trägt den Typ
(`US-`/`T-`, Nachtrag 2) — genau die Auflösung, für die Werkbaum es
schreibt. Antwort ist eine Map Ref → Stand (dieselben Felder wie der
Einzel-Abruf, samt `version` — der Bulk füllt den Ticket-Cache des
Knoten-Fensters vor, das Fenster braucht dann meist keinen Abruf mehr).
- **Fehler-Semantik:** Eine Ref, die es nicht (mehr) gibt, **fehlt still**
in der Antwort — die übrigen kommen trotzdem; 401, unbekanntes Projekt
oder eine tote Instanz brechen die ganze Anfrage ab (sie beträfen jede
Ref). Ungültige Refs in der Liste sind ein **400, nicht still
übersprungen** (D59); doppelte werden vor dem Fächer zusammengelegt, und
bei **200 Refs** ist benannt Schluss (Client schneidet, Server lehnt ab).
- **Die Marke:** Eine Ref, deren Ticket-Status abgebildet ist und nicht zur
Statusbox passt, färbt sich **warnfarben**. Beim Bauen fiel die Lücke des
Vorschlags auf: `- [ ] Login #US-123` macht die Ref zur **Knoten-ID**
(§1, erstes `#`-Token) — sie steht gar nicht im Label, es gibt nichts zu
färben. Für diesen Fall hängt der **Renderer** ein kleines nachgestelltes
Badge mit der Ref an (die D40-Bauform der ”-Marke: vor dem Messen, die
Geometrie stimmt); sichtbare Refs (eigene ID auf der Zeile, D60-Knoten)
färben sich selbst. Ein unabgebildeter Spaltenname markiert nichts —
dieselbe Regel wie im Fenster.
- **Wann:** einmal je Projekt und Sitzung, angestoßen vom Neubau, von
`GET /info` (die Antwort kommt nach dem ersten Neubau) und von jeder
Anmeldung; ohne Anmeldung nie (die Nachtrag-6-Linie). Das Markieren aus
dem Cache ist kostenlos und läuft je Neubau mit; kommt der Bulk an,
rendert sein Callback — **außer** ein Knoten-Fenster ist offen (der Neubau
schlösse es): dann nur die Klassen-Marken, Badges kommen mit dem nächsten
Neubau. Einzel-Abrufe (↻, Schreiben) ziehen die Marken mit nach; nach
„nach Taiga schreiben" räumt `markTicketDiffs` erst ab und setzt neu —
sonst stünde die Marke auf einem Ticket, das wieder einig ist.
- **Nicht im Export, nicht im Druck:** Die Marke hängt an der Sitzung
(Anmeldung, Abrufzeitpunkt) — exportiert wird der Plan, nicht der
persönliche Abrufstand (dieselbe Linie wie der gelbe Kranz, D28). Der
Export liest die Label-Farbe vom **Knoten**, nicht von der Spanne
(nachgemessen), das Badge ist per `excludeSel` ausgenommen, der Druck
blendet es aus. Und im `aria-label` steht die Abweichung nicht — sie ist
asynchrones Sitzungswissen, der Screenreader-Weg ist das Knoten-Fenster;
benannt als Grenze, nicht übersehen.
- **Blass bleibt blass:** Auf einem vom Pfad zurückgetretenen Knoten dimmt
die Marke mit (die frontend/CLAUDE.md-Prüffrage ist gestellt): Anders als
bei `fresh`/`focusmark` ist die Aussage hier an einem nicht gebrauchten
Knoten auch weniger dringend, und erledigte Knoten treten seit D46 ohnehin
nie zurück.
- **Messnotiz für später:** Ob Taigas Listen-Endpunkte eine **Ref-Liste**
als Filter nehmen (`?project=…&refs=…`), ist nicht gemessen — die API-Doku
ist dort dünn. Falls ja, tauscht der Proxy sein Inneres (ein Aufruf statt
Fächer), ohne dass sich am Endpunkt oder im Editor etwas ändert.
**Nachgemessen** Ende-zu-Ende (lokales Backend + Taiga-Stub): Die
Bulk-Antwort trägt beide Refs samt Status und `version` (curl); im Browser
bekommt der Ref-als-ID-Knoten mit Abweichung das Badge in `--warn`
(`rgb(180,83,9)`), der einige Knoten nichts, der Label-Ref färbt sich ohne
Badge; der Stub-Mitschnitt zeigt je Bulk **einmal** `by_slug` und je
eindeutiger Ref eine `by_ref`-Abfrage (Dedupe greift); das Knoten-Fenster
liest danach aus dem Cache (kein weiterer Abruf); im exportierten SVG steht
der Label-Ref genau einmal, in Knotenfarbe, ohne Badge und ohne Bernstein.
Backend-`check` grün (5 neue Tests: Fächer mit Map, 404 fällt still,
401 bricht ab, Bulk-E2E, ungültige Ref → 400); Frontend 617 Tests (15
neue). Gegenproben per Mutation: 404-Überspringen entfernt → genau der
danach benannte Test fällt (1 von 21); Dedupe entfernt → genau der eine.
**Werkzeug-Notizen:** Der D82-Abschieds-Flush gewinnt gegen ein
`localStorage.setItem` + `location.reload()` — wer den Speicher unter einer
lebenden Seite umschreibt, bekommt beim Reload deren Gedächtnis zurück;
wiederhergestellt wird ein Dokument als gewöhnliche Änderung über das
Textfeld. Und der Debug-Reset hängt am `confirm`, das die isolierte Welt
des Prüf-Panes nicht stubben kann (Nachtrag 9) — er ist dort wirkungslos.
+16
View File
@@ -529,6 +529,22 @@ es einen gibt, den **Zuständigen**.
Siehe D91-Nachträge 6, 7 und 8.
**Abweichungs-Marke im Diagramm.** Mit Anmeldung an der Instanz holt **eine
Bulk-Anfrage je Projekt und Sitzung** den Stand aller referenzierten Tickets
(der Proxy fächert sie serverseitig in Einzelabfragen auf — die Kosten
skalieren mit den Refs im Plan, nie mit der Größe des Taiga-Projekts). Eine
Ref, deren Ticket-Status **abgebildet** ist und nicht zur Statusbox des
Knotens passt, färbt sich **warnfarben** — steht sie im Label, die Ref
selbst; ist sie (nur) die Knoten-ID (§1: das erste `#`-Token), erscheint sie
als kleines nachgestelltes Badge hinter dem Titel (die Bauform der ”-Marke).
Ein unabgebildeter Spaltenname markiert nichts — er sagt nichts über den
Plan. Die Einzelheiten samt der beiden Aktionen stehen wie gehabt im
Knoten-Fenster; ein Ticket, das es nicht (mehr) gibt, markiert nichts, und
ein Fehler der Hintergrund-Abfrage bleibt still. Ohne Anmeldung wird nichts
geholt. Die Marke hängt an der Sitzung (Anmeldung, Abrufzeitpunkt) und
erscheint deshalb **weder im Grafikexport noch im Druck** — exportiert wird
der Plan, nicht der persönliche Abrufstand. Siehe D91-Nachtrag 10.
**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
+24
View File
@@ -205,6 +205,9 @@
- [^] #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
- [x] #trk.bulk: Diverging tickets show up in the diagram (M) :#trk.resolve.read
- [x] #trk.bulk.proxy: One request fans out into by_ref queries server-side (S)
- [x] #trk.bulk.mark: A diverging ref turns amber on its node (S)
- [^] #trk.create: Create tickets from nodes (XL)
- [^] #trk.create.proxy: Backend proxy with named endpoints (M)
- [^] #trk.create.login: Log in to Taiga, token stays in the browser (S) :#trk.create.proxy
@@ -1158,6 +1161,27 @@
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.bulk
With a Taiga login, one bulk request per project and session reads the
status of every referenced ticket, and a ref whose ticket no longer
matches the node's status box turns amber. Details and both sync actions
stay in the node window; the mark is session knowledge and stays out of
export and print.
#trk.bulk.proxy
GET /taiga/tickets takes the plan's refs and fans them out into by_ref
queries next to the Taiga instance, in parallel with capped concurrency.
The cost scales with the refs in the plan, never with a project holding
thousands of tickets - the refs themselves are the pre-filter. A ref that
no longer exists is silently absent; the bulk also pre-fills the node
window's ticket cache.
#trk.bulk.mark
A visible ref colours itself; where the ref is the node id and thus not
part of the title, the renderer appends it as a small badge behind the
label - the same construction as the description mark. An unmapped column
name marks nothing: it says nothing about the plan.
#trk.write
The other direction. Neither side wins by itself: a difference between the
ticket and the status box is marked, and both directions are offered as