Commit Graph

2823 Commits

Author SHA1 Message Date
Dan ba9936f90a feat(i18n): add Thai (th) translation 2026-09-24 17:34:29 +02:00
Maurice 3c036ea25d feat(i18n): bring the Slovak translation up to date with dev
Translates the keys dev gained since the branch was cut, the help center
above all, and drops the six keys dev no longer has. Registers sk
everywhere a language is listed: notification emails, the rate-limit
message, the placeholder check and the wiki. The two tests that counted
the languages by hand now derive the count from SUPPORTED_LANGUAGES.
2026-09-24 17:34:09 +02:00
Peter Gonda 80502dcc9d feat(i18n): add Slovak (sk) translation 2026-09-24 17:34:09 +02:00
Maurice ffa085aef7 feat(i18n): bring the Estonian translation up to date with dev
Translates the keys dev gained since the branch was cut, the help center
above all, and drops the six keys dev no longer has. Registers et
everywhere a language is listed: notification emails, the rate-limit
message, the placeholder check and the wiki. The two tests that counted
the languages by hand now derive the count from SUPPORTED_LANGUAGES.
2026-09-24 17:33:51 +02:00
Jaano Rosin 9263421c5e feat(i18n): add Estonian (et) translation 2026-09-24 17:33:51 +02:00
Maurice 33aa1cd52e feat(i18n): bring the Azerbaijani translation up to date with dev
Translates the keys dev gained since the branch was cut, the help center
above all, and drops the six keys dev no longer has. Registers az
everywhere a language is listed: notification emails, the rate-limit
message, the placeholder check and the wiki. The two tests that counted
the languages by hand now derive the count from SUPPORTED_LANGUAGES.
2026-09-24 16:57:11 +02:00
shakirgv 9b843bee80 feat(i18n): add Azerbaijani (az) translation 2026-09-24 16:57:00 +02:00
Maurice 8906b66bc8 feat(settings): set the Google Maps key through PLACES_API_KEY
The server already put PLACES_API_KEY ahead of the key saved in the admin
panel, but nothing said so: the variable was in no template and the admin
field kept showing a stored value that no search used.

GET /me/settings now answers env_keys, the variable behind each key the
environment sets (Google, Unsplash, Amap), and null for that key's value.
The admin field turns read-only and names the variable, Test still probes
the key a search resolves to, and a save leaves those names out so the
stored fallback survives. The variable is documented in .env.example,
docker-compose.yml, the Helm chart (as a secret) and the wiki.

Addresses discussion #1881.
2026-09-24 16:50:02 +02:00
Maurice 79cd3095e0 feat(login): an OIDC-only instance shows the way to the IdP, not a form
On a slow line an OIDC-only login page drew the email and password form while it
asked the server which sign-in it offers, then the SSO button once it knew, and only
then left for the identity provider. Both looked like something to fill in or click.

Until the config answers, the page now shows a short wait instead of the form, and
on an OIDC-only instance it becomes a screen that only says where it is going,
"Taking you to Keycloak…", with nothing to fill in. A repeat visit announces that
redirect at once from the cached config. Should the redirect not have left the
page after eight seconds, a quiet link continues by hand.

The redirect still never runs on a cached config, right after a sign-out or after a
failed sign-in at the provider. That last case now loads the config as well, so an
OIDC-only instance shows its own screen with the error rather than a password form
nobody can use.
2026-09-24 16:34:51 +02:00
Maurice 20e912d6c4 fix(help): the document sync guide speaks for every store, not only Nextcloud
"Keep the documents in step with a Nextcloud folder" walked through Nextcloud
alone: its app password, its base folder, the question where the trip should live
in Nextcloud. The dialog offers five stores. The guide now reads for all of them:
how each files things (by tag, in a folder, in a space), what each signs in with
(an API token for Paperless-ngx, an API key and the organisation for Papra, username
and app password for Nextcloud, username and app token for OpenCloud, username,
password and a two-factor code for Synology Drive), that the base folder belongs to
Nextcloud and Synology Drive, and that the trip lives in a tag, folder or space. The
pictures keep Nextcloud as the example, and the text says so. All 23 languages use
the labels of their own interface.
2026-09-24 15:23:21 +02:00
Maurice 3b05a59b19 fix(help): the days guide shows deleting a day and adding one with a date
"Add, reorder and rename days" still described the reorder popup as it was before
#2485: Move up, Move down and one Add day at the end, and its picture showed that
popup. The guide has five steps now. The reorder step names the bin on each day and
the two ways of adding one, the next date or Without date, and a new step shows the
question before a day goes, with what goes with it, answered with Cancel. The first
step says that shortening the dates lists the days that go, and the tip says moving
days can be undone and deleting one cannot. All 23 languages use the labels their
own interface shows.

