Commit Graph
33 Commits
Author SHA1 Message Date
vchuser b06b3abfd5 Fix session-list double-open and end-session re-save
openSessionList() now closes any already-open overlay itself before
rendering, so a double-tap on the stack bar (which has no lock) can no
longer stack two session lists with duplicate handlers. The onMove/
onRemove callbacks no longer need their own closeOverlays() call since
openSessionList() does it unconditionally now.

onEndSession no longer calls syncView() after clearSession(), which
was writing the freshly emptied session straight back into storage and
undoing the clear. It now only refreshes the visible stacks, so ending
a session actually leaves nothing saved, while a subsequent scan still
saves normally via syncView().
2026-07-28 17:37:20 +02:00
vchuser c4ad0416ab feat: Sitzungsliste und Verdrahtung zur vollstaendigen App
Ersetzt das Sichtpruefungs-Geruest in main.js durch die vollstaendige
Verdrahtung aus Kamera, Barcode/OCR-Erkennung, Sitzungsverwaltung,
Speicherung und Oberflaeche. Neu: session-list.js fuer die
Sitzungsliste zum Umsortieren/Entfernen/Beenden.

Gegenueber dem urspruenglichen Plan an den seither geaenderten
Schnittstellen ausgerichtet: commitAssignment erhaelt die tatsaechliche
Erkennungsquelle (source) statt der Konfidenz als drittes Argument, und
die OCR-Verfuegbarkeitsanzeige ueberschreibt keine noch zu lesende
Fehlermeldung mehr. Bezeichner in beiden Dateien auf Englisch
umgestellt, damit sie der bereits etablierten Projektkonvention
entsprechen.
2026-07-28 17:28:15 +02:00
vchuser 0c96743211 Rot-Dialog: Rollbarkeit bei vielen Stapeln und echte Modalität
Beanstandung 1: .overlay bekommt overflow-y:auto, overscroll-behavior:
contain und justify-content:safe center, damit bei vielen Kandidaten-
Stapeln (Rot-Fall bietet alle Stapel der Sitzung an) keine Schaltfläche
mehr unerreichbar aus dem Bildschirm waechst, ohne dass die Seite
dahinter mitrollt oder sich die Optik bei wenigen Stapeln aendert.

Beanstandung 2: askForStack setzt inert auf die Geschwisterelemente von
root, solange der Dialog offen ist, und hebt es beim Schliessen (ueber
jeden der drei Wege) nur fuer selbst gesetzte Elemente wieder auf.
Fokus kehrt nach dem Schliessen zum vorherigen Element zurueck statt auf
dem entfernten Dialog haengen zu bleiben.
2026-07-28 17:20:19 +02:00
vchuser e5f8952b95 feat: Rot-Dialog fuer mehrdeutige Erkennung 2026-07-28 17:12:26 +02:00
vchuserandClaude Opus 5 d20f964399 Fix promise leak when result overlay is displaced by new scan
When a new scan result displaces a previously visible overlay, the promise
of the displaced overlay was never resolved. This caused the caller waiting
on that promise to block indefinitely, freezing the app.

Now the resolve function is stored in the WeakMap alongside the overlay
and timer. When a new overlay displaces the previous one, the displaced
overlay's promise is resolved immediately with resolve(), signifying that
it is no longer visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:08:25 +02:00
vchuserandClaude Opus 5 b965f2c9ea Beanstandungen der Prüfung beheben (Scan-Ansicht, Overlay, Styles)
- .stacks-Buttons auf min-height 56px angehoben, Leiste auf 76px
  vergrößert, damit die Buttons vollständig hineinpassen
- Datei-Input bleibt dauerhaft hidden statt nach dem ersten Öffnen
  sichtbar zu bleiben und den Kamerabereich zu verkleinern
