chore: Spezifikation und Umsetzungsplan

This commit is contained in:
vchuser
2026-07-28 14:07:51 +02:00
commit 82898a1539
6 changed files with 2855 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules/
dist/
.DS_Store
.superpowers/
+1
View File
@@ -0,0 +1 @@
Describe your project here.
+114
View File
@@ -0,0 +1,114 @@
# Project
## Environment
You are working inside a VCH cloud development environment.
- **OS:** Debian-based LXC container
- **Editor:** code-server (VS Code in browser) on port 8080
- **CLI:** Claude Code is available as `claude` in terminal
- **Git:** Pre-configured. Gitea integration available through VCH.
- **Preview:** Start a dev server on any port — VCH detects listening ports automatically and provides preview URLs with SSL.
- **Publishing:** Assign a public subdomain to any port via the VCH Publish page (automatic SSL).
- **User:** `vchuser` with home at `/home/vchuser`
## Preview Access (IMPORTANT)
Apps are accessed through a **reverse proxy**, not directly. There are two modes:
1. **Subdomain proxy** (preferred): `https://lxc{VMID}-{PORT}.dev.example.com/` — your app is at the domain root, everything works normally.
2. **Path-based proxy** (fallback): `https://host/proxy/{INSTANCE}/p/{PORT}/` — your app is behind a path prefix.
### Rules for portable apps that work in both modes:
- **ALWAYS use relative paths** for assets, API calls, and links:
- `css/style.css` ✅ — NOT `/css/style.css`
- `api/users` ✅ — NOT `/api/users`
- `fetch('api/data')` ✅ — NOT `fetch('/api/data')`
- **Static file serving:** Configure your server to serve from the request path, not the filesystem root.
- **`<base>` tag:** If you must use absolute paths, add `<base href="./">` in `<head>`.
### Framework-specific configuration:
- **Express:** `app.use(express.static('public'))` works — just ensure HTML references are relative.
- **Vite:** Set `base: './'` in `vite.config.ts`.
- **Next.js:** Set `basePath` in `next.config.js` if using path-based proxy, or leave default for subdomain proxy.
- **Create React App:** Set `"homepage": "."` in `package.json`.
## Development Conventions
- Write clear, descriptive commit messages (imperative mood: "Add feature", not "Added feature")
- Prefer small, focused commits over large ones
- Run linters and formatters before committing
- Write tests for critical business logic
- Keep dependencies minimal — use native browser/Node APIs where possible
## README Requirement (MANDATORY)
Every project MUST have a meaningful `README.md` in the project root. This is enforced by the VCH audit system — projects without a proper README cannot be published.
Your README must include at minimum:
- **Project description** — what the project does (not just the template placeholder)
- **Installation instructions** — how to install dependencies
- **Development instructions** — how to start the dev server
- **Tech stack** — what technologies are used
Update the README whenever you add features, change setup steps, or modify the tech stack. The audit will reject READMEs that are still just the default template.
## Project Description (MANDATORY)
Every project has a `.vch-description` file in the project root. Keep this file up to date with a clear, non-technical description of your project:
- What the project does
- What problem it solves or what it's used for
- Who it's for
Do NOT include technical details (tech stack, dependencies, setup instructions) — those belong in the README. The `.vch-description` content is shown in the VCH Showcase and is synced automatically when an audit runs.
Update `.vch-description` whenever the project's purpose or scope changes significantly.
## Web Best Practices
- Use semantic HTML elements (`nav`, `main`, `article`, `section`, etc.)
- Mobile-first responsive design
- Follow accessibility guidelines (ARIA labels, keyboard navigation, color contrast)
- Optimize images and assets for performance
- Use environment variables for configuration — never hardcode secrets
## Commands
Fill in project-specific commands below:
- **Install dependencies:** `npm install`
- **Start dev server:** `npm run dev`
- **Run tests:** `npm test`
- **Build for production:** `npm run build`
- **Lint:** `npm run lint`
## Deploy preparation
If this project should be deployed and reads environment variables from `process.env` (or equivalent), create `.env.example` at the repo root. List every variable the app reads, one per line: `KEY=example-value`. Example values are hints only — they are **not** used in production. If the project needs no env vars, no file is required.
Vibecoders' tip: when in doubt, run `grep -RhoE "process\.env\.[A-Z_]+" src/ | sort -u` and put every result into `.env.example`.
**Listen on `PORT` (IMPORTANT).** VCH assigns every deployed app its own host port automatically — you never pick or coordinate ports, and two projects on the same machine never clash. Your app MUST bind the port from the `PORT` environment variable, not a hardcoded one:
- Node/Express: `app.listen(process.env.PORT || 3000)`
- Next.js: `next start` honours `PORT` automatically
- Vite preview / other servers: pass `--port "$PORT"` (or read `process.env.PORT`)
A hardcoded port works locally but fails the production health-check (VCH checks the assigned port, which usually isn't 3000). For Docker, set `port:` below to the port your app listens on *inside* the container — VCH maps the auto-assigned host port to it for you.
**Containerized projects (Docker).** If your project has a `Dockerfile` (or a `compose.yaml`/`docker-compose.yml`), add a `.vch/deploy.yaml` at the repo root so VCH deploys it correctly:
```yaml
runtime: docker
port: 3000 # the port your app LISTENS ON inside the container
health: / # a path that returns 2xx/3xx when the app is up
```
- VCH builds your image, runs the container with `--restart=always`, injects production env vars via `--env-file` at runtime, and health-checks `port`.
- **Build-time variables:** only variables prefixed `NEXT_PUBLIC_`, `VITE_`, or `PUBLIC_` are passed to the build as `--build-arg` (they are client-visible by convention). Secrets are runtime-only and are never baked into the image — declare matching `ARG` lines in your Dockerfile for the public ones.
- **Compose:** the app service MUST publish its port as `ports: ["${VCH_PORT}:<internal>"]`. VCH sets `VCH_PORT` so it can run an isolated candidate stack during audits without touching your live stack. Add `env_file: [.env]` to any service that needs production env vars — VCH writes your production variables to a `.env` file next to your compose file.
- **VCH runs exactly ONE compose file — no `-f` override chains.** VCH auto-discovers a single compose file (`compose.yaml`/`compose.yml`/`docker-compose.yaml`/`docker-compose.yml`) at the repo root **or in a subfolder** (e.g. `docker/`) and runs only that file. A separate override such as `docker-compose.ports.yml` or `*.override.yml` is **ignored** — put the `${VCH_PORT}` port mapping in the *main* compose file, not in an override. If several matching compose files exist, choose one explicitly with `compose: <path>` in `.vch/deploy.yaml`.
- **The whole compose starts.** VCH brings up the entire compose project, so DB/cache/auth services your compose defines itself (Postgres, Redis, etc.) start automatically — you do **not** also need a separate managed database for them. (Services behind a compose `profiles:` key stay off unless you activate the profile.)
- If you omit `.vch/deploy.yaml`, VCH falls back to the Dockerfile `EXPOSE` port (or `3000`).
+37
View File
@@ -0,0 +1,37 @@
# ocr_scanner
> Short description of what this project does.
## Getting Started
### Prerequisites
- Node.js 20+
- npm or pnpm
### Installation
```bash
npm install
```
### Development
```bash
npm run dev
```
## Tech Stack
- ...
## Project Structure
```
src/
...
```
## License
...
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,253 @@
# RAM-Sortierhilfe — Design
**Datum:** 2026-07-28
**Status:** Freigegeben, bereit für Umsetzungsplan
## Zweck
Eine Sortierhilfe für gebrauchte Server-RAM-Module. Beim Wareneingang werden Module
einzeln gescannt; die App sagt sofort, auf welchen Stapel das Modul gehört. Ziel ist
das physische Sortieren am Tisch — aus einer gemischten Charge entstehen Stapel
identischer Module, die als Kit verkauft werden können.
Die App ist eine reine Sortierhilfe. Nach der Sitzung werden die Daten nicht weiter
gebraucht.
## Ausgangslage
Der vorhandene Netum 2D Tischscanner liest ausschließlich Barcodes und gibt dekodierte
Strings als Tastatureingabe aus. Er stellt kein Rohbild bereit, kann also kein OCR
leisten. Klartext auf Etiketten muss über eine Kamera erfasst werden.
Die Ware ist gemischt: teils Hersteller-Etiketten mit lesbarem Barcode (Samsung, Hynix,
Micron), teils OEM-überklebt (Oracle, HP, Dell, Lenovo), wobei die Hersteller-PN dann
verdeckt sein kann.
## Nicht im Umfang
- Bestandsführung über Sitzungen hinweg
- Ein- und Ausbuchen, Verkaufsabwicklung
- Export nach CSV, Excel oder in ein Warenwirtschaftssystem
- Anbindung des Netum-Scanners an die App (er bleibt für andere Abläufe im Einsatz)
- Andere Komponenten als RAM-Module
- Mehrbenutzerbetrieb, Benutzerkonten, Server
## Hardware und Zugang
- **Kamera:** Handykamera. Die App wird im Handy-Browser geöffnet.
- **HTTPS:** Kamerazugriff verlangt eine sichere Verbindung. Die VCH-Preview-URL
liefert SSL.
- **Netz:** Erkennung und Sortierung laufen vollständig lokal im Browser; während einer
laufenden Sitzung wird keine Netzverbindung gebraucht. Zum Laden der Seite selbst
(und beim ersten Start zusätzlich für die Tesseract-Sprachdaten) ist eine Verbindung
nötig. Echte Offline-Fähigkeit über einen Service Worker ist nicht im Umfang.
## Erkennungsstrategie
Gewählt wurde lokale Erkennung ohne Cloud-Dienst. Die bekannten Schwächen von Tesseract
auf glänzenden Metall-Etiketten werden durch drei Maßnahmen aufgefangen:
1. **Barcode zuerst.** Code-128 und DataMatrix werden aus dem Kamerabild dekodiert.
Barcode-Dekodierung ist exakt, im Gegensatz zu OCR. Aus der Hersteller-PN leitet ein
Decoder die Specs ab. Gelingt das, entfällt OCR vollständig.
2. **Kleiner Erwartungsraum.** RAM-Specs sind kein Freitext. Kapazität, Speed und Rank
stammen aus kurzen, bekannten Wertelisten. Das OCR-Ergebnis wird dagegen abgeglichen;
typische Verwechslungen (0/O, 1/I, 8/B) lassen sich dadurch eindeutig auflösen.
3. **Toleranter Stapel-Abgleich.** Ein neuer Scan wird gegen die bereits offenen Stapel
geprüft. Weicht ein Fingerabdruck nur in einer typischen Verwechslung ab, gilt er als
derselbe Stapel und nicht als neuer.
## Ablauf pro Modul
1. **Kamerabild** — Live-Vorschau, Etikett ins Bild halten.
2. **Barcode-Versuch** — laufende Suche nach Code-128 und DataMatrix.
3. **PN-Decoder** — Hersteller-PN wird in Spec-Felder zerlegt. Erfolg beendet die
Erkennung.
4. **OCR-Fallback** — nur wenn kein Barcode lesbar oder die PN unbekannt ist.
Bildaufbereitung (Graustufen, Kontrast, Schwellwert), Texterkennung, Abgleich der
Felder gegen die bekannten Werte.
5. **Bewertung** — grün bei Barcode, gelb bei eindeutig zugeordnetem OCR-Ergebnis, rot
bei Unsicherheit.
6. **Stapel-Zuweisung** — Ergebnis und Stapelbuchstabe werden groß angezeigt.
Grün und Gelb laufen ohne Eingabe durch. Nur Rot hält den Ablauf an.
## Fingerabdruck
Ein Modul wird auf folgende Merkmale reduziert:
| Merkmal | Beispiel | Quelle |
|---|---|---|
| Kapazität | 64 GB | PN-Decoder oder OCR |
| Bauform | LRDIMM | PN-Decoder |
| Organisation/Rank | 4DRx4 | PN-Decoder oder OCR |
| Speed | PC4-2400 | PN-Decoder oder OCR |
| Hersteller-PN | M386A8K40BM1-CRC4Y | Barcode |
**Gruppierungsstufe:** Elektrische Spec **und** Hersteller-PN müssen übereinstimmen.
Module mit gleicher Spec, aber unterschiedlichem Hersteller bilden getrennte Stapel.
Diese Stufe entspricht dem, was im Gebrauchthandel als "matched kit" gilt.
Der Datumscode wird erfasst und angezeigt, ist aber **nicht** Teil des Fingerabdrucks.
## Stapel-Logik
- Die Sitzung startet mit leerem Tisch, ohne vorab definierte Fächer.
- Erster Scan erzeugt Stapel A, der nächste unbekannte Fingerabdruck Stapel B und so
weiter.
- Vor dem Anlegen eines neuen Stapels wird geprüft, ob ein bestehender Stapel bis auf
typische OCR-Verwechslungen identisch ist. Trifft das zu, wird dorthin einsortiert.
- Die Stapelbuchstaben gelten nur für die laufende Sitzung.
## Rot-Kriterien
Die App fragt nach, wenn:
- kein Barcode lesbar war und OCR keine bekannte Spec-Kombination ergeben hat
- das OCR-Ergebnis auf zwei bestehende Stapel gleich gut passt
- die Kapazität fehlt
Im Rot-Fall zeigt die App die in Frage kommenden Stapel als große, antippbare Flächen,
zusätzlich "neuer Stapel". Ein Tipp setzt den Ablauf fort.
## Korrekturweg
Da grün und gelb ohne Bestätigung durchlaufen, braucht es einen Rückweg:
- Der zuletzt erfasste Eintrag bleibt am unteren Bildschirmrand sichtbar, mit
Rückgängig-Fläche.
- Eine Sitzungsliste zeigt alle Einträge der laufenden Sitzung; jeder lässt sich einem
anderen Stapel zuordnen oder entfernen.
## Oberfläche
Mobil zuerst, große Flächen, im Stehen und mit einer Hand bedienbar.
**Scan-Ansicht** (Hauptansicht):
```
┌──────────────────────────┐
│ │
│ Kamera-Vorschau │
│ ┌────────────────┐ │
│ │ Zielrahmen │ │
│ └────────────────┘ │
│ │
├──────────────────────────┤
│ A:12 B:4 C:7 │ Stapel-Leiste, laufende Zählung
├──────────────────────────┤
│ Zuletzt: 64GB PC4-2400 │
│ LRDIMM → Stapel B [↶] │
└──────────────────────────┘
```
**Treffer-Rückmeldung** (kurz eingeblendet, dann automatisch weiter):
```
┌──────────────────────────┐
│ │
│ ► STAPEL B ◄ │ sehr groß, farbig hinterlegt
│ │
│ 64GB 4DRx4 PC4-2400 │
│ LRDIMM │
│ M386A8K40BM1-CRC4Y │
│ │
└──────────────────────────┘
```
Hintergrundfarbe grün bei Barcode-Erkennung, gelb bei OCR.
**Rot-Dialog** (wartet auf Eingabe):
```
┌──────────────────────────┐
│ Nicht eindeutig │
│ Gelesen: 64GB PC4-24?? │
├──────────────────────────┤
│ ┌────────┐ ┌────────┐ │
│ │Stapel B│ │Stapel D│ │ große Flächen
│ └────────┘ └────────┘ │
│ ┌────────────────────┐ │
│ │ neuer Stapel │ │
│ └────────────────────┘ │
│ ┌────────────────────┐ │
│ │ nochmal scannen │ │
│ └────────────────────┘ │
└──────────────────────────┘
```
**Sitzungsliste:** erreichbar über die Stapel-Leiste. Zeigt Stapel mit ihren Modulen,
erlaubt Umsortieren und Entfernen einzelner Einträge sowie "Sitzung beenden".
Die konkrete Farb- und Typografiegestaltung wird während der Umsetzung festgelegt.
## Technik
- **Vanilla JavaScript mit Vite.** Kein Framework — der Funktionsumfang rechtfertigt
keins, und die Ladezeit auf dem Handy bleibt niedrig. `base: './'` in der
Vite-Konfiguration, damit die App hinter dem VCH-Reverse-Proxy funktioniert.
- **`zxing-wasm`** für Code-128 und DataMatrix.
- **`tesseract.js`** für OCR.
- **Node-eigener Test-Runner** (`node:test`) — keine zusätzliche Test-Abhängigkeit.
Weitere Abhängigkeiten werden bewusst vermieden.
## Module
Jedes Modul hat eine Aufgabe und kennt die Interna der anderen nicht.
| Modul | Aufgabe | Abhängigkeiten |
|---|---|---|
| `camera` | Kamerastrom, Einzelbilder liefern | Browser-API |
| `barcode` | Bild → dekodierte Codes | `zxing-wasm` |
| `pn-decoder` | Hersteller-PN → Spec-Felder | keine |
| `ocr` | Bildaufbereitung, Texterkennung, Feldextraktion | `tesseract.js`, `spec` |
| `spec` | Normalisierung, Fingerabdruck, toleranter Vergleich | keine |
| `session` | Stapel halten, zuweisen, rückgängig machen | `spec` |
| `ui` | Ansichten und Eingaben | alle |
`pn-decoder` und `spec` sind reine Funktionen ohne Kamera- und DOM-Zugriff und damit
vollständig testbar. Sie bilden den fachlichen Kern.
### PN-Decoder
Tabellengesteuert, ein Eintrag pro Herstellerschema. Die Zuordnung von Codefragmenten zu
Kapazität, Bauform und Speed wird gegen reale Module validiert und nicht aus dem
Gedächtnis festgelegt. Unbekannte Schemata sind kein Fehler, sondern führen zum
OCR-Weg. Die Tabelle ist so aufgebaut, dass neue Hersteller ohne Codeänderung ergänzt
werden können.
## Fehlerfälle
| Fall | Verhalten |
|---|---|
| Kamera verweigert oder nicht verfügbar | Hinweis, dazu Datei-Auswahl als Ersatzweg (App bleibt am Rechner nutzbar) |
| Tesseract lädt nicht | App bleibt voll nutzbar, aber barcode-only; sichtbarer Hinweis |
| PN unbekannt | Kein Fehler — regulärer Übergang zum OCR-Weg |
| Barcode unlesbar und OCR unbrauchbar | Rot-Dialog |
| Versehentlicher Seiten-Neuladen | Stapel werden bei jeder Änderung lokal gesichert und beim Start zur Fortsetzung angeboten. Absturzschutz, keine Bestandsführung. "Sitzung beenden" räumt auf. |
## Tests
Mit `node:test`:
- **`pn-decoder`** gegen reale Teilenummern mehrerer Hersteller, einschließlich
`M386A8K40BM1-CRC4Y` (Samsung, 64GB LRDIMM PC4-2400)
- **`spec`** — Normalisierung und toleranter Vergleich, gezielt mit den typischen
Verwechslungen 0/O, 1/I, 8/B
- **`session`** — Szenariotests: eine Folge von Scans hinein, die erwartete
Stapelverteilung heraus, inklusive Rückgängig
- **OCR-Korrektur** — verrauschte Texteingaben gegen die Liste bekannter Spec-Werte
Kamera und Oberfläche werden manuell geprüft; dafür lohnt keine Testautomatisierung.
## Referenzmaterial
Zwei Beispielaufnahmen liegen der Gestaltung zugrunde:
- Oracle-überklebtes Modul, Etikett `Oracle ®PN: 7325773` mit Code-128
- Rückseite desselben Moduls, Samsung-Etikett mit Serial `K136000908252DF287`,
`64GB 4DRx4 PC4-2400T-LD1-11-MC0`, PN `M386A8K40BM1-CRC4Y`, Datumscode `1908`,
dazu DataMatrix und Code-128
Diese beiden dienen als erste Testfälle für den PN-Decoder und die OCR-Feldextraktion.