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.
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.
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.
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.
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.
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.
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.
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.
- 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
- 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.
- 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.
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.
- 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.
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.
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.
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.
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.
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.
- 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.
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.
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.
- 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.
- 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
"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
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.
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
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.
vi.mock is hoisted above the file's imports, so the managed-tab mock could not
use the icons and JSX imported at the top (ReferenceError before initialization).
The trip PDF's print view linked Poppins from fonts.googleapis.com, so printing
a trip sent the reader's IP address to Google, the case German courts treated as
a GDPR breach (LG München I, 3 O 17493/20). It now declares the Poppins files
TREK already ships under /fonts, and the content security policy no longer
allows fonts.googleapis.com or fonts.gstatic.com, so no font can be fetched
from Google again by accident.
The mobile admin never read managedAdminTabs, so a tab an install attaches
there (managed/index.tsx) existed on the desktop only. The phone's section
picker now lists them after the built-in sections, as the desktop sidebar does,
and shows `mobileElement` when a tab brings a phone layout of its own, else its
desktop `element`. TREK itself attaches nothing; the seam stays empty.
MDemoWelcome used raw rem sizes, which ignore the text size setting and broke
theme:lint, and was missing from feature-pairs.json, which broke lint:pairs.
components/ may not import from mobile/ (lint:layers). The phone dashboard
mounts the sheet itself, as it already mounted the welcome, and the desktop
dialog no longer asks which shell it is in.
On a phone the head took half the screen, the list scrolled in a small window
and the start button broke over two lines next to the links. The phone now gets
a bottom sheet of the phone shell: a small head with the countdown, the same
content as a list, a full-width start button with the two links under it. The
photo credits open in its place and back, as on the desktop.
An object literal with airplaneSeat inside the legs() call failed the excess
property check of tsconfig.tests.json; the nodes are built as constants first,
as the other seat tests do.
The grouped flight wrote a seat only on the booking itself, from the first
segment. Each leg in metadata.legs now carries the seat of its own segment,
which the planner and the PDF already show per leg.
KItinerary writes a flight seat as airplaneSeat and a ticket seat as
reservedTicket.ticketedSeat.seatNumber, the schema.org fields, and it
removes seat and seatNumber from a script result. segmentDetail read only
seat and seatNumber, so an imported booking lost its seat.
segmentDetail now also reads airplaneSeat, and then the ticket seat when
no other key has a value. seat and seatNumber stay first, so a booking
that already has a seat gets the same result.