- Rückgängig-Knopf erhält aria-label für Screenreader
- showResult entfernt eine noch sichtbare vorherige Rückmeldung und
  deren Zeitgeber, bevor die neue eingeblendet wird

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:06:51 +02:00
vchuser 4aaff0b08d feat: Scan-Ansicht und Treffer-Rueckmeldung 2026-07-28 17:00:44 +02:00
vchuserandClaude Opus 5 78592d03a1 Fix: OCR-Schwellwert nach Otsu statt fester Schwellwert nach Kontrastspreizung
Ein einzelnes Reflexpixel (Normalfall bei glaenzenden Metalletiketten)
dominierte bisher Minimum/Maximum der Kontrastspreizung und drueckte
Schrift und Untergrund gemeinsam unter den festen Schwellwert 128 - die
Schrift verschwand. Der Schwellwert wird jetzt per Otsu-Verfahren aus dem
Helligkeits-Histogramm des Bildes selbst bestimmt und bleibt dadurch gegen
einzelne Ausreisser unempfindlich. Ausserdem: fehlendes Kleinbuchstaben-x
in der Tesseract-Zeichenliste ergaenzt (Bestueckungsangabe "4DRx4").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:53:42 +02:00
vchuser 65f699a704 feat: OCR-Adapter mit Bildaufbereitung
preprocess() rechnet rein (Graustufen, Kontrastspreizung, Schwellwert) ohne
DOM-Zugriff und ist damit ohne Browser testbar. runOcr() ist der Browser-
Adapter, cacht den Tesseract-Arbeiter und laesst isOcrAvailable() sich nach
einem Fehlschlag wieder erholen statt dauerhaft einzufrieren - analog zum
ensureReady()-Muster in barcode.js.
2026-07-28 16:43:49 +02:00
vchuser 87d7f7c393 feat: Barcode-Adapter fuer Code-128 und DataMatrix
Dekodiert Code-128 und DataMatrix aus einem Einzelbild ueber zxing-wasm.
Statt eines einfachen Boolean-Merkers wird die einmalige WASM-Vorbereitung
per gecachtem Promise realisiert (verhindert doppelte Vorbereitung bei
schnell aufeinanderfolgenden Aufrufen) und im Fehlerfall via
purgeZXingModule() zurueckgesetzt, damit ein spaeterer Versuch echt neu
startet statt eine intern gecachte Ablehnung zu wiederholen.

main.js: Klick-Handler zeigt die dekodierten Codes an und faengt jetzt
auch den Fehler ab, den grabFrame wirft, wenn das Kamerabild noch nicht
bereit ist.
2026-07-28 16:34:12 +02:00
vchuserandClaude Opus 5 1192a898c4 Bildressource in imageDataFromFile mit try/finally freigeben
Stelle sicher, dass bitmap.close() auch bei Fehler aufgerufen wird.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:28:18 +02:00
vchuser 91ac8b9db6 Kamerastrom bei Fehler freigeben, klare Meldung bei fehlenden Bildmassen
- startCamera: schlaegt play() fehl, werden Tracks gestoppt und srcObject
  geloest, bevor der Fehler weitergereicht wird (Kamera-LED erlischt statt
  dauerhaft aktiv zu bleiben).
- grabFrame/imageDataFromFile: pruefen vor dem Verkleinern, ob Breite und
  Hoehe > 0 sind, und werfen sonst eine verstaendliche deutsche Meldung
  statt eines technischen Fehlers aus getImageData.
2026-07-28 16:25:06 +02:00
vchuser 0e3fa1ead0 feat: Kamera-Adapter mit Datei-Ersatzweg 2026-07-28 16:20:27 +02:00
vchuserandClaude Opus 5 330beaeffe Verschaerfe Pruefung auf ganze positive Eintragsnummern
- Fuegt isPositiveInteger() hinzu fuer Validierung von Eintragsnummern
- nextEntryId und entry.entryId muessen ganze Zahlen >= 1 sein
- Akzeptiert nicht mehr negative, gebrochene oder Null-Werte
- Vier neue Tests pruefen die verstaerkten Anforderungen
- Alle bestehenden Tests bleiben gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:16:55 +02:00
vchuserandClaude Opus 5 5966aab6b6 Erzwinge innere Stimmigkeit in loadSession statt nur Typprüfung
Stapelzähler wird beim Laden immer aus den zugeordneten Einträgen neu
berechnet statt aus dem gespeicherten Wert übernommen. Zusätzlich wird
verworfen (null): Einträge mit unbekannter Stapelkennung, doppelte
Eintragsnummern, doppelte Stapelkennungen und ein nextEntryId, der nicht
größer als jede vorhandene Eintragsnummer ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:08:39 +02:00
vchuser 5fea22a8c1 Validiere Inhalt in loadSession statt nur Array-Form
loadSession pruefte bisher nur, ob stacks/entries Arrays sind, nicht
deren Inhalt. Ein fremder oder verfaelschter Eintrag unter dem
gleichen Schluessel kam dadurch als vermeintlich gueltige Sitzung
durch und liess die App beim naechsten Scan mit TypeError abstuerzen
(stack.spec fehlte). Falsch typisierte Felder (nextEntryId als String,
stack.count als String) wurden ebenfalls durchgereicht und verdarben
die Sitzung lautlos (doppelte entryIds, "3" + 1 = "31").

