From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally

---
 ARCHITEKTUR.md |  372 ++++++++++++++++++++++++----------------------------
 1 files changed, 171 insertions(+), 201 deletions(-)

diff --git a/ARCHITEKTUR.md b/ARCHITEKTUR.md
index e90bc6b..a7e9ff7 100644
--- a/ARCHITEKTUR.md
+++ b/ARCHITEKTUR.md
@@ -1,237 +1,207 @@
-# Architektur-Dokumentation – Server-Bibliothek
+# Architektur
 
-Diese Datei ist für die technische Übergabe gedacht: sie erklärt, wie das
-Projekt aufgebaut ist, welche Entscheidungen warum getroffen wurden, und was
-bei einer Anpassung an eine andere Infrastruktur zu beachten ist.
-
-Für die Bedienungsanleitung (Endnutzer-Sicht) siehe `README.md`.
-Für die Entwicklungshistorie (was wurde wann gebaut) siehe `CHANGELOG.md`.
+Die technische Innensicht: wie das Projekt aufgebaut ist, welche Entscheidungen warum so
+getroffen wurden und wo die Grenzen liegen. Für Betrieb und Bedienung siehe
+[README.md](README.md).
 
 ---
 
-## 1. Tech-Stack
+## 1. Grundriss
 
-- **Python 3.8+**, **Flask** (Web-Framework) – einzige externe Abhängigkeit
-  laut `requirements.txt`
-- **SQLite** (`data/bibliothek.db`) – eine einzelne Datei, kein Datenbankserver
-- **Server-seitig gerenderte HTML-Templates** (Jinja2), kein JavaScript-Framework,
-  kein Build-Prozess – nur Vanilla-JS für Interaktivität (Checklisten,
-  Formular-Verhalten)
-- Läuft als einfacher Flask-Entwicklungsserver (`app.run(host="0.0.0.0", ...)`)
-  – **nicht produktionsgehärtet**, siehe Abschnitt 7 "Bekannte Grenzen"
+npm-Workspaces mit drei Paketen. Die Typen liegen bewusst dazwischen: Server und
+Oberfläche teilen sich einen Vertrag, den der Übersetzer prüft — ändert sich ein Feld,
+bricht die Übersetzung auf beiden Seiten.
 
-Bewusste Design-Entscheidung: keine Frameworks/Build-Tools, damit eine Person
-ohne Programmiererfahrung (Zielgruppe dieses Projekts) das System mit einem
-Doppelklick starten kann, ohne `npm install` o. Ä.
+| Paket | Rolle |
+|---|---|
+| `bibliothek-types` | Gemeinsame Typen. Kennt weder Server noch Browser |
+| `bibliothek-server` | Express, SQLite, Bezugsquellen, Zugriffsgrenze |
+| `bibliothek-client` | Oberfläche aus `@dh-software/dh-components` (Lit) |
 
-## 2. Projektstruktur
+TypeScript durchgehend im `strict`-Modus.
 
