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
+43 -3
View File
@@ -9,10 +9,20 @@
zeigt nur die Nummer. */
import { gateOf, isDone, isRealized } from './model.js';
import { STATUS_BY_CODE } from './parser.js';
/* Das Tracker-Muster (SPEC §11): `US-\d+` (Story) und `T-\d+` (Task). */
export const TICKET_ID_RE = /^(?:US|T)-\d+$/;
/* Zerlegt eine Ref in Typ und nackte Nummer: `US-123` -> {kind:'US', nr:123}.
Das Präfix trägt den Typ (SPEC §11) — daran hängen der Frontend-Pfad
(unten), Taigas getrennte `by_ref`-Endpunkte und damit auch der Proxy-Pfad.
Ungültiges ergibt null; geraten wird nirgends. */
export function refParts(ref){
const m = /^(US|T)-(\d+)$/.exec(ref || '');
return m ? {kind: m[1], nr: m[2]} : null;
}
/* Trägt die Zeile eines Knotens schon eine Ticket-Referenz? Das ist der
Idempotenz-Marker (D91-Nachtrag 2): So ein Knoten wird nicht erneut
angelegt. Die Ref ist entweder die Knoten-ID selbst (erstes `#`-Token,
@@ -29,9 +39,39 @@ export function ticketRefOf(n){
genau dafür schreibt Werkbaum es (SPEC §11). Ohne Web-Basis, Projekt-Slug
oder gültige Ref gibt es keine Adresse (null). */
export function ticketUrl(web, slug, ref){
const m = /^(US|T)-(\d+)$/.exec(ref || '');
if(!m || !web || !slug) return null;
return web + '/project/' + slug + '/' + (m[1] === 'US' ? 'us' : 'task') + '/' + m[2];
const p = refParts(ref);
if(!p || !web || !slug) return null;
return web + '/project/' + slug + '/' + (p.kind === 'US' ? 'us' : 'task') + '/' + p.nr;
}
/* Der Pfad am Backend-Proxy zum LESEN eines Tickets (D91-Nachtrag 6),
relativ zu `/api/v1/taiga`: zwei benannte Endpunkte statt eines mit
Typ-Parameter, weil Taiga getrennte `by_ref`-Endpunkte hat. Der Slug
kommt als Query dazu (eine Ref ist nur je Projekt eindeutig). */
export function ticketApiPath(ref, slug){
const p = refParts(ref);
if(!p || !slug) return null;
return '/' + (p.kind === 'US' ? 'userstories' : 'tasks') + '/' + p.nr +
'?slug=' + encodeURIComponent(slug);
}
/* Taiga-Workflow -> Statusbox der Notation (SPEC §4/§9, D91-Nachtrag 6).
Die Vorgabe steht in backend/CLAUDE.md; abgebildet wird im **Editor**, denn
die Statuscodes sind Notations-Vokabular und das Backend parst die Notation
nicht (D14). Groß-/Kleinschreibung und Leerraum sind egal; ein Name
außerhalb der Liste bleibt unabgebildet (null) — geraten wird nicht, und
Taigas Workflows sind je Projekt frei benennbar. */
export const TAIGA_STATUS_CODE = {
'new': ' ',
'in progress': '~',
'ready for test': '/',
'done': 'x',
'archived': '^',
};
export function mapTaigaStatus(name){
if(typeof name !== 'string') return null;
const code = TAIGA_STATUS_CODE[name.trim().toLowerCase().replace(/\s+/g, ' ')];
return code ? STATUS_BY_CODE[code] : null;
}
/* Die Ticket-Referenz unter der Schreibmarke (Strg+Klick im Text,