Commit Graph

2323 Commits

Author SHA1 Message Date
Maurice 91bc1944dc Managed mode: keep the admin settings and addons ratchets green
- The phone's OIDC card moves into MAdminOidcCard, so the settings section
  is back within its complexity limit after the managed-mode conditions.
- enabledPhotoProviderIds reads the enabled ids straight from the
  repository and has a test of its own, which brings the addons coverage
  back to its threshold.
2026-11-03 08:44:45 +01:00
Maurice f99b40cd81 Managed mode: keep the journey page suite within its size baseline
- The owner sign-in and the photo provider the suite runs with move into
  tests/helpers/journeyDetail.ts, so the page test is back under its line
  baseline, which drops to 4028.
- Drop help/registry.base.ts, an unused copy of the help registry whose key
  builders the i18n key check could not resolve.
2026-11-03 08:44:45 +01:00
Maurice 59e68d81b2 Managed mode: satisfy the strict, loop and test ratchets
- enabledPhotoProviderIds skips rows without an id instead of widening the type.
- The listed events read each addon once, in parallel, not in a loop.
- The narrowing test reuses the suite's service and swaps the addon answers.
- PlaceContribPanel keeps its complexity by naming the standalone check.
2026-11-03 08:44:45 +01:00
Maurice 86513fda0d Managed mode: hand place search, routing, plugins and docs to the operator
On a managed install the operator runs place search and routing, the plugin
runtime, the integrations and the documentation, so the admin and the users
no longer see switches and guides for them:

- The place search and routing card, the transit provider and the search log
  are gone from the admin settings (desktop and mobile). places_provider is a
  managed-locked setting, and the transit provider, search log and wanderer
  routes refuse writes there. Share place details stays the admin's, as a card
  of its own, hidden when TREK_PLACES_CONTRIBUTE switches it off.
- File types and trip reminders move to the right column, so the columns stay
  balanced.
- Plugins disappear from the admin and the user settings, wanderer from Tours.
- The Help & Docs page and its links are not offered. The help center stays and
  leaves out guides about what the operator runs (keys, OIDC, backups, storage,
  releases, plugins, document sync) and about integrations that are off.
- Mapbox GL is offered only with the operator's token, Amap tiles only where
  the operator set Amap up.
- The mobile admin no longer shows OIDC and the WebAuthn relying party fields,
  matching the desktop tab.

For every install:

- Notification matrices list only events the install can raise: no plugin row
  without the plugin runtime, no Vacay, collection, photo, Synology, wanderer or
  packing rows while those are off. On a managed install the release and
  replica alerts are left out.
- The journey entry editor offers External photos only where a photo provider
  is switched on, as the phone already did.
- app-config is asked again after a failed answer. A page opened while the
  server restarted kept every default for the session, which on a managed
  install brought back the operator's settings.
2026-11-03 08:44:45 +01:00
Maurice 61378559ca chore(deps): runtime security floors and the CI action updates
- sharp 0.35.5 (librsvg CVE), with the musl pins that ship in the image.
- follow-redirects 1.16.2, postcss-selector-parser 6.1.5 and
  serialize-javascript 7.1.3, from the open advisories.
- GitHub Actions: github-script 9 and the minor-and-patch group.
- plugin-sdk: @types/update-notifier 6.0.9.