-```
-server-bibliothek/
-├── app.py                          Haupt-Flask-Anwendung, alle Routen
-├── updater.py                      Update-Check-Logik (winget/GitHub/Direkt-Link)
-├── automatische_pruefung.py        Eigenständiges Skript für geplante Update-Checks
-├── requirements.txt                Python-Abhängigkeiten (nur Flask)
-├── data/
-│   ├── bibliothek.db               SQLite-Datenbank (wird beim ersten Start angelegt)
-│   └── doku_bilder/<software_id>/  Hochgeladene Doku-Screenshots
-├── installers/                     Standard-Ablageort für Installer-Dateien
-│                                   (Nutzer koennen aber JEDEN Pfad eintragen,
-│                                   dieser Ordner ist nur ein Vorschlag)
-├── static/style.css                Gesamtes Styling (ein File, keine Build-Pipeline)
-├── templates/                      Jinja2-HTML-Templates
-├── install.bat                     Einmalige Einrichtung (Python-Abhaengigkeiten)
-├── start.bat                       Startet den Webserver
-├── pruefe_updates.bat              Manueller Update-Check-Lauf
-├── automatisierung_einrichten.bat  Richtet taeglichen Update-Check per Windows-Aufgabenplanung ein
-├── firewall-freigabe-einrichten.bat Oeffnet Port 5000 in der Windows-Firewall
-├── Update-Quellen-Uebersicht.md    Nachschlagehilfe fuer winget-IDs/GitHub-Pfade
-├── README.md                       Bedienungsanleitung
-├── ARCHITEKTUR.md                  Diese Datei
-└── CHANGELOG.md                    Entwicklungshistorie
+### Warum Node und nicht Flask
+
+Der übernommene Prototyp war Flask. Der Umbau auf Node hatte zwei Gründe: Die Oberfläche
+sollte auf dem hauseigenen Komponenten-Satz aufsetzen, und der Auslieferungsstand sollte
+eine einzelne Programmdatei ohne installierte Laufzeitumgebung sein. Beides zieht Node
+nach sich.
+
+### Warum keine Datenbankbibliothek
+
+Ab Node 22 bringt die Laufzeitumgebung SQLite mit (`node:sqlite`). Damit entfällt eine
+native Abhängigkeit, die für jede Node-Version neu übersetzt werden müsste — genau daran
+war ein erster Versuch mit `better-sqlite3` gescheitert. Journalmodus WAL, damit Lesen
+während eines laufenden Abgleichs nicht blockiert.
+
+---
+
+## 2. Bezugsquellen
+
+Das Herzstück. Jede Art, an eine Datei zu kommen, ist eine Klasse hinter derselben
+Schnittstelle:
+
+```ts
+interface Bezugsquelle {
+  gleicheAb(auftrag: Abgleichauftrag): Promise<QuellenAbgleich>;
+}
 ```
 
-## 3. Datenbankschema
-
-Alle Tabellen werden in `init_db()` (app.py) angelegt. Migrationen (neue
-Spalten) laufen automatisch beim Start via `ALTER TABLE ... ADD COLUMN`,
-bestehende Daten bleiben dabei erhalten.
-
-### `software` – der Tool-Katalog
-| Spalte | Typ | Bedeutung |
-|---|---|---|
-| id | INTEGER PK | |
-| name | TEXT | Anzeigename, wird auch für Dateinamen-Ableitung sanitisiert |
-| kategorie | TEXT | Freitext, für Gruppierung in Checklisten |
-| version | TEXT | Bei "direkt"-Update-Quelle: technische Kennung (ETag), keine echte Versionsnummer |
-| installer_pfad | TEXT | Ordner **oder** Datei-Pfad, siehe Abschnitt 4 "Ordner-Konvention" |
-| install_parameter | TEXT | Silent-Install-Flags, z. B. `/S` oder `/quiet /norestart` |
-| dokumentation | TEXT | Freitext-Anleitung |
-| letzte_pruefung | TEXT (Datum) | Für die Status-Anzeige (Aktuell/Bitte prüfen/Nie geprüft) |
-| erstellt_am | TEXT (Zeitstempel) | |
-| auto_update | INTEGER (0/1) | Automatische Aktualisierung aktiv? |
-| update_quelle | TEXT | `winget` \| `github` \| `direkt` \| leer |
-| update_wert | TEXT | Paket-ID / `besitzer/projekt` / URL, je nach Quelle |
-
-### `zuweisungen` – einzelne Tool-Zuweisungen (Zeilen eines Protokolls)
-| Spalte | Bedeutung |
+| Typ | Vorgehen |
 |---|---|
-| id, software_id | |
-| protokoll_id | FK auf `protokolle` (siehe unten) |
-| mitarbeiter, rechnername | Denormalisiert hier UND im Protokoll gespeichert (Absicht: die Installationsverlauf-Ansicht pro Software bleibt dadurch einfach) |
-| installierte_version, installiert_von, installiert_am, notiz | |
+| `direkt` | HTTP-Abruf eines festen Links |
+| `github` | Neuestes Release über die GitHub-Schnittstelle, passendes Asset wählen |
+| `winget` | `winget download`, danach `winget show` für das Veröffentlichungsdatum |
+| `svn` | `svn export` einer Datei |
+| `git` | Repository klonen bzw. aktualisieren, bauen, Ergebnis übernehmen |
+| `manuell` | Kein Abruf — die Datei wird einmalig hinterlegt und bleibt liegen |
 
-### `protokolle` – ein Eintrag pro Rechner-Einrichtung
-| Spalte | Bedeutung |
-|---|---|
-| id, mitarbeiter, rechnername, datum, erstellt_von, notiz, erstellt_zeitpunkt | |
+Eine neue Quelle anzubinden heißt: eine Klasse schreiben und in der Übersicht eintragen.
+Der Rest — Abgleich, Prüfung, Speicherung, Anzeige — bleibt unberührt.
 
-Ein Protokoll fasst mehrere `zuweisungen`-Zeilen zusammen (1 Protokoll : n
-Zuweisungen), damit bei z. B. 40 Mitarbeitern × 40 Tools keine unübersichtliche
-flache Liste entsteht, sondern eine Übersicht pro Person/Rechner.
+### Nichts wird ersetzt, bevor es geprüft ist
 
-### `update_log` – Protokoll jedes automatischen Update-Checks
-| Spalte | Bedeutung |
-|---|---|
-| id, software_id, zeitpunkt, alte_version, neue_version, erfolg (0/1), meldung | |
+Der teuerste Fehler wäre, eine funktionierende Datei gegen Müll zu tauschen, weil eine
+Quelle stillschweigend etwas anderes liefert als erwartet. Deshalb schreibt keine Quelle
+direkt in den Zielordner:
 
-### `doku_bilder` – hochgeladene Screenshots zur Dokumentation
-| Spalte | Bedeutung |
-|---|---|
-| id, software_id, dateiname, hochgeladen_am | Datei liegt unter `data/doku_bilder/<software_id>/<dateiname>` |
+1. Herunterladen bzw. bauen in `<zielordner>.neu`
+2. `inhaltBeanstanden()` prüft: leer? HTML statt Programmdatei (Signatur und
+   Inhaltstyp)? plötzlich unter der Hälfte der bisherigen Größe und kleiner als 100 KB?
+3. Erst wenn nichts zu beanstanden ist, wird getauscht — sonst verworfen
 
-## 4. Kernkonzept: Ordner-Konvention (`updater.py`)
+Der Eintrag meldet dann das Problem, behält aber seine bisherige Datei. Eine tote Quelle
+kostet Aktualität, nie den Download.
 
-Das Feld `installer_pfad` kann zwei Formen haben:
-- **Datei-Pfad** (endet auf `.exe`/`.msi`/etc.): ältere/explizite Schreibweise
-- **Ordner-Pfad** (kein Dateiname): empfohlene, aktuelle Schreibweise
+### Selbstgebaute Werkzeuge
 
-Die Funktion `updater.effektiver_ablageordner(installer_pfad, name)` ist die
-zentrale Logik: sie sorgt **immer** dafür, dass jedes Tool in einem eigenen
-Unterordner landet (benannt nach dem sanitisierten Tool-Namen), selbst wenn
-im Feld nur ein geteilter Basisordner steht oder ein alter kompletter
-Dateipfad. Das verhindert, dass mehrere Tools sich einen Ordner teilen und
-Dateien "nackt" nebeneinander landen (siehe CHANGELOG, Runden r6–r8, dort
-wurde das mehrfach nachgeschärft, nachdem reale Testfälle Lücken zeigten).
+Bei `git` beschreibt das Repository seinen eigenen Bau in `software-bibliothek.json`:
 
-`updater.verwaltete_datei_pfad(installer_pfad, name)` findet die aktuell
-abgelegte Datei (falls vorhanden), inkl. Rückwärtskompatibilität zu Dateien,
-die noch am alten, nicht verschachtelten Ort liegen (Übergangslogik).
+```json
+{
+  "buildCommand": "…",
+  "artifact": "dist/MeinWerkzeug.exe",
+  "secrets": [{ "name": "…", "description": "…" }]
+}
+```
 
-**Wichtig für Weiterentwicklung:** Jede neue Stelle im Code, die mit
-Installer-Dateien arbeitet, MUSS über diese beiden Funktionen gehen, statt
-`installer_pfad` direkt zu verwenden – sonst wird die
-Unterordner-Garantie umgangen.
+Damit muss die Bibliothek nichts über einzelne Werkzeuge wissen — ein neues anzubinden
+erfordert keine Änderung an ihr. Gebaut wird nur, wenn sich der Commit geändert hat.
+Zeitgrenze 30 Minuten, weil Bauvorgänge mit vielen Abhängigkeiten lange dauern.
 
-## 5. Automatische Aktualisierung (drei Quellen)
+Die Kennung für den Zugriff kommt aus den Einstellungen (`GIT_SSH_COMMAND`), nicht aus der
+URL. So bleibt der Katalogeintrag frei von Kontoangaben, und ein Kontowechsel ist eine
+einzige Änderung statt einer pro Eintrag.
 
-`updater.pruefe_software(name, quelle, update_wert, aktuelle_version, installer_pfad)`
-ist der Einstiegspunkt, delegiert an:
+### Bau-Geheimnisse
 
-- **`pruefe_winget`** – ruft `winget show`/`winget download` auf (nur unter
-  Windows verfügbar; auf Nicht-Windows-Systemen liefert es einen sauberen
-  Fehler statt eines Absturzes)
-- **`pruefe_github`** – nutzt die öffentliche GitHub-API
-  (`api.github.com/repos/.../releases/latest`), sucht ein `.exe`/`.msi`-Asset
-- **`pruefe_direkt`** – fester Download-Link, Versionsvergleich über
-  HTTP-`ETag`/`Last-Modified`-Header statt echter Versionsnummer
+Werte liegen getrennt vom Katalog in `secrets/<id>.json` und werden beim Bau als
+Umgebungsvariablen gesetzt — sie berühren die Platte der Arbeitskopie nie. Das Verfahren
+im Ganzen beschreibt der Skill `build-secrets` im Repository `dh-claude-rules`.
 
-Alle drei prüfen zusätzlich, ob die Datei **physisch noch existiert** – auch
-bei gleicher Versionsnummer wird neu heruntergeladen, falls die Datei manuell
-gelöscht wurde (Bug wurde in Runde r5 gefunden und behoben).
+Geschützt sind sie durch Dateiberechtigungen (`icacls`, beschränkt auf SYSTEM,
+Administratoren und das Dienstkonto, bei jedem Start neu gesetzt), durch die
+Zugriffsgrenze und dadurch, dass sie in keiner aus dem Netzwerk lesbaren Schnittstelle
+auftauchen. Eine Verschlüsselung wäre unter Windows nur über die Datenschutz-Schnittstelle
+des Betriebssystems sinnvoll; die ist aus der Laufzeitumgebung nicht ohne Zusatzpaket
+erreichbar.
 
-Vor jedem Ersetzen einer Datei wird über `sichern_und_ersetzen()` eine
-Sicherheitskopie in einen `_sicherungen`-Unterordner gelegt.
+---
 
-## 6. "Rechner einrichten" – Verteilungs-Mechanismus
+## 3. Zugriffsgrenze
 
-Route `/einrichten` (app.py) erzeugt bei Bedarf ein individuelles
-Installations-Paket (ZIP mit `einrichten.ps1` + `Rechner-einrichten-starten.bat`
-**+ den echten Installer-Dateien/ZIPs selbst** unter `tools/<id>.<endung>`),
-statt vorab für jede Mitarbeiter/Tool-Kombination ein festes Skript zu pflegen.
+Kein Anmeldeverfahren, sondern eine Grenze an der Netzwerkschnittstelle: Eine Middleware
+prüft, ob die Anfrage von `localhost` kommt. Verwaltende Routen tragen sie, lesende nicht.
 
-**Wichtige Design-Entscheidung (Runde r16):** Die Installer-Dateien werden
-bereits beim Erzeugen des Pakets (auf dem PC, der die Checkliste ausfüllt)
-eingebettet, nicht erst beim Ausführen auf dem Zielrechner nachgeladen. Ein
-früherer Ansatz lud die Dateien per `Invoke-WebRequest` zur Laufzeit vom
-Server nach – das erforderte Netzwerkzugriff des Zielrechners auf den Server
-in genau dem Moment der Einrichtung, was in der Praxis nicht immer gegeben
-ist (z. B. Rechner noch nicht ans Firmennetz angebunden). Jetzt braucht der
-Zielrechner für die eigentliche Installation **kein Netzwerk mehr** – nur die
-abschließende, optionale Meldung ans zentrale Protokoll (`/zuweisungen/neu`)
-versucht noch einen Netzwerkzugriff, scheitert dieser, bleibt trotzdem das
-lokale Protokoll (`protokoll.log`) vollständig.
+- Frei erreichbar: Katalog lesen, einzeln oder als ZIP herunterladen, Umgebung abfragen
+- Nur lokal: Anlegen, Ändern, Löschen, Abgleich anstoßen, Datei hinterlegen, Geheimnisse,
+  Export/Import, Einstellungen, Zugriffsprotokoll
 
-Ablauf beim Ausführen auf dem Zielrechner:
-1. `.bat` kopiert den **kompletten entpackten Ordner** (inkl. `tools/`-
-   Unterordner mit den echten Installer-Dateien) nach `C:\ClientBibliothek`
-   (per `xcopy /E /I /Y`)
-2. Self-Elevation (Admin-Rechte-Anfrage) über `Start-Process -Verb RunAs`
-3. Automatische Windows-Defender-Ausnahme für `C:\ClientBibliothek`
-   (`Add-MpPreference -ExclusionPath`) – ein einziger Pfad statt vieler
-   Einzelausnahmen
-4. Pro ausgewähltem Tool: entweder stille Installation aus der **lokal
-   mitgelieferten** Datei unter `tools/<id>.<endung>` (mit hinterlegtem
-   Silent-Parameter, `.msi` läuft über `msiexec`) oder – bei Tools ohne
-   einzelne Installer-Datei ("Ordner-Typ", z. B. portable Programme wie
-   Blender) – die mitgelieferte ZIP-Datei wird nach `C:\ClientBibliothek\pakete\`
-   kopiert, **bewusst ohne automatisches Entpacken** (Entscheidung in Runde
-   r14: der Zielort für portable Tools variiert, das soll eine Person bewusst
-   entscheiden)
-5. Lokales Protokoll unter `C:\ClientBibliothek\protokoll.log`
-6. Optionaler POST an `/zuweisungen/neu` auf dem Server, um das zentrale
-   Protokoll zu befüllen (Rechnername wird dabei über `$env:COMPUTERNAME`
-   automatisch erkannt) – best effort, kein Abbruch bei Fehlschlag
+Die Oberfläche fragt beim Start `/api/umgebung` und blendet aus, was ohnehin abgewiesen
+würde. Das ist Bequemlichkeit, keine Absicherung — die liegt beim Server.
 
-Die Ordner-Struktur unter `C:\ClientBibliothek`:
-- `\tools` – die mitgelieferten Installer-Dateien/ZIPs (Quelle für die
-  Installation, kommen direkt aus dem heruntergeladenen Paket)
-- `\pakete` – ZIPs von portablen Tools, bleiben bewusst liegen
-- `\protokoll.log` – lokale Mitschrift
+Das passt zum Auftrag („Vollzugriff nur am Server") und kommt ohne Benutzerverwaltung aus.
+Der Preis: Wer am Server sitzt, darf alles. Rollen und Rechte sind im Ticket bewusst
+zurückgestellt.
 
-Serverseitig teilen sich `/einrichten` (POST) und `/software/<id>/download`
-die Hilfsfunktion `_zippe_ordner_in_bytes()` für das ZIP-Packen eines
-Ordners, um Code-Duplikation zu vermeiden.
+---
 
-**Der Client-Ordner-Pfad ist eine Konstante** (`CLIENT_ORDNER` in `app.py`,
-aktuell `C:\ClientBibliothek`) – zentral anpassbar, falls die Infrastruktur
-einen anderen Standardpfad vorgibt.
+## 4. Oberfläche
 
-## 7. Bekannte Grenzen / nicht vollständig getestet
+Aufgebaut aus `@dh-software/dh-components`, damit das Werkzeug aussieht wie der Rest des
+Hauses. Zwei Besonderheiten:
 
-- **Keine Authentifizierung.** Es gibt kein Login-System. Jeder, der den
-  Server im Netzwerk erreichen kann, kann Software verwalten, Installer
-  herunterladen und Installations-Pakete erzeugen. Das war für den internen,
-  vertrauenswürdigen Einsatz im Firmennetz eine bewusste Vereinfachung –
-  **bei einer Anpassung an eine andere/größere Infrastruktur unbedingt
-  prüfen, ob das so bleiben darf**, insbesondere wenn der Server auch von
-  außerhalb des Firmennetzes erreichbar sein könnte.
-- **Kein HTTPS/TLS.** Die Kommunikation läuft unverschlüsselt über HTTP. Für
-  ein internes, abgeschottetes Netzwerk meist unkritisch, sollte aber vor
-  einem größeren Rollout bewertet werden.
-- **Flask-Entwicklungsserver**, kein WSGI-Produktivserver (z. B. Waitress,
-  gunicorn). Für kleine interne Nutzung ausreichend, bei mehr gleichzeitigen
-  Nutzern ggf. wechseln.
-- **PowerShell-Teile des Verteilungs-Skripts** (Self-Elevation, `msiexec`,
-  `Add-MpPreference`) konnten in der Entwicklungsumgebung (Linux-Sandbox)
-  nicht Ende-zu-Ende ausgeführt werden, nur mit dem echten PowerShell-Parser
-  auf Syntaxfehler geprüft, plus die reinen Netzwerk-Teile
-  (Invoke-WebRequest/Invoke-RestMethod, Expand-Archive) wurden live gegen den
-  echten Server getestet. Vor größerem Rollout: an 1–2 unkritischen Rechnern
-  gegentesten.
-- **winget-Pfad der automatischen Aktualisierung** konnte aus demselben
-  Grund nicht direkt ausgeführt werden (winget existiert nur unter Windows).
-  Der GitHub- und Direkt-Link-Pfad wurden dagegen live gegen echte
-  Schnittstellen getestet.
-- **Keine mehrsprachige Oberfläche** – komplett auf Deutsch hartkodiert.
+**Light DOM statt Shadow DOM.** Die Komponenten werden über `createRenderRoot()` ins Light
+DOM gerendert, sonst greifen die globalen `::part()`-Regeln des Themes nicht.
 
-## 8. Konfigurationspunkte für die Anpassung
+**Themewechsel zur Laufzeit.** Zwei `<style>`-Elemente mit den Farbdefinitionen für hell
+und dunkel, umgeschaltet über `.disabled`. Drei Modi: System, hell, dunkel. Nach dem
+Vorbild des Meeting-Transkriptors, damit sich die Werkzeuge gleich anfühlen.
 
-| Was | Wo | 
-|---|---|
-| Client-Ordner auf Zielrechnern | `CLIENT_ORDNER` in `app.py` |
-| Port des Webservers | `app.run(..., port=5000)` am Ende von `app.py`, sowie in den `.bat`-Dateien/Firewall-Skript |
-| Schwelle "Bitte prüfen"-Warnung | `TAGE_BIS_WARNUNG` in `app.py` (aktuell 30 Tage) |
-| Erlaubte Bildformate für Doku-Upload | `ERLAUBTE_BILD_ENDUNGEN` in `app.py` |
-| Max. Uploadgröße | `app.config["MAX_CONTENT_LENGTH"]` in `app.py` |
-| Versionsmarker (Diagnose, ob neue Version läuft) | `APP_VERSION` in `app.py` |
+Der Katalog ist eine kompakte Liste statt Karten — er soll dreistellige Zahlen an
+Einträgen aushalten. Reiter trennen lizenzfrei, lizenzpflichtig und intern; die
+Mehrfachauswahl wirkt über Reitergrenzen hinweg und lädt als ZIP.
+
+Zusatzinhalte (Sprachpakete, Erweiterungen) hängen über `elternId` an ihrem Werkzeug und
+stehen eingerückt darunter, ohne eigene Trennlinie — Werkzeug und Zubehör lesen sich als
+ein Block.
+
+---
+
+## 5. Auslieferung als eine Datei
+
+`npm run build:exe` in vier Schritten:
+
+1. TypeScript übersetzen
+2. Server mit esbuild zu einer CommonJS-Datei bündeln
+3. Die Oberfläche als Einzelressourcen in die SEA-Konfiguration eintragen
+4. Node-Programmdatei kopieren und den Blob per `postject` hineinschreiben
+
+**Warum Einzelressourcen und nicht ein eingebettetes Bündel:** Ein früher Versuch hat das
+JavaScript-Bündel als Zeichenkette in die Server-Datei eingebettet. Die eingebettete
+Schriftart enthält Bytefolgen, die als UTF-8 ungültig sind — sie wurden zu U+FFFD und das
+Bündel damit unbrauchbar. Die Seite blieb weiß, ohne Fehlermeldung. Aufgefallen ist es
+erst beim Vergleich der ausgelieferten Bytes mit der Datei auf der Platte. Über
+`sea.getRawAsset()` kommen die Ressourcen unverändert an.
+
+**Warum esbuild und nicht webpack:** webpack scheitert an einer Abhängigkeit in der
+Server-Kette, deren `tsconfig.json` per `extends` auf ein nicht aufgelöstes Paket zeigt.
+esbuild kümmert das nicht.
+
+Der ZIP-Erzeuger für Mehrfach-Downloads ist von Hand geschrieben (nur speichernd, ohne
+Kompression — Installer sind ohnehin komprimiert). Das spart eine Abhängigkeit in einem
+Bündel, das ohne Fremdcode auskommen soll.
+
+---
+
+## 6. Datenmodell
+
+Eine Tabelle `software` und eine Tabelle für das Zugriffsprotokoll. Neue Spalten kommen
+über eine Liste hinzu, die beim Start mit dem tatsächlichen Schema abgeglichen wird —
+fehlende werden ergänzt. Damit übersteht eine bestehende Installation ein Update ohne
+Handarbeit.
+
+Ordnernamen im Datenverzeichnis haben denselben Weg: Eine Umbenennungsliste beim Start
+zieht ältere Installationen nach.
+
+Zur Version: Angezeigt wird das **Veröffentlichungsdatum des Anbieters**, nicht der
+Abrufzeitpunkt. Nur wenn eine Quelle keins liefert, wird ersatzweise der Abrufzeitpunkt
+genannt und als solcher benannt — die Frage „ist das aktuell?" beantwortet das Datum des
+Anbieters, unser Abrufzeitpunkt sagt darüber nichts.
+
+---
+
+## 7. Grenzen
+
+- **Kein HTTPS.** Damit fehlt dem Browser der sichere Kontext, und die Schnittstellen für
+  Dateizugriff (`showDirectoryPicker`, `crypto.subtle`) stehen an der Netzwerkadresse nicht
+  zur Verfügung. Nachgemessen: an `localhost` vorhanden, an der IP nicht.
+- **Kein Dienstbetrieb.** Die Programmdatei muss von Hand gestartet werden.
+- **Keine automatisierten Tests.** Geprüft wurde bisher von Hand gegen die echten Quellen.
+- **Keine Rollen.** Wer am Server sitzt, darf alles.
+- **Ein hinterlegtes Geheimnis lässt sich über die Oberfläche nicht einzeln löschen** —
+  ein leeres Feld bedeutet „unverändert lassen". Nur über die Schnittstelle mit einer
+  Liste, die den Namen weglässt.
+- **Bauvorgänge laufen mit den Rechten des Dienstkontos.** Wer einen Bau-Befehl hinterlegen
+  kann, kann Code auf dem Server ausführen. Das setzt Vertrauen in die angebundenen
+  Repositories voraus.

--
Gitblit v1.9.3