The picture script looked for the Add day button that #2485 removed, so it could not
run any more. It frames the popup itself now, asks about the second day so the
question has places and a booking to list, and clicks the first day a second time
when the steps before left it selected and the first click folded its panel away.
2026-09-24 15:00:30 +02:00
Maurice 055c3ae5c7 feat(help): readable rings, real documents and imagery in the pictures
The pictures picked up five faults that the run could not see, because a
passing step only proves the click landed.

The ring vanished on the lightbox: the accent is nearly black and so is
the overlay. It now measures the surface behind it and puts a white
hairline outside itself and the number only where the contrast is too
low, so every other picture keeps the single line it had.

The number fell outside the picture whenever the target was wider than
the frame, which is every full-width row: the frame is known before the
ring is drawn, so the number moves inside far enough to clear the edge.

The satellite imagery was a grey rectangle although every tile came back
200. Leaflet fades a tile in over 200 ms and takes the progress from
`+new Date()`, and the browser's clock is pinned to the picture day, so
the opacity it writes on every frame is always 0. A stylesheet on
Leaflet's own `leaflet-tile-loaded` outranks that inline style. The
satellite guide also waits for decoded tiles now instead of for a tile
element to exist.

The uploaded files looked like nothing anyone would upload. A PDF is
drawn with an issuer band, a reference, a table of fields and a footer,
and the two photographs are gone: a drawn photograph reads as clip art
at any size, so the trip carries a map screenshot with a pin and a
booking confirmation in a browser window, which are graphics in reality
too.

In the panel, a step picture was covered into a 16:10 box and lost its
sides; contained everywhere now, the hero and the result in 16:9.

Beside that: the place guide gained the drag-and-drop route to a day as
its fourth step, in all 23 languages, and `e2e/help/README.md` now holds
the whole visual contract, the traps that cost this round (the pinned
clock, prettier against the client sources, a killed run holding its
ports) and how to find out which pictures a change forces to be retaken.
2026-09-24 14:06:38 +02:00
Maurice 8aa9e308bb feat(help): every picture from one run
The whole set, 914 files, made in one pass on one machine against one
calendar: 41 screens, 225 guides, 764 steps. What changed beyond the new
guides: the pictures carry the run day's dates now, the Files tab's
preview shows a real PDF page, the Place details card has its hours, its
ring and its phone number, the trip map and the Atlas carry the Dawarich
control the new bullets describe, and the Files toolbar has Document
sync beside Trash.
2026-09-24 14:06:38 +02:00
Maurice c4d436bfc1 feat(help): translate the new guides into all locales
Ninety-eight keys in the other 22 languages: the nine guides of this
round, the reworked read-place and import-booking-file, and the two
screen bullets. Every UI label in them is the app's own string in that
language, and where a label is still English in a locale, the text keeps
it English so the reader finds it on screen.
2026-09-24 14:06:37 +02:00
Maurice 41851429c9 feat(help): guides for what TREK talks to, and a seed that follows the day
The first round could not photograph a few screens because the service
behind them was not there: Dawarich, AirTrail, a document store, the
booking extractor. They are there now, so the guides are too.

- map-dawarich-trail, dawarich-countries and dawarich-wishes: the run
  uploads a recording of the seeded trip, and of the year before it, into
  a Dawarich of its own (dawarich-track.ts) and reads it back.
- airtrail-import: four flights are put into an AirTrail account, two of
  them connecting in Tokyo, so the picker offers the layover.
- import-booking-file and import-transport-file: the confirmations carry
  their facts as schema.org data the way portals send them, the extractor
  reads them, and the guides go on to the filled-in form and the card.
- files-sync and document-providers: a Nextcloud folder with two
  documents in it, bound to the trip, and the switches in the admin panel.
- read-place: three seeded places carry their OpenStreetMap id, so the
  card shows the week's hours, the phone number and the Open or Closed
  ring. The rating stays Google's, and the texts say so now.