loadSession verlangt jetzt, dass nextEntryId eine endliche Zahl ist
und dass jeder Stapel/Eintrag ein echtes Objekt (kein Array, nicht
null) mit den Feldern ist, die session.js tatsaechlich weiterverwendet.
Schlaegt eine Pruefung fehl, liefert loadSession null - ein halb
brauchbarer Zustand wird nicht repariert oder teilweise uebernommen.
2026-07-28 16:03:23 +02:00
vchuserandClaude Opus 5 4d896702d1 feat: Absturzschutz fuer die laufende Sitzung
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:57:28 +02:00
vchuser 37df87a324 Behandle Mehrdeutigkeit auch bei unverwertbaren Barcodes
Mehrere Barcodes ohne bekanntes Nummernschema, die sich in der
Teilenummer widersprechen, durften bisher nicht mehr Vorrang vor der
per OCR gelesenen Teilenummer erhalten als ein einzelner Treffer -
der erste gewann ungeprueft. Jetzt gilt derselbe Massstab wie bei
verwertbaren Treffern: Vorrang nur bei Eindeutigkeit (ein Code, oder
mehrere mit derselben - via canonical() verwechslungstolerant
verglichenen - Teilenummer). Widersprechen sie sich, stuetzt sich das
Ergebnis allein auf die Texterkennung.

Zusaetzlich: ungueltige @param-Zeile fuer deps in recognize() korrigiert.
2026-07-28 15:49:39 +02:00
vchuserandClaude Opus 5 9c0a88b31b Erhöhe DEFAULT_BARCODE_TIMEOUT_MS auf 10000 ms für WebAssembly-Modul-Laden
Die Barcode-Dekodierung selbst ist normalerweise schnell, doch beim ersten
Scan einer Sitzung muss der Barcode-Adapter sein WebAssembly-Modul laden,
was auf einem Handy mehrere Sekunden dauern kann. Die Zeitgrenze dient nicht
als Leistungserwartung, sondern als Absicherung gegen einen dauerhaft
hängenden Adapter — sie muss deshalb auch das einmalige Laden abdecken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:39:07 +02:00
vchuser be437a5fc5 Add adapter timeouts and drop unreachable spec-merge code
- Barcode- und OCR-Adapter erhalten je eine eigene, ueberschreibbare
  Zeitobergrenze (deps.barcodeTimeoutMs / deps.ocrTimeoutMs). Ein
  Adapter, der nie einloest, blockiert recognize() damit nicht mehr
  dauerhaft und faellt stattdessen auf den Ersatzwert zurueck, genau
  wie bei einem geworfenen Fehler. Kein offener Timer bleibt zurueck.
- mergeCompatibleSpecs entfernt: vertraegliche verwertbare
  Barcode-Treffer sind wegen der Kopplung von partNumber und den
  uebrigen Feldern ohnehin identisch, der erste Treffer genuegt.
  Test entsprechend umbenannt, um das tatsaechlich gepruefte
  Verhalten zu benennen statt eine nicht mehr vorhandene
  Auffuell-Semantik zu behaupten.
- Kommentar in pn-tables.js von einer inhaltlichen Wiederholung
  befreit.
2026-07-28 15:36:52 +02:00
vchuserandClaude Opus 5 4099687707 Fix pipeline: robust gegen unerwartete Adapter-Rueckgaben und mehrdeutige Barcodes
recognize() stuerzte ab, wenn decodeBarcodes() etwas anderes als eine Liste
zurueckgab (null/undefined/Objekt), da die for...of-Schleife nicht iterierbare
Werte nicht abfaengt. Nicht-Listen werden nun wie eine leere Liste behandelt,
ebenso wird eine nicht-string-wertige runOcr()-Rueckgabe wie leerer Text
behandelt.

Ausserdem gewann bei mehreren verwertbaren Barcodes im selben Bild bisher
ungeprueft der erste Treffer mit gruener Ampel. Jetzt werden alle verwertbaren
Codes gesammelt: genau einer bleibt gruen wie bisher, mehrere untereinander
vertraegliche werden zusammengefuehrt (spaeterer Treffer ueberschreibt kein
bereits gesetztes Feld) und bleiben gruen, mehrere unvertraegliche ergeben rot
mit leerem Spec statt eines geratenen Ergebnisses.

Korrigiert ausserdem den irrefuehrenden Warnkommentar in pn-tables.js: ein
falscher Tabelleneintrag faellt auf dem Barcode-Weg nicht automatisch auf, da
dort keine Texterkennung zum Abgleich laeuft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 15:29:54 +02:00
vchuser 2d81f7b005 feat: Erkennungs-Pipeline mit Barcode-Vorrang und OCR-Rueckfall 2026-07-28 15:13:27 +02:00
vchuser a90ec05550 Fix shared spec references in session; clarify undoLast contract
commitAssignment and moveEntry stored the same spec object in both
stack.spec and entry.spec. A later, more complete scan enriching
stack.spec would retroactively mutate the entry that first created the
stack, making it appear as if it had been scanned with data it never
actually had. Both places now store independent shallow copies so the
entries log and the stacks spec (comparison baseline) can't leak into
each other.

