Commit Graph

10 Commits

Author SHA1 Message Date
Maurice 94c369b913 feat(journey): prepare the recorded days of a journey's trips in the background, switched on per journey, with each day's state
Journey Settings get "Prepare recorded days automatically" under GPX Tracks,
off by default and set by the owner. With it on, the Dawarich background
check also reads the linked trips of the journey for the owner and every
editor with a connection of their own, within the same 400 day reach and
windows as the trip sync, and only for new and changed stays: a trip the
person travels on is not asked twice, and the stays of a trip they are not
on are filed under no trip. Nothing is written into the journey; the stays
wait at their day, and a stay taken over or left out is never offered again.

Under the switch the caller sees when their stays were last fetched, or why
not (no connection, no dated trip, background check off, the reason of the
last failure beside the state still shown), with a "Fetch now" that the
switch also runs when it is turned on. A day of the trips that is not final
yet carries a dot on its take-over button, on the phone in the stay sheet,
and the tooltip says whether it is still running, was fetched before it
ended, was not fetched yet or shows an older state after a failed refresh.
The day review says so for today too.

Server: migration 046180 adds journeys.auto_prepare_recordings and
journey_recording_prep (the last attempt, last success and last error per
journey and person). JourneyRecordingPrepService in the journey domain lists
the prepared trips and keeps the status; DawarichSyncService walks those
trips in the same loop as the person's own and records each journey's
outcome. GET /api/journeys/:id/recordings/prep answers the caller's status
under a @trek/shared contract, the MCP tool get_journey_recording_prep reads
the same, and update_journey takes auto_prepare_recordings like the PUT
contract. The column stays out of the plugin output. All 27 locales carry
the strings; the Journey, Dawarich and MCP tool wiki pages describe it.
2026-10-21 17:39:33 +02:00
Maurice 7c1de6ed82 feat(journey): take a recorded day over with its stays, chosen, renamed and joined into drafts or one day report
A journey day now has a list button in its header beside the route, with the
number of stays still waiting, and the row of the day's Dawarich stays gets a
"Take over day" button; on a phone the journey's More options offers it for
the day of the card shown. The dialog reads the day again from the caller's
own Dawarich and shows the route and the stays on one map, numbered in the
order they were lived and pinned in the colour of the day, beside a list with
times, durations, the stays Dawarich only guessed or is unsure about, the ones
without a position, the entries the journal has on the day and how many people
will see the drafts. When Dawarich does not answer, it says why and shows the
stays of the last sync.

Open stays start chosen, the same place twice in a row starts joined as one
entry under a title and a chosen map position, any two neighbours can be
joined or kept apart, a name can be corrected and a stay left out. "Day
report" turns every chosen stay into one entry that lists them with their
times, at a chosen stay or off the map. The day's route can go along when the
journey keeps none yet. Nothing is written before "Take over N drafts", and a
blank name or title holds it back with the reason. Stays left out are dismissed
and can be restored in the stay list.

The take-over writes drafts only, never touches what the journal holds, and is
one transaction: every stay is checked to be the caller's and of that day, the
entries, the stays they hold and the marked suggestions commit together, and
the entries are announced after the commit. A stay the journey holds already
is reported instead of written twice, so a retry after a lost answer is
harmless; a failed attempt reads the day again and keeps the choices, and a
route that fails after the drafts can be kept again alone.

Server: migration 046170 adds journey_entry_stays (one row per stay of an
entry with its own name, place and times, unique per journey, source, person
and visit, gone with its entry), owned by the journey domain through
JourneyEntryStaysService. The single stay accept records its stay there too,
over REST and MCP alike. DawarichSyncService.refreshDay reads one day's visits
and upserts them without flagging anything missing; DawarichDayStaysService
lists and settles a day's stays. JourneyDayImportService in journey-recordings
serves POST /api/journeys/:id/recordings/:date/stays/review and /import with
@trek/shared contracts, and the MCP tools review_journey_day_stays and
take_over_journey_day_stays run on the same service and checks. createEntry
can write without announcing for a caller that announces after its commit.

The desktop dialog and the phone sheet of the route (JRN-01) and of the
take-over share one frame each. All 27 locales carry the strings; the Journey
and Dawarich wiki pages and the MCP tool list describe it.
2026-10-21 16:31:40 +02:00
Maurice 1389fe6984 refactor(place-import): file and shared-list readers move into their own domain over one gpx and one kml codec 2026-10-10 20:16:03 +02:00
Maurice 094e0b28f8 fix(booking-import): write an imported venue with its booking in one transaction
confirm() created the venue's place row in its own transaction and
broadcast place:created before the booking and its cost were written in
a second one. When that second write failed, the item was skipped but
the trip kept a place no booking pointed at, and clients had already
been told about it.

