Commit Graph

141 Commits

Author SHA1 Message Date
Maurice 2e1dca9fe9 feat(tours): let a background check hint at changes of your own wanderer trails
Settings, Integrations, wanderer gains an optional background check of the
trails you imported, off until you switch it on. While on, TREK reads the
short record of each such trail at most once a day (never its GPX file) and
leaves a hint when the trail may have moved on: "may have changed" when only
wanderer's change time moved, "changed" when its name or description proves
it, and "not checkable" when the trail is gone, no longer yours or carries no
change time. Nothing is ever said to be unchanged. A new hint arrives as a
notification that opens the tour (a new ?place= link in the planner); the
tour's provenance shows it with the source check right beside it, and the
card lists all open hints in a dialog, each leading to its tour. The check
reads the source in full as before and spends the hint; taking a change in
stays the confirmed reimport. No tour is ever changed or deleted.

The check only asks through your own connection, only about trails your
account imported into trips you can still open, and stops when you switch it
off, remove the connection, wanderer refuses the token (until a test passes
or a new token is saved) or the admin switches wanderer off; hints and
snapshots stay. A run asks at most 25 connections for at most 10 records
each, one after another, and an instance that fails (an outage, a rate
limit) is left alone for an hour, doubling up to two days.

Server: migration 046120 adds the switch and the run bookkeeping to
wanderer_connections and the tour_source_hints table (one row per snapshot,
for its importer, gone with it). WandererWatchService and an hourly
wanderer-watch job on the CronRegistrar live in the tours domain and read
through WandererService; GET and PUT /api/integrations/wanderer/watch and GET
/api/trips/:tripId/tours/wanderer/sources/:placeId/hint (place_edit, like the
check) answer with @trek/shared contracts. A new user notification event,
wanderer_source_hint, can be muted per channel. Saving a new token now clears
what the last check said about the old one. There is no MCP tool, as for the
rest of the personal wanderer connection. All 27 locales carry the strings,
and the admin wiki page says what the check does.
2026-10-20 15:35:32 +02:00
Maurice f833a05461 feat(tours): point a tour at its trip's packing list
A tour can now point at the packing list of its trip, as a whole or at one
of its categories (say "Hiking gear" for a hike), and shows it in its detail.
The new Packing list section reads the list live as the user may see it: the
backpack tile with the item count on its corner and the category's own
colour, how many are packed with a thin progress bar, the first items ticked
off or not with their quantity, how many more there are, and a button that
opens the trip's Lists tab. Who may change the tour links one, changes it or
lets go through a dialog that lists the whole list and every category with
its colour and item count, and writes only on its confirm.

It is a reference, never a copy: the items stay in Packing, are ticked off
and edited there, and each traveller sees in the tour only the items Packing
shows them, so an item kept to oneself never shows through a tour. A
category that is renamed, cleared or only holds items of others reads as
missing, an empty one as empty, a link that could not be read says so with a
retry, and while Lists are switched off the link stays, dimmed, and can
still be let go. Removing the link, or the tour, leaves the list and every
item as they are. Nothing suggests or changes an item by itself.

On the server the link is a row of its own in the tours domain
(tour_packing_links, migration 046110), one per tour and gone with it.
GET, PUT and DELETE /api/trips/:tripId/tours/:placeId/packing-link read it
with the trip and change it with place_edit; linking needs the Lists addon
and, for a category, an item of it the user can see. The writes answer the
tour with its link and tell the trip through tours:changed, so the offline
client queues them as writes of the tour and replays them like one. A copied
trip keeps its tours' links, a tour saved as new or taken over does not.
The MCP tools get_tour_packing, link_tour_packing and unlink_tour_packing run
over the same service and checks; the read also hands the tour's facts as
context for suggesting what to pack, which only becomes an item through the
packing tools. The empty category placeholder now lives in @trek/shared so
both sides leave it out alike. All 27 locales carry the new strings, and
the wiki describes the link and the tools.
2026-10-20 15:35:08 +02:00
Maurice 3fe34061fd feat(tours): keep whole tours in collections and take them into trips
A saved tour can now be kept in a collection, apart from any trip, and
later made into a tour of another trip. The bookmark button in a tour's
footer and in the planner's head (waiting for the save while there are
unsaved changes) opens a dialog with every list of the user; a list they
may only read stays in it, dimmed with a lock and a tooltip. The list
keeps an independent copy: the line, the type, every measure and setting,
the waypoints in order with their roles, the origin, and the trip and tour
it was saved from as they were called then. Its days, its links to places
of the trip and its obstacle decisions stay in the trip, and later changes
to the tour do not reach the copy.

