Commit Graph
7 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 75c22ff4f8 Ladegrenzen für jede Route, Turbopack im Entwicklungsserver
Gemessen statt vermutet: die Datenschicht war nie das Problem. resolvePermissions
unter 1 ms, loadDashboard 11 ms, searchCases 5 ms, die Abteilungsabfragen 3–4 ms.

Die Sekunden kamen aus zwei Ecken.

**Fehlende Ladegrenzen.** Den meisten Routen fehlte loading.tsx - unter anderem
der Übersicht, der Suche und dem gesamten Adminbereich. Ohne diese Datei hat
der Router keine Grenze, wartet stumm auf die fertige Serverantwort, und bis
dahin steht die alte Seite: man klickt, und scheinbar passiert nichts. Neunzehn
Skelette nachgetragen, jede Route hat jetzt eine.

Dazu quittiert der geklickte Link den Klick selbst, mit einem ruhig
pulsierenden Punkt statt eines Kreisels. Regel 5 verbietet den
Blockade-Spinner, der Inhalt ersetzt; eine Quittung im Link ersetzt nichts, und
die eigentliche Antwort auf das Warten bleibt das Skelett.

**Webpack.** Der Entwicklungsserver übersetzte jede Route beim ersten Aufruf
neu: 8 s für die Übersicht, 2,8 s für die Fallliste. Mit Turbopack sind es
0,43 s beim ersten Aufruf und 0,1 s danach. Nur pnpm dev, der Produktionsbau
bleibt unverändert. Gegengeprüft, dass die Anmeldeseite mit CSS und ohne
Fehler im Log rendert.

Nebenbei die eine Abfrage, die schlecht skalierte: die Übersicht zählte
ungelesene Nachrichten mit einer Abfrage je Ort - bei hundert Akten hundert
Hin- und Rückwege. Jetzt ein einziges groupBy mit der Lesemarke im ODER-Zweig
des jeweiligen Ortes. Ein Test prüft, was dabei leicht verrutscht: die Marke
gilt je Ort, und eigene Nachrichten zählen nie mit.
2026-09-03 22:21:43 +02:00
Harlekin d49edeed18 Abstand zur Beschriftung folgt aus der Polsterung des Feldes
„Zwischen der Auswahl und dem Text ist kaum Platz" - und das stimmte überall,
nicht nur an der einen Stelle. Der Grund war messbar: der Abstand zur
Beschriftung betrug 8 px, die Polsterung innerhalb des Feldes 11,2 px. Das Wort
stand also näher am Rahmen als der Inhalt des Rahmens an demselben Rahmen, und
genau daran erkennt das Auge einen zu engen Satz.

Die Regel heisst jetzt: der Abstand nach aussen ist grösser als die Polsterung
nach innen. Sie steht einmal in globals.css als .field-label, .field-row und
.field-hint - vorher war sie ein Dutzend Mal als mt-1, mt-1.5 oder gap-2 an den
Aufrufstellen verteilt, mit drei verschiedenen Werten, von denen keiner
reichte. 33 Stellen umgestellt.

Der Test prüft die Regel und nicht die Zahl: er liest die Polsterung von .field
aus dem CSS und verlangt, dass beide Abstände darüber liegen. Gegengeprobt mit
einem zu kleinen Wert, dann schlägt er an.

Nachgemessen im Browser mit dem gebauten CSS: 8 px senkrecht gegen 5,6 px
Innenpolsterung, 13,6 px waagerecht gegen 11,2 px.
2026-09-03 20:28:42 +02:00
Harlekin aef3df0d44 Abteilungen als Ebene über der Personalakte
Punkt 3 der Wünsche.

Die Abteilung stand als Freitext in der Personalakte. Damit liess sich nichts
fragen: „SHD", „Shd" und „Sicherheitsdienst" waren drei Abteilungen. Sie ist
jetzt ein eigenes Modell, im Adminbereich verwaltbar, und die drei vorhandenen
Freitexte sind in der Migration zu Abteilungen geworden.

**Eine Fallakte trägt keine Abteilung.** Sie gehört zu einer, wenn eine
beteiligte Person ihr angehört - abgeleitet über CaseParticipant und die
Personalakte. Eine eingetragene Zuordnung wäre nach zwei Wochen falsch: Leute
wechseln, Beteiligte kommen dazu, und niemand pflegt das nach. Dasselbe gilt
für die Dokumente: ein Dokument unmittelbar an der Abteilung wäre halb
verdrahtet, weil die Vorlagen über TemplateUse nur Fall- und Personalakte
kennen.

Die Abteilungsseite zeigt Mitglieder, abgeleitete Fälle und Dokumente,
Vermerke, Chat und ein Kreisdiagramm der Fallarten; die Übersicht eines über
alle Abteilungen. Das Diagramm ist serverseitig gerendertes SVG - eine
Bibliothek wäre das Teuerste auf der Seite gewesen. Die Segmente sind
Abstufungen von --steel und nicht die Stufenfarben: ein Tortenstück in Oxidrot
würde als „streng geheim" gelesen.

Die Zusammenfassung ist die gefährliche Stelle. Eine Abteilungsseite fasst
zusammen, was einzeln längst geschützt ist, und eine Zusammenfassung, die nicht
filtert, verrät mehr als jede Einzelansicht. Deshalb läuft hier jede Liste und
jede Zahl durch dieselben Fragmente wie überall; ein Test prüft, dass die Summe
der Diagrammanteile mit der Länge der Liste übereinstimmt. Wo jemand
Personalakten gar nicht sehen darf, steht null und keine Null.

Vermerke erben Regel 2 unverändert: kein DELETE, Version bei jeder Änderung,
Grabstein bei jeder Zurücknahme, als Regel und Trigger in der Datenbank. Ohne
Korrekturkette, Anhänge und Karenzzeit - was Beweiswert hat, gehört in eine
Fallakte.

Nebenbei: das Zugriffsprotokoll hängt jetzt am dafür vorgesehenen Recht
audit.read statt an admin.
2026-09-03 19:57:28 +02:00