- Strip BOM (U+FEFF) from 14 translation files injected by editor
- Guard KMZ unpack against zip-bomb: check entry.uncompressedSize against
50 MB cap (KMZ_DECOMPRESSED_SIZE_LIMIT) before calling .buffer();
limit is an exported constant so tests can override it
- Fix non-BMP HTML entity decoding: replace String.fromCharCode with
String.fromCodePoint + 0x10FFFF bounds check so emoji like 😀
round-trip correctly
- Switch KML namespace stripping from regex to fast-xml-parser's
removeNSPrefix option; XMLValidator accepts namespaced XML natively,
making the pre-strip step unnecessary
- Remove dead skippedCount overwrite after transaction; per-loop
increment already tracks it alongside per-item error messages
- Type multer req.file as Express.Multer.File on both /import/gpx
and /import/map routes instead of (req as any).file
- Add unit tests: emoji entity decoding (decimal + hex), KMZ zip-bomb
rejection, KMZ-with-no-KML rejection
Resolves conflicts with Naver list import (PR #662) — kept both unified
list-import dialog and new KMZ/KML dialog. Dropped duplicate react-dom
import and unused CustomSelect import from PlacesSidebar.
- SSRF: validate user-supplied URLs with checkSsrf() before fetch in
both importNaverList and importGoogleList; upgrade naver.me substring
check to exact hostname comparison to prevent bypass
- i18n: add missing places.importNaverList key to de.ts and es.ts
- migration: switch Naver addon seed to INSERT OR IGNORE to preserve
admin customizations on re-runs; restore budget_category_order
CREATE TABLE to its original formatting
- route: remove redundant cast after type-narrowing guard in naver-list handler
- component: hoist provider ternary above try/catch in handleListImport
- tests: add four new Naver import cases (502, empty list, no-coords,
canonical URL skipping redirect fetch)
- Fix race condition: AbortController cancels in-flight autocomplete
requests on each keystroke; stale responses no longer overwrite fresh ones
- Remove acTrigger state hack; onFocus calls fetchSuggestions directly
- Cap autocomplete input at 200 chars server-side (400 on violation)
- Filter Nominatim suggestions with empty osm_id segments
- Revert getPlaceDetails OSM branch from unconditional parallel fetch to
conditional serial: Nominatim called only when Overpass lacks coords/address
- Wire places.loadingDetails i18n key to Loader2 spinner via aria-label/role
- Add tests: MAPS-017, MAPS-040c, MAPS-093, FE-MAPS-004
- Collapse A2_TO_A3_BASE + let A2_TO_A3 into a single const declaration;
the _BASE copy was vestigial (never read after the clone)
- Add a comment explaining the table's two sources and the load-bearing
invariant: countries whose Natural Earth record has ISO_A2='-99'
(France, Norway) must be listed here since the runtime augmentation
loop skips those features
- Refactor the France-only A3-fallback test fixture into a factory helper
and extend FE-PAGE-ATLAS-041 with a Norway (NOR) case via it.each
- Improve atlas_country_options useMemo: rename a2 → resolvedA2 for
clarity, precompute the A3→A2 reverse-lookup Map once per geoData
change instead of O(n) Object.entries().find() per feature
Removes the client-side guard that blocked toggling vacation entries on
public holiday dates, so users who work on holidays can still book leave.
Also adds a filled blue circle on today's date in the Vacay calendar for
quick orientation.
Closes#651
- Add --bottom-nav-h CSS token (84px + safe-area on mobile, 0px on desktop)
to give all fixes a single source of truth for the nav height
- Apply token to JourneySettingsDialog (fixes#650) and PlacesSidebar
day-picker sheet so bottom-anchored sheets clear the nav bar
- Add paddingBottom to TripPlannerPage Bookings, Lists, and Budget tab
scroll containers so content can be scrolled past the nav
- Bump Modal z-index from z-50 to z-[200] so modals render above the
bottom nav (both share z-50 with nav winning by DOM order)
- Add flex-wrap to settings footer so delete button stays visible when
translated labels (Dutch, German, French) overflow the single row
- Replace no-op pb-safe class with env(safe-area-inset-bottom) inline
style so dialog clears the iOS home indicator on iPhone
Fixes#648, #649
**#541 — File downloads broken in PWA standalone mode**
Replace getAuthUrl + window.open pattern with blob-based fetch using
credentials:include. The old approach minted a 60s single-use ephemeral
token then called window.open, which handed the URL to the system browser
on Android/iOS — losing the PWA cookie jar and producing "invalid or
expired token". The new approach fetches the file directly inside the
PWA WebView as a blob URL, so no auth handoff occurs.
New helper client/src/utils/fileDownload.ts with downloadFile and openFile.
Updated FileManager, ReservationsPanel, ReservationModal, PlaceInspector,
CollabNotes.
Security hardening in fileDownload.ts:
- assertRelativeUrl() guard prevents credentials being sent to external hosts
- openFile() checks blob.type against a safe-inline allowlist; HTML, SVG and
other script-capable MIME types are forced to download instead of being
opened inline, preventing same-origin XSS via blob URLs
- resp.ok check covers all non-2xx responses, not just 401
**#505 — PWA offline session lost on reload**
Wrap authStore with Zustand persist middleware, serializing only
{user, isAuthenticated} to localStorage key trek_auth_snapshot.
maps_api_key is intentionally excluded from the snapshot.
On cold start with no network: persist hydrates isAuthenticated:true,
App.tsx clears isLoading and calls loadUser({silent:true}), ProtectedRoute
renders the dashboard immediately. The network error from loadUser leaves
isAuthenticated intact so no login redirect occurs.
On 401 or logout: store state is cleared, persist writes
{isAuthenticated:false} — stale snapshot does not grant offline access
after session expiry.
- Stop pagination on fetch error (set hasMore=false on non-ok response or catch)
- Set hasMore=false when loading album photos (albums load all at once)
- Hide ScrollTrigger when viewing album photos to prevent timeline photo leak
- Prevent background scroll-through with overscroll-contain and touch event handling
- Use bottom-sheet style on mobile (rounded-t, items-end) for better reachability
- Add extra bottom padding for mobile navbar safe area
- Close dialog when tapping overlay background
- Guard provider badge with truthy check to handle null/undefined provider
- Use explicit provider name matching instead of binary immich/synology fallback
- Remove memories providers from trip addons section
- Show Immich/Synology as sub-items under the Journey global addon
- Same pattern as bag tracking under packing list
- Show "Show more" button on both mobile and desktop when entry text is clamped
- Add "Show less" button when expanded to collapse back
- Add useTranslation hook to ExpandableStory component
- Add i18n keys common.showMore and common.showLess for all 14 languages
- Show spinner and "Uploading..." text on photo upload button in entry editor
- Show spinner on gallery view upload button during upload
- Disable upload buttons while upload is in progress
- Add i18n key journey.editor.uploading for all 14 languages
- Revert filled skeleton entries back to skeleton on delete instead of permanently removing them
- Add per-user hide_skeletons preference on journey_contributors (migration 99)
- Add PATCH /journeys/:id/preferences endpoint for toggling skeleton visibility
- Add Eye/EyeOff toggle button with custom tooltip in journey detail header
- Filter skeleton entries from timeline when hidden
- Add i18n keys for all 14 languages
- testSmtp now surfaces real nodemailer error instead of generic 'SMTP not configured' on send failure
- admin webhook test button uses correct i18n key (was showing 'Test-E-Mail senden' in all languages)
- backup created_at uses stat.mtime instead of unreliable stat.birthtime on Linux
Add missing notes (and other fields) to client Place type so the field
is correctly typed when hydrating the edit form. Fix PlaceInspector to
show description and notes as separate blocks so notes are no longer
hidden when a place also has a description.
Resolves#595. The PDF builder filtered reservations through a transport-only
allow-list, silently dropping all non-transport types. Replace the allow-list
with a single hotel exclusion (hotel is already covered by the accommodations
block) so every other reservation type now appears in the daily itinerary.
Add per-type icon and accent colour matching the existing ReservationsPanel
palette, and per-type subtitle builders (party size, venue, operator) plus a
generic location line for future use.
- Enable attributionControl and add OSM attribution to JourneyMap TileLayer
- Memoize sidebar map entries array to prevent unnecessary map rebuilds
- Use stable callback reference for onMarkerClick
jsdom replaces globalThis.AbortController with its own implementation;
Node.js undici-based fetch validates signals via instanceof against the
native AbortSignal, causing fetch to throw before MSW could intercept.
Fix via custom Vitest environment (tests/environment/jsdom-native-abort.ts)
that captures native AbortController/AbortSignal before jsdom patches them
and restores them after jsdom setup.
Also updates JournalBody test 004 to match component behaviour (headings
rendered as <p>) and removes debug console.log statements.
- broadcastJourneyEvent now excludes by socket ID instead of user ID,
so other devices of the same user receive real-time updates (#615)
- Routes pass x-socket-id header through to broadcast functions
- loadJourney handles 404 gracefully — redirects to /journey with
toast instead of infinite spinner (#616)
- Gallery/timeline load thumbnails instead of originals (50-100KB vs 2-5MB)
- Batch endpoint for adding multiple provider photos in one request
- Optimistic photo deletion — no full page reload on delete
- Immich albums include shared albums
- Select-all button moved outside scroll container (always visible)
- Album tab loads actual album contents via /albums/:id/photos
Replace bulk-loading all Immich photos (up to 20k) with paginated
search: 50 photos per page, automatic infinite scroll via
IntersectionObserver. Prevents server blocking on large libraries.
- Backend: searchPhotos accepts page/size params, returns hasMore
- Frontend: loads 50 at a time, appends on scroll
- AbortController cancels in-flight requests on tab switch
Large Immich libraries (7k+ photos) caused timeouts and pending
requests when using "All Photos". Cap pagination at 5 pages (5000
photos) and abort in-flight requests when switching tabs.
- Load actual album photos instead of date-range search fallback
(new GET /albums/:id/photos for Immich + Synology)
- Add select all / deselect all toggle in photo picker
- Normalize Markdown headings to plain text in journal stories
- Fix setext headings (---) rendering as hr instead of h2
- Add remark-breaks for proper line break rendering
- Fix pros/cons dark mode gradient backgrounds
- i18n: selectAll/deselectAll in 14 languages
- Render h1/h2/h3 as plain paragraphs — journal stories are plain
text, not structured documents
- Preprocess text to insert blank line before --- and === so they
become horizontal rules instead of setext headings
Add remark-breaks plugin so single newlines in note content render
as <br> instead of being collapsed by Markdown. Applies to both
the card preview and the expanded view.
Introduce trek_photos as central photo registry. Frontend uses
/api/photos/:id/:kind instead of provider-specific URLs. Adding
a new photo provider is now backend-only work.
- New trek_photos table (migration 98) with photo_id FK in
trip_photos and journey_photos
- Unified /api/photos/:id/thumbnail|original|info endpoint
- photoResolverService for central resolution and streaming
- ProviderPicker: add "All Photos" tab, rename tabs, fix i18n
- Localize all hardcoded strings in JourneyDetailPage (14 langs)
- Fix date formatting to use browser locale instead of hardcoded 'en'
- Journey stats as styled tile cards
- Integrate a loading spinner for "Name" input field during place search.
- Enhance OpenStreetMap place detail retrieval with Nominatim lookup.
- Update `authStore` to track Google Maps API key presence.
The category filter bridge was collapsing Set<string> to a single
string, emitting '' (no filter) whenever more than one category was
selected. Map now uses the same Set-based membership predicate as the
sidebar list filter.
Closes#602
LOCALES is now built via Object.fromEntries from SUPPORTED_LANGUAGES,
so adding a new language only requires one change in supportedLanguages.ts.
Also types translations as Record<SupportedLanguageCode, ...> so TypeScript
enforces that every supported language has a translation entry.
Move language list to supportedLanguages.ts so TranslationContext and
settingsStore can import from a single source of truth, eliminating
the hardcoded array in setLanguageTransient.
- Use apiClient instead of raw fetch() in configApi.getPublicConfig
- Validate DEFAULT_LANGUAGE against supported codes on server startup
- Log warning instead of silently swallowing fetch errors in LoginPage
- Case-insensitive browser language matching in detectBrowserLanguage
- Guard against undefined navigator in detectBrowserLanguage
- Validate language code in setLanguageTransient before applying
- Import directly from TranslationContext instead of barrel index
Replace the language cycling button on the login page with a dropdown
showing all 14 supported languages. Add automatic browser/OS language
detection via navigator.languages, falling back to a configurable
DEFAULT_LANGUAGE env var, then 'en' as last resort.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Map tooltips now respect light/dark mode via CSS variables
- Journey creation inherits cover image from first selected trip
- Only day-assigned places are synced to journey (no unplanned places)
- Place count in trip picker reflects assigned places only
- Contributor avatars shown in journey detail page
- Suggestion banner button visible in dark mode (!important override)
- Dashboard list view uses correct trips array and status label
Revert common.loading and common.saving from Unicode ellipsis (…) back to
three dots (...) to match the rest of the project (e.g. "Optional caption...").
Update 4 test files that were incorrectly using the Unicode ellipsis character.
- Replace all remaining hardcoded strings in JourneyDetailPage JourneySettingsDialog with t() calls
- Add 14 missing translation keys to all 13 non-English language files
(trips.member*, common.expand/collapse, inspector.remove, memories.*, journey.*)
- Fix common.loading and common.saving to use Unicode ellipsis (…) instead of three dots (...)
- Update 4 test files that expected three-dot ellipsis to use Unicode ellipsis
- All 2541 tests passing
Replace 6 hardcoded German strings in PhotoUpload.tsx with t() calls:
- 'Tag verknüpfen' → t('photos.linkDay')
- 'Kein Tag' / 'Tag N' → t('photos.noDay') / t('photos.dayLabel')
- '{N} Foto(s) ausgewählt' → t('photos.photoSelected/photosSelected')
- 'bis zu 30 Fotos' hint → t('photos.fileTypeHint')
- 'Wird hochgeladen...' → t('common.uploading')
Add all 6 new keys to all 14 language files and update test
assertions from German strings to English equivalents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests were asserting against hardcoded German strings that were replaced
with t() calls. Updated to match the English translation values rendered
by TranslationProvider in the test environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix critical bug: Photos and Files pages had German text hardcoded in JSX,
now use t() keys visible correctly in all languages
- Add 16 new translation keys (photos/files UI, login validation, common errors,
rate limit message) across all 14 language files
- Add missing keys in packing, memories, and budget sections for br, de, it, es,
fr, nl, pl, cs, hu, ru, zh, zh-TW, ar
- Add 152+ missing keys for zh-TW (entire sections were absent)
- Change Vacay addon name to 'Férias' in pt-BR only
- Add client-side HTTP 429 interceptor that shows translated rate limit message
- Replace hardcoded English fallbacks in TripPlannerPage, DayPlanSidebar,
DisplaySettingsTab, MapSettingsTab, AccountTab, and TodoListPanel with t()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export __clearVersionCacheForTests() from adminService; call in
versionNotification beforeEach to reset module-scoped cache between
tests (VNOTIF-002..006 failed because VNOTIF-001 cached
update_available:false, short-circuiting all subsequent test fetches)
- Seed appVersion:'2.9.10' in Navbar test authStore; appVersion moved
from local useEffect state to authStore in last commit so the test
render no longer fetches it independently (FE-COMP-NAVBAR-016)
- Add data-testid="weekend-days" to VacaySettings weekend-days
container; use within() in tests to scope button count to that
section, fixing false positives from the week-start buttons which
share the same inline styles (FE-COMP-VACAYSETTINGS-003/004)
- Pass isPrerelease={true} in GitHubPanel FE-ADMIN-GH-007; component
filters out prerelease releases when isPrerelease=false so the badge
was never rendered (pre-existing, unrelated to last commit)
- Type checkVersion() with VersionInfo interface; fixes TS errors in
checkAndNotifyVersion() where object type blocked property access
- Don't cache fallback on !resp.ok or fetch throw; prevents a transient
GitHub outage from poisoning the 5-min version cache
- Guard parseInt result with Number.isFinite() in compareVersions;
malformed -pre.abc tags no longer silently compare as equal via NaN
- Pre-compute stripped versions before sort in checkVersion(); avoids
mutating input array and redundant replace() calls in comparator
- Bump GitHub releases fetch from per_page=20 to per_page=100
- Store appVersion in authStore; populate from App.tsx getAppConfig call
and remove redundant getAppConfig fetch in Navbar useEffect
- Type GitHubPanel error/expanded state as string|null and Record<number,boolean>
- Add concurrency groups to both workflows to prevent parallel version-bump races
- Defer git tag push to merge job so orphan tags can't exist without a live image
- Pin build/merge jobs to the SHA captured in version-bump to prevent TOCTOU
- Guard auto-finalize in docker.yml against cross-major prereleases (requires bump=major + confirm_major=MAJOR)
- Add STABLE fallback to 0.0.0 for fresh repos with no stable tag
- Fix cleanup sort to extract numeric N via awk instead of fragile sort -t. -k4 -n
- Add 5-minute in-memory cache to checkVersion to avoid GitHub API rate limits
- Type GitHubPanel releases state; remove any cast on filter
- Quote all $VERSION/$MAJOR_TAG vars in imagetools create calls
- Add docker-dev.yml: prerelease CI for dev branch with minor/major bump
inputs; auto-continues in-flight major line via existing pre tags;
publishes floating major-pre Docker tag (e.g. 2-pre)
- Rewrite docker.yml version-bump: tag-based versioning, manual bump
inputs (auto/patch/minor/major), major guarded by confirm_major=MAJOR,
auto-finalizes in-flight prereleases; publishes floating major tag (e.g. 2)
- Inject APP_VERSION build-arg through Dockerfile so the running container
knows its real version instead of reading package.json
- Server reads APP_VERSION env in authService/adminService; exposes
is_prerelease in app config and update-check response; prerelease builds
compare against GitHub prerelease releases rather than latest stable
- Client stores isPrerelease from config; navbar shows amber version badge
on prerelease builds (left of dark-mode toggle); GitHubPanel filters out
prerelease releases unless the running build is itself a prerelease
EXIF was only available for Immich photos and inconsistent for local
uploads. Removed entirely for now — cleaner lightbox with just photo,
nav, counter, and caption. Nav buttons now show on hover (desktop)
and always on mobile.
- New setting in Vacay Settings to choose Mon or Sun as week start
- DB migration adds week_start column to vacay_plans (default: Monday)
- Calendar grid and weekday headers adapt to the selected start day
- Weekend column highlighting works correctly for both modes
- Translations added for all 14 languages
Use position:fixed with calculated coordinates instead of
position:absolute so the dropdown escapes the overflow:hidden
container. Also adds a backdrop to close on outside click.
When a place has a google_place_id, the Maps link now uses the place
name + query_place_id for an exact match. Falls back to lat,lng
coordinates when no google_place_id is available.
- Mobile hero now shows spotlight trip (next upcoming / ongoing) instead of only ongoing
- Reuse SpotlightCard component for mobile hero (same as desktop)
- Smaller status badges on non-hero trip cards (9px text, compact padding)
- CircleCheck icon for completed trips instead of Clock
- Add new fields to AppConfig type and buildAppConfig factory
- Update FE-PAGE-ADMIN-018: heading changed to "Authentication Methods"
- Update FE-PAGE-ADMIN-053: oidc_only toggle removed from OIDC panel
- Update FE-PAGE-LOGIN-007/017: mocks now include password_login/oidc_login
- Update ADMIN-SVC-049: updateOidcSettings no longer writes oidc_only
Replaces the coarse oidc_only + allow_registration settings with four
independent toggles: password_login, password_registration, oidc_login,
oidc_registration. Each can be enabled/disabled individually in
Admin > Settings without affecting the others.
- Add resolveAuthToggles() in authService.ts as the central resolver;
falls back to legacy oidc_only/allow_registration keys when new keys
are absent (backward compat)
- OIDC_ONLY env var still works and overrides DB toggles for password_*,
with a visual lock in the admin UI when active
- Server enforces lockout prevention: cannot disable all login methods
- oidc_login gate added to OIDC /login and /callback routes
- Remove oidc_only toggle from OIDC settings panel; replaced by the
granular toggles in the Settings tab
- Add 6 new resolveAuthToggles() unit tests; fix AUTH-DB-033 error
message assertion
- Update OIDC_ONLY descriptions in README, docker-compose, Helm values,
Unraid template, and .env.example to clarify override semantics
Closes#492
- Fix#521: `isVisitedFeature()` now scopes name-based region matching to
the feature's parent country (via `iso_a2`), preventing same-name regions
in different countries (e.g. Luxembourg BE vs LU) from falsely lighting up
- Fix#489: Add ~50 missing countries to COUNTRY_BOXES, NAME_TO_CODE, and
CONTINENT_MAP so the bounding-box fallback correctly identifies Georgia
instead of falling through to Russia/Azerbaijan's overlapping boxes
- DASH-016/017: Spotlight trip not in list view — test non-spotlight trip instead
- DASH-021: New trip appears in both mobile + desktop — use getAllByText
- Add title attributes to action buttons in SpotlightCard, MobileTripCard, TripCard
so tests can find them by accessible name (edit, delete, archive, copy)
- Remove FE-PAGE-PLANNER-018 test — MemoriesPanel moved to Journey addon
- 5-table schema (journeys, entries, photos, trips, contributors) with migrations 87-91
- Trip-to-Journey sync engine with skeleton entries and photo sync
- Full CRUD API for journeys, entries, photos with Immich/Synology integration
- Timeline, Gallery and Map views with entry editor (markdown, mood, weather, pros/cons)
- Journey frontpage with hero card, stats and trip suggestions
- Public share links with token-based access and photo proxy
- PDF photo book export (Polarsteps-inspired)
- Dashboard redesign: mobile greeting, live trip hero, quick actions, unified card design
- BottomNav profile sheet with settings/admin/logout
- DayPlan mobile inline place picker
- TripFormModal members management
- Vacay calendar trip date indicator dots
- Fix contributor photo access (403) for journey Immich/Synology photos
- Trip deletion cleanup for journey skeleton entries
- i18n: 231 new keys across all 14 languages (native translations, no fallbacks)
12 of 14 language files showed 'Immich-Einstellungen gespeichert' (or
equivalent) instead of the actual provider name when saving settings.
The frontend already passes provider_name to the translation function;
only the translation strings were wrong.
- Fix endpoint path: users now provide full base URL (e.g. https://nas:5001/photo)
- Add OTP/2FA field for Synology login
- Add skip SSL verification option (DB column + checkbox UI)
- Add device ID (synology_did) column for session tracking
- Trigger in-app notification when Synology session is cleared
- Show disconnection banner in MemoriesPanel
- Add URL hint in provider settings
- Map Synology API error codes to human-readable messages
- Update i18n for all locales
Adds new and expanded test suites across client and server to cover the
OAuth 2.1 scope system, MCP session manager, collab service, unified
memories helpers, OIDC service, budget slice, and OAuth authorize page.
Also extends SonarQube coverage exclusions to include bootstrapping files
(migrations, scheduler, main.tsx, types.ts) that are not meaningfully
testable.
jsdom's FormData is incompatible with undici's ReadableStream serialisation
used by MSW 2.x — requests hang under CI resource constraints but pass locally.
Replace server.use() + implicit HTTP roundtrip with vi.spyOn().mockResolvedValueOnce()
for all five FormData POST tests (uploadAvatar, uploadRestore, addFile, importGpx).