Commit Graph

93 Commits

Author SHA1 Message Date
Maurice 1bc82f97fd feat(tours): route paddle tours on the water, shaping points and auto-loaded regions
Paddle tours are planned on the water by TREK's own router: a water network
per paddle region, built from the same Geofabrik extract as the obstacles and
searched in a worker thread of the server (no sidecar). Rivers, canals and
lakes (direct or along the shore), portages round weirs, locks by policy,
whitewater by boat, gaps marked. Found portages and locks become points of
their own; an unplannable leg says why and offers a way out.

- Shaping points: a click on the line sets a point that steers the route
  without being a stop (hikes and paddle tours); recompute a single leg.
- Lake style direct/shore, suggested from the wind forecast.
- Regions load by themselves where a paddler plans (admin setting: automatic,
  on request, off; size cap and a daily count), with requests for the admins,
  a notification, and the load followed on the leg.
- Portage from the last point, portages follow footways via the walking
  router (or stay straight), "put in here" on slipways and put-ins.
- MCP plan_paddle_route with the REST route's rules.
2026-10-18 21:21:32 +00:00
Maurice b76a298bb3 ci(lint): a base version the typed rules cannot lint no longer crashes the report
On the server, linting a file's base text with type-aware rules threw (no program for a text that is not on disk), and the TypeError ended the whole ratchet run. A lint that throws now counts as unknown: the file's current warnings of the rule are told, marked as not compared.
2026-10-18 23:17:33 +02:00
Maurice 17029d364a ci(lint): lintTextClient stays inside the client ratchet
Only check() uses it, as its default; exported it was an unused export to knip, which turned the server quality job on dev red.
2026-10-18 23:16:37 +02:00
Maurice f458278e88 ci(lint): a grown warning count names the file and line that grew it
lint:warnings failed with the total and the files holding the most of the rule, which are old ones the baseline allows; the file that tipped the count was never named, so finding it meant linting by hand. When a rule grows, the ratchet now lints the files that differ from the base twice, as they are and as they were there, and lists every one holding more of the rule, line and message. The base is TREK_LINT_BASE (the CI fetches the pushed-over commit on a push and the target branch on a pull request) or origin/dev; without it the report stays as it was.
2026-10-18 22:46:00 +02:00
Maurice 3db20dd71c chore(client): theme baseline down one for the share button the navbar lost 2026-10-18 19:13:36 +02:00
Maurice 097c1da0af fix(mobile): a transport card shows the notes of its booking
On the phone the Transports tab never rendered a booking's notes, so what
someone typed into a flight was only to be found by opening its sheet,
while the booking cards next to it showed theirs. Both cards now take the
notes from one part, sized on the caption tier so the text size setting
reaches them.
2026-10-18 13:21:53 +02:00
Maurice d46b6fb646 refactor(tours): the map stage's track pick and the planner's focus points in hooks of their own
The tour map stage and the trip planner page hook had grown past the
complexity and length the lint ratchet holds them to. Marking a spot on a
recorded paddle track moves into useTrackLinePick, and what the planner map
frames into usePlannerFocusPoints, each with its own tests. The planner's
viewport snapshot no longer reads a ref while rendering, so the count of
that warning goes down by one.
2026-10-17 02:07:22 +00:00
Maurice c246877f30 refactor(tours): the region repository reads the status enum of its entity, unused exports go
PaddleRegionsRepository types and writes the status through the generated PaddleRegionsStatus instead of a copy of its four values; the paint tokens, a fact key type and a minutes input stay private to their modules, the wind test pins its 502 text, and the planner page's size entry follows it down.
2026-10-16 23:24:20 +00:00
Maurice 9494521ac7 feat(tours): plan paddle tours, hand-drawn legs, reverse and return to start, maps per tour type
- Paddle as a plannable tour type: one boat (kayak, canoe, SUP, other), river or
  lake, a pace that a boat change never overwrites; time is distance over pace