Also documents that undoLast intentionally reverts the last *recorded*
entry, not the last *action* - a subsequent moveEntry/removeEntry on a
different entry does not change what undoLast will take back - and
adds a test pinning that contract after a reorder.
2026-07-28 15:07:15 +02:00
vchuser f8e3a06d44 feat: Sitzungsverwaltung mit Stapel-Zuweisung und Ruecknahme 2026-07-28 14:59:46 +02:00
vchuser 8d50334915 Fix part-number and date-code selection in ocr-extract
Part number now picks the longest hyphenated candidate instead of the
first one, matching the documented intent and preventing a short
unrelated token from being reported as the part number (which feeds
the fingerprint used for sorting).

Date code now only accepts YYWW candidates with a plausible year
(10-39) and valid calendar week (01-53), taking the last match when
several qualify; otherwise it stays null instead of guessing wrong,
since a wrong value is worse than a missing one.

Adds regression tests for both cases plus a no-plausible-candidate
case that must yield null.
2026-07-28 14:52:28 +02:00
vchuser 9098a45aec feat: OCR-Feldextraktion mit Abgleich gegen bekannte Werte 2026-07-28 14:46:52 +02:00
vchuserandClaude Opus 5 4201643fc5 Fix Samsung density-code regex to honor fixed field width
The lazy quantifier {4,6}? never expanded beyond its minimum because
the trailing greedy [A-Z0-9]* always absorbed the rest up to the
hyphen, so a longer density code would silently be truncated to 5
chars and could match an unrelated table entry, producing wrong specs
instead of null. The density code is actually fixed-width (A + 4
chars); the revision after it is the variable part. Tighten the
pattern to {4}, rewrite the stale comment (which still claimed a fixed
3-char revision), and add a test that every Samsung density key is
exactly 5 characters so a future mismatched table entry can't silently
become unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:41:24 +02:00
vchuserandClaude Opus 5 e14d607761 Fix Samsung pattern to handle variable revision lengths
Das Samsung-Muster hat die Revision auf exakt 3 Zeichen festgelegt und die
Dichte-Gruppe auf exakt 4 Folgezeichen. Dies führte dazu, dass bei
abweichender Revisionslänge das gesamte Muster nicht passte und Bauform,
Geschwindigkeit und Kapazität verloren gingen.

Das neue Muster verwendet einen faul (lazy) Quantifier für die
Dichte-Gruppe (4-6 Zeichen) und erlaubt beliebig lange Revisionen vor
dem Bindestrich. Dadurch werden Bauform und Geschwindigkeit auch bei
nicht standardisierten Revisionslängen korrekt erkannt.

- M393A2K43B-CTD (einteilige Revision)
- M393A2K43BBX1-CTD (vierteilige Revision)

Beide Tests bestätigen jetzt die korrekte Erkennung von Bauform und
Geschwindigkeit unabhängig von der Revisionslänge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:34:40 +02:00
vchuser c580d4f924 feat: tabellengesteuerter Teilenummer-Decoder
Deckt die Samsung-Referenz-Teilenummer aus dem physisch vorhandenen
Modul ab. Unbekannte Fragmente (z.B. nicht gelistete Dichte-Codes)
liefern bewusst null statt einer Vermutung - das ist der geplante
Uebergang zum OCR-Weg, kein Fehlerfall.

Das im Task-Brief vorgegebene Regex-Muster liess das literale 'A' der
Dichte-Kennung ausserhalb der Erfassungsgruppe, wodurch die
Tabellensuche nach 'A8K40' nie greifen konnte. Muster korrigiert, damit
die ebenfalls vorgegebenen Tests bestehen; Tabelleninhalte und
Warnkommentar blieben wortgleich.
2026-07-28 14:29:13 +02:00
vchuserandClaude Opus 5 8199d25a6e Fix: canonical() und matchKnown() behandeln Zahlen wie Zeichenketten
canonical() und matchKnown() stützen sich auf normalizeToken(), das bei
Nicht-Strings '' zurückgibt. Dies führt dazu, dass bereits geparste Zahlenwerte
lautlos ignoriert werden: canonical(64) liefert '' statt '64', matchKnown(64, list)
liefert null statt den gefundenen Wert.

Die Behebung konvertiert Zahlen zu Zeichenketten, bevor sie normalizeToken()
erreichen, ohne null/undefined zu ändern (diese ergeben weiterhin ''/'null).

Alle 23 Tests grün.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:21:47 +02:00
vchuser a7adfeb9da feat: toleranter Spec-Vergleich gegen OCR-Verwechslungen 2026-07-28 14:16:31 +02:00
vchuser 3aa54d6c78 feat: Projektgeruest und Spec-Grundlagen 2026-07-28 14:11:54 +02:00
vchuser 82898a1539 chore: Spezifikation und Umsetzungsplan 2026-07-28 14:07:51 +02:00