diff --git a/src/components/tasks/TaskCard.test.tsx b/src/components/tasks/TaskCard.test.tsx
new file mode 100644
index 0000000..b8f39a7
--- /dev/null
+++ b/src/components/tasks/TaskCard.test.tsx
@@ -0,0 +1,82 @@
+import { describe, expect, it } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import type { Task } from "@prisma/client";
+import { TaskCard } from "./TaskCard";
+
+/**
+ * Die Karte ist die einzige Stelle im Projekt, an der eine Aufgabe im
+ * Vorbeigehen zu sehen ist – der Bearbeiten-Dialog verlangt einen Klick.
+ * Steht die Verknuepfung nur dort, weiss niemand vorher, welche Aufgabe
+ * ein Issue hat.
+ *
+ * Serverseitig gerendert wie bei Markdown.test.tsx: es geht um das
+ * erzeugte Markup, nicht um Verhalten im Browser.
+ */
+function aufgabe(felder: Partial = {}): Task {
+ return {
+ id: "t1",
+ projectId: "p1",
+ title: "Etwas tun",
+ description: null,
+ status: "TODO",
+ position: 0,
+ dueDate: null,
+ labels: [],
+ recurrence: null,
+ sourceIssue: null,
+ sourceUrl: null,
+ sourceRepositoryId: null,
+ createdAt: new Date("2026-08-18T10:00:00Z"),
+ updatedAt: new Date("2026-08-18T10:00:00Z"),
+ ...felder,
+ } as Task;
+}
+
+const markup = (task: Task) => renderToStaticMarkup();
+
+describe("Kennzeichen fuer ein verknuepftes Issue", () => {
+ it("zeigt die Nummer, sobald eine Aufgabe an einem Issue haengt", () => {
+ const html = markup(aufgabe({ sourceIssue: 42 }));
+ expect(html).toContain("#42");
+ expect(html).toContain("Verkn");
+ });
+
+ it("erscheint auch ohne Beschreibung, Datum und Labels", () => {
+ // Der eigentliche Fallstrick: die Fusszeile der Karte wird nur
+ // gerendert, wenn es etwas zu zeigen gibt. Faellt die Verknuepfung aus
+ // dieser Bedingung heraus, bleibt genau die nackte Aufgabe unbeschriftet
+ // – und das ist der haeufige Fall bei einer aus einem Issue erzeugten.
+ const html = markup(
+ aufgabe({ sourceIssue: 7, description: null, dueDate: null, labels: [], recurrence: null }),
+ );
+ expect(html).toContain("#7");
+ });
+
+ it("bleibt weg, wenn keine Verknuepfung besteht", () => {
+ const html = markup(aufgabe({ description: "Text" }));
+ expect(html).not.toContain("Verkn");
+ });
+
+ it("bleibt auf einer erledigten Aufgabe sichtbar", () => {
+ // Die erledigte Karte streicht ihren Titel durch; das Kennzeichen bleibt
+ // in voller Helligkeit. Abzudunkeln war versucht und wieder verworfen –
+ // axe-core rechnete es als Kontrastverstoss ab (10px Text, 3,2:1).
+ // Dass hier ein Issue mitgeschlossen wurde, ist ohnehin gerade dann die
+ // interessante Auskunft.
+ const html = markup(aufgabe({ sourceIssue: 5, status: "DONE" }));
+ expect(html).toContain("#5");
+ });
+
+ it("baut keinen Link in die Karte", () => {
+ // Die Karte steckt in einem