- Hand-drawn legs beside routed ones, drawn dotted and labelled as not routed,
  kept through save, reopen and edits elsewhere
- Reverse a tour and close it back to its start, each one undo step
- GPX import shows a preview first (line, measures, duplicates, type) and only
  the confirmation stores the tour
- Base maps and overlays per tour type in the planner: hike keeps topo and the
  via ferrata layer, paddle gets the OpenSeaMap seamark overlay
2026-10-16 23:14:52 +02:00
Maurice 845979902c Tours: client tests for the new hooks and views, row chips fade at their end, admin user table aligned; strict and lint ratchets green 2026-10-15 23:08:46 +02:00
Maurice a0884685ef Map: tracks drawn from their own modules on both maps; mobile filter chips on the type scale 2026-10-15 22:25:34 +02:00
Maurice cd1b1410bd Tours: filter a tour list by source, routing limit and type where it varies (PR1-13) 2026-10-15 22:02:41 +02:00
Maurice 3d9095ccde Tours: provenance contract, source check, one assignment path and the map overview
- P2C-02: tours record their origin (planned, GPX with file name, wanderer)
  instead of reading it off their shape; the detail answers a provenance
  contract and a trip copy keeps a tour's import record
- MF-03: a wanderer snapshot's source can be checked on purpose, read only,
  with a confirmed reimport as a new snapshot
- PR1-18: every way of putting a tour on a day shares one set of rules
- PR1-07/08: each person can hide tours from the trip map; the list shows
  each tour's line colour and the picked tour stands out on both maps
2026-10-15 21:51:22 +02:00
Maurice f1c437b8b8 Keep api/client.ts and trips.service.ts within their size baselines
The admin switch for wanderer moves to api/wanderer.ts and the recorded
track restore to api/recordedTrack.ts; trips.service.ts keeps dev's
formatting with only the derived-from remap on copy.
2026-10-15 18:16:25 +02:00
Maurice f690065de8 Tours: resolve the distance unit once where tour facts are built 2026-10-15 17:51:19 +02:00
Maurice 782e613506 Tours: day GPX import, wanderer trails and planning over an import
- GPX files drop onto the map or a day; the import lands on that day
- wanderer connection per user in Integrations; the workspace offers the
  user's own trails only, behind an admin switch under the Tours addon
- wanderer difficulty is stored on the SAC scale (T1 to T3), backfilled
- a GPX or wanderer import can be planned as an editable tour, either in
  place (the recorded track is kept and can be restored) or as a new tour
  that remembers the import it came from
