Commit Graph
17 Commits
Author SHA1 Message Date
StefanandClaude Opus 5 f3ac3d526e Merge branch 'verbesserungen'
Docker-Image bauen / build (push) Successful in 1m44s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-09-14 04:35:34 +02:00
StefanandClaude Opus 5 c9861613cc GZip, Cache-Dauer fuer statische Dateien, Sicherungsskript
GZip: Die Startseite ging unkomprimiert raus - 9725 Byte, jetzt 1900.
Zuletzt angemeldet heisst zuerst ausgefuehrt, die Komprimierung liegt damit
aussen.

Statische Dateien: StaticFiles kennt keine Cache-Dauer, per Unterklasse
nachgeruestet. Bewusst nur eine Stunde - die Dateien tragen keinen
Versionsanhaenger, eine lange Dauer wuerde nach einem Update die alte
Fassung festhalten. (Die hochgeladenen Bilder hatten ihre Header laengst;
der Plan behauptete faelschlich das Gegenteil, korrigiert.)

Sicherung: sicherung.sh sichert Datenbank UND Bilder - eine Datenbank ohne
die Fotos ist wertlos. Pfade werden aus der Anwendung gelesen statt geraten.
Die Pruefung oeffnet die Kopie, nicht das Original. Erprobt: 4 Eintraege,
4 Bilder, vollstaendig; ein entferntes Bild wird erkannt.

81 Tests weiterhin gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-09-13 21:08:44 +02:00
StefanandClaude Opus 5 c748013e4a Plan fuer Verbesserungen aus der Durchsicht vom 13.09.2026
Vorabpruefung der Branches und Tests ist enthalten. Nichts davon ist
umgesetzt - der Plan haelt nur fest, was gefunden wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-09-13 20:54:09 +02:00
StefanandClaude Opus 5 e0212cfa2c Tests von 55 auf 12 Sekunden, Abhängigkeiten nur noch einmal installieren
Docker-Image bauen / build (push) Successful in 1m7s
Der Testlauf war zu langsam. Gemessen statt geraten: 0,68 s Vorbereitung
pro Test, bei 81 Tests praktisch die ganze Laufzeit.

Ursache war bcrypt. Pro Test wird ein Hash gebildet und geprüft, und das
ist absichtlich langsam - genau das soll es im Betrieb sein. Der Aufwand
ist jetzt über BCRYPT_ROUNDS einstellbar (Standard bleibt 12) und in den
Tests auf 4 gesetzt: dort geht es um die Ablauflogik, nicht um die Stärke
des Hashes.

Der Dekompressionsbomben-Test erzeugte ein Bild mit 400 Megapixeln, allein
dafür 4,8 s. Jetzt wird stattdessen die Grenze heruntergesetzt und ein
kleines Bild verwendet - dieselbe Codestelle, ohne die Wartezeit.

Ausserdem installierten Test- und Anwendungs-Image dieselben
Abhängigkeiten zweimal. Dockerfile.test setzt nun auf dem gebauten
Anwendungs-Image auf, der Testschritt kommt entsprechend danach.

Dabei zwei Fallen, die beide auffielen, weil die Tests plötzlich wieder
langsam waren:
- Das Basis-Image bringt seinen eigenen Stand von app/ mit. Ohne erneutes
  Kopieren prüfen die Tests den Code des Basis-Images - ist es veraltet,
  läuft alles gegen alten Code und meldet Erfolg.
- Der ENTRYPOINT des Anwendungs-Images startet eine Datenbank-Migration.
  Für Tests weder nötig noch erwünscht, darum geleert.

Ganze Kette lokal durchgespielt: bauen, testen, Image prüfen - 22 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-30 14:45:24 +02:00
StefanandClaude Opus 5 2a55dcf1ce Tests in der CI über den Build-Kontext statt per Bind-Mount
Docker-Image bauen / build (push) Successful in 1m31s
Der bisherige Schritt hängte das Arbeitsverzeichnis mit -v "$PWD":/app in
einen Container. Läuft der Gitea-Runner selbst in einem Container, zeigt
$PWD auf einen Pfad, den der Docker-Daemon des Hosts nicht kennt: der
Mount wäre leer, pytest fände keine Tests und meldete Erfolg. Wieder ein
Test, der nicht hätte fehlschlagen können.

