Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2547b9022 | ||
|
|
fd633983b8 | ||
|
|
1b5f3fa1b2 | ||
|
|
6ee529576d |
@@ -255,6 +255,10 @@ Blockiert · Erledigt**.
|
|||||||
Fälligkeitsdatum und Labels.
|
Fälligkeitsdatum und Labels.
|
||||||
- **Fälligkeiten** färben sich mit: überfällig rot, heute gelb, in den nächsten Tagen
|
- **Fälligkeiten** färben sich mit: überfällig rot, heute gelb, in den nächsten Tagen
|
||||||
gedämpft.
|
gedämpft.
|
||||||
|
- **Hängt ein Git-Issue daran**, trägt die Karte dessen Nummer — vorn in der Fußzeile,
|
||||||
|
vor Beschreibung, Wiederholung, Frist und Labels, damit sich eine Spalte danach
|
||||||
|
überfliegen lässt. Bewusst kein Link: der Weg zum Issue führt über den Dialog (4.6),
|
||||||
|
weil ein `<a>` innerhalb der anklickbaren Karte ungültiges Markup wäre.
|
||||||
- Erledigtes wird durchgestrichen; der Zeitpunkt wird festgehalten und beim Zurückziehen
|
- Erledigtes wird durchgestrichen; der Zeitpunkt wird festgehalten und beim Zurückziehen
|
||||||
wieder gelöscht.
|
wieder gelöscht.
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -7,7 +7,7 @@ Stück für Stück abarbeiten. Reihenfolge ist ein Vorschlag, keine Festlegung.
|
|||||||
|
|
||||||
Was bereits steht, listet [FEATURES.md](FEATURES.md).
|
Was bereits steht, listet [FEATURES.md](FEATURES.md).
|
||||||
|
|
||||||
**Stand:** v1.10.0. Alle Issues aus dem Dashboard sind abgearbeitet.
|
**Stand:** v1.10.1. Alle Issues aus dem Dashboard sind abgearbeitet.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ Voraussetzung, falls du das Projekt veröffentlichen willst.
|
|||||||
|
|
||||||
## 3 · Aufgefallen beim Bauen
|
## 3 · Aufgefallen beim Bauen
|
||||||
|
|
||||||
Beobachtungen aus der Arbeit an v1.6.0 bis v1.10.0 — festgehalten, damit sie
|
Beobachtungen aus der Arbeit an v1.6.0 bis v1.10.1 — festgehalten, damit sie
|
||||||
nicht verloren gehen. Nichts davon ist dringend.
|
nicht verloren gehen. Nichts davon ist dringend.
|
||||||
|
|
||||||
### 3.1 Anwendungsname bricht in der Kopfleiste ab `S`
|
### 3.1 Anwendungsname bricht in der Kopfleiste ab `S`
|
||||||
@@ -178,6 +178,7 @@ Damit klar ist, was **nicht** kommt und warum:
|
|||||||
| **v1.8.0** Anmeldung und Sitzungen nach OWASP: scrypt-Aufwand, Aufzählbarkeit, Leerlauffrist, Cookie-Härtung | ✓ |
|
| **v1.8.0** Anmeldung und Sitzungen nach OWASP: scrypt-Aufwand, Aufzählbarkeit, Leerlauffrist, Cookie-Härtung | ✓ |
|
||||||
| **v1.9.0** Geschlossene Issues haken ihre Aufgabe auch ohne Webhook ab | ✓ |
|
| **v1.9.0** Geschlossene Issues haken ihre Aufgabe auch ohne Webhook ab | ✓ |
|
||||||
| **v1.10.0** Wochenrückblick, globale Zeitleiste, Suche über Aufgaben | ✓ |
|
| **v1.10.0** Wochenrückblick, globale Zeitleiste, Suche über Aufgaben | ✓ |
|
||||||
|
| **v1.10.1** Issue-Kennzeichen auf der Aufgabenkarte, Token-Rechte Aufruf für Aufruf belegt | ✓ |
|
||||||
|
|
||||||
Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur
|
Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur
|
||||||
beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben
|
beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.2.0",
|
"version": "1.10.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.2.0",
|
"version": "1.10.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.10.0",
|
"version": "1.10.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)",
|
"description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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> = {}): 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(<TaskCard task={task} />);
|
||||||
|
|
||||||
|
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 <button>; ein <a> darin waere ungueltiges
|
||||||
|
// Markup und im Browser nicht verlaesslich anklickbar.
|
||||||
|
const html = markup(aufgabe({ sourceIssue: 42, sourceUrl: "https://example.com/issues/42" }));
|
||||||
|
expect(html).not.toContain("<a ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("nennt die Verknuepfung auch fuer Vorlesesoftware", () => {
|
||||||
|
// "#42" allein ergibt vorgelesen keinen Sinn.
|
||||||
|
const html = markup(aufgabe({ sourceIssue: 42 }));
|
||||||
|
expect(html).toMatch(/sr-only[^>]*>Verkn/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AlignLeft, CalendarClock, GripVertical, Repeat } from "lucide-react";
|
import {
|
||||||
|
AlignLeft,
|
||||||
|
CalendarClock,
|
||||||
|
GitPullRequestArrow,
|
||||||
|
GripVertical,
|
||||||
|
Repeat,
|
||||||
|
} from "lucide-react";
|
||||||
import type { Task } from "@prisma/client";
|
import type { Task } from "@prisma/client";
|
||||||
import { RECURRENCE_META } from "@/lib/tasks";
|
import { RECURRENCE_META } from "@/lib/tasks";
|
||||||
import { cn, formatDate } from "@/lib/utils";
|
import { cn, formatDate } from "@/lib/utils";
|
||||||
@@ -70,8 +76,26 @@ export function TaskCard({
|
|||||||
{task.title}
|
{task.title}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{(task.description || task.dueDate || task.recurrence || task.labels.length > 0) && (
|
{(task.description ||
|
||||||
|
task.dueDate ||
|
||||||
|
task.recurrence ||
|
||||||
|
task.sourceIssue ||
|
||||||
|
task.labels.length > 0) && (
|
||||||
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-[10px]">
|
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-[10px]">
|
||||||
|
{/* Steht vorn, damit sich eine Spalte danach ueberfliegen laesst.
|
||||||
|
Bewusst kein Link: die Karte steckt in einem <button>, und ein
|
||||||
|
<a> darin waere ungueltig. Der Weg zum Issue fuehrt ueber den
|
||||||
|
Bearbeiten-Dialog, der ihn ohnehin anbietet. */}
|
||||||
|
{task.sourceIssue ? (
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center gap-1 text-violet-300"
|
||||||
|
title={`Verknüpft mit Issue #${task.sourceIssue}`}
|
||||||
|
>
|
||||||
|
<GitPullRequestArrow className="h-2.5 w-2.5" aria-hidden />
|
||||||
|
<span className="sr-only">Verknüpft mit </span>#{task.sourceIssue}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{task.description ? (
|
{task.description ? (
|
||||||
<AlignLeft className="h-2.5 w-2.5 text-slate-600" aria-label="hat Beschreibung" />
|
<AlignLeft className="h-2.5 w-2.5 text-slate-600" aria-label="hat Beschreibung" />
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -41,6 +41,37 @@ export const KIND_META: Record<ChangeKind, { label: string; className: string }>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: Release[] = [
|
export const CHANGELOG: Release[] = [
|
||||||
|
{
|
||||||
|
version: "1.10.1",
|
||||||
|
date: "2026-08-18",
|
||||||
|
headline: "Sichtbar, welche Aufgabe ein Issue hat — und nachlesbar, was ein Token dafür braucht.",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
kind: "neu",
|
||||||
|
text:
|
||||||
|
"Auf dem Aufgabenbrett eines Projekts trägt eine Karte die Nummer ihres " +
|
||||||
|
"verknüpften Git-Issues. Bisher stand die Verknüpfung nur im Bearbeiten-Dialog " +
|
||||||
|
"und in der projektübergreifenden Übersicht — welche Aufgabe schon ein Issue " +
|
||||||
|
"hat, war im Vorbeigehen nicht zu sehen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "besser",
|
||||||
|
text:
|
||||||
|
"Die Rechte einer Git-Verbindung stehen jetzt Aufruf für Aufruf in der README: " +
|
||||||
|
"je System eine Tabelle aus Endpunkt, Zweck und benötigtem Recht. Getrennt nach " +
|
||||||
|
"reinem Abgleich und zusätzlicher Issue-Anbindung, damit erkennbar ist, was sich " +
|
||||||
|
"weglassen lässt. Bei GitLab ist belegt, dass es unterhalb von „api“ keinen " +
|
||||||
|
"Schreibzugriff auf Issues gibt.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "behoben",
|
||||||
|
text:
|
||||||
|
"Der Hinweis unter dem Token-Feld nannte die Rechte für Actions nicht, obwohl " +
|
||||||
|
"der CI-Zustand bei GitHub genau daran hängt. Wer sich daran hielt, sah keinen " +
|
||||||
|
"Build-Status.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "1.10.0",
|
version: "1.10.0",
|
||||||
date: "2026-08-18",
|
date: "2026-08-18",
|
||||||
|
|||||||
Reference in New Issue
Block a user