Per item, the venue and endpoint geocoding, the day lookups and the
cost's frozen rate still run first, outside any transaction. The place
row, the booking and its linked cost then go into one transaction
through the new ReservationsService.createWithCostInTx, the core that
createWithCost itself now wraps. place:created, reservation:created,
accommodation:created, the stay mirror and the cost events are sent
only after the commit. Each item keeps its own transaction, so a
failing item still leaves the others imported; confirm() is tagged
@txIndependent for that and leaves the lint:tx baseline.
2026-10-08 18:42:10 +02:00
Maurice 9cebadf67c fix(memories): save the Immich connection and auto_upload in one transaction
PUT /api/integrations/memories/immich/settings saved the connection through
one service call and then wrote auto_upload through a second one from
the controller, each committed on its own. A failure between them left
a new connection with the old upload choice.

saveImmichSettings takes the auto_upload flag and writes it with the
connection (or the disconnect) inside one transaction, after the SSRF
check that does the DNS lookup. An absent or non-boolean flag still
keeps the stored choice, a refused URL still writes nothing, and the
response is unchanged. The controller's write sequence leaves the
lint:tx baseline, and the MemoriesService facade loses the
immichSetAutoUpload pass-through it no longer needs. No MCP tool writes
these settings.
2026-10-08 18:42:10 +02:00
Maurice f40d92a69e fix(trips): write the budget currency rebase with the trip row
TripsService.update rebased the budget onto the new currency in its own
transaction and only then wrote the trip row in a second one. When the
day grid rebuild, the booking resync or the leave shift threw after the
rebase had committed, budget items and settlements stayed pinned and
re-frozen against a currency the trip never took (#1543).

rebaseTripCurrency is split into prepareCurrencyRebase, which fetches
the rates before any transaction opens, and applyCurrencyRebase, which
reads the outgoing currency and writes the rebase. update() fetches
first and hands the plan to updateTrip, which applies it inside the
transaction that writes the row, the leave shift and the day grid.
updateTrip called without a plan (the plugin RPC host) still never
rebases, and rebaseTripCurrency keeps working as before.

The updateTrip docstring no longer claims the row is written outside
the day regeneration transaction. TripsService.update leaves the lint:tx
baseline, and the size baselines shrink with the files.
2026-10-08 18:42:10 +02:00
Maurice 36a0d1eaba fix(auth): erase plugin user data in the transaction that deletes the user
erasePluginUserData deleted the host-side plugin rows and then queued the
plugin erasures as separate commits, and deleteGuest ran it before its own
transaction. When the guest delete failed, the guest kept its account but had
lost its plugin rows, with erasures queued for a user that still exists. The
erasure is now one transaction, and deleteGuest runs it inside the same
transaction as the budget re-split and the user delete, so it becomes a
savepoint of that write. Only DB writes and a synchronous readdir run inside.
2026-10-08 18:42:09 +02:00
Maurice b7e13e4af6 fix(booking-import): write an imported booking with its cost in one transaction
Confirming an import created the booking, then froze the FX rate and created
the linked cost in a second commit, so a failed cost write left the booking
without its expense. REST, MCP and the plugin RPC already go through
ReservationsService.createWithCost. The import now builds the cost entry and
freezes its rate first, outside the write since that can fetch rates, then
calls createWithCost and announces the cost after the commit. A rate that
cannot be frozen keeps the booking without its cost, as before. The place a
venue becomes still commits on its own, with endpoint geocoding between it
and the booking, so confirm stays in the lint:tx baseline at 4.
2026-10-08 18:41:57 +02:00
Maurice 60a42cfac1 fix(airtrail): import a flight with its AirTrail link in one transaction
The import created the booking and wrote its external_* link as two commits.
A failed link left the booking in the trip while the import reported the
flight as skipped. The flights are fetched before the loop, so nothing between
the two writes waits on the network: each flight or joined connection now goes
in with its link in one transaction, and the method is marked as one
transaction per imported booking instead of being held in the lint:tx baseline.
2026-10-08 18:41:57 +02:00
Maurice 8018f10273 feat(server): gate writes outside a transaction with lint:tx
The rule that a multi-statement write runs in uow.transactional was
written down but nothing held it. scripts/tx-writes.mjs reads every class
under src/ with the TypeScript parser and counts, per method, the writes
it issues outside a transactional callback: repository calls that build
an INSERT, UPDATE or DELETE, calls to methods that write (on the class
itself or on a constructor-injected one), and a transaction holding
writes as one. A write in a loop counts twice, of two branches only the
larger counts, local helpers count where they are called, and a helper
whose every call sits inside a transaction is not reported. --explain
lists the line of every call that counted.

Today's 102 reported methods sit in scripts/tx-writes-baseline.json and
may only shrink; most are syncs with network I/O between their writes,
plugin lifecycle steps and adapters calling two service writes. A new
method fails CI, and an entry above what its method holds fails until
--update lowers it, the same ratchet as lint:size and lint:boundaries.

Two JSDoc tags mark the deliberate cases with their reason in the tag:
@txStandalone for a write that stands on its own whatever the caller does
(the audit row, notification sends, the booking price mirror, the
assignment journey refresh, the quota retention purge), @txIndependent
for writes complete one by one on purpose (the OAuth token retention
sweep, the notification claim and its release). Runs in CI after
lint:boundaries; documented in server/CLAUDE.md, the root CLAUDE.md and
the wiki's dev setup page.
2026-10-08 18:41:57 +02:00