On an install without the TREK Places index and without a Google key the
suggestions come from Nominatim, and autocompleteNominatim asked it without
the box the client had sent, so every suggestion was ranked globally. The
search fallback on the same install dropped the bias the same way. Both now
pass it on as a viewbox with bounded=0, which ranks results near the trip
without hiding anything further away.
Refs #2620
The suggestion list under the place search showed the index's raw ISO code
("福岡市博多区, JP") and listed a place twice when the index and its
OpenStreetMap layer both carried it, which the search list after Enter
already merges. Suggestions now name the country through Intl in the
language of the search, and a layer row at the spot of an index row with a
matching name is dropped by the same rule mergeSearchResults uses, now
shared as samePlace.
Refs #2592
The create and update answers, the reservation:updated broadcast and the MCP
reservation tools all hand back the single booking read, and that read never
loaded reservation_day_positions. The day list puts a booking moved on a day by
that slot before its time, and the client replaces the booking with the answer,
so a booking moved on a day jumped back to its time after every edit until the
next reload. The read now attaches day_positions in the shape the trip list
already sends.
Refs #2631
A 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
A stop with a start time but no end time went out as a VEVENT without
DTEND. Calendar clients fill in their own default length for that, mostly
an hour, so a 20 minute stop showed up as an hour. The event now ends at
start plus the place's duration_minutes when that is set and above zero,
carried over midnight like the stay markers. An explicit end time still
wins, so start and end under an hour stay as they were. The trip download,
both calendar feeds and the MCP export share this builder.
Refs #2641
Marking a region wrote its country with source 'manual', the same as ticking
the country off by hand, and removing the last region then took the country
away whatever its origin, Dawarich confirmations included. A country that
only comes in through a region is now stored as 'region', marking it visited
afterwards turns it into the user's own mark, and the cascade in unmarkRegion
only runs for a country without such a mark. Readers still see 'manual' for
those rows, so the country detail, the MCP resource and the plugin RPC answer
as before. Rows written before this change stay 'manual' and keep their
country.
REST, MCP and plugin RPC share the service. The atlas popup on desktop and
phone now asks the server whether the country is still marked before it
drops it from the map instead of guessing.
Refs #2597
NODE_ENV="production " passed the boot check but matched none of the
exact production comparisons, so the web UI was never served and every
page answered {"error":"Cannot GET /"}, while the banner showed a clean
"production". The boot import now trims these three variables in
process.env before anything else reads them, so Express, the plugin
children and readEnv() agree, and reports each change as a process
warning. Casing stays as it was. A padded "test" is only reported, so an
instance cannot switch to an in-memory database after an update. The
banner prints NODE_ENV as set instead of lowercasing it.
Refs #2565
A Google place URL carries two points: @lat,lng is the centre of the map
view, the !3d!4d data param is the place itself. extractCoords took the
camera first, so a pasted share link put the place around 170 m off and
reverse geocoded the wrong street. The data param is now read before @;
the order of @ and ?q=/?ll= is unchanged. REST and the resolve_maps_url
MCP tool share the resolver, so both pick this up.
Refs #2635
Moving a saved place to another list takes it out of the source list, but
updatePlace only asked for edit rights there, so an editor could empty a
shared list they may not delete from. The source now takes owner or admin,
like a delete, and the target still takes edit rights. REST and the
update_collection_place tool share the service path. The Move to list
action on desktop and phone follows the delete permission.
DAY-SVC-048 and DAY-DEL-011 pinned the old reorder, which handed a day
without a date the first slot's date; they now expect it to keep none and
the day it passes to keep its own. OIDC-SSRF-003 reads oidc.service.ts as
text and missed the token call once Prettier put a trailing comma after its
hop budget of 0; the pattern takes the comma.
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.
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.
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.
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.
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).
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.
The sort-imports plugin moved bare imports along with the rest, which on
src/index.ts would have put reflect-metadata after the modules that need it
and the env validation ahead of dotenv. With importOrderSideEffects off, a
side-effect import stays in place and the imports between two of them are
sorted on their own. The three test files that were already formatted gain
the blank line the new grouping writes after reflect-metadata.
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.
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.
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.
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.
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.
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.
sync_trip_documents and the five manual school-holiday tools carried no demo check of their own before the gate moved into the registry, so a demo account could call them. The gate now lets exactly those through, and a test pins the list. upload_trip_file keeps the gate's order: a demo account is refused before its trip access is checked, where the tool used to answer a trip it cannot see with the no-access result first.
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.
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.
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.
A hand-built service gets a FakeRealtimeService where it took a RealtimeService, a Nest testing module overrides the provider, and a suite over buildApp() spies on the container's instance (spyOnRealtime). The 167 vi.mock factories on src/websocket are gone, RealtimeService reads the socket registry in ws-state.ts directly, and the re-export stub src/websocket.ts is deleted. The facade's own suite now checks what reaches a socket instead of which module function it called.
tests/setup.ts mocks src/config once with tests/helpers/test-config.ts. A suite that needs other values calls overrideTestConfig(), and the setup file restores the defaults after every case. The 191 per-file factories are gone; config.test.ts takes the real module through vi.unmock, and the few suites that signed tokens with their own literal secret read it from TEST_CONFIG.
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.
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.
PERMISSION_ACTIONS, PermissionLevel, PermissionKey and evaluatePermission()
move to shared/src/permissions. PermissionsService decides through
evaluatePermission and re-exports the catalog for its importers, and
@RequirePermission takes a PermissionKey, so a misspelt action fails to
compile. Levels, defaults and every verdict stay as they were.
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.
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.
Non-null assertions become explicit guards, the two demo-gate loops collect
their answers with Promise.all, and the MapsService imports the maps tests no
longer use are gone. The src counts the refactors lowered are taken off the
warnings baseline.