- map-compass on the MapLibre renderer, which the guide switches to and
  back; files-preview opens a PDF, which needs the full browser rather
  than the headless shell (channel: chromium on the help-media project).

Each of those takes an address and a key from e2e/help/media.env, which
is not committed; media.env.example and the README say what each one
needs and why the address has to be the machine's own on the network.

The seed's dates are offsets from the day the pictures are taken
(e2e/dates.ts): the trip ends today, so the boarding pass, What's Next
and the forecast have something to show whenever the run happens, and
nobody bumps a date in three files every few weeks. run.mjs fixes the day
once per run so the projects and the workers agree across midnight.
2026-09-24 14:06:37 +02:00
Maurice b1528b63c4 docs(wiki): a link can be edited now
The Links section still described the panel as it was before #2414: pin
or delete, and add again to correct a typo. It names Edit link now, says
the form takes only http and https addresses and what the chip shows, and
it is a panel beside Notes rather than a tab, except on the phone.
2026-09-24 14:06:36 +02:00
Maurice 33b65ab8fa fix(i18n): two strings the locales had wrong
shared.other labels the same fallback group on the public share page that
the app itself calls Sonstiges and Otros, so de and es said one thing in
the trip and another beside it. admin.tabs.addons was Russian in uk.
2026-09-24 14:06:10 +02:00
Maurice b7dcb58a6b chore(i18n): drop the catalogue entries nothing renders
reservations.fromPlan, packing.suggestions, packing.suggestionsTitle,
packing.allSuggested, packing.emptyHint and the packing.suggestions.items
array are referenced by no component; the suggestions block left the
packing list in #14 and the keys stayed behind in all 23 languages. The
VORSCHLAEGE constant beside them was exported and imported nowhere.

With the one structured entry gone, a catalogue value is a string and
nothing else, so TranslationValue says so and the placeholder spec no
longer needs to skip what is not one.
2026-09-24 14:05:46 +02:00
Maurice 0f28c929be fix(files): the image lightbox renders through a portal
Rendered in place, its fixed overlay sat inside the trip page's stacking
context, below the navbar's z-[200]: the bar painted over the lightbox's
header, so Open in new tab, Download and the x were unreachable. The two
document previews beside it already portal into the body for this reason.

The tests read the lightbox through baseElement now, since a portal puts
it outside the container Testing Library returns.
2026-09-24 14:05:46 +02:00
Maurice e8637f7208 fix(help): the leg mode lands when the menu closes, not when one icon changes 2026-09-24 14:05:46 +02:00
Maurice dbc369beee fix(help): the kind disc answers to its own label, and so does the row around it 2026-09-24 14:05:46 +02:00
Maurice 168cd742a4 fix(help): three the third full run showed up
- The lightbox header's button group is the right thing to ring, but its centre
  falls between the buttons where the picture underneath takes the pointer, so
  the hover never landed. The pointer goes on a button, the ring stays on the
  group.
- The collection the place is saved into was matched by a name another guide's
  list starts with.
- help-media gets four minutes a guide instead of two and a half. A guide is a
  whole task and a few of them wait on the network more than once; the
  nearby-places one runs two POI searches and opens the place form on top.
2026-09-24 14:05:46 +02:00
Maurice 05b453b493 fix(help): the collection helper belongs beside the scripts, not inside them 2026-09-24 14:05:46 +02:00
Maurice 13017b505a fix(help): the undo guide needs the day it puts a place on to stay open
The + at the end of a place row belongs to the open day, and closing the day's
details panel closes the day with it, so there was no + to click and the guide
waited for one until the test gave up.
2026-09-24 14:05:45 +02:00
Maurice f6b2880abb fix(help): three more the whole suite showed up
- place-to-collection found the place already in a list, because the Collections
  screen's guides run earlier and leave it there. It clears the place first now,
  with the same helper its cleanup uses.
- The road trip rail leaves out a day with nothing to drive, which its own
  helper says, and the fold step was the last place still pointing at day 1.
- The transport form's location search took whatever the geocoder ranked first,
  and for a landmark that is as often a shop down the road. It picks the
  suggestion that answers the query.
2026-09-24 14:05:45 +02:00
Maurice 64b190862e fix(help): the locators the whole suite in one database showed up
Every one of these passed when its own file was run alone and failed when all
of them ran against one seeded trip, because another screen's fixture had put
something in the way.