Jetzt über Dockerfile.test - der Code kommt über den Build-Kontext hinein,
unabhängig davon, wie der Runner aufgebaut ist. tests/ bleibt dafür im
Kontext; das Anwendungs-Image kopiert sie weiterhin nicht mit.

Lokal genauso ausgeführt wie die CI es tun wird: 81 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-30 14:33:00 +02:00
StefanandClaude Opus 5 31f59fd3c8 Phase 5: Gitea Actions, Betriebs-Compose und README
Ein Versions-Tag (v*) löst den Bau aus, ein Push auf main nicht - so bleibt
das Zusammenführen von Branches folgenlos und ein Release ist ein bewusster
Schritt. Die CI baut, testet das FERTIGE Image und veröffentlicht erst
danach; "latest" nur bei echten Tags.

Beim Durchspielen des Prüfschritts zwei Fehler gefunden, die beide erst in
der CI aufgefallen wären - und einer davon gar nicht:

1. "docker exec" ohne -i hängt kein stdin an. Das Here-Dokument lief damit
   ins Leere, python bekam ein leeres Programm, tat nichts und meldete
   Erfolg. Ein Test, der nie hätte fehlschlagen können. Lokal nachgestellt
   und mit -i behoben.
2. Die YAML war ungültig: eingebettete Python-Blöcke standen links vom
   Einzug und brachen den run-Block auf. Das Prüfskript liegt jetzt als
   scripts/image-pruefen.py vor - dadurch lässt es sich auch lokal gegen
   ein gebautes Image laufen lassen - und die Warteschleife ist einzeilig.

Aus der Kantone-App übernommen: github.ref_name geht über env in die
Shell statt direkt als Ausdruck (Tag-Namen dürfen " und $ enthalten, und
der Job hält ein Registry-Token), Anmeldung per --password-stdin, docker
logout mit if: always(), Rechte auf contents:read und packages:write.

docker-compose.betrieb.yml liefert bewusst KEIN Passwort mit: ohne eigenes
bleibt der Erfassungsbereich gesperrt. Genau das war die schwerste Lücke
der Kantone-App.

Ausserdem fehlte eine .dockerignore - der ganze Kontext samt .git und .env
ging an den Docker-Daemon, obwohl das Dockerfile nur gezielt kopiert.

81 Tests grün, der CI-Prüfschritt lokal gegen das gebaute Image
durchgespielt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-30 08:40:57 +02:00
StefanandClaude Opus 5 adedaa9a0d Hell/Dunkel-Umschaltung und Versionsangabe in der Fusszeile
Farbschema folgt zunächst der Systemeinstellung, lässt sich per Knopf in
der Kopfzeile umstellen und wird dann gemerkt. Bei gesperrtem Speicher
(privates Fenster) greift wieder die Systemeinstellung.

theme.js steht bewusst OHNE defer und vor dem Inhalt: es setzt die
Farbklasse, bevor gezeichnet wird. Mit defer blitzte beim Laden kurz die
helle Fassung auf. Eine eigene Datei statt eines <script>-Blocks, weil die
CSP nur script-src 'self' erlaubt - inline wäre blockiert.

Fusszeile zeigt Version oder Commit, gleiches Muster wie die Kantone-App:
die CI reicht beides als Build-Argument durch, lokal erscheint der
Build-Zeitpunkt. Der ändert sich bei jedem Neubau und zeigt damit sofort,
ob wirklich der neue Stand läuft.

Beim Ergänzen der Dunkel-Varianten hatte mein Ersetzer die eben
eingefügten dark:-Klassen selbst nochmal getroffen und daraus Paare wie
"dark:text-slate-300 dark:text-slate-600" gemacht - bereinigt.