The grouped npm update is left out: with it the server build runs out of
memory in tsc, and the culprit is not found yet.
2026-11-02 21:58:16 +01:00
Maurice 84df69c18a Revert "chore(deps): take the safe updates from the open Dependabot PRs"
The server build runs out of memory in tsc with the grouped updates. Back to
the previous dependencies until the update that causes it is found.
2026-11-02 21:56:33 +01:00
Maurice 6194a658d9 chore(deps): take the safe updates from the open Dependabot PRs
- The npm minor-and-patch group (#2952) without mapbox-gl: 3.34 drops
  setRTLTextPlugin from its typings, and the RTL plugin is what joins Arabic,
  Hebrew and Persian labels. react and react-dom move to 19.3.1 together,
  overrides included, so the workspace keeps one React.
- Security floors from the open alerts: sharp 0.35.5, vite 8.3.5,
  follow-redirects 1.16.2 (#2927), postcss-selector-parser 6.1.5 (#2865) and
  serialize-javascript 7.1.3.
- GitHub Actions: github-script 9 (#2951) and the minor-and-patch group (#2950).
- plugin-sdk: @types/update-notifier 6.0.9 (#2948).
2026-11-02 21:30:17 +01:00
Maurice 247d5fc519 feat(services): get the key at startup and share earlier place details
An installation now asks for its TREK services key a few minutes after
it starts, and the daily check registers a missing key as well as
renewing a due one. Until now nothing happened before someone searched
for a place or opened the admin settings, so "Share place details"
stayed unavailable on a fresh or updated instance.

When an admin agrees to share place details, what users typed into
places since TREK 5.1.0 goes out too, not only what they type from then
on. The origin of every field is recorded since 5.1.0 whatever the
decision, so a catch-up walks those places page by page before each
send run: only values that are still the ones a user typed, only for
fields switched on, only for places the service can key, and only
while the queue holds fewer than 200 values. It goes on where it
stopped after a restart, never replaces a newer value a save queued,
and ends on switching off, declining or withdrawing everything. A field
switched on later catches up as well, under an agreement given with the
new prompt sentence that says so; an agreement from 5.1.0 does not.

Queuing moves into PlaceContribQueue so a save and the catch-up take
the same path.
2026-11-02 13:56:32 +01:00
Raz Erlich 94c2ef35b5 fix(planner): keep places selection bar visible with long lists 2026-11-01 22:08:45 +01:00
Maurice bf8e04741d test(storage): follow the release queue in the tests that pinned an immediate delete
Replacing a cover or an avatar and deleting a note file now hand the old
bytes to the release queue instead of deleting them in the request; the
integration tests check the queue row and that the bytes are still
there. Release mocks answer with a count, the reclaim e2e sends its
refused bodies one after another, and a stale-count reload that fails is
told to the admin instead of swallowed.
2026-11-01 18:46:58 +01:00
Maurice 9d2ffc91af feat(storage): confirm the count shown, and say a backup or a missing install id held the cleanup
Confirm sends the count the admin saw and waits for its answer; a stale
count reloads the state. A running backup is named as a skip reason, a
missing installation id and a stale count as errors, and the switch's
hint no longer promises removal right away: deleted files go a day
later, once no undo can bring them back. Strings in all 28 languages.
2026-11-01 18:14:49 +01:00
Maurice 59a6d92cea fix(storage): type the key search run, split the reclaim view per category
The longest-run helper typed its reduce as never, reclaimView grew past
the complexity limit (one function per category line now), and a test
read a promise as a boolean.
2026-11-01 16:37:00 +01:00
Maurice e0982e708f feat(storage): show running checks, confirmations and the nightly result in Admin > Storage
The panels follow the reworked reclaim state: Check now starts a check
in the background and the panel polls until it is done, a category with
an unusually large share of unused files offers Confirm, Freed comes
from the last run that deleted, a stopped check says why and no longer
reads as nothing found, the switch waits for its answer and a poll that
was in flight cannot flip it back, and refusals name their reason in
the operator's language. Strings updated in all 28 languages.
2026-11-01 16:25:38 +01:00
Maurice 382797febc fix(storage): let the app boot with the reclaim routes and keep the ratchets where they were
The two admin routes go on the managed allow list, without which buildApp
refused to start. The reclaim calls move to their own api module so
client.ts stays at its size, the phone card sizes its text through the
type tiers, and the tests drop a hand-built service, a non-null assertion
and a promise read as a boolean.
2026-11-01 14:44:53 +01:00
Maurice a1d9cdc8f2 feat(storage): show unreferenced files in Admin > Storage, with the switch and a check
Desktop and phone show what the last check found (can be freed, unused for
less than a week, freed), why a category was skipped, the switch that lets
the nightly run delete and a report-only check on demand, through the
shared storage panel hook. All 28 languages, and the Admin-Storage wiki
page explains the check, the week's wait and when to leave it off.
2026-11-01 14:30:01 +01:00
Maurice 8fde643347 fix(storage): make the reclaim state on the admin storage state nullable
The shared contract made reclaim a required field, so every client fixture
typed StorageAdminState stopped compiling. It is nullable now, as planned,
and the five base fixtures carry reclaim: null.
2026-11-01 14:20:11 +01:00
Maurice 04debce79e fix(planner): keep the lint and size ratchets where they were
The zone helpers of the day order take the merged row's own type instead
of an explicit any, and the day list's size baseline follows it down.
2026-11-01 11:41:56 +01:00
Maurice 4003769a5c fix(planner): keep the date whole and rename the title where it stands with the date first
With "date first in day headings" a long day title squeezed the date in
the day list to "Th…" (#2920); the name now gives way and the date stays
whole. The day panel and the mobile day sheet kept the rename pencil on
the heading, which then is the date, and the input replaced it (#2921);
pencil and input now go with the title line under the date.
2026-11-01 11:41:56 +01:00
Maurice 8692b8790c fix(planner): judge a move by the order a day is drawn in across time zones
The checks behind a drag and the up/down buttons read each booking's raw
clock, so on a day whose flights were put in the order they happen every
move of a timed stop asked to change its time (desktop) or was refused
(mobile). They now read the same key the day is ordered by.
2026-11-01 11:41:56 +01:00
Maurice 1b6a156969 fix(planner): order flights across time zones by when they actually leave
A booking's time is local to its own airport, so a flight leaving
Auckland at 20:20 and landing in Los Angeles at 12:25 the same date
was drawn below the 17:00 flight on from Los Angeles. Bookings whose
dated time has a known zone now take their clock slots in real order.
2026-11-01 11:41:55 +01:00
Maurice cafc8dd46e fix: link dataforest cloud with /en again
The 5.1.0 popup, the sponsor panel in Settings > About and the README had
switched to https://cloud.dataforest.net/?mtm_campaign=trek. The link stays
https://cloud.dataforest.net/en?mtm_campaign=trek everywhere.
2026-10-31 12:26:36 +01:00
github-actions[bot] dc36c21c08 chore: bump version to 5.1.0 [skip ci] 2026-10-31 11:12:52 +00:00
Maurice 75d65ccb1b test(release-notes): build the new cases without non-null assertions
The four cases added with the 5.1.0 popup patched the fixture through
n.release! and read the support card through card!, six warnings over the
tests baseline of the eslint ratchet. They go through a withRelease()
helper and optional chaining instead.
2026-10-31 11:31:13 +01:00
Maurice def8b8a24d release notes: the 5.1.0 popup, and dataforest cloud in Settings > About
The release popup now shows 5.1.0: place search, shared place details
and faster routing as three cards side by side, each with its own
drawing. The support band is set as a card like the promise above it
(optional supportLabelKey; a release without it keeps the plain line),
and the two support buttons get the same glass look in their colours.

Settings > About gets a full-width panel for dataforest cloud, who
sponsor the servers behind place search, address search and routing.
Both logos ship in client/public/brands, so opening Settings reaches no
third party. Wherever a line names dataforest cloud it links to
https://cloud.dataforest.net/?mtm_campaign=trek, the link dataforest
gave us; the README moves to it too.

All 28 locales carry the new copy, and the old 5.0 card keys are gone.
2026-10-31 11:04:19 +01:00
Maurice dc76efc418 admin: a withdrawal that went through is reported even when the state re-read fails 2026-10-31 03:25:56 +00:00
Maurice 314b087f98 planner: the refresh pill compares with the place picked anew in the search box
After another place was picked while editing, the pill still offered the
saved place's details and would have written them onto the new one.
2026-10-31 03:25:56 +00:00
Maurice c10a698b9f planner: keep a place's enrichment in the tab for ten minutes, not for good
A saved place reopened for editing has no community version in its key, so
the answer the tab kept the first time stayed until the tab closed: a picture
another installation contributed since never showed. Kept answers now carry
when they were fetched and are asked again after ten minutes, the same span
the server keeps an empty answer. The key version moves to 5 so open tabs drop
what they kept in the old shape.
2026-10-31 03:25:56 +00:00
Maurice 330644e88b places: show the community's published pictures first in Pick a picture
The service delivers the pictures review approved for a place, but TREK never
read them. A new TrekCommunityService in the maps domain asks the TREK Places
API for them (the index row for a gers place, /v1/community for an OSM one),
accepts only paths of the service's picture store, and fetches the bytes with
the installation's key, capped at 4 MB. Enrichment puts them ahead of Google
and Commons, through the same photo cache, credited as TREK Community under
CC BY 4.0. The enrichment cache now follows the community version for OSM
places too, so a newly approved picture shows on the next open.

Also lowers the client's no-non-null-assertion baseline to 380: moving Use
this text into the description card removed two.
2026-10-31 03:25:56 +00:00
Maurice 0b86a13b51 admin: fold the transmission log to its newest five rows
A busy log pushed the rest of the card down. It shows the newest five rows,
a Show more toggle opens the rest, and the page buttons appear once it is
open.
2026-10-31 03:25:56 +00:00
Maurice 8d8629516b places: offer a newer description in the refresh pill
The pill compared website, phone, e-mail and hours only, so a description
the community published never showed up on a saved place. It now reads the
index description or the OSM summary, compares it as plain words against
the editor's markup, and shows it clamped to three lines.
2026-10-31 03:25:56 +00:00
Maurice 5822910f40 places: put Use this text right under the description it copies
The button was pinned to the foot of the details column, away from the text
it adopts. It now sits inside the description card, under the source line,
with the reason it is blocked directly beneath it.
2026-10-31 03:25:56 +00:00
Maurice ce5ec7132f places: a description the community shared is credited to the TREK community
A community description laid over OSM details or an index row showed up as
OpenStreetMap under ODbL, or as the place's own website. It now has a source of
its own, community, credited as TREK Community under CC BY 4.0, like the
service publishes it.
2026-10-31 03:25:56 +00:00
Maurice 7f2a3b6039 planner: keep REFRESH_FIELDS inside its module; docker: leave shared/node_modules out of the build context
A checkout where npm install ran once carried shared/node_modules into the
image build, and its stale packages broke the client build with a vite that
lacked vite/internal.
2026-10-31 03:25:56 +00:00
Maurice c28604b891 planner: the details refresh reads through a repository, and the form drops what moved out
The fresh lookup behind New place details goes through placeDetailsRepo
(online-only, like the service identity) instead of api/ directly. The form
loses the Translate type and the comment that moved with the head band's
pills, which takes it under its line limit and out of the size baseline; the
maps service suite's test-size entry follows its shorter count.
2026-10-31 03:25:56 +00:00
Maurice aba329b1b1 planner: a saved place offers newer details from its provider, field by field
Editing a saved place looks it up again. Where the lookup has a website,
phone, e-mail or opening hours that differ from the place, a New place details
pill in the form's head band shows how many, and opens what the place holds
beside what the lookup has. Empty fields are ticked, filled ones are left to
the person and say they would replace; Take over writes only the ticked
fields, with the provider as their origin, and nothing the lookup does not
know is ever emptied.

The head band's pills move to PlaceFormHeaderPills.tsx, which brings the form
from 1157 to 1002 lines.
2026-10-31 03:25:56 +00:00
Maurice a1ca1aae44 admin: place search and routing get a card of their own, the keys keep theirs
The TREK API, the transit provider, the place search provider and the search
log move out of API Keys into a card of their own, which keeps them together
and saves as they change. API Keys holds only Google Maps, Unsplash and Amap,
and is not shown on a managed install, where the keys are the operator's.
Allowed File Types moves to the left column, which had room for it.
2026-10-31 03:25:55 +00:00
Maurice 56536ae803 admin: the allowance and the sharing blocks take the settings' own card head
Both blocks in the TREK API card now use SettingsCard like Single Sign-On and
two-factor: an icon tile, the title with its state as a badge, the note under
it and the switch or badges on the right, with a flush variant for a card
inside a card. A figure badge no longer cuts the last pixels of its value, and
the withdrawal toast names how many values it took back.
2026-10-31 03:25:55 +00:00
Maurice b64f4535fd admin: set the service card's state and its one action apart from the figures
The state and its badges sit in a head band, the new-identity button in a foot
band, each with a rule and the card's own surface, so neither reads as part of
the allowance tiles between them.
2026-10-31 03:25:55 +00:00
Maurice 71cd6c2de4 place-contrib: record origins for collection copies and booking imports
A place copied from a collection and a venue a booking import creates now
record their details as an import, so a later edit compares against what was
really written. Dawarich and the background enrichment write none of the
shareable fields, so they need nothing.

The admin panel asks again for a few seconds while a fresh installation is
still fetching its key, so the prompt appears without a reload.
2026-10-31 03:25:55 +00:00
Maurice 36c1599a09 place-contrib: settle the second CI round
- The plugin RPC fallback tests build PlacesRpc with its origin bookkeeping.
- The e2e guard and contract cases send their requests one after another; the
  test server reset connections opened all at once.
- The job's two cron registrations are tagged @txIndependent: neither writes.
- The panel draws its fold head itself instead of importing it from pages/.
2026-10-31 03:25:55 +00:00
Maurice 65abea6910 place-contrib: settle the first CI round
- The pause setting's type comes from its schema, so nullable fields read the
  same under the server's compiler settings.
- The place form passes the origin of a field through to the hook.
- Serial steps run through inOrder (src/nest/common/in-order.ts) and
  independent ones through Promise.all, instead of an await inside a loop. The
  bulk update in places.mcp.ts goes the same way, so its baseline drops by one.
- The status a call without a key gets is a named constant, not an envelope
  literal.
- A picture test no longer inherits the previous test's mock answer.
- The entity counts include the three new tables.
- The log keeps its failure per query instead of resetting it in the effect,
  and usePlaceForm sets its picture through one helper.
2026-10-31 03:25:55 +00:00
Maurice 013aa3954d admin: ask once, then show and steer what is shared of place details
The first time an admin opens the settings on an installation that can share,
a dialog asks for the whole installation, with every field switched on and a
plain list of what never goes along. Nothing is sent before Agree; Not now
asks again in 14 days, No keeps the question away until the terms change, and
closing it decides nothing.

Inside the TREK Places API card, Share place details then holds the switch for
the installation and one per field, the figures of the last 30 days, the
transmission log and the way to withdraw everything sent. The place form
reports for each field whether the user typed it or a provider filled it.
2026-10-31 03:25:55 +00:00
Vini 7277e35a96 serve each country's regions from its own file, so the server no longer holds them all
The Atlas server no longer keeps the admin-1 regions of every country in memory. Each country's regions are now a pre-gzipped file of their own, sent to the map as they are.

**Why:** `GET /addons/atlas/regions/geo?countries=…` built `admin1Store` (every country's region text, kept until restart), then joined the requested countries, `JSON.parse`d them, and Nest stringified them again. On the 512 MB – 1 GB boxes TREK runs on, the first z≥5 view could OOM the server (same shape as #1576).

**What changes:**
- `server/assets/atlas/admin1/<ISO_A2>.geojson.gz`: 198 files, 14.85 MB, the same 3,447 features as `admin1.geojson.gz`.
- New `GET /addons/atlas/regions/geo/:code` with a Zod contract (`regionGeoParamsSchema` in `@trek/shared`: two letters, any case). It sends the country's file as it is, with `Content-Encoding: gzip` and the current 1-day `Cache-Control`, as `countries/geo` does. A country without regions gets an empty FeatureCollection. Anything that is not two letters gets a 400.
- `useAtlas.ts` fetches one URL per uncached country and still rebuilds the layer once. Each country has a stable URL, so a country evicted by `REGION_CACHE_MAX` comes back from the browser cache.
- `getRegionFeatures` reads only the country it needs. `admin1Store` is removed.
- `?countries=` keeps working for cached PWA clients. It joins the per-country files as bytes, without a parse.
- The migrations are untouched. `admin1.geojson.gz` still ships, only for `Migration20200101021500` and `Migration20200101031500`. This costs ~15 MB of disk in the image and no RAM. `build-atlas-geo.mjs` writes both, and ATLAS-BUNDLE-008 keeps them equal.

**Memory** (real `atlas-geo` module, first z≥5 view over Europe = 49 country codes, JS heap + Buffers):

| | peak | kept after |
|---|---|---|
| before, first view (builds the store) | +372 MB | +61 MB |
| before, later views | +115 MB | 0 |
| after, `/regions/geo/:code` | +4.7 MB | 0 |
| after, legacy `?countries=` | +59 MB | 0 |

**Tests:**
- New: `atlas-region-files.test.ts` (moved from `atlas.service.test.ts`, plus join and dedupe cases), the `regionGeoParamsSchema` spec, ATLAS-BUNDLE-007 (the file format the byte join depends on), ATLAS-BUNDLE-008 (bundle and per-country files are equal), ATLAS-016/017/018 (the new route over HTTP, including the 400).
- The `getVisitedRegions` tests (ATLAS-UNIT-019..024) used to pass only when `getRegionGeo`'s `beforeAll` had built the store first. They now also pass on their own.
- The shared, server and client gates pass. Baselines go down only where this change removed code (`no-console`, `no-explicit-any`, `no-swallowed-catch`, file sizes).

**Trade-offs:**
- A first zoom over Europe sends ~49 small parallel requests instead of 1. Over HTTP/1.1 the browser queues them 6 at a time. The total bytes are the same (4.4 MB gz).
- The per-country URLs are unversioned and cached for a day, like `countries/geo`. After an upgrade that changes a country's regions, a browser can show the old shapes for up to 24 h.

Approved on Discord in #github-pr: <paste the thread link here>
Related: #1576, #2559

- [x] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

- [ ] I have read the [Contributing Guidelines](https://github.com/liketrek/TREK/wiki/Contributing)
- [ ] My branch is [up to date with `dev`](https://github.com/liketrek/TREK/wiki/Development-environment#3-keep-your-fork-up-to-date)
- [x] This PR targets the `dev` branch, not `main` *(wiki-only PRs are exempt)*
- [x] I have tested my changes locally
- [x] I have added/updated tests that prove my fix is effective or that my feature works
- [x] I have updated documentation if needed
2026-10-31 00:40:23 +00:00
Maurice 70f27085b9 feat(i18n): bring the Hebrew locale up to date with dev and list it in the docs
Translate the 55 keys dev added since the branch was cut (TREK services
allowance, Valhalla routing service, PDF routing progress, overview
distances, Unsplash key hint, tour day limit) and drop the two reservation
toasts dev removed, so he matches en key for key again. Refresh the two
admin strings and the Valhalla hint whose English changed.

Check he in the placeholder spec and the RTL test, and count 28 languages
with Hebrew in the README and the Languages, Environment Variables,
Contributing and Help Center wiki pages.
2026-10-31 00:40:23 +00:00
Dolev Benezri 156dc4a6c2 feat(i18n): complete Hebrew locale for dev
Fill in every domain added since the first Hebrew pass (help, tours,
assistant*, studio*, wanderer*, journey, demo, ferratas and the rest),
retranslate strings whose English changed, and give every count group
its Hebrew one/two forms. Register he as a non-Latin locale for the
untranslated ratchet, add the Hebrew 429 message, and count 28 locales
in the tours locale spec.
2026-10-31 00:40:23 +00:00
Dolev Benezri d159aec78b feat(i18n): add Hebrew (he) locale with RTL support
Full Hebrew translation of every UI domain plus email/notification
templates. Registered in SUPPORTED_LANGUAGES, marked right-to-left,
wired into the client locale loader and the rate-limit message table.
2026-10-31 00:40:23 +00:00
Maurice e5048643f8 test(assistant): pin the clock in FE-AI-PICK-004
Trip 7 ran until 2026-10-30, so from 2026-10-31 on the hook ranked it as
past and the test failed. Pin the date to the file's TODAY.
2026-10-31 00:39:38 +00:00
Raz Erlich 209f8fe5cc feat(transit): fall back between configured public transit providers 2026-10-28 08:01:53 +02:00
Maurice b953ece4b3 fix(journey): stop the entry views from shifting while photos and providers load
Opening an entry on the share page showed the hero at no height until the
original arrived, then pushed the story down, and the lightbox stayed black
until the full photo had loaded. The hero now reserves the photo's proportions
(4:3 when none were recorded) with its thumbnail behind it, and the lightbox
shows the thumbnail and a spinner until the original is in.

The phone entry sheet probed the photo providers again on every open and added
the External photos button once the answer came, squeezing the icons of the
other two buttons. The journey screen already knows the connected providers, so
it hands them to the sheet, and the icons keep their size.

The picked neutral mood was a grey tint on a grey sheet; a picked mood is now
framed in its full colour and announced as pressed.

Closes #2907
Closes #2908
Closes #2905
2026-10-30 21:15:38 +01:00
Maurice 94a9cbf070 fix(journey): draw the live Dawarich trail in the colour of its day
The live trail arrived in Dawarich's own day palette, counted from the first
recorded day, while a journey colours its days by the entries. A day's line and
its pins therefore wore different colours. The trail of a day that has entries
now takes that day's colour, the way the kept recordings already did.

Closes #2906
2026-10-30 21:15:37 +01:00