- The Bookings fixture names a booking after a place, so the Assign File dialog
  offered two entries opening with the same words.
- The Lists tab has a Shared button, which the loose name of the navbar's Share
  also matches, so opening a trip on that tab never settled.
- A traveller's avatar is named by the initial it draws, not by the username;
  the title carries the name.
- Departure and Arrival are what a booking gets when it crosses days, so the
  guide about them points at the flight, not at the train that starts and ends
  on one day.
- The What's Next stop was timed from the wall clock of whoever runs this,
  while the browser's clock is pinned to the day the pictures are taken on.
  Both now come from the same constant.
- The map was panned with the arrow keys, which Leaflet only reads while its
  container has focus, and focusing it from here did not take. It is dragged
  now, from a point checked to be free of markers, because a press on a marker
  drags the place onto a day instead.
2026-09-24 14:05:45 +02:00
Maurice e9393e9e6c fix(help): a duplicated trip outlived its guide and broke another screen
The duplicate-trip guide dropped its copy by position, keeping whatever the
trips endpoint happened to answer first. The copy survived, and three files
later the journey dialog offered two entries starting "Weekend in Lisbon" and
the create-journey guide could not say which one it meant.

The copy is now dropped by name and the delete is checked, and the journey
dialog picks its trip by accessible name rather than by a substring another
trip shares. Each file passed on its own before this; only the whole suite in
one run showed it.
2026-09-24 14:05:45 +02:00
Maurice d2e352085d fix(help): the corrected sentences in the other languages too
Forty-five values were rewritten after a second pass over the running app found
the old text naming a control that is not there or describing one in the wrong
order. Each locale's existing translation was edited rather than replaced, so
the register and the vocabulary of the block it sits in are kept.
2026-09-24 14:05:45 +02:00
Maurice 821a103146 feat(help): translate the Road Trip block, and parity is green again
All ten screens of a trip are now in every language TREK speaks: 2134 help keys
in each of the 23 locales. i18n:parity:strict passes for the first time since
the help center landed, which also un-skips every CI job that was waiting
behind it.
2026-09-24 14:05:45 +02:00
Maurice 3c805e2c52 feat(help): translate the Costs and Transports blocks into all locales 2026-09-24 14:05:44 +02:00
Maurice e7db35b0a8 feat(help): translate the Lists and Bookings blocks into all locales 2026-09-24 14:05:44 +02:00
Maurice 1a5a8a9372 feat(help): translate the Collab block into all locales 2026-09-24 14:05:44 +02:00
Maurice 0ed0d98c32 feat(help): ten more screens of a trip, registered with their guide scripts
Place details, Day details, Map, Transports, Bookings, Lists, Costs, Files,
Collab and Road Trip: 77 guides over 360 steps, each one a task a reader can
follow, with the Playwright script that will photograph it. The fixtures they
need go in beside ensureDaysFixtures, and every screen is a child of the trip,
so the panel folds them under it.

The capture now pins the browser's clock to the day the seeded trip ends. A
picture has to look the same whoever takes it and whenever: a day later the
trip is over, My Trips moves it out of the boarding pass, What's Next has
nothing left to list, and a forecast for a day in the past is not a forecast.
Only `Date` is pinned, so timers keep running.

trip-days is green end to end after its locators were checked against the
running app; its route guides read day 6, whose two stops sit next to each
other, because day 1's are separated by the flight and have no leg of their own.
2026-09-24 14:05:44 +02:00
Maurice 30a7cfd483 feat(help): the English texts for the ten screens of a trip, and four of them translated
Place details, Day details, Map, Transports, Bookings, Lists, Costs, Files,
Collab and Road Trip: 77 guides and 360 steps, written against the running app
rather than against the components, so every label a guide names is a string the
reader will actually see. Places, Day details, Map and Files are translated into
the other 22 languages; the rest follow.

Also reworded the Settings overview: it called TREK self-hosted, which the
repository's own wording rule forbids and which nothing was running to catch,
because the test that does needs i18n parity and parity has been red.
2026-09-24 14:05:43 +02:00
Maurice cb6aeb7bfd test(members): stop the location stub from swallowing the request it waits for
Two cases replaced window.location wholesale to spy on reload. The copy is flat,
the real object's fields are getters on its prototype, so the replacement has no
href for a relative request URL to resolve against and the DELETE the leave-trip
case waits for never reached the mock server. It also stayed replaced for the
rest of the file, which took six later cases down with it.