81 Tests, alle grün. Neu darunter: die Fusszeile zeigt Tag bzw. Commit,
theme.js steht vor <body> und ohne defer, der Umschalter ist da.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 23:35:35 +02:00
StefanandClaude Opus 5 75b95b38e6 Phase 4: Oberfläche mit Galerie, Erfassung und Reservierung
Jinja2 mit HTMX, Tailwind über die Standalone-Binärdatei im Docker-Build -
kein Node.js, kein npm. Mobile-First, Tippflächen mindestens 44 px hoch.

Seiten: Galerie mit Filterleiste, Detailseite, Erfassungsformular,
Stapel-Import mit Entwurfsliste, Anmeldung. HTMX tauscht beim Filtern nur
die Liste aus statt die ganze Seite neu zu laden.

Aus den Entscheidungen im Plan folgt konkret:

- Der Stapel-Import steht ZUERST im Formular: wer zwanzig Teile hat, will
  nicht zwanzigmal ein Formular ausfüllen. Bewusst ohne capture-Attribut,
  damit sich wahlweise fotografieren oder aus der Mediathek wählen lässt -
  mit capture ginge nur je ein Foto pro Vorgang.
- Grösse, Kategorie, Geschlecht, Saison und Zustand werden vom zuletzt
  gespeicherten Teil übernommen. Beim Abarbeiten einer Kiste sind das fünf
  Felder weniger pro Stück.
- Nach dem Nachtragen eines Entwurfs geht es direkt zum nächsten, nicht auf
  die Detailseite - das ist der eigentliche Arbeitsablauf.
- Titel bleibt leer erlaubt; angezeigt wird dann Kategorie und Grösse.
- reserved_by erscheint öffentlich nur als "reserviert". Den Namen sieht
  ausschliesslich der angemeldete Betreiber.
- Nach dem Reservieren wird der Freigabe-Link angezeigt, als vollständige
  Adresse zum Aufbewahren.
- Post/Redirect/Get überall, damit ein Neuladen nicht erneut abschickt.
- CSRF-Token in jedem Formular, zusätzlich zu SameSite=Lax.

HTMX liegt lokal im Repository, nicht vom CDN: die CSP erlaubt nur
script-src 'self', und ein CDN-Aufruf gäbe die IP jedes Besuchers weiter -
dieselbe Überlegung wie bei Swagger und bei den Schriften der Kantone-App.
Ein Test prüft für alle Seiten, dass nichts von aussen geladen wird.

Nebenbei TemplateResponse auf die aktuelle Signatur umgestellt (64
Deprecation-Warnungen weniger).

77 Tests, alle grün. Zusätzlich der ganze Weg gegen den Container:
anmelden, drei Fotos als Stapel, Entwurf nachtragen, Werte werden für das
nächste Teil übernommen, als Gast reservieren, Name bleibt verborgen,
Selbst-Freigabe über den Link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 23:29:56 +02:00
StefanandClaude Opus 5 01cd0a73e8 Weisse Seite beheben: Swagger lokal ausliefern, Startseite ergänzen
Zwei Fehler, beide erst im Browser sichtbar:

1. /docs blieb weiss. FastAPI holt Swagger von cdn.jsdelivr.net, und die
   in Phase 3 eingeführte CSP (script-src 'self') blockiert das zu Recht.
   Die Dateien liegen jetzt unter app/static/swagger/ im Repository - das
   funktioniert auch ohne Internet und ruft keinen fremden Server auf, aus
   demselben Grund, aus dem die Kantone-App ihre Schriften lokal ausliefert.
   Das Favicon holte FastAPI ebenfalls von aussen; auch das ist jetzt lokal.

2. Die Wurzel / lieferte ein nacktes 404-JSON - im Browser sieht das aus
   wie eine kaputte Anwendung. Jetzt eine schlichte Platzhalter-Seite, bis
   die Galerie in Phase 4 steht.

