Commit Graph
9 Commits
Author SHA1 Message Date
Harlekin e0b03ea336 Gedankenstriche durch Bindestriche ersetzt
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00
Harlekin 18c9a71fda Export ohne Vier-Augen, Wiedervorlage setzbar, Druckansicht mit Weg zurück
**Export.** Das Vier-Augen-Verfahren ab Stufe 3 ist weg. Wer exportiert,
entscheidet jetzt allein die Freigabestufe: mindestens die des Ziels. Das ist
strenger als das Lesen - wer als Beteiligter ausnahmsweise in eine höher
eingestufte Akte darf, darf sie deshalb noch nicht ausser Haus geben.

Dabei fiel eine Lücke auf: hängte man sich an einen laufenden Auftrag,
entstand kein Protokolleintrag. „Jeder Export wird protokolliert" heisst jeder
Abruf, nicht jeder Rendervorgang - wer dieselbe Datei bekommt, muss genauso
darin stehen.

**Fristen.** dueAt wurde überall gelesen - Übersicht, Suchfilter, Ausdruck -
und nirgends gesetzt. Es gab keine Stelle, an der man ein Datum einträgt,
deshalb stand unter „Fristen" immer „Nichts steht an". Jetzt im Kopf der Akte,
mit Protokolleintrag und ohne Verlaufseintrag: eine Wiedervorlage ist eine
Merkhilfe, keine Aussage über den Fall.

**Statuswechsel.** Aus einem Entwurf führt laut Lebenszyklus nur „Offen"
heraus, deshalb standen dort nie mehr als zwei Ziele. Das sah aus wie ein
Fehler und war keiner - jetzt steht die Erklärung daneben und an jeder Option.

**Druckansicht.** Sie hatte kein eigenes Layout: das weisse Blatt lag auf dem
dunklen Thema und es gab keinen Weg zurück. Jetzt heller Grund, eine Leiste
mit Zurück und Drucken, beides beim Drucken ausgeblendet. Das Ziel wird aus
dem Pfad abgeleitet, damit es auch beim direkten Aufruf stimmt.

**Abteilungsstatistik.** Eine Abteilung ohne Freigabe wird als „Ohne Freigabe"
geführt statt weggelassen - sonst summierten sich die Anteile nicht auf die
Zahl daneben. Mehrere ergeben ein Stück, sonst stünde dort, wie viele es sind.

Dazu: Dienstgrad in eckigen Klammern vor dem Namen und in dessen Grösse, in
einer eigenen Spalte; Knopfform für „Personalakte anlegen"; die abgeschnittene
Anzahl im Kopf der Personalaktenliste; „Zuletzt geöffnet" ohne die zwei
Hinweistexte, leer als Zeichen statt als Satz.
2026-09-04 00:14:07 +02:00
Harlekin 738330cee5 Vollständiger Statusfilter, Beteiligte beim Anlegen, Knöpfe in den Listen
Vier Sachen aus der Durchsicht.

**Der Statusfilter zeigte fünf von sieben Zuständen.** DRAFT und SEALED
fehlten - ausgerechnet die beiden, die in der Liste am häufigsten standen; man
konnte nach dem, was man sah, gerade nicht filtern. Das erklärt auch die Frage,
was „In Prüfung" und „Ruhend" bedeuten: die Zustände standen nirgends in der
Oberfläche. Jetzt alle sieben in der Reihenfolge des Lebenszyklus, mit einer
Erklärung als title, und Versiegelt nur für den, der Versiegeltes lesen darf.
Ein Test verlangt zu jedem Zustand einen Namen und eine Erklärung.

**Beliebig viele Beteiligte beim Anlegen.** Das Formular kannte genau eine
„betroffene Person", und die Rolle war auf SUBJECT festgelegt. Jetzt beliebig
viele Zeilen mit je einer Rolle, Konten und Personalakten in einer Auswahl, wie
auf der Aktenseite. Die Zerlegung steht als parseParticipantDrafts in
lib/cases/terms.ts und nicht in der Server Action: seit die Rolle über den
Zugriff entscheidet, darf eine erfundene Rolle nicht durchrutschen.

**Die Filterleisten sind Knöpfe** wie überall sonst, mit gefülltem aktivem
Filter.

