Zod schemas for what the key issuer answers (challenge, key grant, quota) and for GET /api/admin/service-identity. The card no longer claims there is no quota: every locale now says there is no key to enter, a fair daily allowance per installation, and that only the number of requests is counted.
Without an Access Key the image search goes through Unsplash's web endpoint,
which refuses datacenter addresses with a 401 or 403 or answers with a
challenge page. The search then said only "Unsplash search unavailable",
although an admin can already enter a free key in the settings. Such a
refusal now answers 502 with that hint and the code unsplash_key_needed, and
the cover search shows it in the user's language.
Closes#1449
A tour whose walking time and breaks came to more than 24 hours a stage
only greyed out Save, with nothing saying why. The planner now marks the
planned total and says what to do: make it a multi-day tour by ending a
stage at a stop, or enter a shorter planned total. The details section
opens by itself so the reason is in sight.
Closes#2869
A book held at most 150 spreads. The auto layout cut everything past that
without a word, so a long journey's book ended about three quarters of the
way through, with its last page and back cover gone, and adding, copying or
applying a designed page to a full book was refused.
The spread cap is gone from the book schema, the page operations, the
design composer and its apply, the assistant's book card and the design tool
contracts. A design's own page budget (pages.max) still bounds what it lays
out. The save route's 8 MB body still bounds a book's size, which at two to
five kilobytes an auto laid page is a few thousand pages.
The templated design digests move because their counter ids shift by two;
the pages, their order and their elements are unchanged.
Once a store was connected for a trip, its address and login could only be
entered again after a sync run had been refused, and the stored connection
could not be removed at all: the server's DELETE route had no way in from the
app. A binding's settings now open the connection at any time, the folder
picker leads back to it before a folder is chosen, and the connection form
removes a stored connection after a confirmation, on the desktop panel and the
phone sheet alike. Removing it drops the trip's bindings through it; documents
stay on both sides.
Closes#2842
create_day_note and update_day_note described `time` as a clock label and capped
it at 250 characters, while the planner shows it as the Markdown body under the
title and allows 2000. The assistant put long text in the title and had to cut
notes it was asked to edit. Both tools now take text and time from the REST
contract with descriptions naming title and body, and an update still refuses an
empty title. The assistant's change card labels the two fields title and content
instead of text and time.
Closes#2840
Auto layout gathers journal entries with no photo and no text, one after the
other, onto one list page instead of giving each an empty page. Its heading
said "Stations" (German "Stationen"), which read as train stations and gave
no hint why those places were on it. It now says "Along the way" in every
language; each stop still carries its date.
A GPX track imported in 4.x is a plain place: a line, no tours row. It never
showed under Tours, and importing the same file there was refused as a
duplicate by its name, so there was no way to turn it into a tour (#2810).
The tour import now takes over a track of the trip that has the same name
and is no tour yet: the place keeps its name and line and gets the tours
row, with the recorded times the file adds. The preview says so instead of
"Already in the trip". New and taken-over tracks commit in one transaction.
Connecting an own server asked Ollama's /api/tags and gave up on any answer
that was not a 404, and every failure, a refused key included, read as "not
responding". The model list is now looked for at /api/tags, at the address as
entered and at /v1/models, an answer only counts when it is a list, and a
refused key or a server that answered without a list say so (#2808).
AI parsing through an own server always called Ollama's /api/chat, which an
OpenAI-compatible server such as Groq answers with 404. A server without it
now gets the same extraction over /chat/completions at the address as
entered (#2806).
TREK 5.0 left most registry plugins blocked because their authors still declare
"<5.0.0", and the only way past that was TREK_PLUGINS_IGNORE_TREK_RANGE for every
plugin at once. An admin can now install or switch on one such plugin after a
warning; the consent is kept on its row for that plugin version and TREK major,
so an update or a new major asks again. The environment switch works as before.
Discover marks a plugin not confirmed for this TREK on its card, and Install opens
the warning first. Admin > Plugins reads cleaner: one row of alike facts per plugin
with each state that needs attention on its own line, type, status and order behind
one Filter button, and the containment notes in a dialog behind the shield in the
header. The row, the filter menu and the dialog moved into their own files.
An imported hotel was looked up once, by its name and its address together.
A booking site's romanized address (Agoda for a stay in Japan) often matches
nothing and sank the hotel's name with it, so the place was created without
coordinates and silently never showed on the map.
The review now finds the stay right away (name and address, then the name,
then the address) and says under the address whether it is on the map and
where, or that it is not yet, with a way to search again. Picking an address
suggestion sets the spot by hand. The save uses that spot, falls back the same
way when there is none, and a stay saved without a spot says so in a toast.
Desktop dialog and phone sheet share the lookup through useReservationForm.
A token whose account has no local ActivityPub author was reported as
invalid_response, which the settings show as 'The answer did not come from
wanderer', although wanderer had answered fine. It now has its own code,
no_author, with a message in every language that says what is missing.
The overview of a trip's tours decides for itself when it shows, which keeps the planner page hook inside its complexity and length limits, and adds nothing to the camera's key while there is no tour to frame. The elevation helper names its empty case for the strict type check, the self-host wording exemption for the retired Dawarich card goes, the day plan test reads the wrapping pills, and the new tests drop their non-null assertions and loops.
The update dialog now shows the 5.0.0 headliners, Tours, TREK Studio, the assistant and help on every screen in 27 languages, each with a drawing of its own, translated into every language TREK ships. The 4.3.3 cards and their drawings are gone.
Ascent and descent were the raw sum of every height difference, so a flat walk across Venice (highest point 16 m) read 121 m of climbing. Heights now count once they move 5 m from the last one that counted; the server's stored figures and the planner's live analysis share the one function.
A Photo credits link in the dialog's footer opens a second view with every photo: its title linking to its page on Wikimedia Commons, its author and its license linking to the deed. The list is its own chunk, loaded when a visitor opens it. The countdown test reads the clock the interval moved.
The demo account can look at its Account tab, API keys, MCP configuration,
own AI connection and language, but not change them, and the server already
refuses each of those writes. DemoLock renders such a section dimmed and out
of reach under diagonal stripes in theme tokens, with a small Demo mode badge
and one line saying why; useDemoAccountLock decides, on the desktop settings
and the phone screens alike (a new pair in feature-pairs.json).
Locked: the whole Account tab (profile, password, 2FA, passkeys, deletion),
API keys, the MCP card with its tokens and connected apps, connecting an own
AI model and the language picker, which shows English. The admin of a demo
instance sees everything unlocked, 2FA included, so the old instance wide
"Not available in demo mode" line under 2FA and its key are gone from all
locales.
The old popup was a narrow white card with its own copy of the texts in
eight languages, hex colours and a minute counter on the browser clock. The
new DemoWelcome is a wide dialog on DialogShell with theme tokens only: what
TREK is in one line, the hourly reset with a live countdown in the server's
time zone, the 5.0 highlights to try right away (trip planner with bookings
and costs, Tours with 3D terrain, TREK Studio photo books, live
collaboration, Journey, Atlas, Vacay, Collections, offline use), what the
demo leaves out and what the full version adds, and the link to the source.
Its strings are real i18n keys in a new demo domain file, translated into
all 27 locales. Both dashboards open it instead of the old DemoBanner, which
leaves the theme and format baselines with its file and the test non-null
assertions it held.
The client is about to lock settings sections for the shared demo account,
and it has to ask the same question the server does. DEMO_ACCOUNT_EMAILS and
isDemoAccountEmail now sit in @trek/shared, and the server's DEMO_EMAILS is
built from them instead of spelling the two addresses a second time.
MapLibre 6 and Mapbox GL 3 both throw out of their Map constructor without
WebGL2. Every place that builds a GL map now asks one helper first,
components/Map/glSupport.ts, and a browser without WebGL2 gets the Leaflet
map with the user's tiles instead of a map that fails to start:
- the trip map (MapViewAuto) and the journey maps (JourneyMapAuto) pick
their renderer through useGlRenderer, which folds in the Mapbox token
rule they each had;
- the settings preview, desktop and phone, shows the Leaflet preview
through useMapSettingsForm and says why under it;
- the tour planner keeps the 2D map when 3D is asked for and says that
3D needs WebGL2;
- the PDF route map and the Leaflet vector basemap already fell back and
now report a GPU failure too.
Behind the probe, buildGlMap wraps each constructor (MapViewGL,
JourneyMapGL, the settings preview, the terrain map, the PDF map). A GPU
that claims WebGL2 and still refuses the context is reported through
utils/webgl, which turns the answer to no and tells every mounted map,
so they switch to Leaflet without a reload; any other error is still
thrown. Two new strings, map.webgl2.fallback and map.webgl2.terrain, in
all 27 locales.
The four new photo link tools join the journal's tools: the journey chat
offers them to editors, the trip chat loads them with the journey group and
the home chat keeps none. A journeyPhotos card shows the photos as
thumbnails with the entries they leave and reach, the first one marked as
the cover in a new order, and a caption old to new; a library add shows the
photos once they landed. A linked photo comes off again, an unlinked one
goes back, moved photos go back and a caption is written back, each while
nothing changed since. A new order of an entry's photos and a library add
have no way back yet. The journal prompt names the photo tools.
Linking a gallery photo to an entry and taking it off told nobody, so other
tabs, and the journey page after an assistant change, stayed as they were.
Both now go through a photo links service that broadcasts
journey:entry:updated after the write, with the asking socket left out on
REST; a caption change through PATCH photos/:id broadcasts journey:updated
the same way. The service also moves photos from one entry to another of
the same journey in one transaction, as POST
/api/journeys/entries/:entryId/photos/move, with a strict contract. The MCP
tools link_journey_photo, unlink_journey_photo, move_journey_photos and
set_journey_photo_caption call the same methods; the caption tool answers
the caption it replaced.
The update card silently left out the tags and the pros and cons it was
about to write, so the user approved a change the card did not show. An
entry now shows its first tags and how its verdict leans, and an update
names the tags, each side of the verdict and the coordinates it changes.
A new order of the entries is drawn as the entries that move, in their new
places. A changed entry is written back from what the tool answered it
replaced, and a reorder is taken back to the order before, each while
nothing changed since. A Dawarich stay taken in as an entry gets the
entry card; it has no way back, since deleting the entry would leave the
stay marked as taken in.
update_journey_entry now answers previous, the entry's earlier values of
exactly the fields it names (a suggestion written up goes back to being
one), and reorder_journey_entries answers previous_order, the ids in the
order they stood in before. Both come from the service the REST routes
use, which now builds an entry's patch in one pure helper; the routes'
bodies are unchanged. The tools' inline zod moves into strict schemas in
@trek/shared.
Starting a journal, changing its settings, linking or unlinking a trip,
bringing back its suggestions, the user's own view of it and deleting it
fell back to the generic card, which names ids and offers no way back.
They now share a journeyHeader card: the journal with its trips, the one
the change links or unlinks marked, what a new journal brings in and what
a deletion or an unlink takes, and each setting old to new in the
journal's own words. A new journal is deleted again while nothing was
written, added or laid out in it, changed settings are written back from
what the tool answered, a linked trip is unlinked by its owner while none
of its suggestions was written up, and the own switch flips back.
The MCP tool lacked show_trip_tracks and cover_gradient, which PATCH
/api/journeys/:id has always taken. Both now read the input through one
helper, and the tool goes through a small settings service that writes the
same patch in a transaction with the stored values it replaces, answered
as previous, so a change can be written back. The subtitle and the cover
gradient take null to clear them. The tool's inline zod moves into strict
schemas in @trek/shared; the REST route and its body are unchanged.
On /journey/:id the assistant window and the phone's assistant screen now
open the journey's conversation, the one TREK Studio holds too, instead of
the home chat; the Studio route keeps its own column. A turn sent from a
chat the Studio does not host names the journal page, a hosted one the
Studio, so the server offers the right tools. A journal card from the home
chat opens that conversation in place on the desktop and the journal page
on the phone, and the empty chat asks about the journal rather than the
book.
Asked on /journey to create "Japan 2026", the home chat had nothing to
offer: it keeps no write that names a trip or a journey. It now keeps
create_journey, whose trips the tool checks one by one, and delete_journey,
which the journey's own chat leaves out because the conversation goes
with it. For anything inside a journal it offers open_journey_chat, the
twin of open_trip_chat: the journal is read from the user's own journals
and shown as a card whose button goes on in the journal's chat with the
question waiting there. The trip and journal cards share their question
and button.
The journey's assistant lives in TREK Studio today, and the journal page is
about to get one of its own over the same threads. The chat and decision
bodies take an optional surface, studio or journal, and the journey scope
carries it to the service; left out it reads as the Studio, so today's
client is unchanged. The scope key ignores it, so both pages keep one
thread list per journey.
The chips offered to keep an attached file in Files, but nothing could do it:
the assistant's upload_trip_file stays denied, and the file never reached
Files. A new server write, keep_attached_file, is offered to the model where
the thread holds an attachment and held as a change card naming the file and
the booking, expense or place it goes with.
Applied, it runs the checks of a Files upload with the user's rights (the
demo block, the trip, file_upload and file_edit for a link, the instance's
file types, every linked id on the same trip), copies the bytes to a trip
file of their own, and writes the trip file, its expense link and the
attachment's pointer to it in one transaction; the members' planners get
file:created after it. A second keep of the same attachment answers the file
kept the first time. FilesService gains storeBytes and discardBytes so the
storage write stays out of that transaction.
The receipt opens the Files tab; the card's verb, its "Attached to" row and
the receipt's open label are new keys in all 27 locales. Undo is not offered:
no tool takes a trip file back yet.
The assistant could not read a file the user attached: the bytes were gone
after the request, the thread only held the file's name, and read_trip_file
stays denied because it would send any document of the trip to the provider.
A new server tool, read_attached_file, reads only the files attached in this
conversation by this user; any other id is answered "No such attachment in
this conversation.", whoever it belongs to. It is offered in a trip turn whose
thread holds a file with text. The first read of a file is held as a change
card that names the file and the model and host its text would go to; once
the user agrees, the agreement is kept on the attachment and later reads run
at once. The text reaches the model through the history and, for a later
read, as a tool answer only the model gets (AgentToolRun.modelText): no message
part, stream chunk, shared answer or client store ever carries it.
The model is told each kept file's attachment id, and the standing
instructions say to read the file when the user wants to work with it, to
propose a booking found in it through create_reservation and then to offer
keeping the file with it. The card's verb, its "Sent to" row and the step's
name are new keys in all 27 locales.
A booking file without a booking ended the answer on the error "No booking
found in this file." with no action, and the user was left to type. Now the
server answers every booking file itself, without a model: one line that
names the file and what TREK found in it, or that it found nothing, why, and
asks what to do with the file. Under it come up to three chips, picked from
what the user may do on the trip and what the file allows: keep it in Files,
make a booking from its text, attach it to a booking or an expense, ask what
is in it, or keep it with the booking just found. A chip sends its words as
an ordinary chat message.
The line and the chips are written in the language of the request from new
assistant.attach.* keys in all 27 locales, through a small helper the sports
proposals now share. The empty answer finishes as stop; no_bookings_found
stays in the contract for the threads older builds wrote. Route files keep
their answer as it was.
A file attached in the trip chat lived only as a buffer for the one request
that read it, so the assistant could not look at it again and nothing could
keep it in Files. Now its bytes go to storage before the turn writes anything
(under the trip files' category, which is never served by path) and a row in
the new assistant_attachments table keeps its name, type, size, hash and the
text TREK read out of it, written in the turn's opening transaction. The
message's attachment part carries the row's id; old messages without one still
read.
The text is read the way the AI import reads it, through a new public
LlmParseService.readText, and a file whose text cannot be read is kept all the
same. A storage that refuses the bytes refuses the turn before any row exists.
Deleting a thread takes its files along, and the nightly retention sweep drops
files older than seven days and bytes no row names any more.
The rhythm test holds the picker to what it does now: it gives the rhythm up
only where no template of the book's styles frames every photo and keeps it.
A design applied over a stored book keeps that book's theme, as the merge has
always done, so the end to end test checks the pages instead. The MCP test
builds its request through the shared schema, a travel mark's frame is read
with its sides spelled out for the client's looser types, and the client's
react-hooks/refs baseline drops to the 555 it holds now.
Stops nobody wrote about or photographed were set as a small list on a plain
spread, the old composed page. They now take a words-only template of the
book's styles: the list's label as its title, the stops as its story grouped
by day, as many as its story frame holds, and the day in the template's date
where all of them share one. The words go to their roles by name, since a
short list that opens on a date would otherwise read as a date line. The
composed list stays as the fallback where no template takes it.
The picker scored all 108 templates at once, so a page whose photos the own
pool could not frame borrowed a template of another style (a night film page
in a poster pop book). It now goes in tiers: the theme's archetype and its kin
with a frame for every photo, then the same with the rhythm given up for that
page, then another archetype, and only then a photo left in the tray. The
photos of a chapter are cut into the counts the own pool has, and only where
those leave a photo over into counts another archetype has, at a price. Riso
joins poster pop's kin for its one photo story page.
Across the 17 themes and the nine fixture journeys no photo waits in the tray,
no page is laid in another style and no page has a severe finding.
"The whole book" now lays the book on the designed templates through
autoDesignedBook, in the book's own page format and theme (a recoloured
one of its own included), its title kept and every page holding a locked
element kept; each press takes a new seed, so a second press gives another
book, one undo away as before. "This spread" lays the page on another
design than the one on screen and its neighbours, its id kept, and now
works on the cover, the summary and the countries too, so the menu offers
it wherever the auto layout knows the page. Loose gallery photos reach the
layout with the day they were taken, which it shares them out by. The
menu's two hints say what the button now does, in every language. Nothing
here places the classic summary, countries or marks any more.
auto/auto-designed.ts holds what the Studio's Auto layout menu calls.
autoDesignedBook lays the whole book on the designed templates in the
book's own theme object (a recoloured one of its own included, the calm
fabric-classic theme for a book without one), over the stored book, its
page format kept and every page holding a locked element kept; another
seed lays another book. autoRelayoutSpread lays one page again around its
locks, its id kept: an entry's page on the best template other than the
one on screen and its neighbours', avoiding one that would cut its story
where it can; the cover on the next cover template of its archetype and
then of its kin; the summary and the countries in their next look, an old
classic summary included. Pages the engine did not make are left alone,
as before. autoRelayoutKind says which pages those are.
designBook's templated path is split so the relayout reads the same
setting (templatedSetting) as the whole book, and the page layer is
shared (layEntryPage); the parity digests are unchanged by it. buildBook
and relayoutSpread stay exported and unchanged.
The design_book tool says in one sentence that layouts: 'templates' lays
every page on the Studio's designed templates in the archetype of the
book's theme, as the Studio's Auto layout does, and that each plan row then
names its template; style classic alone stays the old built-in Auto
layout. The field comes from the directives contract in @trek/shared, so
the tool's schema takes it without a zod line of its own, and the answer's
plan row gains an optional template. The REST draft routes take no
directives, so nothing changes there. Without the field the answer is the
same as before, no row naming a template.
A new optional directive, layouts: 'templates', lays every entry on the
decorated spreads of the book theme's archetype and its kin
(design-templated.ts). An entry's photos are cut into page-sized parts that
leave no frame empty, at most twelve of them, the first part carrying its
story; each part takes the template that suits it best, poured from a page
of the entry's photos and story so the title, place, date and story stay
bound to the journal. A story longer than its frame is cut at a sentence
and goes on over a page of words. Date marks are set as stamps, never in
the classic styles. Loose gallery photos go to the entry they are linked
to, then to the entry of their day, then to the entry with the fewest
(shareGalleryByDay, beside distributeGallery, which stays as it is); what
no entry has room for closes the book on up to three pages of more
moments, and anything past that is said to be left out, so no photo is
dropped without a word.
The rest is the composer's: scope, the photo picker, the page budget, the
stored pages kept, repair and findings. A book can bring its own theme
object (BookDesignInput.theme), recoloured or not, and it is kept whole.
Plan rows name the template a page was laid on. Without the directive
every design is composed exactly as before, and classic is still buildBook.
AUTODSN-001..015 cover the rules, and AUTODSN-020 pins a digest of every
page on the auto layout's parity journeys and a fourth one in three themes.
design-templated.parity.json was computed with the spec's own canonical form
and digest over these inputs, run through tsx; CI checks it from here on.
design-templated-framing.ts builds the pages around the chapters of a book
laid on the designed templates. The cover is laid on one of the theme
archetype's cover templates, poured from a page whose title and subtitle
are bound to the journey, so the binding survives; an archetype with no
cover for the photos there are has its cover composed as before. The
journey at a glance is its map with a photo at every stop beside the
figures as a poster (tiles for the collage and grid books, a sign for the
postcard ones), a ticket stub of how far and how long, and the furthest
point on an odometer. The countries are one country's map cut to its shape
beside its stamp, a card each for up to twelve, one panel of stamps past
that. The back cover adds a band of days, places and photos and a row of
country stamps. A page of words, where no template takes a story, sets it
beside the stop's map, its day as a stamp and its country as a tag. None of
these sets a summary, a country or a mark in the classic look.
design-template-pick.ts says which decorated template a page of the auto
layout is laid on. Every built-in theme belongs to an archetype
(THEME_ARCHETYPE, the five new themes to their own five), and a book picks
from that archetype's spreads and from those of the archetypes that sit
well beside it (ARCHETYPE_KIN). A template that would leave a photo frame
empty, lose the story, draw a map with nothing to draw, repeat the page
before or follow a busy page loudly is never a candidate; of the rest the
best fit wins: the photos all used, the theme's own archetype, the story
fitting its frame (bodyCapacity measures each frame once per format and
face), the first frame the shape of the first photo, not used a few pages
back, and calm where the book should breathe. Equal ones are chosen by the
design's seed. chunkPhotos cuts an entry's photos into page-sized parts
that leave no frame empty, and pickCoverTemplate does the same for the
cover.
matchDecoratedTemplate reads a page's elements by kind and frame, rounded
to the millimetre, and compares them with every template laid on the same
page format: a template is the page's when nearly all of its design is
there and it explains most of the page, and of those the one explaining the
most wins. A page rearranged by hand reads as none. The auto layout's
"this spread" uses it to offer a template other than the one on screen.