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).
From xl up, as on the admin settings: what connects your trips with other
services (booking mails, photo servers, AirTrail, Dawarich, Wanderer) on
the left, AI and developer access (AI connection, AI parsing, API keys,
MCP) on the right. A column with nothing to show drops out and the other
takes the whole width.
Behind the title stood the review mark, the 'From mail' pill and the two
actions, and the title was cut short. Where a booking came from is looked
up in its detail dialog, so the pill stays there only. The AirTrail note
sat above the mail pill; it is back above AirTrailPill.
- The mail import's status tiles (which, labels, tones) were written out
in the desktop and the mobile card alike; statTiles in the shared model
holds them, each card keeps its own look.
- The journey share link set 'copied' back two seconds after a copy even
when the dialog had closed, which also broke a client test run (window
gone at teardown). useCopied, the trip share's hook, moves to hooks/ and
serves both; it clears its timer on unmount.
- PlanLlmConfig is not exported: only the union is used outside.
- The upload posted its FormData through the client's JSON default, so the
form went as JSON and the server saw no file (400). It goes through
postMultipart now, like every other upload.
- reservation-origin.ts becomes reservation-origin.contract.ts: the booking
import sets an origin, and a contract is what another domain may import.
- CI: the client's strict type check holds the whole program with strict
checks and outgrew the default ~4 GB heap; it gets 8 GB like the server's.
- auth.service: how an admin form value is stored (validated, encrypted, a
masked echo kept) moves to the app settings registry as storedAdminValue
and isMaskEcho.
- reservations.service: a booking's origin (withoutOrigin, originColumns)
moves to reservation-origin.ts, its input shapes to reservation-inputs.ts,
exported on from the service so importers stay as they were.
- Reservations.repository: the joined projection, the origin lookup and the
origin columns move to _shared/, like the Wallet pass context before.
- test-sql: MailIngestApplyService.prepare is marked as no SQL statement.
- The pending bookings dialog opens centred, and says 'nothing waiting' once.
The last read sits in the head line with a green dot, amber while the last
run failed; the day's numbers are tiles with their icon in a soft tone
(mails, added on their own, waiting, failed turning red above zero); the
last error is its own amber note. Desktop and phone.
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.
- mailInboxModel.test: actionsOf takes the entry alone since the AI reread left.
- booking-origin.test: its endpoint helper gives the sequence an endpoint has.
- test-new-service baseline follows booking-import's single service factory.
- lint:tx: the mail transports and the intake document why their writes stand
alone (@txIndependent, network I/O between them, one copy per address); the
JSDoc of NotificationsService.respond is back above it after the response
actions were registered in between.
- lint:boundaries: booking-import asks McpToolGuardsService whether the person
may edit the trip's bookings instead of reading the Users table; its unit
test builds the service through one named factory.
- lint:query-api: findIdByExternal reads through Kysely.
- lint:test-mocks: the mail e2e hands its own snapshot db to the ORM instead
of mocking the database module.
- Entities regenerated (index order only).
- Client: the review row reads the live booking, the shells share
useMailInboxShell, and the admin card's buttons are one size down.
- 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.
The settings event was registered with no sender; renewing or revoking an
address and changing the switches now send it. The client lists both events
as not yet shown, which keeps the registry-parity test green until the mail
screens move them to their listener.
Reading the bridge's MaplibreGL export threw under the test mocks, which
define only the factory, so every mocked basemap counted as an unloaded
chunk. The guard now patches the class of the layer being attached, and
only a class that has _transitionEnd itself, so stand-in layers are left
alone.
The resize guard reached for the bridge's MaplibreGL class, which the test
mocks of the bridge do not provide, so every mocked basemap counted as an
unloaded chunk. The guard now only applies where the class exists. The
mini map hides place names through VectorBasemap's own hideLabelLayers
(a hideLabels prop) instead of a second copy of that logic, and the route
test no longer uses Array.prototype.at, which the client's lib target lacks.
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.
Android lint refuses a composition reading the Configuration through
LocalContext. The arc's mirroring now follows the locale's layout direction,
which is what the widget's language decides anyway.
A new Flight widget on iOS (small to large and the lock screen) and Android
shows the trip's next flight or the one in the air: both airports, the arc
with the aircraft, a countdown the system keeps running, and the delay. It
follows the feed's phase, so a late departure is not flown along the arc.
Around a flight the travel day's Live Activity becomes a boarding pass with
a running bar and countdown, in the Dynamic Island too.
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.
"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".
The whole-list sharing runs on usePackingCategorySharing in the packing panel
and the phone tab, its sheet is markup fed by the tab. The trips service reads
a stored costs_mode as one of TripsCostsMode, which the export check wants used.
feeds and trips import the calendar query DTO, so it lives in
calendar.contract.ts rather than as calendar's internal dto file. The places
controller tests pass the KML service in every call, and the revisit pill
test builds a whole list.
Inside the trip settings, Escape and the backdrop no longer close the dialog
while the date question is up, and the embedded form does not load the
members it does not show. Every trip open is timed afresh for the splash.
The tests follow the cover image card, the danger button, the delayed splash
and the open and download mocks.
New warnings fixed at their source (refs written in effects, typed tile
options, no stray imports, smaller functions and components), the payer and
split heads hold their switch instead of letting it overhang, the baselines
lowered where the round removed warnings, and the tests updated to the
solo trip, the shares view and the revisit status.
The trip settings sheet opens on a list of its areas, each a page of its own
with a way back, and the journal there is drawn in the phone's own cards. A
new packing list is typed into a card with its add and cancel, selecting
places keeps its way out in view with the actions as icons below, and the
journey's satellite switch sits in its bar beside the menu.
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 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 Google link carried no mode, so a hike opened as a drive, and a stop on
the equator or the prime meridian was dropped. CoMaps routes a start and an
end only; a tour with stops between would reach it as unrouted pins.
With a category left out the total card still counted every expense, and in
the table view the only way back was to switch views; the chip now brings
everything back. The phone says what is left out once, under the total.
Linking a receipt offers pictures and PDFs only, like the upload.
The footer wrapped once a place had a day, a website and a collection. Now
only adding to the day, navigation and edit carry words, website and
collection are icons, and removing from the day and delete sit behind one
"…". The words drop out when the panel is narrow (a tablet) instead of the
row wrapping. Folding is kept for the place on show only (#3039).
DialogIconButton is the square footer button DeleteButton already was, for
any action named by its tooltip. A "…" menu near the bottom of the window
used to be pushed up over its own button; it now opens above it.
Safari before 16 has no AbortSignal.timeout, which cost the whole export; a
controller and a timer replace it. Photos decode at twice the thumbnail
height and three at a time, so a long trip no longer ends the web view.
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.
On the road a stop's picture is often what decides whether it is worth the
detour. The phone's stop sheet shows the place's picture full width under its
name and the desktop stop dialog shows it in place of the kind tile; both open
the place's photos on tap. The picture is the place's own image, else the
provider photo for its Google or OSM id through the shared photo cache.
The journey map gets the planner's round satellite switch in its top corner,
on the desktop aside, the phone views and the public page. Leaflet takes the
vector basemap off while the imagery shows and reattaches it on the way back;
the GL renderer slides the imagery under the journey's own layers. The choice
is per view and opens on the map, so no setting is read or written.
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.
The accept dialog opened on "not on a day yet" although the stay carries its
local date. It now picks the trip day whose date matches, on open and when the
trip is switched, and falls back to no day when the trip has none that matches.
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.