Commit Graph

4945 Commits

Author SHA1 Message Date
Maurice bb725ac8d8 fix(native): the first screens on an upright iPad read as one page, not a phone column
On an iPad upright or an unfolded foldable the welcome, address, TREK Cloud
and plan screens kept the phone's narrow column in the middle of the glass,
with the photo fading out across the top left. Now the photo spans the
width as a band across the top, the choices stand under it in a wider
column, and the plans take two columns. Landscape keeps the photo as a
panel beside the choices; phones are unchanged.
2026-11-07 23:25:14 +01:00
Maurice b644235eb6 fix(mobile): level card columns, the day framed beside the plan, the reader's place kept across a fold
From a screen by screen pass over the iPad and foldable sizes:

- Card lists (transports, bookings, tasks, polls) flow down their columns
  instead of filling rows, so the columns start level and a short card no
  longer ends above its neighbour in a ragged row. The cards' own top
  margin gives way to an even gap under each, so every column starts on
  one line.
- A booking's or transport's title may take two lines in a column of a wide
  window instead of being cut after a few words.
- The map beside the plan opened too close: the opening frame was worked
  out for the window, not the narrower map, and Leaflet rounded its zoom up
  to the next whole level, so the day ran off the top and bottom. The map
  now opens at the whole level that fits and, once it knows its own size,
  works the frame out again for it (OpeningFrameFit). On a wide window the
  touch shell frames a day clear of its floating toolbar and corner
  buttons.
- Costs and to-dos in two panes are one element tree with the stacked
  arrangement, so a rotation or a fold between them keeps what is typed or
  open in either pane, and the row at the top of the list stays at the top.
- The place sheet's category pill has room for its name on a wide window.
2026-11-07 23:25:13 +01:00
Maurice d259ecb11c fix(ios): widgets hold at an iPad mini's sizes, the extra large ones balanced
From the iPad renders of the widget previews:

- An iPad mini's small and medium widgets are smaller than an iPhone's. The
  trip widget during the trip lets the weather give way in the small one and
  narrows its stops panel in the medium one, the today widget's weather
  shrinks to its chip and then steps aside before the trip's name is cut,
  the atlas puts "countries" under the number in the small one and lets
  its map shrink in the large one, so the continents and latest countries
  are no longer cut off at the foot.
- The extra large widgets fill their width and height: the trip during the
  trip sets the next stop at the foot of the left column and the next
  booking under the day's stops, today sets the next two bookings across
  the foot, the bookings list spaces its rows beside the card.
- The Live Activity's next stop shrinks a little before it is cut.
2026-11-07 23:25:13 +01:00
Maurice 5d05363f36 feat(ios): the iPad gets the extra large widgets, no Wallet button, a centred share sheet
The app is one binary for iPhone and iPad, so what was built for the
iPhone has to hold on an iPad too:

- Widgets: the trip, today and bookings widgets offer the iPad's extra large
  size. The trip sets its countdown or its day beside the bookings or the
  day's stops, today sets the day, its progress and weather beside the
  timeline, bookings sets the next one as a card beside the rest. The other
  widgets stay at the sizes they were designed for.
- The widget renderer draws every home-screen size of each widget as a 13"
  iPad, an 11" iPad and an iPad mini give it (a mini's small widget is
  smaller than an iPhone's), an iPad lock screen with the accessories and
  the travel day's Live Activity, and that Live Activity at the iPhone's and
  the iPad's widths. To draw it there, its lock-screen banner takes plain
  values (TripDayBanner, now beside the today widget).
- Apple Wallet: an iPad cannot take a pass. The shell reports the wallet
  feature only where PKPassLibrary and PKAddPassesViewController can add
  one, so the UI shows no "Add to Apple Wallet" button there and hands a
  .pkpass to the share sheet like any other file.
- Share extension: on an iPad it is presented over the full screen instead
  of in a form sheet, and in a window of regular width its card stands
  centred over the dimmed app, without the grabber, instead of rising from
  the bottom edge. A narrow Split View window keeps the iPhone's card.
2026-11-07 23:25:13 +01:00
Maurice 6906f80ff8 feat(native): Android tablets get the shortcuts, two panes meet at a foldable's fold
The Android shell now reports keyCommands and windowLayout, so the large
screen layout no longer assumes an iPad:

- The keyboard host hands the shortcuts to the app whenever the shell has
  the keyCommands feature, whatever the platform. Before it also required
  iOS, so an Android tablet with a keyboard answered the keys in the page
  and never listed them in the system's shortcuts.
