Commit Graph

2682 Commits

Author SHA1 Message Date
Maurice a70eea5bf2 feat(costs): let a trip keep its costs as totals only (#1714)
Travellers who track what a trip costs but never split it saw an
Unfinished warning on every expense and a settle-up they had no use for.
A trip now has a costs mode (appended migration, trips.costs_mode, split
by default); totals leaves payers, shares and settling up out of Costs on
desktop and phone through the same gate as a trip of one, and saves new
expenses without a payer. PUT /api/trips/:id and update_trip take it on
budget_edit. The split chips move into their own component.
2026-11-13 10:17:03 +01:00
Maurice c9c419b4ad feat(costs): leave out owing and settling on a trip of one (#2689)
With one traveler every share is the whole amount, yet Costs showed owe,
owed, outstanding, settle-up, balances and the final budget. A shared gate
(showsSplitting) now hides that side on desktop and phone, unless the ledger
still names someone else; the expense editor drops who paid and the split
while the expense holds nothing more. The payment dialog moves into its own
file to keep the panel under the size ratchet.
2026-11-13 10:17:03 +01:00
Maurice 6759277c3b feat(costs): link a trip file as an expense receipt (#2839)
A booking confirmation that is also the invoice had to be uploaded a second
time to sit on the expense. The expense editor (desktop and phone) now lists
the trip's files to link, the booking's own first; the save sends the id in
receipt_file_ids, which the server already links. The desktop receipts block
moves into its own component.
2026-11-13 10:17:02 +01:00
Maurice 10d5f3ed46 feat(costs): split a custom expense by shares (#1165)
A stay shared by people who slept a different number of nights had to be
worked out by hand. The custom split now takes shares next to amounts and
percent: five nights against four split the total 5/9 to 4/9, cent-exact by
largest remainder. Only the amounts are saved, so server and MCP are
unchanged; the phone sheet gains the same unit switch, percent included.
2026-11-13 10:17:02 +01:00
Maurice 2934319722 feat(costs): start a new expense in the trip currency (#2807)
A new expense opened in the display currency, so every bill on the road
needed the trip currency picked from the full list. It now starts in the
trip's own currency (a scanned receipt keeps the one it read), and the
trip and display currencies lead the picker on desktop and phone.
2026-11-13 10:17:02 +01:00
Maurice d4c81bc5f2 chore(lint): lower the warning baseline for the two promise rules the dock change cleared 2026-11-13 08:42:01 +01:00
Maurice 35e0613986 test(native): read the listener names as strings, so the appLinks case typechecks 2026-11-13 08:34:05 +01:00
Maurice ccfa92f650 feat(mobile): the assistant is an entry of the dock, sorted like the pages
The home assistant sat behind the More button on every page outside a
trip, so a dock with free slots showed "..." for a single entry. It is now
an entry of the dock list while its addon is on and it can run for the
user: an account that never sorted its dock gets it in a slot the addons
leave free, and Settings > Appearance > Mobile can move it between the bar
and More like any page.
2026-11-13 08:27:31 +01:00
Maurice fdb2095ced fix(native): a widget tap opens its page every time, also after iOS ended the page
A widget or Live Activity tap reached the UI as Capacitor's appUrlOpen, or
as the launch URL. When iOS had ended the web view's process in the
background, the event went to the old page's listeners and was lost, and
the reloaded page found the same link in getLaunchUrl that it had already
followed once and skipped it: the second tap on the packing widget opened
the start page.

The binary now takes com.liketrek.trek://navigate links itself and hands
them over like a quick action, held until the UI takes it and kept for a
page that is still loading. It reports the feature appLinks, and the UI
only follows the link through Capacitor for binaries without it.
2026-11-13 08:27:23 +01:00
Maurice d6a6b49b6b test(planner): the #3074 case in a file of its own, and the category pick inside guessedCategory, so both files stay within their size 2026-11-12 23:18:51 +01:00
Maurice 0855bafca7 fix(search): place searches find a name typed without its accents (#3075)
"Obidos" did not find "Óbidos", nor "Sao Bras" "São Brás". The search of the
places list, a collection, the saved-place picker, the import from a trip
and the location picker now compare without case and Latin accents, with
letters such as ß, ø or ł folded to their plain spelling. Marks outside Latin
script stay, since there they change the word.
2026-11-12 23:06:36 +01:00
Maurice a39f3fe40c fix(planner): a place added from a collection keeps its category (#3074)
The place form guessed the category of a picked place from its kind, so a
place taken from a collection inside a trip lost the category it was saved
under, while adding it from the collection kept it. A picked place that
carries a category this server has now brings it along; a category chosen
in the form by hand still wins.
2026-11-12 23:06:35 +01:00
Maurice f13a0aa6ee fix(collections): pins on the map show their place's category (#3073)
The server nests a saved place's category, while the markers and the hover
card read the flat fields a trip place carries, so every pin on a
collection's map was the grey default. The map now hands its places on with
the category's colour, icon and name where the markers look for them.
2026-11-12 22:02:05 +01:00
Maurice 10d1a61614 fix(ios): a running TREK Cloud subscription is found on a fresh install too
The check before a purchase read only the entitlements on the device. Those
can be empty while the subscription runs, so the app offered the plans and
the purchase changed the running subscription's plan in the App Store. The
status of the subscription group now decides when the device has none. A
renewal the device has not finished yet no longer counts as a purchase
waiting for its instance either, which also let the check through.
2026-11-12 21:21:51 +01:00
github-actions[bot] 2bcfc1bfcb chore: bump version to 5.1.4 [skip ci] 2026-11-12 19:51:06 +00:00
Maurice b6e3c9bf39 feat(native): the app needs TREK 5.1.4 on the server
nativeApi goes to 3 with 5.1.4, whose UI follows a widget or Live Activity
tap that starts the app. The apps refuse an older server with the hint to
update, and check a server they remember once more.
2026-11-12 20:21:16 +01:00
Maurice 74d243d8e0 fix(airtrail): a new host does not inherit the stored API key
Saving the AirTrail settings with a different URL and the key field left
masked or blank kept the stored key, so the next sync sent the old
instance's key to the new host. The key now goes with the host, as it
already did for the connection test and for Dawarich: a new origin without
a new key clears it, a path edit on the same origin keeps it. The key is
cleared before the new address is written.
2026-11-12 16:58:24 +01:00
Maurice 56b9bac031 fix(native): a widget tap that starts the app opens what it points at
A tap on a widget or a Live Activity hands the app
com.liketrek.trek://navigate/<path>. A running app gets it as appUrlOpen;
an app started by the tap gets it as its launch URL, which nothing read, so
the path was lost and the app opened its start page (the packing widget
landed on the trip's plan instead of its lists). The launch link is now
followed once per launch.
2026-11-12 16:53:04 +01:00
Maurice 4983b3407c fix(planner): a flight without airports keeps its day and times on save
A flight created with a day and times but no endpoints (over MCP) showed
them in the form, but buildTransportPayload took a flight's span only from
rows with an airport, so a save sent day, end day and both times as null.
Like a train without stations, such a flight now takes its day, times,
airline, flight number and seat from the raw rows. Endpoints and legs still
come only from rows with an airport.

Fixes #3070
2026-11-12 16:49:29 +01:00
Maurice ed1728feec refactor(admin): the SSO unlink section is a component of its own
The edit dialog's component crossed the complexity limit with the section's
conditions; the section and its confirm step now live in SsoUnlinkSection,
which decides itself when it shows.
2026-11-12 16:14:34 +01:00
Maurice 11472c05ff fix(native): a failed SSO round trip from the app still hands back on the retry
A sign-in the app starts runs through the server's handoff page in the
system browser. When the SSO callback failed (an expired or overwritten
state), the login page dropped the way back to that page, so the retry from
there was a plain web login and ended in the web app inside the app's
browser sheet instead of signing in the app. The login page now keeps a way
back that points at the handoff page, and only that, and renews its
fallback copy for a slow retry. State, nonce, PKCE and the handoff verifier
are unchanged; the handoff page still asks for a tap before it passes
anything on.
2026-11-12 16:10:14 +01:00
Maurice f2ac12ca4f fix: hold the ratchets after the SSO unlink and the day pager window
- The SSO unlink answers with a response contract (adminSsoUnlinkResponseSchema).
- It reuses linkOidcIdentity with nulls instead of a new repository method,
  so UsersRepository keeps its method count and size.
- The client calls the route through apiClient, so api/client.ts does not
  grow, and its confirm dialog takes no async callback.
- The pager's preview step and pagesFor keep their complexity.
2026-11-12 15:56:59 +01:00
Maurice b53130ca0d feat(admin): remove the SSO link of an account whose provider changed its subject
A sign-in with another subject of the same provider is refused, so an
account whose provider re-issued the subject (a migrated or rebuilt IdP, a
changed sub format) was locked out: editing the email, as the wiki advised,
never touched the stored link. The edit dialog now offers "Remove SSO link"
for other accounts while SSO is set up (DELETE /api/admin/users/:id/sso,
audit admin.user_sso_unlink). The next SSO sign-in links again under the
rules of a first one, so it still needs a verified, not self-set email.
The log line names the remedy, and the wiki tells the two refusals apart.

Fixes #3065
2026-11-12 15:39:21 +01:00
Maurice a3a4f7cf79 fix(mobile): days drawn ahead of a fast swipe through the day pager
Only the selected day and two on either side were drawn, and the selection
moves only once the strip comes to rest, which iOS takes a while for. A run
of fast swipes ran past the drawn pages into blank ones until it settled.
The drawn window now also follows the day the swipe is heading for, so the
next days are there before they slide in. At most two windows of five pages
are mounted, however long the trip.

Fixes #3067
2026-11-12 15:39:20 +01:00
Maurice ac7094df05 fix(planner): a refresh of the trip no longer wipes an open transport or booking form
The transport form re-seeded itself whenever the trip's costs arrived as a
new array, and the booking form whenever days, places or stays did. A
socket reconnect re-reads the whole trip, so behind a proxy that drops idle
sockets every 15 to 30 seconds everything typed so far was wiped. Both forms
now re-seed only for what opens a different form; the data is read when the
form is seeded, as before.

Fixes #3069
2026-11-12 15:39:20 +01:00
Maurice 80130f7946 fix: the provisioning line reads as a key reference, the safe-area test follows margin-inline
lint:i18n-keys took the spread in front of t() for a member call and did not
see native.provisioning.sub; the sentence is its own constant now. The
shell chrome test asks for the logical margins and their right-to-left swap.
2026-11-12 14:52:42 +01:00
Maurice fc648bc7e4 fix: green checks after the first-screen redesign and the safe-area floor
- native-shell/amount.ts: the no-break spaces in the price pattern were
  written out as characters; \s already matches them.
- widgets.ts: the Live Activity's lead time stays inside the module.
- i18n: the count strings of the new plan tiles replace the removed ones
  on NOT_PLURAL, and the address placeholders of the non-Latin locales
  are their own instead of English copies.
- mobile.css: the side safe-area margins of the floating bars use
  margin-inline, swapped for right to left, instead of physical sides.
2026-11-12 14:36:13 +01:00
Maurice 52f0fb882f feat(native): redesign the app's first screens around a living landscape
The screens the app ships itself (welcome, plans, address, own server and
the states between) move from the dark card layout to a light design on an
animated aerial landscape drawn by a WebGL shader (native-shell/scenery.ts):
an island, a coastal town, farmland and mountains in one world.

- Welcome: the landscape with the TREK wordmark, three perks in one card,
  "Explore cloud plans" and "I already have a TREK" as two buttons.
- Plans: one tab per plan; choosing one flies the camera to its place,
  further inland with every plan. Each plan reads as a kind of trip, the
  price sits in a card with a monthly/yearly switch and counts to its new
  amount in the store's own format (native-shell/amount.ts), and a yearly
  price shows what it makes a month. Restore purchases, the renewal terms
  and the links stay as the store rules require.
- Address: the address writes itself on a glass card over the landscape;
  free, the password rules and the agreement sit on white cards.
- Own server: the server address on the glass card; a failed check turns
  the card red with what went wrong, proxy sign-in and client certificate
  panels in the same style.
- States: server not reachable, certificate needed, already subscribed,
  setting up (with its steps, then "Arrived") and the slow setup.
- iPad, an unfolded phone and a phone on its side get two columns, the
  landscape beside the content; on a dual-screen device each half takes
  one screen. Arabic and Hebrew mirror through logical sides only.
- The shader caps the pixel ratio at 2, drops to a lower resolution on a
  slow device, stops in the background and is one still frame with
  reduced motion.
- The sign-in and error page bars over a proxy's page (iOS and Android)
  take the same look: a white card and a dark button.
- Plus Jakarta Sans is bundled for the text, with Montserrat standing in
  for Cyrillic; the wordmark fonts and the old preview images are gone.
- New strings in all 28 languages; the keys of the old screens removed.
- The dev sample plans follow the cloud's plans (start, basis, plus, pro).
2026-11-12 14:18:42 +01:00
Maurice b8ec428ad1 fix(native): show the travel day's Live Activity from three hours before its first stop
The Live Activity started with the first sync of a travel day and stayed on
the lock screen all day, so a day whose first stop is an evening flight held
it from the early morning on. It now appears three hours before the day's
first timed stop and stays until the day is done; liveDayDue() decides.
2026-11-12 14:00:47 +01:00
Maurice a1d3ffc2ce feat(native): the 'server not reachable' screen names the cause, small, for a report
A load that failed sent the app to the address screen with a general message only, and a tester's screenshot told nothing. The web view's own error (net::ERR_..., an HTTP status, NSURLErrorDomain and its code, a client certificate the proxy asked for) is kept natively and shown under the message once.
2026-11-12 10:11:01 +01:00
Maurice 46b9df54bf feat(native): the TREK Cloud screens show the app and make the choice of a plan easy
The cloud screen shows real screens of the app. The plans are a choice now: one tap marks a plan, the recommended one is marked, what every plan has is said once above them, storage says how many photos it holds, and the yearly switch shows what a year saves; one button at the foot carries the chosen plan on. The claim screen names the plan with a way to change it, shows the address as it will be once it is free, ticks off the password rules while typing, says in three words where the servers are, that it can be cancelled and that there are no ads, and puts the photo behind the form away.
2026-11-12 10:04:31 +01:00
Maurice 3c0c3b3f6e fix(native): a store account with a running TREK Cloud subscription buys no second one
One subscription per Apple ID or Google account. Choosing another plan in the app changed the running subscription on the App Store, and the redeem then failed with 'We could not set up your TREK'. The app now asks the store first and shows a screen of its own: the account already has TREK Cloud, a new one can be taken out once it has ended, with Open my TREK and the store's page to manage it. The native purchase refuses as well; a paid one that was never set up is still redeemed. On the address screens the link 'What is a TREK server?' is gone, and the small print under a purchase is set in the system text face at regular weight.
2026-11-12 09:28:56 +01:00
Maurice 5395d53332 fix(native): the top bar keeps clear of a camera in the corner of the screen
The iOS app hands the page the safe area it measures, a floor under env() where WebKit reports none, and on a phone the floating bars keep clear of the side insets as well.
2026-11-12 08:42:17 +01:00
Maurice ec08032e98 fix(native): SSO in the Android app survives Android ending the app while the browser is open
A Samsung phone may end the app while the sign-in runs in the browser. The browser's return then started the app anew without the call that waited for it, the first page load failed while the network was still held back, and the user came back to 'server not reachable' and then to the sign-in page, in a loop. The return is now kept natively and redeemed by the sign-in page with the verifier kept until then, and a page that failed on the network is loaded once more before the app gives up.
2026-11-12 08:42:17 +01:00
Maurice cc5eca5553 feat(native): a client certificate can be added on the address screen before connecting
The import was only offered once the check saw the proxy ask for a certificate during the handshake. A proxy that refuses without asking left no way in. The wiki also points to the certificate's new place in Settings and explains why a certificate from an iOS configuration profile cannot be used by the app.
2026-11-12 08:42:16 +01:00
Maurice 009be05d22 fix(mobile): a sheet no longer shows the page through it while it opens
The dim faded in on the sheet's own parent, and opacity multiplies down the tree, so for its first frames even the opaque sheet let the list behind read through as ghost text (Android tester, expense editor). The dim now fades on a layer of its own beside the sheet, and the card is opaque halfway through its pop.
2026-11-12 08:42:16 +01:00
Maurice 860ffc1dee fix(planner): the drag state setters are stable, so the sidebar's dragend listener lists them and is still set up once 2026-11-12 08:42:15 +01:00
Maurice c515f95d17 fix(ci): the drag hook lists its dependencies, closeOnDone stays module-private, and the weight test waits for its save 2026-11-12 07:10:18 +00:00
Maurice 855bf8610a refactor(planner): the day plan's drag state lives in a hook of its own; the holiday provider cases get a suite of their own
DayPlanSidebar grew past its size baseline with the Firefox drag fix. Its drag state (what is dragged, the drop line, the day under the drag, whether a drag happened) and the effect that ends a lost drag move into useDayPlanDragState, typed, which also lowers the file's strict-mode count; size and strict baselines are lowered with it.

vacay.service.test.ts went past the 2000-line limit once the Nager v4 cases were back in it. They move into vacay.holiday-provider.test.ts, which takes VacayService from a Nest testing module over a snapshot db, so neither the db mock nor a hand-built service is needed.
2026-11-11 22:38:59 +00:00
Maurice 742edc4a79 fix(ci): today's changes pass the release gates
- iOS: the navigation proxy reports an error page through a callback the controller sets; it called the controller's method on itself and did not build.
- keyboard: the done key reads the field's text in the capture phase, before the field's own handler, so a list that adds an item on Enter and clears itself keeps the keyboard; the tests wire it as the app does.
- Ireland migration: the queued writes use addSql instead of awaiting inside loops; only table constants and integer ids reach the SQL.
- tests: the Dawarich GL test types its two layer specs once (strict baseline lowered), the collab test reads the id off the listed message, the reasoning test runs its four servers in parallel, and a wasm copy covers the precompressed branch without a charset.
- en wording: the deviations agreement no longer calls TREK self-hosted.
2026-11-11 22:06:50 +00:00
Maurice 1975a23aab fix(mobile): the settings group headings follow the text size setting
They used a fixed 10px size, which theme:lint refuses; they take the caption tier now.
2026-11-11 21:32:05 +00:00
Maurice 80c8d9c602 feat(mobile): settings group their sections by what they reach: your account or this device
A tester could not tell which settings follow the account to every device and which stay on the phone, and looked for offline mode for a while. The section list and dropdown now stand under two headings, "Your account" and "This device". The server the app opens, the app lock and a client certificate move out of Account into a section of their own, "App", shown only in the app, next to Offline; About stands under TREK. Labels in all locales. TestFlight feedback, 5.1.3.
2026-11-11 21:31:14 +00:00
Maurice 520562ef86 fix(planner): keep day plan drags alive in Firefox
Firefox fires dragstart while it handles the mouse move that starts the
drag and delivers that move to the page afterwards, button still held.
The listener that clears a drag whose dragend never arrived took that
move as the end of the drag and dropped the drag data at pick-up, so a
stop could only be reordered with its arrows. The same held-button moves
come from the touch polyfill on hybrid laptops throughout a drag. Only a
move with no button held now counts as the drag being over.

The day list also switched its stagger animation back on whenever a drag
ended, which replayed the entrance of every day card ("weird reload").
It now plays once on mount and stays off after the first drag.

Fixes #3041
2026-11-11 21:26:15 +00:00
Maurice 001253c39b fix(ios): the strip above the page carries the page colour on releases that lay the web view out below the status bar
On iOS 27.0.x the web view started below the status bar, and the strip above it showed the view controller's black instead of the screen background. The colour behind the web view now follows the theme the UI reports through StatusBar.setStyle (the top colour of --m-scr), so the strip reads as part of the page on every release. TestFlight feedback, 5.1.3.
2026-11-11 21:24:35 +00:00
Maurice 5345b50d17 test(vacay): the holiday provider cases live in the existing suites again
The two new test files mocked the db module and built VacayService by hand,
which the test-mocks and new-service ratchets refuse for files without a
baseline entry. The cases move back into vacay.service.test.ts and
vacay.test.ts unchanged; VACAY-037 asks for a year no other case caches. The
unrelated admin addon test change is reverted and two comments name the new
provider.
2026-11-11 21:06:29 +00:00
Luffy a3622e91b6 feat(vacay): implement Nager.Holidays v4 integration and adapt holiday data structure 2026-11-11 21:04:07 +00:00
Sebastian Fischer ec78cfbdd0 feat(tours): find bakeries and groceries on the map and along a hike
A multi-day hike restocks on the way: bread in the morning, food for
lunch and supper every day or two. Neither could be searched for.

- Explore pill: new `bakery` and `supermarket` chips. `supermarket`
  already existed on the server (shop=supermarket|convenience) but no
  pill offered it; `bakery` is new (shop=bakery).
- Along the tour: `bakery` and `supermarket` are offered to a hike,
  after its existing kinds so a new hike still starts on the same three.
- TREK places index: `bakery` maps to food_and_drink>casual_eatery>bakery;
  `supermarket` is narrowed to grocery and convenience stores, since the
  whole food_and_beverage_store branch also brought tobacconists and
  wine shops. Both paths checked against a live answer of the index.
  As the more specific term, a bakery is no longer listed as a place to
  eat when only `food` is asked along a tour.
- Category guess: a bakery or grocer picked from the map was preselected
  as Bar/Cafe or Shopping even where the admin had made a Bakery or
  Groceries category. The guess now looks for such a category first, by
  name in the usual languages or by icon, and keeps the broad one when
  there is none. The category icon picker offers Croissant and
  ShoppingBasket, the icons the map chips use.
- Labels in every locale; MCP tool description names the new kinds.
2026-11-11 20:59:59 +00:00
John Jahn 04b1fb72bb perf(static): serve the hashed assets from Brotli copies written at build time
Every file under assets/ was compressed again on each download, at the low
level a request path can afford. They carry their content hash in the name
and never change, so the client build now writes a Brotli copy beside each
compressible one (postbuild, scripts/precompress.mjs) and production points
a browser that accepts Brotli at that copy before express.static serves it.

Measured on a production build of 5.1.2, fetching the 652 precached files
the way a service worker install does: 16.3 MB -> 13.9 MB on the wire and
about 1.0 s -> 0.2 s of server CPU per install. A client without Brotli, a
range request and a build without copies are served exactly as before.
2026-11-11 20:59:40 +00:00
John Jahn 848e3f90e1 perf(client): the icons the first load needs ship as one chunk instead of 187
Every lucide icon is its own module, and the plugin icon lookup reaches all
of them by name from the navigation. The bundler therefore put each icon
another chunk also uses into a chunk of its own: 187 files of a few hundred
bytes, each one a request on a first visit and an entry in the precache.

A `vendor-icons` group collects them. The first load asks for 32 script
files instead of 224 (the bytes stay the same, 3.9 MB before compression),
and the service worker installs 460 files instead of 652. With 50 ms added
to every response the login page reaches DOMContentLoaded in 0.5 s
instead of 3.8 s.
2026-11-11 20:59:27 +00:00
Maurice 102e2141ec fix(mobile): screens with pull-to-refresh bounce like every other iOS list
The dashboard, journeys and notifications switched the iOS bounce off entirely so it would not race the pull. The pull now holds only a clearly downward move from the very top, from its first pixel, and the page bounces everywhere else, its end included. TestFlight feedback, 5.1.3.
2026-11-11 20:41:25 +00:00