Ausserdem beim Testen aufgefallen: eine lokale .env-Datei reicht
ADMIN_PASSWORD auch dann herein, wenn die Umgebungsvariable gelöscht wird -
bei pydantic-settings haben Umgebungsvariablen Vorrang, ein leerer Wert
überstimmt die Datei, ein Löschen nicht. Der Test setzt jetzt leer statt zu
löschen; im Betrieb ist das dieselbe Falle.

Der neue Test prüft, dass /docs keinen einzigen Verweis auf einen fremden
Host enthält - genau das würde sonst unbemerkt wieder hereinrutschen.

61 Tests, alle grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 23:17:42 +02:00
StefanandClaude Opus 5 99ba74330c Phase 3: Anmeldung, Rate-Limits und Sicherheits-Header
Lesen darf jeder, schreiben nur der Betreiber - mit der bewussten Ausnahme
des Reservierens. Dazu Dockerfile und Compose-Datei, damit sich das lokal
ausprobieren lässt.

Beim Bauen sind zwei Fehler aufgefallen, die ohne Test nicht aufgefallen
wären:

1. slowapi zählt pro URL-Pfad. Weil jedes Kleidungsstück eine eigene URL
   hat, bekam jedes seinen eigenen Zähler - ein Skript hätte also den
   gesamten Bestand reservieren können, ohne je an ein Limit zu stossen.
   Genau der Missbrauch, gegen den das Limit gedacht ist. Behoben mit
   shared_limit und festem scope.
2. Der erste Anlauf des Tests machte fünf Anfragen gegen ein Limit von
   fünf und konnte damit gar nichts zeigen. Geprüft wird jetzt der
   tatsächlich ausgelieferte Standardwert, mit mehr Anfragen als erlaubt.

Weiter umgesetzt:

- Passwort als bcrypt-Hash aus der Umgebung, einmal beim Start gebildet
  und gemerkt. Bei jeder Anfrage neu gehasht liesse sich die Anwendung
  sonst ohne Anmeldung lahmlegen - bcrypt ist absichtlich langsam.
- Ohne hinterlegtes Passwort bleibt der Erfassungsbereich gesperrt (503)
  statt offen zu stehen. Kein mitgeliefertes Standardpasswort.
- Sitzung als signiertes Cookie, HttpOnly, SameSite=Lax (blockt
  seitenfremde POSTs), Secure abschaltbar nur fürs lokale Testen,
  Abmeldung nach zwei Stunden Ruhe.
- CSP mit script-src 'self', nosniff, frame-ancestors none, dazu noindex
  und robots.txt: die Galerie ist frei zugänglich, soll aber nicht
  dauerhaft im Suchindex stehen.
- Der Betreiber darf Reservierungen ohne Token aufheben, damit sich eine
  Missbrauchswelle aufräumen lässt.
- uvicorn mit --proxy-headers: hinter einem Reverse-Proxy zählte sonst
  alles auf dessen IP, und ein einzelner Besucher sperrte alle aus.
- Container läuft nicht als root; Code gehört root, nur Daten und Bilder
  dem Dienstbenutzer.

Umgebungsvariablen heissen jetzt ausdrücklich englisch (ADMIN_PASSWORD,
SECRET_KEY, ...), passend zur Anleitung und zur Kantone-App.

59 Tests, alle grün. Zusätzlich gegen den laufenden Container geprüft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 23:00:23 +02:00
StefanandClaude Opus 5 1d122b33f9 Phase 2: Bild-Upload, WebP-Konvertierung und Stapel-Import
Uploads werden geprüft, nach WebP gewandelt (max. 1200 px Breite) und über
eine eigene Route ausgeliefert. Dazu der Stapel-Import: viele Fotos auf
einmal hochladen, pro Foto entsteht ein Entwurf.

Der Upload ist die grösste Angriffsfläche der Anwendung - die Galerie ist
frei zugänglich, und hier landet fremdes Binärmaterial auf der Platte.
Entsprechend:

- Der Dateiname wird selbst erzeugt (UUID + .webp), nie aus dem Upload
  übernommen. Beim Ausliefern wird er zusätzlich gegen ein Muster geprüft:
  er kommt dort aus der URL und ist damit Nutzereingabe, auch wenn wir ihn
  ursprünglich vergeben haben.