A collection shows its kept tours as cards above its places: the grade
tile with the source badge, measures and the waypoint count as badges,
the trip it came from, and who kept it. The owner or an admin of the list
can remove one after a question; tours made from it stay. "Add to a trip"
shows the kept tour on a map with its facts and numbered waypoints, lists
the user's trips (the ones they may not add tours to dimmed), and asks
outright whether the waypoints come along. "Line only" keeps the line as
saved and adds no waypoints; a tour kept without a line can only bring
them. The new tour has an id of its own, no days and no new places, and
names the trip and tour it was saved from; the dialog then offers to open
that trip. Neither the collection nor the original tour changes.

On the server the kept tours are a table of their own in the tours domain
(collection_tours, migration 046100), each row written whole once. Every
copy, the in-trip "save as new tour", the take-over from another trip and
both directions here, now goes through one copy model (tour-copy.helpers)
instead of a second one. Reading and removing follow the collection's
roles, keeping one also needs access to the source trip, and taking one
over needs place_edit on the target trip; the kept tour is read again
inside the write, so one removed meanwhile leaves no half-made tour. The
MCP tools list_collection_tours, get_collection_tour,
save_tour_to_collection, take_over_collection_tour and
delete_collection_tour run over the same service and checks. All 27
locales carry the new strings, and the wiki describes the flow.
2026-10-20 15:32:47 +02:00
Maurice bde2ad2ca8 feat(atlas): sum up the tours you have done, by period and type (STA-02)
With the Tours addon on, the Atlas panel gets a third tab, Tours, beside
Stats and Bucket List: what you have done on your tours across all your
trips. A period (all time, this year, the last 12 months or an earlier year)
and a tour type narrow it; the tab shows the distance large with the number
of tours at its corner, the ascent and descent, and the types with their
distance. "Show tours" opens every tour of the period in a dialog with the
same filters, the sums, the split by type and one row per tour.

What counts is said, never guessed. A tour counts once the day it is on is
over, with the values of the planning, badged "Planned" and explained as
not measured. A linked actual track (ACT-01) replaces those values for its
execution, badged "Recorded" and named, and is never added on top nor
counted as a tour of its own. A tour on several days counts once, a whole
route does not count beside the stages planned from it, a paddle tour sums
no heights, and a value nobody knows stays out of the sum, which then reads
"at least". Only the user's own part counts: a day names them among its
participants, or names nobody on a trip of their own; being able to open
somebody else's trip makes none of its tours theirs. Tours still ahead, on
no day or only on undated days are named as not counting. A tour that fell
through can be left out of the sums ("Leave out"): it stays in the plan
and in the list, dimmed, and "Count again" brings it back. Only the user's
own numbers change.

Server: GET /api/tours/activity-stats answers the user's activity entries
across every trip they can open, and PUT /api/tours/activity-stats/exclusions
leaves a tour out or counts it again (the tour must be in such a trip, else
404). A tour_activity_exclusions table keeps the exclusions per user and
tour. The sums are added up by a shared summary in @trek/shared, so the
Atlas and the assistant say the same numbers. MCP: get_tour_activity_stats
and set_tour_activity_excluded over the same service. The tab reads live
and needs a connection, like the rest of the Atlas statistics.
2026-10-20 15:23:55 +02:00
Maurice d0cc6fc2f4 feat(tours): link the track a tour was actually done with, and show its own values (ACT-01)
A planned tour can now be given the track it was actually done with. In the
tour's detail, "Add actual track" opens a dialog that asks for the day the
tour was done on (or none) and a recording of the trip: an import nobody
planned over, or a new one through the existing GPX import, its preview
preset to the tour's type. What the choice measures is shown before anything
is written, and only "Link" writes. The linked track keeps its own name and
source; the detail shows its distance, heights, profile and, only where the
file carried time stamps, the time it took, marked as actual values next to
the untouched plan. A track can be changed or unlinked again.

