* fix(mcp): replace relative oauth constent redirect by absolute redirect derived from APP_URL (#987)
* feat(journey): convert HEIC/HEIF uploads to JPEG for cross-platform compatibility
HEIC is an Apple-only format not recognised as an image by many browsers
and platforms. heic-to (lazy-loaded) now converts HEIC/HEIF files to JPEG
before upload in both the gallery and entry editor photo pickers.
Embedded metadata (EXIF, GPS) may be lost during conversion — documented
in the Journey Journal wiki page.
* fix(journey): skip heic-to import for non-HEIC files to avoid test env failures
* fix(notifications): prevent double-escaping HTML in password reset emails
buildPasswordResetHtml passed a pre-built HTML block to buildEmailHtml,
which then escaped it again — rendering raw tags as plain text in the email.
* fix(security): equalise login response timing to prevent user enumeration (CWE-208)
Always run bcrypt.compareSync regardless of whether the email exists, using a
module-scope DUMMY_PASSWORD_HASH for unknown/OIDC-only accounts. Also wraps the
login handler in a 350ms minimum-latency pad (matching /forgot-password) as
defence-in-depth against CPU jitter and future code-path drift.
Fixes: CWE-203, CWE-208 — Observable Timing Discrepancy (CVSS 5.3 Medium)
* chore(deps): patch hono/picomatch/ip-address/brace-expansion CVEs, bump to node:24-alpine
Extends server/package.json overrides to pin hono >=4.12.16, picomatch >=4.0.4,
brace-expansion >=2.0.3, ip-address >=10.1.1. Adds matching overrides to client/.
Lockfiles regenerated to resolve: hono 4.12.18, ip-address 10.2.0, picomatch 4.0.4.
Also bumps base image node:22-alpine -> node:24-alpine (reduces base image CVEs)
and adds .github/workflows/security.yml to gate PRs on critical/high CVEs via
Docker Scout.
Addresses: CVE-2026-44456, CVE-2026-44455 (hono), CVE-2026-42338 (ip-address),
CVE-2026-33671, CVE-2026-33672 (picomatch), CVE-2026-33750 (brace-expansion)
* chore: update emails in security.md
* ci(security): use docker/login-action for Scout auth instead of env vars
* chore: regenerate lock files
* chore: correct secret names
* chore: pr perms write
* fix(docker): remove package-lock.json from production image after npm ci
Docker Scout reads package-lock.json as an SBOM source and reports all
lockfile entries including devDependencies (e.g. picomatch via vitest/vite)
even when they are not physically installed. The lockfile has no runtime
purpose after npm ci completes, so delete it to ensure Scout only reports
packages actually present in node_modules.
* fix(docker): remove npm CLI from production image to eliminate bundled CVEs
picomatch@4.0.3, brace-expansion@5.0.4, and ip-address@10.1.0 were all
coming from /usr/local/lib/node_modules/npm — npm's own bundled packages
shipped with node:24-alpine. The production container only needs the node
binary to run the server; npm is unused at runtime.
Removing npm + npx after npm ci drops the package count from 500 to 365
and eliminates all npm-ecosystem CVEs (0H 0M remaining from npm packages).
Only busybox CVE-2025-60876 remains, which has no fix in Alpine 3.23.
* fix(deps): remove client overrides and brace-expansion server override; audit fix
brace-expansion ^2.0.3 in the client forced all installations to v2, breaking
minimatch in CI (test:coverage path via @vitest/coverage-v8 -> test-exclude)
which expects the named-export API of brace-expansion v5. The CVE it targeted
(>=4.0.0,<5.0.5) was only in npm's own bundled packages, already eliminated
by removing npm from the Docker image.
Also removes picomatch and ip-address client overrides for the same reason:
all three CVEs sourced from /usr/local/lib/node_modules/npm/, not app deps.
Drops brace-expansion from server overrides (server uses v2.1.0, outside the
affected range >=4.0.0).
* fix(#981): align public share itinerary order with daily planner (#985)
The public share page rendered daily items in a different order than the
authenticated planner because it used a simplified, divergent merge
algorithm. Five specific bugs:
1. shareService never loaded reservation_day_positions, so per-day
transport positions were lost on the share page (fell back to
day_plan_position ?? 999, pushing transports to the bottom).
2. Multi-day transports (overnight trains/flights) only appeared on their
start day due to date-string filtering instead of day_id span logic.
3. Assignment-linked transports appeared twice (once as place, once as
transport card) because the assignment_id exclusion was missing.
4. Time-based transport insertion was absent; missing positions used 999
instead of a computed fractional position from the place timeline.
5. created_at tiebreaker was missing for assignments and notes with equal
order_index/sort_order, making order non-deterministic on the share page.
Fix: extract the authoritative merge logic (parseTimeToMinutes,
getSpanPhase, getDisplayTimeForDay, getTransportForDay, getMergedItems)
from DayPlanSidebar into client/src/utils/dayMerge.ts and use it in both
the planner and SharedTripPage. Enrich the shareService payload with
day_positions from reservation_day_positions and add created_at tiebreakers
to the assignment and day_notes ORDER BY clauses.
* fix(#983): shift owner vacay entries when update_trip moves trip window
updateTrip() now calls shiftOwnerEntriesForTripWindow() which looks up
the owner's own vacay plan (not the active plan) and shifts all entries
in the old date window by the same offset as the trip start date.
sharp's prebuilt Linux x64 binary requires SSE4.2 (x86-64-v2), causing a
SIGILL crash on older hardware (e.g. AMD A6-3420M). Replace with jimp, a
pure-JS image library with no native binaries. Also skip thumbnail generation
entirely when the Journey addon is disabled (the default), preventing the
issue for most installs regardless of the image library used.
- Mapbox GL provider alongside Leaflet for trip and journey maps (opt-in in
settings with token, style presets incl. 3D on satellite, quality mode,
experimental badge).
- GPS "blue dot" with heading cone on mobile; three-state FAB (off / show /
follow), geodesic accuracy circle, desktop-hidden since browser IP geo is
too coarse for navigation.
- Marker drift fix: outer wrap no longer carries inline position/transform,
so mapbox's translate keeps the pin pinned at every zoom and pitch.
- Journey map popup (mapbox-gl): Apple-Maps-style tooltip on marker
highlight/click showing entry title + location / date subline.
- Journey feed reorder: up/down controls to the left of each entry reorder
sort_order within a day. Server endpoint, optimistic store update, rollback
on failure.
- Journey entry editor: desktop modal now centers over the feed column only,
backdrop still blurs the whole page (map included).
- Scroll-sync guard on journey: marker click locks the sync so smooth-scroll
can't steer the highlight to a neighbouring entry mid-animation.
- Misc: map top-padding aligned with hero, live/synced badges replaced by a
compact back-button in the hero, skeleton entries no longer pollute the
journey map, journey detail no longer shows map on mobile path when
combined view is active.
Server-side notice registry with per-user condition evaluation (firstLogin,
existingUserBeforeVersion, addonEnabled, dateWindow, role, custom).
Notices are sorted by priority then severity, filtered against dismissals
stored in a new user_notice_dismissals table, and served via
GET /api/system-notices/active + POST /api/system-notices/:id/dismiss.
Client renders notices through a host component that partitions by
display type (modal / banner / toast). The modal renderer supports
multi-page pagination with directional slide transitions, keyboard
navigation, and correct dismiss-all semantics on CTA / X / ESC.
Dismissals are optimistic with a single background retry.
Includes 3.0.0 upgrade notices (v3-photos, v3-journey, v3-features),
onboarding welcome modal, and full i18n coverage across 15 languages.
The /journey route is addon-gated on both client and server.
Also includes: unit + integration test suites, registry integrity test
that validates action CTA IDs against client source, and technical
documentation in docs/system-notices.md.
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.
- 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)
vitest@4 requires vite@^6, causing two conflicting esbuild versions in
the lockfile and EBADPLATFORM errors during Docker npm ci. Pin to vitest
3.x which supports vite@5 and resolves a single esbuild@0.21.5.