Commit Graph

1124 Commits

Author SHA1 Message Date
jubnl 70ba4d5435 fix(reservations): show day date range on accommodation cards
Hotel reservations store their date range in day_accommodations rather
than on reservation_time, so the card date block never rendered. Pull
accommodation_start_day_id / accommodation_end_day_id from the SQL join
and surface them on the card.

Also apply Maurice's badge-pill pattern (day name + localized date pill)
to the day-range display, consistent with the modal day selectors.
2026-04-21 18:12:40 +02:00
jubnl 881b9d0939 chore: add troubleshooting in bug report template 2026-04-21 17:25:59 +02:00
jubnl 758de855bf docs: more common issues in troubleshooting 2026-04-21 17:22:09 +02:00
jubnl 9652874bbd fix: update dockerignore and gitignore 2026-04-21 17:02:49 +02:00
jubnl 840f5e82aa docs: update contributing wiki page 2026-04-21 16:57:38 +02:00
jubnl d59b3334dc docs(wiki): add Contributing and Development-environment to sidebar and cross-links 2026-04-21 16:52:38 +02:00
Maurice 5a64d8994e Merge pull request #785 from mauriceboe/fix/synology-cached-thumbnail-size
fix: bump synology cached thumbnail size sm->m (#782)
2026-04-21 15:28:56 +02:00
Maurice e6222894e9 fix: bump synology cached thumbnail size sm->m (#782)
fetchSynologyThumbnailBytes was still serving 240px while the
uncached streamSynologyAsset path had been bumped to 320px in
#761. Align the cached path with the streaming default.
2026-04-21 15:21:58 +02:00
Julien G. 9d48c06068 Merge pull request #783 from mauriceboe/fix/pdf-thumbnail-lat-lng
fix: pass lat/lng/name to placePhoto in PDF thumbnail fetch
v3.0.0-pre.57
2026-04-21 14:27:14 +02:00
jubnl 9f70b56a3a fix: pass lat/lng/name to placePhoto in PDF thumbnail fetch
Without these args, the Wikimedia fallback (used when no Google Maps key
is configured) silently skips the fetch because lat/lng are NaN. The plan
view (PlaceAvatar/photoService) already passes all three; this aligns the
PDF path with the same behaviour.
2026-04-21 14:21:11 +02:00
Julien G. 232dc78cc9 Merge pull request #781 from mauriceboe/fix/pdf-thumbnail-mcp-places
fix: PDF thumbnails missing for MCP-added places
v3.0.0-pre.56
2026-04-21 13:53:20 +02:00
jubnl d2c44380a4 doc: add missing pages in wiki 2026-04-21 13:44:08 +02:00
jubnl 2f9d7adf4a fix: PDF thumbnails missing for MCP-added places (osm_id)
fetchPlacePhotos only checked google_place_id, skipping places that
only have osm_id (e.g. those added via MCP). Mirror PlaceAvatar logic
by falling back to osm_id in both the filter and the photo fetch call.
2026-04-21 13:43:15 +02:00
Julien G. ba4a64241b Update Discord link in contribution guidelines 2026-04-21 13:34:52 +02:00
Maurice ee14f706c8 Merge pull request #780 from mauriceboe/feat/day-selector-date-badge
feat: show date badge on day selectors + i18n transport modal titles
2026-04-21 12:54:19 +02:00
Maurice 1cc43f63df fallback day-number badge when a day has no date
If a trip has no dates set but a day has a custom title, the
dropdown showed only the title with no context. Fall back to
'Day N' as the badge so users can still tell which day it is.
2026-04-21 12:34:45 +02:00
Maurice 3450bd59f8 feat: show date badge on day selectors + i18n transport modal titles
Day selectors in the Transport, Reservation and Hotel-Day-Range
modals only showed the renamed day title once a day had a custom
name — hiding the actual date. Added an optional badge prop to
CustomSelect, rendered as a pill next to the label, and wired the
date badge onto all affected dropdowns. FileManager day section
headers got the same pill for consistency.

Also translated transport.addTransport and transport.modalTitle.*
in all 13 non-English language files; the keys existed but still
carried the English source string.
2026-04-21 12:28:43 +02:00
Maurice 457d436cf6 Merge pull request #778 from mauriceboe/fix/public-mobile-trip-photos-filter
fix: filter [Trip Photos] container from mobile public view (#764)
v3.0.0-pre.55
2026-04-21 11:29:53 +02:00
Julien G. 1127efb9c4 Merge pull request #777 from mauriceboe/fix/issues-773-774-backups-and-trip-files
fix(backups,files): auto-backups rejected by validator; trip file download broken after cookie migration
2026-04-21 11:24:44 +02:00
Maurice 0a98d3c2e7 fix: filter [Trip Photos] container from mobile public view (#764)
MobileMapTimeline received the raw entries array, bypassing the
synthetic-container filter applied to timelineEntries. On screens
below the lg breakpoint (<1024px) the [Trip Photos] sync container
leaked back into the combined map+timeline view.
2026-04-21 11:24:07 +02:00
jubnl 5eaf7492dc fix(backups,files): auto-backups rejected by validator; trip file download broken after cookie migration
Fixes #773: isValidBackupFilename regex anchored to ^backup- rejected all
auto-backup-* filenames, causing 400 on download/restore/delete. Broadened
to ^(?:auto-)?backup-.

Fixes #774: three regressions in the trip Files tab —
- openFile import shadowed by a local function of the same name inside
  FileManager; PDF preview modal was calling the local with a URL string,
  corrupting state and crashing on the second click (mime_type read on
  undefined). Fixed by aliasing the import as openFileUrl.
- GET /:id/download used a bespoke authenticateDownload that checked only
  Bearer header and ?token= query param, ignoring the trek_session cookie.
  After the JWT-to-cookie migration the client sends cookies only, so every
  download silently 401-ed. Extended authenticateDownload to accept req and
  check cookie → Bearer → query token in priority order.
- files.download and files.openError translation keys were missing from all
  15 locale files; t() was returning the raw key as a truthy string,
  defeating the || 'Download' fallback.
2026-04-21 11:18:17 +02:00
jubnl ee31c78db8 fix(maps): null stale proxy image_url entries instead of writing unbacked proxy URLs
Migration 107 and the previous fix both wrote /api/maps/place-photo/<id>/bytes
into places.image_url without ever fetching the photo bytes. photoService
short-circuits on that URL prefix and hits /bytes directly, which 404s because
nothing is on disk.

- Add migration to null proxy image_url rows with no backing google_place_photo_meta
  entry — restores the normal fetch-and-cache flow for affected rows
- Fix the previous legacy-URL migration to null instead of rewrite, so fresh
  installs don't hit the same 404 path

Fixes #770 (follow-up)
v3.0.0-pre.54
2026-04-21 00:46:29 +02:00
jubnl edf14e2ebc test(maps): update getPlacePhoto stubs to use text() instead of json()
mapsService now reads the details response body via .text() before parsing,
so test stubs need text() rather than json().
v3.0.0-pre.53
2026-04-21 00:16:54 +02:00
jubnl 2aad8f465c fix(maps): prevent server crash when legacy Google photo URLs are stored as placeIds
Migration 107 only rewrote image_url rows matching /places/%/photos/%; URLs using
the /place-photos/ or /places/<opaque> paths survived the upgrade and were passed
verbatim to the Places API, producing a malformed request whose empty/HTML response
body threw SyntaxError before detailsRes.ok was checked. The resulting rejection was
leaked by placePhotoCache.setInFlight via an unhandled .finally() chain, triggering
Node 22's default unhandledRejection=throw and terminating the process.

- placePhotoCache: add .catch() after .finally() to prevent unhandled rejection crash
- mapsService: reject URL-shaped placeIds early; read response as text before JSON.parse
- migrations: add migration to rewrite remaining googleusercontent/places.googleapis URLs
- MapView/MapViewGL: prefer stable proxy URL form of image_url before google_place_id

Fixes #770
2026-04-21 00:13:35 +02:00
jubnl 16b81a8356 fix(bookings): preserve accommodation dates when place is unlinked or missing
- Remove NOT NULL constraint on day_accommodations.place_id (migration)
  and change ON DELETE CASCADE → SET NULL so deleting a place no longer
  cascades to the accommodation row
- Switch listAccommodations / getAccommodationWithPlace to LEFT JOIN so
  accommodations without a linked place are visible to the modal
- Relax create/update guards in reservationService to only require
  start_day_id + end_day_id, not place_id; place_id remains optional
- Client save guard now sends create_accommodation whenever FROM/TO days
  are set, regardless of whether a hotel place was selected
- Add re-hydration useEffect in ReservationModal to back-fill hotel
  fields from the accommodations prop when it arrives after modal opens
  (race between isOpen and the tripAccommodations fetch)
- Fix demo-seed TDZ crash: move db Proxy declaration before DEMO_MODE
  block so circular require in demo-reset resolves correctly
- Sidebar accommodation badge falls back to reservation title when
  place_name is null; click/cursor disabled for placeless accommodations
- listAccommodations now joins reservations to expose reservation_title
v3.0.0-pre.52
2026-04-20 23:09:05 +02:00
Maurice 5984adb2ea Merge pull request #768 from mauriceboe/fix/ui-pre-release-bugs
fix: pre-release UI bug batch (#759 #760 #761 #763 #764)
2026-04-20 22:18:53 +02:00
Maurice f8eb1915fe fix(map): render transport reservations on Mapbox GL
ReservationOverlay was Leaflet-only: react-leaflet components, L.divIcon,
panes, useMap/useMapEvents. When the user switched the planner map to
Mapbox GL, the entire feature disappeared — no polylines, no endpoint
badges, no clickable IATA labels.

Add a matching overlay for the Mapbox renderer:

- New reservationsMapbox.ts with an imperative `ReservationMapboxOverlay`
  class — mapbox-gl is imperative, so a React component wrapper would
  fight its own lifecycle every render. The manager owns one GeoJSON
  source + line layer for the arcs, one HTML `mapboxgl.Marker` per
  endpoint badge, and one per flight stats label. It cleans itself up
  when the map is rebuilt (style/token/3d toggle) or unmounted.
- Geometry helpers (great-circle arc, antimeridian split, haversine,
  tz-aware duration math, label formatting) are copied from the Leaflet
  overlay so both renderers produce the same lines. Great-circle is
  useful even on the Mapbox globe because the mercator projection mode
  still draws the short-way line, and the antimeridian split prevents
  a NYC↔Tokyo flight from wrapping halfway around the planet.
- Flights / cruises get geodesic arcs; trains / cars get straight
  lines. All four types get clickable endpoint badges with the
  matching lucide icon; only flights render the rotating mid-arc stats
  label (IATA → IATA · distance · duration) — same rule as the Leaflet
  overlay.
- The stats label's rotation is recomputed on every `render` event by
  projecting two points straddling the arc midpoint, which keeps it
  parallel to the arc as the camera rotates/zooms on the globe.
- Visibility thresholds mirror the Leaflet overlay (per-type min pixel
  distance before a line / endpoint label is worth drawing).
- MapViewGL now accepts the `reservations`, `visibleConnectionIds`,
  `showReservationStats`, `onReservationClick` props that the Leaflet
  MapView already took. `visibleConnectionIds` is honoured the same way
  — the per-booking toggle in DayPlanSidebar controls which routes
  appear, so switching the renderer doesn't lose that UX.
- Added a `mapReady` gate so the overlay can only add its source/layer
  once the map's `load` handler has attached the other trip sources;
  the gate resets on every style rebuild.
2026-04-20 22:09:19 +02:00
jubnl b556c636eb fix: tighten 401 redirect allowlist and add reset-password paths
Replaced loose includes()/startsWith() path checks with exact equality
for static routes and strict prefix matching for dynamic-token routes.
Added /forgot-password and /reset-password to the allowlist so the
password-reset flow is usable without auth. Extracted isAuthPublicPath
as a pure testable function with 14 unit tests covering regressions.
2026-04-20 21:55:15 +02:00
Maurice b20db1428d fix: pre-release UI bug batch
- Budget table column alignment: the NAME data cell had
  `display: flex` directly on the <td>, which pulled it out of the
  table-layout and desynced the column widths between data rows and the
  AddItemRow. Moved the flex wrapper into a <div> inside the cell.
  Closes #759
- Packing list: template-apply and bulk-import handlers called
  `window.location.reload()` to refresh the list, which re-rendered the
  whole trip loading screen. Both flows now merge the returned items
  into the trip store instead. Closes #760
- Journey timeline: move-up / move-down arrows were rendered on
  skeleton suggestions — skeletons are places from the linked trip and
  don't participate in sort order. Skip canReorder when
  entry.type === 'skeleton'. Closes #763
- Journey public view: the synthetic `[Trip Photos]` and `Gallery`
  entries produced by syncTripPhotos were leaking into the public
  timeline and map. The owner view already strips these in
  JourneyDetailPage — apply the same filter on JourneyPublicPage.
  Gallery photos still come from every entry so a shared gallery keeps
  showing the trip-synced photos. Closes #764
- Journey thumbnails: public gallery grid was loading the original
  asset for every tile. `photoUrl()` now takes an optional kind and the
  grid requests `thumbnail`; the lightbox still opens the original.
  Synology thumbnail default bumped from `sm` (240px) to `m` (320px)
  because `sm` looked pixelated on retina. Closes #761
2026-04-20 21:53:45 +02:00
Julien G. 4a5a59cb78 Merge pull request #766 from mauriceboe/security/audit-fixes-batch-1
security: internal audit — batch 1
2026-04-20 21:41:00 +02:00
jubnl 20bf9c2312 security: close SEC-H4/H6 gaps from second-pass review
- SEC-H6: remove conditional audience check in mcp/index.ts — audience is
  now always enforced against the mcpResource URL. Add migration to revoke
  pre-existing oauth_tokens with audience=NULL so dead rows don't linger.
- SEC-H4: validate doc.issuer against config.issuer inside discover() to
  prevent a MITM'd discovery doc from supplying a crafted expected issuer.
  verifyIdToken caller now passes config.issuer as ground truth, not
  doc.issuer.
- tests: cover three new OIDC callback failure paths (no_id_token,
  id_token_invalid, subject_mismatch) and two idempotency caps (key length
  >128 chars returns 400, body >256 KiB skips caching).
2026-04-20 21:35:30 +02:00
Maurice 9f57ab4517 security: address second-pass audit findings
- CI-C1 false positive: actions/{checkout,setup-node,upload-artifact}
  @v6 do exist (v6.0.0 releases published Oct-Dec 2025). Restore the
  @v6 refs — the earlier batch-1 commit downgraded them unnecessarily.
- Widen idempotency_keys primary key to (key, user_id, method, path)
  via new migration. Batch 1 widened the middleware lookup but left
  the table PK at (key, user_id), so `INSERT OR IGNORE` silently
  skipped the second endpoint that reused a key — the cache was
  never populated for it and a replay re-ran the handler. The
  migration rebuilds the table preserving existing rows (the old
  narrower PK guarantees no conflicts against the new looser key).
- HSTS: keep `includeSubDomains` OFF by default. Enabling it for
  every NODE_ENV=production install would break apex-domain setups
  where siblings still serve HTTP. Operators who want the stricter
  policy opt in with HSTS_INCLUDE_SUBDOMAINS=true.
- Extend the idempotency unit tests to cover the (method, path)
  dimension — same user+key on different path no longer replays.
2026-04-20 21:04:09 +02:00
Maurice 292e443dbe security: address silent-failure review findings on top of batch 1
Second-pass fixes caught by a self-review after the initial commit — each
one would have undermined a fix from the previous commit.

- mfaPolicy now goes through `verifyJwtAndLoadUser` too. Without this,
  a JWT stolen before a password reset still satisfied `require_mfa`
  until its natural 24h expiry, defeating the whole point of the
  password_version bump.
- Drop the `?? keys[0]` fallback in OIDC JWKS key selection. When the
  token carries a `kid` that is not in the current JWKS, refuse
  outright instead of picking an arbitrary key and letting the
  signature check produce a generic failure — the real failure mode
  deserves a specific error code.
- Tighten OAuth DCR custom-scheme rule so `javascript:`, `data:`,
  `vbscript:`, `file:`, `blob:`, `about:`, `chrome:` are all rejected.
  Previously the catch-all "not http/https" check admitted them; the
  authorize flow later 302s the browser to whatever is registered,
  which with a `javascript:` URI would execute attacker script on
  redirect. Also require the private-use scheme body to be reverse-DNS
  (contain a dot), matching RFC 8252 §7.1.
- permanentDeleteFile / emptyTrash only delete the trip_files row when
  the on-disk unlink actually succeeded. Previously Promise.all
  swallowed individual unlink failures and DELETE ran unconditionally,
  so a permission / ENOSPC failure would orphan bytes on disk.
- restoreFromZip also invalidates the permissions cache in the outer
  catch. If extraction threw before the DB swap even started, the
  cache wasn't stale, but belt-and-braces is cheap and guarantees no
  failed-restore path leaves stale cache behind.
2026-04-20 20:44:57 +02:00
Maurice 2d0414b4a3 security: internal audit — batch 1
Fixes the critical + high + medium findings from our internal security
review. Bundled into one PR because the changes overlap heavily (JWT
verification unifies across three call sites; backup-code hashing and
demo-email handling cross-cut several services); splitting them out
would mean redundant reviews of the same files.

Critical
- CI-C1 — .github/workflows/test.yml: restore actions/{checkout,setup-
  node,upload-artifact} to @v4. The @v6 refs don't exist, so the test
  workflow was errorring before a single test ran.
- SEC-C1 — mfaPolicy now extracts the token via extractToken() (cookie-
  first, Bearer fallback). Previously it only read Authorization, so
  every cookie-authenticated SPA session bypassed require_mfa entirely.
- SEC-C2/C4/C6 — all JWT verification paths (MCP bearer, file download,
  photo route) now go through the shared verifyJwtAndLoadUser that
  checks password_version. resetPassword additionally deletes every
  mcp_tokens row and marks outstanding oauth_tokens revoked, so a
  password reset invalidates ALL credential classes — not just the
  cookie JWT.

High
- SEC-H2 — reset email URL is built from server-side APP_URL /
  ALLOWED_ORIGINS (via existing getAppUrl()), not request headers.
  Closes the host-header-injection vector into reset links.
- SEC-H3 — OIDC findOrCreateUser wraps the invite-redemption UPDATE +
  user INSERT in a transaction. The UPDATE is the capacity check; if
  a concurrent callback takes the last slot, the whole transaction
  aborts with registration_disabled instead of double-creating users.
- SEC-H4 — new verifyIdToken() performs full JWT signature
  verification via the provider's JWKS (Node's crypto.createPublicKey
  accepts JWK directly — no extra dependency), plus iss/aud/exp
  checks. The callback also rejects the login when userinfo.sub does
  not match id_token.sub.
- SEC-H5 — OAuth DCR now validates redirect_uris against an allowlist
  of schemes: https, http-loopback, or a private custom scheme. Plain
  http://non-loopback is rejected.
- SEC-H6 — oauthService audience defaults to mcpResource when the
  `resource` parameter is missing, so tokens are always audience-bound
  to /mcp instead of being issued with audience=null.
- SEC-H7 — HSTS is enabled any time NODE_ENV=production (previously
  required FORCE_HTTPS=true), includeSubDomains defaults on and can
  be disabled with HSTS_INCLUDE_SUBDOMAINS=false.
- SEC-H8 — trek_session cookie Secure flag is also driven by
  req.secure (which Express resolves from X-Forwarded-Proto once
  trust proxy is set), so instances behind a TLS-terminating proxy
  get Secure cookies without needing FORCE_HTTPS.

Medium
- SEC-M1 — permanentDeleteFile / emptyTrash / avatar unlink now use
  fs.promises.rm with { force: true } (one async op vs the previous
  existsSync + unlinkSync pair per file).
- SEC-M2 — invalidatePermissionsCache() is called inside restoreFromZip
  so a restored DB with different permission rows is honoured
  immediately.
- SEC-M3 + C1 — idempotency store bounds the key at 128 chars, caches
  only responses ≤ 256 KiB, and scopes the lookup by (key, user_id,
  method, path) rather than (key, user_id). Same key replayed against
  a different endpoint no longer returns a stale unrelated body.
- SEC-M4 — share_tokens gets an expires_at column; new tokens default
  to 90-day TTL, expired tokens are denied at lookup. Existing tokens
  stay NULL = no expiry so already-published links don't break.
- SEC-M5 — /uploads/photos/:filename now resolves the photo to its
  trip_id and requires the share token to cover THAT trip. Previously
  any share token for any trip would unlock any photo filename.
- SEC-M6 — BLOCKED_EXTENSIONS is the single source of truth shared
  between fileService and collab uploads. The '*' allowed_file_types
  wildcard now still rejects executables/scripts.
- SEC-M7 — single DEMO_EMAILS constant (services/demo.ts) used by
  demoUploadBlock, mfaPolicy, and every demo-mode guard in
  authService. The old demoUploadBlock only matched 'demo@nomad.app'
  so the seed 'demo@trek.app' could in fact upload in demo mode.
- SEC-M8 — MFA backup codes are now bcrypt-hashed at rest
  (hashBackupCodeBcrypt). matchBackupCode accepts both bcrypt and
  legacy SHA-256 hex hashes, so existing installs keep working until
  the user regenerates codes via enableMfa.
- SEC-M9 — document the "security via UUID v4 filename" model for
  /uploads/avatars|covers|journey. Requires no code change but
  captures the decision so future reviewers don't re-flag it.
- SEC-M10 — already covered by the resetPassword revocation logic
  above: mcp_tokens DELETE + oauth_tokens UPDATE … SET revoked_at.

Performance
- PERF-H1 — new migration adds the indexes flagged in the audit:
  trips(user_id), trips(created_at DESC), photos(day_id),
  photos(place_id), reservations(day_id), share_tokens(token), plus
  conditional day_accommodations and notifications indexes depending
  on which columns are present.

Tests
- tests/integration/oidc.test.ts now mocks verifyIdToken and passes
  an id_token in the exchangeCodeForToken stub for the three flows
  that exercise a successful callback. The three remaining failures
  tests pointed out were all pre-existing (file-upload flakes +
  notificationPreferences event_types count drift), none introduced
  by this PR.
2026-04-20 20:36:52 +02:00
Maurice e612de9143 Merge pull request #757 from mauriceboe/feat/todo-due-reminders
feat(notifications): reminders for todos with upcoming due dates
2026-04-20 17:43:59 +02:00
Maurice c857d38bcd test(notifications): bump event_types count to 9 after adding todo_due 2026-04-20 17:38:25 +02:00
Maurice d7a71c0572 feat(notifications): reminders for todos with upcoming due dates
Todos already support a due_date field but nothing notifies the user
when a deadline is approaching — you'd only remember if you happened
to look at the Lists tab. This wires a reminder into the existing
notification pipeline so due-date todos behave like trip-start
reminders.

Details:
- New `todo_due` event type alongside trip_reminder; all four channels
  (in-app, email, webhook, ntfy) supported and toggleable per user in
  Settings > Notifications.
- New daily scheduler task (9 AM local TZ) queries unchecked todos
  whose due_date is within the next 3 days. Each todo gets at most
  one reminder per 24 hours, tracked via a new todo_items.reminded_at
  column (migration 116).
- If the todo has an assigned user, only that user is reminded; if
  not, every member of the trip gets the notification.
- Strings added in all 15 UI languages and for all notification
  carriers.
- Gated by app_settings.notify_todo_due (default on) so admins can
  disable it globally.
2026-04-20 17:31:25 +02:00
Julien G. 58c061e653 Merge pull request #756 from mauriceboe/fix/planner-drag-drop-jank
fix(planner): eliminate drag-and-drop jank in trip planner
2026-04-20 17:23:06 +02:00
Maurice 22d1d06d39 docs(readme): point hero GIF URL at renamed trek-media repo 2026-04-20 17:17:08 +02:00
jubnl 290f566daa fix(planner): eliminate drag-and-drop jank in trip planner
- Suppress trek-stagger animation on the day list while a drag is active
  so nth-child delays (0–320 ms) no longer re-fire on every hover change
- Replace sibling drop-indicator <div> injections with borderTop/borderBottom
  on the target row to prevent nth-child index shifts during drag
- Dedup setDragOverDayId calls in onDragOver handlers so setState is only
  invoked when the active day actually changes
- Move initTransportPositions out of getMergedItems (render path) into a
  useEffect to stop mid-drag setState cascades
2026-04-20 17:16:57 +02:00
Maurice 8ca2507050 Merge pull request #755 from mauriceboe/fix/readme-hero-gif-external
docs(readme): move hero GIF to external release asset
2026-04-20 17:11:07 +02:00
Maurice 9c666a0aaf docs(readme): move hero GIF to external release asset
Moves the 91 MB product-tour GIF out of the repo entirely. Standard
clones and CI checkouts no longer pull it — even LFS-aware clients
previously downloaded the blob on checkout, which made `git pull`
noticeably slower for everyone.

The file now lives as a release asset on a separate repo and is
referenced from README via its GitHub Fastly-backed download URL.
Removes the LFS tracking entry from .gitattributes.
2026-04-20 17:09:02 +02:00
Julien G. b3f2f7308a Merge pull request #748 from mauriceboe/docs/wiki
Docs/wiki
2026-04-20 16:50:50 +02:00
Maurice af9b31c1ff Merge pull request #754 from mauriceboe/fix/journey-gallery-picker-safari
fix(journey): repair gallery picker grid collapsing in Safari (#717)
2026-04-20 16:47:54 +02:00
jubnl d7d1493289 docs(wiki): document self-service password reset feature
Update Password-Reset.md and Login-and-Registration.md to reflect the
email-based forgot-password flow added in feat(auth): 51387b0, including
the SMTP-less console fallback, MFA gate, session invalidation, rate
limits, and security properties.
2026-04-20 16:43:53 +02:00
Maurice 54e042b736 fix(journey): repair gallery picker grid collapsing in Safari (#717)
The 'From Gallery' picker on the journey entry editor used `aspect-square`
on grid items inside an overflow-scrolling container. Safari (desktop and
iOS) collapses the computed height of aspect-ratio boxes in this layout,
which stacked every thumbnail at y=0 — making selection impossible.

Swap to the classic padding-top spacer pattern (`paddingTop: '100%'` on
the cell + absolutely positioned image) which is bulletproof across
browsers and preserves the 5/6-column grid on mobile/desktop.
2026-04-20 16:43:21 +02:00
Julien G. 0ba31847eb Merge pull request #753 from mauriceboe/dev
Dev
2026-04-20 16:36:34 +02:00
Maurice 26ab39dc21 Merge pull request #752 from mauriceboe/feat/readme-redesign
docs(readme): Apple-style redesign — animated hero, feature tiles, product tour
2026-04-20 16:27:59 +02:00
Maurice 00be0eab05 docs(readme): Apple-style redesign — animated hero, feature tiles, gallery
- Animated TREK logo (light + dark variants) via <picture> + prefers-color-scheme
- 60-second product tour GIF (91MB, 1100x619, 10fps) stored via Git LFS so
  standard clones don't pull it by default
- 9 feature tiles as composite SVG grids: 3x3 on desktop, 2x4 on mobile
- 8 fresh screenshots captured from dev.pakulat.org
- Feature details folded into a collapsible 2-column table
- Environment variables moved behind a collapsible
- Roadmap badge added next to Live Demo / Docker / Discord
- Removed redundant Community section and footer
2026-04-20 16:25:38 +02:00
Maurice ed97bb1deb Merge pull request #750 from mauriceboe/feat/password-reset
feat(auth): password reset via email with MFA + session invalidation
2026-04-20 14:16:17 +02:00