- rails, rows and detail dialog follow the plan sidebar's cards and badges
- admin addon shelves with more than one row fold, collapsed by default
- reverse-geocoded waypoint names are cached on the device
2026-10-15 17:44:15 +02:00
Maurice 0a50119c52 chore(layout): lower the navbar's theme baseline to what it holds now 2026-10-14 21:24:15 +02:00
Maurice 976a612351 fix(tours): ferrata map tokens in a style module of their own, keep module-private helpers unexported 2026-10-14 20:44:53 +02:00
Maurice d87e276831 fix(tours): translate the ferrata badge everywhere, count the tile cache writes as standalone, settle the new test warnings 2026-10-14 20:34:20 +02:00
Maurice e3983ef857 feat(tours): tidy the tour editor and tour list, set points only on purpose, name waypoints on the map 2026-10-14 20:21:39 +02:00
Maurice 160bc89e78 feat(ferratas): keep loaded via ferratas in a tile cache on server and device, group segments, own look and count badge 2026-10-14 20:21:38 +02:00
Maurice a9882a6415 feat(tours): via ferrata layer mounted on the tours map (2D and 3D) 2026-10-14 20:21:37 +02:00
Maurice 341520f98b fix(planner): give day plan rows their full width until the controls are wanted
The hover controls took their room at rest, so a place's line and a note's
text broke early. A place now gives way only while hovered, a note's controls
fade in over the end of its text in the note's own colour, and a picked place
shows none. All three controls of a place come out together: the add-booking
ticket waited for a React hover state and came a beat late. A click no longer
leaves them showing; keyboard focus and an open menu do.
2026-10-14 13:24:27 +02:00
Maurice 7795061630 refactor(tours): move the planner additions out of the map, sidebar and page files that grew past their size baseline 2026-10-14 10:00:08 +02:00
Maurice 9adc87c002 chore(lint): lower the size, strict, hand-built service and knip baselines to what the split files hold now 2026-10-12 21:14:28 +00:00
Maurice 3dedfe6b63 chore(lint): lower the client baseline to the warnings the app code holds now 2026-10-12 19:57:48 +00:00
Maurice bdd8c08dd4 fix(planner): places put on a day, day names, day notes and trip details changed offline wait in the queue and sync once the connection is back
The day plan wrote straight to the API, so offline a place dropped on a day
snapped back with "Error assigning place", a renamed day or a new day note
failed, and a moved stop, a leg's travel mode or who joins a visit were lost.
Visits (put on a day, taken off, reordered, moved, travellers, leg mode), the
day's name, notes text and travel mode, and the day notes (add, edit, delete,
move) now go through assignmentRepo, dayRepo and dayNoteRepo and the mutation
queue: the cached day changes at once, a visit or note made offline gets a
temporary id that the edits after it wait for, and an online write held behind
an older queued one joins the queue, since none of these carry a version
token. A new order of a day that names a visit made offline is sent with its
real id, or without it when its create failed. Desktop, the phone timeline and
note sheet all write through the store, so both are covered.

The replay reads each answer for what it names: the day answer keeps the
cached notes and visits, the participants answer lands on its visit, and a
visit the server refuses with 409 (a Tour already on that day) fails instead
of waiting as a conflict no retry can solve.

The trip's name, description, reminder and the archive flag wait in the queue
too, from the planner, the phone and the dashboard. What only the server can
do says so instead of failing: new dates, a day count, a currency or a cover,
a new trip, a copy or a delete, members, guests, share and invite links, and
a new order of the days, which moves the dates of the bookings.

Refs #2601
2026-10-12 20:17:57 +02:00
Maurice 92c48fba22 fix(files): file notes, links, stars and the trash changed offline wait in the queue and sync once the connection is back
Every file write went straight to the API, so offline a star snapped back
without a word and moving a file to the trash failed with "Failed to delete
file". Notes, the place or booking of a file, a further link, the star, the
trash move, restore, delete for good and emptying the trash now go through
fileRepo and the mutation queue like the other trip data: the local copy
changes at once, and an online write to a file that still has an older one
queued joins the queue behind it, since file writes carry no version token.
The desktop file manager, its assign dialog and the phone's files tab,
menu, link and trash sheets all write through it.

The single file answers carry no ids of further links, so a replayed edit,
star or restore keeps the ones the local copy holds, and the link answer
({ success, links }) writes no row.

What only works online says so instead of failing: an upload shows that it
needs a connection, a place saved with newly picked files is stopped with a
hint, and removing a further link (its row id is known only to the server)
asks to try again once online.

Refs #2601
2026-10-12 19:32:16 +02:00
Maurice 64ceecefb3 fix(bookings): bookings, transports and stays saved offline wait in the queue and sync once the connection is back
Every booking and stay write went straight to the API, so offline a new
booking failed with "Network Error" and was lost, and a status tick, the
travellers or a moved transport snapped back. Bookings (add, edit, status,
travellers, delete, day order) and stays (add, edit, delete) now go through
reservationRepo and accommodationRepo and the mutation queue like places and
costs: a temporary row offline, held behind an older queued write online,
since neither carries a version token. Desktop, the phone sheets, the day
plan, the road trip nights and the PDF all write and read through them.