Server: a tour_activities table points a tour's execution (a day assignment,
or none) at a recording of the same trip, and the tours GPX import keeps the
first and last time of a track whose every point carries one. The values are
read from the recording's own line on every read, so a changed recording
updates them while the plan's line, measures and revision stay as they were.
Every check runs inside the write: the recording must be an import of this
trip not planned over, documenting no other tour; the day must be one of this
tour; a refusal names its reason and changes nothing. PUT and DELETE
/api/trips/:tripId/tours/:placeId/activities answer the detail, which now
carries the actual tracks; the list names them per tour and per recording.
MCP: assign_tour_track and unassign_tour_track over the same service and
checks, get_tour_plan reads the values without the line.

A recording linked as a tour's actual track is no longer counted as a tour of
its own in the trip's tour summary, and the GPX preview imports a city walk
as one instead of as a hike.
2026-10-20 15:22:55 +02:00
Maurice f4533fc69b feat(tours): save_tour_change saves a confirmed change to a city walk whole or not at all
An assistant can now save the change prepare_tour_change prepared, once the
traveller said yes to it exactly as shown. Until now a proposal could only
be looked at; the traveller had to redo it by hand in the planner.

- The save names the proposal: the same changes, the revision it was
  prepared on, the proposal_id the preparation answered and confirmed true.
  Anything else, a change altered on the way or no yes, is refused and
  nothing is saved.
- Place editing on the trip and the tour are checked again before anything
  is written. A tour changed since the proposal was prepared is a conflict
  on the tour's one revision (no second one), telling the assistant to
  prepare the change anew instead of writing over newer edits. The changes
  are laid on the saved tour once more inside the write, so a station
  visited or skipped meanwhile, or a place of the trip gone, refuses the
  save with every reason.
- The change goes through the write the planner's save uses, in one
  transaction: line, stations, keys, measures and visits together or not at
  all, and the trip is told only after the commit. Stations kept keep their
  keys and visits. Where the router gives no way on foot the walk keeps its
  stations only and waits for the planner, as a created one does.
- Every save is kept as a step of the tour (tour_change_steps): the
  proposal, the revision it was prepared on and the one it raised the tour
  to, the changes and the tour as it was before them, so the step can be
  followed and taken back later. Nothing of the conversation is kept.
- A new walk has no revision to name and stays with create_city_walk; both
  tool descriptions and the wiki say which is which.
2026-10-20 15:14:29 +02:00
Maurice d5d2d6b25e feat(tours): city walks in the contract and on the server (CW-01, CW-02, CW-03, CW-09, CW-10)
A tour can be a city walk. Its points are stations that keep a key across
saves, a name, a stay (0 for a point passed through, empty when unknown), an
importance and a protection; the visits of a walk's stations are kept per day
assignment, so another day or a copy starts with nothing visited. A station
that is gone takes its visits with it. An assistant reads how far a walk got
on a day, what is open and which times are known.
2026-10-20 07:09:56 +00:00
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 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 80b6739362 feat(journey): a trip can link a journal and open it to the members it picks
A trip can now show one journal from the Journey addon as its own: any
journal its owner picks, and one journal on as many trips as they like.
The trip members chosen for the link get into the journal as viewers or
editors, see it in their own journal list and follow it live, for as long
as the link holds them and they stay on the trip.

That access is never copied into journey_contributors. It is read where
journal access is read: the contributor repository answers with the
stronger of a direct role and a link role, the journal list and the live
update recipients take the link members in, and the photo access check
goes the same way. Every one of those reads starts from linkReach, which
only counts members still on the linking trip, so unlinking, deselecting
someone and their leaving the trip all end exactly what the link gave.
A member who leaves is also taken off the link, so coming back later does
not quietly reopen the journal.

