The card gets a header with its icon in a round badge, its section labels use
the eyebrow of the form's own fields below it, and the description sits on
the mobile field surface with a pill button to take its text over.
Add place on the phone: the place details showed the mascot half a screen
high while nothing was picked or nothing was found, and pushed the name field
below the fold. Both states are now one quiet line, as in the desktop dialog,
and the details sit in a card in the mobile tokens.
Sheets with a header over a scroll box (trip settings, day, export,
transport, files and more) cut their content off in a hard line right under
the header icon. The scroll box now keeps a little room under the header and
fades its content out at the edges.
Copy and share next to a share or invite link are round icon buttons of the
same size; the share button showed no icon because the copy button's padding
left it no room.
The previous commit carried, by mistake, shorter page transitions and a
route preloader that was never wired up. Both go back to how they were;
the warm-up and day rail fixes stay.
The iOS app pulls every file of a build into WebKit's cache so it can start
offline. It did so four at a time at normal priority, for all 1,900 files of
every new build including all 27 translations, and a page opened meanwhile
queued behind it: right after a server update, a first visit to Vacay took
five to seven seconds. The warm-up now runs two at a time at low priority,
takes only the user's language and English, and walks a build once.
The day rail in the trip header lit the new day only after the pager's snap
had landed and the plan had re-rendered, a second or two after the swipe.
The pager now reports the day a swipe heads for as soon as its page is the
nearer one, and the rail and its pill move with the finger; the plan still
switches when the snap lands.
The journal list (its own scroll box, so the hook now takes an optional
scroller) and the notifications screen reload by pulling, like the
dashboard. The list stays on screen while it reloads.
Page transitions: on the phone, pages move like a native navigation stack,
built on the View Transitions API (mobile/routeTransition.ts). A page opened
from another slides in from the trailing edge, back slides it away, and
switching between the dock's sections cross-fades while the dock stands
still. Lazy routes do not flash their skeleton: the swap waits, behind the
frozen snapshot, until the new page rendered. The edge-swipe back gesture
already moves the page and skips the transition. RTL mirrors the motion,
reduced motion and desktop get none.
Pull-to-refresh on the mobile dashboard: the page follows the finger with
resistance, ticks once past the threshold and re-reads trips and
notifications quietly, without a loading state.
Share: trip share and invite links and the journey link get a share button
next to copy, opening the system share sheet (Capacitor Share in the apps,
Web Share in browsers that have it).
Haptics: a selection click for switches and segmented controls, a tick when
a sheet is flung closed, and a firmer bump for the chat's long press.
Theme lint: this branch's new screens use the type and z-index tokens, the
Vacay company tint is a token, and the baseline is lowered to the new
counts.
The Android app now subscribes like the iOS one: TrekShell.registerPush asks
for the notification permission (Android 13+), creates the device's P-256
Web Push keys and returns the relay endpoint for its FCM token
(push.liketrek.com/v1/fcm/<token>). TrekPushService receives the relay's
data message, decrypts the record (RFC 8291, TrekWebPush) and shows the
notification. A tap opens the linked page, also when it starts the app.
The keys are sealed with an AES key kept in the Android Keystore. Firebase
is configured from res/values/firebase.xml, so no google-services plugin is
needed. The decryption is tested against the RFC 8291 example, and the
native workflow now runs the Android unit tests. On the web side push is no
longer limited to iOS: it follows the nativePush capability on both platforms.
A WidgetKit extension (TREKWidgets) with two parts: the next trip on the home
screen, with a countdown before it starts and today's day and next stops
while it runs, and a Live Activity for the travel day on the lock screen and
in the Dynamic Island showing the next timed stop and the day's progress.
The web app builds both from the trips stored offline on the device
(native/widgets.ts) and hands them over through TrekShell
(setWidgetSnapshot, syncTripDay, endTripDay). The texts are already
translated, so the widget needs no localisation of its own. The snapshot
lives in the keychain group the app shares with its extensions, so no App
Group is needed. Taps open com.liketrek.trek://navigate/<path>.
NativeWidgetsSync refreshes on start, on returning to the foreground and every
quarter hour. Both parts are gated behind the widgets / liveActivity
capabilities, so older binaries and the PWA are unaffected.
The relay hands APNs the encrypted record with a placeholder text and
mutable-content set; the new Notification Service Extension opens it with
the device's Web Push keys from the shared keychain group (RFC 8291 on
CryptoKit) and shows the real title, text and link. Without the keys the
placeholder stays, so a notification arrives either way and nothing on the
way reads it.
The target came in through ios/scripts/add-extension.rb (kept, idempotent),
its App ID is registered, and the release lane signs every target with its
own App Store profile. Web Push payloads now stop at 2800 bytes so the
encrypted, base64-encoded record still fits Apple's 4 KB.
The app subscribes to Web Push like a browser, only the app makes the
subscription: it asks for permission, registers with APNs and keeps its own
P-256 key pair and auth secret in a keychain group a notification extension
can share. The endpoint is liketrek's relay for this device's token, so the
server encrypts exactly as for a browser and nobody on the way can read the
notification. The settings card, the test push, sign-in resync and the logout
teardown all take this path where the binary has nativePush; a tapped
notification opens the place it points at, also from a cold start.
The App ID has push enabled, the app carries the aps-environment and
keychain-group entitlements.
The apps subscribe to Web Push like a browser, with their own key pair, but
with an endpoint on liketrek's relay: only liketrek holds the apps' APNs and
FCM keys. The server encrypts exactly as for a browser (RFC 8291), so the
relay hands on ciphertext it cannot read and the device decrypts it. Lookalike
hosts stay refused.
TREK now appears under "Open in" and in the share sheet for PDFs, calendar
entries, Wallet passes and mails (iOS document types, Android VIEW and SEND
filters). The native side reads the file (up to 15 MB) and hands it to the UI,
which asks which trip it belongs to, upcoming trips first, then opens that
trip's booking import with the file already chosen.
WKWebView asks for the location per server origin, under the server's name,
and again across launches. Binaries with the geolocation feature answer
navigator.geolocation from CoreLocation instead: one When-In-Use prompt as
the app, a stream that runs only while something listens, at the accuracy
the most demanding caller asked for. Every caller keeps the browser API.
A tap on the status bar now scrolls the list in the middle of the screen
back to the top, as in every iOS app.
A switch in the account settings, inside the app on a device that can unlock
that way (iOS LocalAuthentication, Android BiometricPrompt with the screen
lock as fallback). Turning it on asks once, so nobody locks themselves out.
With it on, TREK asks at launch and after half a minute in the background
before showing anything, and the app switcher shows a blurred cover (iOS) or
no snapshot (Android) instead of the screen. The setting stays on the
device, not the account.
WKWebView runs no service worker for a self-hosted server, so on iOS nothing
kept a map tile once it scrolled away. Binaries with the tileCache feature
answer a trek-tile:// scheme from a store on disk: a tile viewed once is
served from there for up to 30 days (Mapbox's limit for a device cache,
applied to every provider), then fetched anew; the store keeps under 250 MB,
dropping the least recently used tiles first, sends a unique app User-Agent,
and never prefetches. Mapbox's own resources (style, tiles, glyphs, sprites)
and Leaflet's raster tiles go through it, telemetry never does.
Offline, a Mapbox map in such an app now stays Mapbox where it was viewed,
and the pill offers the stored trip region instead. The offline settings
show what the store takes against its limit, with a way to empty it.
WebKit's swipe from the left edge slid away a screenshot of the whole page,
closed no sheet and took every drag near the edge from the UI. Binaries from
this one turn it off and report an edgeBack feature, and the UI draws its own:
the page follows the finger, a release past a third or a flick slides it off
and goes back, anything else springs it home, with a light detent.
Open sheets now register in one overlay stack, so a way back closes the one on
top first, through its own close path (unsaved changes still ask): the edge
swipe on iOS and Android's back gesture alike.
The UI ships with the server and the binary through the stores, so the two
drift. TrekShell.getCapabilities now reports the binary's shell API, version
and native features by name (iOS and Android), and the UI turns each native
feature on only where the binary has it; a binary from before the handshake
counts as shell API 1 with none. The other way round, the server names the
oldest binary its UI works with (minNativeShellApi), and an app below it is
asked to update with a link to its store.
The editor's twelve month chips were twelve grey boxes; they sit on one glass
surface now, the picked month under a pill that glides to it across both
rows. The month heads in the year overview are glass pills too.
In the editor the months of the leave year page like the trip's days, the
neighbour sliding in with the finger; in the overview the plan's years do
the same (only between years the plan has, the arrows still create one).
The day pager now pages anything with an id and can sit in a page's flow.
The row of person and legend chips is two compact pills now: People opens a
sheet to pick the member and show or hide shared calendars, Legend opens one
that explains the company days and holiday calendars, which were never a
choice.
Planned was a label with a switch in a pill of its own beside the full-width
wishlist button, and with Dawarich on the row grew cramped. Wishlist,
Dawarich and Planned are now buttons of one shape, equal width and the phone
shell's glass; Planned fills while the planned countries show.
The error and new-version fallback was a bordered box with a warning
triangle and the raw error message on show. It is now one centred card with
a round symbol (a friendly one for an update), a short title and line, and a
full-width reload button; the technical message stays for issue reports,
folded under Details. Phone, PWA and desktop alike.
The German Completed filter (Abgeschlossen) made the dashboard's filter pill
wider than its row, so it scrolled inside its box and showed cut off. It is
now Beendet, the help texts naming the filter follow, and the intrinsic
segments take a little less padding.
The day plan offered Google Maps and CoMaps as two tiles. One Maps button now
opens the day in whichever app the traveller picks: Google Maps, Apple Maps
with the stops between as waypoints, CoMaps, Waze and on Android any other
map app, the last two to the first stop, which they say. The preferred map
app from the settings opens straight away.
The day's actions sit together on one glass surface as round icons over
their labels, and the up-next card wears the same surface as the timeline
card under it.
- The rename and reorder controls beside the day's name, and the day and
weather chips on the timeline card, are now round or pill glass controls of
one height, so they read as tappable; the stay chips follow.
- A leg between two stops is one pill, as on the desktop: the mode, the time
in full weight and the distance beside it, larger and without a dot.
- The timeline flows under the up-next card at a fixed 12px, whatever its
height, instead of an offset that left a gap when the stop has no address.
- The places bar on the map wears the day rail's glass at its 36px height.
- A context menu stays inside all four edges of the screen, measured at its
real size rather than mid-animation, and scrolls when taller than it.
The phone map stacked four controls on the right and one on the left, in
three different looks. Now each side has two at most, all in one frosted
style, 20px off the edge and 8px apart: the whole-trip overview and locate on
the right, the base layer and the Dawarich route on the left, with the compass
appearing only while the map is turned off north.
The map's own places filter button and sheet are gone: the places tab carries
the same filter, and the map's pins follow it. The jump-to-today button beside
the day rail is gone too, since today's chip is already marked.
Also fixes a leftover of the segmented-control change that broke the parse.
Offline, a Mapbox map, another OpenFreeMap style or a raster template the
device cannot serve now switches to the stored OpenFreeMap basemap, light or
dark with the colour scheme, keeping every marker, route and track, with a
small Offline map pill. It switches back once the connection returns.
"Prepare for offline" stores that basemap's region for every trip, whatever
the provider, and both styles' sprites and glyphs, including the scripts a
trip in East or South Asia labels its places in. The vector prefetch keeps to
OpenFreeMap's download rules (four requests at a time, 50 MB per run), raster
tiles are no longer prefetched from OpenStreetMap's own servers, which their
policy forbids, nor inside the iOS app, which has no service worker to serve
them.
A pull on a sheet's content only answered touch, so with a mouse the sheet
could only be dragged by its top edge, and a pull that started on a button
read as a press. Mouse and pen now pull the content like a finger, a pull
that starts on a button swallows its click, and text fields keep their own
gesture.
The active day chip and the segmented controls now glide one shared pill to
the new item on the compositor, instead of repainting two buttons.
The docked sheet could only be pulled by an invisible 20px strip, re-rendered
React on every finger move, and a flick sprang back to the top before fading
out. A finger on the content now carries the sheet once the content is at its
top, the panel moves through direct style writes, and a dismissing release
throws it off screen from where it was let go, faster the harder the flick.
With unsaved changes it springs back and asks instead. Pulling up only
stretches a little.
The docked sheet now rises from below the screen edge and sinks back there,
and the floating card pops in place the way a native alert does.
The day swipe only rubber-banded the current day, then cut to the next one
after a fixed slide and a synchronous re-render of the whole planner. The
previous, current and next day now sit side by side in a horizontal
scroll-snap strip, so the finger drags the neighbour in, and momentum,
edge bounce and axis locking are the browser's own. The day changes once
the strip comes to rest, with a light haptic tick in the app.
The pager is one building block, MDayPager, used by the plan timeline and
the road trip stages alike. Each page renders its own day through the
timeline and stage hooks, which now take the day as a parameter, and keeps
its state when it slides into the middle.
The plan picker no longer shows a yearly badge, so its string reached nothing
and failed the i18n key check; the shell's start also initialised a value it
always overwrote.
The welcome headline and subtext, the demo button, the have-a-server link,
the unlimited-plan line and the claim title changed in en and de with the
redesign. All other locales now say the same, and the English headline
reads Your Trips to match the German plural.
The light scheme dropped the Kyoto band and drew the grid so faint it
vanished, leaving a bare white page. The photo now stays in both schemes
under a veil in the page colour (a dark dusk, or a bright haze in light
mode so the black wordmark still reads), and the light grid and wash are
strong enough to see.