Conversation
gulp 5 / vinyl-fs 4 changed the default encoding from binary to UTF-8, corrupting PNG/ICO bytes > 0x7F during copyFavicons. Add encoding: false to gulp.src() to restore binary copying.
Add Node.js setup and npm ci steps to the Claude workflow so it can run tests and type checks. Create repo-level .claude/settings.json granting npm/npx permissions.
Grant contents:write permission and full git history so Claude can rebase PR branches. Add git command permissions to settings.
- Add iOS/Apple PWA meta tags (apple-mobile-web-app-capable, status-bar-style, title) to index.html - Replace hardcoded white theme-color with per-project brand color via HtmlWebpackPlugin options - Add themeColor and shortName to all project JSON configs - Add GenerateSW (Workbox) service worker in webpack for production builds with NetworkFirst/CacheFirst runtime caching - Register service worker in src/app.tsx (production only) - Add generateManifest gulp task that generates build/favicons/ manifest.json with correct name, short_name, theme_color, start_url, scope, and orientation per aerodrome - Add workbox-webpack-plugin devDependency - Add service-worker.js no-cache header in firebase.json - Add SPA catch-all rewrite in firebase.json for deep links Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Service worker registration in app.tsx runs whenever !__DEV__, but GenerateSW was only included when ENV=production. Test deployments (e.g. lsze-test.web.app) built without ENV=production had no service-worker.js, causing Firebase's catch-all rewrite to serve index.html with a text/html MIME type error. Fix: use !process.env.DEV as the condition for GenerateSW, matching the registration condition in app.tsx. Fixes #640 Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Replace the email magic link authentication with a 6-digit OTP code flow that works reliably in iOS PWA standalone mode and avoids link expiry/mangling issues. New Cloud Functions: - generateSignInCode: generates crypto-random 6-digit code, stores SHA-256 hash in RTDB /signInCodes (admin-only path) - verifySignInCode: verifies code against stored hash, rate limits to 5 attempts, deletes code on success (single-use), returns Firebase custom auth token Updated Cloud Functions: - sendSignInEmail: accepts signInCode instead of signInLink - emailTemplates: updated to render OTP code in email body - Old generateSignInLink function kept (will be removed later) Frontend changes: - OtpCodeForm: world-class 6-box OTP input with auto-advance, backspace navigation, paste support, autoComplete="one-time-code" - EmailLoginForm: shows OtpCodeForm after email is sent - EmailLoginFormContainer: wires verifyOtpCode, removes magic link props - firebase.ts: adds requestSignInCode/verifyOtpCode, removes isSignInWithEmail/signInWithEmail - Auth module: adds VERIFY_OTP_CODE/OTP_VERIFICATION_FAILURE actions, removes magic link completion actions/sagas - RTDB rules: /signInCodes path set to read:false/write:false Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- Fix critical: generateSignInCode now sends email server-side
and returns only { success: true } — plaintext code is never
sent to the client, closing the authentication bypass
- Fix high: remove sendSignInEmail as a standalone HTTP endpoint
(open email relay); refactor into an internal helper module
- Add cleanupExpiredSignInCodes scheduled Cloud Function to
prune stale entries from /signInCodes every 60 minutes
- Fix medium: remove error details (error.message) from all
500 responses in generateSignInCode and verifySignInCode
- Fix low: simplify attempt-tracking logic in verifySignInCode
by removing the dead-code fallback double-iteration block
- Fix usability: add "Resend code" button to OtpCodeForm with
a 60-second cooldown timer; wire up in EmailLoginForm
- Update firebase.ts requestSignInCode to pass airportName and
themeColor to the server (needed for server-side email)
- Update auth saga to call requestSignInCode with all params
and remove the now-redundant sendSignInEmail saga step
- Add comprehensive tests: generateSignInCode.spec.js,
verifySignInCode.spec.js, cleanupExpiredSignInCodes.spec.js
- Update sendSignInEmail.spec.js and firebase.spec.ts to match
new API; update sagas.spec.ts for simplified flow
Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Fix the attempt-tracking test: move attemptsUpdates apply before the validKey check so non-matching codes are incremented on both success and failure paths (not just failure). Add missing test files for previously untested functions: - api/basicAuth.spec.js - api/fetchAerodromeStatus.spec.js - api/fetchUserInvoiceRecipients.spec.js - invoiceRecipients/invoiceRecipientsTrigger.spec.js All 213 function tests now pass across 24 test suites. Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- Add SEND_AUTHENTICATION_EMAIL_SUCCESS handler to set submitting: false, so OTP form inputs are not permanently disabled after email is sent - Clear otpVerificationFailure in SET_SUBMITTING handler so the error message does not persist when resend is triggered - Update reducer test: verify SET_SUBMITTING clears otpVerificationFailure - Add test for SEND_AUTHENTICATION_EMAIL_SUCCESS handler Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- Handle SEND_AUTHENTICATION_EMAIL_FAILURE in auth reducer to reset submitting and set failure, preventing the email form from freezing permanently on network/server errors - Clear OTP digits and refocus first input on resend to prevent auto-submission of stale digits mixed with new code - Remove ineffective Math.random spy in generateSignInCode.spec.js (crypto.randomInt does not call Math.random) Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- OtpCodeForm: show OTP-specific error message instead of generic login failure; use Trans component for grammatically complete German instruction sentence with email interpolation - loginPage reducer: handle SEND_AUTHENTICATION_EMAIL_FAILURE by resetting emailSent to false, returning user to email form on resend failure so the error is visible - cleanupExpiredSignInCodes: also delete codes with attempts >= MAX_ATTEMPTS (exhausted codes), not only expired ones - Update tests for all changed files Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
36 tests covering: rendering, digit input/navigation, backspace, arrow key navigation, paste handling, auto-submit, submit button, resend cooldown, digit clearing on resend, and failure messages. Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Add a responsive breakpoint at 480px so the 6-digit OTP row fits within the 1em-padded container on all portrait phones. Also add max-width: 100% to the row to prevent overflow. Breakpoints: - default: 2.8rem wide (~309px total row) - ≤480px: 2.4rem wide (~270px total row) - ≤360px: 2.0rem wide (~232px total row) Fixes #644 Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Adds a "Wrong email? Change it" link below the OTP code inputs that resets the emailSent state, returning the user to the email input form pre-filled with their current email for easy correction. - Add RESET_OTP action and handler to loginPage reducer - Add onChangeEmail prop to OtpCodeForm component - Wire up resetOtp in EmailLoginFormContainer - Add German translation for otpChangeEmail - Add tests for the new change email button Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Smart inline card that suggests installing Flightbox as a PWA. Shows between EntryPoints and MarketingLink only when all eligibility criteria are met: regular user (not guest/kiosk/ ipauth), not already installed, browser supports installation, 3+ distinct visit days, and not dismissed. Supports native install prompt on Chromium and manual instructions on iOS Safari. Dismissal is temporary (90 days) on first use, permanent on second.
- Move recordVisitDay() into useState initializer to avoid localStorage writes on every render (issue #1) - Add comment explaining dual beforeinstallprompt listeners (module-level for early events, useEffect for post-mount) (issue #2) - Clear promptRef and cachedPromptEvent before calling prompt() to prevent stale reference after event is consumed (issue #3) - Replace withTranslation() HOC with useTranslation() hook to match codebase convention of hooks over HOCs (issue #4) - Add @internal JSDoc tag to _resetCachedPromptForTesting (issue #5) - Merge IosInstructions into styled(Description) extension - Fix navigator.platform cleanup in iOS Safari test - Simplify react-i18next mock (remove unused withTranslation mock) Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- Export AuthData interface and reuse it in InstallCard.tsx to eliminate duplication - Extract isStandalone() to a local variable in usePwaInstall to avoid calling it twice per render - Add setPromptAvailable(false) in install() so the card hides immediately after the native prompt is triggered, regardless of whether the user accepts or dismisses it - Add TODO comment for deprecated navigator.platform - Add test coverage for install button hiding the card (both dismissed and accepted outcomes); add act() wrapper to flush async state update in accepted case Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
- Remove dead userChoice.then callback after install() — setPromptAvailable already hides the card synchronously, and the async setState risked a warning if the component unmounted before userChoice resolved - Add test for exact VISIT_THRESHOLD boundary (count=3) - Add test for macOS Safari 17+ UA code path - Drop email field from regularAuthData/ipauthData test fixtures (not part of AuthData interface) Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Add createdBy_orderKey to .indexOn for departures and arrivals in Firebase rules so non-admin movement queries return results. Add email claim to custom token in verifySignInCode so the email is explicitly available in the JWT.
Poll for SW updates every 3 minutes so deploys are detected quickly. When the new SW takes control, reload the page to ensure the client runs code matching the new Cloud Functions. Remove runtime caching rules from GenerateSW — the NetworkFirst catch-all cached API responses (stale data risk) and the CacheFirst rule for static assets was redundant with webpack content hashing. Precaching alone is sufficient.
Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Distinguish macOS Safari from iOS Safari so the install card shows "Zum Dock hinzufügen" instead of "Zum Home-Bildschirm". Use platform-neutral description text that works for all platforms.
moment('24:00:00', 'hh:mm:ss') uses 12-hour format, making '24'
out of range. This caused setTimeout to fire immediately, creating
an infinite reload loop. Use moment().endOf('day') instead.
Wrap the React tree with Sentry.ErrorBoundary so that unhandled render errors show a fallback UI with a reload button instead of unmounting the entire app to a blank white screen.
Return a fallback channel with null instead of undefined when watchAuthState throws. This triggers the normal unauthenticated flow instead of crashing the entire saga tree in a restart loop.
Add a 5-second cooldown between SW controller-change reloads using sessionStorage. Prevents infinite reload loops when iOS kills and resumes a PWA process, triggering controllerchange during init.
Add two Cloud Functions for DSGVO-compliant data retention: - scheduledAnonymizeMovements: strips PII from movements older than the configured retention period, preserving statistical data for BAZL reporting - scheduledCleanupMessages: deletes contact messages older than the configured retention period Both functions are opt-in: they read retention days from Firebase settings and skip entirely if no value is configured. Guard existing onWrite triggers (associated movements, enrich) against anonymized records to prevent cascading writes.
- Fix endAt/< boundary mismatch: use <= for dateTime comparison to be consistent with the inclusive Firebase endAt query - Add comment explaining client-side filtering of already-anonymized records fetched by the endAt query - Assert arrival updates independently in anonymization test - Add test for partial PII fields: verify absent fields are not included in the anonymization update object Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
Add admin UI for managing DSGVO-related settings: - Privacy policy URL (writable, was read-only) - Movement retention days (new) - Message retention days (new) Gated behind `privacySettings` project config flag.
Add project config, theme, landing fee strategy, and Firebase target for the new LSPL aerodrome.
Replace bare privacy policy link with consent sentence "Mit der Anmeldung akzeptieren Sie die Datenschutzerklärung." Hide the consent text on the OTP verification screen.
Move privacy policy link into MarketingLink component so all pages share a consistent centered footer. Use flexbox sticky footer layout to keep it at the viewport bottom.
Material Icons are already self-hosted via webpack bundle in global-style.ts. The CDN link was redundant and caused unnecessary data transfer to Google servers on every page load.
Disable automatic PII collection (IP addresses, cookies, user info). Only error messages and stack traces are needed for debugging.
- Record `privacyPolicyAcceptedAt` timestamp on user profiles for personal logins (on first login) - Record `privacyPolicyAcceptedAt` timestamp on new movement records for all user types (including guest/kiosk) - Show consent text with privacy policy link on the last page of the movement wizard (departure + arrival) - Add Firebase validation rules for the new field on profiles, departures, and arrivals
The Workbox SW precaches index.html but not the bundle (exceeds the 2 MB default). After a deploy the old SW serves stale HTML that references a bundle hash that no longer exists on the server. Firebase's SPA catch-all rewrite returns index.html (HTML, 200) for the missing JS URL, so the browser parses HTML as JavaScript and the app shows a blank page. Excluding index.html from precache lets the browser always fetch fresh HTML from the network (Cache-Control: no-cache is already set in firebase.json), which always references the current bundle.
Hide the privacy consent text in the movement wizard for personal login users since they already consented on the login screen. Only guest, kiosk and ipauth users see it in the wizard. Record privacyPolicyAcceptedAt on the user profile upon login by listening for FIREBASE_AUTHENTICATION_EVENT in the profile saga. Previously this only ran when visiting the profile page.
Guard all firebaseToLocal(snapshot.val()) calls against null values from deleted or missing records. editMovement navigates back to the list, loadMovement returns early, and bulk load / realtime listener paths skip null entries. Fixes JAVASCRIPT-REACT-40 Fixes JAVASCRIPT-REACT-3W
Unsubscribe all Firebase realtime listeners (child events and association watchers) when FIREBASE_AUTHENTICATION_EVENT fires without authData. Prevents orphaned listeners from crashing on undefined auth state after session expiry. Adds safety guard in addMovementToState for events already queued in the channel. Fixes JAVASCRIPT-REACT-49
- Capture generator.next(snapshot) result directly instead of calling generator.next() again on already-finished generator - Remove unused unsubArrival variable in teardownOnAuthLost test Co-authored-by: Roli Züger <rzueger@users.noreply.github.com>
The Cache-Control: no-cache header in firebase.json only matched requests for /index.html, but the app uses hash history so the browser only requests /. Firebase served / with max-age=3600, causing the browser to cache HTML for up to 1 hour. After a new deployment the cached HTML referenced a bundle hash that no longer existed on the server, resulting in SyntaxError and a blank screen. Add no-cache header for / in firebase.json and add NetworkFirst strategy for navigation requests in the service worker.
The bundled Material Icons font does not include the 'shield' glyph. Replace with 'security' which is a shield shape and is available in the font.
Wrap each field with its help text in a FieldGroup with a bottom border separator so it is clear which description belongs to which input.
Change manifest orientation from 'portrait' to 'any' so Android Chrome allows the installed PWA to rotate to landscape. iOS Safari ignores this field, so no impact there. Note: existing installs need to be reinstalled to pick up the change. https://claude.ai/code/session_01CX1JWcjRBXSgg8sVW58pQP
The copyFavicons and generateManifest gulp tasks ran in parallel, causing the static manifest.json (which lacks orientation) to overwrite the generated one. Now copyFavicons excludes manifest files, and generateManifest runs after copyFavicons to guarantee the generated manifest with orientation: 'any' is what ships. https://claude.ai/code/session_01CX1JWcjRBXSgg8sVW58pQP
Bump manifest cache-buster query from ?v=2 to ?v=3 so Chrome on Android fetches the updated manifest with orientation: any. Also add no-cache header for the manifest in Firebase hosting. https://claude.ai/code/session_01CX1JWcjRBXSgg8sVW58pQP
- Add privacyPolicyAcceptedAt (consent timestamp per movement) - Add paymentMethod/invoiceRecipientName (nested PII) - Remove aircraftType, mtow (not PII without immatriculation) - Remove carriageVoucher (boolean, not PII) https://claude.ai/code/session_01KHZDkEfyznUCcoGCWrGdsY
Anonymized records have immatriculation set to null, which crashes localeCompare in compareDescending/compareAscending. Fall back to empty string for null values. https://claude.ai/code/session_01KHZDkEfyznUCcoGCWrGdsY
Anonymized records have null immatriculation, which can't be meaningfully grouped in a per-aircraft landing summary. Skip them and add defensive null-safe sorting. https://claude.ai/code/session_01KHZDkEfyznUCcoGCWrGdsY
## Summary - Treat anonymized movement records as locked in the movement list - Admins see a lock icon and cannot edit/delete anonymized records
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.