feat(tasks): Aufgabe als Git-Issue anlegen, Zustand wandert mit

Die Zuordnung stand im Schema laengst – sourceIssue, sourceUrl und
sourceRepositoryId samt Eindeutigkeit je Repository, gefuellt von der
Uebernahme aus Issues seit v0.9.0. Es fehlte die Gegenrichtung.

Ein Knopf im Aufgabendialog legt das Issue an; Nummer und Adresse landen in
denselben Feldern, der Dublettenschutz gilt also fuer beide Wege. In den
Rumpf kommt ein Rueckverweis auf das Projekt.

Ausdruecklich per Knopf und nie nebenbei: was in ein fremdes System
geschrieben wird, soll jemand gewollt haben. Ist die Aufgabe erst
verknuepft, wandert der Zustand allerdings mit – erledigt schliesst das
Issue, ein Zurueckziehen oeffnet es wieder. Fehlertolerant: ein nicht
erreichbares Git-System darf das Abhaken nicht verhindern.

Die Zustaende passen nicht aufeinander, und das bleibt so. Nebula kennt
vier, ein Issue zwei; nach aussen geht nur die Grenze um ERLEDIGT. Ein
Rundlauf verliert damit IN ARBEIT und BLOCKIERT. Labels waeren die
Alternative, wuerden aber den Issue-Tracker mit Zustaenden fuellen, die
dort niemand pflegt.

Kein Widerspruch zu "keine eigenen Git-Operationen": Issues laufen ueber
die HTTP-API der Provider. Dafuer gibt es jetzt sendJson neben getJson –
durch dieselbe SSRF-Schleuse, aber ohne Umleitungen zu folgen. Ein 307 auf
eine interne Adresse naehme den Rumpf und den Token im Header mit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-15 00:35:11 +02:00
co-authored by Claude Opus 5
parent 6a7365946b
commit e1d435f10a
10 changed files with 553 additions and 3 deletions
+18
View File
@@ -10,6 +10,7 @@ import {
refreshDerivedProgress,
spawnRecurrence,
} from "@/lib/tasks";
import { issueShouldBeOpen, mirrorStatusToIssue } from "@/lib/tasks/issues";
import { formatZodError, taskUpdateSchema } from "@/lib/validation";
export const dynamic = "force-dynamic";
@@ -74,6 +75,23 @@ export async function PATCH(request: Request, { params }: Params) {
await refreshDerivedProgress(existing.projectId);
/**
* Haengt die Aufgabe an einem Issue, wandert der Zustand mit: erledigt
* schliesst es, ein Zurueckziehen oeffnet es wieder. Nur die Grenze um
* ERLEDIGT geht nach draussen – mehr kennt ein Issue nicht.
*
* Fehlertolerant und ohne den Aufrufer warten zu lassen, was schiefgehen
* kann: ein nicht erreichbares Git-System darf das Abhaken nicht
* verhindern. Beim naechsten Wechsel wird es erneut versucht.
*/
if (update.status !== undefined) {
const vorher = issueShouldBeOpen(existing.status);
const nachher = issueShouldBeOpen(update.status as TaskStatus);
if (vorher !== nachher) {
await mirrorStatusToIssue(id, update.status as TaskStatus);
}
}
// Nur der Spaltenwechsel kommt in den Verlauf. Ein umbenannter Titel oder
// ein neues Label sind zu kleinteilig, um die Geschichte des Projekts zu
// erzaehlen – und beim Ziehen im Brett ist der Wechsel die eigentliche Tat.