Files
nebula/FEATURES.md
T
florian.wildeandClaude Opus 5 5253b0a317
CI / pruefen (push) Has been cancelled
test: mehrere Repositories abdecken, Doku, Version 1.0.0
Drei neue Ende-zu-Ende-Tests: das Verwalten mehrerer Repositories samt
Datentrennung, das Lesen von Exportdateien der Version 1, und die
Uebernahme ohne verknuepftes Repository.

Dabei fiel auf, dass der Uebernahme-Endpunkt ohne Repository mit 404
antwortete, obwohl gar keine Repository-ID angefragt war. Wer nichts angibt,
hat schlicht noch keines verknuepft - das ist kein "nicht gefunden", sondern
409 mit dem Hinweis, eines zu hinterlegen.

Version 1.0.0: der Funktionsumfang ist beisammen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:24:08 +02:00

31 KiB
Raw Blame History

Funktionsübersicht

Vollständige Auflistung dessen, was Nebula kann — gegliedert nach Bereichen. Für Installation und Betrieb siehe README.md.


0. Konten und Zugang

0.1 Betriebsarten

Beim ersten Aufruf führt /setup durch die Einrichtung des Administratorkontos. Dort — und später jederzeit unter Admin-Einstellungen — wird die Betriebsart gewählt:

Modus Verhalten
Einzelbetrieb Genau ein Konto. Keine Registrierung, keine Benutzerverwaltung.
Mehrbenutzerbetrieb Beliebig viele Konten mit strikt getrennten Daten. Selbstregistrierung optional zuschaltbar.

Der Wechsel in den Einzelbetrieb wird abgelehnt, solange mehr als ein Konto existiert — sonst würden Konten ausgesperrt. Nach der Einrichtung ist /setup dauerhaft gesperrt.

0.2 Anmeldung

  • Benutzername und Passwort. Mindestens 10 Zeichen, offensichtliche Passwörter werden abgelehnt. Hashing mit scrypt.
  • Passkey (WebAuthn). Fingerabdruck, Gesichtserkennung oder Sicherheitsschlüssel. Die Anmeldung läuft über auffindbare Credentials, ein Benutzername ist also nicht nötig. Mehrere Passkeys pro Konto, benennbar und einzeln entfernbar.
  • Zwei-Faktor per TOTP. Zeitbasiertes Einmalkennwort zusätzlich zum Passwort, für jede gängige Authenticator-App. Einrichtung über QR-Code oder abgetippten Schlüssel; scharf wird er erst nach einem bestätigten Code, damit sich niemand aussperrt, der den Scan nicht hinbekommt. Dazu zehn Wiederherstellungscodes, jeder genau einmal gültig — sie werden nur bei der Ausgabe angezeigt. Abschalten und Neuerzeugen verlangen das Passwort, nicht bloß eine offene Sitzung.
  • Die Passkey-Anmeldung fragt nicht nach dem zweiten Faktor: der Passkey ist bereits an ein Gerät gebunden und damit selbst der zweite Faktor.
  • Ein Konto kann ausschließlich per Passkey nutzbar sein. Der letzte Passkey lässt sich dann nicht entfernen, solange kein Passwort gesetzt ist.
  • Fehlversuche werden gezählt; nach acht sperrt sich das Konto für 15 Minuten.
  • Die Fehlermeldung ist immer dieselbe und die Antwortzeit gleich lang, egal ob es den Benutzernamen gibt — so lässt sich nicht ausprobieren, welche Konten existieren.

0.3 Mein Konto

Anzeigename und E-Mail ändern · Passwort setzen oder wechseln · Zwei-Faktor einrichten und abschalten · Passkeys verwalten · aktive Sitzungen einsehen (Browser, Betriebssystem, IP, letzte Aktivität) · überall abmelden, wobei das eigene Gerät angemeldet bleibt.

0.4 Benutzerverwaltung (Administratoren)

  • Konten anlegen, umbenennen, Rolle wechseln, deaktivieren, löschen
  • Passwort eines Kontos neu setzen — beendet dessen Sitzungen sofort
  • Übersicht je Konto: Projektzahl, Passkeys, letzte Anmeldung, Sperrstatus
  • Selbstregistrierung an- und abschaltbar
  • Schutzregeln: Der letzte aktive Administrator kann weder herabgestuft, deaktiviert noch gelöscht werden; das eigene Konto lässt sich nicht löschen oder deaktivieren.
  • Administratoren sehen keine fremden Projekte. Sie verwalten Konten, nicht Inhalte.

1. Projekte