**Keine leere Kennungsspalte mehr.** In „Zuletzt geöffnet" stand bei einer
Person nichts, wo sonst das Aktenzeichen steht - jetzt steht dort
„Personalakte". Nebenbei zwei Stellen zurückgenommen, an denen die
Spaltenklasse im Seitenkopf gelandet war statt in einer Zeile.
2026-09-03 23:38:56 +02:00
Harlekin 9c24858d75 Feste Spaltenbreiten für jede Listenzeile
„Keine Textzentrierung" - die Beobachtung stimmte an einundzwanzig Stellen, und
die Ursache war überall dieselbe: die Zeilen waren Flex-Behälter, in denen
jedes Element so breit war wie sein Inhalt. Damit fing der Titel in jeder Zeile
woanders an, und die Stempelmarken hatten nur rechts eine gemeinsame Kante. Bei
„ETHIK" und „WS" untereinander sieht man es sofort.

Die Breiten stehen jetzt einmal in globals.css - record-key, record-label,
record-title, record-meta, stamp-mark - und sind über Variablen je Liste
anpassbar: eine Liste von Abteilungskürzeln braucht keine vierzehn Zeichen,
eine Liste von Aktenzeichen schon.

Zwei Dinge, die dabei mehr sind als Kosmetik:

Eine Spalte wird auch reserviert, wenn sie leer ist. Vorher hing der Dienstgrad
an einer Bedingung, und bei einer Person ohne Rang rutschte alles Folgende nach
links.

Was keine Kennung ist, gehört nicht in die Kennungsspalte. In „Zuletzt
geöffnet" stand der Name einer Person dort, wo sonst das Aktenzeichen steht: in
Mono, abgeschnitten, und der Dienstgrad wanderte an die Stelle des Titels. Für
eine Person ist der Name der Titel.

Die Stempelmarke ist überall gleich breit; ein zu langer Name wird gekürzt, die
Stufenziffer bleibt stehen - sie ist die eigentliche Auskunft. Unter 40 rem
fallen die festen Breiten weg, sonst liefen sie über.

Nachgesehen im Browser mit dem gebauten CSS, Fallakten, Abteilungen, Zuletzt
geöffnet und Beteiligte.
2026-09-03 22:53:07 +02:00
Harlekin 6af1e437e7 Runde Bedienelemente, Admin zusammengelegt, Kanäle entfallen
Vier der acht Änderungswünsche.

**Knöpfe.** Rund vierzig der siebzig Knöpfe waren blosser Text in Akzentfarbe,
von einem Link nicht zu unterscheiden. Jeder trägt jetzt Rahmen und
Grundfläche, über die Klasse .btn an einer Stelle definiert statt siebzigmal
als Klassenkette. Beim Drücken zieht der Knopf sich zusammen und bekommt einen
inneren Ring - er wirkt kurz dicker, ohne dass die Rahmenbreite sich ändert und
das Umfeld springt; die Rückmeldung setzt ohne Verzögerung ein und klingt
langsam ab, sonst bliebe ein kurzer Klick unsichtbar.

Dazu die neue Regel: Flächen bleiben eckig, alles Bedienbare ist rund. Damit
ist die Rundung eine Auskunft und keine Verzierung.

**Verwaltung.** Rollen, Vorlagen und Änderungsrichtlinie waren drei Einträge in
der Kopfzeile für etwas, das man selten und dann zusammenhängend braucht. Jetzt
ein Eintrag /admin mit Reiterleiste, dazu eine gemeinsame Tabs-Komponente, die
auch der Fallakte zeigt, welcher Reiter offen ist.

**Kanäle.** Ersatzlos entfernt, mitsamt Tabelle. Der Chat gehört dorthin, wo
der Vorgang steht; ein eigener Reiter mit freien Räumen war der Rückweg nach
Discord. Die Personalakte hat jetzt denselben Chat wie die Fallakte - ohne
„als Vermerk übernehmen", denn dafür braucht es einen Verlauf, und Meldungen
gehen nur an Erwähnte, nie in den allgemeinen Kanal: dass es zu jemandem eine
Personalakte gibt, ist selbst eine Auskunft. Ein CHECK erzwingt jetzt, dass
eine Nachricht zu genau einem Ort gehört.

**Kopfzeile.** Suchbalken statt Suchlink, als gewöhnliches Formular ohne ein
Byte im Bündel. Thema, Benachrichtigungen und Abmelden sitzen hinter dem
Profilbild, das von Discord kommt; wer keins hat, bekommt seine Initialen.

Nebenbei: Aktenzeichen in Tests waren nur auf vier Stellen eindeutig und
kollidierten zwischen Läufen, weil Akten nicht löschbar sind.
2026-09-03 15:07:43 +02:00
Harlekin da4a067850 Zuletzt geöffnet als Sprungliste, ohne Verlauf
Die letzte offene Zeile aus TASKS.md. Sie blieb offen, weil eine Spur über
jeden Aktenaufruf schnell etwas anderes wird als eine Bequemlichkeit.

