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:
co-authored by
Claude Opus 5
parent
e77a4d1ce4
commit
a1e4a3bbab
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user