1.1 Anlegen und Verwalten

  • Idee genügt. Ein Projekt braucht nur einen Namen. Alles Weitere — Beschreibung, Repository, Tags — lässt sich jederzeit nachtragen. Neue Projekte starten deshalb standardmäßig im Status Idee.
  • Bearbeiten über einen Dialog, der auf dem Dashboard und der Projektseite identisch funktioniert.
  • Löschen mit Rückfrage; zugehörige Notizen und der Repo-Snapshot verschwinden mit (Fremdschlüssel mit ON DELETE CASCADE).
  • Slug wird automatisch aus dem Namen erzeugt (umlautfest) und bei Namenskollision durchnummeriert.

1.2 Felder

Feld Beschreibung
Name Pflichtfeld
Kurzbeschreibung Ein Satz für die Karte im Board
Beschreibung Ausführliche Ideenskizze, Zeilenumbrüche bleiben erhalten
Status Idee · In Planung · Offen · In Entwicklung · Fertig · Archiviert
Priorität Niedrig · Normal · Hoch · Kritisch
Fortschritt 0–100 %, per Regler
Akzentfarbe Sechs Farbverläufe, prägen Karte und Detailseite
Tags Kommagetrennt, bis zu 12 Stück, kleingeschrieben und dedupliziert
Favorit Anpinnen — Favoriten stehen im Board immer oben
Git-Verbindung Optionale Zuordnung zu einem Git-System
Repository URL sowie Owner/Name (werden aus der URL abgeleitet)

1.3 Status

Sechs Zustände, jeder mit eigener Farbe und Erklärung im Auswahlmenü:

Status Bedeutung
Idee Noch ein Gedanke — nichts gebaut, nichts entschieden
In Planung Scope, Stack und Schnitt werden festgelegt
Offen Startklar, liegt aber gerade still
In Entwicklung Wird aktiv gebaut
Fertig Ausgeliefert und abgeschlossen
Archiviert Ruht — bleibt erhalten, ist im Board aber ausgeblendet

Der Status ist direkt auf der Projektkarte umschaltbar, ohne den Detailbereich zu öffnen. Die Änderung wird optimistisch angezeigt und bei einem Fehler zurückgerollt.


2. Dashboard

  • Kennzahlen oben: Anzahl Projekte mit Ø-Fortschritt, offene Ideen, aktive Entwicklungen, fertige Projekte, verknüpfte Repositories.
  • Volltextsuche über Name, Kurzbeschreibung, Beschreibung, Tags und Repo-Pfad.
  • Statusfilter als Chips mit Trefferzähler, mehrere gleichzeitig kombinierbar.
  • Sortierung nach zuletzt geändert, Anlagedatum, Name, Fortschritt oder Priorität — Favoriten bleiben stets oben.
  • Drei Ansichten: freies Raster, nach Status gruppierte Abschnitte oder Kanban.
  • Volltextsuche über Notizen. Die Suche im Board arbeitet auf den geladenen Projekten (Name, Kurzbeschreibung, Tags, Repo-Pfad). Notizen liegen dort nicht vor — sie mitzuladen würde die Seite aufblähen. Stattdessen sucht Postgres serverseitig mit seiner deutschen Textsuche, entprellt ab zwei Zeichen. Treffer erscheinen als eigene Liste mit hervorgehobener Fundstelle, die betroffenen Projekte werden zusätzlich eingeblendet. Deutsche Stammformen greifen: „Webhook" findet auch „Webhooks".
  • Archiv ist standardmäßig ausgeblendet und per Schalter einblendbar.
  • Leerzustände mit direktem Einstieg statt einer leeren Fläche.

Schnellsuche

Strg+K (auf dem Mac Cmd+K) öffnet überall eine Palette:

  • Projekte nach Name, Kurzbeschreibung und Tags — ohne Eingabe stehen die letzten gleich bereit
  • Notizen über dieselbe Volltextsuche wie im Board, entprellt ab zwei Zeichen
  • Bereiche: Dashboard, Git-Verbindungen, Mein Konto, Admin-Einstellungen

Bedienung über Tastatur: Pfeile wählen, Enter öffnet, Esc schließt. Die Projektliste wird beim ersten Öffnen geholt und dann behalten — sie bei jedem Tastendruck neu zu laden wäre Verschwendung.

Kanban

Spalten je Status, Karten per Ziehen verschiebbar. Der Wechsel wird sofort angezeigt und erst danach gespeichert; scheitert das Speichern, springt die Karte zurück.

  • Gezogen wird am Griff links, nicht an der ganzen Karte — so bleibt der Titel ein normaler Link.
  • Das Ziehen startet erst nach ein paar Pixeln Bewegung, damit Klicks weiter funktionieren. Auf Touchgeräten nach kurzem Halten.
  • Die Reihenfolge innerhalb einer Spalte folgt der gewählten Sortierung. Frei sortieren ginge nur mit einer gespeicherten Position — bewusst nicht gebaut.

