Zum Inhalt springen

Changelog

Changelog

Kuratierte Highlights der letzten Releases. Für vollständige API-Versionen und Breaking-Change-Policy siehe die API-Versionierung & LTS-Policy.

Detaillierte Änderungen an einzelnen Endpoints: OpenAPI-Spezifikation und interaktive API-Referenz.


2026-10 · Farben im Dashboard

  • Neu: Die Detailseite eines Codes hat die Karte Farben: Vorder- und Hintergrund als Hex-Wert, transparenter Hintergrund, Vorschau über die echte Bildroute. Geprüft wird wie bei PATCH /v1/codes/{id}: Kritische Paare brauchen eine Bestätigung, Paare unter 1,5 : 1 lassen sich nicht speichern.
  • Zurücksetzen auf Schwarz/Weiß liefert in allen vier Formaten wieder dieselben Bytes wie ohne Farben. Codes ohne Farben ändern sich nicht.
  • Transparente Codes erscheinen in allen Dashboard-Vorschauen auf einem Schachbrett. Details: Farben im Dashboard.

2026-10 · Farben in PDF und EPS

  • Neu: qr.pdf und qr.eps zeichnen fg und bg, als Parameter und als gespeicherte Farben. Schwarz und jedes Grau werden als Graustufe geschrieben (nur Schwarzplatte), jede andere Farbe als CMYK in ganzen Prozent, etwa 1F4E79 als C74 M36 Y0 K53.
  • Hintergrund wie beim SVG: Sobald eine Farbe gewählt ist, liegt eine deckende Fläche hinter dem Code und seiner Ruhezone, weiß oder bg. bg=transparent zeichnet keine. Titel bleibt schwarz, Logo-Platte weiß.
  • Keine Änderung ohne Farben: Ohne fg/bg und ohne gespeicherte Farben liefern beide Formate dieselben Bytes wie vorher. Das PDF eines Produktpasses bleibt ungefärbt.
  • Grenzen: kein ICC-Profil, kein PDF/X. Details: Farben im Druck.

2026-09 · Statische Codes ohne Weiterleitungs-Cache, gleichzeitiges Löschen

  • Geändert: Statische Codes liegen nicht mehr im Weiterleitungs-Cache. Ein Aufruf ihres Kurz-Links (redirect_url) liest immer den aktuellen Stand. Nach DELETE /v1/account leitet ein statischer Code deshalb beim nächsten Aufruf nicht mehr weiter, vorher bis zu 24 Stunden lang. Auch ein Tarifwechsel wirkt bei statischen Codes sofort. Dynamische Codes räumt die Kontolöschung wie bisher. Gedruckte statische Codes enthalten ihr Ziel direkt und sind nicht betroffen.
  • Behoben: Zwei gleichzeitige DELETE /v1/codes/:id auf denselben Code liefern einmal 200 und einmal 404. Der Webhook qr.deleted wird genau einmal gesendet, vorher zweimal.
  • Neu: POST und DELETE /v1/codes/:id/logo antworten mit 409 (errors/conflict), wenn eine andere Anfrage das Logo desselben Codes gleichzeitig geändert hat, und ändern dann nichts. Vorher konnte ein hochgeladenes Logo ungenutzt gespeichert bleiben. Zwei gleichzeitige DELETE /v1/codes/:id/logo liefern beide 200. Details: Logo.

2026-09 · TypeScript-SDK 1.2.0

  • Neu in @qr3/sdk 1.2.0: client.codes.update() nimmt appearance an und liefert die Befunde der Kontrastprüfung als issues am Ergebnis; ohne Befund fehlt das Feld. client.codes.imageUrl() nimmt fg, bg und ecc an. Jeder Code, den die API zurückgibt (get, list, create, update, batchCreate), trägt title und appearance.
  • Aktualisieren: npm install @qr3/sdk@latest. Python, Go und PHP folgen. Details: SDKs & CLI und Farben am Code speichern.

