Commit Graph

406 Commits

Author SHA1 Message Date
Maurice 1c6a175ebc fix(storage): queue what a delete left unnamed past every undo, and tell a copied database apart
A delete or a replace no longer deletes inside the request. The owner's call
queues the keys in storage_release_queue, due after RELEASE_DELAY_MS (26 h:
the assistant's 24 h approval window plus a margin), and StorageReleaseJob
takes the due rows every ten minutes, at most 200 a tick and 50 keys a
question, asks every reference source again and deletes what none names.
It refuses what the nightly pass refuses (StorageReclaimGuardService: the
ledger, the required sources, a shared location, another installation's
owner marker, cached per category), and waits while a restore or a backup
runs or another storage job holds the slot. An assistant undo or a late
write that names the old value again keeps its object.

The install id lives in data/.trek-install-id, outside any backup and never
written by a restore. At boot and after every reopen the database is
reconciled with it: a database naming another installation (a boot restore,
a restart after a failed restore, a copied travel.db) loses its switch,
confirmations, reports, marks and queue before anything reads them.

A confirmation now stores the count the admin was shown and when; it covers
up to one and a half times that count (at least 100 more) for 14 days, is
used up once the share is back under a fifth, and a count the last report
no longer holds is refused as reclaim_confirm_stale. The admin state folds
a live confirmation in, so the prompt goes at once.

A pass reads each source once for every category and drops each category's
keys once judged; right before a batch it reads the category's sources
again, whole and in memory, instead of a LIKE per key. A narrowed read holds
at most 50 LIKE terms. A pass held off by a backup reports backup_running,
and a category the ledger never reclaims reports not_reclaimed even after a
stop.
2026-11-01 18:11:43 +01:00
Maurice 790fccb92c fix(storage): judge unreferenced files without a brake anyone can trip
The 20 % brake skipped a category and wiped its marks every night once a
fifth of it looked unreferenced. Any user could push a small install over
that line, a backlog from #2924 started braked, and a second install on the
same bucket below the line was not caught at all. It is gone:

- Each pass leaves a trek-owner-<install id> marker at the root of every
  category it judges. A category holding another install's marker is
  skipped as foreign_install, one where the marker cannot be written as
  owner_unknown. Backups leave the markers out.
- The install id lives in app_settings outside the storage.* prefix. The
  switch is stored as that id, so a database from another install reads as
  off, and every reopen (a restore) writes this install's id back, turns
  deletion off and drops the marks, confirmations and reports.
- More than a fifth unreferenced still marks and reports, but deletes only
  after an admin confirmed the count shown (POST .../reclaim/confirm). A
  larger count later waits again.

A skip no longer touches the marks, so a failed listing or lookup does not
restart the week. Check now runs in the background and answers 202; the
admin state says running and keeps the last deleting report apart, so a
report-only check never resets what was freed. A pass that deletes writes a
system audit entry, a pass that stops or fails keeps its partial counts and
marks the rest interrupted, a backend error stops deleting in its category,
and deleting stops for the rest of the pass once the switch is off or a
backup is reading the uploads. The usage scan waits for a pass still
running.

Sources read each category once per pass and answer the pre-delete
re-check with a query narrowed to the keys' plain runs. Bare names keep
their raw spelling (a '#', '?' or trailing blank is part of an upload's
name), and local roots are resolved through their symlinks before the
overlap check. A pass lists only a category's root.
2026-11-01 16:18:08 +01:00
Maurice f44dd92ee0 test(storage): run the reclaim e2e and source check on the whole app
The reclaim e2e and the source-registration check mocked src/db/database,
which lint:test-mocks holds at zero for new files; both now boot buildApp
against the worker's snapshot db. The key parser drops its non-null
assertions (the indexed reads are typed string already), and the e2e sends
its refused bodies at once instead of awaiting inside a loop, so the eslint
ratchet stays where it was.
2026-11-01 14:20:10 +01:00
Maurice fb2429ebb2 feat(storage): find and reclaim upload objects no row names any more
Domains whose rows name stored objects register a reference source with
StorageReclaimRegistryService; storage never imports them. A ledger in
storage decides per category whether it is reclaimed at all and which
sources must have answered first, and a category whose resolved location
overlaps another one is left alone.

A nightly pass at 04:05 lists the root level of files, journey, covers,
avatars and places, skips objects younger than a day or without a
modification time, asks every source in pages of 500 and marks the rest.
Only with deletion switched on, and only for marks a week old, it asks every
source about the single key again and deletes through StorageService. A
category is skipped and its marks start over when a source is missing or
fails, the listing fails, or more than a fifth of its objects look
unreferenced. A pass shares the one-job slot with backfills and migrations,
never runs on a demo instance and stops when the database is reopened.

Admins switch deletion and run a pass on demand under
/api/admin/storage/reclaim; both are audited, and GET /api/admin/storage
carries the switch and the last report. Deletion is off after install and
upgrade.
2026-11-01 13:37:53 +01:00
Maurice 682ae19143 test(place-enrichment): a cached OSM place still reads its details for the community version
An OSM element can hold community values, so the service reads its details
before the cache to learn the version it is served under. The second request
reads them once and then answers from the row cache without another lookup.
2026-10-31 06:27:31 +00:00
Maurice 36c1599a09 place-contrib: settle the second CI round
- The plugin RPC fallback tests build PlacesRpc with its origin bookkeeping.
- The e2e guard and contract cases send their requests one after another; the
  test server reset connections opened all at once.
- The job's two cron registrations are tagged @txIndependent: neither writes.
- The panel draws its fold head itself instead of importing it from pages/.
2026-10-31 03:25:55 +00:00
Maurice 65abea6910 place-contrib: settle the first CI round
- The pause setting's type comes from its schema, so nullable fields read the
  same under the server's compiler settings.
- The place form passes the origin of a field through to the hook.
- Serial steps run through inOrder (src/nest/common/in-order.ts) and
  independent ones through Promise.all, instead of an await inside a loop. The
  bulk update in places.mcp.ts goes the same way, so its baseline drops by one.
- The status a call without a key gets is a named constant, not an envelope
  literal.
- A picture test no longer inherits the previous test's mock answer.
- The entity counts include the three new tables.
- The log keeps its failure per query instead of resetting it in the effect,
  and usePlaceForm sets its picture through one helper.
2026-10-31 03:25:55 +00:00
Maurice 182ed56fa4 places: let an admin share the details users typed with the TREK Places API
Every shareable field of a place now records where its value came from: typed
by a user, filled by a provider, written by the assistant, a plugin or an
import. Only values a user typed are ever sent, and only after an admin agreed
to the current terms for the whole installation, field by field.

The new place-contrib domain keeps the decision in app settings, queues values
in an outbox a cron job sends from, logs what the service made of each one and
can withdraw everything this installation sent. Development, test and demo
instances, an own TREK_PLACES_URL, TREK_SERVICES=off and
TREK_PLACES_CONTRIBUTE=off never send anything. Index results carry the
community version, so the details caches refresh when it changes.
2026-10-31 03:25:55 +00:00
Vini f56f6ae11e perf(atlas): keep the admin0 country index as Int16 arrays and send countries/geo from disk 2026-10-31 03:25:13 +00:00
Maurice 47dad114df test(atlas): cover the per-country region route in the e2e suite 2026-10-31 00:40:23 +00:00
Raz Erlich 209f8fe5cc feat(transit): fall back between configured public transit providers 2026-10-28 08:01:53 +02:00
Maurice 1e5314d847 maps, routing, i18n: settle what the checks found
- The lead is Photon's own row first; a row of its name inside the lead
  only stands in where Photon's row went as the same place. Rows without a
  line below the name are never folded as reading the same.
- A miss of Photon where it is the geocoder no longer asks it a second time
  on the way to an empty answer.
- The transport words of the identity match use a Unicode word boundary
  (straße, estación) and match 駅 anywhere.
- Opening an OSM place reads the coordinate it was placed at explicitly.
- The ferry spans of a Valhalla leg move to valhallaFerries.ts, and a day
  cut into pieces is asked with one options object.
- "Limit" on the allowance tiles is "Daily limit", which no locale left
  as an English copy.
- Tests follow the new behaviour: no Nominatim behind Photon, zoom 13,
  and the expectations of the merge and edit-distance cases.
2026-10-30 16:36:22 +01:00
Maurice 686592437a fix(server): rest the routing relay after a failure and keep it under the installation's minute
A hung TREK Valhalla cost every leg, height chunk and trace the full 30 s before FOSSGIS answered. The relay now waits 10 s for a route or heights, rests a minute after an error or timeout and answers 503 at once meanwhile, follows a 429's Retry-After (an hour at most), passes that header on to the browser, and holds the installation to 100 questions a minute below the gate's 120 (60 per user).
2026-10-29 13:34:28 +01:00
Maurice 1da7d0627a fix(server): judge the address search in the admin status by TREK_PHOTON_URL and an own NOMINATIM_URL
The status asked a fixed Photon origin and compared NOMINATIM_URL with the public address, so a moved Photon or an explicitly set public Nominatim showed the wrong service as in use. It now reads the same photonUrl and nominatimOwn the Photon client decides by. TREK_VALHALLA_URL joins the commented compose variables.
2026-10-29 13:34:28 +01:00
Maurice bc62eb0d6f feat(admin): show the allowance of place search, address search and routing in the TREK API card
GET /api/admin/service-identity lists places, photon and valhalla, each with
today's allowance read fresh from /v1/quota of its own host (cached for 20
seconds per host) and whether this instance asks it right now: Photon only
beside the public Nominatim, the TREK Valhalla only where the routing choice
comes to it. Photon and Valhalla are asked only with a key and only while
TREK_SERVICES_HOSTS lists them. The last allowance an answer stated is kept
per host for when a host does not answer.