- Der Inhalt entscheidet, nicht die Endung - Pillow muss das Bild öffnen
  können. Eine als .jpg getarnte PHP-Datei fällt durch.
- Pixelmasse ausdrücklich begrenzt, VOR dem Dekodieren. Pillow allein
  wirft erst bei der doppelten Zahl einen Fehler; dazwischen gibt es nur
  eine Warnung, die niemand sieht.
- Gespeichert wird nur das Ergebnis. Das entfernt eingebettete
  Fremdinhalte und die EXIF-Daten - bei Handyfotos oft samt
  GPS-Koordinaten der Wohnung. Die Drehung wird vorher auf die Bildpunkte
  angewendet, sonst lägen Hochkant-Fotos quer.
- Bilder liegen nicht unter static/, sondern werden mit festem
  Content-Type und nosniff ausgeliefert.

HEIC wird unterstützt (pillow-heif): iPhones nehmen so auf, und ohne das
fiele beim Stapel-Import jedes iPhone-Foto durch. Ein unlesbares Bild
lässt bewusst den ganzen Stapel scheitern - bei zwanzig Fotos fiele sonst
niemandem auf, dass eines fehlt.

Beim Löschen werden die Dateien mitentfernt (ON DELETE CASCADE räumt nur
die Zeilen weg), und wird das Vorschaubild gelöscht, rückt das nächste
nach.

42 Tests, alle grün. Zusätzlich von Hand gegen die laufende API geprüft:
3000x2200-Foto kommt als WebP 1200x880 an, Pfadmanipulation und getarnte
Dateien werden abgewiesen, Entwürfe bleiben aus der Galerie draussen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 22:25:06 +02:00
StefanandClaude Opus 5 3d7b7c706a Phase 1: Datenmodell, CRUD-API und Stammdaten
FastAPI mit SQLAlchemy und SQLite, Migrationen über Alembic. Die
Endpunkte aus projekt.md für items, categories und sizes stehen samt
Reservierung; Bilder und Anmeldung folgen in Phase 2 und 3.

Umgesetzt wie in Plan.md festgelegt:

- Feste Wertelisten als Enum UND als CHECK in der Datenbank. Die
  CHECK-Bedingung ist der eigentliche Schutz: an SQLAlchemy vorbei (Import,
  sqlite3 von Hand) käme sonst "Gril" durch, und die Filter griffen still
  nicht mehr.
- Status "draft" für den Stapel-Import. Entwürfe erscheinen weder in der
  Galerie noch in GET /items, solange nicht ausdrücklich status=draft
  angefragt wird, und lassen sich nicht reservieren.
- Titel ist freiwillig und wird sonst beim ANZEIGEN aus Kategorie und
  Grösse gebildet ("Jacken 98/104") - nicht beim Speichern, damit er einer
  späteren Korrektur der Grösse folgt.
- reservation_token: nur wer es hat, kann die eigene Reservierung aufheben.
  Verglichen mit compare_digest, gelöscht beim Freigeben und beim
  Erledigen, damit ein alter Link nicht später eine fremde Reservierung
  aufhebt.
- Eigene sizes-Tabelle mit sort_order statt SELECT DISTINCT: sonst stünde
  "104" vor "56" und jeder Tippfehler würde zur Filteroption.
- Pagination auf GET /items, in projekt.md nicht vorgesehen.

Zwei SQLite-Eigenheiten, die leicht untergehen: foreign_keys ist
standardmässig AUS (ohne PRAGMA greift ON DELETE CASCADE nicht), und
check_same_thread muss für FastAPI abgeschaltet werden. Beides in
database.py, dazu WAL fürs gleichzeitige Lesen.