- useWindowFold follows the layout the Android shell reports (feature
  windowLayout). When a vertical fold separates the window, a foldable half
  open like a book, MobileShell marks it with data-fold and its place, and
  the leading pane (the trip's plan, a tab's summary, a list beside its
  detail) ends at the fold while the trailing one starts there, on either
  reading direction. A flat fold that does not separate stays one screen.
- The split size class starts at 840pt, Android's expanded width, instead
  of 900pt, so a foldable unfolded on its side (about 841dp) gets two panes
  like an iPad does. An 11" iPad upright (834pt) stays a single pane. The
  address screen of the native shell follows the same width.
- TrekShell exports NativeWindowLayout for it.

Also: the sheet's width and anchor types and the collab board type stay
private to their modules, which nothing else imports (knip).
2026-11-07 23:25:13 +01:00
Maurice 7083667cbf fix(mobile): grid cards keep their height, a previewed photo its proportions
Cards in the large-screen grid keep their own height instead of stretching
to a taller neighbour in their row, which drew a short booking as an empty
panel. The file preview beside the grid shows a photo at its own
proportions rather than letterboxed in a portrait frame.

On a 13" iPad on its side the cloud plans of the native shell stand two by
two in their half of the screen, so the whole offer is in view at once.
2026-11-07 23:25:12 +01:00
Maurice 0b7cee0b0d feat(mobile): the trip's tabs, administration, collections and the journal use the large screen
On an iPad or an unfolded foldable the list screens kept one phone-wide
column in the middle of the page. They now lay out for the room they have:

- Transports, bookings, polls and tasks put their cards in a grid of as many
  phone-wide columns as the page holds (CardGrid), three on a 13" iPad on
  its side, two upright, one in a narrow Split View.
- Costs: with room for two panes (split size class) the summary (total,
  owe and owed, settle up, final budget, categories) stands in a pane of its
  own beside the expenses, each scrolling on its own (TabPanes). The search
  field sits in the expenses' column again instead of running past it.
- Files: a grid of tiles with each photo's picture; with room for two panes
  the picked file is previewed beside the grid with what it belongs to and
  its actions, the way Files on an iPad does. The signed preview address
  comes through fileRepo.previewUrl.
- Packing: the categories stand in columns, each card whole in its column;
  the action menu drops from its button at a popover's width.
- To-dos: the four smart filters as tiles and the trip's lists in a pane
  beside the tasks, which take the filter's name as their head.
- Collab: the chat stays in view at the trailing edge beside the notes,
  links or polls, and the header switches only the board. The chat keeps its
  place in the tree, so a fold or a rotation into two panes keeps the draft;
  a chat at its newest message stays there when its box changes size. Notes
  stand as a board in columns.
- Administration and settings share a section list beside the page
  (MSectionNav); the stats grid takes four columns.
- Collections: the lists stand in a column beside the places, which take two
  columns, and the head names the list on show.
- Journal: the entries become a feed beside the map, under their days, with
  photo, place and the start of the story, in step with the map like the
  strip of cards it replaces; it fades out under the header.
- The assistant opens as a tall panel at the trailing edge over a dimmed
  page, as Slide Over would, instead of covering the whole screen.
- The rail fits every name: a word too wide for it shrinks the label before
  it is hyphenated, and the admin entry uses its short name.
- The journeys and notifications headers keep to their column.
- The address screen of the native shell sets the photo as a panel beside
  the choices on a large screen on its side.

On a foldable that reports its fold, a leading pane ends at the fold and a
trailing one starts there. Phones keep their layout.
2026-11-07 23:25:12 +01:00
Maurice 36ed0e0d23 fix(mobile): the map frames the day beside the pane, atlas search as a centred panel
With the plan beside the map, the map frames the day once the splash has
gone, and again once its box has settled after the pane opens or hides, a
rotation or a fold: a frame fitted to the old box left the day half outside
the new one. On a wide window the atlas country search stands as a centred
panel instead of a band across the screen.
2026-11-07 23:25:12 +01:00
Maurice b1c0353548 feat(native): iPad shortcuts in the app menu, pointer input on iPad
The UI's keyboard shortcuts for the screen on show become native key
commands (TrekShell.setKeyCommands, feature keyCommands): they appear in the
overlay a held Command key brings up and in the iPadOS menu bar, named in the
UI's language, and a press is reported back to the page ("keyCommand").
Each screen replaces the list, so only what works there is ever offered.

