Vier neue Kapitel in der Anleitung (mehrere Sammler, Preise, Erinnerungen, Dubletten) mit vier automatisch erzeugten Bildern, Changelog 0.3.0 samt der bewusst weggelassenen BrickLink-Anbindung, Roadmap abgehakt und um einen offenen Ausblick ergaenzt, Devlog mit Zeiten und Stolpersteinen. Dazu zwei Korrekturen aus dem Testlauf: Der Zeitplan fuer Erinnerungen liess sich nicht aus Next' instrumentation.ts starten - die Datei wird auch fuer die Edge-Runtime uebersetzt, und das native SQLite-Modul bricht dort den Build. Er startet jetzt aus dem Healthcheck heraus, den der Container ohnehin alle 30 Sekunden aufruft, und zusaetzlich aus dem App-Layout; der Aufruf ist idempotent. Der Screenshot-Lauf waehlte ploetzlich die falschen Kacheln aus: Der neue Sammler-Umschalter benutzt ebenfalls aria-pressed. Die Auswahlkacheln tragen jetzt ein eigenes data-set-card. Ausserdem raeumen die Testskripte einen Container weg, den ein abgebrochener Lauf zurueckgelassen hat - sonst blockiert er den Port. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
167 lines
10 KiB
Markdown
167 lines
10 KiB
Markdown
# Devlog
|
||
|
||
Zeiterfassung und Nachvollziehbarkeit der Entwicklung. Eine Zeile je
|
||
abgeschlossenem Arbeitsschritt, Zeiten in `HH:mm` (Europe/Berlin). Die
|
||
Commit-Spalte verweist auf den Stand am Ende des Schritts.
|
||
|
||
## Phase 1 – MVP (v0.1.0)
|
||
|
||
### 06.09.2026
|
||
|
||
| Von | Bis | Dauer | Schritt | Ergebnis | Commit |
|
||
|---|---|---|---|---|---|
|
||
| 22:06 | 22:12 | 0:06 | Planung | Phase-1-Plan und Drizzle-Schema zur Freigabe vorgelegt; fünf Entscheidungsfragen geklärt (Cent statt Float, Migrator statt drizzle-kit im Image, Bilder öffentlich / PDFs geschützt, Token gehasht, Branch pro Phase) | – |
|
||
| 22:12 | 22:20 | 0:08 | Scaffold | pnpm installiert, Next 15.5.25 + Tailwind 4 + shadcn/ui (radix-nova) mit 20 Komponenten, Touch-Größen auf 44 px angehoben | `0de49a9` |
|
||
| 22:20 | 22:25 | 0:05 | Datenmodell | 19 Tabellen in Drizzle, CHECK-Constraints, Unique-Index `(wishlist_id, set_id)`, Migration erzeugt und angewendet, WAL verifiziert | `06268bc` |
|
||
| 22:25 | 22:26 | 0:01 | Dokumentation | CLAUDE.md, Roadmap, Changelog, Devlog | `b74cecb` |
|
||
| 22:26 | 22:40 | 0:14 | Auth und App-Shell | better-auth mit Passwort und Passkeys, erster Nutzer wird Admin, Rate-Limit in der Datenbank, Konto-Seite, Bottom-Navigation, `/api/health` | `4c78c56` |
|
||
| 22:40 | 22:46 | 0:06 | Stammdaten | CRUD für Hersteller, Themen und Standorte über eine gemeinsame CrudSection; zod-Schemata, ActionResult, Slug, Geld- und Datumshelfer | `af0ae03` |
|
||
| 22:46 | 22:59 | 0:13 | Katalog | Kachelliste mit Suche und vier Filtern, Besitzlogik in `ownership.ts`, Set-Formular, Detailseite, Upload-Pipeline mit Magic-Byte-Prüfung, pdf.js-Viewer, Seed-Skript | `856800b` |
|
||
| 22:59 | 23:06 | 0:07 | Wunschlisten und Sammlung | Mehrfachauswahl mit Dubletten-Skip, Wunschlisten-Verwaltung, Teilen, Überraschungsmodus, öffentliche Ansicht mit Reservierung und Storno, Sammlungsübersicht mit Statistik | `79b0133` |
|
||
| 23:06 | 23:31 | 0:25 | Tests | 50 Vitest-Unit-Tests, Playwright-Smoke-Test über den ganzen Weg bis zur Reservierung durch einen Besucher ohne Konto | `b0152b8` |
|
||
|
||
### 07.09.2026
|
||
|
||
| Von | Bis | Dauer | Schritt | Ergebnis | Commit |
|
||
|---|---|---|---|---|---|
|
||
| 23:31 | 00:09 | 0:38 | Docker | Multi-Stage-Build, Entrypoint mit Rechteprüfung und Migration, Compose-Stack, Healthcheck, `update.sh` mit Rollback; Image gebaut und Container verifiziert | `61b1249` |
|
||
| 00:09 | 00:50 | 0:41 | Dokumentation und Screenshots | README, QUICKSTART mit Nginx-Proxy-Manager-Konfiguration, bebilderte Anleitung, 16 automatisiert erzeugte Screenshots, Changelog 0.1.0 | folgt |
|
||
|
||
**Summe Phase 1: 2:44**
|
||
|
||
## Phase 2 – Komfort (v0.2.0)
|
||
|
||
### 07.09.2026
|
||
|
||
| Von | Bis | Dauer | Schritt | Ergebnis | Commit |
|
||
|---|---|---|---|---|---|
|
||
| 06:50 | 07:01 | 0:11 | Einladungen | Admin erzeugt einmal gültige Registrierungslinks; Freigabe über AsyncLocalStorage, damit die Rolle nicht aus dem Formular kommen kann. Smoke-Test deckt den ganzen Weg ab | `52d69e5` |
|
||
| 07:01 | 07:12 | 0:11 | Rebrickable-Import | Setnummer genügt: Metadaten, Thema und lokal gespeichertes Bild; 14 Tests gegen gestubbtes `fetch` inklusive aller Fehlerfälle | `4e3cbb8` |
|
||
| 07:12 | 07:15 | 0:03 | Barcode-Scanner | EAN mit der Handykamera, Prüfziffer-Kontrolle, zxing wird erst beim Öffnen geladen | `baa94bf` |
|
||
| 07:15 | 07:18 | 0:03 | Fehlteile | Liste je Sammlungseintrag; Vollständigkeit pflegt sich daraus selbst | `5c5c521` |
|
||
| 07:18 | 07:23 | 0:05 | Export und Import | ZIP, JSON und CSV; Import ersetzt in einer Transaktion, 10 Tests für den Rundweg | `45aa8f1` |
|
||
| 07:23 | 07:27 | 0:04 | PWA | Manifest, Icons aus SVG-Quelle, Service Worker mit Offline-Katalog | `39271f8` |
|
||
| 07:27 | 07:45 | 0:18 | Verifikation und Dokumentation | Smoke-Test, Docker-Build, Changelog 0.2.0, Anleitung und Roadmap | folgt |
|
||
|
||
**Summe Phase 2: 0:55**
|
||
|
||
## Phase 3 – Nice to have (v0.3.0)
|
||
|
||
### 07.09.2026
|
||
|
||
| Von | Bis | Dauer | Schritt | Ergebnis | Commit |
|
||
|---|---|---|---|---|---|
|
||
| 08:35 | 09:00 | 0:25 | Mehrere Sammler | Neue Tabelle, Migration mit Nachziehen der Bestandsdaten, Besitzlogik um eine Dimension erweitert, Umschalter in Katalog und Sammlung, Zuordnung in Sammlungseintrag und Wunschliste | `c23b754` |
|
||
| 09:00 | 09:13 | 0:13 | Dublettenprüfung | Ähnlichkeitsvergleich mit konservativer Schwelle, Warndialog mit Links, E2E-Test | `87131b8` |
|
||
| 09:13 | 09:17 | 0:04 | Preisbeobachtung | Preisreihe je Set, Wertentwicklung gegen die UVP, Kachel „Aktueller Wert" | `91fb66b` |
|
||
| 09:17 | 09:22 | 0:05 | Erinnerungen | SMTP optional, stündliche Prüfung, Sperrfrist je Liste, Verwaltungsseite mit Testfunktionen | `0d08df0` |
|
||
| 09:22 | 09:50 | 0:28 | Verifikation und Dokumentation | Docker-Build, Smoke-Test, Screenshots, Changelog 0.3.0, Anleitung | folgt |
|
||
|
||
**Summe Phase 3: 1:15**
|
||
|
||
---
|
||
|
||
## Notizen und Entscheidungen
|
||
|
||
### Umgebung
|
||
|
||
- **pnpm**: `corepack enable` scheitert auf diesem Rechner an fehlenden
|
||
Schreibrechten in `C:\Program Files\nodejs`. Stattdessen
|
||
`npm install -g pnpm@10`; pnpm liegt unter `%APPDATA%\npm` und ist im PATH.
|
||
- **Port 3000** ist auf dem Entwicklungsrechner von einer anderen Anwendung
|
||
belegt. Der Dev-Server läuft deshalb lokal auf 3100, die Testläufe auf
|
||
3210 und 3220.
|
||
- **`pnpm build` unter Windows** scheitert mit `EPERM … symlink`: Der
|
||
Standalone-Output legt Symlinks in `.next/standalone` an, wofür Windows den
|
||
Entwicklermodus oder administrative Rechte braucht. Der Docker-Build unter
|
||
Linux ist davon nicht betroffen; lokal prüft man mit
|
||
`docker build -t bricktrack:test .`.
|
||
|
||
### Bibliotheken
|
||
|
||
- **shadcn-CLI 4.21** arbeitet mit Presets (`-p nova`) und Base-Libraries
|
||
(`-b radix`) statt der früheren Style-Auswahl. Sie zieht `cn` und `radix-ui`
|
||
als Pakete; `clsx` und `tailwind-merge` werden dadurch nicht mehr direkt
|
||
gebraucht und wurden wieder entfernt. Die Voreinstellungen sind auf
|
||
Desktop-Dichte ausgelegt (Buttons `h-8`) – für die Bedienung am Handy auf
|
||
44 px angehoben.
|
||
- **better-auth 1.7.3**: Das Passkey-Plugin liegt nicht mehr unter
|
||
`better-auth/plugins/passkey`, sondern im eigenen Paket
|
||
`@better-auth/passkey`. Der Drizzle-Adapter löst Modelle über die
|
||
TypeScript-Property-Namen auf, nicht über die Spaltennamen – deshalb können
|
||
auch die Auth-Tabellen durchgehend `snake_case` verwenden.
|
||
- **Drizzle**: Selbstbezügliche Fremdschlüssel (`parent_theme_id`) brauchen
|
||
`AnySQLiteColumn` als Rückgabetyp, nicht `AnyColumn`.
|
||
- **pdf.js** wird lokal gebündelt, inklusive Worker. Kein CDN, damit der
|
||
Browser des Nutzers keine fremden Server kontaktiert.
|
||
|
||
### Stolpersteine
|
||
|
||
- **CRLF im Entrypoint**: Eine unter Windows geschriebene `entrypoint.sh` mit
|
||
CRLF scheitert im Container mit dem irreführenden
|
||
`exec /app/entrypoint.sh: no such file or directory` – der Kernel sucht
|
||
dann `/bin/sh\r`. Behoben mit einer `.gitattributes`, die für Shell-Skripte
|
||
LF erzwingt.
|
||
- **Migrations-Bundle**: Der mit esbuild als ESM gebündelte Migrations-Runner
|
||
scheiterte an `Dynamic require of "fs" is not supported` – mitgebündelte
|
||
CJS-Abhängigkeiten (dotenv) rufen `require()` für Node-Builtins auf. Als
|
||
CommonJS gebündelt läuft es.
|
||
- **Playwright und Reihenfolge**: Playwright startet den Web-Server **vor**
|
||
dem `globalSetup`. Das Löschen des Testdatenverzeichnisses im globalSetup
|
||
scheiterte deshalb unter Windows an der bereits geöffneten SQLite-Datei;
|
||
das Aufräumen läuft jetzt im npm-Skript davor.
|
||
- **Screenshots gegen den Dev-Server** waren nicht reproduzierbar: unter Last
|
||
brach der Stream mit `ChunkLoadError` beziehungsweise
|
||
`Unexpected end of JSON input` ab. Die Bilder entstehen jetzt gegen das
|
||
Produktions-Image im Container – verlässlich und zugleich näher an dem, was
|
||
Nutzer wirklich sehen.
|
||
|
||
### Phase 2
|
||
|
||
- **`server-only` in Tests**: Das Paket wirft beim Import ausserhalb einer
|
||
Server-Komponente, wodurch sich Servermodule nicht testen liessen. In
|
||
`vitest.config.mts` ist es jetzt auf einen leeren Stub gemappt – der Import
|
||
ist eine Build-Zeit-Absicherung, kein Verhalten.
|
||
- **Namenskonflikt `BASE_URL`**: Vite benutzt genau diesen Namen für seinen
|
||
Public-Base-Path und setzt ihn auf `/`. Die Tests überschreiben ihn deshalb
|
||
ausdrücklich. Dabei ist aufgefallen, dass `src/env.ts` bei einem leeren Wert
|
||
abbrach statt auf den Standard zurückzufallen – behoben, denn ein
|
||
`BASE_URL=` in der `.env` ist ein realistischer Tippfehler.
|
||
- **Rebrickable und Anleitungen**: Die API v3 stellt keine Anleitungsdateien
|
||
bereit. Statt das zu verschleiern, legt der Import einen externen Link auf
|
||
die LEGO-Serviceseite zur Setnummer an.
|
||
- **Import statt Zusammenführen**: Zwei Sammlungen zu mischen bräuchte
|
||
Konfliktregeln für jede Tabelle und wäre im Ergebnis nicht vorhersagbar.
|
||
Der Import ersetzt deshalb – dafür mit getipptem `ERSETZEN` als Bremse.
|
||
|
||
### Phase 3
|
||
|
||
- **Migration mit Bestandsdaten**: Die generierte SQL-Datei wurde von Hand um
|
||
drei Anweisungen ergänzt, die einen Sammler anlegen und alles Vorhandene
|
||
zuordnen. Ohne das wären nach dem Update alle Sammlungseinträge ohne
|
||
Besitzer gewesen.
|
||
- **Instrumentation und native Module**: Der Zeitplan für Erinnerungen liess
|
||
sich nicht aus Next' `instrumentation.ts` starten – die Datei wird auch für
|
||
die Edge-Runtime übersetzt, und das native SQLite-Modul bricht dort den
|
||
Build. Der Zeitplan startet jetzt aus dem Healthcheck heraus, den der
|
||
Container ohnehin alle 30 Sekunden aufruft.
|
||
- **BrickLink bewusst weggelassen**: OAuth1 mit einer pro Nutzer registrierten
|
||
Anwendung ist viel Apparat für eine Zahl, die man in zehn Sekunden eintippt.
|
||
Im Plan war der Punkt als optional vermerkt.
|
||
|
||
### Beim Testen gefunden und behoben
|
||
|
||
- Eine spontan aus dem Katalog angelegte Wunschliste war **nicht teilbar**:
|
||
Das Feld `isPublic` kommt aus einer Checkbox und fiel beim programmatischen
|
||
Anlegen auf `false`. Die Schnellanlage setzt es jetzt ausdrücklich.
|
||
- Das Sternchen an Pflichtfeldern gehörte zum zugänglichen Namen des Feldes
|
||
(„Name *"). Jetzt `aria-hidden` – der Pflichtstatus steckt ohnehin im
|
||
`required` des Eingabefeldes.
|
||
- In der Mehrfachauswahl verdeckte die Aktionsleiste die letzte Kachelreihe;
|
||
das Raster bekommt im Auswahlmodus zusätzliches Polster nach unten.
|
||
- Die Sperrfrist der Erinnerungen rechnete mit umgedrehtem Vorzeichen und
|
||
hätte jede Erinnerung dauerhaft unterdrückt. Der Test dafür war zuerst da.
|
||
- Der Screenshot-Lauf wählte plötzlich die falschen Kacheln aus: Der neue
|
||
Sammler-Umschalter benutzt ebenfalls `aria-pressed`. Die Auswahlkacheln
|
||
tragen jetzt ein eigenes `data-set-card`.
|