The leave-trip case asserts the request, so it keeps the real location: jsdom's
reload only logs. The transfer case asserts the reload, so it keeps the stub,
and the original is put back after every test.

Nobody saw this: the client coverage job needs i18n parity, which has been red.
2026-09-24 14:05:43 +02:00
Maurice d3a9e00752 test(help): find the fold toggle inside its own row in the switcher
'2 sub-screens' stopped identifying one button the moment a second screen had
two of them, and the trip is about to have a dozen. The toggle is looked up
within the row it belongs to instead, which says what the assertion means.
2026-09-24 14:05:43 +02:00
Maurice 1f01447093 fix(collab): the expand button on a note showed its own translation key
`collab.notes.expand` was never added to the catalogue, and `t()` answers with
the key itself, so the `|| 'Expand'` fallback never ran and the tooltip read
collab.notes.expand. `common.expand` says the same thing and is already
translated everywhere, so the button uses that, and it now carries the label
for assistive tech as well.
2026-09-24 14:05:43 +02:00
Maurice 1e9b23c5dd fix(help): the corrected places texts in the other languages too 2026-09-24 14:05:43 +02:00
Maurice 72b32c6a12 docs(wiki): a page for the help center
What the ? in the navbar opens, what an overview and a guide contain, how the
search and the screen switcher work, where the pictures come from and what
happens on a screen that has no help yet. Linked from the sidebar, from the
features list and from In-App-Help, which documents the other thing and was
being confused with it.
2026-09-24 14:05:42 +02:00
Maurice 431eb883c9 feat(help): pictures and translations for the trip's places column
All eight guides of Trip > Places now have their pictures, taken from the real
app, plus the hero of the screen, and the block is translated into the other
22 languages.

Making the pictures showed three texts describing a UI that is not there, and
they are corrected: the file import dialog has no Enrich places via Google
switch (only the list import has one, and only with a Google key), a GPX import
closes with a message while only KML and KMZ leave a summary behind, and both
list providers are always offered. The search and the details column need no
Google key either: they fill themselves from the TREK index, OpenStreetMap,
Wikipedia, Wikivoyage and Wikimedia, and only ratings are Google's.

The script needed the same honesty. ConfirmDialog and the two import dialogs
are portals of their own with none of Modal's classes, so they get their own
locators, and create-place now looks up a place whose entry actually carries a
picture, opening hours and a description, instead of photographing an empty
details column.
2026-09-24 14:05:42 +02:00
Maurice 5584d287a9 refactor(help): register a screen from one description, and fix the doc links
Every context file carried its own copy of the same little guide factory and
the same context literal. Thirteen screens of a trip would have made thirteen
copies of it, which is both the duplication a quality gate counts and the
hand-mirrored state the repository asks us not to keep, so the shape moves into
defineScreen and a context file becomes data: ids, counts, docs, cross-links.

Four doc links never resolved. Trip-Planner and Sharing-and-Collaboration are
not wiki pages (Trip-Planner-Overview and Trip-Members-and-Sharing are), the
places import anchor is importing-multiple-places, and the offline anchor was
written in GitHub's slug, which is not the one the in-app reader produces.
Nothing caught them, so the registry test now checks every slug and every
anchor against wiki/, with the heading slug itself lifted out of HelpPage so
the reader and the test cannot drift apart.
2026-09-24 14:05:42 +02:00
Maurice cd0a80e70d feat(help): translate the trip block into all locales 2026-09-24 14:05:42 +02:00
Maurice 4346d7f2c1 feat(help): in-app help center with per-screen guides and pictures
A help icon on every screen opens a panel with the screen's overview, its
sub-screens, task guides with a framed picture per step, search across guides
and the wiki, and a switch to any other screen's help. Guides exist for the
dashboard, Vacay, Atlas, Collections, Journey (list, journal, studio), Settings
(all tabs), Admin (all tabs) and the trip frame; the trip's own screens are
in progress. Pictures are produced by Playwright (client/e2e/help), texts in
all 23 languages.
2026-09-24 14:05:42 +02:00
github-actions[bot] 151e32107f chore: bump version to 4.3.2 [skip ci] v4.3.2 2026-09-24 11:14:02 +00:00
Maurice 8779529efb feat(roadtrip): the corridor asks in one line, and a narrow rail stops counting stops
Along the route: once kinds are picked, the closed "Looking for" control shows them by
their coloured icons alone, and names them in its tooltip and to a screen reader; the
names stay in the list. The day the search runs along moved out of the header into the
same line, as a dropdown of the same shape, so the question reads as one: these kinds,
along this day. Both share one small module for the shape and for closing on a click
outside or Escape.