Linking or changing the members takes the journal's owner and the new
journal_link trip permission (trip owner by default); unlinking takes
either. /api/trips/:tripId/journal answers the link as each member may see
it, /candidates says who a journal would be opened to for the first time,
and four MCP tools do the same over the same service. A trip:journal:changed
event tells the trip to look again.
2026-10-18 15:59:33 +02:00
Maurice 2c3b20bb57 feat(tours): paddle portages and locks, paddle time, wind at the start and obstacle data from OpenStreetMap
PAD-03 portages and locks on planned tours and recorded tracks, PAD-07 paddle time with portage and lock stops, PAD-08 wind forecast for the start, PAD-05A regional obstacle import from Geofabrik extracts with admin endpoints and a monthly refresh, PAD-05B first cut of the obstacle layer on Leaflet, GL and 3D. Work in progress: checklist (PAD-06), check bench (PAD-09), admin UI, translations and tests follow.
2026-10-16 23:44:58 +02: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 b33727ec75 Tours: no non-null assertion, useless escape or awaited loop in the tour and trip copy paths 2026-10-15 23:25:16 +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 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 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 26833f374c fix(tours): keep the places repository at its size and format baselines 2026-10-14 10:23:22 +02:00
Maurice dbee1664af fix(tours): hold the client warning ratchet and the server format baseline flat after the rebase onto dev 2026-10-14 09:11:47 +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 36e8b299f7 chore(lint): lower the server test baseline to the warnings the tests hold now 2026-10-12 19:56:44 +00:00
Maurice 66e574fcc0 fix(llm-parse): the local model list and pull send the saved api key to an ollama behind an auth proxy
The admin screen's model list (/api/tags) and pull (/api/pull) went out
without the Authorization header that the extraction and /api/show already
send, so an Ollama behind a Bearer proxy answered 401 and the list came back
as a 502. The controller now adds the key saved with the local provider, read
server side through LlmConfigResolver.readLocalServerAccess, and only when the
requested base URL reaches the same server root as the saved one, so another
address in the form never receives it.

A 401 or 403 now says whether the key was rejected or none is saved for that
address, and the admin form shows the server's reason instead of the axios
status line. A server without /api/tags (LM Studio, vLLM) is asked for its
OpenAI-compatible /v1/models instead, and the list body is read with a size
cap and checked before use.