Projektkarte auf einen Blick

Name und Favoritenstern · Kurzbeschreibung · Fortschrittsbalken · Provider-Badge · Default-Branch samt Anzahl weiterer Branches · letzter Tag · Alter des letzten Commits · Anzahl Notizen · Warnung bei Sync-Fehler · Tags · Statusauswahl · Priorität.


2a. Aufgaben je Projekt

Jedes Projekt hat ein eigenes Aufgabenbrett mit vier Spalten: Offen · In Arbeit · Blockiert · Erledigt.

  • Ziehen und Ablegen zwischen den Spalten und innerhalb einer Spalte. Anders als beim Projektbrett ist die Reihenfolge hier bedeutsam, deshalb wird sie gespeichert.
  • Gezogen wird am Griff links, damit ein Klick auf die Karte weiter den Dialog öffnet.
  • Eine Aufgabe braucht nur einen Titel. Optional: Beschreibung (Markdown), Fälligkeitsdatum und Labels.
  • Fälligkeiten färben sich mit: überfällig rot, heute gelb, in den nächsten Tagen gedämpft.
  • Erledigtes wird durchgestrichen; der Zeitpunkt wird festgehalten und beim Zurückziehen wieder gelöscht.

Aus Git-Issues übernehmen

Im Git-Panel, Reiter Issues: ein Klick macht aus den offenen Issues Aufgaben — Titel, Labels und ein Link zurück ins Git-System.

  • Grundlage ist der zuletzt geholte Stand, hier wird nichts nachgeladen. Ein Klick soll nicht ungefragt fremde APIs ansprechen; wer den neuesten Stand will, gleicht vorher ab.
  • Die Issue-Nummer bleibt an der Aufgabe und ist je Projekt eindeutig. Ein zweiter Klick legt deshalb keine Dubletten an, auch wenn die Aufgabe inzwischen verschoben oder umbenannt wurde.
  • Ohne Repo-Stand meldet der Server das mit einem Hinweis statt still nichts zu tun.

Fortschritt aus Aufgaben

Pro Projekt umschaltbar. Ist es an, ergibt sich der Fortschritt aus dem Anteil erledigter Aufgaben und wird bei jeder Änderung neu berechnet — der Regler ist dann gesperrt, statt einen Wert anzubieten, der gleich wieder überschrieben würde.

Auf der Projektkarte im Dashboard steht das Verhältnis als 3/8.

Wie die Reihenfolge gespeichert wird

Beim Ablegen schickt die Oberfläche je Spalte die vollständige Liste der Aufgaben in ihrer neuen Reihenfolge; der Server vergibt daraus Position und Status. Das ist wiederholbar, kommt ohne Positionsrechnerei im Browser aus, und ein verlorener Aufruf lässt sich einfach erneut senden. IDs, die nicht zum Projekt gehören, werden ignoriert statt verschoben.


3. Notizen

  • Beliebig viele Notizen pro Projekt, mit optionalem Titel und mehrzeiligem Text.
  • Markdown wird dargestellt — Überschriften, Listen, Tabellen, Codeblöcke, Zitate und Links. Aus - [ ] Aufgabe werden echte Checklisten. Gilt auch für die Projekt- beschreibung. Rohes HTML wird beim Rendern entfernt (rehype-sanitize) — der Inhalt stammt vom Benutzer und wäre sonst ein XSS-Loch.
  • Anpinnen — angepinnte Notizen stehen oben und sind farblich hervorgehoben.
  • Bearbeiten und Löschen direkt in der Liste; Löschen fragt nach.
  • Zeitstempel: Anlagedatum immer, „bearbeitet vor …" nur bei echter Änderung.
  • Zeilenumbrüche und Einrückungen bleiben erhalten — Checklisten und Code-Schnipsel überleben das Speichern.
  • Eine neue Notiz aktualisiert die Änderungszeit des Projekts, damit die Sortierung „zuletzt geändert" die tatsächliche Aktivität abbildet.

4. Git-Anbindung

4.0 Mehrere Repositories je Projekt

