Every sheet now stands on the opaque sheet surface: the frosting the
translucent one relied on is dropped while the scrim fades, so the page
read through the fields as sharp ghost text.
On a wide window a sheet is centred in the content beside the navigation
rail instead of on the whole window, so it never lies on the rail, and it
grows to the window's height instead of stopping at 880px. On a phone the
card stands between the status bar and the dock rather than across the
dock. A sheet marked as a form fills the height of a window as short as a
phone on its side, with Cancel and Save beside the title (new `short:`
variant, m-form-sheet grid in mobile.css).
The form chrome gains a scrolling body that fades at the edges with more
behind them (useEdgeFade on the vertical axis), a date field whose
keyboard entry moves into the picker, a field wrapper for amounts with a
currency, and two-line titles. The focused sheet no longer draws the
app-wide focus frame or loses its corners, and the desktop's dark field
paint stays out of the touch shell, which painted a dark box inside every
rounded field.
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.
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).
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.
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.
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.
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.
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.
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.
- 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 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.
The track was w-10, which is rem and followed the text size setting, while
the knob and its travel are px; with a smaller text size the knob hung over
the end of the track. The track is 40 px now.
- The entry carousel draws the card at the centre larger while the swipe is
still moving (focusIndex, a transform), and the active card and the map
follow 90 ms after it stops instead of 150; the grow takes 200 ms
- The journey map's zoom buttons set their display inline, which beat the
class that hides them on touch screens; the display is a class now (#3001)
- The packing widget data no longer carries a colour literal
The active card grew its box from 136 to 164 px once the swipe had settled,
which moved every card after it and made the snap pull the strip a second
time. Every card now keeps the same box and the active one is drawn larger
with a transform, so nothing moves; the strips keep the room with a wider gap
and top padding. Where the browser reports scrollend, the card grows the
moment the snap lands instead of a beat later.
TestFlight builds now carry the release tag's message as What to Test.
- RTL: the measured pills and centred controls use logical forms or a marked line
- i18n: the two native widget numbers are positions, not counts (NOT_PLURAL)
- Types: styleOverride on the lazy GL map, the location and shared-file payloads,
the capabilities fallback, the transport sheet's stay metadata
- ESLint: latest-value refs move to layout effects, usePresence keeps the last
open state in state, the booking import opens clean with a shared document
kept in the list (the reset effect used to clear it again)
- Layers: the phone import sheet lives under mobile/ and reads through tripRepo;
the offline host check moves to utils/tileUrl
- Sizes: plugin layer geometry, hardNavigate and the exit animations get
modules of their own
- Tests follow the status radios, the stay chip overview, the dock + button
for the first list and the field token; store registry lists the new stores
Docked sheets defaulted to the lighter bar glass, through which their header
buttons hardly stood out; every sheet now uses the same glass. The Vacay
people and legend sheets get the icon tile header.
Atlas bucket list, open route in, open in TREK, the place actions and the
MCP/OAuth client sheets now show the icon tile header (or, where a sheet
closes only by its buttons, the same title row without the close).
Admin sheets: their frame draws the icon tile header (an icon per sheet, the
plugin sheets' icons moved out of the title into the tile), the soft scroll
edge and 42 px footer buttons; admin fields use the sheets' eyebrow labels
and round inputs. The settings picker, the bulk category sheet and the save
to list sheet get the common header (TileHeader may now leave out the icon
for a plain picker). Every phone sheet now uses the glass material, as the
trip settings do; it stays 94 % opaque, so forms over the map remain
legible.
The shared confirm sheet (44 places) gets the icon tile with the title, a
warning icon for destructive questions, and the sheets' 42 px buttons with
the danger colour on a destructive confirm. The dashboard's and the
collections' own confirm dialogs now use it.
Vacay invite, shared calendars and settings get the icon tile header; the
settings' rows separated by hairlines become cards on the inner surface, as
the trip settings' sections.
Both get the icon tile header (the entry keeps its move buttons between
title and close), glass, the soft scroll edge and the sheets' 42 px footer
buttons; the journey name is a round field, delete in the entry's footer a
round icon button. The collection share sheet's invite button is round like
the trip settings' one.
The collections sheets had a header of their own with a hairline, white
square fields and short buttons. Their kit now draws the icon tile header
every phone sheet has (an icon per sheet), round 42 px fields on the field
surface and 42 px footer buttons; the sheets sit on glass with the soft
scroll edge. New list and add place lose their last own field styles: the
labels are the sheets' eyebrows, the search fields, the category picker and
the status choices are round fields of the same height.
The pending / confirmed toggle was shorter than the booking code field next
to it. It is now a 42 px framed field with the status colour as a dot in
each option, marked up as a radio group, and sits under the code at full
width, where both words fit.
New trip: the icon tile header, sections with labels above round fields
instead of floating labels in boxes, the round date fields, the shared
footer buttons, and no box within a box for the reminder and the cover
search.
The shared select, date, time and airport pickers style themselves inline
for the desktop and looked out of place in phone sheets, white and
squarer than the fields beside them. Inside the phone UI they now take the
sheets' 42 px round field on the field surface; the desktop is unchanged.
A tour tapped in the plan opened the desktop detail dialog in a hand-built
overlay: no slide, no drag to close, the desktop's frame on a phone. The
dialog now renders into the frame it is given; on the phone that is a sheet
like every other detail (MTourDetailShell), with the tour's name as its
title, its whole content and every action, the back gesture and an exit
animation.
Tapping the accommodation in the plan went straight to the stay's editor,
where travellers only wanted to look it up. It now opens the overview of the
booking behind it, as on the desktop, with check-in, check-out and nights as
tiles (one shared stayStats for both), and only a stay without a booking or
a place still leads to the editor.
Edit in that overview opened the transport editor for every booking, a stay
or a restaurant table included; non-transport bookings now open the booking
editor, as in the Bookings tab.
A stay with two linked bookings showed its chips twice in the day header,
because the list repeats a stay per booking; the header counts it once.
The shared form fields are round like the trip settings' fields (multi-line
ones keep a softer radius), and the shared footer's cancel, submit and
delete are the sheets' 42 px buttons. Every trip form built on these parts
(place, transport, booking, cost, note, packing, tasks and more) follows.
The journey settings sheet looked like a different app next to the trip
settings. It now uses the same parts: the glass card with the icon tile
header and the journey's name under the title, sections as inner cards with
a title, count badge and hint, 42 px round fields and buttons, the shared
link field, and switch rows for tracks, entry fields and the public link.
Those parts (SheetSection and the SHEET_* button and field classes) moved
into MTripSheetUi for every sheet to use.
Fields and buttons sitting directly in a column no longer shrink below
their height; name, subtitle and the archive button were squeezed flat.
Collections: the empty screen's own New list button is gone; the dock's +
starts the first list while there is none and adds places once there is one.
Trip settings: counts sit in a badge next to the section title instead of
reading as part of it (ACCESS 1), and every round button of the sheet,
invite and add guest as well as the member and guest actions, is one 42 px
circle; the icon-only buttons were wider than tall.
Journey settings: the public link is the same field as the trip's share and
invite links, through one shared MLinkField; the shared map's extras hang
under the map row with a guide line and smaller labels; removing the link is
a full-size button.
The transition waited for the new page to replace its route fallback and
polled for that with requestAnimationFrame. While a view transition waits
for its update the browser renders no frames, so the callback never fired
and every navigation to a page whose chunk was still loading sat frozen
until the browser gave up after four seconds. The wait now polls with a
timer: from Vacay to the journal takes 0.45 s instead of 4.1 s with an
empty cache.
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.
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.
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 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 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.