The replay reads the booking from `reservation` only, so the travellers
answer ({ travelers, reservation }) lands on the booking it names. A booking
or its stay that names a place or stay created offline is sent with the real
id once that one has synced, inside create_accommodation too, so an imported
hotel reviewed offline can be linked to a place made for it.

What only works online says so instead of failing: an AirTrail import, a
booking file import, the AI retry and an attachment on a saved booking show
that they need a connection, and a save with newly picked files is stopped
with a hint, since an upload cannot wait in the queue.

Refs #2601
2026-10-12 19:07:33 +02:00
Maurice cfdbfb49c0 fix(costs): expenses and payments saved offline wait in the queue and sync once the connection is back
Every Costs write went straight to the API, so offline an expense failed with
"Unknown error" and was lost. Expenses (add, edit, delete, split, paid tick,
reorder) now go through budgetRepo and the mutation queue like places and
packing items: a temporary row offline, held behind an older queued write
online. The replay reads the expense from `item` only, so the split answer
({ members, item }) keeps the local payers and the paid answer ({ member })
never lands in the expense table. An expense linked to a place or booking
created offline is sent with the real id once that one has synced.

Payments are queued without a local row and a hint says they show once
sent; the ledger reads the balances again when the queue is through, and
offline it shows the last balances it read (new Dexie table, v10). Receipts
need an upload, so an offline save with new ones is stopped with a hint.

Refs #2601
2026-10-12 18:14:30 +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 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 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 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 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 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 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 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
Maurice 242756708e ci(client): run theme:lint as a per-file ratchet with the missing token rules
theme:lint only reported a total and exited 0, skipped whole files by name and
never ran in CI. It now counts per file under src/ against
scripts/theme-baseline.json, which only shrinks, like lint:rtl: the colour
literals, arbitrary colour classes and numeric font sizes it already saw, plus
palette classes (bg-gray-100, text-indigo-600, bg-white), raw text sizes
(text-sm, text-[13px]) instead of the type tiers, z-index literals above z-50
instead of a step of the layering scale, and reads of the dark_mode setting.
The file exemptions are gone; the map, PDF and brand files carry their count in
the baseline instead. theme-lint-disable markers stay valid in a comment and
are counted against scripts/theme-disable-baseline.json, like rtl-lint-disable
in lint:rtl; a new one raises its file's entry by hand, where review sees it.
The comment handling lint:rtl already had moves into scripts/lib/source.mjs so
both read source the same way.
The check runs in the client-quality job.
2026-10-10 21:32:05 +02:00
Maurice f51a64017d refactor(client): type both trip map renderers against one MapViewProps contract
MapViewAuto took props: any and the Leaflet MapView destructured any, so a prop
one renderer learned and the other never heard of went unnoticed.
components/Map/mapViewContract.ts holds MapViewProps, derived from the GL
renderer's props and what the callers pass; MapView and the GL bindings in
glLazy declare satisfies ComponentType against it, and the GL renderer extends
it with its engine. The file imports types only, so check:gl-split is not
affected. Types only, no runtime change.