Ein Vorhaben besteht oft aus mehreren Repositories — Frontend und Backend etwa, oder App und Infrastruktur. Sie gehören trotzdem in eine Karte.

  • Im Git-Panel steht eine Leiste mit allen Repositories des Projekts, sobald es mehr als eines gibt. Jedes hat eigene Kennzahlen, Listen, CI und eigenen Abgleich; „Alle" gleicht sie nacheinander ab — nicht gleichzeitig, denn ein Schwung Anfragen an denselben Anbieter ist genau das, worauf Rate-Limits reagieren.
  • Eine frei wählbare Bezeichnung („Frontend") tritt an die Stelle von owner/name.
  • Das erste Repository ist das führende. Wo nur eines dargestellt werden kann — auf der Karte im Dashboard, im Kanban —, ist es dieses. Die Karte zeigt daneben +2 Repos. Ein Abgleichsfehler an irgendeinem Repository erscheint auf der Karte, sonst bliebe er unbemerkt.
  • Stillstand bemisst sich am jüngsten Commit über alle Repositories: an einem Vorhaben wird gearbeitet, sobald an einem seiner Teile gearbeitet wird.
  • Benachrichtigungen merken sich den gemeldeten Zustand je Repository. Sonst verdeckte ein rotes Frontend die rote Pipeline des Backends. Der Betreff nennt das Repository nur, wenn es nicht ohnehin so heißt wie das Projekt.
  • Übernommene Issues merken sich, aus welchem Repository sie stammen — Frontend und Backend haben beide ein Issue Nummer 5, und das sind zwei Aufgaben.

Der Projektdialog bearbeitet weiterhin nur das führende Repository; weitere verwaltet das Git-Panel. Beim Anlegen eines Projekts entsteht aus den Git-Feldern das erste.

4.1 Unterstützte Systeme

System API Authentifizierung Besonderheit
GitHub REST v3 Authorization: Bearer github.com und GitHub Enterprise (/api/v3)
GitLab API v4 PRIVATE-TOKEN gitlab.com und Self-Hosted, verschachtelte Gruppen
Gitea / Forgejo API v1 Authorization: token beliebige eigene Instanz

Mehrere Verbindungen desselben Typs sind möglich — etwa privates GitHub und Firmen-Gitea nebeneinander.

4.2 Verbindungen verwalten

  • Anlegen mit Anzeigename, System, Basis-URL und Access Token.
  • Verbindungstest auf Knopfdruck: prüft Erreichbarkeit und Token, zeigt den erkannten Benutzer und bei Gitea zusätzlich die Serverversion. Ergebnis und Zeitpunkt werden gespeichert und in der Karte angezeigt.
  • Token-Verwaltung: verschlüsselt gespeichert, in der UI nur als Hinweis (ghp_…9f3a) sichtbar. Beim Bearbeiten bedeutet ein leeres Feld „unverändert lassen".
  • Repository-Auswahl: Nebula listet alle Repos, auf die der Token Zugriff hat, mit Suchfeld. Ein Klick übernimmt URL, Owner, Name und — falls noch leer — Projektname und Kurzbeschreibung.
  • Löschen trennt nur die Verknüpfung; die Projekte selbst bleiben erhalten.

4.3 Was beim Sync geholt wird

Bereich Details
Repository Voller Pfad, Beschreibung, Default-Branch, Sichtbarkeit, Größe, letzte Aktivität
Kennzahlen Stars, Forks, offene Issues, offene Pull-/Merge-Requests
Pull-/Merge-Requests bis zu 50 offene, mit Nummer, Titel, Autor, Alter, Labels, Entwurfskennzeichnung und Link
Issues bis zu 50 offene, gleiche Angaben. Bei GitHub und Gitea werden PRs herausgefiltert, die dort in derselben Liste stecken
CI-Status Letzter Lauf auf dem Default-Branch: erfolgreich, fehlgeschlagen, läuft, wartet oder abgebrochen — mit Name und Link
Branches bis zu 50, mit SHA, Default-Markierung, Schutzstatus und (wo die API es liefert) letztem Commit
Tags bis zu 25, mit SHA, Nachricht und Datum
Commits die letzten 15 zur Anzeige; geholt werden 100 für die Aktivitätskurve
Commit-Aktivität Balkenreihe der letzten Tage, auf Karte und Projektseite
Releases bis zu 10, inklusive Pre-Release- und Draft-Kennzeichnung
Sprachen Verteilung als Balken mit Prozentwerten

Woher der CI-Status kommt:

System Quelle
GitHub actions/runs für den Default-Branch; Lauf-Status und Ergebnis werden zusammengeführt
GitLab pipelines für den Default-Branch
Gitea Kombinierter Commit-Status — deckt Gitea Actions und externe Systeme wie Woodpecker ab, anders als ein reiner Actions-Endpunkt, den ältere Instanzen nicht kennen

Zur Ehrlichkeit der Aktivitätskurve: geholt wird eine Seite mit 100 Commits. Reicht die nicht über den ganzen Zeitraum, wird die Kurve auf den tatsächlich belegten Ausschnitt gekürzt — eine Null zu zeichnen, wo wir schlicht nichts wissen, wäre falsch.

  • Snapshot-Prinzip: Alle Daten werden in der Datenbank abgelegt. Das Dashboard bleibt ohne Netzverbindung oder bei erreichtem Rate-Limit vollständig nutzbar.

4.4 Automatischer Abgleich

Standardmäßig aus — jeder Durchlauf verbraucht fremde API-Kontingente, das soll eine bewusste Entscheidung sein. Einschalten in den Admin-Einstellungen.

Einstellung Bedeutung
Abstand 15 Minuten bis täglich
Projekte je Durchlauf 1–100, Voreinstellung 25
Fertige und archivierte auslassen Dort ändert sich selten noch etwas

Wie der Durchlauf arbeitet:

  • Der Zeitgeber läuft im Serverprozess selbst (instrumentation.ts), prüft minütlich, ob ein Durchlauf fällig ist, und startet ihn. Kein zusätzlicher Container, kein externer Cron. Zwei Durchläufe überschneiden sich nie.
  • Am längsten nicht abgeglichene zuerst. Was nicht in den Durchlauf passt, kommt beim nächsten Mal.
  • Zurückhalten nach Fehlschlägen: Nach jedem Fehlversuch verdoppelt sich der Abstand bis zum nächsten Versuch (gedeckelt beim 16-fachen). Ein Repository mit abgelaufenem Token blockiert damit nicht dauerhaft das Kontingent.
  • Kurze Pause zwischen zwei Projekten, damit kein Schwall entsteht.
  • Pro Projekt abschaltbar über einen Schalter im Projektdialog. Manuell synchronisieren geht unabhängig davon immer.
  • Der Adminbereich zeigt letzten und nächsten Durchlauf sowie einen Kurzbericht (aktualisiert / fehlgeschlagen / zurückgestellt) und hat einen Knopf Jetzt abgleichen.
  • Wer lieber von außen taktet, lässt den Automatismus aus und ruft POST /api/admin/sync/run per eigenem Cron auf.

Der Abgleich läuft über alle Konten hinweg — er ist ein Serverauftrag, keine Dateneinsicht. Ein Administrator sieht dadurch weiterhin keine fremden Projekte.

  • URL-Erkennung für https://…, git@host:owner/repo.git und GitLab-Weboberflächen-URLs mit /-/tree/…; verschachtelte Gruppenpfade bleiben erhalten.
  • Ohne Token funktionieren öffentliche Repositories, mit deutlich strengerem Rate-Limit.
  • Fehler landen am Projekt statt in einer Fehlerseite: die Karte zeigt „Sync-Fehler", die Projektseite die genaue Meldung. Typische HTTP-Codes werden in Klartext übersetzt (401 Token, 403 Scopes/Rate-Limit, 404 Pfad, 408 Timeout).
  • Zeitlimit pro Aufruf über GIT_API_TIMEOUT_MS einstellbar; die Teilabfragen laufen parallel, eine fehlschlagende Liste bricht den restlichen Sync nicht ab.

4a. Export, Import und Sicherung

Projekte mitnehmen

  • JSON-Export einzeln oder für alle Projekte: Stammdaten, Notizen, Tags, Repo-Verknüpfung. Ohne Access Tokens — die Verbindung wird nur beschrieben, das Token muss beim Import neu hinterlegt werden. Auch ohne Snapshots (reproduzierbar) und ohne Datenbank-IDs (gelten nur in der Quellinstanz).

  • Markdown-Export eines Projekts: Beschreibung, Kennzahlen, Notizen und der letzte bekannte Git-Stand als lesbare Datei.

  • Import mit Vorschau. Die Datei wird streng geprüft, bevor irgendetwas passiert. Abgeglichen wird über den Slug; bei Treffern stehen drei Strategien bereit:

    Strategie Wirkung
    Überspringen Vorhandene bleiben unverändert
    Zusätzlich anlegen Kopie mit freiem Slug (nebula → nebula-2)
    Ersetzen Stammdaten überschreiben, Notizen austauschen

    Die Vorschau zeigt je Projekt, was geschähe, und weist auf fehlende Git-Verbindungen hin. Erst ein zweiter Klick schreibt.

Sicherung

  • JSON-Sicherung im Adminbereich: Einstellungen, Konten, Verbindungen, Projekte und Notizen in einer Datei. Passwörter als scrypt-Hash, Git-Tokens als Chiffrat — ohne dasselbe APP_SECRET wertlos. Sitzungen und Passkeys bleiben draußen: erstere sind kurzlebig, letztere hängen an Gerät und rpID.
  • Datenbank-Sicherung über scripts/backup.*: pg_dump im Container, mit --clean --if-exists, also für sich allein einspielbar. Gegenstück ist scripts/restore.*, das vor dem Überschreiben nachfragt und sich über einen Parameter auf eine Wegwerf-Datenbank umlenken lässt — damit sich eine Sicherung prüfen lässt, ohne die echte anzufassen.

4b. Benachrichtigungen

Einzurichten unter Mein Konto → Benachrichtigungen, pro Konto und beliebig viele Ziele.

Anlässe

Anlass Wann
⚠️ Abgleich fehlgeschlagen Das Git-System war nicht erreichbar oder hat abgelehnt
🔴 CI rot Der letzte Lauf auf dem Default-Branch ist fehlgeschlagen
🏷️ Neues Release Ein neuer Tag oder ein neues Release ist aufgetaucht
🕸️ Projekt steht still Seit längerem kein Commit — Schwelle einstellbar, Vorgabe 60 Tage

Nur bei Änderung

Der entscheidende Punkt: Gemeldet wird ausschließlich, wenn sich etwas ändert gegenüber dem, worüber zuletzt benachrichtigt wurde. Ein Repository mit abgelaufenem Token meldet sich also einmal, nicht bei jedem stündlichen Durchlauf. Wird der Fehler ein anderer, kommt eine neue Meldung; ist er behoben und tritt später erneut auf, ebenso.

Zwei Feinheiten:

  • Beim allerersten Abgleich eines Repositories wird kein Release gemeldet — sonst wäre jede bereits vorhandene Version eine „Neuigkeit".
  • Der Merkstand wird auch dann fortgeschrieben, wenn kein Ziel eingerichtet ist. Sonst käme beim ersten eingerichteten Ziel die ganze Vergangenheit hinterher.

Stillstand

Der einzige Anlass, der an keinem Ereignis hängt — es passiert ja gerade nichts. Deshalb ein eigener Durchlauf, höchstens einmal am Tag, unabhängig vom automatischen Abgleich. Hört niemand auf diesen Anlass, läuft die Prüfung gar nicht erst.

Maßstab ist ausschließlich der letzte Commit im verknüpften Repository. Das Änderungsdatum des Projekts taugt nicht: jeder Abgleich fasst es an und würde den Stillstand immer wieder zurücksetzen. Projekte ohne Repository bleiben deshalb außen vor, ebenso fertige und archivierte.

Gemeldet wird je Projekt einmal. Erneut erst, wenn danach wieder ein Commit kam und es seitdem wieder still geworden ist — ein Projekt, das aufwacht und wieder einschläft, meldet sich also ein zweites Mal.

Die Schwelle setzt ein Administrator unter Admin-Einstellungen → Stillstand (14 Tage bis ein Jahr). Eine Änderung wirkt sofort, nicht erst nach 24 Stunden.

Zustellwege

Weg Details
ntfy Vollständige Themen-URL. Titel, Priorität und Symbol als Kopfzeilen; optionales Bearer-Token für geschützte Themen. Läuft auch gegen ein selbst gehostetes ntfy im eigenen Netz.
Webhook JSON per POST mit Anlass, Titel, Text, Projektbezug und Tieflink. Mit hinterlegtem Geheimnis wird der Rumpf per HMAC-SHA256 signiert (X-Nebula-Signature), damit der Empfänger die Herkunft prüfen kann.
E-Mail Über SMTP. Steht erst zur Verfügung, wenn SMTP_HOST in der .env gesetzt ist — sonst ist die Auswahl in der Oberfläche gesperrt und erklärt, warum.

Jedes Ziel lässt sich einzeln testen und pausieren; Ergebnis und Zeitpunkt der letzten Zustellung stehen an der Karte.

Absicherung

  • Zieladressen sind Benutzereingaben, die der Server abruft — sie laufen deshalb durch denselben SSRF-Schutz wie die Git-Verbindungen. Ein ntfy im eigenen Netz bleibt erlaubt, Loopback und Cloud-Metadaten nicht.
  • Redirects werden nicht verfolgt: ein Umleitungsziel wäre nicht geprüft.
  • Geheimnisse liegen AES-256-GCM-verschlüsselt und verlassen den Server nie; die Oberfläche zeigt nur einen Hinweis wie test…mnis.
  • Eine fehlgeschlagene Zustellung lässt den Abgleich nicht scheitern — der Fehler landet am Ziel, nicht im Sync.

5. Oberfläche

  • Dark Glassmorphism: milchige Glasflächen, driftende Nebel im Hintergrund, feines Raster, Neon-Akzente pro Projektfarbe.
  • Animationen: gestaffelt einblendende Karten, Hover-Lift mit Farbverlaufsrand, animierte Fortschrittsbalken.
  • Responsiv von Handy bis Ultrawide (bis zu vier Kartenspalten).
  • Barrierearm: durchgehende ARIA-Labels, Escape schließt Dialoge, sichtbarer Fokusring, prefers-reduced-motion schaltet Animationen ab.
  • Deutschsprachig, Datums- und Zahlenformate in de-DE mit fester Zeitzone Europe/Berlin, damit Server- und Browser-Darstellung übereinstimmen.

6. Daten und Technik

6.1 Datenmodell

Tabelle Inhalt
User Konten, Rolle, Passwort-Hash, Sperrzähler
Session Serverseitige Sitzungen (nur der Token-Hash)
Credential Passkeys inkl. öffentlichem Schlüssel und Signaturzähler
WebAuthnChallenge Kurzlebige, einmalig verwendbare Challenges
RecoveryCode Wiederherstellungscodes für die Zwei-Faktor-Anmeldung (nur der Hash)
PendingLogin Zwischenzustand zwischen Passwort und zweitem Faktor, fünf Minuten gültig
AppSettings Betriebsart und Registrierung (eine Zeile)
Project Projektstammdaten, Status, Fortschritt, Besitzer
Repository Repositories eines Projekts inkl. Abgleichstand (n:1 zu Projekt)
Task Aufgaben mit Spalte, Position, Fälligkeit, Labels und Issue-Herkunft
Activity Verlauf je Projekt, neueste zuerst, auf 200 Einträge begrenzt
NotificationTarget Zustellwege je Konto, mit verschlüsseltem Geheimnis
Note Notizen (n:1 zu Projekt, Cascade-Delete)
GitConnection Git-Systeme inkl. verschlüsseltem Token, Besitzer
RepoSnapshot Zwischengespeicherter Repo-Stand (1:1 zu Repository)

Project.ownerId und GitConnection.ownerId sind bewusst optional: so überleben Daten aus der Zeit vor der Benutzerverwaltung die Migration und werden beim Anlegen des ersten Kontos übernommen. Slug und Verbindungsname sind je Besitzer eindeutig — zwei Personen dürfen beide ein Projekt „dashboard" haben.

Listen wie Branches oder Commits liegen als JSONB, damit providerspezifische Felder nicht verloren gehen. Indizes auf Status, Verbindung und Änderungsdatum.

6.2 Sicherheit

  • Zugangsschutz in zwei Ebenen. Die Middleware prüft nur, ob ein Sitzungscookie überhaupt mitkommt — im Edge-Runtime gibt es keine Datenbank. Verbindlich entschieden wird serverseitig in jeder Seite und jedem Endpunkt (requireUserPage / requireUserApi). Das ist die eigentliche Sicherheitsgrenze.
  • Datentrennung über eine Besitzerbedingung in jeder Abfrage. Schreibzugriffe laufen über updateMany/deleteMany mit ownerId in der where-Klausel, treffen fremde Datensätze also gar nicht erst. Fremde IDs liefern 404 statt 403.
  • CSRF: Origin- bzw. Sec-Fetch-Site-Prüfung für alle schreibenden Methoden, dazu erzwungener Content-Type: application/json.
  • CSP mit Per-Request-Nonce und strict-dynamic, plus frame-ancestors 'none', object-src 'none', base-uri 'none' und die üblichen Schutz-Header.
  • SSRF-Schutz vor jedem ausgehenden Git-Aufruf: Hostname wird aufgelöst, jede Antwortadresse geprüft (schützt auch gegen DNS-Rebinding über einen zweiten A-Record), Redirect-Ziele werden erneut geprüft. Konfigurierbar über GIT_ALLOW_LOOPBACK, GIT_BLOCK_PRIVATE und GIT_ALLOWED_HOSTS.
  • Rate-Limiting für Anmeldung (je IP und je Konto), ausgehende Git-Aufrufe und WebAuthn-Optionen.
  • Access Tokens verschlüsselt mit AES-256-GCM, Schlüssel per scrypt aus APP_SECRET abgeleitet, Chiffrat versioniert (v1.<iv>.<tag>.<data>).
  • Tokens werden nie an den Browser gesendet — alle API-Antworten nutzen eine Feldauswahl ohne tokenCipher.
  • Zod-Validierung jeder Eingabe an der API-Grenze mit Feldfehlern für die UI, Body auf 256 KB begrenzt.
  • Prisma arbeitet ausschließlich mit parametrisierten Abfragen.

6.3 Betrieb

  • Zwei gleichwertige Betriebsarten: Standalone (Node-Prozess, beliebige PostgreSQL) oder komplett in Docker — dieselbe Codebasis, dieselbe .env.
  • Docker-Compose-Profile: db läuft immer, --profile full startet zusätzlich die App, --profile tools bringt Adminer als DB-Oberfläche mit.
  • Automatische Migration beim Containerstart, inklusive Warteschleife auf die Datenbank.
  • Healthchecks: pg_isready für Postgres, /api/health für die App.
  • Setup-Skript erzeugt die .env samt zufälligem APP_SECRET.
  • Seed-Daten auf Wunsch, idempotent — mehrfaches Ausführen legt nichts doppelt an.

6.4 Tests

Zwei Ebenen. npm run test:all führt beide aus.

npm test — Unit-Tests (Vitest, 56 Stück, unter einer Sekunde, ohne DOM und ohne Datenbank):

Bereich Was geprüft wird
utils Slug-Erzeugung inklusive Umlauten und Längenbegrenzung, Tag-Aufbereitung, Kürzen, Zahlenformat
git/parse URL-Zerlegung für https, SSH, verschachtelte GitLab-Gruppen und Oberflächenpfade; Provider-Erkennung; Bildung der Aktivitätskurve
security/ssrf Sperrlisten für Loopback, Link-Local, Metadaten-Endpunkt, CGNAT und Multicast; Formprüfung; die drei Umgebungsschalter
auth/password scrypt-Rundlauf, unterschiedliches Salz, Ablageformat, Verhalten bei kaputten Hashes, Passwortregeln
tasks Berechnung des abgeleiteten Fortschritts, Spaltenreihenfolge
notify/pending Wann eine Benachrichtigung fällig ist: neuer Fehler ja, gleicher Fehler nein; CI-Wechsel nach rot; Release erst ab dem zweiten Abgleich
notify/stale Stillstandsschwelle, keine Zweitmeldung, aber erneute Meldung nach zwischenzeitlicher Aktivität
validation Dass jeder Anlass, den die Oberfläche anbietet, auch vom Schema akzeptiert wird — entstanden aus genau diesem Fehler

npm run test:e2e — Ende-zu-Ende (Playwright, 13 Stück, rund 20 Sekunden). Läuft gegen die gebaute Anwendung mit eigener Datenbank auf Port 3111 — also durch Middleware, Authentifizierung, Validierung und Datenbank hindurch:

Datei Was geprüft wird
e2e/api.spec.ts Einrichtung sperrt sich nach dem ersten Konto · kein Zugriff ohne Anmeldung · fremdes Origin und falscher Content-Type abgewiesen · SSRF geblockt · Datentrennung zwischen zwei Konten · Aufgaben-Reihenfolge, abgeleiteter Fortschritt und untergeschobene IDs · Export ohne Geheimnisse, Import je Strategie
e2e/ui.spec.ts Anmelden · Projekt anlegen · Aufgaben anlegen und per Ziehen verschieben (inklusive Prüfung nach dem Neuladen) · Volltextsuche · Abmelden sperrt geschützte Seiten wieder

Zwei Dinge sind bewusst so gebaut:

  • Die Testdatenbank kann nicht die echte sein. e2e/global-setup.ts bricht ab, wenn der Datenbankname nicht test enthält — vor dem TRUNCATE. Der teuerste denkbare Fehler wäre eine Testreihe, die die Produktivdaten leert.
  • Playwright startet den Server selbst (webServer in playwright.config.ts) mit eigener DATABASE_URL und eigenem Port. Eine laufende Instanz auf 3000 stört nicht.

Einmalig vorbereiten:

docker exec nebula-db psql -U nebula -d postgres -c "CREATE DATABASE nebula_e2e_test"

6.5 Fortlaufende Prüfung

.gitea/workflows/ci.yml prüft jeden Push und jeden Pull Request: Typen, Unit-Tests, Bau und Ende-zu-Ende-Tests gegen einen frischen PostgreSQL-Dienst. Schlägt etwas fehl, wird der Playwright-Bericht als Artefakt abgelegt.

Dafür müssen in der Gitea-Instanz Actions aktiviert sein (app.ini: [actions] ENABLED = true) und ein Runner registriert. Die Syntax ist GitHub-kompatibel — für GitHub die Datei nach .github/workflows/ kopieren.

6.6 API

Zehn REST-Endpunkte für Projekte, Notizen, Verbindungen, Sync, Repo-Listen und Health. Alles, was die Oberfläche kann, ist damit auch scriptbar. Übersicht in der README.


7. Ideen für später

Was als Nächstes kommen könnte, steht mit Aufwandsschätzung und offenen Fragen in ROADMAP.md — gedacht zum Durchsprechen und schrittweisen Abarbeiten.