Commit Graph

1689 Commits

Author SHA1 Message Date
Maurice fbba8f96b7 fix(planner): road trip mode, the dawarich trail and the route overview follow the account to another browser
The three planner view switches lived in sessionStorage only, so a new tab,
another browser or another device opened every trip on the plain view again.
They are now kept per trip in one user setting, planner_trip_views, validated
against a contract in @trek/shared. The server keeps only switches that are on
and only trips the user can still open, so a deleted trip or one the user left
drops out on the next write. The tab copy in sessionStorage stays and answers
until the settings have loaded.

Refs #2639
2026-10-12 17:41:56 +02:00
Maurice adb88142ab fix(places): the place search looks where the plan map is looking
Autocomplete and the full search took their location hint only from the
open day or the whole trip, and dropped a trip spread over several cities
as too wide. Zoomed in on one city of such a trip, the search went global.

The plan map now hands its viewport to the trip store on desktop and on
the phone, and useLocationBias tries it first. A viewport wider than the
same 60 km radius, one across the antimeridian or one from another trip
is ignored, and the day and the trip follow as before. The viewport is
cleared when the plan map leaves the screen and on a trip switch.

Refs #2620
2026-10-12 17:18:17 +02:00
Maurice 3d7e8be9aa fix(oidc): a sign-in links to an account by email only when the address was not set by the account itself
An SSO login that matched no account by its subject took the account with the
same email address and linked the provider identity to it. That address proves
nothing when the account typed it in itself: anyone could sign up with, or
switch their profile to, a colleague's address and receive the colleague's
first SSO login. The provider switch branch (#2110) relinked a row on any email
match without even asking for email_verified, and also for another subject of
the same provider.

users.email_self_asserted remembers an address the account set itself: signup
(not the first account) and an email change in the profile settings set it, an
admin entering a new address clears it, and accounts the provider creates start
without it. Existing accounts keep linking, except those whose address moved
away from the registered one without an admin editing it. A login by email now
needs a verified address and an unmarked account, and is refused for another
subject of the same issuer, with email_link_blocked and a translated message on
the login screen. An email change in the profile settings writes a
user.email_change audit row.

Refs #2110
2026-10-12 17:06:52 +02:00
Maurice 1b58acc78f fix(trips): deleting a guest keeps the costs adding up instead of dropping their payments
A guest's payer rows and recorded payments hung on the users row by ON DELETE
CASCADE, so deleting the guest wiped money that had really moved: expenses
they paid were left without a payer, settled payments reopened, and a custom
or receipt split lost the guest's share so the balances no longer summed to
zero.

Deleting a guest who paid for an expense or is part of a recorded payment is
now refused with a 409 (REST and the delete_trip_guest MCP tool, same text),
and the share dialog says to change the payer or delete the payment first.
Otherwise the guest's share is handed on: an equal split re-splits as before,
a custom amount goes to the others by largest remainder in the currency's
smallest step (members without an amount get one, so the expense stays a
custom split), and a receipt drops the guest from its lines and is split
again like the cost editor does. A legacy paid_by_user_id is cleared as on
account deletion. Account deletion goes through the same re-split. The
confirmation text in all languages and the wiki say what really happens.

Refs #2604
2026-10-12 16:39:17 +02:00
Maurice 2149fd5315 refactor(budget): the receipt split by line lives in shared so the server can split a receipt the way the cost editor does
calculateTicketShares in the cost editor now converts the hundredths of the
new shared splitTicketLines. Same arithmetic as before: a line nobody is
ticked for is carried by everyone on the receipt and the leftover hundredths
of a line go to the lowest user ids first.

Refs #2604
2026-10-12 16:39:07 +02:00
Maurice 92af437ff6 fix(roadtrip): the stop a booking put on its check-in day keeps the night over a rest later that day
The night went to the last visit of the hotel. On a day without hours the
booking seats its own stop first, and Days does not show that stop, so a rest
the traveller put mid-day was the last visit: it was held to the check-in and
the places behind it followed from there, the way the report describes.

The night is now the visit that ends the day, else the booking's stop, else
the last visit. A visit the traveller adds to the check-in day no longer moves
the night or its check-in. nightVisits in shared holds the rule, and the road
trip, calculate_roadtrip, the stored ride slot and the stop editor all read it.

Refs #2636
2026-10-12 16:05:17 +02:00
Maurice 36ac81322a fix(i18n): a session opened in a fresh browser starts in the browser's language
wiki/Languages.md has the account's language first, then the browser's.
The browser step only ran on the login page, so opening a trip with a
live session in a new browser (or after the site data was cleared) showed
English to anyone whose account had no language of its own.

The app now starts in the browser's language when nothing was chosen in
this browser, and keeps it when the account has none. Nothing is written
to the account; a language set in the account or picked in the app still
wins. The detection moved to its own module so the settings store can use
it at startup.

Refs #2604
2026-10-12 15:46:17 +02:00
Maurice a0331bf53b fix(dashboard): a refused archive, delete or copy says why instead of only that it failed
A member without the right to archive a trip clicked Archive and read
"Failed to archive trip", while the server had answered why. The archive,
restore, delete and copy toasts now show the server's reason, as creating
and editing a trip already did; a failure without one keeps the old line.
The buttons stay where they are.

Refs #2604
2026-10-12 15:46:17 +02:00
Maurice 125649ce1b fix(planner): cards over the map stay clear of the layer pill, and a wide tab bar shows full tab names
The base-layer pill sits in the map's bottom-left corner on purpose. The
place inspector, a tour's detail and the day panel are centred between the
sidebars and were assumed never to reach that corner, but with both
sidebars open in a 1280 window they do, and the pill covered "Remove from
Day" and Edit. These cards now keep their left edge clear of the pill: a
narrow map moves the card right of it, a wide map keeps it centred as
before. The pill itself does not move, and the phone sheets are unchanged.

The Bookings tab showed "Book" at every width, which read as cut off. The
short form now only shows where the bar is tight; from xl up the tab shows
its full name.

Refs #2604
2026-10-12 15:46:09 +02:00
Maurice cb04ebe6d2 fix(trips): the trip form drops its complaint once you edit, and the typed date hint follows your language
"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
2026-10-12 15:46:02 +02:00
Maurice 35eba21128 fix(costs): row buttons, settle-up rows and share toggles say what they are about to a screen reader
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
2026-10-12 15:45:56 +02:00
Maurice dbbbd07b1c fix(costs): amounts follow their currency's decimals and format, and payments count on the local day
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
2026-10-12 15:04:32 +02:00
Maurice 9064dbb2f0 fix(costs): an expense made from a booking is dated on the booking day
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
2026-10-12 15:04:22 +02:00
Maurice 676f6233ed fix(planner): undoing a place delete brings the place back whole, or is not offered
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
2026-10-12 13:44:44 +02:00
Maurice e70fc334bd fix(costs): people who left the trip are named and keep their balance in the costs lists
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
2026-10-12 13:22:32 +02:00
Maurice 106b748551 fix(maps): marker thumbs from cached place photos load under the same request limit
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
2026-10-12 12:59:45 +02:00
Maurice 8acc035efa fix(collections): all saved reads its lists four at a time and reloads once per burst of updates
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
2026-10-12 12:59:44 +02:00
Maurice 2f0b867685 fix(settings): the integrations tab is always there on desktop so api keys stay reachable
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
2026-10-12 12:46:12 +02:00
Maurice 93173b1573 fix(planner): bookings a collaborator moves on a day take their new slots in every open list
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
2026-10-12 12:33:19 +02:00
Maurice 12f08e3646 fix(planner): a timed booking dropped against its time goes back alone instead of taking the stops behind the drop along
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
2026-10-12 12:33:12 +02:00
Maurice c2dd66986c fix(maps): the day route leaves an imported gpx track to the track and carries on from its end
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
2026-10-12 11:43:50 +02:00
Maurice 3dbe76945d fix(roadtrip): the day view drives the road picked in the road trip
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
2026-10-12 11:32:15 +02:00
Maurice 565333a90c fix(roadtrip): a hotel visited again on its check-in day is the night only at its last visit
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
2026-10-12 11:05:41 +02:00
Maurice e3bf18f9a6 fix(collections): a list invite can be accepted where its notification leads, on phone and desktop
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
2026-10-12 10:44:57 +02:00
Maurice c94d275cfd fix(journey): gallery photos can be deleted on touch screens and ask before they go
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
2026-10-12 10:32:32 +02:00
Maurice 90352b8e3a fix(journey): draw the dashed line between entries on fullscreen leaflet maps too
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
2026-10-12 10:19:39 +02:00
Maurice 4ee5e3fa96 fix(bookings): booking cards always show the calendar date of their linked day
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
2026-10-12 10:16:01 +02:00
Maurice 7cb96c3d74 fix(mobile): show the trek version as a footer in the mobile account menu
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
2026-10-12 10:08:08 +02:00
Maurice 36b8baf259 fix(atlas): removing the last region keeps a country the user marked visited itself
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
2026-10-12 09:55:52 +02:00
Maurice b3fe593a1e fix(share): translate cost categories on the public trip page
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
2026-10-12 09:38:19 +02:00
Maurice 11ede4fbef fix(journey): emptying the location field in the entry editor removes the location
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
2026-10-12 09:33:00 +02:00
Maurice aa1bb41cf5 fix(collections): moving a place out of a list needs the same role as deleting it
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.
2026-10-12 09:17:08 +02:00
Maurice aeaf3693ad fix(mascot): typecheck the clip metadata and keep the client ratchets green
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.
2026-10-11 21:33:31 +02:00
Maurice ee1b6a837f feat(mascot): 3D mascot as transparent 60 fps clips with a click animation per scene
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.
2026-10-11 15:08:48 +02:00
Maurice 4a36a60f5a test(roadtrip): a ferry leg draws no car on its band, while the day head keeps its drive pill's car 2026-10-11 15:06:59 +02:00
Maurice c6f45b9be6 refactor(planner): a day head's pill row moves into a module of its own
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.
2026-10-11 14:22:46 +02:00
Maurice 32606d20a6 fix(roadtrip): the rail passes the strict and lint gates, and its tests describe the new markup
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.
2026-10-11 14:01:18 +02:00
Maurice e8fd41b9b4 feat(roadtrip): the Google route import and the track dialog use the shared dialog frame, and Search and Manual match the day plan's buttons
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.
2026-10-11 14:01:18 +02:00
Maurice 0c554e319b fix(planner): a day head keeps its stays on one line, each name cut by the same share, under the name and the tools alike 2026-10-11 02:42:21 +02:00
Maurice 856f5b5b08 feat(roadtrip): the rail and its dialogs in the planner's card language
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.
2026-10-11 02:36:58 +02:00
Maurice 3ff88062f1 fix(planner): pills on a white row keep a hairline, share one height, and a transport reads its time before its phase 2026-10-11 02:36:58 +02:00
Maurice cc94bc6953 refactor(planner): the day header's stay order lives in its own module and its tests in their own file
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.
2026-10-10 22:18:36 +02:00
Maurice 10ffc24395 chore: drop the Place import the removed helper type left unused and lower the client test lint baseline 2026-10-10 22:04:10 +02:00
Maurice 3fdae47041 chore: drop the exports the reworked server modules left without an importer
knip found them once the server lanes and the client ratchets met: re-exports
and helpers that moved into place-import, nest-rpc and auth-core but kept an
export nothing reads, the demo-denied helper the central MCP gate replaced,
and two settings keys only the selector uses. @OptionalAuth stays exported
as the documented opt-out and is marked @public. The baselines for knip and
the unused en keys drop to the merged tree.
2026-10-10 21:52:09 +02:00
Maurice b9a8bfc3e7 refactor(budget): the cost preview splits through the shared cent split the settlement uses 2026-10-10 21:43:08 +02:00
Maurice d2e044869a fix(days): a day without a date keeps no date when it is moved, so it can go to the front of a dated trip
Reordering handed out the dates by position, so moving a day added without a
date to the front gave it the first date and took the date of the last dated
day. The undated row stayed at the end and the move looked like it did
nothing, by drag and by the arrows alike. The dates now go to the dated days
only, in their new order; the rule lives in shared so the server, the
planner's optimistic list and the MCP tool apply the same one.
2026-10-10 21:34:51 +02:00
Maurice d496dd5d30 fix(planner): a day without a stay centres its name on the day badge 2026-10-10 21:33:38 +02:00
Maurice b31c2ff1d2 ci(client): hold the en keys nothing reaches to a baseline that only shrinks
lint:i18n-keys only listed unused en keys as information, and it looked for
references in client/src alone, so every key the server sends (system notice
and notification channel keys in its registries) read as dead. The reference
search now also reads server/src and plugin-sdk/src; those references only
mark keys as reached and are not held to the client's key rules. The unused
keys per en domain file are counted against scripts/i18n-unused-baseline.json,
which only shrinks and fails on an entry above its count until --update
lowers it. Today's 841 unused keys in 44 files are frozen, none deleted.
2026-10-10 21:33:29 +02:00
Maurice 83d807781e ci(client): hold strict null check errors per file to a baseline that only shrinks
The client compiles with strict: false, so nullable contract fields arrive as
if always present and implicit anys stay invisible. tsconfig.strict.json is the
same program with strictNullChecks and noImplicitAny, and lint:strict
(scripts/strict-lint.mjs) counts its errors per file against
scripts/strict-baseline.json: a file may not gain one, a new file has to be
clean, and an entry above its count fails until --update lowers it. Nothing is
converted here; today's 534 errors in 165 files are frozen. allowJs stays: the
tests import the .mjs scripts and read their JSDoc types through it.
The todo MSW handler maps a cleared priority to its default so the test
helper written in this branch starts at zero. The check runs after the
typecheck in client-quality.
2026-10-10 21:32:59 +02:00
Maurice c266a5e1a7 chore(client): freeze typed promise, hook, size, store and global rules in the ESLint baseline
The client lint ran untyped with two react-hooks rules, so nothing saw a
floating promise, an async handler where a void one is expected, a whole-store
Zustand subscription or a window event bus, all of which client/CLAUDE.md
forbids. eslint.config.mjs now lints with the project service and adds, as
warnings: no-floating-promises, no-misused-promises (void returns in JSX
attributes included), the rest of react-hooks v7's recommended set,
complexity 20, max-depth 4, max-params 5 and max-lines-per-function (120 in a
.ts function, 300 in a .tsx one, whose body also holds its markup),
no-console and no-non-null-assertion. Three local rules in
scripts/lib/eslint-rules.mjs cover what no stock rule sees: an empty
.catch() callback, use*Store() without a selector (the message points to
useShallow, since an object selector without it loops on Zustand 5), and
window.__* globals and window.dispatchEvent with an event of our own.
scripts/eslint-baseline.json takes today's count of each new rule, so the
existing code is frozen, not changed, and each new instance fails
lint:warnings. The CI step gets the 8 GB heap typed linting needs.
2026-10-10 21:32:23 +02:00