Commit Graph

1416 Commits

Author SHA1 Message Date
Maurice 743b724cbc feat(packing): three-tier sharing — personal, shared-with-people, common pool (#858)
Rework the private-packing flag into a full sharing model. Every item is now
Common (the group pool — where all existing items live, so nothing breaks),
Personal (private to its owner) or Shared with specific people (it shows up on
those travelers' own lists, marked "by <bringer>"). is_private discriminates
restricted from common; a new packing_item_recipients table holds who a shared
item covers, and packing_item_contributors records "I can bring that too"
pledges on Common items.

The panel gains a Gemeinsam / Meine Liste view switch, each item a sharing
control (owner sets the tier + the people it covers), and Common items can be
co-brought or cloned onto your personal list. Visibility is enforced server-side
in listItems and the WebSocket broadcasts are scoped to exactly who can see an
item across every tier transition. All 22 locales stay in parity.
2026-06-30 19:12:43 +02:00
Maurice 04f2ec72c6 feat(trips): guest members for accountless participants (#1362, #1291)
Add "guest" trip participants — people without a Trek account who can still be
assigned to costs, packing, to-dos and day-plan activities. A guest is a
credential-less users row (is_guest=1) joined into trip_members, so it is
assignable everywhere a real member is, with the cost-splitting, settlement,
packing and assignment paths working unchanged.

Guests are firewalled from everything account-related: they can never sign in
(password, OIDC and reset lookups skip them), never appear in the global user
directory, the member-add picker or admin user management, are never resolved as
notification recipients, can't be invited to another trip, and can't be made
owner. The trip owner manages guests from the share dialog in a dedicated,
clearly-labelled section (add / rename / remove), and guests carry a "Guest"
badge wherever members are picked. All 22 locales stay in parity.
2026-06-30 15:03:57 +02:00
Maurice 7673aa52f2 fix(packing): drop the always-true guard in the row drag handler (#969)
The onDragOver guard `drag.isDragging || true` is a constant condition (eslint
no-constant-condition). The handler is already gated by canDrag, so run the
drag-over logic directly, matching the to-do row.
2026-06-30 15:03:57 +02:00
Maurice e56a901d82 i18n: translate the booking link field across all locales (#935)
Fan out reservations.urlLabel / reservations.urlPlaceholder to the remaining
locales so the dedicated booking URL field is localised everywhere.
2026-06-30 15:03:57 +02:00
Maurice 641711322e feat(trips): transfer trip ownership to a member (#973)
Add POST /api/trips/:id/transfer so the owner can hand a trip to one of its
existing members. The swap runs in a transaction: the new owner takes
trips.user_id and the former owner is kept on as a regular member, so nobody
loses access. The endpoint is owner-only, writes a trip.transfer_ownership
audit entry and broadcasts the refreshed trip. The members modal gains a
"Make owner" action, shown only to the current owner.
2026-06-30 15:03:57 +02:00
Maurice fac2393388 feat(lists): reorder packing/to-do lists and private packing items (#969, #858)
Add drag-to-reorder to the packing and to-do lists, mirroring the budget
panel's native HTML5 drag pattern. A drag within a filtered/grouped view is
mapped back onto the global order so untouched items keep their place, and the
order persists optimistically via the existing reorder endpoints.

Packing items can now be marked private (#858): a private item is visible only
to its owner. createItem/bulkImport stamp the owner, listItems filters by the
viewer, and the WebSocket broadcasts are scoped to the owner so a private item
never reaches another member's screen — including the public/private toggle
transitions. Owners get a lock toggle and a private indicator on their items.
2026-06-30 15:03:57 +02:00
Maurice 7eac5a5a02 feat(files): render uploaded Markdown files inline (#1345)
Markdown (.md/.markdown) is now an allowed upload type and opens in a rendered preview in the file manager instead of just downloading. Reuses the existing react-markdown stack with rehype-sanitize (these are untrusted uploads, so output is sanitized) and detects markdown by extension first since browsers send unreliable MIME for .md.
2026-06-30 15:03:57 +02:00
Maurice d6ed7a60a0 feat(bookings): add a dedicated URL field to reservations (#935)
Bookings get a first-class url column (migration) instead of users pasting links into notes. It's editable in the booking modal and rendered as a clickable link on the reservation card. The reservation request schemas are open passthroughs, so only the entity schema + service SQL enumerate it.
2026-06-30 15:03:57 +02:00
Maurice ad64df42ed test(video): cover the new upload-handler branches
Add controller tests for the gallery-video route (success / no-video / not-allowed / cleanup-on-reject), the per-asset media_types loops (gallery + entry, batch + single), and the file-manager per-type cap + unlink-on-rejection — restoring branch coverage on src/nest above the 80% gate.
2026-06-30 12:27:28 +02:00
Maurice 4af35b162e test(video): update gallery accept selector + complete fileService mocks
The gallery upload input now accepts image/*,video/* — update the two JourneyDetailPage selectors that matched the old value. The files/journey e2e suites mock fileService and were missing the new MAX_VIDEO_SIZE / isVideoExtension / isVideoMime exports, which broke module load.
2026-06-30 12:27:28 +02:00
Maurice 20c1858b23 fix(video): harden upload handling and fix video playback edge cases
Security: the gallery-video poster is now always stored as .jpg instead of the client-supplied extension, so a poster declared image/* but named x.html / x.js can't be written with that extension and served inline same-origin; local gallery files are also served with X-Content-Type-Options: nosniff.

Robustness: rejected/unauthorised uploads no longer orphan their bytes on disk (the gallery-video and file-manager handlers unlink before throwing); the file-manager per-type size cap is keyed on the extension like the filter, so a real video labelled application/octet-stream isn't wrongly rejected. UX: the file-manager thumbnail strip shows a play placeholder for video instead of a broken image; shared (public) journeys now return media_type and play videos with a play badge; and a poster-less video shows a neutral tile instead of a broken thumbnail.
2026-06-30 12:27:28 +02:00
Maurice e986c9ab27 feat(video): upload and play videos in the trip file manager
The file manager (which already attaches files to a place/activity) now accepts video uploads up to the larger video cap — other types stay at the document limit — and the lightbox plays them with the Plyr player over the plain same-origin download URL, so cookie auth and HTTP Range both work. Videos are excluded from the offline blob prefetch so one clip can't evict a trip's documents.
2026-06-30 12:27:28 +02:00
Maurice 61ffdb553e test(photos): assert the forwarded Range arg on the original stream
Follow-up to the Range-aware photo proxy.
2026-06-30 12:27:28 +02:00
Maurice 1abc9b2bc7 feat(video): link and stream Immich videos in the journey gallery
Immich timeline and album listings no longer filter out videos; each asset now carries its media type, which the provider picker forwards when linking. A linked video streams through Immich's transcoded /video/playback endpoint, and the asset proxy forwards the viewer's Range header (and passes 206/Content-Range back) so the player can seek. Synology video stays excluded until its stream API is verified.

Adds media_type/media_types to the provider-photos request contract.
2026-06-30 12:27:28 +02:00
Maurice 8713443665 feat(video): use Plyr for the gallery video player
Swaps the bare <video> element for a Plyr-wrapped player so playback controls match a consistent, cleaner skin. The instance is created per source and destroyed on unmount, so the lightbox stops playback when you navigate away.
2026-06-30 12:27:28 +02:00
Maurice c92c02e1b8 feat(video): play local gallery videos in the journey gallery
Picking a video in the journey gallery now captures a poster frame + duration in the browser and uploads the raw clip; the grid shows the poster with a play badge and the lightbox plays it with a native video player (HTTP Range seeking). Images keep their existing HEIC-normalised path. No server-side transcoding.

Server media_type work was committed separately.
2026-06-30 12:27:28 +02:00
Maurice 993d9bf713 feat(video): media_type discriminator + local gallery video upload (server)
trek_photos gains a media_type column (migration) so the registry can hold video as well as images. A new POST :id/gallery/video endpoint accepts a video plus a client-captured poster (500 MB cap, video MIME/extension allowlist), stores the poster as the thumbnail, and the photo stream serves the poster for the thumbnail kind and the raw file (HTTP Range) for the original — without running the image thumbnailer on video bytes.
2026-06-30 12:27:28 +02:00
Maurice c7e4b2781b docs(wiki): document force-offline, selective storage and conflicts 2026-06-30 10:04:15 +02:00
Maurice a88cd772cf i18n(offline): offline settings strings across all locales 2026-06-30 10:04:15 +02:00
Maurice 98d11d4267 feat(offline): Settings -> Offline controls and a status banner
The Offline tab gains a force-offline switch, a prepare-for-offline download with progress, per-trip and map-tile storage toggles, and a conflict resolver with a default strategy. The floating status pill now reflects forced-offline and unresolved conflicts.
2026-06-30 10:04:15 +02:00
Maurice 6707dac4a9 feat(offline): force-offline mode, selective sync and a conflict queue
A force-offline override routes every read to the cache and every write to the queue; preparing for offline downloads trip data, documents and map tiles up front and waits for them to finish. Map tiles and individual trips can be left out of the cache. Queued edits carry the version they were based on so the queue can surface server conflicts for a keep-mine / keep-theirs decision; chained offline edits to one entity no longer conflict with each other, and evicting a trip preserves its unsynced writes.
2026-06-30 10:04:15 +02:00
Maurice c552472b63 feat(offline): detect update conflicts on the server for places and packing
Update handlers accept an optional X-Base-Updated-At token and reject a stale overwrite with 409, returning the current server row. An absent token keeps the existing last-write-wins behaviour, so older clients are unaffected. packing_items gains an updated_at column (migration + stamped on every insert) so it can take part in conflict detection too.
2026-06-30 10:04:15 +02:00
Maurice 5fd66f4833 feat(map): include the day's route in the map fit (#1128)
Selecting a day already fits the map to that day's destinations; this also
folds the route polyline into the bounds. BoundsController fits the
destinations immediately, then re-fits once — when the day's route finishes
computing asynchronously — to destinations + the full route, so a route that
bulges past its stops (a detour or ferry) stays in view. One-shot per day
selection, so later route-profile toggles don't re-zoom.
2026-06-30 00:04:38 +02:00
Maurice 50609b078a feat(places): bulk "change category" from the selection toolbar
Closes the UI half of #1168: in the Places selection mode, a new tag button
before delete opens a category picker that applies one category (or "No
category") to every selected place in a single request. Adds a REST
/places/bulk-update endpoint reusing updatePlacesMany, an offline-aware repo +
store action that patches both the place pool and the day-assignment
projections, undo grouped by each place's prior category, and the i18n keys
across all locales.
2026-06-29 23:19:33 +02:00
Maurice 42b45dcd82 feat(dashboard): show the year on trip dates from other years
Trip dates only showed month + day, so trips from other years were ambiguous
(#1323). Dashboard cards and the boarding-pass hero now include the year, and
so does the shared formatDate (planner day headers etc.) — but only when it
isn't the current year, so this year's trips stay compact. Order and
punctuation follow the locale (EN "Sep 10, 2026", DE "10. Sep 2026").
2026-06-29 22:29:57 +02:00
Maurice 9dd9057b7b feat(mcp): add bulk_update_places tool
Apply the same field values to many places in one call instead of one
update_place per place — e.g. re-categorising 80 POIs at once. Adds the
updatePlacesMany service (one transaction, trip-scoped, partial patch
built on updatePlace) and the bulk_update_places MCP tool with the usual
demo/access/place_edit guards and a place:updated broadcast per place.
2026-06-29 22:29:57 +02:00
Maurice 23987c76bb harden calendar feeds: absolute URLs, real disable, folding, schema sync
- Resolve feed URLs against the request host when APP_URL is unset, so the
  webcal:// / Add-to-Google links work on a default install (not just behind a
  configured reverse proxy).
- Give the public link a real off switch: POST enables, PUT rotates, DELETE
  clears the token (feed_token = NULL). The subscribe dialog no longer mints a
  token just from being opened — the user opts in explicitly.
- Fold ICS content lines at 75 octets (UTF-8 safe) in exportICS, so download
  and feed both stay RFC 5545-compliant for long/non-ASCII summaries.
- Extract VEVENTs by structural line scan instead of a lazy END:VEVENT regex
  that user text could truncate.
- URL-encode the Google Calendar cid; mirror feed_token into schema.ts.
- Collapse the duplicated all-trips modal into the shared IcsSubscribeModal.
2026-06-29 21:53:06 +02:00
michael-bohr 7173e82fe8 feat(feeds): subscribable ICS calendar feeds for trips
Adds TripIt-style live calendar subscriptions alongside the existing one-time
.ics download. A trip (or all of a user's trips) exposes a secret, revocable
feed URL that Google/Apple/Outlook poll to stay in sync.

- Public read endpoints GET /api/feed/trip/:token.ics and /api/feed/user/:token.ics
  (no auth — the secret token is the credential), reusing the existing exportICS()
  generator and adding REFRESH-INTERVAL / X-PUBLISHED-TTL hints.
- JWT-guarded token endpoints to generate (lazy, idempotent) and regenerate/revoke
  per-trip and per-user feed tokens; tokens stored in nullable feed_token columns.
- All-trips feed excludes archived trips and trips ended >90 days ago.
- UI: ICS toolbar button becomes a Download/Subscribe menu; modal offers one-click
  "Add to Google Calendar" (render?cid=webcal://) and a webcal:// link for
  Apple/Outlook, plus copy-link fallbacks. All-trips feed reachable from dashboard.
- Feed base URL read from the existing APP_URL env var.

Purely additive: new endpoints + two nullable columns, no breaking changes.

Tests: server/tests/e2e/feeds.e2e.test.ts covers lazy token generate + idempotency,
regenerate-invalidates-old, 401/404 auth+access, public feed content-type + hint
injection, unknown-token 404, and the archived/>90-day all-trips exclusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:53:06 +02:00
Maurice 72dfa2c60c docs(helm): clean up existingClaim notes
Strip stray zero-width characters from the persistence docs, move the PVC
note out of the ENCRYPTION_KEY usage block into its own Persistence section
in NOTES.txt, and document that persistence.enabled=false falls back to an
ephemeral emptyDir.
2026-06-29 21:00:25 +02:00
yael-tramier d19305bda4 fix(helm): emptyDir is used as a fallback when persistence is disabled. 2026-06-29 21:00:25 +02:00
yael-tramier 7aa2f6e4f2 feat(helm): Add existingClaim variable for custom PVC usage. 2026-06-29 21:00:25 +02:00
Maurice 3e64cb86a6 chore(i18n): sync Vietnamese with latest dev keys
Add the keys dev gained since this PR opened so the new vi locale keeps full
parity: the help namespace (wiki help center), settings appearance options,
costs split modes, dashboard Unsplash cover search, the insecure-cookie login
hint, nav.help and the admin group labels.
2026-06-29 20:48:51 +02:00
leeduc e4efcf0840 feat(i18n): add Vietnamese translations 2026-06-29 20:48:51 +02:00
Zorth Thorch e34f40b686 feat(costs): Splitwise-like cost splitting
Add per-payer and per-member custom split amounts with Equally, Custom and
Ticket split modes on top of the existing equal split, keep legacy "paid by"
expenses working, and document the modes in the Budget Tracking wiki page.
2026-06-29 20:34:24 +02:00
Maurice 3701ab6cad feat(auth): explain the plain-HTTP secure-cookie gotcha on login
When the server issues a Secure session cookie but the request arrived over
plain HTTP (the common LAN install over http://ip:3000), the browser drops
the cookie and the next request dead-ends on a bare "Access token required" —
the top source of avoidable install issues. The login response now flags this
exact case and the login page shows a localized box explaining the fix (use
HTTPS, or set COOKIE_SECURE=false) with a link to the Troubleshooting guide.
It only triggers in the real failure case, never for correct HTTPS setups.
2026-06-29 18:32:58 +02:00
Maurice e91f592f22 feat(help): embed the TREK wiki as an in-app help centre
Add a Help section (profile menu, /help) that renders the GitHub wiki inside
TREK. /api/help fetches the wiki markdown — the nav from _Sidebar.md, pages,
and proxied images — from GitHub and caches it (1h TTL, serves stale on
outage), so it auto-syncs on wiki edits with no redeploy and the client never
calls GitHub directly. The page is styled to match TREK with a section
sidebar, search and react-markdown; wiki [[links]] are rewritten to in-app
routes and HTML-comment placeholders are stripped. Page state lives in a
useHelp() hook per the page pattern. Adds nav.help and a help namespace
across all locales.
2026-06-29 18:32:58 +02:00
Maurice 1cc69fc22a refactor(admin): group the admin sidebar tabs into sections
The admin sidebar had 11 flat tabs. PageSidebar now supports optional group headings (backward-compatible; the Settings sidebar stays flat), and the admin tabs are grouped into Users, Configuration, Integrations and Maintenance. Group labels added across all locales.
2026-06-29 13:59:00 +02:00
Maurice 4d131db9af refactor(settings): rename the Display tab to General and group its settings
The Display tab became a catch-all once theming moved to its own Appearance tab, and its 'Display' label no longer fit. It is now 'General' (Allgemein) and split into 'Language & region' and 'Travel & map' sections. Tab labels and the new section titles are added across all locales.
2026-06-29 13:59:00 +02:00
Maurice f5d03e7213 chore(about): remove the monthly supporters section 2026-06-29 13:59:00 +02:00
Maurice 891171ce6c feat(appearance): mark the Readability section as experimental
Transparency-off, density and per-size typography are best-effort while the token migration is ongoing, so the section carries an Experimental badge. Adds the i18n key across all locales.
2026-06-29 13:59:00 +02:00
Maurice 720edce2ee fix(appearance): make the dashboard hero boarding-pass solid with transparency off 2026-06-29 13:59:00 +02:00
Maurice b27793f99a fix(appearance): shorten the Auto color-mode label to 'Auto' on mobile 2026-06-29 13:59:00 +02:00
Maurice 813db0ca6e feat(appearance): show per-size text controls inline with examples
The four size-class sliders (Large/Medium/Normal/Small) are now always visible instead of behind a disclosure, each with a live sample rendered at that size and an example of what it affects (e.g. Normal = place names/descriptions, Small = addresses/labels).
2026-06-29 13:59:00 +02:00
Maurice 741639edf0 feat(appearance): granular per-size text scaling with live preview
The text-size control now adjusts each size class (Large / Medium / Normal / Small) independently as well as all-at-once. Inline px sizes are mapped to a class by their value, so the per-class sliders reach real content; each class variable = global factor x its per-class factor (no double-scaling with the root font-size that handles rem text). The settings UI gains a live preview that resizes as you drag, and the four size sliders sit behind a clear toggle.
2026-06-29 13:59:00 +02:00
Maurice bb8f4d4e5e fix(appearance): keep i18n key parity and update the scaled-emoji test
Add the new appearance settings keys (widget group titles, sidebar/density hints) to every locale so the strict key-parity check passes, and update the single-emoji chat test to expect the now-scalable calc() font size.
2026-06-29 13:59:00 +02:00
Maurice fac043c691 fix(appearance): clearer widget settings, density hint, solid surfaces with transparency off
Dashboard widget settings are grouped by where they sit on the dashboard (below the hero / right sidebar / bottom of page); the right-sidebar master toggle now nests its individual widgets and greys them out when the sidebar is off, instead of a confusing flat list mixing the master with its children. Density gains an explanatory hint plus a real compact spacing effect. Transparency-off also solidifies the Atlas glass panels and tooltip, Leaflet zoom controls and GL popups — class-based surfaces via CSS, the Atlas inline panels via a noTransparency flag.
2026-06-29 13:59:00 +02:00
Maurice a3f395e5ac fix(appearance): scale inline px font sizes so text-size reaches all content
The global text-size control only set the root font-size, which scales rem-based text (navbar, menus) but not the dense inline px sizes used across the trip planner, budget, journey and panels — so place titles and addresses stayed fixed. applyAppearance now also exposes the factor as --fs-scale-text, and a codemod wraps inline numeric fontSize in calc(<px> * var(--fs-scale-text, 1)) across components and pages (map popups and PDF excluded). Sizes are byte-identical at 100%; the control now visibly resizes the actual content.
2026-06-29 13:59:00 +02:00
Maurice b6a414b79f chore(appearance): add theme:lint guard for hardcoded styles
A theme:lint script (modeled on i18n:parity) flags new inline color/fontSize literals and arbitrary-hex Tailwind classes that bypass the design tokens, so future code stays themeable. Map/PDF surfaces are exempt. The token taxonomy and the six theming rules are documented in src/theme/README.md.
2026-06-29 13:59:00 +02:00
Maurice 200108b76a feat(dashboard): per-device widget visibility with layout reflow
Dashboard widgets (currency, timezones, upcoming reservations, atlas and the stat tiles) can be shown or hidden independently on desktop and mobile from the appearance settings. The stat grid spreads its visible tiles to full width, and disabling the right sidebar collapses the layout to a single centered column.
2026-06-29 13:59:00 +02:00
Maurice a7334a9060 feat(settings): appearance settings tab
New Appearance tab with color mode (moved out of Display), color-scheme swatches, a custom accent picker with a live WCAG contrast hint, transparency and reduce-motion toggles, density, a global text-size slider with advanced per-tier controls, and per-device dashboard widget toggles. Edits preview live and commit on a short debounce. i18n keys added across all locales, translated for German.
2026-06-29 13:59:00 +02:00