UIApplicationSupportsIndirectInputEvents turns on the iPad's pointer input
for trackpads and mice.
2026-11-07 23:25:12 +01:00
Maurice c204a25a9c feat(mobile): the touch shell laid out for iPad and unfolded foldables
The app shows the touch shell at every size now, an iPad's and an unfolded
foldable's included, instead of switching to the desktop pages from 768px.
One route tree survives rotation, Split View, Stage Manager and folding, and
the app's own features (Wallet, sharing into a trip, the edge swipe back)
are there on the big screen too.

Two size classes drive the layout (hooks/sizeClass.ts, Tailwind variants
wide: and split:):

- wide (600pt and taller than a phone on its side): a glass navigation rail
  on the leading edge replaces the dock and holds every destination with its
  name; sheets become centred form sheets; the trip's More menu is a popover
  beside the rail; lists keep a readable measure; dashboard, journey and
  vacay get columns; full-bleed maps run under the floating rail.
- split (900pt): the trip's plan, places or road trip chain sit in a pane
  beside the map, which follows the plan's day; the list/map switch hides
  and shows the pane like a sidebar; on a foldable that reports its fold the
  pane ends at the fold. Settings show their sections beside the page and the
  dashboard keeps its widgets in a column of their own.

A hardware keyboard gets the shortcuts an iPad app has (⌘1 to ⌘9 for the
sections, ⌘N, ⌘, , ⌥←/⌥→ through the days, ⌃⌘S for the pane, ⌘[ back),
answered by the page or, with a binary that has the keyCommands feature,
handed to it as native key commands.

The day strip fades at an edge that runs on, journey cards grow on a wide
window and a card without a photo takes the day's colour, and the Leaflet
map measures itself again when its box changes size.
2026-11-07 23:25:12 +01:00
Maurice b008e010a5 fix(native): sort the offline manifest with localeCompare, so Sonar's reliability gate holds 2026-11-07 22:03:20 +01:00
Maurice 02b0454b3d fix(native): watch complications stay current, times hold on clock changes, and a review's findings
The complication timeline has entries for every planned day in its window
and rebuilds at midnight; its countdowns count by themselves. Times of
day are wall-clock hours and minutes, never minutes added to midnight,
and every formatter follows the zone the watch is in. A ride ends in its
arrival's zone, which the snapshot now carries. Bookings without a time
are all-day and get no countdown, a day of untimed places shows its
first place instead of "day done", idle complications say what fits the
trip's phase. The watch finishes WatchConnectivity deliveries in a
background task, sends a tick to the phone only until it is handed over,
and keeps nothing in UserDefaults, so its bundles use no API that needs a
privacy declaration.
2026-11-07 18:13:17 +01:00
Maurice 9c1e63e64b fix(native): the watch bridge stays silent on an iPad
The "watch" feature is offered only where WatchConnectivity is
supported, so the UI never builds a watch snapshot on an iPad, and the
phone's link neither activates a session nor stores or sends anything
there.
2026-11-07 17:18:50 +01:00
Maurice dacc413c34 fix(native): a list's progress bar heads its first section, so two items show on the small watch 2026-11-07 17:14:12 +01:00
Maurice b8f6a99895 fix(native): long single-word menu rows shrink further instead of cutting; lists close up their sections 2026-11-07 17:14:12 +01:00
Maurice db82e04b49 fix(native): compact list progress, one-line menu rows, a booking header that wraps instead of cutting
The packing list and to-dos open with a slim progress bar and their count,
the list right under it. The trip's menu rows stay on one line and shrink
a little before anything wraps. A booking's kind and status share a line
when they fit and stack when they do not; its countdown drops the day
word before it moves under the time. The place map fades its top edge,
where labels were cut, and leaves Apple's attribution as MapKit draws it.
The complication gallery has no English section names.
2026-11-07 17:14:11 +01:00
Maurice 3abc9eacf4 fix(native): no cut words on the watch, in any language or case size
The booking's countdown moves under its time and its reference and seat
under each other when a row is too short, menu rows and the countdown
title take a second line, a list's count opens the list instead of
crowding its title, text people wrote in Latin letters breaks between
words in a Chinese, Japanese or Korean interface, and the day opens at
its top with nothing behind its title. A trip's pages take one of a set
of clean colours by its id, the same in every language; the capture
switches the simulators to a 24 hour clock after English.
2026-11-07 17:14:11 +01:00
Maurice 3a0edafdf0 fix(native): the watch's booking and today cards fit the 40 mm case in every language
The booking's kind and status share one line and its day steps aside when
the countdown needs the room; the next stop's name is a size smaller on
the small cases; eyebrows shrink a little before they cut; the day header
points forward in right-to-left languages; the sample trip keeps the
watch's own 12 or 24 hour clock.
2026-11-07 17:14:11 +01:00
Maurice 35d2b19f4c ci(native): pass the screenshot clock on the Mac's bash 3 2026-11-07 17:14:11 +01:00
Maurice 6b5b2f614d fix(native): watch screens fit every size and language
Countdowns use the system's short relative style in every language
("через 2 ч", "in 5 Min."), and durations take the watch's language;
a stop on now shows its range, not a countdown. The top-level pages drop
the titles that collided with the scroll indicator, the pages keep the
standard margins, the day header carries a chevron instead of a crowded
icon, the booking page fits on one screen with its reference and seat,
the day timeline opens at the stop that is on, the map is drawn from
above without labels, the bookings list has the trip's light, and the
complication preview adapts to the case size. The sample trip speaks
German and Russian in those languages.
2026-11-07 17:14:10 +01:00
Maurice 92f10531d0 ci(native): capture the watch screens one watch at a time, retried, and complete
Each launch is terminated first, retried after 2, 5 and 10 seconds when
the simulator refuses it, and captured once the app reports the screen
drawn instead of after a fixed wait. The job fails when any expected
image is missing.
2026-11-07 17:14:10 +01:00
Maurice d5ba4439f2 feat(native): a calmer watch map, whole street names, a today page that ends clean
The place map is flat and muted without points of interest, with the
stop's own pin; the day timeline gives a street two lines; the today page
puts what follows into the card instead of a list cut off at the fold, and
its header opens the day. The screenshot mode takes the real clock, moved
to the hour a screen needs through the app's time zone, so the clock in
the corner agrees; the capture covers three watch sizes, three languages
and every state.
2026-11-07 17:14:10 +01:00
Maurice 78a28e2747 ci(native): capture the watch screens on simulators the runtime supports 2026-11-07 17:14:10 +01:00
Maurice 71e3ddabed ci(native): install the packages before building the watch app on its own 2026-11-07 17:14:09 +01:00
Maurice e59af6dcb3 feat(native): TREK on Apple Watch, with complications
A watchOS app embedded in the iPhone app: today's next stop with its
countdown, the next booking with reference, seat and platform, the trip
with its days and places (Directions into Apple Maps), and the packing
list and to-dos to tick off. The phone sends the snapshot through
WatchConnectivity; ticks go back as queued user info. Three
complications (next stop, countdown, trip day) in every accessory family,
scored for the Smart Stack. 28 languages.

The native workflow captures every screen from watchOS simulators in a
screenshot mode, on the largest and the smallest watch.
2026-11-07 17:14:09 +01:00
Maurice 9755330113 feat(native): build the Apple Watch snapshot and take back what the watch ticks off
The hero trip goes to the watch in full: every day with its stops and
coordinates, the bookings with reference, seat, platform and both ends,
the packing list and to-dos with ids. Ticks from the watch come back
through TrekShell.takeWatchActions and are written through the trip
store's toggles, so they take the repos and the mutation queue like a tap
in the app.
2026-11-07 17:14:09 +01:00
Maurice 5ca44453ae fix(ci): the Android merge passes the client gates
- wallet.ts: the Google Wallet state is read through a function after it was
  set, so TypeScript no longer narrows it to its old value (TS2367)
- iap.ts: a store listener that cannot be added is reported through
  reportNativeFailure instead of an empty catch
- iap.test.ts: the restored purchase is checked instead of asserted non-null
- trekShell.ts: the fold's rectangle is typed as a DOMRect pick, geometry the
  RTL lint does not read as text sides
2026-11-07 17:13:47 +01:00
Maurice 4461899910 docs(android): each tile store comment sits over the code it describes 2026-11-07 16:49:07 +01:00
Maurice ec22642f38 fix(android): review findings on the tile store, widgets, location, billing and the share sheet
- Tile store: the WebView's headers go along (a URL-restricted Mapbox token
  is checked against the Referer), and anything but a tile is left to the
  WebView, so a 401/403 never comes from the store.
