"Title is required" stayed on screen after the title was typed, until the
next save. Any edit in the trip form, desktop and phone, now clears the
last save's complaint; the next save checks again.
The keyboard date field always hinted DD.MM.YYYY, while it was filled and
read back in the user's own order: a Korean user saw "2026. 12. 10." under
a German hint. The hint is now the locale's own pattern (YYYY. MM. DD. in
Korean, MM/DD/YYYY in US English), and the field's name is translated.
Refs #2604
A screen reader lists a page's buttons together, and the costs views gave
it twenty "Edit" and "Delete" buttons with nothing to tell them apart.
- Edit and delete on an expense name the expense ("Edit: Dinner"), edit
and undo on a payment name who paid whom, on desktop and phone.
- A settle-up row shows only initials in its avatars, its names lived in a
tooltip; the row now carries them for assistive tech too, and its Settle
button says who pays whom and how much.
- The search field of every searchable select (currency, time zone,
category) was read out as "..."; it now says Search in the user's
language.
- The public link toggles on the phone share sheet and the journey share
toggles say whether they are on (aria-pressed), not only by colour.
- Japanese still said "you" in the settle-up lines.
Refs #2604
Several small things in the costs views, on desktop and phone alike:
- The outstanding card no longer says "You're all settled up" while a
payment is still open in Settle up.
- The custom split shows its suggested shares as a faint placeholder in
the currency's own form (30000 for won, not 30000.00), so it no longer
reads like an amount already typed.
- Amount fields take only the decimals the currency has: won and yen take
none, KWD and the other three-decimal currencies three. A switch of
currency cuts what was already typed. 1000.555 won was saved as typed,
listed as 1,001 and reopened as 1001.
- The expense sheet on the phone prints shares and sums with the same
formatter as the list, with grouping and the currency's decimals.
- A payment recorded with Settle or Settle all is dated on the viewer's
own day, and one without a date of its own is grouped by its UTC record
time read on the local calendar. In Seoul before 09:00 it landed on the
day before.
- "1 expenses need a payer" now uses the plural forms of each language.
Refs #2604
Creating the expense from a booking opened the editor on today, and the
cost a booking import writes by itself had no date at all, so it sat under
"No date". Both now take the booking's own date, else a hotel's check-in
day, else the day the booking sits on. The server sets it for every
booking write that creates a cost, so REST, MCP, plugin RPC and the import
agree.
A start time typed on a booking with no day picked no longer borrows
today's date on desktop. Today was mostly outside the trip, the server
then moved the booking onto day 1 while the list kept today. Desktop now
does what the phone already did and keeps the time without a date.
Refs #2604
The ledger nets every currency in hundredths, and the settlement handed those
hundredths out as they were. For a currency shown without decimals the panels
then rounded each balance and flow on its own: 10,000 KRW three ways read
+6,667 / -3,333 / -3,333, two flows of 3,333 sat under "You're owed 6,667",
and Settle booked 3,333.34 won.
The balances, flows and final budget lines are now allocated by largest
remainder in whole steps of the display currency (whole won or yen, unchanged
for two and three decimal ones), so the shown set adds up to zero and the flow
amount Settle sends is the whole amount on the row. The ledger, stored rows
and manual settlements are untouched. REST and the MCP settlement summary
share the same method.
Refs #2604
Moving a trip's start shifted the owner's vacay entries one row at a time,
checking each target against rows that were about to move themselves. A
block shifted by less than its own length kept its first days behind and
pushed the rest onto the weekend, even with weekends blocked.
The shift now decides every day before writing: a day whose target is a
blocked weekend or a whole company holiday is dropped, as is one whose
target already holds leave that is not moving; a half company holiday
leaves half a day. The rest move in the direction of travel inside one
transaction. The plan is the one the owner is active in, so a member of a
fused plan gets their days moved too, and a trip edit no longer creates a
vacay plan for an owner who has none. Open vacay views are notified after
the trip update commits.
The pure vacay helpers moved to vacay.helpers.ts and the trip window tests
to their own suite, so both files stay inside their size budgets.
Refs #2604
The undo wrote a new place from nine of its fields and put it on its days as a
bare stop, so notes, website, phone, hours, picture, the visit times, day notes
and travellers were gone while the toast said it was undone. It now writes every
field the place had and plans each visit again with its times, note, end of day,
route flag, travellers and leg modes, and casts the own rating again.
The server takes a booked night, its booking and expense, linked expenses, file
links, other members' ratings and an uploaded picture down with the place, and
writing the place again brings none of that back. Such a delete is no longer
offered for undo. Undo steps that name a deleted place are dropped at the delete
instead of failing later, and adding a place, moving categories and the phone
reorder and optimize now carry their place ids for that.
Taking a stop off a day and clearing a day undo with the visit details too. The
stays are read again when a deleted place had one, here and for collaborators.
Refs #2604
Someone removed from a trip stays on the expenses they paid for or shared,
but the costs views only knew the current members. They showed up as "?"
in the expense chips and the settle-up suggestions, and the balances and
final budget lists left them out, so the listed balances no longer added
up to zero.
The names now come from what the server already sends with the expenses
and the settlement, and anyone off the trip who still carries money gets a
row after the members, on desktop and on the phone. The expense form is
unchanged.
Refs #2604
The copy has nobody on it but the copier, yet it took every payer and
split row along. Its balances then had the copier owing people who cannot
see the trip, and the first save of such an expense dropped them and
shrank its total.
An expense anyone else pays for or shares now comes across as a planning
entry with its total and no payers, split or receipt lines; one that is
the copier's alone is copied whole. Assignment participants and the
legacy paid_by_user_id keep only the copier for the same reason.
Refs #2604
Removing someone from a trip leaves their payer and split rows on the
expenses they were part of. Every save ran the ids the form sends back
through the roster filter, so an unchanged save dropped them, total_price
shrank to what the rest had paid and a custom split stopped adding up.
Ids already on the expense now stay allowed, as payer or as member, and
can still be taken off it on purpose. A new expense and an id that never
was on the expense are filtered as before (#1952). The paid ticks of the
split carry over a form save instead of being reset. REST, the plugin RPC
and the MCP split checks all read the same allowed set from the service.
Refs #2604
A place whose image_url already is the place-photo proxy URL skipped the five
slot limiter and loaded its marker picture right away. A map showing a big
list, such as "All saved" in collections, asked for every picture at once.
Those downloads now wait for a slot like the provider lookups do; the entry
itself is still there immediately.
Refs #2559
The "All saved" view read every list in full at the same time, and every
collections:* websocket event started that again right away. An agent saving
places one by one over MCP sends one event per place, so an open collections
tab stacked up full reads of every list on the server.
The union now goes through the same batched loader the trip's saved-places
picker uses (four lists at a time), reloads asked for while it runs fold into
one more pass, and a list or union read that lands after the user switched to
something else is dropped instead of painting its places over the new view.
The page waits for a run of events to settle before it reloads and keeps the
selection on "All saved" like it already did on a single list. Desktop and
phone share useCollections, so both get it.
Refs #2559
API keys were split off from MCP to work without any addon, but the tab
that holds them still hid unless a photo provider, MCP, AirTrail, AI
Parsing or Dawarich was enabled. On a fresh install all of those are off,
so there was no way to create a key. The desktop tab now always shows;
the addon backed sections inside it keep their own gates. The phone layout
has no API Keys section and keeps its gate.
The wiki now says where keys are created and that no addon is needed.
Refs #2561
On an install without the TREK Places index and without a Google key the
suggestions come from Nominatim, and autocompleteNominatim asked it without
the box the client had sent, so every suggestion was ranked globally. The
search fallback on the same install dropped the bias the same way. Both now
pass it on as a viewbox with bounded=0, which ranks results near the trip
without hiding anything further away.
Refs #2620
The suggestion list under the place search showed the index's raw ISO code
("福岡市博多区, JP") and listed a place twice when the index and its
OpenStreetMap layer both carried it, which the search list after Enter
already merges. Suggestions now name the country through Intl in the
language of the search, and a layer row at the spot of an index row with a
matching name is dropped by the same rule mergeSearchResults uses, now
shared as samePlace.
Refs #2592
reservation:positions was on the ignored list, while the stop order the same
reorder sends was applied, so in other tabs and for other members the bookings
sat between the wrong stops until a reload. The event now writes the slots the
way the mover's own list did, for the day it names (day_id from REST, dayId
from the MCP tool) or as the global slot without one, and caches them offline.
A booking handed back by an edit or a reservation:updated event without day
slots keeps the ones it had, in the store and in the offline cache.
Refs #2631
The day list draws a row with a time by that time, and an untimed stop takes the
time of the timed row before it. A transit ride dropped behind a later stop was
stored there, drawn back at its time on the next render, and every untimed stop
behind the drop went with it; the next drag then stored that shuffle. A dropped
booking or timed note now lands in the slot nearest the drop the list draws it
in, so nothing else moves and what is stored is what is shown. Rides dropped
where their time allows stay on the drop, and the rule for untimed stops behind
a booking is unchanged.
The chronology check for a timed stop reads a booking's time for the day (the
arrival on the last day of a multi-day ride) instead of its departure, and the
three copies of the remove and insert step share one helper.
Refs #2631
The create and update answers, the reservation:updated broadcast and the MCP
reservation tools all hand back the single booking read, and that read never
loaded reservation_day_positions. The day list puts a booking moved on a day by
that slot before its time, and the client replaces the booking with the answer,
so a booking moved on a day jumped back to its time after every edit until the
next reload. The read now attaches day_positions in the shape the trip list
already sends.
Refs #2631
A GPX or KML import stores the track's first point as the place's position,
so the day route treated it as a single stop there and routed from the
track start to the next stop, a second road running alongside the track.
Any assigned place with a usable track now gets the gap a Tour already had:
the route runs up to the track start and continues from the track end, on
the desktop map and day list, the mobile timeline, the trip overview and the
PDF map, which now draws the track as a line. A place without a usable track
stays a plain stop and never shows as an invalid Tour.
Refs #2600
A chosen alternative or a hand-bent drive is stored as vias, and only the
road trip rail routed through them. The day route on the map, its
connectors in the day list and on the phone, and the overview of all days
asked the router for the bare stops, so they drew and measured the default
road again.
They now thread the same vias between two stops that follow each other in
the stored list, with the anchor counted the way the rail counts it, and
fold the answer back to one leg per stop pair. Only while the road trip
addon is on; avoid settings and the routing engine stay with the rail.
Refs #2580
A stop counted as the booked night whenever a stay began at its place on its
day, so a hotel on its check-in day twice (the booking's own stop and a rest
the traveller planned there) was read as two nights. Both were held to the
check-in: the rest waited for the room or was set back to it, and with a check-in
far from its arrival the rest and every stop behind it moved onto the next day's
card.
oneNightPerStay in shared keeps the night and its check-in on the last visit of
the place and turns the earlier ones into ordinary stops, still a hotel. The
browser and calculate_roadtrip both run it, the stored ride slot times only that
row by the check-in, and opening an earlier visit in road trip mode opens the
stop rather than the night.
Refs #2636
The invite notification links to /collections/:id, a list the invitee
cannot read until they accept. The phone screen had no invite UI at all
and showed "Start your first collection", and on desktop the rail with the
invites is hidden below 1080px while the empty state had no way to open it.
useCollections now recognises a route id that is a pending invite, keeps
it out of the route sync and puts it first in the invites it hands out.
The phone screen shows the pending invites as cards above the lists, and
the desktop body shows them when the person has no lists of their own,
or the one invite the route points at. Both reuse the existing invite
strings. A route id that is neither a list nor an invite (declined,
withdrawn, deleted) now drops back to /collections once the lists are in,
instead of leaving the page on a list it cannot show.
Refs #2645
The delete on a gallery tile only showed on mouse hover, so a phone or
tablet never saw it, and the phone screen had no delete at all. Where it
was reachable it deleted at once, although the server removes the photo
for good and takes it out of every entry that uses it.
The desktop tile now shows its delete on hover, on keyboard focus and
always where there is no hover, with a larger target there and an aria
label; viewers no longer get the button. The phone gallery gets the same
corner button for editors. Both ask through ConfirmDialog first, saying
that the photo also leaves the entries it is used in, and both delete
through the store action. The entry editor's photo controls follow the
same hover rule.
Refs #2593
The Leaflet journey map only drew the dashed route between entries when
fullScreen was off. Since the desktop sidebar and the mobile map plus
carousel view both pass fullScreen, the line was missing on the public
journey page (desktop and phone) and in the logged in views with the
Leaflet provider, while the GL renderer always drew it. fullScreen now
only enables wheel zoom, matching JourneyMapGL where it only sets the pitch.
Refs #2606
Phone booking and transport cards printed only the title of the linked day,
so a long title cut off in the field left no date at all. They now print the
date beside the title like the desktop card, as a span that never shrinks
while the title is truncated, and a day range takes the full width with the
time under it. The desktop card gets the same non-shrinking date.
A day without a date of its own (a trip planned without dates) now borrows
the booking's start or end date, on the desktop cards, the list, the detail
dialog, the shared page and the phone alike.
Refs #2590
The avatar menu of the mobile dashboard lost the version pill that the
desktop account menu shows under sign out. It now renders the same
appVersion from the auth store as a small footer, with the wordmark
following the active theme. Discord, help and the prerelease badge stay
desktop only.
Refs #2581
A stop with a start time but no end time went out as a VEVENT without
DTEND. Calendar clients fill in their own default length for that, mostly
an hour, so a 20 minute stop showed up as an hour. The event now ends at
start plus the place's duration_minutes when that is set and above zero,
carried over midnight like the stay markers. An explicit end time still
wins, so start and end under an hour stay as they were. The trip download,
both calendar feeds and the MCP export share this builder.
Refs #2641
Marking a region wrote its country with source 'manual', the same as ticking
the country off by hand, and removing the last region then took the country
away whatever its origin, Dawarich confirmations included. A country that
only comes in through a region is now stored as 'region', marking it visited
afterwards turns it into the user's own mark, and the cascade in unmarkRegion
only runs for a country without such a mark. Readers still see 'manual' for
those rows, so the country detail, the MCP resource and the plugin RPC answer
as before. Rows written before this change stay 'manual' and keep their
country.
REST, MCP and plugin RPC share the service. The atlas popup on desktop and
phone now asks the server whether the country is still marked before it
drops it from the map instead of guessing.
Refs #2597
The shared Costs tab grouped expenses by the stored category and printed it
as is, so the fixed keys ("accommodation", "flights") showed in English
whatever language the reader picked. It now groups through catMeta and
labels each group with costs.cat.*, the way the Costs tab does, so old
free-text labels also join their key instead of forming groups of their own.
Refs #2596
The field showed the stored place as soon as the search query was empty, so
deleting the last character snapped it back and the location could never be
removed. Clearing the field now empties the place and its coordinates, closes
the suggestions and drops any search answer that comes back afterwards. Typed
text without a picked result keeps the stored location as before. The hook is
shared, so the desktop dialog and the phone sheet both get the fix.
Refs #2615
NODE_ENV="production " passed the boot check but matched none of the
exact production comparisons, so the web UI was never served and every
page answered {"error":"Cannot GET /"}, while the banner showed a clean
"production". The boot import now trims these three variables in
process.env before anything else reads them, so Express, the plugin
children and readEnv() agree, and reports each change as a process
warning. Casing stays as it was. A padded "test" is only reported, so an
instance cannot switch to an in-memory database after an update. The
banner prints NODE_ENV as set instead of lowercasing it.
Refs #2565
A Google place URL carries two points: @lat,lng is the centre of the map
view, the !3d!4d data param is the place itself. extractCoords took the
camera first, so a pasted share link put the place around 170 m off and
reverse geocoded the wrong street. The data param is now read before @;
the order of @ and ?q=/?ll= is unchanged. REST and the resolve_maps_url
MCP tool share the resolver, so both pick this up.
Refs #2635
Moving a saved place to another list takes it out of the source list, but
updatePlace only asked for edit rights there, so an editor could empty a
shared list they may not delete from. The source now takes owner or admin,
like a delete, and the target still takes edit rights. REST and the
update_collection_place tool share the service path. The Move to list
action on desktop and phone follows the delete permission.
The tuple cast on clips.meta.json failed tsc. The clip lookup and the 3D
opt-out move into mascotClips.ts so the component file only exports the
component, the crop offset is allowed as a deliberate physical side, and
MDancingTrek leaves the format baseline.
Every empty-state scene of the TREK mascot now plays a pre-rendered 3D clip
instead of the SVG: a seamless loop per scene and theme plus a one-shot
animation on click that cross-fades in and out.
The clips are plain H.264 MP4 with stacked alpha (colour on top, the mask
below) that a small WebGL shader joins into a transparent image, so the same
file plays in every browser including iOS, decoded in hardware. The videos stay
2 px stand-ins and are drawn onto a canvas, which keeps browser video controls
off the mascot. Each clip is cropped to the area its scene uses and placed back
inside the 88:96 box, so every scene keeps the same scale.
MDancingTrek picks the clip when one exists for the scene and face and falls
back to the SVG under reduced motion, without WebGL, when autoplay is refused,
in tests, or with localStorage trek-mascot-3d set to 0.
The stays, hire cars and plugin label under a day's name now live in DayHeadPills, so DayPlanSidebar shrinks back under its size baseline. The stay fields are read through their types instead of an any cast, and the size, eslint and theme-disable baselines are lowered to the new counts.
The day head falls back to metric when no distance unit is set, VISIT_BOX is no longer exported, the day head is split into smaller parts and the booking icon is created once. The component tests now check the pill badges, the day tile and the ride cards instead of the old boxes, and both baselines are lowered to the new counts.
Both dialogs now open in the same frame as the stay and stop dialogs: an icon tile and title in the head, the content in a card, and the actions in the footer. Closing stays blocked while an import runs. In the right column, Search and Manual take the size, text and icon of the day plan's toolbar buttons.
Day heads take the day plan's layout: the number on a raised tile with the date
under it, the name beside it and its facts as pills that never wrap but give way
in steps. Rides, terminals and hire desks are cards in their type's colour with
their icon on the line and their times in the arrival column; a stop's bookings
sit on its own badge row. Every badge on the rail is one pill shape, the drive
bands are round, and the stay, stop and driving settings dialogs use the shared
dialog frame.
DAY-SVC-048 and DAY-DEL-011 pinned the old reorder, which handed a day
without a date the first slot's date; they now expect it to keep none and
the day it passes to keep its own. OIDC-SSRF-003 reads oidc.service.ts as
text and missed the token call once Prettier put a trailing comma after its
hop budget of 0; the pattern takes the comma.
The day badge fix grew DayPlanSidebar.tsx and its test past their size
baselines. The check-out, through, check-in order of the header's stays is
now dayHeadStays() with a test of its own, and the day header tests sit in
DayPlanSidebar.dayHead.test.tsx; both files drop below their old entries.