24 Tests, alle grün. Zusätzlich von Hand geprüft: Migration anwenden,
Eintrag anlegen, reservieren, ohne Token freigeben (403), mit Token
freigeben (200), Swagger UI erreichbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 22:00:58 +02:00
StefanandClaude Opus 5 3cf6903df5 Reservierung aufheben und offene Galerie entschieden
- Aufheben darf der Reservierende selbst (per Token-Link) und der
  Betreiber. Ein offener release-Endpunkt wäre die naheliegende, aber
  falsche Variante: dann löscht jeder Besucher fremde Reservierungen, und
  weil reserved_by mitgeht, bleibt nicht mal nachvollziehbar, dass jemand
  reserviert hatte. Token wird beim Aufheben und beim Erledigen gelöscht,
  Vergleich mit compare_digest.
- Die Galerie bleibt frei zugänglich, ohne Zugangscode. Damit wird das
  Rate-Limit zur einzigen Bremse vor dem Reservieren-Endpunkt, und zwei
  Dinge gehören zwingend dazu: reserved_by erscheint öffentlich nur als
  "reserviert", und noindex/robots.txt verhindern, dass Fotos und Texte
  dauerhaft im Suchindex landen.

projekt.md bei /release entsprechend präzisiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 21:50:34 +02:00
StefanandClaude Opus 5 da4267c369 Stapel-Import und automatischer Titel
Beim Erstbestand sind viele Teile auf einmal zu erfassen. Der Aufwand
steckt nicht im Fotografieren, sondern im Rundlauf pro Stück. Darum:

- Stapel-Import: erst alles mit der Kamera-App fotografieren, dann alle
  Fotos auf einmal hochladen. Pro Foto entsteht ein Entwurf, die Details
  kommen später. Bewusst ohne capture="environment" - das erzwingt ein
  Foto pro Vorgang und schliesst multiple aus.
- Titel wird optional und sonst aus Kategorie und Grösse gebildet
  ("Hose 98/104"). Erzeugt beim Anzeigen, nicht beim Speichern, damit er
  einer späteren Korrektur der Grösse folgt.

Daraus folgt ein neuer Status "draft" im Datenmodell; title und size
müssen NULL erlauben. Entwürfe erscheinen nicht in der Galerie.

Ausserdem HEIC in Phase 2 aufgenommen: iPhones nehmen so auf, und beim
Stapel-Import fiele ein nicht lesbares Bild sonst still hinten runter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 21:45:29 +02:00
StefanandClaude Opus 5 c8f11e0642 Bauen über Gitea Actions in die Roadmap aufnehmen
Fehlte in Phase 4: das Image soll wie bei der Kantone-App über einen
Versions-Tag gebaut, vor dem Veröffentlichen getestet und in die Gitea-
Registry gestellt werden.

Der Plan hält dazu die Punkte fest, die dort nachträglich korrigiert werden
mussten - vor allem, Werte aus ${{ ... }} nicht direkt in die Shell zu
schreiben, weil Tag-Namen Sonderzeichen enthalten dürfen und der Job ein
Registry-Token hält.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 21:29:21 +02:00
StefanandClaude Opus 5 198108ff5f Zeilenenden auf LF festlegen
Gilt unabhängig vom lokalen core.autocrlf und damit auch für Klone auf
anderen Rechnern. Wichtig, sobald Shell-Skripte im Container laufen: mit
CRLF scheitern die dort an "bad interpreter: /bin/sh^M".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 21:15:44 +02:00
StefanandClaude Opus 5 d5bb83a94f Projektbeschrieb und Umsetzungsplan
projekt.md beschreibt die Kinderkleider-Börse (FastAPI, SQLite, Bild-Upload
mit WebP-Konvertierung). Plan.md ergänzt die dort offenen Entscheidungen:
Frontend-Variante A (Jinja2 + HTMX, kein Node.js), Erfassen hinter Passwort,
Reservieren ohne Anmeldung, erreichbar aus dem Internet.

Aus dieser Kombination folgt der Schwerpunkt des Plans - ein offener,
zustandsändernder Endpunkt im Internet und ein Bild-Upload sind die beiden
Angriffsflächen, die den Ausschlag geben.

Noch kein Code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 21:03:10 +02:00