Products list: larger product thumbnails — ProductThumbnail takes a size via cellRendererParams (default 26 px unchanged elsewhere), picks the first Strapi format ≥ 2× the rendered size for sharp HiDPI output, rounded border + zoom-in cursor + hover enlarge, unscoped styles (AG Grid renderer); AgGridServer gets an optional rowHeight prop; /materials/products and /materials/products-v2 use 48 px thumbnails in a fixed 64 px column with 56 px rows (details)
Sales orders grid: faster initial load and scroll blocks — summary endpoint enriches each 50-row block with one batched m_inout lookup (tracking no, shipment id/status, marketplace confirm log) and one batched Strapi attachments lookup (count + documentId), so the TrackingNo, Attachment and OrderAction cell renderers read row data instead of firing 3 requests per rendered row (105 on first paint, ~180 per two scroll steps); grid-ready latest-order-id now a 40 ms c_order top-1 query instead of an unfiltered summary call that summed all ~40k orders (2.5 s / 5 MB) on every page open; fees/revenue totals only on block 0 (AgGridServer sends withAggregates), $select on the view query (257 → 150 KB per block), debug log removed, organization auto-size once instead of every block, 150 ms block-load debounce; attachment files loaded on click only, per-row fallback kept when the Strapi batch is unavailable; voided check in the attachment tag now reads docStatusId; recipe .claude/rules/sales/orders-grid.md (details)
Remove unused legacy orders ag-grid routes (so, notfulfilled, notfulfilled-5days, notfulfilled-fastlane) — no caller since the 2026-06-02 cleanup (page switched to ag-grid/summary), zero hits in 59 days of prod and dev web logs; they expanded c_orderline + m_inout child tables per block and copied the whole country table into every row (details)
Security-key login: PIN-free YubiKey tap on web + native Android app, admin enrolment and central overview — enrolment now creates non-discoverable, cross-platform credentials with userVerification 'discouraged' (a YubiKey only holds discoverable credentials with a PIN set and Chrome/Windows then prompt for it on every login); login-options sends the credential IDs of all enrolled keys (webauthnIndex.ts: service-token ad_user index, 60 s cache, cap WEBAUTHN_GLOBAL_ALLOWLIST_MAX=150, username-first fallback via needsUserName) and login-verify resolves the user by credential ID (userHandle only as legacy fallback), bumps counter + lastUsedAt; challenge token also returned in the body for cookie-less clients with a better-sqlite3 replay ledger (data/webauthn-replay.db); multiple accepted origins via WEBAUTHN_EXTRA_ORIGINS (Android android:apk-key-hash of the debug-keystore cert, assetlinks.json under public/.well-known); native app: WebAuthnPlugin.java on androidx.credentials (Credential Manager → Play services FIDO over NFC/USB), useWebAuthn routes create/get through it, login-verify delegates to app-auth/login for X-Logship-App requests and the app fetch plugin feeds that response into the session store (+ biometric unlock enrolment after a key login); tenant admins enrol/revoke keys for employees on the user edit page (admin/users/[id]/webauthn/*, isAdminRole gate, password capture with the service token) and on the new overview /users/security-keys (admin/webauthn/keys: all keys, last used, enrolled by, revoke, enrol for a picked employee; menu Users → Security Keys); sign-in page: key button first on devices that used a key before, clear cancel / not-enrolled / throttled messages, per-IP throttle on login-options; de/en/es texts reworded from passkey to security key; recipe .claude/rules/auth/webauthn-security-keys.md (details)
Security-key login: allow-list always advertises usb + nfc for roaming keys — Android Credential Manager only arms NFC when 'nfc' is hinted, and keys enrolled over USB on a desktop recorded ['usb'] only, so a tap on the phone opened the YubiKey's NDEF page instead of the FIDO ceremony (details)
Security-key login: show the Credential Manager reason behind 'Key not recognised' in the native app (asset-link / provider failures looked like a missed tap); recipe: ISPConfig nginx denies dot paths — /.well-known/assetlinks.json proxy exception added by hand on the app/dev-app vhosts, verification URL, Play-services cache note (details)
Security-key login in the native app: own in-app YubiKey ceremony instead of the Android Credential Manager sheet — WebAuthnPlugin.java now runs CTAP2 getAssertion/makeCredential itself on YubiKit 3.1.0 (NFC reader mode with skipNdefCheck + USB discovery, own "hold the key to the phone" AlertDialog, raw Ctap2Session so a PIN-protected key is never asked for its PIN, allow-lists longer than maxCredentialCountInList probed in silent up=false chunks, connection loss keeps the dialog open for the next tap); the assertion signs for the web origin (public.apiBase = WEBAUTHN_ORIGIN) so no Play services, no asset links and no apk-key-hash origin are involved any more (mode 'system' keeps the Credential Manager path as fallback); NFC permission + optional nfc/usb-host features in the manifest; useWebAuthn passes origin, localized dialog texts and exposes cancelNative/capabilities, sign-in page cancels an open dialog on unmount; mobile web sign-in (≤768 px, not iOS, not in the app) shows an Android APK download card with the public manifest's version; de/en/es texts; recipe updated (details)
Security-key login (native app): never hide the key button — the YubiKit manager is created lazily (a throwing Plugin.load() makes Capacitor drop the whole plugin and the sign-in page silently lost the button), isAvailable() always resolves and reports nfc/usb/yubikit flags plus the init error text, the composable treats a rejected availability call as "plugin unavailable" and surfaces that reason on tap instead of hiding the button, dialog.show() guarded (details)
Sign-in Android download card: point at the CURRENT build in the ERP release channel — new public GET /api/mobile/app-download returns version + a 24 h signed download URL (data/mobile-releases via mobileReleases.ts), legacy www.logyou.de manifest only as fallback; the old public web root still served the September build 1.0.45, so the card would have handed out a stale APK (details)
Sales orders grid: faster initial load and scroll blocks — summary endpoint enriches each 50-row block with one batched m_inout lookup (tracking no, shipment id/status, marketplace confirm log) and one batched Strapi attachments lookup (count + documentId), so the TrackingNo, Attachment and OrderAction cell renderers read row data instead of firing 3 requests per rendered row (105 on first paint, ~180 per two scroll steps); grid-ready latest-order-id now a 40 ms c_order top-1 query instead of an unfiltered summary call that summed all ~40k orders (2.5 s / 5 MB) on every page open; fees/revenue totals only on block 0 (AgGridServer sends withAggregates), $select on the view query (257 → 150 KB per block), debug log removed, organization auto-size once instead of every block, 150 ms block-load debounce; attachment files loaded on click only, per-row fallback kept when the Strapi batch is unavailable; voided check in the attachment tag now reads docStatusId; recipe .claude/rules/sales/orders-grid.md
Remove unused legacy orders ag-grid routes (so, notfulfilled, notfulfilled-5days, notfulfilled-fastlane) — no caller since the 2026-06-02 cleanup (page switched to ag-grid/summary), zero hits in 59 days of prod and dev web logs; they expanded c_orderline + m_inout child tables per block and copied the whole country table into every row
Security-key login: PIN-free YubiKey tap on web + native Android app, admin enrolment and central overview — enrolment now creates non-discoverable, cross-platform credentials with userVerification 'discouraged' (a YubiKey only holds discoverable credentials with a PIN set and Chrome/Windows then prompt for it on every login); login-options sends the credential IDs of all enrolled keys (webauthnIndex.ts: service-token ad_user index, 60 s cache, cap WEBAUTHN_GLOBAL_ALLOWLIST_MAX=150, username-first fallback via needsUserName) and login-verify resolves the user by credential ID (userHandle only as legacy fallback), bumps counter + lastUsedAt; challenge token also returned in the body for cookie-less clients with a better-sqlite3 replay ledger (data/webauthn-replay.db); multiple accepted origins via WEBAUTHN_EXTRA_ORIGINS (Android android:apk-key-hash of the debug-keystore cert, assetlinks.json under public/.well-known); native app: WebAuthnPlugin.java on androidx.credentials (Credential Manager → Play services FIDO over NFC/USB), useWebAuthn routes create/get through it, login-verify delegates to app-auth/login for X-Logship-App requests and the app fetch plugin feeds that response into the session store (+ biometric unlock enrolment after a key login); tenant admins enrol/revoke keys for employees on the user edit page (admin/users/[id]/webauthn/*, isAdminRole gate, password capture with the service token) and on the new overview /users/security-keys (admin/webauthn/keys: all keys, last used, enrolled by, revoke, enrol for a picked employee; menu Users → Security Keys); sign-in page: key button first on devices that used a key before, clear cancel / not-enrolled / throttled messages, per-IP throttle on login-options; de/en/es texts reworded from passkey to security key; recipe .claude/rules/auth/webauthn-security-keys.md
Security-key login: allow-list always advertises usb + nfc for roaming keys — Android Credential Manager only arms NFC when 'nfc' is hinted, and keys enrolled over USB on a desktop recorded ['usb'] only, so a tap on the phone opened the YubiKey's NDEF page instead of the FIDO ceremony
Security-key login: show the Credential Manager reason behind 'Key not recognised' in the native app (asset-link / provider failures looked like a missed tap); recipe: ISPConfig nginx denies dot paths — /.well-known/assetlinks.json proxy exception added by hand on the app/dev-app vhosts, verification URL, Play-services cache note
Security-key login in the native app: own in-app YubiKey ceremony instead of the Android Credential Manager sheet — WebAuthnPlugin.java now runs CTAP2 getAssertion/makeCredential itself on YubiKit 3.1.0 (NFC reader mode with skipNdefCheck + USB discovery, own "hold the key to the phone" AlertDialog, raw Ctap2Session so a PIN-protected key is never asked for its PIN, allow-lists longer than maxCredentialCountInList probed in silent up=false chunks, connection loss keeps the dialog open for the next tap); the assertion signs for the web origin (public.apiBase = WEBAUTHN_ORIGIN) so no Play services, no asset links and no apk-key-hash origin are involved any more (mode 'system' keeps the Credential Manager path as fallback); NFC permission + optional nfc/usb-host features in the manifest; useWebAuthn passes origin, localized dialog texts and exposes cancelNative/capabilities, sign-in page cancels an open dialog on unmount; mobile web sign-in (≤768 px, not iOS, not in the app) shows an Android APK download card with the public manifest's version; de/en/es texts; recipe updated
Security-key login (native app): never hide the key button — the YubiKit manager is created lazily (a throwing Plugin.load() makes Capacitor drop the whole plugin and the sign-in page silently lost the button), isAvailable() always resolves and reports nfc/usb/yubikit flags plus the init error text, the composable treats a rejected availability call as "plugin unavailable" and surfaces that reason on tap instead of hiding the button, dialog.show() guarded
Sign-in Android download card: point at the CURRENT build in the ERP release channel — new public GET /api/mobile/app-download returns version + a 24 h signed download URL (data/mobile-releases via mobileReleases.ts), legacy www.logyou.de manifest only as fallback; the old public web root still served the September build 1.0.45, so the card would have handed out a stale APK