diff --git a/e2e/ui.spec.ts b/e2e/ui.spec.ts
index 011ba29..32c036f 100644
--- a/e2e/ui.spec.ts
+++ b/e2e/ui.spec.ts
@@ -163,6 +163,46 @@ test("Schnellerfassung legt Idee und Aufgabe mit einem Feld an", async ({ page }
await expect(page.getByText("Aus der Bahn notiert")).toBeVisible();
});
+test("Dialoge weichen der Bildschirmtastatur aus", async ({ page }) => {
+ await page.setViewportSize({ width: 375, height: 667 });
+ await page.getByRole("button", { name: "Schnell erfassen" }).click();
+ await expect(page.getByRole("dialog")).toBeVisible();
+
+ const TASTATUR = 336;
+
+ /**
+ * Eine echte Tastatur laesst sich nicht einblenden. Stattdessen meldet
+ * visualViewport eine kleinere Hoehe – genau die Schnittstelle, an der
+ * der Dialog haengt. Ueberschrieben wird am echten Objekt: der Dialog
+ * merkt sich beim Oeffnen eine Referenz darauf.
+ */
+ await page.evaluate((tastatur) => {
+ const fenster = window.visualViewport;
+ if (!fenster) throw new Error("visualViewport fehlt");
+ const hoehe = window.innerHeight - tastatur;
+ Object.defineProperty(fenster, "height", { get: () => hoehe, configurable: true });
+ Object.defineProperty(fenster, "offsetTop", { get: () => 0, configurable: true });
+ fenster.dispatchEvent(new Event("resize"));
+ }, TASTATUR);
+
+ await page.waitForTimeout(300);
+
+ // Eingabefeld und Absendeknopf muessen oberhalb der Tastatur liegen.
+ // Vorher stand der Dialog mittig im vollen Fenster – und damit zur
+ // Haelfte darunter.
+ const frei = 667 - TASTATUR;
+ for (const ziel of [
+ page.locator("#quick-title"),
+ page.getByRole("dialog").getByRole("button", { name: "Anlegen" }),
+ ]) {
+ const kasten = await ziel.boundingBox();
+ expect(kasten).not.toBeNull();
+ expect(kasten!.y + kasten!.height).toBeLessThanOrEqual(frei);
+ }
+
+ await page.keyboard.press("Escape");
+});
+
test("Aufgabenuebersicht sammelt ueber alle Projekte und hakt ab", async ({ page }) => {
await page.getByRole("link", { name: "Aufgaben", exact: true }).click();
await expect(page.getByRole("heading", { name: "Aufgaben", level: 1 })).toBeVisible();
diff --git a/src/app/tasks/page.tsx b/src/app/tasks/page.tsx
index d55f417..d3095db 100644
--- a/src/app/tasks/page.tsx
+++ b/src/app/tasks/page.tsx
@@ -42,6 +42,7 @@ export default async function TasksPage() {
dueDate: task.dueDate ? task.dueDate.toISOString() : null,
labels: task.labels,
recurrence: task.recurrence,
+ sourceIssue: task.sourceIssue,
sourceUrl: task.sourceUrl,
bucket: dueBucket(task.dueDate, jetzt),
project: task.project,
diff --git a/src/components/tasks/TaskDialog.tsx b/src/components/tasks/TaskDialog.tsx
index 00ebbd9..0003de6 100644
--- a/src/components/tasks/TaskDialog.tsx
+++ b/src/components/tasks/TaskDialog.tsx
@@ -2,12 +2,33 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
-import { Loader2, Trash2 } from "lucide-react";
-import type { Recurrence, Task, TaskStatus } from "@prisma/client";
+import { ExternalLink, GitPullRequestArrow, Loader2, Trash2 } from "lucide-react";
+import type { Recurrence, TaskStatus } from "@prisma/client";
import { Modal } from "@/components/ui/Modal";
import { RECURRENCE_META, TASK_STATUS_META, TASK_STATUS_ORDER } from "@/lib/tasks";
import { cn, parseTags } from "@/lib/utils";
+/**
+ * Was der Dialog von einer Aufgabe tatsaechlich liest.
+ *
+ * Bewusst nicht der vollstaendige Prisma-Datensatz: die Aufgabenuebersicht
+ * ueber alle Projekte reicht eine schlankere Fassung durch, in der das Datum
+ * als Zeichenkette ankommt – ein Date ueberlebt den Weg vom Server zum
+ * Browser nicht. Ein echtes Task erfuellt diese Form ebenfalls.
+ */
+export interface EditableTask {
+ id: string;
+ title: string;
+ description: string | null;
+ status: TaskStatus;
+ dueDate: Date | string | null;
+ labels: string[];
+ recurrence: Recurrence | null;
+ /** Gesetzt, sobald die Aufgabe an einem Git-Issue haengt. */
+ sourceIssue?: number | null;
+ sourceUrl?: string | null;
+}
+
/** Datum fuer – ortszeitbezogen, nicht UTC. */
function toDateInput(value: Date | string | null): string {
if (!value) return "";
@@ -26,7 +47,7 @@ export function TaskDialog({
}: {
open: boolean;
projectId: string;
- task: Task | null;
+ task: EditableTask | null;
initialStatus: TaskStatus;
onClose: () => void;
}) {
@@ -38,10 +59,38 @@ export function TaskDialog({
const [labels, setLabels] = useState("");
const [recurrence, setRecurrence] = useState
+