- Widgets read their snapshot inside the composition, so every write reaches
  a running Glance session; the trip picker updates only the widget the id
  belongs to.
- Location: Approximate counts as allowed; the stream pauses with the app,
  resumes with it, and stops when the page reloads or the activity goes.
- Billing: an already-owned subscription comes back as the purchase, so the
  shell redeems it; a payment that clears outside the sheet is announced
  (storeTransaction) and found again at start (storeUnfinished).
- getWindowLayout answers with the last posture and hinge, not the bare window.
- Share sheet: keeps the picked trip through rotation and folding, and takes
  content:// only, never a file:// path read with TREK's own rights.
2026-11-07 14:57:50 +01:00
Maurice 46f4cab1b7 fix(native): location at the accuracy asked for, and a Play payment that clears later is redeemed
- The location shim runs at high accuracy only while a watcher wants it and
  drops back once that watcher is gone, instead of keeping the 2 s GPS rate
  for the rest of the session.
- The purchase client can hand over a Play subscription that was paid but
  never delivered (a pending payment that cleared later), and the plans screen
  redeems it at once, as a restore would, before Google Play refunds it after
  three days.
2026-11-07 14:57:50 +01:00
Maurice c1c71a12cf fix(native): show Add to Google Wallet only where the server can make the pass
The Android app asks the server once whether Google Wallet works there and
offers the button only after it says yes; a 404 from the pass route takes the
button away again. The wallet button's test mocks the new exports and covers
the Google case.
2026-11-07 14:52:09 +01:00
Maurice 99a8701bd4 fix(wallet): offer Google Wallet only where it works, and never post it to the Apple route
- GET /api/trips/:tripId/reservations/google-wallet answers whether this
  server can make Google Wallet passes: passes on, and the TREK services'
  signer with its Google route mounted (it is once liketrek has a Wallet
  issuer). The probe is kept for ten minutes.