Vier Festlegungen halten sie auf der richtigen Seite:

  - Kein Verlauf. Der Primärschlüssel (userId, targetType, targetId) lässt je
    Nutzer und Datensatz genau eine Zeile zu, die überschrieben wird. Die
    Tabelle kann nicht sagen, wie oft oder in welcher Reihenfolge gearbeitet
    wurde. Zwanzig Einträge, älteres fällt hinten heraus.
  - Nur die eigene. Keine Ansicht, kein Adminbereich, keine Funktion mit
    fremder Kennung. Jede Abfrage nimmt p.userId.
  - Jederzeit löschbar, ohne Rückfrage und ohne Grabstein.
  - Vorabrufe zählen nicht. Ohne die Prüfung auf next-router-prefetch stünde
    dort, worüber jemand mit der Maus gefahren ist.

Gelesen wird über dieselben Sichtbarkeitsfragmente wie überall: eine
vorgemerkte Kennung ist keine Berechtigung. Steigt eine Akte über die
Freigabestufe, verschwindet sie ohne Lücke und ohne Platzhalter; Personalakten
brauchen zusätzlich personnel.read. Das Zugriffsprotokoll bleibt unberührt und
bleibt die einzige Auskunft darüber, wer wann was gelesen hat.

Sie steht auf der Übersicht und im leeren Feld der Kommandopalette. Der zweite
Ort ist der eigentliche.

Nebenbei: NEXT_DIST_DIR, damit ein Build neben einem laufenden
Entwicklungsserver nicht dasselbe .next-Verzeichnis abräumt.
2026-09-03 10:52:23 +02:00
Harlekin addef20e16 Phase 9 abgeschlossen: Übersicht, Beobachtungsliste, helles Thema, Messung
Die Übersicht beantwortet vier Fragen, die man morgens hat: woran arbeite ich,
was ist fällig, wo hat jemand geschrieben, was hat sich getan. Jede Liste
läuft durch visibleCaseWhere - das ist der Ort, an dem eine unachtsame
Abfrage am ehesten eine Akte verrät. Ein Test prüft jede einzeln gegen eine zu
hoch eingestufte Akte. Der bisherige Inhalt ("Dein Zugang") steht jetzt unter
/zugang.

Beobachten ist eine Merkliste, kein Recht: nur was sichtbar ist, lässt sich
merken, und steigt eine Akte später über die eigene Stufe, verschwindet sie
aus der Liste, ohne dass jemand aufräumt.

Das helle Thema tauscht nur die semantische Ebene; die Grundpalette bleibt.
Die Klassifizierungsfarben sind für hellen Grund eine Spur dunkler, im Farbton
aber dieselben - eine Stufe muss in beiden Themen wiedererkennbar sein. Drei
Zustände, "wie das System" ist der Auslieferungszustand, und ein Skript im
Kopf setzt die Wahl, bevor das erste Mal gemalt wird.

Die Messung misst nicht den Nutzer. web-vitals schickt Zahlen und das
Routenmuster, keine Kennung und keinen konkreten Pfad: wer wann welche Akte
geöffnet hat, steht im Zugriffsprotokoll, und eine zweite Spur daneben wäre
eine Überwachungsfunktion ohne Auftrag. Ausgewertet wird Median und 75.
Perzentil, nicht der Mittelwert.

Mobil: die breiten Tabellen scrollen in sich, statt die Seite breiter zu
machen. Ein e2e-Test hält fest, dass keine Seite waagerecht überläuft.

scripts/restore-test.sh spielt eine Sicherung in eine Wegwerf-Datenbank und
prüft ausdrücklich, dass Regeln und Trigger mitkommen: eine Wiederherstellung
ohne entry_no_delete wäre eine Datenbank ohne die Zusicherung, um derentwillen
das System gebaut wird. Einmal durchgespielt, alles angekommen.

186 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 54 e2e.
Grösste Route 116,2 von 120 kB.
2026-09-03 10:28:50 +02:00
Harlekin f1106a57d6 Phase 8 abgeschlossen: Export als PDF, Wasserzeichen, Vier-Augen-Freigabe
Die Druckansicht ist die einzige Vorlage für den Export: kein zweites Layout
von Hand in einer PDF-Bibliothek. Der Playwright-Dienst ruft genau diese Seite
auf und bekommt dafür ein signiertes, kurzlebiges Zeichen statt eines
Sitzungscookies. Es gilt nur, solange der Auftrag läuft, ist an den Anfragenden
gebunden, und die Stufe wird beim Einlösen frisch aufgelöst und auf die im
Zeichen vermerkte gedeckelt.