2026-09 · Geänderte Ablaufzeit gilt innerhalb von etwa einer Minute

  • Behoben: Ein PATCH auf expires_at räumt jetzt den Weiterleitungs-Cache des Codes. Die neue Ablaufzeit gilt damit innerhalb von etwa einer Minute statt erst bis zu 24 Stunden später. Vorher lieferte ein entfernter oder verschobener Ablauf bis zu 24 Stunden weiter 410, und ein auf jetzt gesetzter Ablauf ließ die Weiterleitung bis zu 24 Stunden weiterlaufen.
  • Behoben: Ein mit expires_at angelegter Code läuft auch dann pünktlich ab, wenn der Ablauf innerhalb der ersten 24 Stunden liegt.
  • Behoben: Wird ein Code gelöscht, während sein Logo hochgeladen oder entfernt wird, antworten POST und DELETE /v1/codes/:id/logo mit 404 und ändern den gelöschten Code nicht, wie schon PATCH.
  • Behoben: Auch ein statischer Code, dessen Kurz-Link (redirect_url) aufgerufen wurde, liegt im Weiterleitungs-Cache. Ändern, Pausieren und Löschen räumen ihn jetzt ebenfalls, nicht nur bei dynamischen Codes.

2026-09 · Farben am Code speichern

  • Neu: PATCH /v1/codes/:id nimmt appearance mit foreground_color und background_color an (#RRGGBB, der Hintergrund auch transparent). qr.svg und qr.png zeichnen die gespeicherten Farben ohne Parameter; ein Parameter wie ?fg=000000 hat weiter Vorrang. PDF und EPS zeichnen Farben erst mit einem späteren Release.
  • Kontrastprüfung: Ein Paar unter 1,5:1 ergibt 422. Schwächere Paare werden gespeichert und in meta.issues als warning oder critical gemeldet; ein transparenter Hintergrund ist immer critical, nie gesperrt.
  • Zusammenführen: Weggelassene Felder behalten ihren gespeicherten Wert, null setzt zurück. Gleichzeitige Änderungen am selben Code überschreiben sich nicht mehr gegenseitig.
  • In jeder Code-Antwort: appearance steht in jeder Code-Antwort und in den Webhooks qr.created und qr.updated. POST, der Batch und der Import lehnen es mit 422 ab.
  • Keine Änderung für bestehende Codes: Ohne gespeicherte Farben liefert jeder Code dieselben Bytes wie vorher. Details: Farben am Code speichern.

2026-09 · Farben und Fehlerkorrektur als Parameter der Bildrouten

  • Neu: Alle vier Bildrouten nehmen fg (Vordergrundfarbe), bg (Hintergrundfarbe oder transparent) und ecc (Fehlerkorrektur L, M, Q, H) an. SVG und PNG zeichnen die Farben; PDF und EPS nehmen sie an, zeichnen sie aber erst mit einem späteren Release. ecc wirkt in allen vier Formaten, ein Logo erzwingt weiter H.
  • Transparenz: bg=transparent liefert ein SVG ohne Hintergrund und ein PNG mit echtem Alphakanal. Der Untergrund muss hell sein und die Ruhezone frei lassen.
  • Nie ein Fehler: Ungültige Werte werden ignoriert; das Bild kommt dann genau wie ohne Parameter.
  • Zwischenspeicherung: Jeder wirksame Parameter liefert Cache-Control: public, max-age=300 statt der 24 Stunden des Standardbilds.
  • Keine Verhaltensänderung ohne Parameter: Jeder bestehende Code liefert in allen vier Formaten dieselben Bytes wie vorher.
  • In jedem Tarif verfügbar. Details: Farben und Fehlerkorrektur.

2026-09 · PDF und EPS betten das Logo jetzt ebenfalls ein

  • Erweiterung: qr.pdf und qr.eps betten ein gesetztes Logo jetzt genauso ein wie qr.svg und qr.png (siehe unten) — als eingebettetes Bild (PDF über ein Image XObject, EPS über ein Bilddictionary in PostScript, dort nur mit %%LanguageLevel: 3), zentriert auf derselben Fläche, mit derselben Anhebung der Fehlerkorrektur auf H. Beide Formate lieferten das Logo bisher nicht mit aus; das ist jetzt behoben.
  • Zwischenspeicherung: Mit gesetztem Logo liefern jetzt auch qr.pdf und qr.eps Cache-Control: public, max-age=300 statt der sonst üblichen 24 Stunden — genau wie SVG/PNG.
  • Rückfall: Kann das Logo-Bild selbst nicht aufbereitet werden (etwa ein beschädigtes gespeichertes Objekt), bleibt die Fehlerkorrektur H, aber die reservierte Fläche bleibt leer statt eines Bilds — nie ein Serverfehler.
  • Keine Verhaltensänderung ohne Logo: Codes ohne Logo liefern qr.pdf/ qr.eps weiterhin byte-identisch, mit unverändertem 24-Stunden-Cache.
  • Details und Druckhinweise: Logo im QR-Code.

2026-09 · Logo im QR-Code — Dashboard und API

  • Neu: Ein Code kann jetzt ein Logo in der Mitte tragen. Im Dashboard legt die Detailseite eines Codes eine eigene Logo-Karte an: Logo auswählen, beim ersten Hinzufügen oder beim Entfernen eine Warnung mit Pflicht-Häkchen bestätigen (beides ändert das Punktmuster), dann Logo hochladen/Logo entfernen. Ein Ersetzen braucht keine Bestätigung — nur das Bild wechselt, nicht das Muster. Die Rolle viewer sieht Status und Vorschau, aber keine der drei Aktionen.
  • API: POST /v1/codes/{id}/logo (Multipart-Feld file; PNG, JPEG oder WebP, höchstens 1 MB, SVG wird abgelehnt) legt ein Logo an oder ersetzt es; DELETE /v1/codes/{id}/logo entfernt es wieder und ist idempotent. Ein zu großes Bild liefert 413, ein nicht unterstütztes Format 422, die Rolle viewer 403.
  • Darstellung: qr.svg und qr.png betten das Logo als tatsächliche Pixel ein (512 × 512, normalisiert) und heben dafür die Fehlerkorrektur auf H an. Hinzufügen und Entfernen ändern damit das Punktmuster (M ↔ H), ein Ersetzen nicht. qr.pdf und qr.eps betten kein Logo ein und bleiben unverändert. Ein bereits gedruckter Code funktioniert in jedem Fall weiter, weil das kodierte Ziel nicht am Logo hängt — neu drucken muss nur, wer die (neue) Logo-Optik auch auf dem Material sehen will, und dabei alte und neue Druckdateien nicht mischen.
  • Zwischenspeicherung: Mit gesetztem Logo liefern qr.svg und qr.png Cache-Control: public, max-age=300 statt der sonst üblichen 24 Stunden. Details, Grenzwerte und Druckhinweise: Logo im QR-Code.

2026-08 · operationId für alle 75 Operationen, und eine Wann-Anleitung für Agenten

  • Ergänzung: Alle 75 Operationen der OpenAPI-Spezifikation tragen jetzt eine operationId — listCodes, createCode, getCodeStats, archiveWorkspace und so fort. Bisher fehlte das Feld durchgehend, sodass jeder Generator den Methodennamen aus HTTP-Methode und Pfad ableiten musste (postV1Codes). Solche Namen hängen am Pfad und ändern sich mit jedem Pfad-Umbau. Namensform an allen 75 Stellen gleich: list/get/create/update/replace/delete, sonst das Verb der Fachhandlung (validateDpp, registerGs1Identifier, pingWebhook). Das Verb folgt der Fachlichkeit, nicht der HTTP-Methode — archiveWorkspace ist ein DELETE, importDpps ein POST.
  • Auswirkung auf selbst generierte Clients: Wer sein SDK aus der Spezifikation generiert, bekommt beim nächsten Lauf umbenannte Methoden (postV1Codes → createCode). Das ist der Zweck der Änderung, aber es ist ein Umbenennen in fremdem Code — deshalb hier ausdrücklich genannt. Pfade, Parameter, Antwortformen und Statuscodes sind unverändert; die offiziellen SDKs, CLI und MCP-Server sind nicht betroffen.
  • Agenten-Einstieg: https://qr3.app/llms.txt hat einen Abschnitt ## When to use qr3.app — sechs Aufgaben statt sechs Funktionen, plus den Satz, wofür qr3.app nicht das richtige Werkzeug ist. Dieselbe Auskunft liefert der MCP-Server jetzt im Handschlag: initialize gegen https://mcp.qr3.app/mcp antwortet mit einem Feld instructions (vorher: elf Werkzeuge ohne jede Einordnung). Enthalten ist auch, welche Aufrufe einen API-Schlüssel brauchen — initialize und tools/list nicht, jedes tools/call schon.
  • Korrekturen in llms.txt: Vier Angaben wurden gegen Produktion gemessen und hielten nicht: (1) das Python-SDK qr3app ist auf PyPI nicht veröffentlicht — die pip install-Zeile ist ersatzlos raus; (2) Accept: text/markdown gilt für Startseite und Blog, nicht für jede serverseitig gerenderte Seite; (3) die Kontaktadresse lautet [email protected], auch im JSON-LD der Startseite; (4) /de/security/ ist eine Weiterleitung auf einen Anker, keine eigene Seite. Neu verlinkt: docs.qr3.app/de/skills/.
  • Absicherung: Drei Zusicherungen in openapi-spec.test.ts — Vollzähligkeit, Eindeutigkeit, lowerCamelCase —, jede mit einer Mutation gegengeprüft. Neun weitere Tests halten die vier korrigierten Angaben fest, damit sie nicht zurückkehren.
  • Bekannte Einschränkung: Zwei der 75 Operationen sind in der Spezifikation beschrieben, antworten aber 404 (GET /v1/codes/{id}/stats und GET /v1/account). Sie haben hier einen Namen bekommen und verlieren ihn wieder, sobald entschieden ist, ob sie gebaut oder gestrichen werden.

2026-08 · Tarif-Downgrades und Kündigungen wirken jetzt auch auf die Limits

  • Fix: Der Stripe-Webhook schrieb bei Planwechsel und Kündigung nur den Plannamen, nicht die vier Limit-Spalten der Organisation (max_workspaces, max_members, max_dynamic_codes, max_scans_per_month). Da die Durchsetzung das Maximum aus gespeichertem Wert und Plan-Baseline nimmt, behielt eine herabgestufte oder gekündigte Organisation ihre alten, höheren Limits — das Downgrade war wirkungslos. Der Webhook setzt die Spalten jetzt bei jedem echten Planwechsel auf die Baseline des neuen Tarifs und bei Kündigung auf die Free-Baseline — genau wie es die Admin-Verwaltung bereits tut.
  • Verhalten: Routine-Updates der Subscription (Verlängerung, Zahlungsmittel, Proration) fassen die Limits weiterhin nicht an; individuell gewährte höhere Limits überstehen sie unverändert. Erst ein echter Planwechsel setzt sie auf die Baseline des neuen Tarifs.
  • Auswirkung: Keine API- oder Antwortformat-Änderung. Organisationen, deren Downgrade vor diesem Fix lag, behalten die alten Werte, bis der nächste Planwechsel greift oder der Support sie anpasst.

2026-08 · Keyset-Cursor jetzt auf allen Listen-Endpoints

  • Fix: Der Cursor-Fix für GET /v1/codes und GET /v1/dpp (siehe unten) ist jetzt auf alle übrigen cursor-paginierten Listen ausgerollt: GET /v1/qr-codes/:id/comments, /v1/workspaces, /v1/gs1/identifiers, /v1/members, /v1/audit-logs, /v1/admin/orgs, /v1/admin/users und /v1/webhooks/:id/deliveries. Alle blätterten allein über created_at; Zeilen mit identischem Zeitstempel (Audit-Einträge einer Batch-Operation, Webhook-Retries, Mitglieder-Importe) konnten auf der Folgeseite verloren gehen. Sortierschlüssel ist jetzt überall das Tupel (created_at, id).
  • API-Änderung: Auf diesen Listen ist meta.pagination.next_cursor ab sofort ebenfalls ein opaker Wert (base64url) statt eines nackten Zeitstempels; unlesbare Cursor liefern 400, alte Zeitstempel-Cursor werden übergangsweise weiter akzeptiert. Ausnahme GET /v1/webhooks/:id/deliveries: dort bleibt next_cursor die ID der letzten Zustellung (unbekannte ID → erste Seite). Clients, die next_cursor unverändert zurückgeben — Dashboard, CLI, SDKs, MCP —, müssen nichts ändern.
  • Auswirkung: Keine Migration nötig. Wer in einer dieser Listen (z. B. Audit-Log oder Zustellungs-Log im Dashboard) beim Blättern Einträge vermisst hat: Sie waren nie weg — die Listen zeigen sie ab jetzt vollständig.

2026-08 · Zeitstempel nach Bearbeiten und Löschen wieder OpenAPI-konform

  • Fix: Nach PATCH /v1/codes/{id} kam updated_at im SQLite-Format ohne Zeitzone zurück (2026-08-17 09:00:00), obwohl die OpenAPI-Spezifikation format: date-time zusagt und Anlegen (POST) den ISO-Zeitstempel (2026-08-17T09:00:00.000Z) liefert. Dasselbe galt für deleted_at/updated_at beim Soft-Delete sowie für die Bearbeiten-/Löschen-Pfade von API-Keys, Organisationen, Workspaces, Mitgliedern, Kommentaren und Webhooks, außerdem für last_used_at von API-Keys und last_triggered_at von Webhooks. Alle Schreibpfade stempeln jetzt ISO 8601 (UTC, T und Z).
  • Auswirkung: Clients, die updated_at mit new Date(...) parsen (SDKs, CLI, Dashboard), lasen das Leerzeichen-Format als Ortszeit — die CLI zeigte bei einmal bearbeiteten Codes die Uhrzeit um den lokalen Offset verschoben (Wien: −2 h). Das ist behoben. Zusätzlich normalisiert eine Daten-Migration bereits gespeicherte Werte im alten Format auf ISO, damit Sortierung und Vergleiche in gemischten Beständen stimmen. Keine Änderung an Feldnamen oder Antwortstruktur.
  • Hintergrund: Gleiche Fehlerklasse wie die beiden Fixes unten (API-Key-Ablauf, Re-Scan-Cutoff): SQLites datetime('now') schreibt YYYY-MM-DD HH:MM:SS, alle übrigen Schreiber ISO 8601. Ein Quelltext-Guard-Test verhindert künftig neue Vorkommen.

2026-08 · Listen-Pagination verliert keine Batch-Codes mehr

  • Fix: GET /v1/codes und GET /v1/dpp paginierten allein über created_at. Codes aus POST /v1/codes/batch, POST /v1/dpp/batch und dem CSV/XLSX-Import teilen sich aber einen Zeitstempel — sobald ein Batch größer war als limit (Default 20), lieferte die zweite Seite die restlichen Zeilen desselben Zeitstempels nicht mehr. Die Codes existierten und waren per GET /v1/codes/:id erreichbar, tauchten in der Liste (Dashboard, CLI qr3 list, SDKs, MCP) aber nie auf. Der Cursor ist jetzt ein Keyset über (created_at, id).
  • API-Änderung: meta.pagination.next_cursor ist ab sofort ein opaker Wert (base64url) statt eines nackten Zeitstempels. Wer den Cursor unverändert als ?cursor= zurückgibt — so wie Dashboard, CLI, alle SDKs und der MCP-Server es tun — muss nichts ändern. Alte Zeitstempel-Cursor werden übergangsweise weiter akzeptiert; unlesbare Cursor liefern jetzt 400 statt stillschweigend die erste Seite.
  • Auswirkung: Wer nach einem Batch-Import in der Liste weniger Codes gesehen hat, als angelegt wurden: Die Codes waren nie weg — die Liste zeigt sie ab jetzt vollständig. Keine Migration nötig.

2026-08 · Sicherheits-Re-Scans laufen wieder im 24-Stunden-Takt

  • Fix: Das periodische Re-Scanning von Ziel-URLs und Landingpage-Links (Google Web Risk) übersprang Codes, deren letzter Scan am selben Kalendertag wie der 24-Stunden-Cutoff lag — je nach Uhrzeit verzögerte sich der Re-Scan um bis zu einen weiteren Tag. Der Cutoff wird jetzt im selben ISO-Format berechnet, in dem die Scan-Zeitstempel gespeichert sind.
  • Auswirkung: Eine Ziel-URL, die nach dem letzten Scan als unsicher eingestuft wird, führt wieder innerhalb des dokumentierten 24-Stunden-Fensters zum automatischen Pausieren des Codes. Keine API- oder Antwortformat-Änderung.

2026-08 · API-Keys laufen zum Ablaufzeitpunkt ab

  • Fix: Ein API-Key, dessen expires_at am selben Tag lag, wurde bis Mitternacht UTC weiter akzeptiert. Der Ablauf wird jetzt als Zeitstempel verglichen statt als Zeichenkette — ein abgelaufener Key liefert sofort 401.
  • Hintergrund: expires_at wird als ISO-Zeitstempel gespeichert (2026-08-14T09:00:00Z), die Vergleichsseite lieferte das Leerzeichen-Format (2026-08-14 09:00:00). Der rohe Zeichenketten-Vergleich stimmte deshalb nur, solange sich schon das Datum unterschied.
  • Auswirkung: Keine Migration nötig, das Antwortformat von GET /v1/api-keys bleibt unverändert. Unlesbare Ablaufwerte gelten jetzt als abgelaufen statt als gültig.

2026-08 · API-Referenz: Tenant-Verwaltung dokumentiert

  • OpenAPI: Die Spezifikation — und damit die interaktive Referenz — dokumentiert jetzt Organisationen (inkl. GET /v1/organizations/usage), Workspaces, Mitglieder & Rollen und Audit-Logs.
  • Billing: Die Tarifübersicht (GET /v1/billing/plans) ist öffentlich; Checkout (POST /v1/billing/checkout) und Stripe-Kundenportal (GET /v1/billing/portal) sind als Endpoints für Organisations-Administratoren ausgewiesen.
  • Scan-Export: Scan-Statistiken (GET /v1/codes/{id}/scans) und der Rohdaten-Export (…/scans.csv, …/scans.xlsx) sind vollständig dokumentiert — inklusive DSGVO-Hinweis: Der ip_hash ist nie im Export enthalten.
  • Fehlerverhalten: Neu dokumentiert ist auch die 400-Antwort der Request-Validierung: Der Body ist der rohe Zod-Fehler, kein RFC-7807-Problem-Dokument — ausgeliefert wird er dennoch unter dem Content-Type application/problem+json.
  • Dashboard: Öffentliche Dateien auf der Code-Detailseite haben jetzt einen Button, der ihren öffentlichen Link in die Zwischenablage legt – direkt als Ziel-URL eines QR-Codes verwendbar, wenn ein Scan sofort ein bestimmtes Dokument öffnen soll statt der Landingpage mit der Dateiliste.
  • API: Die Datei-Endpoints (/v1/files) liefern zusätzlich public_url. Das Feld ist nur bei Dateien mit visibility: public gesetzt – private Dateien bekommen keine öffentliche Adresse.
  • Verhalten: Der Link braucht keine Anmeldung und öffnet die Datei direkt im Browser. Ein Ersetzen der Datei lässt ihn unverändert, ein darauf gedruckter Code bleibt also gültig. Details: Dateien & Datenblätter.

2026-07 · Team-Rollen: Bearbeiter ohne Löschen & Admin-Abrechnung

  • Neu: Mitgliederrolle Bearbeiter (ohne Löschen) — legt QR-Codes, Dateien und Digital Product Passports an und bearbeitet sie, kann aber nichts löschen und keine API-Keys anlegen. Alle destruktiven Endpoints prüfen die Rolle serverseitig (403).
  • Abrechnung: Tarif-Upgrades und das Stripe-Kundenportal (POST /v1/billing/checkout, GET /v1/billing/portal) sind jetzt Organisations-Administratoren vorbehalten — alle anderen Rollen sehen eine schreibgeschützte Tarifübersicht.
  • Dashboard: Aktionen, die die eigene Rolle nicht zulässt, werden ausgeblendet: Ein Betrachter sieht z. B. keine Buttons zum Anlegen, Bearbeiten oder Löschen; Listen, Downloads und Statistiken bleiben sichtbar. Details: Team & Rollen.
  • Landingpage: Die von qr3 gehostete Landingpage eines Codes kann jetzt externe, selbst gehostete Links ({ label, url }) auflisten – zusätzlich zu oder anstelle von hochgeladenen Dateien, etwa für Datenblätter auf der eigenen Seite.
  • API: POST/PATCH /v1/codes akzeptieren ein links-Array (0–20 Einträge, http(s), ≤ 2048 Zeichen). Jede URL wird mit Google Web Risk geprüft; eine unsichere URL liefert 422. Ein leeres Array löscht alle Links.
  • Dashboard: Links auf der Code-Detailseite hinzufügen, sortieren und entfernen.
  • Sicherheit: Gerenderte Links bleiben XSS-sicher (escaped, nur http(s)) und die Seite behält ihren noindex-Header.

2026-04 · Dashboard-Analytics pro QR-Code

  • Dashboard: Der Analytics-Button in der QR-Code-Liste öffnet jetzt die Statistikseite des jeweiligen QR-Codes unter /dashboard/codes/{id}.
  • Routing: Der Alias /dashboard/codes leitet weiterhin auf /dashboard um, fängt aber keine Detailrouten wie /dashboard/codes/{id} mehr ab.
  • API: Die Detailseite lädt den QR-Code direkt per GET /v1/codes/:id; dadurch ist sie nicht mehr von Listen-Pagination-Limits abhängig.
  • Tests: Regressionstests decken den Alias-Redirect und den direkten Code-Load ab.

2026-04 · Dashboard-Löschdialog für QR-Codes

  • Dashboard: Der Mistkübel in der QR-Code-Liste öffnet jetzt einen eigenen React-Dialog statt eines nativen Browser-Popups.
  • Feedback: Nach dem Löschen erscheint eine Toast-Rückmeldung für Erfolg oder Fehler.
  • Tests: packages/dashboard/tests/dashboard.test.ts verhindert Regressionen auf confirm() im QR-Code-Löschflow.
  • Dashboard: Shortcodes in der QR-Code-Liste sind jetzt direkt als externe Redirect-Links klickbar. Das External-Link-Icon neben z. B. wu3qaa öffnet https://qr3.app/{shortCode} in einem neuen Tab.
  • i18n: Tooltip-Texte für Deutsch und Englisch ergänzt.
  • Tests: packages/dashboard/tests/dashboard.test.ts schützt Link-Href, neues Tab-Verhalten, noopener noreferrer und Icon gegen Regressionen.

2026-04 · Redirect-Worker-Route für dynamische QR-Codes

  • Fix: Dynamische QR-Codes unter https://qr3.app/{shortCode} werden wieder vom Redirect-Worker verarbeitet. Die Production-Route nutzt jetzt qr3.app/*, weil Cloudflare Worker-Routen keine :code-Pfadparameter unterstützen.
  • Härtung: Nicht passende Pfade werden an die Landing-Origin durchgereicht, damit normale Seiten wie /de/pricing nicht vom Redirect-Worker blockiert werden.
  • Tests: packages/redirect/tests/unit/redirect.test.ts prüft die Wildcard-Route, Shortcode-Verarbeitung und Origin-Pass-through.

2026-04 · Workspace-DPP-Scan-Übersicht (Q3.4.2)

  • Neu: GET /v1/workspace/stats/dpp?days=30 — aggregiert alle dpp_scans des API-Key-Workspaces (active_dpps, scans_by_day, top_dpps mit Produktname/Kategorie).
  • Dashboard: Karte auf der Startseite (/dashboard) mit 30-Tage-Balkendiagramm + Top-Listen — parallel zu den QR-Code-Karten.
  • Public: Marketing-Shortlink GET /dpp/dpp_<id> (ein Segment) für Live-Demos, parallel zu /dpp/{gtin}/{serial}.

2026-04 · DPP-Scan-Analytics (Q3.4.1)

  • Neu: GET /v1/dpp/:id/stats?days=30 — aggregierte Scans des öffentlichen GS1-Resolvers pro DPP. Felder: total_scans, period_scans, scans_by_day, top_countries, top_devices, top_representations.
  • Neu: Tabelle dpp_scans (Migration 0011) — getrennt von scans (Redirect-Worker). Die aktuelle Implementierung pseudonymisiert IP-Adressen mit HMAC-SHA-256 aus langlebigem Secret, Zweck und UTC-Tag; Original-IP-Adressen erreichen nie D1.
  • Dashboard: Mini-Chart-Karte (SVG, keine Chart-Library) auf /dashboard/dpp/:dppId mit 30-Tage-Balken + Top-3-Breakdowns. Empty-State sobald ein DPP live ist aber noch keine Scans hatte.

2026-04 · Live-EU-Compliance-Simulator (Q3.3.7)

  • Neu: POST /v1/dpp/:id/validate-update — simuliert Teil-Updates stateless (Status, Markt-Liste, …) ohne Persistenz. Antwort enthält eu_compliance + preview.changed_fields.
  • Dashboard: Simulator-Karte im DPP-Detail (/dashboard/dpp/:dppId) — Chips für DE/AT/FR/IT/ES/NL + Custom, Status-Dropdown, Preview EU impact / Save changes / Reset. Non-blocking via Remix useFetcher.
  • Härtung: Ausgelagerte Simulator-Helper (readUpdatePatchFromForm, marketCountriesKey) + 18 neue Unit-Tests; Bugfix: Lone-Non-ISO-Input löscht die Marktliste nicht mehr.

2026-04 · Live-EU-Compliance-Preview im Create-Formular (Q3.3.6)

  • Geändert: POST /v1/dpp/validate liefert zusätzlich eu_compliance — derselbe Validator wie GET /v1/dpp/:id/eu-compliance, stateless vor dem Speichern.
  • Dashboard: Preview unter dem bestehenden Validation-Panel + neuer Save-Guard-Banner vor den Submit-Buttons, wenn Errors/Warnings offen sind (i18n-Pluralisierung DE/EN).

2026-04 · EU-Validator + Textil-UI (Q3.3.4 + Q3.3.5)

  • Neu: EU-Compliance-Validator mit 5 Textil-Regeln (TEXTILE_AGEC_REQUIRED, TEXTILE_MICROPLASTICS_CONSISTENCY, TEXTILE_SVHC_THRESHOLD, TEXTILE_GREENWASHING, TEXTILE_ESPR_READY).
  • Neu: GET /v1/dpp/:id/eu-compliance mit compliant / espr_ready / issues[] / summary.
  • Dashboard: EU-Compliance-Section im DPP-Detail (Summary-Tiles, gruppierte Issue-Karten, ESPR-Ready-Badge im Header).

2026-04 · Textil-DPP-Schema (Q3.3.1–Q3.3.3)

  • Neu: Kategorie textile mit AGEC-Pflichtchain (Weben/Stricken → Färben/Drucken → Konfektion), pro Faser origin_country + recycled_pct, svhc_substances[], ESPR-Opt-in (PEF, Lebensdauer, Recyclability).
  • Neu: Basisfeld market_countries: string[] (ISO 3166-1 alpha-2) auf allen DPP-Kategorien — steuert FR-spezifische AGEC-Regeln und die französische Consumer-Pflichtnotiz.
  • Neu: Consumer-HTML-Template mit AGEC-Mikroplastik-Warnbox, 3-stufiger Herkunftskette (Flag-Pills), SVHC-Liste, Durability- und Recyclability-Section.
  • Migration: 0010_dpp_market_countries (D1).

2026-04 · DPP-Bulk-Import (Q3.2.1–Q3.2.5)

  • Neu: POST /v1/dpp/import akzeptiert CSV und XLSX (Worker-kompatibel via SheetJS xlsx, ~283 KB gzip Bundle).
  • Skaliert: planbasiertes Limit (Free 100 → Enterprise 10k) + chunked db.batch() à 100 + 5 MB Body-Limit.
  • Neu: Fehlerreport als CSV im errors_csv-Feld der 201-Antwort; GET /v1/dpp/import/templates/:category?format=csv|xlsx liefert fertige Vorlagen für Batterie und Textil.
  • Dashboard: Drag-and-Drop-Upload unter /dashboard/dpp/import mit Template-Proxy und Inline-CSV-Download.

Nicht-Breaking — LTS-Erweiterungen

Alle oben genannten Änderungen sind additiv:

  • Bestehende POST /v1/dpp/validate-Clients ignorieren das neue eu_compliance-Feld ohne Änderung.
  • Bestehende battery-Flows sind unverändert.
  • market_countries ist optional und defaultet auf [].

Siehe API-Versionierung für die Breaking-Change-Policy.