The card lists the three services under each other in the look it had,
a service this instance does not ask dimmed and named as such.
2026-10-29 13:34:28 +01:00
Maurice 80183bc986 feat(server): relay Valhalla routing to the TREK services with the installation key
POST /api/routing/valhalla/route, /trace_attributes and /height ask the
Valhalla at TREK_VALHALLA_URL (default https://valhalla.liketrek.com) for a
signed-in user. The body is held to the shared contract (64 KB, at most 20
locations), every question counts against 120 a minute per user, and the
installation key goes along only to a host TREK_SERVICES_HOSTS lists.
Valhalla's answer is passed on as it came, refusals included.

The road trip router asks the same relay when the routing choice lands on
the TREK services, so the MCP road trip, city walk and hike tools share its
limits, and hands a question it cannot answer to FOSSGIS.
2026-10-29 13:34:28 +01:00
Maurice 256f8c08e2 feat(server): ask the TREK services' Photon before Nominatim for search and reverse lookups
An installation that holds a key for the TREK services and has no
NOMINATIM_URL of its own now sends the OpenStreetMap search and the
reverse lookups to photon.liketrek.com first. The answers are read into
Nominatim's shape, so the place records stay the same, and every miss
(refused key, allowance spent, timeout, error, foreign body) falls back
to Nominatim, with a one minute pause after four misses in a row.

TREK_PHOTON_URL points at another copy, and the default of
TREK_SERVICES_HOSTS now lists photon.liketrek.com and
valhalla.liketrek.com beside places.liketrek.com.
2026-10-29 13:34:28 +01:00
Maurice 01ae91c939 feat(server): give each installation a key for the TREK services and report its allowance
A new service-identity domain registers the installation with places.liketrek.com on its first Places request, in the background and behind a light scrypt proof of work, keeps the key encrypted in app_settings, renews it from day 60 through a daily job, drops it after three 401s in a row with up to six hours of jitter, and freezes it on a revocation until an admin starts over. TREK_SERVICES_KEY wins and turns registration off; TREK_SERVICES=off sends no key; a mirror set with TREK_PLACES_URL never registers and never sees the key, which goes only to the hosts in TREK_SERVICES_HOSTS over https.

The Places client takes its headers from the identity port, sends a TREK/<version> User-Agent and keeps the instance token. A 401 or 403 does not open its breaker. GET /api/admin/service-identity reports the state, tier and today's allowance, asking the issuer at most every 20 seconds; POST .../regenerate starts a new identity.
2026-10-29 13:34:28 +01:00
Maurice ef86bf2660 fix(places): keep two different tracks of the same name apart on import
Two GPX tracks a watch named alike (two "Afternoon Hike"s) were taken for one:
the import compared a track only by its name, so the second was skipped as
already in the trip. A track now matches a track of that name only when it
also starts, ends and reaches as far as that one, to about a hundred metres,
so the same file imported again is still skipped. The GPX, KML and tour
imports and the tour preview all read the same rule.

Closes #2870
2026-10-28 15:43:52 +01:00
Maurice 186dd94b61 fix(assistant): chat with an own server entered without /v1 at /v1
The assistant sent an own server's chat to the address as entered plus
/chat/completions. Ollama, entered as http://host:11434 the way its own docs
write it, answers that with a 404, so every message ended in "something went
wrong" while connecting had worked: the model list is found under the root
at /api/tags. A bare server address now chats under /v1, where Ollama, LM
Studio, vLLM, llama.cpp and LiteLLM all answer; an address with a path, such
as https://api.groq.com/openai/v1, is used as entered.
2026-10-28 12:17:54 +01:00
Maurice e677d58f91 fix(places): a track is a duplicate only of a track, never of a sight of that name
A GPX or KML import skipped every row whose name was already a place of the
trip. A watch names a hike after where it went, so "Hokitika Gorge" was
refused as already in the trip because the gorge itself was a planned
place, with no track to show for it (#2810). A track now matches only the
tracks of that name; a pin still matches every place of its name.

The Places filter also hid its "Tracks" choice whenever Tours was on, on the
grounds that tracks are tours then. A track imported before Tours is a
plain place and stays in the list, so the choice now counts the tracks that
are no tour.
2026-10-26 14:08:56 +01:00
Maurice 39ebf0976c fix(tours): a tour import takes over a track imported before Tours
A GPX track imported in 4.x is a plain place: a line, no tours row. It never
showed under Tours, and importing the same file there was refused as a
duplicate by its name, so there was no way to turn it into a tour (#2810).

The tour import now takes over a track of the trip that has the same name
and is no tour yet: the place keeps its name and line and gets the tours
row, with the recorded times the file adds. The preview says so instead of
"Already in the trip". New and taken-over tracks commit in one transaction.
2026-10-26 12:17:15 +01:00
Maurice 1ecc449f3e test(ai): fix the model-not-found pattern, guard the fetch mocks, and read the booking in an ORM context 2026-10-26 10:20:47 +01:00
Maurice b8990782cc fix(ai): connect and read bookings through any OpenAI-compatible own server
Connecting an own server asked Ollama's /api/tags and gave up on any answer
that was not a 404, and every failure, a refused key included, read as "not
responding". The model list is now looked for at /api/tags, at the address as
entered and at /v1/models, an answer only counts when it is a list, and a
refused key or a server that answered without a list say so (#2808).

AI parsing through an own server always called Ollama's /api/chat, which an
OpenAI-compatible server such as Groq answers with 404. A server without it
now gets the same extraction over /chat/completions at the address as
entered (#2806).
2026-10-26 10:20:46 +01:00
Maurice d911122a37 fix(assistant): let an admin switch the model of a shared ChatGPT plan
The model picker in Admin > Addons did nothing for a ChatGPT connection: the
only way to change the instance's model was saving the connection again, which
needs an API key a plan does not have. A PATCH /api/admin/ai/connection/model
now switches it on its own, to a model the provider offers.

Closes #2783
2026-10-25 16:29:31 +01:00
Maurice 2372dde716 fix(demo): journey and collab uploads are refused for the demo account too
Six upload routes refused the shared demo account in demo mode; journey photos, videos, gallery media and covers, collab note files and chat images did not, so any visitor could put images in front of every other one for the hour. They answer the same 403 now, checked in the handler after the upload is read, and a text-only chat message still goes through. The Demo Mode wiki page lists them, and says document sync is not covered.
2026-10-25 00:46:24 +00:00
Maurice eae74c5605 feat(demo): the shared demo account keeps its keys, account and language
Every visitor of a demo instance signs in as the same account, so one
visitor's API key, MCP token, OAuth client, passkey or new email address
became everybody's. These writes now answer 403 for the demo account through
refuseForDemoAccount in demo-write.ts: creating API keys, creating and
deleting MCP tokens, registering, rotating and deleting OAuth clients,
approving an app and ending a connected app, changing the name, address or
picture, and enrolling, renaming or deleting passkeys. Password, 2FA,
account deletion, sessions and the own AI connection were refused already.

The demo account's language is fixed to English: a single settings write of
the language answers unchanged, a bulk save drops it and keeps the rest. The
MCP settings tool is held back for the demo account by the tool gate, as
every write tool is. The admin of a demo instance and every real instance
are untouched.
2026-10-24 23:22:15 +00:00
Maurice a955328070 feat(journey): photo links tell every tab, move between entries, and gain MCP tools
Linking a gallery photo to an entry and taking it off told nobody, so other
tabs, and the journey page after an assistant change, stayed as they were.
Both now go through a photo links service that broadcasts
journey:entry:updated after the write, with the asking socket left out on
REST; a caption change through PATCH photos/:id broadcasts journey:updated
the same way. The service also moves photos from one entry to another of
the same journey in one transaction, as POST
/api/journeys/entries/:entryId/photos/move, with a strict contract. The MCP
tools link_journey_photo, unlink_journey_photo, move_journey_photos and
set_journey_photo_caption call the same methods; the caption tool answers
the caption it replaced.
2026-10-24 22:54:45 +02:00
Maurice 05666295e0 test(assistant): every keep proposal in the attachments e2e takes a call id of its own
The refusal test proposed two keeps in one thread under the same call id, so
the second decision met the first call, already decided, and no refusal came
back. Each proposal now has its own id, and the refused right is set after the
proposal so it is the apply that refuses.
2026-10-24 22:32:48 +02:00
Maurice b333910533 test(assistant): the attachments e2e provider answers the model list a saved connection asks for
Saving the custom connection lists the server's models first, and the fake
provider answered that with a 404, so the setup got 502. It answers /api/tags
the way the other assistant suites' fake provider does.
2026-10-24 22:32:48 +02:00
Maurice 89c33ac3f4 feat(assistant): an attached file can be kept in Files, on its own or with a booking or an expense
The chips offered to keep an attached file in Files, but nothing could do it:
the assistant's upload_trip_file stays denied, and the file never reached
Files. A new server write, keep_attached_file, is offered to the model where
the thread holds an attachment and held as a change card naming the file and
the booking, expense or place it goes with.

Applied, it runs the checks of a Files upload with the user's rights (the
demo block, the trip, file_upload and file_edit for a link, the instance's
file types, every linked id on the same trip), copies the bytes to a trip
file of their own, and writes the trip file, its expense link and the
attachment's pointer to it in one transaction; the members' planners get
file:created after it. A second keep of the same attachment answers the file
kept the first time. FilesService gains storeBytes and discardBytes so the
storage write stays out of that transaction.

The receipt opens the Files tab; the card's verb, its "Attached to" row and
the receipt's open label are new keys in all 27 locales. Undo is not offered:
no tool takes a trip file back yet.
2026-10-24 22:32:48 +02:00
Maurice aeda658f18 feat(assistant): the model reads an attached file once the user agrees
The assistant could not read a file the user attached: the bytes were gone
after the request, the thread only held the file's name, and read_trip_file
stays denied because it would send any document of the trip to the provider.

A new server tool, read_attached_file, reads only the files attached in this
conversation by this user; any other id is answered "No such attachment in
this conversation.", whoever it belongs to. It is offered in a trip turn whose
thread holds a file with text. The first read of a file is held as a change
card that names the file and the model and host its text would go to; once
the user agrees, the agreement is kept on the attachment and later reads run
at once. The text reaches the model through the history and, for a later
read, as a tool answer only the model gets (AgentToolRun.modelText): no message
part, stream chunk, shared answer or client store ever carries it.

The model is told each kept file's attachment id, and the standing
instructions say to read the file when the user wants to work with it, to
propose a booking found in it through create_reservation and then to offer
keeping the file with it. The card's verb, its "Sent to" row and the step's
name are new keys in all 27 locales.
2026-10-24 22:32:47 +02:00
Maurice 2f3a0219cd feat(assistant): every attached booking file gets an answer with what to do next
A booking file without a booking ended the answer on the error "No booking
found in this file." with no action, and the user was left to type. Now the
server answers every booking file itself, without a model: one line that
names the file and what TREK found in it, or that it found nothing, why, and
asks what to do with the file. Under it come up to three chips, picked from
what the user may do on the trip and what the file allows: keep it in Files,
make a booking from its text, attach it to a booking or an expense, ask what
is in it, or keep it with the booking just found. A chip sends its words as
an ordinary chat message.

The line and the chips are written in the language of the request from new
assistant.attach.* keys in all 27 locales, through a small helper the sports
proposals now share. The empty answer finishes as stop; no_bookings_found
stays in the contract for the threads older builds wrote. Route files keep
their answer as it was.
2026-10-24 22:32:47 +02:00
Maurice 6a77496438 feat(assistant): an attached file stays with its conversation
A file attached in the trip chat lived only as a buffer for the one request
that read it, so the assistant could not look at it again and nothing could
keep it in Files. Now its bytes go to storage before the turn writes anything
(under the trip files' category, which is never served by path) and a row in
the new assistant_attachments table keeps its name, type, size, hash and the
text TREK read out of it, written in the turn's opening transaction. The
message's attachment part carries the row's id; old messages without one still
read.

The text is read the way the AI import reads it, through a new public
LlmParseService.readText, and a file whose text cannot be read is kept all the
same. A storage that refuses the bytes refuses the turn before any row exists.
Deleting a thread takes its files along, and the nightly retention sweep drops
files older than seven days and bytes no row names any more.
2026-10-24 22:32:47 +02:00
Maurice cb34c46226 fix(studio): CI on the templated auto layout
The rhythm test holds the picker to what it does now: it gives the rhythm up
only where no template of the book's styles frames every photo and keeps it.
A design applied over a stored book keeps that book's theme, as the merge has
always done, so the end to end test checks the pages instead. The MCP test
builds its request through the shared schema, a travel mark's frame is read
with its sides spelled out for the client's looser types, and the client's
react-hooks/refs baseline drops to the 555 it holds now.
2026-10-24 20:54:16 +02:00
Maurice 9729b0b575 feat(server): design_book offers the designed templates
The design_book tool says in one sentence that layouts: 'templates' lays
every page on the Studio's designed templates in the archetype of the
book's theme, as the Studio's Auto layout does, and that each plan row then
names its template; style classic alone stays the old built-in Auto
layout. The field comes from the directives contract in @trek/shared, so
the tool's schema takes it without a zod line of its own, and the answer's
plan row gains an optional template. The REST draft routes take no
directives, so nothing changes there. Without the field the answer is the
same as before, no row naming a template.
2026-10-24 20:13:43 +02:00
Maurice 728a275fef chore(assistant): drop Connect with Claude from the integration settings
The card that opened claude.ai's custom connector dialog for this instance
goes, with its phone twin, the line in the assistant setup that jumped to it,
its endpoint and its contract. Anyone who wants Claude uses TREK's MCP
server under Integrations, or an Anthropic key in the assistant. It came with
the assistant and never shipped in a release.
2026-10-24 18:28:41 +02:00
Maurice 658536c217 fix(trips): a member another member removed cannot rejoin through the invite link
Removing a member deleted their membership row only, and the invite link
checked nothing but its own token, so whoever kept the link walked straight
back in (GHSA-wr4c-964p-39jg).

trip_member_removals now notes who another member took off a trip. The invite
link refuses them on preview and on accept with a 403 that says so, old link
or regenerated, and the join page shows that instead of the join button.
Adding them back by name, or through a trip-bound invite or a plugin acting
for a member manager, lifts the note. Leaving a trip yourself leaves none, so
the link keeps working for whoever left and for everybody else.
2026-10-24 08:29:51 +02:00
Maurice bfcc205f05 fix(assistant): a change left waiting past its day no longer holds the next one back
The open changes of a thread are now the ones still inside their decision
window. A call older than a day already reads as expired in the thread and
can no longer be decided, but it stayed approval_requested until the nightly
sweep, so "+ Tour", "+ Day" and "Plan this" answered decide_first with
nothing left to decide, and a decision had to name the expired call too.
listOpen takes the window's start, and every caller passes the same cutoff.
2026-10-24 01:41:36 +00:00
Maurice 82b8eb1067 feat(studio): book themes with palette, font pairing and motif set 2026-10-23 22:08:13 +00:00
Maurice 7a7876b9f4 feat(studio): photo look with border, hard shadow, zoom, mirror, adjustments, duotone and cutout
Photos get a look beyond crop, mask and frame style, in Studio and for the
assistant alike:

- border (width and colour, following the mask or the corner radius), a hard
  offset shadow and a die-cut cutout edge, all flat vector shapes in print
- zoom 1 to 4 about the focal point and a left to right mirror
- brightness, contrast, saturation and warmth, and a duotone; these are
  filters, labelled raster in print in the inspector
- the print resolution of the selected photo in the inspector, with a warning
  under 200 dpi, measured from the original

PhotoView moves out of SpreadView into its own file and uses per instance
SVG ids, so a spread drawn twice keeps its clips and filters apart. The style
operation takes the look, describe-spread lists it, the dpi and crop findings
account for zoom and mirror, and a new raster_effect finding notes filters on
a large photo. BOOK_SCHEMA_REV goes to 6.
2026-10-23 22:08:13 +00:00
Maurice 5d334d7ec8 feat(assistant): the assistant opens outside a trip, as a home chat about all trips
A third scope, home, next to trip and journey: threads that name neither a
trip nor a journey (the CHECK now only forbids both), reached under
/api/assistant by their owner only. A home turn reads the user's trips
through list_trips into a short card (under way, next, later, behind,
undated), offers every read (a trip's on the id the model names, checked by
the tool with the user's rights) and only the writes that take no trip or
journey (atlas, vacation planner, collections, tour library, settings), all
held as change cards. open_trip_chat answers with a card that goes on in
that trip's own chat with the question waiting in its composer.

On the desktop the window opens on the home chat outside a trip; its head
lists the trips to switch into and a switched trip chat has the way back.
On the phone the home chat opens full screen from the dashboard head and the
dock's More list, and the card opens the trip with its chat. Both run on
useAssistantHome.
2026-10-23 22:00:06 +02:00
Maurice cd60d4d859 feat(tours): assistant takes found places into saved tours and route files from the chat
P6: insert_tour_stop and remove_tour_stop take a place into a saved hike or
city walk as one stop and out again, each the other's undo, with change
cards in the chat. The planner's insertion rule (poiInsertion) moved to
shared so the planner and the server place a stop the same way. "+ Tour" on
a found place holds it as a stop of the saved tour open in the planner.

P7: GPX, KML and KMZ files attached in the chat are read into their tracks
and each is held as a change that takes it in as a hike through
import_tour_gpx, undoable per track; the GPX text stays on the held call.
2026-10-23 22:00:05 +02:00
Maurice 767d91146a feat(tours): the assistant edits stations, reverses and closes tours, sets breaks, colour and copies, and writes ski and paddle plans
City walk changes take edit, reverse and close_loop; edits alone keep the
saved way and never ask the router. update_tour turns a hike round or closes
its loop with reshape. New MCP tools mirror the Tours tab's REST writes and
reads: update_tour_details, set_tour_route_color, copy_tour,
classify_ski_track, set_paddle_plan, decide_paddle_obstacles,
restore_recorded_track, get_tour_trail_details and find_horse_routes, with
change cards, undo where the answer keeps the values before, and broadcast
parity cases.
2026-10-23 21:59:02 +02:00
Maurice c6b3f609b7 fix: dev green after the Studio, place picture and tour merges
The ids of long catalogues (fonts, shapes, patterns) read as a checked string
in the book tools' schemas, so restore_book_page and the page edit stay under
their ceilings. The lightbox takes its reading direction from the language.
Baselines follow the removed avatar upload and the lower counts, the doubled
NEWER constants and three unused imports go, the locale indexes are sorted,
and the place gallery, picture, sheet, inspector weight and paddle tests
follow the merged behaviour.
2026-10-23 21:58:43 +02:00
Maurice d8213bfa42 feat(studio): groups, a layers panel, marquee, nudge, multi-resize, align and paste style
Elements can be grouped on a page and picked, moved, resized, aligned and
restacked as one. A layers panel lists the page front to back with lock,
visibility, rename and drag to restack. The sheet gains marquee selection,
arrow-key nudge, one box that resizes a multi-selection with Shift holding
the proportions, and the design-tool shortcuts. The inspector lines a
selection up and spaces it out, and copies and pastes a style.

The assistant gets the same through the group, ungroup, name and hide
operations, and describe_book_page lists the groups. The format gains the
optional group, name and hidden fields and a spread's group names, so
BOOK_SCHEMA_REV goes to 2.
2026-10-23 21:07:13 +02:00
Maurice 8c6f5deab2 feat(studio): a sticker library of 148 hand-drawn illustrations, placed, recoloured and searched by people and the assistant
A new element kind, sticker, draws layered vector illustrations from a library in twelve themes, behind its own entry point @trek/shared/stickers. Studio gets a Stickers rail section (themes, localized search, recently used, click or drag to place) and an inspector for colour slots, mirroring, turning and opacity. The assistant gets list_book_assets, add and style of stickers in edit_book_page, stickers in describe_book_page and an overlap warning for a sticker over words. BOOK_SCHEMA_REV goes to 2.
2026-10-23 21:06:09 +02:00
Maurice fabefed39f feat(studio): 34 typefaces with real weights and italics, a categorised font picker
The book catalogue grows from 7 families to 34, all self-hosted through
@fontsource under the SIL OFL 1.1 or the Apache License 2.0: condensed and
poster faces, heavy retro display, elegant display serifs, Montserrat, twelve
scripts and DM Mono. Weights run from 100 to 900, each family offering only
the weights it ships, and the serifs gain real italics.

The catalogue lives in @trek/shared (book-fonts.data.ts) with each family's
roles (display, heading, body, script, mono), weights, italics and letter
width, so the Studio, the findings, the design engine and the assistant read
one list. Page operations and the design engine set a weight a family lacks
at its nearest one, describe_book_page names the role and the weight, and
font_mix no longer counts scripts and mono faces, warning above three.

The Studio gets StudioFontPicker: the current family set in itself, a
searchable list grouped by kind with every family in its own face and the
book's own families first, and a weight slider over the weights the family
has. Faces load only when used; the assistant's page snapshot embeds only the
faces a page draws, the service worker installs only the latin files of the
original seven families, and the faces of an opened book are cached for
offline use. BOOK_SCHEMA_REV goes to 2.
2026-10-23 21:01:29 +02:00
Maurice 6e43490ba0 feat(assistant): plan and save every tour type, bike tours, runs, paddle and ski tours included
The assistant can now plan and save every type the Tours tab plans. A bike
tour or a run is proposed and created through the hike's own plan and save
(propose_tour, create_tour), routed on the server with the costing of its
profile, now kept once in shared and read by the planner as well. A paddle
or ski tour planned with plan_paddle_route or plan_ski_route is saved with
create_paddle_tour or create_ski_tour, and "Plan this" takes those plans,
bike tours and runs too. Every create has its card and its way back.
2026-10-23 21:01:13 +02:00
Maurice d07b8ba56e feat(assistant): web search through the provider, and cards with a way back for the smaller tour writes
- Web search: ChatGPT (Responses web_search), Anthropic (web_search server
  tool) and OpenRouter (web plugin) search the web themselves when the admin
  switch allows it (on as it ships). A search shows as a step ("Searching the
  web for ..."), the cited pages as source chips under the answer. Anthropic
  answers that searched are replayed with their blocks (new provider state
  adapter); the search step never goes back to the model as a call. The
  prompt says when to search and that chosen places still go through
  search_place.
- Tour writes: assign_tour_track (undo: take that assignment off),
  unassign_tour_track, set_tour_activity_excluded (undo: the other switch),
  link_tour_packing and unlink_tour_packing (undo from the link before, which
  both answers now name) get change cards and their ways back.
2026-10-23 19:13:51 +02:00