The types show two known gaps, recorded in the contract rather than papered
over: onMapReady is called by the GL renderer only, and callers pass pins
without trip_id (assignment and collection places) while both renderers type
their pins as Place; MapViewAuto bridges the latter with one commented cast.
2026-10-10 21:31:34 +02:00
Maurice 5fcb307270 test(client): hold the default MSW handlers to the @trek/shared contracts
contractHandler() parses the request body against the shared request schema,
answering 400 like the server's Zod pipe on a body it would refuse, and the
mock's answer against the response schema, failing on a factory that drifted.
The trips, budget, places, todo, tags and categories, day notes, packing,
reservations and assignments handlers and the vacay plan and colour updates use
it wherever shared has a schema; the offline bundle and the other vacay routes
have none yet and stay plain.
The typed bodies showed where the old handlers echoed request shapes back as
stored rows: payers and members are no longer echoed into a budget item, and
checked and is_private come back as 0/1 the way the server stores them.
2026-10-10 21:31:34 +02:00
Maurice e971c41d1a chore(client): delete the old budget panel components that only their own tests still imported
BudgetPanel, its summary, category table, inline edit cell, add row, per person
line, pie chart, ring avatar and the useBudgetPanel hook were replaced by the
costs panel and nothing outside their own tests imports them any more.
BudgetPanel.constants, BudgetPanel.helpers and BudgetPanelMemberChips stay in
use. The rtl, format, offline and eslint baselines drop the deleted files.
2026-10-10 21:31:33 +02:00
Maurice d118423570 fix(journey): the desktop gallery Upload button shows the files counting up again
Uploading photos into a journey gallery on desktop gave no sign that
anything was happening until the toast at the end; the Upload button
stayed clickable the whole time. Up to v4.0.0 the button sat inside the
gallery and showed a spinner with "Uploading done/total" while it was
disabled. When the button moved up into the page header (onRegisterUpload)
that indicator was dropped, and the count kept being tracked with no
reader.

useGalleryUpload now also returns the progress it tracks, the gallery
hands it up through onUploadProgress into useJourneyDetail, and the
page's Upload button is disabled and shows the spinner with
journey.editor.uploadingProgress while the files go, as it did before.

Red before the fix: src/pages/JourneyDetailPage.wiring.test.tsx
FE-JRN-DETWIRE-043 failed with "expected undefined to be [Function
Mock]": the gallery was never given an onUploadProgress callback and
the button never showed a count. FE-JRN-GALLERY-021 and
FE-JRN-GALLERYUPLOAD-007 cover the gallery and the hook. No existing
assertions changed.
2026-10-09 19:00:45 +02:00
Maurice e870270fb9 fix(planner): the plan view panel tabs say whether their panel is open and show a focus ring
In the plan view the tabs that collapse and reopen the left and right
panels gave a screen reader no open or closed state and showed no focus
ring when reached with the keyboard. The same tabs in the tour planner
had both, because only there LeftPanelTab got the "announced" option,
and the plan view's right tab was a hand copy of the left one.

LeftPanelTab becomes PanelTab with a side prop and always sets
aria-expanded and the focus ring. The plan view's right tab renders it
mirrored, so every panel tab shares one component. The offsets are
written with literal left and right keys so lint:rtl still counts them.
The page shrinks by 11 lines.

Red test: FE-PAGE-PLANNER-016b in client/src/pages/TripPlannerPage.test.tsx
failed on the old page with
expect(element).toHaveAttribute("aria-expanded", "true").
FE-PAGE-PLANNER-LEFTTAB-005 covers the mirrored right tab.

Changed assertions (they asserted the missing state):
- TripPlannerPage.leftPanelTab.test.tsx FE-PAGE-PLANNER-LEFTTAB-003:
  not.toHaveAttribute('aria-expanded') and not.toHaveClass
  ('focus-visible:outline') are now toHaveAttribute('aria-expanded',
  'true') and toHaveClass('focus-visible:outline') (title updated).
- TripPlannerPage.leftPanelTab.test.tsx FE-PAGE-PLANNER-LEFTTAB-004: the
  "announced" prop is dropped from both renders (title updated); the
  expectations are unchanged. The file renders PanelTab instead of
  LeftPanelTab.
2026-10-09 19:00:44 +02:00
Maurice 62f45e7934 chore(client): lower the ratchet baselines after removing the last copied blocks
The copies inside the map renderers, the location inputs, the day plan
sidebar, the trip planner page, the time picker, studio, the store
slices and a few small components are gone, and so is the markup the
two phone booking sheets, the two phone booking tabs, the desktop
booking dialogs, the vacay sheets and the storage panel drew twice.
lint:dup now holds every file at zero, and lint:size, lint:rtl and the
eslint warning counts go down with it. Nothing goes up.
2026-10-09 16:29:40 +02:00