Der Export darf nie sauberer aussehen als die Akte: gesperrte Felder erscheinen
als Balken mit Vermerk statt zu fehlen, geänderte Einträge tragen ihre Version,
zurückgezogene stehen als Grabstein. Wasserzeichen mit Name, Zeitstempel und
Stufe auf jeder Seite.

Die Stufe steckt im Zwischenspeicherschlüssel. Ohne sie bekäme ein Leser der
Stufe 1 das PDF, das für Stufe 4 gerendert wurde.

Ab Stufe 3 wartet der Auftrag auf Freigabe durch ein zweites Augenpaar - und
der Anfragende darf sich nicht selbst freigeben, auch nicht mit dem Recht
dazu. Offene Freigaben stehen auf der Übersicht.

Dazu Einzeldokumente als PDF (Aktenblatt bleibt Aktenblatt), ZIP-Sammelexport
mit eigenem achtzigzeiligem Schreiber statt einer Abhängigkeit, und der
JSON-Vollabzug ohne Stufenfilterung - die einzige Stelle, an der die
Klassifizierung nicht greift, deshalb hinter einem eigenen Admin-Endpunkt.

Zwei Funde unterwegs, beide in docs/13 festgehalten:

Die Druckansicht warf notFound(), und das Skelett aus dem Wurzel-loading.tsx
blieb im Browser stehen - der PDF-Dienst hätte leere Blätter gedruckt.
Gefunden nur, weil der e2e-Test auf den Inhalt prüft und nicht auf den
Statuscode. Sie rendert die Auskunft jetzt selbst.

Und die Entdopplung der Anhänge verwies auf Dateien, ohne nachzusehen, ob sie
noch daliegen. Nach einem geleerten /tmp zeigte jeder weitere Upload desselben
Inhalts stumm ins Leere.

178 Tests im Web-Paket, 17 in der Datenbank, 7 im Worker, 44 e2e.
Fallakte bei 119,8 von 120 kB - das Budget ist aufgebraucht.
2026-09-03 10:01:57 +02:00
Harlekin c6aefb310c Phase 1: Anmeldung, Rechte und Rollenverwaltung
Auth.js übernimmt nur den OAuth-Ablauf mit state und PKCE, Scope identify. Das
Zugangstoken des Nutzers wird danach verworfen: die Rollen holt der Bot mit
seinem eigenen Token. Die Sitzung steht in der Datenbank, im Cookie liegt nur
userId und sessionId. Ohne die Zeile in Session ist das Cookie wertlos, und nur
so lässt sich eine Sitzung zurückziehen.

Wer sich anmelden darf, steckt in lib/auth/signInPolicy.ts statt im Callback:
das ist die Regel, an der alles hängt, und sie gehört einzeln prüfbar. Kein
Guild-Mitglied, kein Zugang. Keine Rolle mit grantsMembership, kein Zugang.
Notfall-Admins umgehen die Rolle, aber nicht die Guild-Mitgliedschaft.

RoleMapping trägt jetzt grantsMembership, category und isPresent. Der Worker
spiegelt jede Rolle der Guild hierher, damit im Adminbereich nichts von Hand
eingetragen werden muss. Gelöschte Rollen werden nicht entfernt, sondern auf
isPresent = false gesetzt: beim Umzug auf den echten Server bleiben sie als
Vorlage stehen.

Der Worker gleicht über Gateway-Ereignisse und zusätzlich alle fünf Minuten
voll ab. Verliert jemand seine AfiS-Rolle, beendet er dessen Sitzungen sofort
und setzt isActive auf false - der Datensatz bleibt, damit Autorenangaben in
alten Akten gültig bleiben. Das passiert im Worker und nicht in der Web-App,
weil es dort erst beim nächsten Request des Betroffenen griffe.

Zwei Widersprüche der Dokumente aufgelöst, festgehalten in
docs/13-entscheidungen.md: der Ausschluss schlägt auch den Admin (das
Codebeispiel in 03-auth-und-rechte.md übergeht ihn, die Reihenfolge eine Zeile
darüber nicht), und versiegelte Akten fallen im Where-Fragment heraus statt in
einer Prüfung danach.

45 Tests, davon die Negativfälle aus docs/03 gegen eine echte Datenbank: Stufe 2
sieht die Stufe-4-Akte nicht und findet sie auch nicht in der Trefferzahl, ein
Ausgeschlossener mit Stufe 5 kommt nicht heran, ein Rollenentzug beendet die
Sitzungen, und ein Ausfall der Discord-API sieht nicht aus wie eine verweigerte
Mitgliedschaft.
2026-09-01 19:11:53 +02:00