The phone's MCP card still read the config fold state that the shared hook
no longer holds, so the client failed to typecheck. The phone keeps its
folded config, so it now holds that state itself.
SettingsColumns takes start and end instead of left and right: the prop
types counted as two physical sides for lint:rtl, and in a right-to-left
language the first column is the right one anyway.
Also: an unused icon import, seven files outside the format baseline run
through Prettier, Routing (de) and Services (fr) confirmed as the same word
as in English, and the size and warning baselines lowered to what the
redesign left (AdminPluginsPanel one wrapped line shorter, two promise
warnings, one long function and two non-null assertions in tests fewer).
The settings pages had grown into a mix of styles: folds, dialogs behind
rows, full-width rows next to boxed ones, pills for every figure and buttons
in four sizes. Every page now uses the same kit.
- Cards keep their tinted head band; every part of a card is a rounded block
with its own small head (SettingsBlock), and rows sit in rounded boxes.
- Nothing folds and nothing hides behind a dialog: the MCP config, OAuth
scopes, mail how-to, AI privacy note and addon sub-options are all shown.
- Each page is split into two columns balanced by length (SettingsColumns).
- Long explanations moved into an info mark next to the label (HintMark).
- One button size everywhere (text-control); tab names are one word.
- Admin: the old Settings tab is split into Security and Services
(?tab=settings still opens Security, and the phone keeps its single
Settings section for both); the Integration group is gone, live flights
and mail import sit next to the addons they configure.
- Addons: one card per type with rows, no search, filter or fold.
- Quota, TREK API facts, mail import status and Google's daily limit are
compact lines and strips instead of tiles and pills.
- Language is a select; routing has its own card under the defaults;
storage saves from the foot of its categories card.
- Unused keys, FoldSummary and the page-width wrapper are removed.
The settings pages list everything at once, so they read as a wall of
forms. SettingsCard and Section take 'collapsible': the card starts shut
and its badge says its state ('Connected', 'Not set up'); its title is the
button, stretched over the head, the head's action stays its own button.
The body stays mounted while shut. A link to #id opens the card and
scrolls to it; the browser remembers a card left open.
Every integrations card is collapsible now; AI parsing shows its saved
model and MCP the apps signed in. The assistant's settings link opens the
AI connection card. Tests that read into a card open it first
(tests/helpers/settingsCards.ts).
Upload only, IMAP and webhook each explain themselves in the admin card,
desktop and phone: what the way means, what it needs (a mailbox of its own
taking plus addresses, a signing secret), with its icon centred beside the
title and the sentence. All 28 locales.
ai_available folded the person's own switch in, so switching AI reading off
greyed the switch out and it never came back. It now says only whether AI
reading works on the instance (the AI Parsing addon and a connection). When
it does not, the settings card says it needs the AI Parsing addon, by its
localized name, in all 28 locales; the wiki says the same.
- Admin settings (desktop and phone, self-hosted only): the instance switch,
how mail arrives (IMAP or a signed webhook) with its fields, the address
template, the AI allowance per person and day, a connection test and the
status in counts. Changed fields only are saved; secrets stay masked.
- The review mark of an imported booking takes its mark off: the pill in the
desktop dialog and a row in the phone's transport sheet ('Looks right'),
through the ordinary booking update. Its hint no longer speaks of airports.
- The inbox follows mailInbox:changed and mailInbox:settings, and
?inbox=mail (the 'bookings waiting' notification) opens it.
- mailIngestRepo: the online-only path the views take, answers parsed
against the shared contracts.
- Dropped 'read with AI' and the unused mail-review contract: the server
reads a passing AI failure again on its own.
- mailInbox, mailAdmin and the new reservation, settings and notification
strings in all 28 locales; the pending notice is a plural group.
- The live flight line on the booking cards is one of the card's fields,
full width on the phone; the phone's live card keeps its map to the middle.
- Admin flight and mail cards sit in the left column.
The cron callbacks hand back nothing; the cursor's lookup is findCursor so it
no longer shadows the base repository's find(); the draft's suggested trip
uses the shared contract's type; endpoints spell out their optional columns;
the relay page's running count is typed. Prettier over every new file outside
the generated entities.
A plan without the import switches it off from outside the server, and an
admin can switch it off too; the plan promises a "mail import paused" note.
An hourly job sends mail_ingest_paused to each holder of a live address with
their own import on, notes the pause first, and forgets it once it is back.
The IMAP box, the webhook secret and the address template join the admin
settings form (masked, encrypted, held by the operator on a managed install),
and MAIL_INGEST_* variables override them through the mailIngestConfig token.
The audit row masks every masked key, and key rotation covers the new secrets.
Addresses per person and per trip (random base32 token, stored hashed and
encrypted), an intake every transport shares, and a job that reads each
mail once under a lease: filters without AI, machine-readable data always,
one AI request with the person's trips, the server's checks G1 to G9, then
one transaction per mail that adds what passed, keeps the rest in the inbox
with a reason, and notifies the forwarder and co-travellers. Undo within
7 days, manual apply, filing as documents, a prefilled form and a nightly
retention complete it; on a managed install the operator's switch decides.
mail_booking_added tells the forwarder (with keep or undo) and co-travellers
of a shared trip; mail_booking_pending points at the inbox. Both are listed
only while the mail import is on, with texts in all 28 locales.
registerResponseAction lets a domain name what a yes/no tap does without
this module importing it.
Zod contracts for the forwarding address, the person's switches, messages,
the bookings read from them, the inbox and every action on an entry, the upload,
the webhook and the admin status card. One reason enum names why a mail or booking
waits, mapped from every AI outcome; mailInbox:changed and mailInbox:settings are
registered as user-scoped events.
LlmParseService.parse() now says how a reading ended (ok, empty, no_text,
quota_exhausted, ai_disabled, user_cap, provider_error, too_large). The clients throw
LlmRequestError with status and body code, so the TREK Cloud gate's 429 QUOTA_SPENT
and HARD_CAP read as a spent allowance. The preview reports the outcome per file and
the assistant reads its empty reason from it rather than matching warning strings.
confirm() now answers { created, failed: [{ index, reason }] } instead of logging
and dropping the items it could not write, and checks the trip and reservation_edit
itself so the import route and the assistant get one answer. An item with a booking
reference records it as its origin (sync off) and a repeat import is a duplicate.
The live flight card shows a small real map behind the pass: the great
circle between the airports, solid where flown and dotted ahead, the
aircraft on the line turned along the route. The route is framed in the
free middle, the map fades out under the airports and times laid over it,
and its place names are hidden. Data and map credits sit on the map as its
hover text instead of a footer line.
The card head now follows the booking dialog: a tinted band with pills for
the state, what comes next and the delay; the numbers of the moment use the
dialog's fields on the desktop and the sheet's stat boxes on the phone; both
times sit on equal tiles in one line. Without live data the card, the strips
and the plan line show nothing, and the pulsing dots are gone.
Also fixes the failed dev run: unit symbols and the leg position are listed
as NOT_PLURAL, the aircraft label uses logical sides, the flight store is in
the test reset registry, the sync loop stops once no flight lies ahead (no
endless timers under fake timers), and timeCell and widgetFlight drop below
the max-params and complexity limits. Guards the GL bridge's resize frame
against a layer removed before it runs, and keeps the booking code pill as
tall as its neighbours.
The booking dialog and the phone's transport sheet show a flight's moment,
both airports with planned and expected times, the route as an arc with the
aircraft on it and the numbers of the flight; booking cards and day plan
rows get a one-line state with a progress bar. The trip map draws the
aircraft in the air on Leaflet and GL, and the admin settings carry the
switch. Translated into every language.
A new flight-live domain polls the open feeds adsb.lol and adsb.fi (no key,
no cost; adsb.fi only on self-hosted installs, its terms are non-commercial)
for every booked flight leg in its watch window, derives phase, take-off,
landing, delay and an arrival estimate, and pushes changes as flight:live.
GET /api/trips/:tripId/flights/live, the MCP tool get_trip_flight_status,
notifications for take-off, landing and delays, and the admin switch
flight_live_enabled (FLIGHT_LIVE_ENABLED wins) come with it.
"Vorhandene verknüpfen" broke onto two lines in the narrow half of the
booking dialog. The labels now truncate instead of wrapping, and the German
label is the shorter "Datei verknüpfen", matching "Datei anhängen".
Edit trip is an area of the settings dialog, beside People, Sharing and
Journal, instead of a second dialog. The side column names the areas only, the
dialog keeps one height, and every area uses the same cards, fields and
buttons; the trip form on its own takes the same head, cards and buttons. A
trip that loads quickly opens without the splash on the desktop too.
The trips entity takes costs_mode as the generator writes it, the plugin
output contract publishes it, and the hand-built test schemas carry it. The
new MCP fields come from @trek/shared, the link tool tests join the collab
file that already mocks the database, oidc_auto_redirect is the operator's
on a managed install, and the tests follow the KML service, the dated trip
days and the zod pipe.
The transport sheet's row wrapped once a ticket and the wallet were there.
Its actions are icons now, named by their labels, and the attachments sit in
their own rows with a download, like the transports list. The wallet is a
black circle, beside the close button in the booking editor. A failed
download on the phone now says so.
The list menu on desktop and phone gets "Sharing for Whole List": pick
Shared, Personal or Shared with people once and every item of yours in
that list moves there through the existing per-item sharing write. Items
other people added stay as they are, and the dialog says so up front.
Each attached file row now has a download icon next to its name, on the
desktop booking cards and detail dialog and on the phone's bookings and
transports cards. On the phone the file rows move out of the card body
button so open and download are two real sibling buttons.
A new app setting oidc_auto_redirect (default on) is part of the admin form
and the public app config. Off, an instance without password login no longer
sends visitors straight to the IdP: the login page shows its SSO button,
also while the cached config is read. Admin toggle on desktop and phone.
Both feed routes and the one-off export.ics take ?days=0, validated by a
shared query contract, and then skip the all-day "Day N" event per day; the
default is unchanged. The subscribe dialog gets a Day overview events switch
that adds the parameter to its links, export_trip_ics takes dayEvents and the
feed tools explain the parameter.
The tour detail gets a Maps button beside Navigation that hands the whole route
to the apps that follow one, in the tour's own mode (walking or cycling). The
stops are spread evenly along the line and capped at ten, the most a Google
Maps directions link opens. Ski, paddle and horse tours are not offered, since
a map app would route them by road; the GPX export stays the way for those.
A shared Immich or Synology album can hold hundreds of photos from other
trips. Once an album is open the picker now offers a from/to range that
filters the already loaded assets by the day the grid files them under, so
Select all only takes the photos in range. No server change.
Google Maps takes no list from outside, but My Maps imports a KML layer.
The trip export dialog and the phone export sheet get "Places for Google
My Maps" (GET places/export.kml, MCP export_trip_kml), and a list's export
menu gets KML next to GPX. The KML codec gains a pin writer; the list file
mapping moves to collections.helpers to keep the service within its size.
A read tool that answers one list a page at a time (100 by default, up to
500) as compact one-line JSON rows, filtered by status, label or "no label",
with the list's labels, the matching total and a cursor. The cursor is the
last place's position (sort_order, created_at, id), so labelling a page
while paging through unlabelled places skips none. getCollection orders by
the same keys, id last, so both tools agree on ties.
The update contract takes google_place_id, google_ftid, osm_id, website,
phone, price and currency like saving does, website with the same
normalisation and refusal. REST and update_collection_place share it, so an
agent can correct a place instead of re-saving it and losing its labels,
ratings, position and id. The assistant's change card names the new fields.
A fourth status after visited, for a place already seen that the user wants
to go back to. It joins the one-tap cycle, the filters, the pickers, the
assistant cards and the MCP status tool, and counts as visited wherever
visits are counted (isVisitedCollectionStatus), so "visited everywhere"
keeps it. The status pickers go two by two where four would not fit.
A third switch next to the page break and transport notes switches scales the
day content's type by 1.35 and darkens its greys, for readers who print the
plan. Off by default and remembered per browser like the other two; the day
content's sizes and greys now come from three CSS variables on <body>.
Mapbox Standard labels shops, restaurants and sights of its own; they look like trip
places but cannot be picked. A Hide Points of Interest switch next to the other Mapbox
options, on the desktop and the phone, turns them off on the trip map and the preview
without a rebuild. The Standard config handling moves into useStandardBasemapConfig.
A place saved with a typed address and no search pick, link or map point gets no
coordinates, so it never shows on the map, and nothing said why. The place form warns
under the address while that is so, and the inspector, the phone's place sheet and
both place lists mark such a place. No geocoding on save; the form stays unchanged
for a service stop, which explains a missing position itself.
The trip form was mounted in the planner but nothing opened it, so title, dates,
cover and currency could only be changed from My Trips. Trip settings now lists
Edit Trip at the foot of its areas for anyone with trip_edit; it swaps the dialog
for the trip form. The phone already has Edit Trip in the More sheet.
With hotel bookends on, a day whose first stop was a hire car pick-up got no morning
bookend, so the drive from the stay to the desk was missing. Only a landing means the
traveller was not at the hotel; the pick-up is the first drive of the day.
Costs always converted to the display currency from Settings, so a trip
that moves on to another country could not be read in its new local
currency. A picker in the Costs bar (beside the Expenses heading on the
phone) now reads one trip in any currency; the server already settles in
any base. The pick is per trip and kept in this browser, nothing stored.
Seeing what a trip cost without, say, food meant adding it up by hand. A
click (or tap) on a category in the breakdown now leaves it out of the
total, your share and what you paid, dimmed in place with a line naming it
and Count all to undo. View only: nothing is saved, and what is owed, open
or settled stays whole. The desktop breakdown moves into its own component.
Travellers who track what a trip costs but never split it saw an
Unfinished warning on every expense and a settle-up they had no use for.
A trip now has a costs mode (appended migration, trips.costs_mode, split
by default); totals leaves payers, shares and settling up out of Costs on
desktop and phone through the same gate as a trip of one, and saves new
expenses without a payer. PUT /api/trips/:id and update_trip take it on
budget_edit. The split chips move into their own component.
A stay shared by people who slept a different number of nights had to be
worked out by hand. The custom split now takes shares next to amounts and
percent: five nights against four split the total 5/9 to 4/9, cent-exact by
largest remainder. Only the amounts are saved, so server and MCP are
unchanged; the phone sheet gains the same unit switch, percent included.
nativeApi goes to 3 with 5.1.4, whose UI follows a widget or Live Activity
tap that starts the app. The apps refuse an older server with the hint to
update, and check a server they remember once more.
- The SSO unlink answers with a response contract (adminSsoUnlinkResponseSchema).
- It reuses linkOidcIdentity with nulls instead of a new repository method,
so UsersRepository keeps its method count and size.
- The client calls the route through apiClient, so api/client.ts does not
grow, and its confirm dialog takes no async callback.
- The pager's preview step and pagesFor keep their complexity.
A sign-in with another subject of the same provider is refused, so an
account whose provider re-issued the subject (a migrated or rebuilt IdP, a
changed sub format) was locked out: editing the email, as the wiki advised,
never touched the stored link. The edit dialog now offers "Remove SSO link"
for other accounts while SSO is set up (DELETE /api/admin/users/:id/sso,
audit admin.user_sso_unlink). The next SSO sign-in links again under the
rules of a first one, so it still needs a verified, not self-set email.
The log line names the remedy, and the wiki tells the two refusals apart.
Fixes#3065
- native-shell/amount.ts: the no-break spaces in the price pattern were
written out as characters; \s already matches them.
- widgets.ts: the Live Activity's lead time stays inside the module.
- i18n: the count strings of the new plan tiles replace the removed ones
on NOT_PLURAL, and the address placeholders of the non-Latin locales
are their own instead of English copies.
- mobile.css: the side safe-area margins of the floating bars use
margin-inline, swapped for right to left, instead of physical sides.
TREK is the application, not a thing one has a copy of: "I already have
TREK", not "a TREK". The same for the not-TREK messages in German and
Dutch and the agreement title in French, Spanish, Italian and Catalan.
The screens the app ships itself (welcome, plans, address, own server and
the states between) move from the dark card layout to a light design on an
animated aerial landscape drawn by a WebGL shader (native-shell/scenery.ts):
an island, a coastal town, farmland and mountains in one world.
- Welcome: the landscape with the TREK wordmark, three perks in one card,
"Explore cloud plans" and "I already have a TREK" as two buttons.
- Plans: one tab per plan; choosing one flies the camera to its place,
further inland with every plan. Each plan reads as a kind of trip, the
price sits in a card with a monthly/yearly switch and counts to its new
amount in the store's own format (native-shell/amount.ts), and a yearly
price shows what it makes a month. Restore purchases, the renewal terms
and the links stay as the store rules require.
- Address: the address writes itself on a glass card over the landscape;
free, the password rules and the agreement sit on white cards.
- Own server: the server address on the glass card; a failed check turns
the card red with what went wrong, proxy sign-in and client certificate
panels in the same style.
- States: server not reachable, certificate needed, already subscribed,
setting up (with its steps, then "Arrived") and the slow setup.
- iPad, an unfolded phone and a phone on its side get two columns, the
landscape beside the content; on a dual-screen device each half takes
one screen. Arabic and Hebrew mirror through logical sides only.
- The shader caps the pixel ratio at 2, drops to a lower resolution on a
slow device, stops in the background and is one still frame with
reduced motion.
- The sign-in and error page bars over a proxy's page (iOS and Android)
take the same look: a white card and a dark button.
- Plus Jakarta Sans is bundled for the text, with Montserrat standing in
for Cyrillic; the wordmark fonts and the old preview images are gone.
- New strings in all 28 languages; the keys of the old screens removed.
- The dev sample plans follow the cloud's plans (start, basis, plus, pro).
The cloud screen shows real screens of the app. The plans are a choice now: one tap marks a plan, the recommended one is marked, what every plan has is said once above them, storage says how many photos it holds, and the yearly switch shows what a year saves; one button at the foot carries the chosen plan on. The claim screen names the plan with a way to change it, shows the address as it will be once it is free, ticks off the password rules while typing, says in three words where the servers are, that it can be cancelled and that there are no ads, and puts the photo behind the form away.
One subscription per Apple ID or Google account. Choosing another plan in the app changed the running subscription on the App Store, and the redeem then failed with 'We could not set up your TREK'. The app now asks the store first and shows a screen of its own: the account already has TREK Cloud, a new one can be taken out once it has ended, with Open my TREK and the store's page to manage it. The native purchase refuses as well; a paid one that was never set up is still redeemed. On the address screens the link 'What is a TREK server?' is gone, and the small print under a purchase is set in the system text face at regular weight.