- The Google route is derived from TREK_WALLET_SIGNER_URL by its path, the
  query kept; a signer URL that does not end in /pass has none, and the
  booking is refused with a 404 instead of going to the Apple signer.
- End-to-end tests for the save link: owner, stranger, another trip, no
  issuer yet, a link that is not Google Wallet, and the availability.
- Italian and Hungarian wording of the button.
2026-11-07 14:50:17 +01:00
Maurice 924836be31 fix(android): the brand strings are not translated, and stopping location needs no permission
Android lint counts every string without a translation once the app has
languages; the app name and the URL scheme are names, not text. Removing
location updates is guarded for a permission revoked meanwhile.
2026-11-07 14:27:57 +01:00
Maurice da05ba1121 refactor(native): keep the Android bridge additions apart from the iOS ones in trekShell.ts
The new methods sit at the end of the interface and the restore's Android
answer has a type of its own, so the watch and large-screen branches, which
add their methods after storeRestore, merge without a conflict.
2026-11-07 14:20:33 +01:00
Maurice 1e4f1922e8 feat(android): bring the Android app level with the iOS app
Everything the iOS shell gained, built natively for Android 16 with Material
You, and back to Android 7:

- Home-screen widgets in Jetpack Glance: trip, today, bookings, to-dos,
  packing, world map and travel group, from the same snapshot the iOS widgets
  read. Wallpaper colours on Android 12+, resizable in three layouts, a trip
  picker per widget (reconfigurable), live previews in Android 15's picker
  and preview images before it.
- The travel day as a Live Update: a promoted ongoing notification with the
  next stop as the status-bar chip, a countdown and the day's stops as
  segments (ProgressStyle), a quiet ongoing notification before Android 16.
- A share sheet of its own: a Material bottom sheet with the trip picker for
  files, mails, pages and links shared from any app, and the coming trips as
  launcher and direct-share shortcuts.
- Push: channels per kind of notification (chat, trip updates, invitations,
  general) by the event the server now names, stacked per channel.
- Location for navigator.geolocation from the fused provider, a store for
  Mapbox's map resources (30 days, 250 MB), Google Play Billing behind the
  StoreKit methods, and Google Wallet save links.
- Large screens: every orientation and window size without a restart,
  the window's size classes and a foldable's posture and hinge for the UI
  (windowLayout), the UI's keyboard shortcuts as the app's (keyCommands),
  predictive back, per-app language.
- The native strings in the 28 languages of the iOS app, generated from its
  strings files (scripts/generate-android-strings.mjs).