Pulled narrow, the road trip rail drops the stop count from its head, with the hairline
beside it, and from every day header, and keeps distance and time.
2026-09-24 11:32:52 +02:00
Maurice e1315806c2 feat(roadtrip): the ride block and the stay rows read as badges
The flight block in the road trip rail printed each fact two or three times: the booking
title in capitals with its duration cut off, the airport code three times per side, an
unlabelled 14:15 on the right with "Departure 15:15" under it, 17:20 twice, and a warning
"+9 h 39 min" measured against a check-in nobody could see, for a flight that was missed.

It now reads in four lines: the booking as typed; the two codes with their timetable clocks
and the ride between them; the places without the code in brackets; and the check-in as a
badge that carries its own state, the hour when the drive is on time, by how much when it
is late for the check-in, and "Missed, there at 23:54" when it gets there after the
departure, with the duration beside it. A missed ride edges the block and says so in the
day header, also when the day is folded. Trains and buses say boarding. The phone shows
the same, and its rows now read the lateness and missed-leave findings at all.

The stays at the edge of a day are named by the hotel, with the edge in a badge: Check-out
with the latest hour, Day start, Overnight, or Check-in with the hour the room is ready. A
plan that leaves after the check-out turns that badge instead of adding a line. They wear
the hotel stop's own disc, the stay's own stop shows its check-in the same way, and the
driving settings button shows when the stays are switched on.

The two-part badge the rail used in six hand-typed copies is one FigureBadge now, and the
airport code is stripped by one shared helper instead of two private copies.
2026-09-24 11:32:52 +02:00
Maurice e64ade010f fix(roadtrip): a flight day ends at the stay it lands at, and no drive runs where nobody drives
On a day a flight, train, ferry, cruise or bus leaves and lands, tonight's stay was seated
by its check-in time. A hotel in Munich checked into from 15:00 stood ahead of a 15:15 flight
out of Hamburg, so the drive went from the Munich hotel to Hamburg airport and reported the
flight as nine hours late. A check-in is the earliest arrival, not an appointment: such a
stay now waits behind the landing when it lies nearer the arrival than the departure, and
its check-in only holds it when the ride lands before the room is ready. A time typed on the
stop keeps its seat.

With the stays at both ends of a day switched on:
- a day with nothing but the next stay's check-in and a ride booked across it gets no drive
  from one hotel to the other, the same rule the day plan follows since #2476;
- a stay is never joined by road to a terminal more than 2000 km away. The check moved to
  shared/roadtrip so Days and the road trip read one copy.

A departure terminal's pin is dated: reaching it more than half a day late no longer moves
the flight to the next day without a word, it is a late warning with the full minutes.
A check-in evening also carries the stay's check-in time, as a label only.

calculate_roadtrip runs the same code and its description says all of the above.
2026-09-24 11:32:52 +02:00
Maurice 3062df153f chore(notices): the release notice points at 4.3.2 2026-09-24 11:27:12 +02:00
Maurice 233076030b fix(docsync): a store picked before the panel settles stays picked
The effect that keeps the selection on a bound store read the pick from
its own render. When a click landed after the card was committed but
before that effect ran, the effect then wrote the first store back over
the click. On a loaded CI runner this is what made FE-DOCSYNC-PANEL-008
fail now and then. The effect now reads the current pick when the update
runs and only follows the list.

FE-DOCSYNC-PANEL-023 forces that order and fails on the old effect.
2026-09-24 08:14:29 +02:00
Maurice c5ac479915 fix(dayplan): sort the trip's dates with an explicit compare
ISO dates sort the same as plain strings, but a sort without a compare
function reads as an accident to anyone checking it, and SonarCloud flags
it as one. The three places that order the trip's dates now say how.
2026-09-23 23:11:10 +02:00