Refs #2649
2026-10-12 21:04:23 +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 ef150439f9 fix(vacay): leave days follow a moved trip as a whole block and never land on blocked days
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
2026-10-12 14:12:36 +02:00
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00
Maurice f4e518b2df build(server): Prettier leaves the generated entity files to their generator
src/db/entities/ is written by scripts/generate-entities.ts, and
check:entities holds every file byte for byte to its output, which puts the
MikroORM import first where Prettier's import sorting puts it last.
Formatting them would fail that gate, and npm run format would do it on any
run. lint:format no longer scans the directory (its 133 entries leave the
format baseline) and a .prettierignore keeps the format script off it.
2026-10-10 21:21:16 +02:00
Maurice 947d51cef7 build(server): a test-sql-allow marker covers the first call of the statement Prettier broke up below it
Following the method chain was not enough: Prettier also wraps a cast read
in parentheses (const n = ( above db.prepare(), puts a for head above its
body and nests the call in an argument list, each leaving it more than one
line below its marker. When the line under a marker holds no call, the
marker now excuses the first call further down in that statement, up to
the line ending it with a semicolon, never past a blank line and at most
eight lines down. Every file counts the same as before on today's tree and
on the formatted one.
2026-10-10 21:05:16 +02:00
Maurice 97f4784790 refactor(server): keep three suppressed lines whole so Prettier cannot move them past their directive
Prettier wraps the emoji regex onto the line after its declaration and
spreads two any-cast lines in the plugin tests over several lines, which
would leave the casts outside the eslint-disable-next-line above them. The
regex directive now sits on the line right before the literal, the dev-link
controller takes never instead of any for the collaborators it ignores, and
the supervisor test reaches the private kill/onExit through one cast.
2026-10-10 20:52:57 +02:00
Maurice 5c198f2848 build(server): lint:mcp-zod counts a z. call that a line break splits from its dot
Prettier breaks a long schema into z on one line and .object( on the next,
which the count read as nothing: formatting the tool files would have taken
134 hand-written fields out of the tally. Whitespace before the dot counts
now. Four files already held such a split call, so their entries rise to
what they really hold (atlas 23, doc-sync 1, help 4, transit 16).
2026-10-10 20:49:22 +02:00
Maurice 0903dcd5ab build(server): a test-sql-allow marker covers the statement below it when Prettier breaks it into a chain
Prettier splits a long db.prepare('...').all() into db on one line and
.prepare( on the next, which put the call two lines below its marker and
made every parity oracle in the repository tests count as a raw statement.
The marker now also covers the method-chain lines that continue the
statement right below it, up to the first line that does not start with a
dot.
2026-10-10 20:48:33 +02:00
Maurice 939b13859d test(server): the booted test app stubs the Nominatim reverse lookup instead of rebuilding the photo capture service
bootTestApp replaced JourneyPhotoCaptureService with a factory calling its constructor positionally, only to hand it an offline geocoder, so a dependency the service gained broke the boot of every MCP suite. The container now builds the service itself and the OsmClient's reverse lookup answers that nothing is known, which also keeps every other reverse lookup a booted suite reaches inside the process.
2026-10-10 20:16:14 +02:00
Maurice ac918d362f build(server): the test-mock and hand-built-service ratchets read code, not comments, and see the import() and alias forms
lint:test-mocks missed vi.mock(import('../src/config'), ...), the typed form Vitest takes, and counted mocks quoted in doc comments, which left slack a real mock could take. lint:test-new-service missed a service built under an import alias or as new (Name)(...). Both now strip comments first and recognise those forms; the baselines lose the entries that were only comments.
2026-10-10 20:16:14 +02:00
Maurice ef68c62d80 build(server): the response-contract exempt marker only excuses a handler that writes through @Res()
A response-contract-exempt comment counted any handler as covered, whatever it returned, so one comment line let a JSON handler past the ratchet. The marker now counts only on a handler taking @Res() or @Next(). The five vacay holiday proxies return the provider's JSON, so they lose their markers and are counted again until they declare a schema.
2026-10-10 20:16:14 +02:00
Maurice 8187a423e2 fix(admin): an invite for a trip that does not exist answers 404 instead of an empty 201
createInvite returned { error: 'Trip not found', status: 404 } and the admin controller never looked at it: POST /api/admin/invites with an unknown trip_id answered 201 with an empty body and wrote an admin.invite_create audit row for resource 'undefined'. The service throws the DomainError now, so the route answers 404 { error: 'Trip not found' } and writes neither the invite nor the audit row.
2026-10-10 20:16:14 +02:00
Maurice 8fb888c8a3 build(server): lint:service-http counts envelopes in every domain file and every spelling of a branch on one
The ratchet only read *.service.ts for status literals and one branch shape in controllers and tools, so an envelope in a helper, impl or provider file, a branch written as result.error === 'not_found', and RPC handlers all went uncounted. It now counts the literals in every file under src/nest outside the HTTP layer, and comparisons, negations, optional chains and ternaries on a returned error in controllers, tools and RPC handlers. The baseline takes what the wider count finds.
2026-10-10 20:16:13 +02:00
Maurice ebd8a4f8c9 build(server): lint:boundaries counts the RPC and MCP layers as domains and sees getRepository calls
src/nest-rpc, src/nest-mcp and src/mcp import domains and are imported by them, but the domain map only knew src/nest, so their edges dropped out of domainCycles and their repository injections out of foreignRepositories. Each layer now counts as one domain. The repository rule reads the syntax tree for @InjectRepository(X) and for getRepository(X) on an EntityManager, so a comment no longer counts and a foreign read through the EntityManager no longer passes. The baseline takes the cycles and the repository reaches the wider view finds; settings -> Users goes, it was a comment.
2026-10-10 20:16:13 +02:00
Maurice 0f6ae8b928 refactor(vacay): the invite and share tools ask a kernel user lookup instead of injecting UsersRepository
VacayMcp read the caller's username and email straight from UsersRepository, a table the auth domain owns. UserLookupService in auth-core answers exactly that, so vacay neither imports AuthModule nor injects a foreign repository, and its foreignRepositories entry goes.
2026-10-10 20:16:13 +02:00
Maurice 4427c435a8 ci(server): module mocks, hand-built services and test file length in tests/ may only shrink
lint:test-mocks counts vi.mock/vi.doMock of src/config, src/nest/realtime and src/db per file under tests/, lint:test-new-service counts new XService(/new XRepository( there with every class whose constructor takes nothing allowed (derived from src/ and tests/helpers/), and lint:size holds files under tests/ to 2000 lines against their own baseline. Both counters share scripts/lib/count-ratchet.mjs, which gains a prepare hook for a check that reads the tree first. The three run as steps of the existing server lint job; the CLAUDE.md files and the module guide describe the test helpers they point to.
2026-10-10 20:16:12 +02:00
Maurice 9bb5854dbb test(server): the suites left in tests/unit/services move next to the code they cover
src/services is gone, so the folder named a layer that no longer exists. The airtrail suites go to tests/unit/nest/integrations, the crypto, cookie, avatar, password policy, timezone and conflict helpers to tests/unit/nest/common, the kitinerary mapper to booking-import, the photo media suite to photos, the atlas bundle guard to tests/unit/nest and the two migration suites to tests/unit/db. Only the relative import paths change, and the format baseline follows the new paths.
2026-10-10 20:16:12 +02:00
Maurice 6ce15157a1 test(server): MCP suites run on the booted container and unit suites take services from their module
createTestModule (tests/helpers/test-module.ts) builds a Nest testing module of the domain modules under test over the suite's test db, with a FakeRealtimeService and provider overrides for the collaborators a suite controls. bootTestApp (tests/helpers/test-app.ts) boots AppModule once per file, with recording realtime, fixture storage and an offline geocoder, and the MCP harness takes McpRegistryService from it inside a request context instead of hand-wiring roughly eighty services. The days, day removal and day notes service suites take their subjects from DaysModule and DayNotesModule.
2026-10-10 20:16:11 +02:00
Maurice 848655d593 chore(plugins): the host plugin API file holds its size baseline again
Tightens the two comments the rawBodyBase64 field and the type parity note
added to runtime/plugin-sdk.ts, lowers its lint:size entry, and points the
shared guide at the client files it names by their full path.
2026-10-10 20:16:09 +02:00
Maurice e21f2d97b6 feat(contracts): vacay and journey routes declare their response schemas
The vacay plan, calendars, years, entries, stats and shares, and the
journey list, create, tracks, stats, share link and every success answer
get response schemas in shared and a @ResponseContract on their handler.
The holiday proxies pass the provider's answer through and are marked
exempt. The journey e2e stubs return whole rows, as the service does.
2026-10-10 20:16:08 +02:00
Maurice 8e01dbb92f feat(contracts): todo, settings, collab and file routes declare their response schemas
shared gains the response schemas these routes answer with (rows in their
storage spelling, as the routes have always sent them) plus the common
success and empty answers, all exported for the client to type its calls
with. Every JSON handler in the four controllers carries its
@ResponseContract; the file download, which streams, is marked exempt. The
files e2e fixture table gains the note_id column the real one has.
2026-10-10 20:16:08 +02:00
Maurice 87071bf738 feat(server): @ResponseContract checks a handler's responses against its shared schema in tests
The decorator records the @trek/shared response schema a route answers
with. Under NODE_ENV=test it adds an interceptor that parses every response
and fails the request with a logged 500 naming the handler and the issues,
so the e2e and integration suites catch a response that drifted from its
contract; in production nothing is added to the request path and the body
is never rewritten. lint:response-contracts holds the handlers still without
one to a per-file baseline that only shrinks, and runs in server-quality.
2026-10-10 20:16:08 +02:00
Maurice 81ec7dd28d fix(plugin-sdk): validate checks apiVersion and the manifest rules exactly as an install does
The SDK accepted any number as apiVersion while the host refuses anything
but a positive integer no newer than its plugin API, a false green for an
author. The manifest patterns, sets and caps now live in the host's
protocol/manifest-rules.ts, and gen-plugin-facts writes them and
PLUGIN_API_VERSION into the SDK, so check:plugin-facts covers them. One
manifest corpus is judged by both validators in a server test (the SDK may
only be stricter, case by case), and a typecheck test holds the SDK's plugin
API types to the host child's copy, which gains rawBodyBase64.
2026-10-10 20:16:08 +02:00
Maurice 8d716aec2f refactor(budget): money arithmetic lives in shared and the settlement uses it
toMinor, sumMinor, splitEqualShares and currencyDecimals move to
shared/src/money. The budget service and its MCP tools call them instead of
private copies, and the share table both sides were pinned to becomes the
spec of the shared function. Stored values and every computed figure stay
the same.
2026-10-10 20:16:07 +02:00
Maurice e5a2af5999 chore(server): size, strict and warning baselines follow the error-envelope work 2026-10-10 20:16:07 +02:00
Maurice e7c1acfb47 chore(server): lint:mcp-zod and lint:service-http hold inline tool zod and error envelopes to a shrinking baseline 2026-10-10 20:16:07 +02:00
Maurice 2c3fb15263 style(server): format the files the health lanes added or moved
Prettier on every new or moved file that was not formatted, so lint:format
holds them; the files taken off the format baseline are the ones that moved
or were formatted on the way.
2026-10-10 20:16:04 +02:00
Maurice a28044dbd4 chore(server): lint:boundaries holds the user-connection repositories and the journey read at their new count
The module comment no longer spells an @InjectRepository call the scanner took
for a foreign Users injection in nest/database, and journey's Users entry is
gone now that it reads the Immich flag through UserImmichRepository.
2026-10-10 20:16:04 +02:00