Debug builds carry a widget gallery that draws every widget at every size.
2026-11-07 14:16:04 +01:00
Maurice 3beb5c4223 feat(native): the Android app's widgets, location, tile store, Play Billing and Google Wallet
The UI now turns these on by feature on Android as well as iOS:

- widgets, widgetTrips and the travel day (liveActivity) write the same
  snapshot to the Android app's Glance widgets; the travel day also sends
  the next stop's minutes and the stops' colours for the Live Update.
- geolocation answers navigator.geolocation from the app's location stream.
- tileCache: Android keeps Mapbox's resources in its WebView client, so the
  map is offline-capable there too, without the iOS trek-tile:// rewrite.
- playBilling: the purchase client buys through Google Play on Android,
  with the purchase token as the receipt the control plane verifies.
- googleWallet: a booking's button adds it to Google Wallet on Android,
  through the save link the server asks the TREK services for.
2026-11-07 14:11:59 +01:00
Maurice d41a4f0827 feat(server): name the event in a push and offer a booking as a Google Wallet pass
The push payload carries the event it reports, so the Android app can file a
notification under a channel of its own (chat, trip updates, invitations).
A browser and the iOS extension ignore the field.

GET /api/trips/:tripId/reservations/:id/wallet-pass/google sends the same
request the Apple Wallet signer takes to the TREK services' Google route and
answers with the signed save-to-wallet link the Android app opens. Off with the
other TREK services, and a clear 404 while liketrek has no Wallet issuer yet.
2026-11-07 14:11:58 +01:00
Maurice 41c713fd9a fix(ci): WalletPassField is used only in its own file, so it is not exported 2026-11-07 12:29:00 +01:00
Maurice e1a08e82b9 fix(native): name our terms before the purchase and speak Google Play on Android
- The claim screen says, right above the buy button, that the TREK Cloud
  terms govern the instance and links them (liketrek.com/terms?app=1, the
  view without the site's order buttons); the plan screen links them too,
  next to Apple's EULA, which is now named as Apple's.
- On Android the payment note and the empty restore say Google account and
  Google Play's own path, not Apple ID and App Store.
- The redeem sends the app's language, so the contract confirmation mail
  arrives in it.
2026-11-07 12:22:34 +01:00
Maurice 0557c7290b test: the server section links the app's view of the privacy policy 2026-11-07 12:16:00 +01:00
Maurice a2aeeb32c8 fix(ci): keep the reservations repository at its size baseline
The wallet pass commit had reformatted the whole repository file, which put
it 200 lines over its size baseline. It is back to its old form, in the format
baseline again, and the pass-context query lives in a helper of its own.
2026-11-07 12:07:44 +01:00
Maurice 640998a4ef fix(native): the privacy manifest declares trip expenses as other financial info
Budget keeps expenses, splits and who owes whom; declared for app functionality,
linked, never for tracking, as in the App Privacy answers.
2026-11-07 11:55:19 +01:00
Maurice ac7dde7d93 fix(native): the privacy manifest names the user id, the push token and messages too
It now matches the App Privacy answers in App Store Connect: user id, device id
(the push token for the relay) and emails or text messages (group chat, imported
booking mails), next to email, name, purchases, photos, other user content and
precise location; all for app functionality, linked, never for tracking.
2026-11-07 11:54:31 +01:00
Maurice 6e1c9e8e9f fix(native): App Review findings on buying TREK Cloud in the app
- Orders closed at the cloud: the app shows the plans and that TREK Cloud
  opens soon, and sells only in a test build (TestFlight, App Review), whose
  sandbox purchases the cloud still sets up; a store purchase it would refuse
  is never offered. The plans arrive without prices then, the store's own are
  shown where it gives them.
- A purchase paid but never set up (Ask to Buy approved later, the app closed
  mid-purchase) is picked up by Restore purchases and goes on to the claim with
  that purchase, instead of failing; nothing is bought twice.
- The buy button names the period with the price (guideline 3.1.2).
- The privacy link opens the policy without the site's sales header.
- The privacy manifest declares what TREK Cloud keeps: email, name, the store
  purchase, photos, other user content and saved places, all for the app to
  work, linked to the account, never for tracking.
- The email hint promises messages about the contract, not receipts, which the
  store sends.
2026-11-07 11:48:03 +01:00
Maurice 354e30a075 fix(ci): wallet passes pass the server gates
- TREK_WALLET_PASS_URL read as a secret to the env check (the word PASS); it
  is TREK_WALLET_SIGNER_URL now, everywhere it is documented
- findPassContext compared the TEXT accommodation_id with an integer id, which
  Postgres refuses; it compares them as numbers now, as both databases accept
- the wallet-pass service test takes its subject from createTestModule instead
  of building it by hand, and no longer names the DOM-only BodyInit type
- Reservations.repository.ts is formatted, so it leaves the format baseline
- the wallet button test reads the translated texts the render helper gives
2026-11-07 11:45:00 +01:00
Maurice 37a4e84360 feat: Apple Wallet passes for bookings
"Add to Apple Wallet" on a booking in the iOS app turns it into a TREK pass:
a boarding pass from and to for flights, trains, buses and boats, check-in and
check-out for a stay, pickup and drop-off for a car, the time for anything
else, with the confirmation code as a QR code. Wallet brings it up on the lock
screen around its time and place: times carry the offset of the endpoint's zone
(looked up from the coordinates where the endpoint has none), stays and
restaurants their place's coordinates. The labels come from the booking forms'
own strings, so nothing new to translate on the pass.

Only liketrek holds the Pass Type ID certificate, so the server sends the pass's
texts to the TREK services' signer (TREK_WALLET_PASS_URL, push.liketrek.com)
once per tap; it stores nothing, and TREK_SERVICES=off turns it off with the
other services. Texts are cleaned of links and shortened here, the answer must
be a .pkpass within 512 KB, and a user gets 20 passes a minute.

- New wallet-pass domain: GET /api/trips/:tripId/reservations/:id/wallet-pass
  behind the trip guard, the booking checked to belong to the trip; trip title
  and place come from the reservations domain (findPassContext)
- MWalletButton in the transport and booking sheets, only where the app binary
  has the wallet feature; undated bookings and failures are told apart
- TREK_WALLET_PASS_URL in .env.example, compose, Helm and the wiki
2026-11-07 11:25:19 +01:00
Maurice 794da3be72 feat(native): booking passes go straight into Apple Wallet
Opening a .pkpass in the iOS app brought up the generic share sheet. It now
shows Wallet's own sheet with the pass and "Add", and a pass that is already in
Wallet opens there instead of being offered twice (TrekShell.addToWallet,
feature wallet). A broken pass or a device without Wallet falls back to the
share sheet as before.

Passes carry an "Apple Wallet" badge in the trip's files and a wallet icon
where they hang on a booking, so it is clear what a tap does.
2026-11-07 11:25:18 +01:00
Maurice 7b8bd4b166 feat(native): TREK in the share sheet, with the trip picked right there
A share extension puts TREK in the share sheet of Mail, Safari, Files, Wallet
and any app that shares a document, a link, text or a photo. It shows what came
in (a Wallet pass, a PDF, a mail, the page as Safari rendered it, text, a photo)
and the user's trips with their covers, the next one already picked, and hands
both to the app, which opens that trip's booking import with the file in place.
"Choose later" leaves the trip question to the app's import sheet as before.

- ShareExtension target (add-extension.rb learns "share": JavaScript and the
  localizations go in as resources), bundle com.liketrek.trek.share, signed in
  the release lane, its plists linted in native.yml
- Safari hands over the rendered page (SharePage.js), so a confirmation behind
  a sign-in arrives whole; other apps' links are fetched
- The item travels through the shared keychain group the widgets already use;
  the app takes it on com.liketrek.trek://share or when it next becomes active
- Feature shareSheet; strings in the app's 28 languages
2026-11-07 11:25:18 +01:00
Maurice 792e55b2ca style(native): format the in-app purchase files and strings 2026-11-07 11:25:17 +01:00
Maurice 5462bce9c3 feat(native): TREK Cloud subscriptions through the App Store
The plan picker sent buyers to the website, which the App Store does not allow.
It now sells through StoreKit 2 in the TrekShell plugin (feature storeKit):
App Store price strings in the buyer's currency, the purchase sheet, the signed
transaction redeemed at the control plane, and the transaction finished only
once the instance exists. A purchase whose redeem failed is redeemed again on
retry, never bought twice.

Restore purchases, the auto-renewal terms and the links to the terms of use and
the privacy policy sit under the plans. Where no store purchase is possible
(Android, an older binary) the picker shows the plans with no way to buy and no
pointer to the web.
2026-11-07 11:25:17 +01:00