test: smoke-test gegen das docker-image, dazu die doku zu phase 2
Der Dev-Server verlor unter der Last eines ganzen Durchlaufs reproduzierbar Chunks (ChunkLoadError beim Laden von app/layout), was sich weder durch Aufwaermen noch durch laengere Zeitlimits stabilisieren liess. Der Smoke-Test faehrt jetzt - wie der Screenshot-Lauf schon vorher - das Produktions-Image hoch. Ergebnis: 29 Sekunden statt fuenf Minuten, keine Aussetzer, und getestet wird genau das Artefakt, das spaeter deployt wird. Dabei behoben: Der Fehlteile-Dialog hielt eine Kopie der Liste aus dem Moment des Oeffnens und zeigte ein gerade hinzugefuegtes Teil nicht an. Er liest jetzt den aktuellen Eintrag aus den Props statt aus dem Zustand. Gefunden hat das der erweiterte Screenshot-Lauf. Dokumentation zu Phase 2: Changelog 0.2.0, Roadmap abgehakt, Devlog mit Zeiten, CLAUDE.md um die neuen Module und Testkonventionen ergaenzt und die Anleitung um fuenf Kapitel (Import, Scanner, Fehlteile, Einladungen, Daten, Startbildschirm) - mit vier neuen, automatisch erzeugten Bildern. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@@ -22,7 +22,12 @@ Kinderzimmer, nicht für den Schreibtisch.
|
||||
8. [Wunschlisten](#8-wunschlisten)
|
||||
9. [Teilen – so sehen es Oma und Tante Anne](#9-teilen)
|
||||
10. [Passkeys einrichten](#10-passkeys-einrichten)
|
||||
11. [Antworten auf die zwei wichtigen Fragen](#11-die-zwei-fragen)
|
||||
11. [Sets im Schnellverfahren: Import und Scanner](#11-import-und-scanner)
|
||||
12. [Fehlteile](#12-fehlteile)
|
||||
13. [Noch ein Konto für die Familie](#13-einladungen)
|
||||
14. [Daten sichern, umziehen, auswerten](#14-daten)
|
||||
15. [Auf den Startbildschirm legen](#15-startbildschirm)
|
||||
16. [Antworten auf die zwei wichtigen Fragen](#16-die-zwei-fragen)
|
||||
|
||||
---
|
||||
|
||||
@@ -291,7 +296,130 @@ gib ihnen sprechende Namen.
|
||||
|
||||
---
|
||||
|
||||
## 11. Die zwei Fragen
|
||||
## 11. Import und Scanner
|
||||
|
||||
Sets von Hand einzutippen ist der langsame Weg. Für Lego gibt es zwei
|
||||
schnellere.
|
||||
|
||||
### Lego-Import über die Setnummer
|
||||
|
||||

|
||||
|
||||
Auf **Katalog → Set anlegen** steht oben das Feld für den Import. Setnummer
|
||||
von der Packung eintippen, „Holen" – und Name, Jahr, Teile, Minifiguren und
|
||||
Thema stehen drin. Das Bild wird **lokal gespeichert**, funktioniert also auch
|
||||
weiter, wenn die Quelle es irgendwann austauscht. Fehlen Hersteller oder
|
||||
Thema noch, legt der Import sie an.
|
||||
|
||||
Dafür braucht es einen kostenlosen API-Key von
|
||||
[rebrickable.com/api](https://rebrickable.com/api/), eingetragen als
|
||||
`REBRICKABLE_API_KEY` in der `.env`. Ohne Key steht an derselben Stelle ein
|
||||
Hinweis, und alles andere funktioniert unverändert weiter.
|
||||
|
||||
> Anleitungsdateien liefert Rebrickable nicht. Der Import legt stattdessen
|
||||
> einen Link auf die LEGO-Serviceseite zur Setnummer an – von dort kommst du
|
||||
> zum PDF, das du dann hochladen kannst.
|
||||
|
||||
### Barcode scannen
|
||||
|
||||
Im Katalog sitzt neben „Set anlegen" ein Scanner-Symbol. Strichcode der
|
||||
Packung ins Bild halten:
|
||||
|
||||
- **Set schon im Katalog** → es öffnet sich direkt. Genau das, was man im
|
||||
Laden vor dem Regal wissen will.
|
||||
- **Noch unbekannt** → das Formular „Neues Set" öffnet sich mit
|
||||
ausgefüllter EAN.
|
||||
|
||||
Gelesen wird ausschließlich im Browser, es geht nichts an einen fremden
|
||||
Dienst. Jeder Code wird gegen seine Prüfziffer geprüft – verlesene Ziffern
|
||||
landen so gar nicht erst im Katalog. Die Kamera braucht wie Passkeys HTTPS.
|
||||
|
||||
---
|
||||
|
||||
## 12. Fehlteile
|
||||
|
||||

|
||||
|
||||
Beim Aufbauen fehlt ein Stein. Auf der Set-Detailseite hat jeder
|
||||
Sammlungseintrag ein Puzzle-Symbol: dort trägst du ein, was fehlt – mit
|
||||
Teilenummer und Farbe, wenn du sie kennst, sonst reicht eine Notiz wie
|
||||
„Dachstein vorne links".
|
||||
|
||||
Ist das Teil nachgekauft, hakst du es ab. Die **Vollständigkeit pflegt sich
|
||||
dabei selbst**: Der erste offene Eintrag setzt den Sammlungseintrag auf
|
||||
„Teile fehlen", der letzte erledigte zurück auf „vollständig". Der Filter in
|
||||
der Sammlung stimmt damit, ohne dass du daran denken musst.
|
||||
|
||||
---
|
||||
|
||||
## 13. Einladungen
|
||||
|
||||

|
||||
|
||||
Die Registrierung ist nach dem ersten Konto geschlossen – sonst könnte sich
|
||||
jeder, der die Adresse kennt, ein Konto anlegen. Für den zweiten Elternteil
|
||||
gibt es deshalb **Verwaltung → Einladungen**:
|
||||
|
||||
1. „Einladung erstellen", optional an eine E-Mail-Adresse gebunden, mit Rolle
|
||||
und Gültigkeit.
|
||||
2. Den Link kopieren und weitergeben. Er wird **nur einmal angezeigt** – in
|
||||
der Datenbank liegt nur sein Hash.
|
||||
3. Die eingeladene Person legt darüber ihr Konto an. Danach ist der Link
|
||||
verbraucht.
|
||||
|
||||
„Mitglied" darf die Sammlung pflegen und Wunschlisten anlegen; „Administrator"
|
||||
zusätzlich einladen und Daten exportieren.
|
||||
|
||||
---
|
||||
|
||||
## 14. Daten
|
||||
|
||||

|
||||
|
||||
Unter **Verwaltung → Daten** gibt es drei Downloads:
|
||||
|
||||
| | Wofür |
|
||||
|---|---|
|
||||
| **Alles als ZIP** | Umziehen auf einen anderen Server, zweites Netz |
|
||||
| **Nur Daten (JSON)** | Zum Reinschauen oder Weiterverarbeiten |
|
||||
| **Sammlung als CSV** | Für Excel oder LibreOffice, Semikolon-getrennt |
|
||||
|
||||
**Nicht im Export:** Konten, Passwörter und Passkeys. Ein Export voller
|
||||
Passwort-Hashes im Download-Ordner wäre ein Risiko, kein Backup. Ebenso außen
|
||||
vor: Reservierungen und Teilen-Links – die gehören zu der Instanz, auf der sie
|
||||
entstanden sind.
|
||||
|
||||
> **Das richtige Backup bleibt `./data`.** Ein Verzeichnis, Datenbank und
|
||||
> Dateien darin. Siehe [README](../README.md#backup-und-restore).
|
||||
|
||||
Der **Import** liest ein solches ZIP wieder ein – und **ersetzt dabei alle
|
||||
vorhandenen Daten**. Zusammenführen zweier Sammlungen ist bewusst nicht
|
||||
möglich: Es bräuchte Konfliktregeln für jede Tabelle, und das Ergebnis wäre
|
||||
nicht vorhersagbar. Deshalb muss man zur Bestätigung `ERSETZEN` eintippen.
|
||||
Wunschlisten gehören danach dir und sind zunächst nicht geteilt.
|
||||
|
||||
---
|
||||
|
||||
## 15. Startbildschirm
|
||||
|
||||
Bricktrack lässt sich wie eine App installieren:
|
||||
|
||||
- **iPhone/iPad (Safari):** Teilen-Symbol → „Zum Home-Bildschirm".
|
||||
- **Android (Chrome):** Menü → „App installieren" bzw. „Zum Startbildschirm
|
||||
hinzufügen".
|
||||
|
||||
Danach startet Bricktrack ohne Browserleiste, und langes Drücken auf das Symbol
|
||||
springt direkt zu „Fehlende Sets" oder zur Sammlung.
|
||||
|
||||
**Ohne Verbindung** bleibt lesbar, was du schon einmal geöffnet hast – der
|
||||
Katalog also, wenn du ihn vorher im WLAN durchgeblättert hast. Ändern lässt
|
||||
sich offline nichts: Eine Reservierung, die nur auf deinem Gerät liegt, wäre
|
||||
für alle anderen unsichtbar und damit eine Lüge. Beim Abmelden räumt die App
|
||||
den Zwischenspeicher, damit auf einem geteilten Gerät nichts lesbar bleibt.
|
||||
|
||||
---
|
||||
|
||||
## 16. Die zwei Fragen
|
||||
|
||||
Dafür ist die ganze App gebaut:
|
||||
|
||||
@@ -302,8 +430,8 @@ Dafür ist die ganze App gebaut:
|
||||
→ Katalog, Filter „Fehlt noch". Von dort in zwei Handgriffen auf eine
|
||||
Wunschliste und per Link zu Oma.
|
||||
|
||||
Im Laden vor dem Regal: Katalog öffnen, Setnummer von der Packung eintippen.
|
||||
Steht ein grüner Haken dran, habt ihr es schon.
|
||||
Im Laden vor dem Regal: Scanner öffnen, Strichcode einlesen. Steht ein grüner
|
||||
Haken dran, habt ihr es schon.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -29,6 +29,22 @@ Commit-Spalte verweist auf den Stand am Ende des Schritts.
|
||||
|
||||
**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**
|
||||
|
||||
---
|
||||
|
||||
## Notizen und Entscheidungen
|
||||
@@ -86,6 +102,24 @@ Commit-Spalte verweist auf den Stand am Ende des Schritts.
|
||||
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.
|
||||
|
||||
### Beim Testen gefunden und behoben
|
||||
|
||||
- Eine spontan aus dem Katalog angelegte Wunschliste war **nicht teilbar**:
|
||||
|
||||
@@ -34,17 +34,17 @@ Laeuft vollstaendig im Docker-Stack.
|
||||
- [x] Dokumentation: README, QUICKSTART, bebilderte Anleitung,
|
||||
update.sh, Nginx-Proxy-Manager-Konfiguration
|
||||
|
||||
## Phase 2 - Komfort (Ziel: v0.2.0)
|
||||
## Phase 2 - Komfort (v0.2.0, abgeschlossen am 07.09.2026)
|
||||
|
||||
- [ ] Rebrickable-Import: Setnummer eingeben, Metadaten und Bild
|
||||
- [x] Rebrickable-Import: Setnummer eingeben, Metadaten und Bild
|
||||
uebernehmen, Bild lokal cachen. Ohne API-Key bleibt alles manuell.
|
||||
- [ ] Barcode-Scanner (EAN) per Handykamera, Suche nach EAN, bei
|
||||
- [x] Barcode-Scanner (EAN) per Handykamera, Suche nach EAN, bei
|
||||
Unbekanntem direkt ins Formular "Neues Set"
|
||||
- [ ] Fehlteile je Sammlungseintrag pflegen
|
||||
- [ ] Einladungslinks durch den Admin
|
||||
- [ ] Export/Import: gesamte Datenbank als JSON, Dateien als ZIP,
|
||||
- [x] Fehlteile je Sammlungseintrag pflegen
|
||||
- [x] Einladungslinks durch den Admin
|
||||
- [x] Export/Import: gesamte Datenbank als JSON, Dateien als ZIP,
|
||||
Sammlung zusaetzlich als CSV
|
||||
- [ ] PWA: Manifest, Service Worker, Katalog offline lesbar
|
||||
- [x] PWA: Manifest, Service Worker, Katalog offline lesbar
|
||||
|
||||
## Phase 3 - Nice to have (Ziel: v0.3.0)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 257 KiB |