feat(projects): add decisions and result to project dialog
Add optional Decisions and Result blocks to the project dialog. Fill them for ToBePlayed, Tribble, Claude-in-a-Box and Playground. Remove Portainer and the Google Drive backup claims from Tribble.
This commit is contained in:
1 parent
1cff9e6eb2
commit
0857ce482b
6 files changed
+132
-21
No files matched your search
+26
-9
@@ -250,7 +250,7 @@
|
||||
},
|
||||
"TRIBBLE": {
|
||||
"TITLE": "Homeserver 'Tribble'",
|
||||
"DESCRIPTION": "Ein Mini-PC zu Hause betreibt Gitea mit Actions-Runner und Registry, dazu Jellyfin, Paperless-ngx und AdGuard Home. Ein VPS liefert die öffentlichen Seiten aus und holt neue Images mit Watchtower über Tailscale. Läuft seit Mai 2024, Backups auf Google Drive.",
|
||||
"DESCRIPTION": "Ein Mini-PC zu Hause betreibt Gitea mit Actions-Runner und Registry, dazu Jellyfin, Paperless-ngx und AdGuard Home. Ein VPS liefert die öffentlichen Seiten aus und holt neue Images mit Watchtower über Tailscale. Läuft seit Mai 2024.",
|
||||
"LINK_INTERNAL": "Projektdetails",
|
||||
"HIGHLIGHTS": {
|
||||
"P1": "Gitea-CI baut und testet diese Seite und To Be Played zu Hause; Watchtower spielt die Images auf dem VPS ein.",
|
||||
@@ -305,7 +305,9 @@
|
||||
"CLOSE": "Schließen",
|
||||
"SECTION": {
|
||||
"TECHNICAL": "Technische Herausforderungen",
|
||||
"LEARNINGS": "Learnings & Soft Skills"
|
||||
"LEARNINGS": "Learnings & Soft Skills",
|
||||
"DECISIONS": "Entscheidungen",
|
||||
"RESULT": "Ergebnis"
|
||||
},
|
||||
"PLAYGROUND": {
|
||||
"TITLE": "Playground",
|
||||
@@ -322,7 +324,11 @@
|
||||
"CHALLENGE_3": "Eine bestehende SPA vorrendern. Beim Build wird jede Seite in Node gerendert, und dort gibt es kein WebGPU, kein Canvas für Babylon.js und keinen localStorage. Browser-Code musste Komponente für Komponente abgesichert werden, bis der Build durchlief. Die Client-Shell index.csr.html bleibt, weil Service Worker und 404-Seite sie weiter brauchen.",
|
||||
"LEARNING_1": "Compute Shader verändern, wie man über einen Algorithmus denkt. Das Datenlayout und die Reihenfolge der Schreibzugriffe zählen mehr als die Mathematik.",
|
||||
"LEARNING_2": "Eine kleine CI mit einem Abgleich der Übersetzungsschlüssel und einem Lighthouse-Lauf fängt die langweiligen Fehler ab (eine fehlende Übersetzung, eine langsame Seite), bevor ich sie auf der Live-Seite sehe.",
|
||||
"LEARNING_3": "Eine Datenschutzseite ist eine Vorgabe für den Server. Sobald dort steht, dass es kein Access-Log und keine Verbindung zu Google gibt, darf nginx kein Log schreiben, und die Schriften müssen mit der Seite ausgeliefert werden. Es zuerst aufzuschreiben hat mir diese Entscheidungen abgenommen."
|
||||
"LEARNING_3": "Eine Datenschutzseite ist eine Vorgabe für den Server. Sobald dort steht, dass es kein Access-Log und keine Verbindung zu Google gibt, darf nginx kein Log schreiben, und die Schriften müssen mit der Seite ausgeliefert werden. Es zuerst aufzuschreiben hat mir diese Entscheidungen abgenommen.",
|
||||
"DECISION_1": "Vorrendern beim Build statt Client-Rendering oder einem SSR-Server. Jede Route wird beim Build zu einer eigenen HTML-Datei, die nginx als statische Datei ausliefert; auf dem VPS läuft kein Node-Prozess. Der Grund war die Link-Vorschau: Ein geteilter Link braucht Titel und Meta-Tags im HTML, nicht erst, nachdem die App geladen hat. Ein Node-Server wäre für eine Seite, deren Inhalt ohnehin im Browser läuft, zu aufwendig gewesen.",
|
||||
"DECISION_2": "Vitest mit jsdom statt Karma mit Chrome. Karma brauchte einen Headless-Chrome im CI-Container; Vitest läuft ohne Browser. Der Nachteil: jsdom wendet kein echtes CSS an, zwei Tests auf die geladenen Schriften sind dabei gestrichen worden.",
|
||||
"DECISION_3": "Tailwind ohne Preflight statt SCSS-first oder Tailwind komplett. Die Styles sind aus einer großen styles.scss in Utility-Klassen im Template umgezogen, damit ein Style neben dem Markup steht, das er betrifft; SCSS bleibt nur für das Material-Theme und seine Overrides. Tailwinds eigener Reset (Preflight) bleibt draußen, damit der Reset von Angular Material Vorrang behält.",
|
||||
"RESULT": "Die Seite läuft so, wie die Datenschutzseite es verspricht: ohne Cookies, ohne Tracking, ohne Google-Fonts-Request, und der Build schlägt fehl, sobald einer auftaucht. Ein Push auf main ist nach wenigen Minuten live, ohne manuellen Schritt. Geteilte Links zeigen jetzt Titel und Vorschau, und die CI fängt fehlende Übersetzungen und einen fehlenden Titel oder Canonical-Tag ab, bevor ich sie auf der Live-Seite sehe."
|
||||
},
|
||||
"TRIBBLE": {
|
||||
"TITLE": "Self-Hosted Infrastructure",
|
||||
@@ -330,13 +336,16 @@
|
||||
"INTRODUCTION": "Zwei Maschinen. Zu Hause läuft auf einem Mini-PC namens „Tribble“ Gitea mit Actions-Runner und Container-Registry, dazu die privaten Dienste. Ein Hostinger-VPS liefert die öffentlichen Seiten aus, auch diese hier. Ein Git-Push baut das Image zu Hause, der VPS holt es mit Watchtower über Tailscale, und Traefik übernimmt TLS und Routing. Das Ganze läuft seit Mai 2024.",
|
||||
"BULLET_1": "Hardware: ein KODLIX GD50 Mini-PC mit einem 8-Kern-Intel-Core-i5, 32 GB RAM und einer 512-GB-SSD.",
|
||||
"BULLET_2": "Gitea für Git, den Actions-Runner und die Container-Registry. Diese Seite und To Be Played werden dort gebaut und getestet.",
|
||||
"BULLET_3": "Private Dienste: Jellyfin für Medien, Paperless-ngx für Dokumente, AdGuard Home als DNS-Server und Werbeblocker, Portainer, um die Container im Blick zu haben, und Traefik davor.",
|
||||
"BULLET_3": "Private Dienste: Jellyfin für Medien, Paperless-ngx für Dokumente, AdGuard Home als DNS-Server und Werbeblocker, Traefik davor.",
|
||||
"BULLET_4": "Tailscale verbindet die beiden Maschinen. Darüber holt der VPS neue Images aus der Registry zu Hause, und Watchtower tauscht den laufenden Container aus.",
|
||||
"BULLET_5": "Die Backups gehen auf Google Drive.",
|
||||
"CHALLENGE_1": "DNS. Namen müssen von zu Hause aus, über Tailscale und aus dem öffentlichen Internet auf die richtige Adresse zeigen, und AdGuard Home sitzt mittendrin. Das richtig hinzubekommen hat länger gedauert als jeder einzelne Dienst.",
|
||||
"CHALLENGE_2": "Alles gleichzeitig zum Laufen zu bringen und es dann wiederholbar zu machen. Das erste Setup war eine lange Liste von Handgriffen. Ich habe es automatisiert, damit sich der Server neu aufsetzen lässt, ohne dass ich mir jeden Schritt merken muss.",
|
||||
"LEARNING_1": "Selbst hosten kostet wenig Geld und viel Zeit. Die meiste Zeit geht in die Teile, die niemand sieht: DNS, Zertifikate und Backups.",
|
||||
"LEARNING_2": "Ein Git-Push, der ohne Handgriff dazwischen in einem laufenden Container auf dem VPS endet, ist der Teil dieses Setups, den ich nicht mehr hergeben würde."
|
||||
"LEARNING_1": "Selbst hosten kostet wenig Geld und viel Zeit. Die meiste Zeit geht in die Teile, die niemand sieht: DNS, Zertifikate und Updates.",
|
||||
"LEARNING_2": "Ein Git-Push, der ohne Handgriff dazwischen in einem laufenden Container auf dem VPS endet, ist der Teil dieses Setups, den ich nicht mehr hergeben würde.",
|
||||
"DECISION_1": "Der VPS ist der einzige öffentliche Eingang. Traefik auf dem VPS leitet die Namen, die zu Hause gehostet werden, über Tailscale an den Heimserver weiter, mit ein paar YAML-Dateien im File-Provider. Die Alternative wäre eine Portweiterleitung im Router gewesen. So bleibt zu Hause kein Port offen, und die Adresse des Heimservers bleibt privat.",
|
||||
"DECISION_2": "Auf dem VPS zieht Watchtower, zu Hause bleiben die Tags gepinnt. Der VPS schaut jede Minute nach neuen Images und tauscht nur die markierten Container aus, einen nach dem anderen; die CI braucht dafür keinen Zugang zum VPS. Der Heimserver bekommt kein „latest“: Dort ist jedes Image-Tag festgeschrieben, und ein Update ist ein manueller Schritt pro Stack, weil mit „latest“ eine Datenbankmigration als Nebenwirkung passiert, und die lässt sich nicht zurücknehmen.",
|
||||
"DECISION_3": "Neu aufsetzen per Skript statt aus dem Gedächtnis. bootstrap.sh legt das Proxy-Netz und die Verzeichnisse an und warnt vor dem DNS-Port-Konflikt zwischen systemd-resolved und AdGuard; rebuild.sh startet die Stacks in fester Reihenfolge, Traefik zuerst, weil es alphabetisch zuletzt käme. Ansible hätte bei sechs Compose-Stacks mehr Aufwand gekostet, als es spart. Zwei Shell-Skripte reichen und sind leichter zu lesen. Die Skripte decken die Infrastruktur ab, nicht die Daten.",
|
||||
"RESULT": "Heute laufen zu Hause sechs Stacks: Traefik, AdGuard Home, Gitea mit Runner, Jellyfin, Paperless-ngx und Leantime; Nextcloud ist wieder abgeschaltet. Auf dem VPS laufen Traefik, Watchtower und die öffentlichen Seiten. Jeder Push dieser Seite und von To Be Played wird zu Hause gebaut, in die Registry zu Hause geschoben und kommt ohne manuellen Schritt auf den VPS. Im September habe ich alle Stacks in einem Durchgang aktualisiert, Stack für Stack mit den gepinnten Tags, ohne dass ein Dienst ausgefallen ist."
|
||||
},
|
||||
"EL_MUCHO": {
|
||||
"TITLE": "El Mucho (Steam Release)",
|
||||
@@ -397,7 +406,11 @@
|
||||
"CHALLENGE_4": "Zu wissen, ob eine Änderung an der Box etwas bringt. Jede angepasste Regel fühlte sich wie eine Verbesserung an, also habe ich die Eval-Suite gebaut: feste Aufgaben mit versteckten Prüfungen, vor und nach einer Änderung mit denselben Modellen ausgeführt. Die Suite prüft auch sich selbst: Die unveränderte Vorlage muss an jeder Aufgabe scheitern und die Referenzlösung muss bestehen, sonst sagt ein grüner Lauf nichts aus.",
|
||||
"LEARNING_1": "Die Formulierung der Prompts war deutlich weniger wichtig, als ich dachte. Fast der ganze Unterschied kam vom Drumherum.",
|
||||
"LEARNING_2": "Den Output des Agenten genauso zu reviewen und durch dieselben Gates zu schicken wie den Pull Request eines neuen Teammitglieds hat sich als die nützlichste Regel überhaupt herausgestellt.",
|
||||
"LEARNING_3": "Kleine Agenten mit jeweils frischem Kontext schlagen einen langen Chat. Nicht weil das Modell klüger wird, sondern weil niemand frühere Fehler mitschleppt und jede Übergabe eine Datei ist, die ich lesen und korrigieren kann."
|
||||
"LEARNING_3": "Kleine Agenten mit jeweils frischem Kontext schlagen einen langen Chat. Nicht weil das Modell klüger wird, sondern weil niemand frühere Fehler mitschleppt und jede Übergabe eine Datei ist, die ich lesen und korrigieren kann.",
|
||||
"DECISION_1": "Hooks statt Berechtigungsregeln. Claude Code kann Werkzeuge per Regel sperren, aber eine Regel gilt pro Werkzeug: Ein Leseverbot für .env-Dateien hält „cat .env“ in der Shell nicht auf. Deshalb sind der Secret-Guard, das Commit-Gate und der Type-Check Hooks, die jeden Aufruf sehen. Und seit ein Timeout im Hook einmal Commits ungeprüft durchgelassen hat, schließt das Gate bei jedem Fehler: Keine Antwort heißt rot.",
|
||||
"DECISION_2": "Der Docker-Socket des Hosts statt eines eigenen Docker-Daemons im Container. Damit laufen die Testcontainers-Tests der Java-Repos im Commit-Gate, statt übersprungen zu werden. Der Nachteil steht im README: Wer den Socket hat, ist auf dem Host root. Die Box ist also eine Arbeitsumgebung mit festen Werkzeugen, keine Sandbox.",
|
||||
"DECISION_3": "Ein zweites Modell statt nur eines zweiten Kontexts. Anfangs lief alles auf einem Modell, das zweite wurde nie benutzt. Jetzt ist das Modell, das plant und reviewt, nicht das, das implementiert, weil eine zweite Meinung mehr wert ist, wenn sie nicht aus demselben Modell kommt. Für einen Ein-Datei-Fix lohnt der Wechsel nicht, deshalb überspringen kleine Änderungen einzelne Schritte der Pipeline.",
|
||||
"RESULT": "Die Box ist seit Juli 2026 mein tägliches Werkzeug, in jedem Repo, an dem ich arbeite, im Job und zu Hause. Der Build mit echten Testcontainers läuft im Commit-Gate. Die Pipeline besteht jede Aufgabe der Eval-Suite, während der unveränderte Ausgangszustand an jeder scheitert. Das zeigt vor allem, dass die Aufgaben noch zu leicht sind; härtere sind der nächste Schritt."
|
||||
},
|
||||
"TBP": {
|
||||
"TITLE": "To Be Played",
|
||||
@@ -413,7 +426,11 @@
|
||||
"CHALLENGE_3": "Ein Spiel vorzuschlagen ist schwerer, als es klingt. Jede Vorschlagsreihe braucht eigene Regeln, und die Listen hinter jeder Zahl müssen dieselben Regeln nutzen wie die Zahl selbst, damit ein Spiel überall gleich zählt.",
|
||||
"CHALLENGE_4": "Missbrauch beim Login. Zu viele fehlgeschlagene Logins bekommen die Antwort 429 mit Retry-After. Auch bei einer unbekannten E-Mail läuft eine volle BCrypt-Prüfung, sodass die Antwortzeit nichts verrät. Ein Header-Schutz blockiert CSRF, CORS erlaubt nur exakte Origins, und das PlayStation-Token wird verschlüsselt gespeichert.",
|
||||
"LEARNING_1": "Die Aufteilung in Dienste lohnt sich nur mit einem klaren Vertrag. Die Token-Claims stehen in einer Vertragsdatei, gegen die beide Dienste testen, und ein Skript, das ich von Hand starte, prüft, dass beide Kopien gleich sind.",
|
||||
"LEARNING_2": "Auch ein Hobbyprojekt braucht die langweiligen Teile: tägliche Datenbank-Dumps, die sieben Tage, vier Wochen und sechs Monate aufbewahrt werden, Migrationen und eine schriftlich festgehaltene Deploy-Reihenfolge. Sie haben mehr darüber entschieden, wie stabil es läuft, als jedes Feature."
|
||||
"LEARNING_2": "Auch ein Hobbyprojekt braucht die langweiligen Teile: tägliche Datenbank-Dumps, die sieben Tage, vier Wochen und sechs Monate aufbewahrt werden, Migrationen und eine schriftlich festgehaltene Deploy-Reihenfolge. Sie haben mehr darüber entschieden, wie stabil es läuft, als jedes Feature.",
|
||||
"DECISION_1": "Ein Schlüsselpaar statt eines gemeinsamen Geheimnisses. Die erste Version hat die Tokens mit einem HMAC-Secret signiert, das Auth und Core beide kannten. Jetzt hält Auth den privaten Ed25519-Schlüssel und veröffentlicht den öffentlichen über einen JWKS-Endpunkt. Core kann einen Login prüfen, aber nie selbst ausstellen, und wenn ein Token eine unbekannte Key-ID trägt, holt Core sich den neuen Schlüssel selbst.",
|
||||
"DECISION_2": "Mails über eine Outbox-Tabelle statt direkt per SMTP. Jede Mail wird in der Transaktion ihres Ereignisses als Zeile in mail_outbox geschrieben, ein Job verschickt die Zeilen jede Minute. So scheitert eine Registrierung nie am Mailserver, und beim Neustart von Auth geht keine Mail verloren. Mit einer Auth-Instanz reicht „mindestens einmal“.",
|
||||
"DECISION_3": "Installierbar, aber ohne Service Worker. Die App hat ein Web-App-Manifest und lässt sich auf Handy und Desktop installieren, aber keinen Service Worker. So trifft ein Deploy nie auf ein altes, gecachtes Bundle. Der Nachteil ist ein fehlender Offline-Modus.",
|
||||
"RESULT": "Heute laufen Auth, Core und das Frontend als eigene Container neben dieser Seite, mit einer PostgreSQL-Instanz mit zwei Datenbanken, nächtlichem Sync und täglichen Dumps. Registrieren kann sich jeder mit bestätigter E-Mail, freigeschaltet wird von Hand. Ich nutze es täglich: Was ich als Nächstes spiele, kommt jetzt aus einer Liste, nicht mehr aus dem Scrollen durch die Steam-Bibliothek."
|
||||
}
|
||||
},
|
||||
"IMPRINT": {
|
||||
|
||||
Reference in new issue
Block a user