diff --git a/wiki/AI-Booking-Import.md b/wiki/AI-Booking-Import.md index 420a9aa9e..ed5b0388b 100644 --- a/wiki/AI-Booking-Import.md +++ b/wiki/AI-Booking-Import.md @@ -4,7 +4,7 @@ The **AI Parsing** addon adds a large-language-model fallback to TREK's booking It is an **opt-in addon, disabled by default**, and it works with a self-hosted local model, so no booking data has to leave your server. -> **Admin:** Enable **AI Parsing** in [Admin-Addons](Admin-Addons) (it sits in the *Integration* group). Booking import itself still requires the `kitinerary-extractor` binary — see [Reservations-and-Bookings](Reservations-and-Bookings#when-the-button-is-not-visible). +> **Admin:** Enable **AI Parsing** in [Admin-Addons](Admin-Addons) (it sits in the *Integration* group). Booking import then works even without the `kitinerary-extractor` binary — with no extractor present the model parses every uploaded file instead of only the ones Itinerary can't read. Installing the extractor is still the better setup: structured tickets stay fast and deterministic and the model remains a fallback. Configure a provider and model before pointing anyone at it, though: the **Import from file** button appears as soon as the addon is on, and with neither an extractor nor a configured model the import fails outright. See [Reservations-and-Bookings](Reservations-and-Bookings#import-from-booking-confirmation). ## How it fits with normal import @@ -14,7 +14,7 @@ AI parsing does not replace [KDE Itinerary](Reservations-and-Bookings) — it ba 2. Only files that Itinerary returns **nothing** for are sent to the AI model. 3. Every reservation the AI produces is flagged **Review** so you can confirm it before (or after) saving. -So structured tickets keep being parsed the fast, deterministic way; the AI only steps in for the documents that would otherwise fail. If the addon is disabled, import behaves exactly as before. +So structured tickets keep being parsed the fast, deterministic way; the AI only steps in for the documents that would otherwise fail. If the addon is disabled, import behaves exactly as before. On an install without the extractor there is nothing to back up — every file goes straight to the model. ## Choosing a provider @@ -54,9 +54,11 @@ You can also select any other model already installed on the server, or type a m If an admin leaves the instance config blank, each user can configure their own model under **Settings → Integrations → AI parsing** (the section only appears when the addon is enabled): -> *Use your own AI model to extract bookings from uploaded files. This applies only when your administrator has not configured a model for the whole instance.* +> *Choose the AI model used to extract bookings from uploaded files. This applies only when your administrator has not configured a model for the whole instance.* -The fields mirror the admin panel — provider, model, base URL (for local / OpenAI), and an API key that is *stored encrypted* (leave blank to keep the current key). There is also a **Send documents as images** toggle for vision-capable models. +The fields are a **Provider** (only **OpenAI** or **Anthropic** here), a **Model** id, and an **API key** that is *stored encrypted* (leave blank to keep the current key). There is no personal Base URL: the address this server calls is instance configuration, so a local (Ollama) model can only be set up by an admin on the addon, and the server answers 403 to anyone, admins included, who tries to store a personal base URL or a personal `local` provider. + +There is also a **Send documents as images** toggle. It is stored per user, but extraction currently ignores it: only Anthropic is sent the raw PDF, every other provider always gets the extracted text. > **Precedence:** an admin instance model always wins. Personal settings only take effect when no instance-wide model is configured. @@ -64,12 +66,18 @@ The fields mirror the admin panel — provider, model, base URL (for local / Ope The upload flow is the normal booking import — the AI simply runs behind it: -1. In the trip planner, open the **Reservations** tab and click **Import from file**. +1. In the trip planner, open the **Bookings** tab and click **Import from file**. 2. Drop your files (EML, PDF, PKPass, HTML, TXT — up to 5 files, 10 MB each) onto the upload area. 3. The upload dialog closes right away and a **background widget** (bottom-right) shows *Parsing files…* with a running count. You can keep navigating TREK while it works; the widget survives a page reload and even follows you to other pages. 4. When parsing finishes, click the widget's **Import** button to start the review. 5. Each parsed booking opens **pre-filled in the normal reservation (or transport) editor**, one at a time. Nothing is saved until you confirm each one. +### When nothing is found + +If a job finishes with *No reservations could be extracted*, the widget offers a **Try AI parsing** button. It re-uploads the same files in force-AI mode: KDE Itinerary is skipped entirely and every file goes straight to the model. The offer only appears on an empty result, and never on a run that was already forced. If neither an instance-wide nor a personal model is configured, the retry fails with the error on the card instead of starting. + +It is a retry, not a different route — on a normal import the model has already seen every file Itinerary returned nothing for, so it is worth pressing mainly when the addon or the model was configured after that job started. There is no way to push a document Itinerary *did* read through the model: as soon as the parse produced any booking at all, the widget shows **Import** and no retry button. Fix such a booking in the review editor instead. + ### What gets filled in and created The model is asked to capture the full booking — including **every leg of a multi-segment flight** — and, on save, TREK wires each item into the trip: diff --git a/wiki/Admin-Addons.md b/wiki/Admin-Addons.md index f38c33aac..e600860f8 100644 --- a/wiki/Admin-Addons.md +++ b/wiki/Admin-Addons.md @@ -31,10 +31,11 @@ Each sub-toggle can be disabled independently while the parent addon remains ena ### Global addons -Global addons add features that are not tied to a single trip. The default global addons are **Vacay**, **Atlas**, and **Journey**. +Global addons add features that are not tied to a single trip. The default global addons are **Vacay**, **Atlas**, **Collections**, and **Journey**. - **Vacay** — personal vacation day planner with calendar view. Enabled by default. - **Atlas** — world map of visited countries with travel stats. Enabled by default. +- **Collections** — personal place library: save places across trips into named lists, copy them into any trip, and share them. **Disabled by default.** See [Collections](Collections). - **Journey** — trip tracking and travel journal (check-ins, photos, daily stories). **Disabled by default.** **Sub-items on global addons:** @@ -43,9 +44,11 @@ Global addons add features that are not tied to a single trip. The default globa ### Integration addons -Integration addons connect TREK to external services. Enabling an integration addon typically requires additional configuration (API keys, URLs) in the **Settings** tab. +Integration addons connect TREK to external services. Most of them need additional configuration (API keys, URLs) once enabled, but not in the admin **Settings** tab — each one has its own place. - The **MCP** addon requires `APP_URL` to be set in your environment. When enabled, the **MCP Access** tab appears in the Admin Panel. **Disabled by default.** See [MCP-Overview](MCP-Overview) for full details. +- The **AirTrail** addon syncs flights from a self-hosted AirTrail instance. **Disabled by default.** The toggle here is the instance-wide switch only; each user connects their own instance (URL + API key) in **Settings → Integrations**. +- The **AI Parsing** addon is the LLM fallback for booking imports KItinerary cannot read. **Disabled by default.** When enabled, its provider, base URL, API key, and model fields appear inline underneath the addon row. Filling them in sets the instance-wide config for all users; leaving them blank lets each user configure their own provider in **Settings → Integrations**. See [AI-Booking-Import](AI-Booking-Import). ## Enabling or disabling an addon diff --git a/wiki/Admin-Categories.md b/wiki/Admin-Categories.md index 0b5d40f4a..ad1c4cebe 100644 --- a/wiki/Admin-Categories.md +++ b/wiki/Admin-Categories.md @@ -11,7 +11,7 @@ A category is a label consisting of a name, a color, and an icon. Users assign c - In the place form's category selector - As colored chips on place cards - In the places filter panel -- In the map legend +- As the color of a place's map marker, and in the marker's hover popup ## Creating a category diff --git a/wiki/Admin-GitHub-Releases.md b/wiki/Admin-GitHub-Releases.md index c24b9ec4b..056216741 100644 --- a/wiki/Admin-GitHub-Releases.md +++ b/wiki/Admin-GitHub-Releases.md @@ -26,7 +26,7 @@ Below the support cards, a chronological timeline lists GitHub releases for the - **Release date** and author - A **Show details / Hide details** toggle that expands the release notes (Markdown rendered inline) -When the running server version is a stable release, pre-release entries are filtered out of the timeline. +Pre-release entries are hidden unless the server has both found a newer version and is itself running a pre-release. In every other case, including a pre-release install that is already on the newest pre-release, the timeline shows stable releases only. Releases load 10 at a time. Click **Load more** at the bottom of the timeline to fetch additional pages. diff --git a/wiki/Admin-Panel-Overview.md b/wiki/Admin-Panel-Overview.md index 088f99f22..a331754c7 100644 --- a/wiki/Admin-Panel-Overview.md +++ b/wiki/Admin-Panel-Overview.md @@ -4,7 +4,7 @@ The Admin Panel is the central control surface for TREK instance operators. It i ## Accessing the Admin Panel -Navigate to the **Admin** link in the top navbar. If you do not see it, your account does not have admin privileges. +Open the user menu in the top navigation bar (your avatar), then select **Admin**. If the entry is not there, your account does not have admin privileges. ![Admin Panel](assets/AdminPanel.png) @@ -18,11 +18,11 @@ The Admin Panel is divided into tabs. Most tabs are always visible; a few appear | **Personalization** | Packing templates and place categories | No | | **User Defaults** | Default settings applied to new users | No | | **Addons** | Enable or disable optional features instance-wide | No | -| **Plugins** | Install, update, and manage plugins; rescan the plugins folder; review each plugin's capability audit. See [Admin-Plugins](Admin-Plugins) | No | +| **Plugins** | Install, update, and manage plugins; rescan the plugins folder; view each plugin's error log. See [Admin-Plugins](Admin-Plugins) | No | | **Storage** | Storage backends, category assignment, replication, health | Hidden on managed instances | | **Settings** | Authentication methods, MFA, allowed file types, API keys, OIDC/SSO configuration, and JWT secret rotation | No | | **Notifications** | SMTP, webhook, ntfy, and push notification channel configuration; trip reminder toggle; admin notification preferences | No | -| **Backup** | Manual and scheduled database backups | Hidden on managed instances | +| **Backup** | Manual and scheduled full-instance backups: database, uploads, plugin data and plugin code. See [Backups](Backups) | Hidden on managed instances | | **Audit** | Chronological activity log | No | | **MCP Access** | OAuth sessions and static API tokens | Only when the MCP addon is enabled | | **GitHub** | Release timeline and support links | Hidden on managed instances | @@ -30,11 +30,39 @@ The Admin Panel is divided into tabs. Most tabs are always visible; a few appear ![Admin panel on the User Defaults tab, setting instance-wide defaults for colour mode, temperature unit, distance unit, time format, currency and blurred booking codes](assets/AdminUserDefaults.png) +### Linking to a tab directly + +Every panel is reachable by URL, so a bookmark, an onboarding mail or a support reply can point at the one tab it is about instead of at the top of a page with thirteen of them: + +``` +/admin?tab=audit +``` + +Unlike the trip planner's `tab` parameter, this one stays in the address bar and is rewritten as you switch tabs, so the URL always says which panel you are looking at. It replaces the current history entry instead of adding one, so the back button still leaves the admin page rather than walking back through the tabs you visited. + +| Tab | Id | +|-----|-----| +| Users | `users` (the default — carries no `?tab=`) | +| Personalization | `config` | +| User Defaults | `defaults` | +| Addons | `addons` | +| Plugins | `plugins` | +| Storage | `storage` | +| Settings | `settings` | +| Notifications | `notifications` | +| Backup | `backup` | +| Audit | `audit` | +| MCP Access | `mcp-tokens` | +| GitHub | `github` | +| Dev: Notifications | `dev-notifications` | + +An id with no panel behind it opens **Users**, and on a managed instance the three tabs hidden there — `storage`, `github` and `backup` — fall back to **Users** as well. The other two conditional tabs behave differently: `mcp-tokens` and `dev-notifications` open their panel even when the MCP addon is off or the instance is not in development mode, with nothing highlighted in the sidebar, because the entry is missing from it. + ## Plugin activity and audit Plugins that are granted data-access capabilities have every host-mediated action they take recorded in a tamper-evident, hash-chained log. This log is separate from the instance **Audit** tab described above. -- **Admins** can review the per-plugin capability audit — every core-data read, broadcast, notification, and AI call a plugin made, with the acting user, the resource touched, and the outcome. This is exposed for each installed plugin in the admin plugin management view. +- **Admins** can review the per-plugin capability audit — every core-data read, broadcast, notification, and AI call a plugin made, with the acting user, the resource touched, and the outcome. It is served by `GET /api/admin/plugins//audit`; the admin plugin view itself currently surfaces only each plugin's error log (**View error log**). - **Every user** (not just admins) can see the plugin actions taken in their own name under **Settings → Plugins**. This is what keeps a plugin's broad read grants accountable to the person whose data was read. See [Audit-Log](Audit-Log) for details on the hash chain and how the two logs differ. diff --git a/wiki/Admin-Plugins.md b/wiki/Admin-Plugins.md index 14467483a..bc86ff711 100644 --- a/wiki/Admin-Plugins.md +++ b/wiki/Admin-Plugins.md @@ -29,7 +29,7 @@ Both views share a toolbar: a **Search plugins…** box, a **Type** filter (Widg 2. Click a card to open the **pre-install review dialog** (see below). 3. Click **Install**. -If the newest release requires a newer TREK than you are running, the button changes: when an older release still fits you get **Install {version}** for that one; when nothing fits, the button reads **Incompatible** and is disabled. Either way the dialog explains why in an amber note — it does not hide the reason behind a tooltip. +If the newest release does not support the TREK you are running — either it needs a newer one, or your TREK has outgrown the upper bound it declares (`>=3.2.0 <4.0.0` on TREK 4, say) — the button changes: when an older release still fits you get **Install {version}** for that one; when nothing fits, the button reads **Incompatible** and is disabled. Either way the dialog explains why in an amber note — it does not hide the reason behind a tooltip. A newly installed plugin is **off**. Nothing runs until you enable it. @@ -37,7 +37,7 @@ A newly installed plugin is **off**. Nothing runs until you enable it. Before installing, read these sections: -- **What it can access** — the plugin's requested permissions, rendered as plain-language lines ("Read the trips…", "Create and edit places…"). Unknown permission codes appear verbatim. If it asks for nothing, this reads *Needs no special access.* +- **What it can access** — a plain-language summary of what the plugin's permissions let it do ("Reads your trips", "Edits places", "Provides photos"), plus a line for its own isolated database. It is a summary, not the raw permission list: permissions TREK has no summary line for — `notify:send`, `ai:invoke`, `jobs:run`, `db:write:members` and others — do not appear here at all, and a plugin that asks only for those still reads *Needs no special access.* For the complete list, read the manifest in the plugin's source repository. Only the consent dialog for an update renders permissions code by code, unknown codes verbatim. - **Connects to** — every host the manifest declares it may reach, as monospace chips. - **Setup** — settings the plugin will ask you (or each user) to fill in, tagged **Instance-wide** or **Per user**, and **Required** where applicable. - **Details** — version, size, the TREK version range it requires, when it was reviewed, and total downloads. @@ -48,7 +48,7 @@ A footer links to the **Source repository**, **Report an issue**, and the plugin Some plugins talk to a service only *you* can name — a self-hosted Gotify, ntfy, or similar. Their manifest cannot list a host, so the review dialog adds a **+ hosts you add** chip and this note: -> *This plugin talks to a service only you can name (a self-hosted server). After installing, add the hosts it may reach under ⋯ → Allowed hosts. It can reach no others.* +> *This plugin talks to a service only you can name. After installing, add the hosts it may reach under ⋯ → Allowed hosts. It can reach no others.* After install, open the row's **⋯ → Allowed hosts** dialog and add hostnames one at a time. Until you add at least one, the plugin's row shows an amber **Add allowed host** chip, because it can reach nothing and would otherwise look silently broken. Once hosts exist the chip turns blue and counts them. @@ -56,10 +56,13 @@ Saving restarts the plugin so it picks up the new list — a running plugin's al ## Enabling, restarting, and disabling -Each installed row has a toggle (**Enable plugin**) and shows **Active** or **Off**. A coloured dot on the icon tile reflects runtime health: active, starting, error, inactive, disabled, or incompatible. +Each installed row has a toggle (**Enable plugin**) and shows **Active** or **Off**. A coloured dot on the icon tile reflects runtime health: green when the plugin is active, a pulsing blue while it starts, red when it has errored, grey when it is not running. Incompatibility is not a dot colour — a plugin this TREK cannot run gets an amber chip on the row instead. -Enabling can be refused for good reasons, each of which opens the right remedy: +Enabling can be refused for good reasons. The first three are version gates that run before everything else — a plugin that cannot run on this TREK is never offered a permission dialog, because consenting would not make it start: +- **This TREK is outside the range the plugin declares** — the row carries an amber *Needs TREK {range} — this server runs {host}* chip and the toggle is refused. Expect this after a major upgrade: a plugin that declared `>=3.2.0 <4.0.0` stops activating on TREK 4 even though it ran fine the day before. There is no dialog to click through, it takes a release from the author that admits this TREK — and once the registry has one, the row's **Update** button offers it. +- **The plugin does not say which TREK versions it supports** — the chip reads *Does not say which TREK versions it supports*. That is a plugin folder which predates the range field or was dropped into the plugins directory by hand: **Rescan** registers it rather than letting it vanish silently, and the gate then refuses it instead of guessing. It needs a manifest with a `trek` range. +- **The plugin needs a newer plugin API** — its manifest's `apiVersion` is higher than the plugin API this TREK implements (currently v1). Install, upload, and dev-link reject that up front, so this only shows for a plugin picked up from the plugins directory on disk — registered at the next restart, or when you press **Rescan**. - **A required addon is disabled** — a toast names the addon; turn it on in [Admin-Addons](Admin-Addons). - **A plugin dependency is missing or outdated** — a dialog lists each dependency with a one-click **Download** / **Update** that installs the newest compatible version and then retries. - **The update widened its permissions** — the consent dialog (below). diff --git a/wiki/Admin-Users-and-Invites.md b/wiki/Admin-Users-and-Invites.md index 7cbd8e0fb..570de54b8 100644 --- a/wiki/Admin-Users-and-Invites.md +++ b/wiki/Admin-Users-and-Invites.md @@ -12,7 +12,7 @@ The user table shows every registered account with the following columns: |--------|-------------| | **User** | Avatar, username, and an always-visible presence dot (green = online, grey = offline) | | **Email** | Account email address | -| **Role** | Badge showing `admin` or `user` | +| **Role** | Badge showing **Administrator** or **User** | | **Created** | Account creation date | | **Last Login** | Date and time of most recent login | | **Actions** | Edit and delete buttons | @@ -27,11 +27,13 @@ Click the pencil icon on any row to open the edit form. You can change: - **Username** - **Email address** -- **Role** — toggle between `user` and `admin` +- **Role** — a dropdown with **User** and **Administrator** - **Password** — set a new password; must be at least 8 characters Click **Save** to apply changes. +Below the fields sits **Reset passkeys**, which removes every passkey that user has registered — the recovery path when someone loses their authenticator. It asks for confirmation, reports how many passkeys it removed, and takes effect immediately rather than on **Save**. The user can still sign in with their password. See [Passkeys](Passkeys). + ### Delete a user Click the trash icon and confirm. Deletion is permanent. The user's account is removed from the database along with their data (cascade behavior is enforced at the database level). @@ -50,7 +52,7 @@ Invite links let a specific number of people register themselves. This is useful ### Creating an invite -Click **Create Invite** (invite links section, below the user table). Configure: +Click **Create Link** (invite links section, below the user table). Configure: - **Max uses** — how many times the link can be used before it expires: `1×`, `2×`, `3×`, `4×`, `5×`, or `∞` (unlimited). Defaults to `1×`. - **Expiry** — how long the link remains valid: `1d`, `3d`, `7d`, `14d`, or `∞` (no expiry). Defaults to `7d`. @@ -87,4 +89,5 @@ The **Users** tab also hosts the Permissions panel at the bottom, which controls - [Invite-Links](Invite-Links) - [Admin-Permissions](Admin-Permissions) - [Two-Factor-Authentication](Two-Factor-Authentication) +- [Passkeys](Passkeys) - [Admin-Panel-Overview](Admin-Panel-Overview) diff --git a/wiki/Appearance-Settings.md b/wiki/Appearance-Settings.md index 41caebdef..6ed4ac32e 100644 --- a/wiki/Appearance-Settings.md +++ b/wiki/Appearance-Settings.md @@ -8,7 +8,7 @@ Tune how TREK looks — colour mode, accent scheme, readability, and which dashb Open **Settings** and select the **Appearance** tab. -Changes preview instantly and are saved to your account a moment later, so they follow you to every browser you sign in with. A **Reset to defaults** button at the bottom of the tab restores everything on this page in one click. +Changes preview instantly and are saved to your account a moment later, so they follow you to every browser you sign in with. A **Reset to defaults** button at the bottom of the tab restores the colour scheme, the readability settings, the dashboard widgets and the mobile layout in one click. Your **Color mode** (Light / Dark / Auto) is a separate setting and is left as it is. > These are personal settings — no permission and no admin toggle gate them. @@ -77,6 +77,15 @@ Each of the four rows renders a live sample in that tier ("Large heading", "Medi Hiding a widget here only affects your own dashboard. Widgets that depend on an addon (for example **Collections**) also need that addon enabled by an admin — see [Dashboard-Widgets](Dashboard-Widgets) and [Admin-Addons](Admin-Addons). +## On a phone + +Opened on a phone, the Appearance screen carries an extra **Mobile** card that the desktop tab does not have: + +- **Bottom navbar** — which items sit in the bottom bar, in which order, and which are demoted under **More**. **Dashboard** is pinned first and cannot be moved; two more items fit next to it, and a counter above the list shows how full the bar is. Leaving both lists untouched keeps TREK's automatic split. +- **Dashboard order** — reorder how the trip list and the inline widgets stack on the phone dashboard. The featured trip always stays on top. A block whose widget is switched off under **Dashboard widgets** keeps its place in the list and is marked **Hidden**. + +Both are stored on your account alongside the rest of the appearance settings, and both are cleared by **Reset to defaults**. + ## Permissions None. Every setting on this tab applies only to your own account. diff --git a/wiki/Audit-Log.md b/wiki/Audit-Log.md index a51f9a606..bb861a4e8 100644 --- a/wiki/Audit-Log.md +++ b/wiki/Audit-Log.md @@ -21,6 +21,11 @@ Actions are grouped by area below. The **Action key** is the raw value stored in | `user.login_failed` | Login attempt failed | | `user.password_change` | User changed their password | | `user.account_delete` | User deleted their account | +| `user.password_reset_request` | Password reset requested | +| `user.password_reset_success` | Password reset completed | +| `user.password_reset_fail` | Password reset attempt rejected (`reason` in details) | + +A request for an account that can actually be reset writes **two** rows: one with `delivered: "pending"` when the mail is handed off, one with the delivery result. Every other outcome writes a single row carrying a `reason` instead — `no_user`, `oidc_only`, `throttled_per_email` or `password_login_disabled`. Passkey logins are not a separate key: they land as `user.login` with `method: passkey` in the details. ### MFA @@ -29,6 +34,14 @@ Actions are grouped by area below. The **Action key** is the raw value stored in | `user.mfa_enable` | MFA enabled on an account | | `user.mfa_disable` | MFA disabled on an account | +### Passkeys + +| Action key | Description | +|---|---| +| `user.passkey_register` | Passkey enrolled | +| `user.passkey_delete` | Passkey removed (resource = the passkey's numeric ID) | +| `user.passkey_clone_suspected` | A passkey presented a signature counter that did not advance — a possible cloned authenticator. That assertion is rejected, the credential stays enabled | + ### Trips | Action key | Description | @@ -37,6 +50,10 @@ Actions are grouped by area below. The **Action key** is the raw value stored in | `trip.update` | Trip updated (includes changed fields) | | `trip.copy` | Trip duplicated (includes source and new trip IDs) | | `trip.delete` | Trip deleted (includes trip ID and title) | +| `trip.transfer_ownership` | Trip ownership transferred (includes the trip title and both owner emails) | +| `trip.invite_link_create` | Trip invite link created or rotated (a trip has one link, so creating it again replaces the previous token) | +| `trip.invite_link_delete` | Trip invite link revoked | +| `trip.invite_link_join` | An invite link was accepted (`joined` in details). The row is written even when nobody was added — the trip owner or an existing member opening the link logs `joined: false` | ### Admin actions @@ -45,22 +62,41 @@ Actions are grouped by area below. The **Action key** is the raw value stored in | `admin.user_create` | User created by admin | | `admin.user_update` | User edited by admin (role, email, username, etc.) | | `admin.user_delete` | User deleted by admin | +| `admin.user_mfa_reset` | A user's MFA reset by admin | +| `admin.user_passkeys_reset` | All of a user's passkeys removed by admin | | `admin.invite_create` | Invite link created | | `admin.invite_delete` | Invite link deleted | | `admin.permissions_update` | Instance permissions updated | | `admin.oidc_update` | OIDC/SSO settings updated | | `admin.addon_update` | Addon enabled, disabled, or configured | -| `admin.oauth_session.revoke` | OAuth session revoked by admin | +| `admin.oauth_session_revoke` | OAuth session revoked by admin | +| `admin.mcp_token_delete` | MCP token revoked by admin | | `admin.rotate_jwt_secret` | JWT secret rotated | | `admin.bag_tracking` | Bag tracking feature toggled | | `admin.places_photos` | Places photos feature toggled | | `admin.places_autocomplete` | Places autocomplete feature toggled | | `admin.places_details` | Places details feature toggled | +| `admin.places_enrich` | Place enrichment feature toggled | | `admin.collab_features` | Collaboration features updated | +| `admin.packing_template_create` | Packing template created | | `admin.packing_template_delete` | Packing template deleted | +| `admin.plugin_retrust` | Plugin signing key re-trusted (records the old and the new key fingerprint) | +| `admin.storage_update` | Storage configuration saved (secrets redacted in the details) | +| `admin.storage_test` | Storage backend probed (the probe writes and deletes one test object) | +| `admin.storage_backfill` | Replica catch-up started | +| `admin.storage_backfill_cancel` | Replica catch-up cancelled | +| `admin.storage_migration` | Category migration started | +| `admin.storage_migration_cancel` | Category migration cancelled | +| `admin.storage_stats_refresh` | Storage usage stats recomputed | | `admin.default_user_settings_update` | Default user settings updated | | `admin.demo_baseline_save` | Demo baseline snapshot saved | + +### Settings + +| Action key | Description | +|---|---| | `settings.app_update` | App settings updated (SMTP, webhooks, MFA policy, etc.) | +| `settings.api_keys_update` | Maps / OpenWeather / Unsplash API keys changed. Written for any user saving their own keys; an admin's save also updates the instance-wide ones. Records the changed key **names** only, never the values | ### Backups @@ -91,19 +127,21 @@ Actions are grouped by area below. The **Action key** is the raw value stored in | `oauth.token.revoke` | OAuth token revoked | | `oauth.token.grant_failed` | OAuth token grant attempt failed | | `oauth.token.client_auth_failed` | OAuth client authentication failed | +| `oauth.token.replay_detected` | An already-revoked refresh token was replayed. The whole token chain and the user's OAuth sessions for that client are revoked. Two clients racing on the same token do **not** land here: that is logged as `oauth.token.refresh` with `concurrent: true` | ### Integrations | Action key | Description | |---|---| | `immich.private_ip_configured` | Immich URL saved that resolves to a private IP | +| `airtrail.private_ip_configured` | AirTrail URL saved that resolves to a private IP | ## Log columns | Column | Description | |---|---| | Time | Timestamp of the action | -| User | Username and email of the acting user (or `anonymous` for unauthenticated events) | +| User | Username of the acting user, falling back to their email, then `#`. Unauthenticated events show `—` (the plain-text log file writes `anonymous` for those instead) | | Action | Action key (see tables above) | | Resource | Affected resource (filename, trip ID, tool name, etc.) where applicable | | IP | Client IP address | diff --git a/wiki/Backups.md b/wiki/Backups.md index f9a78ea3b..0789710ae 100644 --- a/wiki/Backups.md +++ b/wiki/Backups.md @@ -15,7 +15,7 @@ A backup is a ZIP archive with these entries: | Entry | Contents | |---|---| | `travel.db` | The full SQLite database | -| `uploads/` | All uploaded attachments, covers, and avatars (default location — see [[Admin: Storage|Admin-Storage]]) | +| `uploads/` | All uploaded attachments, covers, and avatars (default location — see [Admin: Storage](Admin-Storage)) | | `plugins-data/` | Each installed plugin's own database + files (present only if plugins are installed) | | `plugins-code/` | The installed plugin code, so a restore is self-contained (dev-linked plugins are skipped) | @@ -32,7 +32,9 @@ You can also download or delete any existing backup from the list. You can restore from: - **A stored backup** — click **Restore** next to any backup in the list. -- **An uploaded ZIP** — click **Upload & Restore** and select a backup file from your computer (maximum upload size: 500 MB by default, configurable with the `BACKUP_UPLOAD_LIMIT_MB` environment variable — see [Environment-Variables](Environment-Variables)). +- **An uploaded ZIP** — click **Upload Backup** and select a backup file from your computer (maximum upload size: 500 MB by default, configurable with the `BACKUP_UPLOAD_LIMIT_MB` environment variable — see [Environment-Variables](Environment-Variables)). + +A second limit caps the **decompressed** size of a restore archive at 5 GB by default (`BACKUP_MAX_DECOMPRESSED_MB`). It applies to both routes — a stored backup as well as an uploaded ZIP — and a restore that exceeds it is refused with `Backup exceeds the maximum decompressed size.` Raise it if your own backups legitimately grow past the default, otherwise they become unrestorable. Before restoring, TREK runs integrity checks on the uploaded database: @@ -41,7 +43,7 @@ Before restoring, TREK runs integrity checks on the uploaded database: > **Warning:** Restoring replaces all current data. Back up your current state first if you want to keep it. -> **Plugins & restart:** `travel.db` and `uploads/` are swapped in immediately. Plugin data and code are **staged** and applied on the **next server restart** — the running plugins hold their databases open, so they can't be swapped live (the same reason the bundled encryption key only takes effect on restart). Restart the server after restoring an instance that uses plugins. +> **Plugins & restart:** `travel.db` and `uploads/` are swapped in immediately. Plugin data and code are **staged** beside the live trees and applied right away: the running plugins hold their databases open, so the restore asks the plugin runtime to shut them down and swaps the trees in the moment the handles are closed. If the runtime isn't up (plugins switched off, or a restore that happens during startup), the staged trees are applied at the next boot instead. Restart the server after restoring an instance that uses plugins — the plugins are stopped for the swap and stay down until the process restarts, and the bundled encryption key is only read at startup. ## Auto-backup @@ -54,7 +56,7 @@ Enable scheduled backups in the **Auto-Backup** section of the Backup tab. - Weekly - Monthly -**Retention** (`Keep last … days`) — enter a number of days. Backups older than that many days are pruned after each auto-backup run. Set to **0** to keep all backups indefinitely (no pruning). +**Retention** (`Delete old backups after`) — pick one of the preset windows: 1 day, 3 days, 7 days, 14 days, 30 days, or **Keep forever**. Auto-backups older than the chosen window are pruned after each auto-backup run; **Keep forever** (stored as `keep_days = 0`) keeps all backups indefinitely. **Schedule** options (depend on interval): @@ -84,7 +86,7 @@ The following actions are recorded in the [Audit-Log](Audit-Log): ## See also -- [[Admin: Storage|Admin-Storage]] — replicate backups to S3-compatible storage +- [Admin: Storage](Admin-Storage) — replicate backups to S3-compatible storage - [Encryption-Key-Rotation](Encryption-Key-Rotation) - [Admin-Panel-Overview](Admin-Panel-Overview) - [Security-Hardening](Security-Hardening) diff --git a/wiki/Budget-Tracking.md b/wiki/Budget-Tracking.md index d4c522bae..e092b86d5 100644 --- a/wiki/Budget-Tracking.md +++ b/wiki/Budget-Tracking.md @@ -24,40 +24,36 @@ Costs is **multi-currency** (#551). Three settings are involved, and they do dif - Each **expense** carries **its own currency** — pick it in the expense modal and enter what the receipt says (a $100 dinner on a rouble trip is `100 USD`). It is converted into the trip currency at a rate **frozen when you save it**, so a settled debt doesn't reopen when the market moves. - Your **display currency** (Settings → General) converts what you *read* — totals, chart, balances — into one currency. It changes nothing that is stored. Left on **Trip currency** (the default), each trip is shown in its own currency. -165 currencies are supported, with rates from [Frankfurter](https://frankfurter.dev) (no API key needed). When an item's currency differs from the display currency, the modal shows the converted amount alongside the rate (`1 {from} in {to}`), and the ledger row shows both (`$100.00 → 7 668,71 ₽`). +165 currencies are supported, with rates from [Frankfurter](https://frankfurter.dev) (no API key needed). When an item's currency differs from the display currency, the modal shows the original next to the converted amount (`$100.00 ≈ 7 668,71 ₽ · live rate`), and the ledger row shows both (`$100.00 → 7 668,71 ₽`). > **Read [Currencies](Currencies) for the full picture** — how the three interact, what happens when you change a trip's currency, and which currency a public share link is shown in. ## Categories -Expenses are grouped into categories. Each category is shown with a small colored square indicator that cycles through a 12-color palette as you add more categories. +Every expense sits in one of **14 fixed categories**. You cannot add, rename, reorder or delete them — you pick one from a row of pills in the expense modal. Each category carries its own icon and colour, which is what the coloured tab on an expense row and the swatches in the sidebar breakdown are showing: -From the toolbar you can: +**Accommodation**, **Food & drink**, **Groceries**, **Transport**, **Flights**, **Activities**, **Sightseeing**, **Shopping**, **Fees & tickets**, **Health**, **Tips**, **Fuel**, **Parking**, **Other**. -- **Add a category** — type a name and click the **+** button (or press Enter). -- **Rename a category** — click the pencil icon next to its name in the category header. -- **Reorder categories** — drag the grip handle on the left of the category header. -- **Delete a category** — click the trash icon in the category header. This deletes all expense items inside it. +Expenses written before the Costs rework carried free-text categories. Those are matched onto the fixed keys where the stored label is recognisable (a booking saved as `Flight` reads as **Flights**, `gas` as **Fuel**) and fall back to **Other** otherwise. Nothing is rewritten in the database, only how the row is displayed. ## Expense items -Each category contains a table of items with the following columns: +Expenses are listed as a ledger grouped by **day**, newest day first, with the day's total on the right of each group header. Recorded settle-up payments sit in the same list, so the ledger reads as everything that happened on the trip's money in order. A row shows the category as a coloured tab, the name, the payer chips with what each of them put in, the note, and the total in your display currency — plus a green **you lent** / red **you borrowed** chip under the amount when the split leaves you up or down on it. An expense entered in another currency shows the original and the converted amount under the name. -| Column | Notes | +Above the list are a search box, a category filter, a day filter, an **All / Paid by me / I'm owed** switch and the CSV export button. + +Click **Add expense**, or the pencil beside a row, to open the expense editor: + +| Field | Notes | |---|---| -| Name | Editable inline. Read-only when linked to a reservation. | -| Total | The total cost for this item. | -| Persons | Number of persons (or member chips on multi-member trips). | -| Days | Number of days. | -| Per Person | Calculated: Total ÷ Persons. | -| Per Day | Calculated: Total ÷ Days. | -| Per Person/Day | Calculated: Total ÷ (Persons × Days). | -| Date | Optional expense date. | -| Note | Free-text note. | - -Click any editable cell to edit it inline. Drag the grip handle to reorder items within a category. - -Add a new item using the inline **add row** at the bottom of each category table. +| What was it for? | The expense name. Required — the dialog will not save without it. | +| Total amount | What the receipt says. In Ticket mode it is summed from the items instead and cannot be typed. | +| Currency | The expense's own currency. | +| Day | Optional expense date. Undated expenses group under **No date**. | +| Category | One of the 14 fixed categories. | +| Who paid? | Who actually put the money down — see [Who paid](#who-paid). | +| Split | How the total is shared out — see [Splitting costs](#splitting-costs). | +| Note | Free-text note, shown on the row. | ### Expenses linked to a booking or a place @@ -65,26 +61,34 @@ An expense can hang off a **booking** (reservation or transport) or off a **plac Deleting the booking or the place deletes its linked expense with it. Removing the expense from the record's Costs block deletes only the expense and leaves the record standing. +## Who paid + +**Who paid?** in the expense editor records who actually put the money down. It is the other half of the settlement maths — the split says who owes for the expense, this says who is out of pocket for it: + +- **One person paid** — pick a member from the dropdown. This is the default. +- **Multiple people paid** — switch with the link beside the label, include each payer and type what each of them put in. The amounts have to add up to the total; until they do, the dialog reads *Payer amounts must add up to …* and refuses to save. +- **No one paid yet** — the first entry in the single-payer dropdown, for an expense you are only planning. The amount still counts toward **Total trip spend**, and everyone on its split is still charged their share in **Balances** — what is missing is the credit side, so nobody is recorded as having covered it. + +An expense with no payer is flagged **Unfinished** on its row and counted into the **Outstanding amount** card, which is where to look when the balances show money owed that nobody is down as having paid. + ## Splitting costs -The **Persons** column behaves differently depending on the trip: +**Split** decides who owes for the expense. Every trip member is listed and can be included or excluded, and there are three modes: -- **Single-user trip** — enter a number of persons directly. -- **Multi-member trip** — a member chip picker appears. Click the edit button to open the expense modal, where you can select: - - **Equally** — Splits the cost equally among selected members. Remainder cents (from rounding errors) are distributed deterministically and rotated using the item ID to ensure everyone is charged equally over the course of the trip. - - **Custom** — Enter specific custom amounts for each traveler. The sum of the custom splits must balance exactly to the total price. - - **Ticket** — Build an itemized list of expenses (e.g. Apples: $10, cake: $50, Milk: $40) and assign specific trip participants to split each individual item. Individual shares are calculated cent-perfectly, the total expense price is automatically summed, and the list of itemized splits is saved/restored across edits. - -Click an assigned member chip again to mark them as **paid** (the chip shows a green ring). +- **Equally** — Splits the cost equally among selected members. Remainder cents (from rounding errors) are distributed deterministically and rotated using the item ID to ensure everyone is charged equally over the course of the trip. +- **Custom** — Enter specific custom amounts for each traveler. The sum of the custom splits must balance exactly to the total price. +- **Ticket** — Build an itemized list of expenses (e.g. Apples: $10, cake: $50, Milk: $40) and assign specific trip participants to split each individual item. Individual shares are calculated cent-perfectly, the total expense price is automatically summed, and the list of itemized splits is saved/restored across edits. ![Add Expense](assets/BudgetAddExpensive.gif) ## Settlement calculator -When multiple members are assigned to expenses and there are outstanding debts between members, a collapsible **Settlement** section appears inside the total card. Click the section header to expand it. It shows the minimum number of transfers needed to settle all debts (using a greedy matching algorithm), including: +Costs works out the minimum number of transfers needed to settle all debts (using a greedy matching algorithm) and keeps the answer in the right-hand column, split across two cards: -- Transfer flows: who pays whom and how much. -- Net balances: each member's overall surplus or deficit. +- **Settle up** — the transfer flows: who pays whom and how much. The number of open flows sits in the card header, and each flow has a **Settle** button that records it as done. +- **Balances** — net balances: each member's overall surplus or deficit. + +**Settle up** in the panel header records every open flow at once. **Add payment** on the card records a single transfer by hand, for a repayment that did not follow a suggested flow. Recorded payments then appear in the expense ledger as their own rows, with edit and undo beside them. Balances are always netted in the **trip currency** and converted to your display currency once, at the end — so they stay stable even when the trip mixes currencies. @@ -96,10 +100,14 @@ A recorded payment carries **its own currency** too: settling a rouble debt with ## Costs summary -The right-hand column contains two widgets: +Four cards sit above the expense list: -- **Total card** — displays the grand total in large type. On multi-member trips it also shows a per-member breakdown with a proportional bar. -- **Donut chart** — spending by category. Each segment uses that category's color. The legend always shows the amount and percentage for each category; hovering a legend row highlights it. +- **You owe** — what you still have to transfer, with the members you owe it to. +- **You're owed** — the same in the other direction. +- **Outstanding amount** — the total of the expenses that have no payer yet, and how many of them there are. +- **Total trip spend** — the grand total in large type, with your share and what you paid underneath. + +Below the settle-up and balances cards, the right-hand column ends with **By category**: spending per category as a ranked list of bars in the category colours. Only categories with spend on them appear, sorted by amount, and the bars are scaled against the biggest category rather than the trip total, so the ranking stays readable. ## Exporting @@ -107,7 +115,7 @@ Click **Export CSV** in the toolbar to download all expenses as a spreadsheet (r ## Permissions -All write operations (adding/editing/deleting items and categories, and an expense's currency) require the `budget_edit` permission. The **trip** currency lives on the trip itself, so changing that requires `trip_edit` instead. +All write operations (adding/editing/deleting expenses and settle-up payments, and an expense's currency) require the `budget_edit` permission. The **trip** currency lives on the trip itself, so changing that requires `trip_edit` instead. ## See also diff --git a/wiki/Calendar-Feeds.md b/wiki/Calendar-Feeds.md index 7e9bdcf06..f187b5bfb 100644 --- a/wiki/Calendar-Feeds.md +++ b/wiki/Calendar-Feeds.md @@ -2,13 +2,13 @@ Subscribe your calendar app to a TREK trip so it stays in sync automatically, instead of importing a snapshot once. -> **Not the same as the ICS export.** The **Download ICS** action described in [Day-Plans-and-Notes](Day-Plans-and-Notes) writes a one-off `.ics` file that never changes after you import it. A calendar *feed* is a live URL your calendar re-fetches on its own. Use the export for a frozen copy, a feed for something that keeps up with your edits. +> **Not the same as the ICS export.** The **Download .ics** action described in [Day-Plans-and-Notes](Day-Plans-and-Notes) writes a one-off `.ics` file that never changes after you import it. A calendar *feed* is a live URL your calendar re-fetches on its own. Use the export for a frozen copy, a feed for something that keeps up with your edits. ## Where to find it There are two feeds, reached from two places: -- **Per-trip feed** — in the trip planner, hover the **ICS** button in the Day Plan sidebar toolbar and choose **Subscribe to calendar**. (The other entry in that menu, **Download ICS**, is the one-off export.) +- **Per-trip feed** — in the trip planner, click the **Export** button in the Day Plan sidebar toolbar and pick **Subscribe to calendar** under **Calendar**. (The entry above it, **Download .ics**, is the one-off export.) The subscribe entry only shows up if you may manage the trip's share links — see *Permissions* below. - **All-trips feed** — on the **My Trips** dashboard, click the calendar-plus button in the toolbar (**Subscribe to all trips**). Both open the same dialog. @@ -61,12 +61,13 @@ The feed carries the same events as the ICS export: - **Reservations** — hotels, restaurants, and transport. Flights and other transport take their start and end from the departure and arrival endpoints, each in its own time zone. Reservations with no placeable date are skipped. - **Accommodations** — an all-day event covering every night of the stay, from the arrival day to the departure day, so a hotel sits above those days rather than appearing once on the day you check in. The dates come from the trip days the stay is attached to, so reordering days moves the event with them. - **Check-in and check-out** — separate timed events on the arrival and departure days whenever the stay records those times. If you entered a check-in window, its end becomes the event's end time. +- **Car pickup and drop-off** — a reservation of type *Car* also gets two standalone timed events, *Pickup: {title}* and *Drop-off: {title}*, on top of the rental's own booking block, both carrying the reservation's location. Each side prefers its own endpoint — the pickup endpoint for the pickup, the return endpoint for the drop-off — and takes that endpoint's local time and time zone. Otherwise it falls back to the booking's own times (an end value stored as a bare clock is paired with the booking's start date) and to the time zone of the linked place. A side with no usable date and time from either source is skipped, so a rental imported with only one geocoded endpoint can end up with just one marker. Feeds are served with cache headers that tell clients not to cache, plus an hourly refresh hint (`REFRESH-INTERVAL` / `X-PUBLISHED-TTL` of one hour). Most calendar apps treat that as a suggestion — Google in particular refreshes on its own schedule, often much slower — so an edit may take a while to show up. ## Permissions -No dedicated permission gates feeds. You can manage a per-trip token if you own the trip or are a member of it; anyone else gets a *Trip not found*. The all-trips feed is always scoped to your own account. +Managing a per-trip token requires the **`share_manage`** permission — the same right that governs invite and share links. By default it sits with the trip owner; an instance can lower it to trip members in [Admin-Permissions](Admin-Permissions). A member without it never sees **Subscribe to calendar** in the export dialog and gets a *No permission* from the token endpoint, while anyone with no access to the trip at all still gets a *Trip not found*. The all-trips feed needs no permission — it is always scoped to your own account. Because the token grants unauthenticated read access, enabling a feed effectively shares that trip's contents with whoever holds the link, regardless of trip roles — see [Public-Share-Links](Public-Share-Links) for the equivalent trade-off on the sharing side. diff --git a/wiki/Collab-Chat.md b/wiki/Collab-Chat.md index fbf7bea79..17462e8c9 100644 --- a/wiki/Collab-Chat.md +++ b/wiki/Collab-Chat.md @@ -14,7 +14,7 @@ The Collab addon must be enabled by an admin and the Chat sub-feature must be tu Type in the input field at the bottom and press **Enter** (or click the send button) to post. Hold **Shift + Enter** to insert a line break without sending. -Messages load in pages of 100. A **Load more** button appears at the top of the chat when older messages are available. +Messages load in pages of 100. A **Load older messages** button appears at the top of the chat when older messages are available. ## Emoji diff --git a/wiki/Collab-Notes.md b/wiki/Collab-Notes.md index f4a19eb6e..963da2d9b 100644 --- a/wiki/Collab-Notes.md +++ b/wiki/Collab-Notes.md @@ -47,7 +47,13 @@ Click the pin icon on a note card to pin it. Pinned notes sort to the top of the You can attach images, PDFs, and other files to a note (requires the `file_upload` permission). Maximum file size is **50 MB** per file. -The following file types are blocked: `.svg`, `.html`, `.htm`, `.xml`, `.xhtml`, `.js`, `.jsx`, `.ts`, `.exe`, `.bat`, `.sh`, `.cmd`, `.msi`, `.dll`, `.com`, `.vbs`, `.ps1`, `.php`. +The following file types are blocked: + +- Markup a browser would render: `.svg`, `.svgz`, `.html`, `.htm`, `.shtml`, `.shtm`, `.xml`, `.xhtml`, `.xht` +- Scripts: `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.php`, `.py`, `.rb`, `.pl` +- Executables: `.exe`, `.bat`, `.sh`, `.cmd`, `.msi`, `.dll`, `.com`, `.vbs`, `.ps1`, `.app` + +An attachment is also rejected when its MIME type contains `svg`, `html`, or `javascript`, whatever its extension says. The same blocklist guards the trip file manager (see [Documents-and-Files](Documents-and-Files)), where uploads must additionally match the admin's **Allowed File Types** setting (video files are exempt from that list) — note attachments are not subject to it at all. Image thumbnails are shown on the note card. Click a thumbnail to open a lightbox. PDFs open in a document viewer overlay. diff --git a/wiki/Collab-Polls.md b/wiki/Collab-Polls.md index d00143593..faa33703c 100644 --- a/wiki/Collab-Polls.md +++ b/wiki/Collab-Polls.md @@ -10,21 +10,22 @@ Open the trip planner → **Collab** tab → **Polls** section. The Collab addon ## Creating a poll -Users with `collab_edit` permission can click **+ New** in the Polls header. A modal opens with the following fields: +Users with `collab_edit` permission can click **+ New Poll** in the Polls header. A modal opens with the following fields: | Field | Required | Notes | |-------|----------|-------| | Question | Yes | The poll prompt | | Options | Yes | At least 2 options required; add more with **+ Add option** | | Multiple choice | No | Toggle on to allow voters to select more than one option | +| Deadline | No | Mobile only; date and time after which voting closes automatically | -Click **Create** to save and broadcast the poll to all connected members. +Click **Create Poll** to save and broadcast the poll to all connected members. ## Voting Click an option button to vote. A filled circle and blue highlight indicate your selection. For **multiple-choice** polls, you can click additional options to select more than one; clicking an already-selected option removes your vote for that option. For **single-choice** polls, clicking a different option moves your vote to the new selection. -Percentage bars and voter avatars are only visible **after you have voted** or once the poll is closed or expired. +Voter avatars and the numeric percentage are only visible **after you have voted** or once the poll is closed or expired. The fill bar behind each option is always drawn, in a faint neutral tint until you vote, and the total vote count in the poll header is visible to everyone — so the rough distribution can be read before you cast your vote. ## Results @@ -38,7 +39,7 @@ The option with the most votes is highlighted when the poll is closed or expired ## Deadline -Polls may have an optional deadline set via the API. When a deadline is present, a live countdown badge appears on the poll card, updating every **30 seconds**. The badge shows the remaining time in days/hours/minutes format. +Polls may carry an optional deadline. On **mobile** the new-poll sheet has a **Deadline** field — a date-and-time picker with a clear button. The desktop create modal does not offer one yet, so on desktop a deadline can only be set through the API or MCP. When a deadline is present, a live countdown badge appears on the poll card, updating every **30 seconds**. The badge shows the remaining time in days/hours/minutes format. When the deadline passes, the poll is automatically treated as closed: voting is disabled and results are displayed to everyone. diff --git a/wiki/Collections.md b/wiki/Collections.md index 005dc5911..13cd0be53 100644 --- a/wiki/Collections.md +++ b/wiki/Collections.md @@ -14,7 +14,7 @@ A place you save to a trip only exists inside that trip. Collections is the oppo When the admin has enabled the addon, a **Collections** entry appears in the main navigation (and a bottom-tab on mobile). Opening it shows your lists on the left, the active list's places in the middle, and a map on the right. -The dashboard also gains a **Collections widget** that surfaces your lists as compact badges. Each user can hide that widget from their own dashboard under [Display Settings](Display-Settings) without affecting anyone else. +The dashboard also gains a **Collections widget** that surfaces your lists as compact badges. Each user can hide that widget from their own dashboard under **Dashboard widgets** on the Appearance tab (see [Appearance-Settings](Appearance-Settings)) without affecting anyone else. ## Lists diff --git a/wiki/Contributing.md b/wiki/Contributing.md index 5d5c21d0f..f4a0db201 100644 --- a/wiki/Contributing.md +++ b/wiki/Contributing.md @@ -52,12 +52,14 @@ See the [[Development Environment|Development-environment]] page for the full se ## Tech Stack -| Layer | Technology | -|---|---------------------------------------------------------------------------------| -| Frontend | React 18, TypeScript, Zustand, Leaflet, Tailwind CSS, Vite | -| Backend | Express, TypeScript, better-sqlite3 | -| Real-time | WebSocket (ws) | -| Database | SQLite with WAL mode | -| Auth | JWT (HS256), bcrypt, TOTP MFA, OIDC | -| Maps | Leaflet + react-leaflet, OSRM, Nominatim, CartoDB tiles | -| i18n | 15 languages (EN, DE, ES, FR, NL, IT, PT-BR, CS, PL, HU, RU, ZH, ZH-TW, AR, ID) | +| Layer | Technology | +|---|-------------------------------------------------------------------------------------------| +| Frontend | React 19, TypeScript, Zustand 5, Leaflet, Tailwind CSS 3.4, Vite 8 (Rolldown) | +| Backend | NestJS 11 (Express 4 adapter), TypeScript, better-sqlite3, Zod (@trek/shared) | +| Real-time | WebSocket (ws) | +| Database | SQLite with WAL mode | +| Auth | JWT (HS256), bcrypt, TOTP MFA, OIDC | +| Maps | Leaflet + react-leaflet (default, CartoDB tiles), MapLibre GL, Mapbox GL, OSRM, Nominatim | +| i18n | 23 languages, EN canonical (locale directories live in shared/src/i18n/) | + +Every translation key must exist in all 23 locales — the `i18n Key Parity` CI job fails on drift, so run `npm run i18n:parity:strict --workspace=shared` before pushing. Two directory names differ from the language they hold: Brazilian Portuguese is `br`, Greek is `gr`. diff --git a/wiki/Creating-a-Trip.md b/wiki/Creating-a-Trip.md index ad11667e0..a97d7a580 100644 --- a/wiki/Creating-a-Trip.md +++ b/wiki/Creating-a-Trip.md @@ -4,7 +4,7 @@ ## Opening the Dialog -Click the **New Trip** button in the dashboard toolbar (or the **Create First Trip** button on the empty state) to open the Create Trip dialog. +On desktop, click the floating **New Trip** button in the bottom-right corner of the dashboard, or the dashed **New Trip** tile at the end of the trip grid, to open the Create New Trip dialog. On mobile the floating button is replaced by the **+** in the centre of the bottom tab bar, and an empty dashboard also offers a **Create First Trip** button. You can also open it directly via a deep link: navigate to `/dashboard?create=1`. This is the URL used by system notices that prompt you to create a trip. @@ -28,11 +28,11 @@ You cannot set only one date and leave the other blank via normal interaction ### Currency -The trip's currency — its **accounting base**. Every expense in the Costs tab is converted into it, and every balance and settle-up suggestion is calculated in it. Defaults to **EUR**; 165 currencies are available. +The trip's currency — its **accounting base**. Every expense in the Costs tab is converted into it, and every balance and settle-up suggestion is calculated in it. The dialog pre-fills your own display currency (Settings → General → *Display currency*); if you left that on **Trip currency**, it falls back to **EUR**. An administrator can preset the instance-wide value under Admin → **User Defaults**, which new users inherit until they pick their own. 165 currencies are available. Pick the currency you will actually settle up in. It is not a cosmetic label, but it is not a one-way door either: you can change it later from the same dialog (with the `trip_edit` permission), and TREK re-bases the existing expenses so no money moves — see [Currencies → Changing the trip currency](Currencies#changing-the-trip-currency). -> This is **not** the same as the display currency in Settings → General, which only changes what *you* read. See [Currencies](Currencies). +> This is **not** the same as the display currency in Settings → General, which changes what *you* read on trips that already exist. See [Currencies](Currencies). ### Cover Image @@ -69,7 +69,7 @@ Add initial trip members from the members selector. On a **new** trip, selected ## Saving -Click **Create Trip**. The trip is saved and you are taken to the [Trip-Planner-Overview](Trip-Planner-Overview) for the new trip. +Click **Create New Trip**. The trip is saved, the dialog closes and a *"Trip created successfully!"* toast confirms it — you stay on the dashboard. The new trip is sorted into the trip list by its dates; click its card (or the Spotlight card, if it is the trip that earns that spot) to open the [Trip-Planner-Overview](Trip-Planner-Overview). ## Related Pages diff --git a/wiki/Currencies.md b/wiki/Currencies.md index b0d839254..7be7db623 100644 --- a/wiki/Currencies.md +++ b/wiki/Currencies.md @@ -12,7 +12,7 @@ The short version: **the trip currency is the accounting base, the expense curre ## Trip currency -Every trip has exactly one currency. It is set when you create the trip and can be changed later in the trip edit dialog (requires the `trip_edit` permission). It defaults to **EUR**. +Every trip has exactly one currency. It is set when you create the trip and can be changed later in the trip edit dialog (requires the `trip_edit` permission). The new-trip dialog pre-fills it with **your display currency**, falling back to **EUR** when you have left that on *Trip currency*. This is the one moment the display currency touches stored data: it seeds the field, and from then on the trip's currency is its own. This is the trip's **accounting base**. It is not a cosmetic label: @@ -59,7 +59,9 @@ It has two modes: | **Trip currency** (the default) | Each trip is shown in **its own** currency. A Tokyo trip reads in yen, a Moscow trip in roubles. | | A specific currency (e.g. `USD`) | **Every** trip is converted into that currency for you, whatever its own currency is. | -Leave it on **Trip currency** unless you specifically want everything in your home currency regardless of where you are going. An administrator can set an instance-wide default for new users (Admin → Default User Settings); choosing **Trip currency** yourself overrides it, because it is a deliberate choice rather than an absence of one. +Leave it on **Trip currency** unless you specifically want everything in your home currency regardless of where you are going. + +An administrator can set an instance-wide default (Admin → User Defaults), and it interacts with **Trip currency**: picking **Trip currency** stores an *empty* value, and an empty value on a defaultable setting counts as "not set", so the admin's currency is applied again (#1634). While an instance-wide default currency is configured, there is no way back to per-trip currencies from the Settings page — the admin has to clear it. > Display conversion uses **live** rates, not the frozen ones — it is a view, and a view should reflect today. This is why a converted total can shift slightly day to day while the underlying balances stay rock steady. diff --git a/wiki/Dashboard-Widgets.md b/wiki/Dashboard-Widgets.md index 0dc131112..1188b6255 100644 --- a/wiki/Dashboard-Widgets.md +++ b/wiki/Dashboard-Widgets.md @@ -1,18 +1,24 @@ # Dashboard Widgets -The My Trips dashboard includes two utility widgets: a currency converter and a timezone clock. +The My Trips dashboard has a widget sidebar holding a currency converter, a timezone clock and a list of upcoming reservations, plus a Collections shortcut when the [Collections](Collections) addon is enabled (it ships disabled). Installed plugins can contribute further widgets to the same sidebar. This page covers the currency converter and the timezone clock. ![Dashboard Widgets](assets/DashboardWidgets.png) ## Where they appear -On large screens (desktop/wide tablet), both widgets appear in a sticky right-hand sidebar of the [My-Trips-Dashboard](My-Trips-Dashboard). On mobile and narrow screens, the widgets are accessible via **Quick Actions** buttons on the dashboard — tapping the Currency or Timezone button opens a bottom sheet containing the full widget. +On large screens (desktop/wide tablet), the widgets appear in a sticky right-hand sidebar of the [My-Trips-Dashboard](My-Trips-Dashboard). At 1280px and below that column stops being sticky and reflows underneath the trip grid as a wrapping row of cards. On a phone (under 768px) the mobile dashboard takes over and renders each enabled widget inline as its own panel, interleaved with the trip list in an order you can rearrange under Settings → Appearance. There is no bottom sheet for the widgets on any screen size. -Each user configures their own widgets independently. Whether each widget is shown or hidden is saved to your account on the server (synced across devices). The selected currency pair and saved timezone list are stored in your browser's local storage and are device-specific. +Each user configures their own widgets independently. Whether each widget is shown or hidden is saved to your account on the server (synced across devices). The selected currency pair and the saved timezone list live on your account as well, so they follow you to every device you sign in on. ### Showing and hiding widgets -On desktop, click the **Settings (gear) icon** in the dashboard toolbar to reveal toggle switches for each widget. Turning a widget off removes it from the sidebar; the preference is saved to your account. +Widget visibility lives in **Settings → Appearance**, under **Dashboard widgets** — there is no gear icon on the dashboard itself. Desktop and mobile are configured independently: + +- **Desktop → Right sidebar** has a master switch for the whole column (turn it off and the dashboard centers) plus one switch each for Currency, Collections, Timezones and Upcoming reservations. +- **Desktop → Below the hero** switches the Atlas / countries, Trips total, Days traveled and Distance flown tiles. +- **Mobile → Bottom of page** switches Currency, Collections, Timezones and Upcoming reservations; **Mobile → Below the hero** switches Trips total and Days traveled. + +Turning a widget off removes it from the dashboard; the preference is saved to your account. --- @@ -31,7 +37,7 @@ You can also click the swap arrow to reverse source and target. **Exchange rates** are fetched from [Frankfurter](https://frankfurter.dev) using the `https://api.frankfurter.dev/v2/rates?base={from}` endpoint. Rates are refreshed each time you change a currency or click the refresh icon. -**Supported currencies:** 162 currencies are available in the selector, including all major fiat currencies (USD, EUR, GBP, JPY, etc.) and many minor ones. +**Supported currencies:** 165 currencies are available in the selector — the full set Frankfurter v2 supports, including all major fiat currencies (USD, EUR, GBP, JPY, etc.) and many minor ones. --- @@ -41,16 +47,12 @@ The timezone clock displays live clocks for multiple time zones simultaneously. **How to use:** -- Your local time is always shown at the top. -- Below it, any zones you have added are listed with their current time and offset relative to your local zone. -- Click **+** to add a zone. You can pick from 18 preset city zones, or enter any IANA timezone identifier (e.g. `America/Denver`) with an optional custom label (if omitted, the city portion of the identifier is used as the label). +- Until you change the list, the widget starts with your local zone plus London and Tokyo. Your local zone is an ordinary row and can be removed like any other. +- Each row shows the zone's current time and its short timezone name or UTC offset (for example `GMT+9`), not an offset relative to your own zone. +- Click **+** to add a zone. A searchable dropdown lists every IANA timezone your browser knows (e.g. `America/Denver`); type to filter and pick one. The row is labelled with the city part of the identifier — there is no custom label. - Hover over a zone row and click **×** to remove it. -**Preset zones (18):** - -New York, London, Berlin, Paris, Dubai, Mumbai, Bangkok, Tokyo, Sydney, Los Angeles, Chicago, São Paulo, Istanbul, Singapore, Hong Kong, Seoul, Moscow, Cairo. - -Clocks update every 10 seconds. The 12-hour or 24-hour format follows your display settings (see [Display-Settings](Display-Settings)). +Clocks refresh every 30 seconds and are always shown in 24-hour time — the 12-hour display setting does not apply to this widget. --- diff --git a/wiki/Day-Plans-and-Notes.md b/wiki/Day-Plans-and-Notes.md index daa4428b4..4ecdf9a51 100644 --- a/wiki/Day-Plans-and-Notes.md +++ b/wiki/Day-Plans-and-Notes.md @@ -6,7 +6,7 @@ The Day Plan sidebar lets you organize places into days, add free-form notes, an ## The Day Plan sidebar -The Day Plan sidebar is the left panel in the trip planner. Each trip day is shown as a collapsible section. Expanded or collapsed state is saved per trip in `sessionStorage` (key: `day-expanded-{tripId}`), so your layout is preserved across page reloads in the same browser session. +The Day Plan sidebar is the left panel in the trip planner. Each trip day is shown as a collapsible section. Expanded or collapsed state is saved per trip in `localStorage` (key: `day-expanded-{tripId}`), so your layout is preserved across page reloads and between browser sessions on that device. ## Day timeline @@ -32,7 +32,7 @@ Items are sorted by their time or position index. You can also reorder places within a day, or move them to a different day, by dragging and dropping inside the sidebar. -To remove a place from a day, click the **X** button next to the place in the day timeline. +To remove a place from a day, right-click the entry in the day timeline and choose **Remove from day**, or select the place and use the **Remove from Day** button in the place detail panel. On mobile, switch the plan screen to **Plan** and tap the **X** next to the place. Deleting the place itself, from the same right-click menu, removes it from every day. ![Removing a place by button](assets/DayItineraryRemovePlaceByButton.gif) @@ -44,17 +44,19 @@ A reservation that spans multiple days appears in each relevant day with a phase |---|---|---|---| | Flight | Departure | In transit | Arrival | | Car | Pickup | Active | Return | +| Parking | Drop-off | Not shown | Pickup | | Other | Start | Ongoing | End | -Car rentals that are in the "Active" (middle) phase are shown in the day header rather than the timeline. +Car rentals that are in the "Active" (middle) phase are shown in the day header rather than the timeline. A multi-day parking booking is dropped from the days in between entirely — it appears on its drop-off day and its pickup day only, with no day header badge. ## Day notes -Click the note **+** button in any day section to add a note. Notes have three fields: +Click the note **+** button in any day section to add a note. Notes have four fields: - **Title** (required) — the main note text shown in the timeline - **Subtitle / detail** (optional) — a free-form text field (Markdown supported) displayed beneath the title -- **Icon** — choose from 20 icons: FileText, Info, Clock, MapPin, Navigation, Train, Plane, Bus, Car, Ship, Coffee, Ticket, Star, Heart, Camera, Flag, Lightbulb, AlertTriangle, ShoppingBag, Bookmark +- **Icon** — choose from 32 icons: FileText, Info, Clock, MapPin, Navigation, Train, Plane, Bus, Car, Ship, Coffee, Ticket, Star, Heart, Camera, Flag, Lightbulb, AlertTriangle, ShoppingBag, Bookmark, Utensils, Wine, ParkingSquare, Fuel, Footprints, Mountain, Waves, Sun, Umbrella, Music, Landmark, Gift +- **Colour** (optional) — tints the note card in the day timeline; the dialog shows a live preview of the card the note will become Notes interleave with places and transports in the day timeline and are ordered by their `sort_order`. Use the **↑ / ↓** chevron buttons on a note to reposition it within the merged timeline. Notes can also be repositioned by dragging. @@ -72,11 +74,18 @@ The panel can be collapsed to a slim header bar or closed entirely with the **X* At the top of the Day Plan sidebar: -- **Export PDF** — downloads a PDF of the full trip plan. See [PDF-Export](PDF-Export). -- **ICS** — exports the trip as a calendar file (.ics) for import into calendar apps. +- **Export** — opens the export dialog, which holds every format in three groups: **Document** (a PDF of the full trip plan, see [PDF-Export](PDF-Export)), **Calendar** (a `.ics` file for import into calendar apps, plus a subscribable calendar feed for members who can manage share links) and **Maps & GPS** (GPX — whole trip, places only, or days as routes). - **Expand / Collapse all** — toggles all day sections open or closed at once. - **Undo** — reverses the last drag, reorder, or assign action. +- **Reorder days** — reorder the days of the trip or insert a new one. A day's places, notes and bookings move with it. Shown to members who can edit days. +- **Show all booking routes** — draws the connection of every routable booking on the map at once. Shown once the trip has at least one routable booking. -Route controls (optimize order, open in Google Maps) appear inside each expanded day section after the place list. +Route controls appear at the bottom of a day section, after the place list, and only for the day you have selected — click a day header to select it. The row only appears on a day that can actually be routed: two or more places on the day, a single located place that accommodation optimization can bookend with a hotel, or a transfer day where you check out of one hotel and into another. On a phone the same controls sit in the **Daily Overview** sheet instead, opened from the pill above the plan timeline, and appear there when the day has two located places, or one located place plus a hotel to start from. + +- **Route** — draws that day's route on the map. +- **Open in Google Maps** — hands the day's stops to Google Maps as a route, in planned order. +- **Open in CoMaps** — the same day handed to CoMaps for offline navigation, carrying the day's travel mode. +- **Optimize** — reorders the day's places into the shortest route. See [Route-Optimization](Route-Optimization). +- **Travel mode** — Driving or Walking for that day, plus any travel mode a plugin adds. **See also:** [Places-and-Search](Places-and-Search) · [Map-Features](Map-Features) · [Route-Optimization](Route-Optimization) · [Weather-Forecasts](Weather-Forecasts) · [Reservations-and-Bookings](Reservations-and-Bookings) \ No newline at end of file diff --git a/wiki/Demo-Mode.md b/wiki/Demo-Mode.md index 4aab92ba4..1c0060b96 100644 --- a/wiki/Demo-Mode.md +++ b/wiki/Demo-Mode.md @@ -38,15 +38,19 @@ The admin account is unaffected and retains full access. TREK schedules an automatic hourly reset of the demo database. At each reset: 1. The current `travel.db` is replaced with the saved baseline (`travel-baseline.db`). -2. The admin account's credentials (`password_hash`, API keys, avatar) are re-applied on top of the restored baseline, so admin API keys and password changes survive the reset. +2. The admin account's credentials (`password_hash`, API keys, avatar) are re-applied on top of the restored baseline, so admin API keys and password changes survive the reset — but only when `DEMO_ADMIN_EMAIL` is set explicitly. If no baseline has been saved yet, the reset is skipped and a message is logged. +The seeder defaults the admin address to `admin@trek.app` while the reset defaults it to `admin@nomad.app`, a legacy quirk that is pinned deliberately. With `DEMO_ADMIN_EMAIL` unset the reset looks up an address that does not exist, finds no admin row, and skips the carry-over entirely — a password change, API key or avatar set after the baseline was saved is lost on every hourly reset. Set `DEMO_ADMIN_EMAIL` (to `admin@trek.app`, for instance) if you want them to survive. Unlike the seed-time variables above, the reset reads it on every run, so setting it on an already-seeded instance and restarting is enough. + +The instance-wide Maps and Unsplash keys stored in `app_settings` are carried across either way, so map and photo search keep working after a reset. + ## Saving a baseline The baseline is the snapshot the hourly reset restores to. The admin can update it at any time: -**Endpoint:** `POST /admin/save-demo-baseline` +**Endpoint:** `POST /api/admin/save-demo-baseline` This is available in the admin panel. The baseline captures the current state of the database — including trip data, settings, and encrypted API keys — so demo features (maps, photos, weather) continue to work after each reset. diff --git a/wiki/Development-environment.md b/wiki/Development-environment.md index 86426c1bb..9b06e7c17 100644 --- a/wiki/Development-environment.md +++ b/wiki/Development-environment.md @@ -71,12 +71,18 @@ Branch naming conventions: ## 5. Install Dependencies -The repo is an npm workspace monorepo. One command at the root installs everything: +The repo is an npm workspace monorepo with three workspaces — `shared`, `server`, and `client`. One command at the root installs all three: ```bash npm ci ``` +`plugin-sdk/` is **not** a root workspace: it has its own lockfile and is published to npm independently, so a root `npm ci` never touches it. If you are working on the SDK, install and run its commands from that directory: + +```bash +cd plugin-sdk && npm ci +``` + --- ## 6. Optional: KItinerary (Booking Import) @@ -94,17 +100,17 @@ sudo apt-get install -y libkitinerary-bin Add these to your local `.env` (or export them before starting the server): ```bash -# Required: path to the extractor binary -KITINERARY_EXTRACTOR_PATH=/usr/local/bin/kitinerary-extractor - # Prevent Qt from probing for a display in headless/server environments QT_QPA_PLATFORM=offscreen # KDE cache directory (avoids writing to $HOME) XDG_CACHE_HOME=/tmp/kf6-cache + +# Optional: only needed when the binary is not found on its own +# KITINERARY_EXTRACTOR_PATH=/usr/lib/x86_64-linux-gnu/libexec/kf6/kitinerary-extractor ``` -You can override `KITINERARY_EXTRACTOR_PATH` if you installed the binary to a different location. +`KITINERARY_EXTRACTOR_PATH` is optional. Left unset, the server looks for the Debian/Ubuntu location `/usr/lib//libexec/kf6/kitinerary-extractor` and then for `kitinerary-extractor` on `PATH` — one of which is what `libkitinerary-bin` gives you. Set it only if the binary ended up somewhere neither lookup reaches, and make sure the path exists: a value that does not resolve logs a warning and stops the lookup instead of falling back, which turns the import feature off. The Docker image sets it to `/usr/local/bin/kitinerary-extractor`, a symlink created at image build; that path does not exist on a plain apt install. --- @@ -119,7 +125,7 @@ These commands run across all workspaces at once and are the recommended way to | `npm run dev` | Build shared, then start shared (watch), server, and client together via `concurrently` | | `npm run build` | Build shared → server → client in order | | `npm test` | Run tests in shared, server, and client | -| `npm run test:cov` | Run coverage for server and client | +| `npm run test:cov` | Run coverage for shared, server, client and plugin-sdk | | `npm run test:e2e` | Run end-to-end tests (server) | | `npm run lint` | Lint shared, server, and client | | `npm run format` | Format shared, server, and client | @@ -133,7 +139,7 @@ The `@trek/shared` package is the single source of truth for code shared between | Command | Description | |-----------------------------|--------------------------------------| -| `npm run build` | Compile shared package (tsup) | +| `npm run build` | Compile shared package (tsdown) | | `npm run build:watch` | Compile in watch mode | | `npm test` | Run tests | | `npm run test:watch` | Run tests in watch mode | @@ -146,21 +152,26 @@ The `@trek/shared` package is the single source of truth for code shared between ### Server (`/server`) -| Command | Description | -|----------------------------|------------------------------------------| -| `npm start` | Start the server (production) | -| `npm run dev` | Start the server in watch mode | -| `npm run build` | Compile server | -| `npm run typecheck` | Type-check without emitting | -| `npm test` | Run all tests | -| `npm run test:unit` | Run unit tests only | -| `npm run test:integration` | Run integration tests | -| `npm run test:ws` | Run WebSocket tests | -| `npm run test:e2e` | Run end-to-end tests | -| `npm run test:watch` | Run tests in watch mode | -| `npm run test:coverage` | Run tests with coverage report | -| `npm run lint` | Lint source | -| `npm run format` | Format source | +> **Tip:** `tests/` sits outside the build `tsconfig.json`, so `npm run typecheck` skips it — `npm run typecheck:tests` is the only step that catches a broken test call site. CI runs both. + +| Command | Description | +|------------------------------|------------------------------------------| +| `npm start` | Start the server (production) | +| `npm run dev` | Start the server in watch mode | +| `npm run build` | Compile server | +| `npm run typecheck` | Type-check without emitting | +| `npm run typecheck:tests` | Type-check `tests/` too (CI gate) | +| `npm test` | Run all tests | +| `npm run test:unit` | Run unit tests only | +| `npm run test:integration` | Run integration tests | +| `npm run test:ws` | Run WebSocket tests | +| `npm run test:e2e` | Run end-to-end tests | +| `npm run test:watch` | Run tests in watch mode | +| `npm run test:coverage` | Run tests with coverage report | +| `npm run lint` | Lint source | +| `npm run lint:check` | Lint everything, no `--fix` (CI gate) | +| `npm run check:plugin-facts` | Verify generated plugin facts (CI gate) | +| `npm run format` | Format source | ### Client (`/client`) @@ -169,12 +180,16 @@ The `@trek/shared` package is the single source of truth for code shared between | `npm run dev` | Start the Vite dev server | | `npm run build` | Build for production (runs icon generation first) | | `npm run preview` | Preview the production build locally | +| `npm run typecheck` | Type-check without emitting (CI gate) | | `npm test` | Run all tests | | `npm run test:unit` | Run unit tests only | | `npm run test:integration` | Run integration tests | | `npm run test:watch` | Run tests in watch mode | | `npm run test:coverage` | Run tests with coverage report | | `npm run lint` | Lint source | +| `npm run lint:check` | Same command as `npm run lint` — the name CI uses | +| `npm run lint:pages` | Enforce the Page pattern (CI gate) | +| `npm run theme:lint` | Flag styling that bypasses the appearance tokens | | `npm run format` | Format source | --- @@ -199,5 +214,5 @@ Then open a Pull Request from your fork to `liketrek/TREK` targeting the `dev` b ## Tips - Always branch off from an up-to-date `dev` — run `git fetch upstream && git rebase upstream/dev` before starting new work. -- Run tests before pushing: `npm test` at the repo root runs all workspaces. +- Run tests before pushing: `npm test` at the repo root runs all workspaces. That alone is not the full CI gate — also run `npm run typecheck && npm run typecheck:tests && npm run lint:check && npm run check:plugin-facts` in `server/`, `npm run typecheck && npm run lint:check && npm run lint:pages` in `client/`, and `npm run i18n:parity:strict --workspace=shared` at the root if you touched translations. - Follow the commit message conventions described in the [[Contributing]] guidelines. diff --git a/wiki/Display-Settings.md b/wiki/Display-Settings.md index e6ccd931c..656a0c1bb 100644 --- a/wiki/Display-Settings.md +++ b/wiki/Display-Settings.md @@ -8,7 +8,7 @@ The General tab (Settings → General) controls your locale preferences and a fe Open the user menu in the top navigation bar, select **Settings**, and stay on the **General** tab — it is the tab the page opens on. -The tab is split into three sections: **Startup** (where opening TREK lands), **Language & region** (currency, language, temperature, distance, time format) and **Travel & map** (booking route labels, POI pills, blur booking codes). +The tab is split into three sections: **Startup** (where opening TREK lands), **Language & region** (currency, language, temperature, distance, time format) and **Travel & map** (booking route labels, always show booking routes, explore places on the map, blur booking codes, optimize route from accommodation). > Color mode (Light / Dark / Auto) is **not** here — it lives on the **Appearance** tab. See [Appearance-Settings](Appearance-Settings). @@ -44,7 +44,7 @@ The parameter takes the planner's internal tab ids, which are historic German na | Tab | Id | |-----|-----| | Plan | `plan` | -| Transport | `transports` | +| Transports | `transports` | | Bookings | `buchungen` | | Lists | `listen` | | Costs | `finanzplan` | @@ -116,6 +116,12 @@ This only sets the *default* for a trip you haven't touched before. If you've al When enabled, confirmation codes and reference numbers are blurred until you hover or tap. Set to **On** or **Off**. +## Optimize route from accommodation + +When **On** (the default), the **Optimize** button in the day's route tools anchors the route on that day's accommodation instead of only reordering the places among themselves: an ordinary day becomes a loop out from the hotel and back to it, a transfer day a run from the hotel you check out of to the one you check into that evening. Set to **On** or **Off**. + +A day without an accommodation, or one whose hotel has no coordinates, is optimized from its first place as before. Locked and timed places keep their slots either way. See [Route-Optimization](Route-Optimization). + ## See also - [Currencies](Currencies) diff --git a/wiki/Documents-and-Files.md b/wiki/Documents-and-Files.md index b3f44786b..712200686 100644 --- a/wiki/Documents-and-Files.md +++ b/wiki/Documents-and-Files.md @@ -17,14 +17,14 @@ Open the **Files** tab inside the trip planner, or navigate directly to `/trips/ Drag and drop files onto the upload area, click it to open the file picker, or paste an image directly into the Files panel. - **Maximum file size:** 50 MB per file. -- **Blocked file types:** `.svg`, `.html`, `.htm`, `.xml` — these are always rejected. -- **Default allowed types:** jpg, jpeg, png, gif, webp, heic, pdf, doc, docx, xls, xlsx, txt, csv, pkpass. An admin can customize the allowed list in [Admin-Addons](Admin-Addons). +- **Blocked file types:** renderable documents (`.svg`, `.svgz`, `.html`, `.htm`, `.shtml`, `.shtm`, `.xml`, `.xhtml`, `.xht`), scripts (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.php`, `.py`, `.rb`, `.pl`) and executables (`.exe`, `.bat`, `.sh`, `.cmd`, `.msi`, `.dll`, `.com`, `.vbs`, `.ps1`, `.app`) — these are always rejected, as is any file whose MIME type contains `svg`. +- **Default allowed types:** jpg, jpeg, png, gif, webp, heic, pdf, doc, docx, xls, xlsx, txt, csv, pkpass, pkpasses, md, markdown. An admin can customize the allowed list under **Admin → Settings → Allowed File Types** — comma-separated extensions, or `*` to allow everything except the blocked types above. Requires the `file_upload` permission. ## Browsing and filtering -The toolbar provides filter tabs: **All**, **PDF**, **Images**, **Docs**, and conditionally **Starred** (only shown when at least one file is starred) and **Collab** (only shown when files exist from collaborative notes). Each tab shows a count badge. +The toolbar provides filter tabs, in this order: **All**, a star icon for **Starred** (only shown when at least one file is starred), **PDFs**, **Images**, **Documents**, and **Collab Notes** (only shown when files exist from collaborative notes). Each tab shows a count badge. ## Previewing files diff --git a/wiki/Encryption-Key-Rotation.md b/wiki/Encryption-Key-Rotation.md index b0d558cf9..419012bfc 100644 --- a/wiki/Encryption-Key-Rotation.md +++ b/wiki/Encryption-Key-Rotation.md @@ -9,14 +9,17 @@ TREK encrypts sensitive settings at rest using AES-256-GCM. The following values - Mapbox access token (per user) - OpenWeather API key (per user) - Immich API key (per user) +- AirTrail API key (per user) - Synology Photos password, session ID, and device ID (per user) -- Per-user webhook URL and ntfy notification token (in `settings` table) +- Per-user webhook URL, ntfy notification token, and LLM provider API key (in `settings` table) - OIDC client secret (global, in `app_settings`) - SMTP password (global, in `app_settings`) - Admin webhook URL and admin ntfy token (global, in `app_settings`) - MFA (TOTP) secrets for all users - Photo passphrases for Synology shared-link photos (in `trek_photos`) +- Passphrases for shared trip album links (in `trip_album_links`) - S3 storage backend secret access keys (in `app_settings['storage.backends']`; see [Admin-Storage](Admin-Storage)) +- Plugin OAuth access and refresh tokens, plus every plugin settings field the plugin's manifest marks `secret` (instance scope in `plugins.config`, user scope in `plugin_user_config.config`) The encryption derives a key from `ENCRYPTION_KEY` using SHA-256 (with a domain suffix per secret type), so the raw `ENCRYPTION_KEY` value is never stored in the database. @@ -35,7 +38,9 @@ All encrypted settings (API keys, SMTP password, OIDC secret, MFA secrets, notif ## Backing up the key -Your backup ZIP does **not** include the encryption key. Store the key separately from your backups — for example, in a password manager or a secrets manager. See [Backups](Backups). +Unless you set `ENCRYPTION_KEY` in the environment, your backup ZIP **does** include the encryption key: `./data/.encryption_key` is bundled into every archive, manual and automatic, as a root-level `.encryption_key` entry, so a restore onto a different install can still decrypt the stored secrets. That makes the ZIP exactly as sensitive as the key itself — store and transfer it accordingly. + +On installs that supply the key through the environment the file is not the source of truth, so no backup carries it. Keep it somewhere separate — for example, in a password manager or a secrets manager. See [Backups](Backups). To find your current key: check the `ENCRYPTION_KEY` environment variable or read `./data/.encryption_key`. @@ -46,7 +51,7 @@ Use `scripts/migrate-encryption.ts` to re-encrypt all stored secrets without dow **Docker:** ```bash -docker exec -it trek node --import tsx scripts/migrate-encryption.ts +docker exec -it -w /app/server trek node --import tsx scripts/migrate-encryption.ts ``` **Host (run from the `server/` directory):** @@ -63,8 +68,10 @@ The script: 4. Re-encrypts all stored secrets across all tables: - `app_settings`: `oidc_client_secret`, `smtp_pass`, `admin_webhook_url`, `admin_ntfy_token`, `maps_api_key`, `unsplash_api_key` - `app_settings['storage.backends']`: the `secretAccessKey` of every S3 storage backend - - `users` (per user): `maps_api_key`, `unsplash_api_key`, `openweather_api_key`, `immich_api_key`, `synology_password`, `synology_sid`, `synology_did`, `mfa_secret` - - `settings` (per user): `webhook_url`, `ntfy_token`, `mapbox_access_token` + - `users` (per user): `maps_api_key`, `unsplash_api_key`, `openweather_api_key`, `immich_api_key`, `synology_password`, `synology_sid`, `synology_did`, `airtrail_api_key`, `mfa_secret` + - `settings` (per user): `webhook_url`, `ntfy_token`, `mapbox_access_token`, `llm_api_key` + - `plugin_oauth_tokens`: `access_token`, `refresh_token` (per plugin and user) + - `plugins.config` and `plugin_user_config.config`: every settings field a plugin's manifest marks `secret`, resolved per plugin and scope from `plugin_settings_fields` - `trip_album_links`: `passphrase` - `trek_photos`: `passphrase` 5. Reports counts of migrated, already-migrated, skipped (empty), and errored values. diff --git a/wiki/Environment-Variables.md b/wiki/Environment-Variables.md index 479069fb5..7c1630069 100644 --- a/wiki/Environment-Variables.md +++ b/wiki/Environment-Variables.md @@ -6,12 +6,39 @@ Complete reference for all environment variables TREK reads. - **Docker Compose** — use the `environment:` block or a `.env` file alongside `docker-compose.yml` - **Docker run** — pass each variable with `-e VARIABLE=value` -- **Helm** — use `env:` for plain values and `secretEnv:` for sensitive values in `values.yaml` +- **Helm** — use `env:` for plain values and `secretEnv:` for sensitive values in `values.yaml`. The chart only + passes through the keys it declares (26 in `templates/configmap.yaml`, 5 in `templates/secret.yaml`), so a variable + that is not one of them is dropped silently — patch it onto the Deployment or add it to the chart - **Unraid** — set in the container template editor - **Proxmox Community Script** — set in `/opt/trek/server/.env` --- +## Startup Validation + +TREK checks almost all of this surface once at boot. A variable that is **unset or blank** falls back to its documented +default; a variable that is **present but malformed** aborts startup, with an aggregated report naming every +offending value: + +``` +Invalid environment configuration: + - PORT="not-a-port": must be a port number (1-65535) + - SESSION_DURATION="bogus": must be a duration like "1h", "7d" or "30d" +``` + +In Docker this crash-loops the container until the value is corrected or removed. Boolean switches accept +`true`/`false`, `1`/`0`, `on`/`off`, `yes`/`no` in any casing — anything else counts as malformed. Variables TREK +does not know are passed through untouched. + +`TREK_DB_JOURNAL_MODE` and `TREK_DB_SYNCHRONOUS` are exceptions: they log a warning and fall back instead of +aborting, because `reset-admin.js` — the way back into a locked-out instance — reads the same two variables and has +to keep working. The three plugin caps `TREK_PLUGIN_AI_PER_DAY`, `TREK_PLUGIN_NOTIFY_PER_DAY` and +`TREK_PLUGIN_AUDIT_MAX_ROWS` are read by the plugin host rather than by the boot schema, so a malformed value there +falls back to the default without a warning. `NODE_ENV` and `TZ` are not validated at all, so a non-standard value +like `NODE_ENV=staging` still boots. + +--- + ## Core | Variable | Description | Default | @@ -23,8 +50,8 @@ Complete reference for all environment variables TREK reads. | `TZ` | Timezone for logs, reminders, and cron jobs (e.g. `Europe/Berlin`) | `UTC` | | `LOG_LEVEL` | `info` = concise user actions; `debug` = verbose details | `info` | | `DEFAULT_LANGUAGE` | Default language on the login page — see supported codes below | `en` | -| `SESSION_DURATION` | How long a login session stays valid before re-login is required. Used when **"Remember me" is unchecked** on the login form (the default): applies to the `trek_session` JWT `exp` claim, and the cookie is issued as a **browser-session cookie** (no `maxAge`, cleared when the browser closes). Accepts `ms`-style strings: `1h`, `12h`, `7d`, `30d`, `90d`. Invalid values warn at startup and fall back to the default. Does not affect the short-lived MFA challenge token or MCP OAuth tokens (those keep their own TTL). | `24h` | -| `SESSION_DURATION_REMEMBER` | Session length used when the user **ticks "Remember me"** on login: a longer-lived JWT `exp` claim plus a **persistent** `trek_session` cookie whose `maxAge` matches, so the session survives browser restarts. Same `ms`-style format and startup-fallback behaviour as `SESSION_DURATION`. | `30d` | +| `SESSION_DURATION` | How long a login session stays valid before re-login is required. Used when **"Remember me" is unchecked** on the login form (the default): applies to the `trek_session` JWT `exp` claim, and the cookie is issued as a **browser-session cookie** (no `maxAge`, cleared when the browser closes). Accepts `ms`-style strings: `1h`, `12h`, `7d`, `30d`, `90d`. Invalid values abort startup — see Startup Validation above. Does not affect the short-lived MFA challenge token or MCP OAuth tokens (those keep their own TTL). | `24h` | +| `SESSION_DURATION_REMEMBER` | Session length used when the user **ticks "Remember me"** on login: a longer-lived JWT `exp` claim plus a **persistent** `trek_session` cookie whose `maxAge` matches, so the session survives browser restarts. Same `ms`-style format and the same startup validation as `SESSION_DURATION`. | `30d` | | `ALLOWED_ORIGINS` | Comma-separated origins for CORS and email notification links | same-origin | | `ALLOW_INTERNAL_NETWORK` | Allow outbound requests to private/RFC-1918 IPs. Set `true` if Immich or other integrated services are on your local network. Loopback (`127.x`) and link-local (`169.254.x`) addresses remain blocked regardless. | `false` | | `APP_URL` | Public base URL (e.g. `https://trek.example.com`). Required when OIDC is enabled — must match the redirect URI registered with your IdP. Also used as the base URL for email notification links and subscribable calendar feed URLs (the `webcal://`/`https://` links the Subscribe dialog hands to Google/Apple/Outlook). | — | @@ -65,7 +92,7 @@ Setting `ENCRYPTION_KEY` explicitly is recommended so you can back it up indepen ### `DEFAULT_LANGUAGE` — Supported Codes -You can set `DEFAULT_LANGUAGE` to any of the 22 languages TREK ships. The currently supported codes are: +You can set `DEFAULT_LANGUAGE` to any of the 23 languages TREK ships. The currently supported codes are: | Code | Language | |---------|--------------------| @@ -91,8 +118,10 @@ You can set `DEFAULT_LANGUAGE` to any of the 22 languages TREK ships. The curren | `gr` | Ελληνικά | | `sv` | Svenska | | `vi` | Tiếng Việt | +| `ca` | Català | -If you set a code that isn't supported, TREK falls back to English (`en`). This list grows as new +If you set a code that isn't on this list, TREK refuses to start and prints +`DEFAULT_LANGUAGE="…": must be one of: …`. Leave the variable unset to use English (`en`). This list grows as new translations are added to TREK. --- @@ -130,7 +159,7 @@ full explanation. | `FORCE_HTTPS` | When `true`: 301-redirects HTTP→HTTPS, sends HSTS (`max-age=31536000`), adds CSP `upgrade-insecure-requests`, forces cookie `secure` flag. Only useful behind a TLS proxy. Requires `TRUST_PROXY`. | `false` | | `HSTS_INCLUDE_SUBDOMAINS` | When `true`: adds the `includeSubDomains` directive to the HSTS header, extending HTTPS enforcement to all subdomains. Only effective when HSTS is active (`FORCE_HTTPS=true` or `NODE_ENV=production`). Leave `false` if you run other services on sibling subdomains over plain HTTP. | `false` | | `TRUST_PROXY` | Number of trusted proxy hops. Tells Express how far into `X-Forwarded-For` to look for the real client IP, and where to read `X-Forwarded-Proto`. Count your hops: with two proxies in front of TREK and `TRUST_PROXY=1`, the IP on every audit row is the inner proxy's. `0` trusts nothing and always uses the socket address. Required for `FORCE_HTTPS` to detect the forwarded protocol. | `1` (production) | -| `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived as `true` when `NODE_ENV=production` or `FORCE_HTTPS=true`. Set to `false` only as an escape hatch for LAN testing without TLS — not recommended in production. | auto | +| `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived as `true` when `NODE_ENV=production`, when `FORCE_HTTPS=true`, or when the request itself arrived over TLS on the outermost hop (`X-Forwarded-Proto: https` with `TRUST_PROXY` set). Set to `false` only as an escape hatch for LAN testing without TLS — not recommended in production. | auto | > **Warning:** `FORCE_HTTPS=true` without `TRUST_PROXY` set causes a redirect loop. @@ -147,7 +176,7 @@ For setup instructions, see [OIDC-SSO](OIDC-SSO). | `OIDC_CLIENT_SECRET` | OIDC client secret | — | | `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` | | `OIDC_ONLY` | Force SSO-only mode: disables password login and registration, overrides Admin > Settings toggles, cannot be changed at runtime. First SSO login becomes admin on a fresh instance. | `false` | -| `OIDC_ADMIN_CLAIM` | OIDC claim used to identify admin users (e.g. `groups`) | — | +| `OIDC_ADMIN_CLAIM` | OIDC claim inspected for the admin role. Only takes effect once `OIDC_ADMIN_VALUE` is set. | `groups` | | `OIDC_ADMIN_VALUE` | Value of the OIDC claim that grants admin role (e.g. `app-trek-admins`) | — | | `OIDC_SCOPE` | Space-separated OIDC scopes to request. **Fully replaces** the default — always include `openid email profile` plus any extra scopes (e.g. add `groups` when using `OIDC_ADMIN_CLAIM`) | `openid email profile` | | `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Required for providers with a non-standard path (e.g. Authentik) | — | @@ -207,7 +236,7 @@ For setup instructions, see [MCP-Overview](MCP-Overview). | Variable | Description | Default | |----------------------------|---------------------------------------------------------------------------------------------------------|---------| -| `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `300` | +| `MCP_RATE_LIMIT` | Max MCP API requests per minute, counted **per user and per OAuth client** — a user connected from two MCP clients gets this budget twice. Requests made with a static `trek_` token or a session JWT carry no client and share one further bucket per user. | `300` | | `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user. At the cap the user's least-recently-active session is closed to make room — requests are not rejected. | `20` | | `MCP_SESSION_TTL` | Session idle timeout in seconds (max 86400) | `3600` | | `MCP_SSE_KEEPALIVE` | SSE keep-alive ping interval in seconds — keeps the stream alive through reverse proxies. `0` disables the pings; an open stream still refreshes the session's idle timeout. | `25` | @@ -232,11 +261,13 @@ Request bodies validated with Zod are documented automatically from the same sch |-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| | `KITINERARY_EXTRACTOR_PATH` | Full path to the `kitinerary-extractor` binary. When unset, TREK searches `/usr/lib/*/libexec/kf6/kitinerary-extractor` and then `PATH`. Set this if you install the binary to a non-standard location. | auto-detected | -The official TREK Docker image bundles the binary automatically: on amd64 it downloads the static release from -`https://cdn.kde.org/ci-builds/pim/kitinerary/`; on arm64 it installs `libkitinerary-bin` via apt (Debian trixie). When -running TREK from source, install `libkitinerary-bin` (Debian trixie / Ubuntu 25.04+) or download the static binary -directly and place it anywhere on `PATH`. The `GET /api/health/features` endpoint returns `{ "bookingImport": true }` -when the binary is found, and the Import button in the Reservations panel is hidden when it is not. +The official TREK Docker image bundles the binary automatically: on both amd64 and arm64 it installs +`libkitinerary-bin` via apt (Debian trixie) and symlinks it to `/usr/local/bin/kitinerary-extractor`, which the image +also pins via `KITINERARY_EXTRACTOR_PATH`. When running TREK from source, install `libkitinerary-bin` (Debian trixie / +Ubuntu 25.04+) or download the static binary directly and place it anywhere on `PATH`. The +`GET /api/health/features` endpoint returns `{ "bookingImport": true }` when the binary is found. The Import button in +the Reservations panel is hidden only when **neither** extractor is available — an instance running the AI Parsing +addon still offers it with `bookingImport: false`. Booking import can also fall back to an AI model for documents KDE Itinerary can't read. That feature (the **AI Parsing** addon) is configured entirely in the UI and needs no environment variables — see [AI-Booking-Import](AI-Booking-Import). @@ -279,12 +310,13 @@ Storage backends and category assignment are configured in [[Admin: Storage|Admin-Storage]] (or a seed-once `storage-config.json`), not through environment variables. `TREK_PLACE_PHOTO_DIR` below is unaffected. -| Variable | Description | Default | -|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------| -| `TREK_PLACE_PHOTO_DIR` | Directory where cached Google place photos are stored. Created recursively on boot. Set this to point photo storage at a dedicated mounted volume. | `uploads/photos/google` | -| `BACKUP_UPLOAD_LIMIT_MB` | Maximum **compressed** size (in MB) of a restore-backup archive that may be uploaded. Raise it if your backups (which include the `uploads/` directory) exceed the default. Non-positive or invalid values log a warning and fall back to the default. | `500` | -| `TREK_DB_JOURNAL_MODE` | SQLite [journal mode](https://sqlite.org/pragma.html#pragma_journal_mode): `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL` or `OFF`. Set `DELETE` when the data directory lives on network storage — see below. Values SQLite doesn't know log a warning and fall back. | `WAL` | -| `TREK_DB_SYNCHRONOUS` | SQLite [synchronous](https://sqlite.org/pragma.html#pragma_synchronous) level: `OFF`, `NORMAL`, `FULL` or `EXTRA`. The default follows the journal mode — `NORMAL` under WAL (what SQLite itself uses there), `FULL` otherwise, because a rollback journal at `NORMAL` can lose committed transactions on a power cut. | `NORMAL` / `FULL` | +| Variable | Description | Default | +|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------| +| `TREK_PLACE_PHOTO_DIR` | Directory where cached Google place photos are stored. Created recursively on boot. Set this to point photo storage at a dedicated mounted volume. | `uploads/photos/google` | +| `BACKUP_UPLOAD_LIMIT_MB` | Maximum **compressed** size (in MB) of a restore-backup archive that may be uploaded. Raise it if your backups (which include the `uploads/` directory) exceed the default. Non-positive or invalid values abort startup. | `500` | +| `BACKUP_MAX_DECOMPRESSED_MB` | Maximum **decompressed** size (in MB) of a restore-backup archive — the zip-bomb guard. Independent of `BACKUP_UPLOAD_LIMIT_MB` and enforced on both restore paths, so a restore that fits the upload cap can still be refused with `Backup exceeds the maximum decompressed size.` Raise both when restoring a very large instance. | `5120` (5 GB) | +| `TREK_DB_JOURNAL_MODE` | SQLite [journal mode](https://sqlite.org/pragma.html#pragma_journal_mode): `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL` or `OFF`. Set `DELETE` when the data directory lives on network storage — see below. Values SQLite doesn't know log a warning and fall back. | `WAL` | +| `TREK_DB_SYNCHRONOUS` | SQLite [synchronous](https://sqlite.org/pragma.html#pragma_synchronous) level: `OFF`, `NORMAL`, `FULL` or `EXTRA`. The default follows the journal mode — `NORMAL` under WAL (what SQLite itself uses there), `FULL` otherwise, because a rollback journal at `NORMAL` can lose committed transactions on a power cut. | `NORMAL` / `FULL` | ### Running the database on network storage @@ -314,9 +346,9 @@ next key rotation or admin reset would quietly switch the file back to WAL. | Variable | Description | Default | |---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------| -| `IDEMPOTENCY_TTL_SECONDS` | How long (in seconds) stored idempotency keys are kept before garbage collection. The offline client replays queued mutations with their `X-Idempotency-Key` on reconnect, so this must exceed the longest expected offline window or a replay could create a duplicate. Invalid values silently fall back to the default. | `2592000` (30 days) | +| `IDEMPOTENCY_TTL_SECONDS` | How long (in seconds) stored idempotency keys are kept before garbage collection. The offline client replays queued mutations with their `X-Idempotency-Key` on reconnect, so this must exceed the longest expected offline window or a replay could create a duplicate. Invalid values abort startup. | `2592000` (30 days) | | `OVERPASS_URL` | Custom [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) endpoint(s) used by the map's POI "explore" search, comma-separated. When set it **replaces** the bundled public mirrors — point it at an internal or self-hosted Overpass instance when the public mirrors are unreachable from your network (e.g. firewalled/locked-down egress in a Kubernetes cluster). Entries that aren't valid `http(s)` URLs are ignored. If you don't run your own Overpass but the public mirrors throttle TREK, first make sure `APP_URL` (or `ALLOWED_ORIGINS`) is set: that alone gives outbound Overpass/Nominatim requests a unique User-Agent, which the public mirrors rate-limit far less. | bundled public mirrors | -| `OVERPASS_TIMEOUT_MS` | Per-endpoint timeout (in milliseconds) for Overpass POI requests. Endpoints race in parallel and one that hasn't answered within this window is abandoned so a faster mirror can win. Raise it if you run a slow self-hosted Overpass instance. Invalid values fall back to the default. | `12000` | +| `OVERPASS_TIMEOUT_MS` | Per-endpoint timeout (in milliseconds) for Overpass POI requests. Endpoints race in parallel and one that hasn't answered within this window is abandoned so a faster mirror can win. Raise it if you run a slow self-hosted Overpass instance. Invalid values abort startup. | `12000` | --- @@ -347,15 +379,15 @@ The plugin system is **on by default**. The runtime and the Admin → Plugins pa | `TREK_PLUGINS_DATA_DIR` | Directory for each plugin's own **data** (its private SQLite file). Kept separate from the code tree; persist it as a volume too. | `/plugins-data` | | `TREK_PLUGIN_REGISTRY_URL` | Override the plugin registry index the *Discover* tab browses. Point it at your own fork or mirror of the registry. | `https://raw.githubusercontent.com/liketrek/TREK-Plugins/main/dist/index.json` | | `TREK_PLUGIN_MAX_RSS_MB` | Per-plugin memory ceiling in MB. A plugin process that exceeds it is stopped. | `300` | -| `TREK_PLUGIN_PERMISSIONS` | Set to `off` to opt **out** of the Node.js OS-level permission sandbox for plugin child processes (not recommended). Any other value keeps the sandbox on. | `on` | +| `TREK_PLUGIN_PERMISSIONS` | Set to any falsy value (`off`, `false`, `0`, `no` — any casing) to opt **out** of the Node.js OS-level permission sandbox for plugin child processes (not recommended). A truthy value, or leaving it unset, keeps the sandbox on; anything outside that set is rejected at boot. | `on` | | `TREK_PLUGIN_ALLOW_PRIVATE_EGRESS`| Set to `on` to let a plugin's declared outbound hosts resolve to private/internal addresses (e.g. a service on your LAN). By default connections to private, loopback, link-local and metadata addresses are refused. | off (private egress blocked) | -| `TREK_PLUGINS_DEV_LINK` | **Development only.** Set to exactly `1` to enable *dev-link*: registering a plugin from a local build directory and hot-reloading it against a live instance's data. Dev-linked code bypasses the install-time signature/integrity checks and (under `npm run dev`) runs with the OS permission jail off, so it must never be reachable in production — any value other than `1` (including absent) keeps it off. Data access is still fully gated by the capability host. | off (disabled) | +| `TREK_PLUGINS_DEV_LINK` | **Development only.** Set to any truthy value (`1`, `true`, `on`, `yes` — any casing) to enable *dev-link*: registering a plugin from a local build directory and hot-reloading it against a live instance's data. Dev-linked code bypasses the install-time signature/integrity checks and (under `npm run dev`) runs with the OS permission jail off, so it must never be reachable in production — absent, blank or an explicitly falsy value (`0`, `false`, `off`, `no`) keeps it off. Data access is still fully gated by the capability host. | off (disabled) | | `TREK_PLUGIN_AI_PER_DAY` | Per-plugin **daily** cap on shared-LLM broker calls (`ai.complete` / `ai.extract`). Bounds how much a single plugin can spend on the admin's LLM quota per UTC day, independent of its granted permissions. Counts persist across restarts within the same day; set to `0` to disable the AI broker entirely. Generous by design — only bites a runaway plugin. | `200` | | `TREK_PLUGIN_NOTIFY_PER_DAY` | Per-plugin **daily** cap on user-notification broker calls (`notify.send`), so one plugin can't spam a user. Same UTC-day window and restart-safe counting as `TREK_PLUGIN_AI_PER_DAY`; set to `0` to disable the notify broker. | `100` | | `TREK_PLUGIN_RPC_PER_SEC` | Sustained rate limit (calls **per second**) for a plugin's host RPC calls (`ctx.*`) once its burst allowance is spent. Prevents a tight-loop plugin from starving the single-threaded host. | `20` | | `TREK_PLUGIN_RPC_BURST` | Burst allowance — how many host RPC calls a plugin may fire back-to-back before the per-second limit applies. | `60` | | `TREK_PLUGIN_RPC_INFLIGHT` | Max concurrent host→plugin RPC dispatches allowed for a single plugin at once (concurrency cap). | `16` | -| `TREK_PLUGIN_AUDIT_MAX_ROWS` | Per-plugin retention cap for the capability audit log (kept in the shared `trek.db`). The newest N rows per plugin are retained and older ones pruned; the retained window stays tamper-evident. Set to `0` to disable pruning. | `20000` | +| `TREK_PLUGIN_AUDIT_MAX_ROWS` | Per-plugin retention cap for the capability audit log (kept in the shared `data/travel.db`). The newest N rows per plugin are retained and older ones pruned; the retained window stays tamper-evident. Set to `0` to disable pruning. | `20000` | All of these are optional — the defaults are safe. Set `TREK_PLUGINS_ENABLED=false` if you want to switch the plugin system off entirely. diff --git a/wiki/FAQ.md b/wiki/FAQ.md index 7d3888cae..ec3599c53 100644 --- a/wiki/FAQ.md +++ b/wiki/FAQ.md @@ -2,13 +2,13 @@ ## Do I need a Google Maps API key? -No. When no Google Maps key is configured, TREK automatically falls back to OpenStreetMap (Nominatim) for place search — no API key or account required. If you want richer place data (photos, ratings, opening hours), an admin can optionally add a Google Maps key in [User Settings](User-Settings). +No. When no Google Maps key is configured, TREK automatically falls back to OpenStreetMap (Nominatim) for place search — no API key or account required. If you want richer place data (photos, ratings, opening hours), an admin can optionally add a Google Maps key in **Admin Panel → Settings** — see [Admin Panel Overview](Admin-Panel-Overview). A key saved there applies instance-wide to every member; there is no per-user field for it in the UI. ## Can I use TREK offline? -Yes. TREK is a Progressive Web App. After your first visit, the service worker (powered by Workbox) caches map tiles (Carto and OpenStreetMap), non-sensitive API responses, uploaded covers and avatars, and all static assets. Subsequent visits work without a network connection for already-cached content. See [Offline Mode and PWA](Offline-Mode-and-PWA) for installation instructions. +Yes. TREK is a Progressive Web App. After your first visit, the service worker (powered by Workbox) caches map tiles (Carto, OpenStreetMap, Mapbox GL and OpenFreeMap), uploaded covers and avatars, and every page of the app. Subsequent visits work without a network connection for already-cached content. Trip data does not come from that cache: it is stored per user in IndexedDB and read back through TREK's own offline layer, with writes queued and replayed once you reconnect. See [Offline Mode and PWA](Offline-Mode-and-PWA) for installation instructions. -> **Note:** Auth, admin, backup, and settings endpoints are intentionally excluded from the offline cache. +> **Note:** API responses are **never** stored in the service-worker cache. Workbox keys its entries by URL and cannot vary them on the session cookie, so caching them would mean one account's data could be served to the next on a shared device. ## How many MCP tokens can I create? diff --git a/wiki/Home.md b/wiki/Home.md index a934fb7c4..9b44865d2 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -27,26 +27,36 @@ TREK is a self-hosted, real-time collaborative travel planner licensed under AGP - **Invite Links** — one-time registration links with configurable max uses and expiry - **OIDC SSO** — sign in with Google, Apple, Authentik, Keycloak, or any OIDC provider - **Two-Factor Authentication** — TOTP-based 2FA with QR code setup +- **Passkeys** _(admin-enabled)_ — passwordless WebAuthn sign-in with Touch ID, Windows Hello, Android screen lock, or a hardware key; owning one also satisfies the require-2FA policy (see [Passkeys](Passkeys)) - **Public Share Links** — share a read-only view of any trip ### Addons _(admin-toggleable)_ - **Lists** — packing lists and to-dos with templates, member assignments, optional bag tracking -- **Budget Planner** — expense tracker with category breakdown, splits, multi-currency +- **Costs** — expense tracker with category breakdown, splits, multi-currency - **Documents** — file manager for trips, places, and reservations - **Collab** — group chat, shared notes, polls, day-by-day attendance - **Vacay** — personal vacation day planner with calendar view, public holidays, and carry-over tracking - **Atlas** — interactive world map, bucket list, travel stats, continent breakdown +- **Collections** — personal place library: save places across trips into named lists, copy them into any trip, share with per-member roles (see [Collections](Collections)) - **Journey** — magazine-style travel journal with entries, photos (via Immich/Synology Photos), maps, and moods - **Naver List Import** — import places from shared Naver Maps lists - **MCP** — expose TREK to AI assistants via the Model Context Protocol (OAuth 2.1) +- **AirTrail** — sync flights from your self-hosted AirTrail instance into trips +- **AI Parsing** — LLM fallback that extracts bookings from confirmation files KDE Itinerary cannot read (see [AI-Booking-Import](AI-Booking-Import)) > Dashboard widgets (currency converter and timezone clock) are per-user preferences, not an admin-toggleable addon — see [Dashboard-Widgets](Dashboard-Widgets). +> +> The full addon table, with type and per-addon notes, lives in [Addons-Overview](Addons-Overview). + +### Plugins +- **Sandboxed Plugin Runtime** — third-party plugins run in a forked child process with permission-gated RPC, install-time manifest and signature checks, and an egress allowlist (see [Plugins](Plugins)) +- **Plugin SDK** — build and publish plugins with the `trek-plugin-sdk` npm package: dev server, preflight, signing, submit (see [Plugin-Development](Plugin-Development)) ### AI / MCP Integration - **MCP Server** — built-in Model Context Protocol server with OAuth 2.1 authentication - **150+ Tools** — create trips, plan itineraries, manage budgets, send messages, and more - **30 Resources** — read-only `trek://` URIs for trips, days, places, budget, packing, journeys, and more -- **27 OAuth Scopes** — granular permissions across 13 permission groups +- **29 OAuth Scopes** — granular permissions across 14 permission groups - **Pre-built Prompts** — `trip-summary`, `packing-list`, and `budget-overview` context loaders ### Admin diff --git a/wiki/Install-Docker-Compose.md b/wiki/Install-Docker-Compose.md index da08b8690..1176d61ec 100644 --- a/wiki/Install-Docker-Compose.md +++ b/wiki/Install-Docker-Compose.md @@ -16,7 +16,7 @@ The compose file ships with several hardening options enabled by default: | `security_opt: no-new-privileges:true` | Prevents the process from gaining additional Linux privileges via setuid/setgid executables | | `cap_drop: [ALL]` | Drops all Linux capabilities from the container | | `cap_add: [CHOWN, SETUID, SETGID]` | Adds back only the capabilities needed for the entrypoint to drop privileges to the `node` user | -| `tmpfs: /tmp:noexec,nosuid,size=64m` | Mounts a 64 MB in-memory `/tmp`; required because the container root is read-only | +| `tmpfs: /tmp:noexec,nosuid,size=128m` | Mounts a 128 MB in-memory `/tmp`; required because the container root is read-only | > **Note (Docker from snap):** If you installed Docker via `snap` (config under `/var/snap/docker/...`), `no-new-privileges:true` will prevent the container from starting with `exec /usr/bin/dumb-init: operation not permitted`. This is a [snap/AppArmor limitation](https://bugs.launchpad.net/snapd/+bug/1908448), not a TREK issue — install Docker from the [official apt repository](https://docs.docker.com/engine/install/ubuntu/) instead, or remove `no-new-privileges`. See [Troubleshooting](Troubleshooting#container-wont-start-exec-usrbindumb-init-operation-not-permitted). @@ -48,17 +48,23 @@ Docker creates the volumes automatically on first `docker compose up`. Use `dock ## Environment Variables -The compose file reads variables from a `.env` file placed alongside `docker-compose.yml`. At minimum, set: +The compose file has no `env_file:` key, so a `.env` placed alongside `docker-compose.yml` is used only for `${VAR}` interpolation — a value reaches the container only if a line in the `environment:` block references it. Four entries are wired that way: ```bash # .env ENCRYPTION_KEY= TZ=Europe/Berlin +LOG_LEVEL=info ALLOWED_ORIGINS=https://trek.example.com -APP_URL=https://trek.example.com ``` -Uncomment and fill in the OIDC, initial setup, or MCP variables as needed. For a full description of every variable, see [Environment-Variables](Environment-Variables). +The commented `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` lines are interpolated as well, so uncommenting them is enough to pick their values up from `.env`. + +Every other variable — `APP_URL`, the OIDC block, `FORCE_HTTPS`, `TRUST_PROXY`, `ADMIN_EMAIL`/`ADMIN_PASSWORD`, the MCP limits — ships as a commented literal. Uncomment the line in `docker-compose.yml` and put the value there; setting it in `.env` alone has no effect. + +`APP_URL` is usually not needed. TREK resolves its public base URL as `APP_URL` → first `ALLOWED_ORIGINS` entry → `http://localhost:`, so the `ALLOWED_ORIGINS` value above already gives OIDC redirect URIs, passkey origin checks and the links in email notifications the correct origin. Uncomment `APP_URL` only when the public base URL has to differ from the first allowed origin. + +For a full description of every variable, see [Environment-Variables](Environment-Variables). ## Image Tags @@ -67,14 +73,14 @@ Three tag strategies are available: | Tag | Example | Behavior | |---|--------------------------|---| | `latest` | `mauriceboe/trek:latest` | Always the newest release across all major versions | -| Major version | `mauriceboe/trek:3` | Latest release pinned to that major version | -| Full version | `mauriceboe/trek:3.4.0` | Exact release; never changes | +| Major version | `mauriceboe/trek:4` | Latest release pinned to that major version | +| Full version | `mauriceboe/trek:4.0.0` | Exact release; never changes | The compose file above uses `latest`. To pin, change the `image:` line: ```yaml -image: mauriceboe/trek:3 # track major version 3 -image: mauriceboe/trek:3.4.0 # pin to exact release +image: mauriceboe/trek:4 # track major version 4 +image: mauriceboe/trek:4.0.0 # pin to exact release ``` ## Start TREK diff --git a/wiki/Install-Helm.md b/wiki/Install-Helm.md index 87ee8cb3c..4e447b22d 100644 --- a/wiki/Install-Helm.md +++ b/wiki/Install-Helm.md @@ -110,7 +110,7 @@ env: # TZ: "Europe/Berlin" # timezone for logs, reminders, cron jobs # LOG_LEVEL: "info" # "info" = concise, "debug" = verbose # TREK_WIKI_DIR: "/app/wiki" # where /help reads its docs from; leave unset (the image ships them) - # DEFAULT_LANGUAGE: "en" # fallback language on login page; supported: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, tr, ar, id, ja, ko, uk, gr + # DEFAULT_LANGUAGE: "en" # fallback language on login page; supported: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, tr, ar, id, ja, ko, uk, gr, sv, vi, ca # ALLOWED_ORIGINS: "https://trek.example.com" # APP_URL: "https://trek.example.com" # FORCE_HTTPS: "false" # enable HTTPS redirect + HSTS; requires TRUST_PROXY @@ -139,10 +139,13 @@ secretEnv: ADMIN_EMAIL: "" # initial admin email (first boot only) ADMIN_PASSWORD: "" # initial admin password (first boot only) OIDC_CLIENT_SECRET: "" # set if using OIDC + UNSPLASH_ACCESS_KEY: "" # optional; free key from unsplash.com/developers ``` Alternatively, use `generateEncryptionKey: true` to let the chart generate and manage the encryption key, or point `existingSecret` / `existingSecretKey` at an existing Kubernetes Secret. +> **Note:** Without `UNSPLASH_ACCESS_KEY` the server queries Unsplash's unauthenticated endpoint, which many datacenter and VPS IP ranges — including a lot of Kubernetes clusters — are blocked or rate-limited on. Trip-cover and place-image search then fail with **"Unsplash search unavailable"**. A free Access Key switches the server to Unsplash's authenticated API (`api.unsplash.com`), which is not subject to that block. The key can also be set in **Admin → Settings → API Keys**; this value takes priority over that one. See [Environment-Variables](Environment-Variables#image-search-unsplash). + ### Persistent Storage ```yaml diff --git a/wiki/Install-Portainer.md b/wiki/Install-Portainer.md index 207709a35..480835d09 100644 --- a/wiki/Install-Portainer.md +++ b/wiki/Install-Portainer.md @@ -28,7 +28,11 @@ Portainer must be installed and connected to your Docker environment. Use **Stac See https://github.com/liketrek/TREK/blob/main/docker-compose.yml -Set at minimum `ENCRYPTION_KEY`, `TZ`, and `APP_URL` in the **Environment variables** section of the stack editor. Generate an encryption key with: +Set at minimum `ENCRYPTION_KEY`, `TZ`, and `ALLOWED_ORIGINS` in the **Environment variables** section of the stack editor. Portainer's stack variables are only substituted into `${...}` placeholders in the compose file, they are not injected into the container, and the shipped compose file interpolates exactly four: `ENCRYPTION_KEY`, `TZ`, `LOG_LEVEL`, and `ALLOWED_ORIGINS`. + +Every other variable ships commented out, so setting it here does nothing. To use one — `APP_URL`, for example, which OIDC needs and which email notification links are built from — uncomment its line in the **Web editor** and put the value there directly, or change it to `- APP_URL=${APP_URL:-}` so the stack variable is picked up. + +Generate an encryption key with: ```bash openssl rand -hex 32 @@ -41,10 +45,10 @@ Three tag strategies are available: | Tag | Example | Behavior | |---|---|---| | `latest` | `mauriceboe/trek:latest` | Always the newest release across all major versions | -| Major version | `mauriceboe/trek:3` | Latest release pinned to that major version | -| Full version | `mauriceboe/trek:3.4.0` | Exact release; never changes | +| Major version | `mauriceboe/trek:4` | Latest release pinned to that major version | +| Full version | `mauriceboe/trek:4.0.0` | Exact release; never changes | -Use `latest` or a major-version tag (e.g. `3`) if you want automatic updates on redeploy. Use a full version tag (e.g. `3.4.0`) if you want explicit control over which release runs. +Use `latest` or a major-version tag (e.g. `4`) if you want automatic updates on redeploy. Use a full version tag (e.g. `4.0.0`) if you want explicit control over which release runs. ## Updating @@ -54,7 +58,7 @@ How you update depends on the tag you chose: ![Re-pull image and redeploy switch ticked, with arrows pointing to the switch and the Update button](assets/portainer-force-pull.png) -**Pinned full-version tag** — Edit the stack, change the tag in the `image:` line (e.g. `3.4.0` → `3.4.1`), then click **Update the stack**. No need to toggle the re-pull switch — a tag change forces a fresh pull. +**Pinned full-version tag** — Edit the stack, change the tag in the `image:` line (e.g. `4.0.0` → `4.0.1`), then click **Update the stack**. No need to toggle the re-pull switch — a tag change forces a fresh pull. ![Edit stack page with an arrow pointing to the image tag in the compose editor](assets/portainer-update-version.png) diff --git a/wiki/Install-Unraid.md b/wiki/Install-Unraid.md index ea361d0e9..d8da44e98 100644 --- a/wiki/Install-Unraid.md +++ b/wiki/Install-Unraid.md @@ -40,12 +40,12 @@ The Unraid template exposes the following fields in the container UI: | `TZ` | `UTC` | Timezone for logs, reminders, and scheduled tasks (e.g. `Europe/Berlin`) | | `ALLOWED_ORIGINS` | *(empty)* | Comma-separated origins for CORS and email notification links, e.g. `https://trek.example.com` | | `APP_URL` | *(empty)* | Public base URL; required when OIDC is enabled (must match the redirect URI registered with your IdP) | -| `ADMIN_EMAIL` | *(empty)* | Email for the first admin account (first-boot only; no effect once any user exists). Must be set together with `ADMIN_PASSWORD`. | +| `ADMIN_EMAIL` | `admin@trek.local` | Email for the first admin account (first-boot only; no effect once any user exists). Pre-filled by the template — must be set together with `ADMIN_PASSWORD`, otherwise both are ignored. | | `ADMIN_PASSWORD` | *(empty)* | Password for the first admin account (first-boot only). Must be set together with `ADMIN_EMAIL`. If either is omitted, TREK creates the account with email `admin@trek.local` and a random password printed to the container log. | ### Advanced Variables -Additional variables (`PORT`, `NODE_ENV`, `LOG_LEVEL`, `TREK_WIKI_DIR`, `DEFAULT_LANGUAGE`, `FORCE_HTTPS`, `TRUST_PROXY`, `COOKIE_SECURE`, `ALLOW_INTERNAL_NETWORK`, `SESSION_DURATION`, `SESSION_DURATION_REMEMBER`, all OIDC variables, `MCP_RATE_LIMIT`, `MCP_MAX_SESSION_PER_USER`, `DEMO_MODE`) are available under **Advanced View** in the template editor. +Additional variables (`PORT`, `NODE_ENV`, `LOG_LEVEL`, `TREK_WIKI_DIR`, `DEFAULT_LANGUAGE`, `FORCE_HTTPS`, `HSTS_INCLUDE_SUBDOMAINS`, `TRUST_PROXY`, `COOKIE_SECURE`, `ALLOW_INTERNAL_NETWORK`, `SESSION_DURATION`, `SESSION_DURATION_REMEMBER`, all OIDC variables, `MCP_RATE_LIMIT`, `MCP_MAX_SESSION_PER_USER`, `DEMO_MODE`, `UNSPLASH_ACCESS_KEY`) are available under **Advanced View** in the template editor. ## Setting the Encryption Key diff --git a/wiki/Internal-Network-Access.md b/wiki/Internal-Network-Access.md index 981ae63d4..074590e3a 100644 --- a/wiki/Internal-Network-Access.md +++ b/wiki/Internal-Network-Access.md @@ -4,19 +4,25 @@ TREK makes outbound HTTP requests when you configure integrations such as Immich ## Default behavior -All outbound requests go through an SSRF guard (`ssrfGuard.ts`). The guard resolves the hostname to an IP address before allowing the connection and blocks addresses in private ranges. +TREK has two SSRF guards, both in `ssrfGuard.ts`. Which one applies depends on the call site, not on who configured the URL. + +**The strict guard** (`safeFetch` / `safeFetchFollow`, built on `checkSsrf`) covers most outbound traffic — Immich, Synology Photos, AirTrail, notification webhooks, ntfy, Unsplash, and place lookups. It resolves the hostname to an IP address before allowing the connection and blocks loopback, link-local and private ranges. Only the private ranges open up, and only with `ALLOW_INTERNAL_NETWORK=true`. The two tables below describe this guard. + +**The relaxed guard** (`safeFetchAdminConfigured`, also exported as `safeFetchLlm`) covers endpoints that are expected to live on your own network: OIDC (discovery, token, userinfo, JWKS), the LLM providers behind the AI Parsing addon (a local Ollama or any OpenAI-compatible endpoint), and plugin OAuth token exchanges. It deliberately permits loopback and LAN targets, so a model server on `localhost` or an identity provider on your LAN works **without** `ALLOW_INTERNAL_NETWORK`. It still resolves every hostname, re-checks every redirect hop, and always blocks link-local and cloud-metadata addresses (`169.254.0.0/16`, the full `fe80::/10`, and the AWS and Alibaba metadata addresses). ## Always blocked (no override possible) -These ranges are blocked regardless of any setting: +Under the strict guard, these ranges are blocked regardless of any setting: | Range | Description | |---|---| | `127.0.0.0/8`, `::1` | Loopback | | `0.0.0.0/8` | Unspecified | -| `169.254.0.0/16`, `fe80::/10` | Link-local / cloud metadata endpoints | +| `169.254.0.0/16`, `fe80::/16` | Link-local / cloud metadata endpoints | | `::ffff:127.x.x.x`, `::ffff:169.254.x.x` | IPv4-mapped loopback and link-local | +The IPv6 link-local rule here matches the `fe80:` hextet only, which is narrower than the nominal `fe80::/10` prefix (`fe80:`–`febf:`). In practice that is the same set of addresses, since RFC 4291 link-local addresses are always `fe80::/64`. The relaxed guard covers the whole `/10`. + ## Blocked unless `ALLOW_INTERNAL_NETWORK=true` | Range / Hostname | Description | @@ -27,15 +33,15 @@ These ranges are blocked regardless of any setting: | `100.64.0.0/10` | CGNAT / Tailscale shared address space | | `fc00::/7` | IPv6 ULA | | IPv4-mapped RFC-1918 variants | e.g. `::ffff:10.x`, `::ffff:192.168.x` | -| `*.local`, `*.internal` hostnames | mDNS / internal DNS suffixes (e.g. Docker service names, LAN hosts) | +| `*.local`, `*.internal`, `localhost` hostnames | mDNS / internal DNS suffixes (e.g. Docker service names, LAN hosts) and the literal `localhost` | -The hostname `localhost` is not blocked at the hostname stage, but it resolves to `127.0.0.1` which is caught by the loopback rule above and is therefore always blocked. +The hostname `localhost` is matched at the hostname stage too, but it normally resolves to a loopback address (`127.0.0.1` or `::1`), which the always-blocked loopback rule catches first — so under the strict guard it is blocked no matter how `ALLOW_INTERNAL_NETWORK` is set. On a host that maps `localhost` somewhere else, the hostname rule still applies and it stays blocked unless `ALLOW_INTERNAL_NETWORK=true`. The relaxed guard allows `localhost` outright, which is what makes a local Ollama the supported default for AI Parsing. `*.local` and `*.internal` hostnames are permitted when `ALLOW_INTERNAL_NETWORK=true` — the guard still resolves them to an IP and enforces all IP-level rules, so any such hostname that resolves to a loopback or link-local address remains blocked regardless. ## When to enable -Set `ALLOW_INTERNAL_NETWORK=true` when Immich, Synology Photos, or another integrated service is hosted on your local network and you need TREK to reach it. +Set `ALLOW_INTERNAL_NETWORK=true` when a service reached through the strict guard — Immich, Synology Photos, AirTrail, or a notification webhook — is hosted on your local network and you need TREK to reach it. You do **not** need it for a local or LAN Ollama, an OpenAI-compatible endpoint, an OIDC provider on your LAN, or plugin OAuth; those go through the relaxed guard and already work. Leave the flag off if only those need internal access, since turning it on widens the surface for every strict-guard integration at once. See [Environment-Variables](Environment-Variables) for how to set environment variables. @@ -47,7 +53,7 @@ Even with `ALLOW_INTERNAL_NETWORK=true`, TREK pins the DNS resolution to prevent ## Audit log -When a user saves an Immich URL that resolves to a private IP, TREK records an `immich.private_ip_configured` entry in the [Audit-Log](Audit-Log) including the URL and the resolved IP address. This audit event is specific to Immich; Synology Photos does not emit an equivalent event. +When a user saves an Immich URL that resolves to a private IP, TREK records an `immich.private_ip_configured` entry in the [Audit-Log](Audit-Log) including the URL and the resolved IP address. AirTrail does the same with `airtrail.private_ip_configured`. Synology Photos does not emit an equivalent event. ## See also diff --git a/wiki/Journey-Journal.md b/wiki/Journey-Journal.md index f3c704457..4d1759990 100644 --- a/wiki/Journey-Journal.md +++ b/wiki/Journey-Journal.md @@ -33,7 +33,7 @@ Each entry corresponds to a day in your journey. The entry editor provides: | `neutral` | Neutral | Grey | | `rough` | Rough | Violet | -- **Weather** — choose one of six values: Sunny, Partly cloudy, Cloudy, Rainy, Stormy, Cold. +- **Weather** — choose one of six values: Sunny, Partly cloudy, Cloudy, Rainy, Stormy, Snowy. (Snowy is stored under the id `cold`.) - **Photos** — attach photos to the entry. The first photo becomes the card thumbnail in list views. > **Note on HEIC files:** HEIC is an Apple-only format that many browsers and platforms do not recognise as an image. To ensure broad compatibility, HEIC/HEIF files are automatically converted to JPEG before upload. This conversion may result in the loss of embedded metadata (EXIF data such as GPS coordinates, camera information, etc.). - **Pros / Cons** — optional verdict cards. Add items to a **Pros** list (thumbs-up) or a **Cons** list (thumbs-down) to summarise what you loved or what could have been better. These are stored in the `pros_cons.pros` and `pros_cons.cons` arrays on the entry. @@ -65,6 +65,26 @@ The thin dashed line connecting entries in date order is something else and stay ![Journey detail page for "Autumn in Japan" with its cover header and day/place/entry/photo counts, the day-by-day timeline with Add Entry actions on the left, and the entry map on the right](assets/JourneyDetail.png) +## TREK Studio + +A journey can also be laid out as a printable photo book. Open a journey and click **Studio** in the header. The designer opens on top of the journey at `/journey/:id/studio`, so the journey stays open underneath and **Back to the journey** drops you straight back into it. Studio is marked **Beta**. + +![The TREK Studio editor with the pages rail on the left, a spread on the workbench and the properties inspector on the right](assets/TREK-Studio-Editor.png) + +- **Page format** — Square 21 × 21 cm (the default), Square 30 × 30 cm, A4 landscape, A4 portrait, A5 landscape, or a custom width × height in millimetres. Everything is drawn as a spread (two pages side by side) with 3 mm bleed and a 5 mm safe margin. +- **Auto layout** has two entries. **This spread** builds the spread on screen again from the journal entry it came from, and is only offered on a spread that came from one. **The whole book** replaces every page, keeping your title and page setup. Both are ordinary undo steps, so you can press one, look at it, and undo. +- **Pages, Content, Elements, Travel and Layouts** are the sections of the left rail. Content holds the journey's own photos and entries; Layouts has thirteen spread layouts (Hero and story, Four up, Strip and text, Mosaic and so on) plus a separate set of five for the cover and back; Elements has text styles, shapes, lines, grids, empty frames with their frame styles, and a searchable icon library; Travel builds figures out of the journey itself — route maps, country outlines and lists, flags, date, day and distance marks, and a trip summary. +- **Properties** on the right edits whatever is selected: position and size, crop and focal point, fill or fit, a look filter, corner radius, frame style, stacking order, lock. An element that auto layout tied to a journal entry follows that entry until you edit it here, which breaks the link. +- **Export** opens a print view that your browser turns into a PDF. Pick **Single pages** (one leaf per sheet, what a printer wants) or **Spreads** (two pages at a time, the way the book opens), with optional crop marks that add the bleed on every edge and mark where to cut. +- **Spreads travel between books.** Download the spread you are on as a design file and import it into another book. The file carries the design only, not the photographs. +- **Several people can design at once.** Everyone in the same book sees the others' pointers with their names on them, and a save is pushed to the rest live. A save that lands on a version somebody else has already changed comes back as a conflict, with the other version alongside it, rather than quietly overwriting their work. + +![Frame styles and frame shapes in TREK Studio, with a photo's crop, look and frame options in the inspector](assets/TREK-Studio-Frames.png) + +The book belongs to its journey and inherits its access exactly: anyone who may read the journey may open its book, and only those who may edit the journey may save or delete it. There is no second set of permissions to keep in step. + +Studio is desktop only: it needs a viewport at least 1024 px wide. Below that — a tablet, or a narrowed browser window — it opens over the journey and shows a "Studio needs a bigger screen" notice with a way back. On a phone the journey opens in its mobile view as usual and Studio is not offered at all. Making the PDF is desktop only too. + ## Plugin entry rows Installed plugins can add extra rows to a journal entry card via the `journalEntryProvider` hook. A plugin returns rows (`label`, optional `value`, optional `url`) and TREK renders them natively under the entry — no iframe. Rows are additive and fail-safe: they require the Journey addon, the entry's journey is access-checked the same way as reading it, only http/https/mailto links are allowed, and a provider that errors or is slow is simply skipped. @@ -75,6 +95,10 @@ Installed plugins can add extra rows to a journal entry card via the `journalEnt You can share a journey with a read-only public link. When creating the link you can independently toggle which sections are visible to visitors: **Timeline** (entries and stories), **Gallery** (photos), and **Map**. Visitors can only see the sections you have enabled, and no TREK account is required. See [Public-Share-Links](Public-Share-Links) for details on the separate journey share token mechanism. +**Photos appear on the public map too**, as long as **Gallery** and **Map** are both on. A gallery photo that knows where it was taken becomes a thumbnail pin, clustered into one pin where several were taken close together, and sitting below the entry pins so the itinerary stays the point of the map. The location comes from the file's own EXIF for uploads and from Immich or Synology for provider photos, and a photo that carries none simply stays off the map (see the HEIC note above — that conversion drops GPS, so iPhone uploads usually arrive without a location). + +Both toggles matter. A photo's coordinates follow the **Map** switch: with Map off the gallery is still shared, but every photo's coordinates are stripped before they leave the server. With Gallery off no photos are sent at all, so a map-only share shows the entry pins and nothing else. + ## See also - [Addons-Overview](Addons-Overview) diff --git a/wiki/Languages.md b/wiki/Languages.md index dc3131ab2..52105e8ac 100644 --- a/wiki/Languages.md +++ b/wiki/Languages.md @@ -1,6 +1,6 @@ # Languages -TREK ships with translations for 20 languages. You can change your language at any time without logging out. +TREK ships with translations for 23 languages. You can change your language at any time without logging out. ## Supported languages @@ -26,6 +26,9 @@ TREK ships with translations for 20 languages. You can change your language at a | `ko` | 한국어 | | `uk` | Українська | | `gr` | Ελληνικά | +| `sv` | Svenska | +| `vi` | Tiếng Việt | +| `ca` | Català | ## RTL support diff --git a/wiki/Login-and-Registration.md b/wiki/Login-and-Registration.md index 622162848..925f0bb0e 100644 --- a/wiki/Login-and-Registration.md +++ b/wiki/Login-and-Registration.md @@ -4,7 +4,7 @@ ## Signing in -Navigate to `/login` and enter your email and password. On success, the server sets a `trek_session` cookie — httpOnly, sameSite=`lax`, and secure in production — that persists your session for 24 hours across page reloads and browser restarts. You do not need to sign in again until the session expires or you explicitly log out. +Navigate to `/login` and enter your email and password. On success, the server sets a `trek_session` cookie — httpOnly, sameSite=`lax`, and secure in production — that keeps you signed in across page reloads. How long it survives depends on the **Remember me** switch, described below. You do not need to sign in again until the session expires or you explicitly log out. > **Note:** The `secure` flag on the cookie can be overridden by setting `COOKIE_SECURE=false` in the server environment (useful for plain-HTTP dev setups), or force-enabled with `FORCE_HTTPS=true`. @@ -12,17 +12,30 @@ If your account has two-factor authentication enabled, you are prompted for a TO If you have forgotten your password, click the **"Forgot password?"** link below the password field to start the self-service reset flow. See [Password-Reset](Password-Reset) for details. +### Remember me + +The **Remember me** switch below the password field decides how long the session lives: + +- **Off** (the default) — a browser-session cookie with no `maxAge`, cleared when you close the browser, backed by a token valid for `SESSION_DURATION` (default `24h`). +- **On** — a persistent cookie matching a token valid for `SESSION_DURATION_REMEMBER` (default `30d`), so the session survives browser restarts. + +Both durations are configurable per instance, see [Environment-Variables](Environment-Variables). Your choice is carried through the MFA step, through a password change, and through SSO sign-in. While you keep using TREK the cookie is silently re-issued once the token is past half its lifetime, with the same Remember me semantics, so an active session never expires mid-use. Only cookie sessions are renewed this way — API and MCP clients authenticating with a bearer token are not. + +Sign-ins that never pass through this switch — registration, the **"Try demo"** button and passkey sign-in — get the historical default instead: a persistent cookie lasting `SESSION_DURATION`. + ### Forced password change -If an admin has marked your account as requiring a password change, a **Set new password** form is shown immediately after a successful login (or after the MFA step). The session cookie is not issued until the new password is saved. +Some accounts are flagged as needing a new password: the admin account TREK seeds on first boot always is, and so is an account restored with the `reset-admin.js` recovery script (see [Troubleshooting](Troubleshooting)). For those accounts a **Set new password** form is shown immediately after a successful login (or after the MFA step). The session cookie has already been issued at that point — the app simply keeps you on the form until the new password is saved, and saving it re-issues the cookie and signs your other sessions out. + +There is no control in the Admin Panel that flags an existing account. An admin-set password (see [Admin-Users-and-Invites](Admin-Users-and-Invites)) neither raises the flag nor clears it, so a user who already carries it is still asked to choose their own password at the next sign-in. ## Registering The Register form appears under one of these conditions: - **Open registration** — the admin has enabled password registration for the instance (`password_registration` setting). -- **Valid invite link** — you visited `/login?invite=TOKEN` with a valid token (see below). -- **First user** — no accounts exist yet; the registration form is shown automatically and the first account created becomes an admin. +- **Valid invite link** — you visited `/register?invite=TOKEN` with a valid token (see below). +- **No accounts at all** — nobody has signed up yet; the registration form is shown automatically and the first account created becomes an admin. On a normal install this never happens, because TREK seeds an admin on first boot (see [First run](#first-run) below). Registration fields: **username**, **email**, and **password**. @@ -42,7 +55,7 @@ Passwords must meet all of the following rules: ### Invite link flow -When an admin shares an invite link (`/login?invite=TOKEN`), visiting it: +When an admin shares an invite link (`/register?invite=TOKEN`), visiting it: 1. Validates the token against the server. 2. Switches the login page to Register mode automatically. @@ -50,13 +63,25 @@ When an admin shares an invite link (`/login?invite=TOKEN`), visiting it: If the token is invalid, expired, or exhausted, an error is shown. -## First user +## First run -On a fresh TREK instance with no existing accounts, the registration form opens immediately. The first account created is automatically assigned the **admin** role. +On a normal install TREK seeds an admin account on first boot, before anyone can register, so the login page opens in Sign In mode and the Register link is hidden. The credentials depend on how you start the container: + +- With **both** `ADMIN_EMAIL` and `ADMIN_PASSWORD` set, those values are used. +- With only one of the two set, both are ignored (a warning is logged) and the generated credentials below are used instead. +- Otherwise the account is `admin@trek.local`, username `admin`, with a randomly generated password printed to the container log — run `docker logs trek` to retrieve it. + +The seeded admin is flagged as requiring a password change, so you are forced to set a new password on first sign-in. Until that happens the Register link stays hidden even though password registration is enabled by default; once the password is changed the link appears normally. + +Two setups skip the seeder entirely. `DEMO_MODE=true` seeds the demo data instead (see [Demo-Mode](Demo-Mode)), and on an **OIDC-only** install no local account is created at all — there the first user who signs in through the identity provider is assigned the **admin** role. + +See [Quick-Start](Quick-Start) for the full first-boot walkthrough. ## Rate limiting -Failed login attempts are rate-limited to **10 attempts per 15-minute window** per IP address. After exceeding the limit, further attempts return HTTP 429 until the window resets. +Requests to the login endpoint are rate-limited to **10 per 15-minute window** per IP address. The counter is incremented before the credentials are checked, so successful sign-ins count against it too, not only failed ones. After exceeding the limit, further requests return HTTP 429 until the window resets. The window is fixed from the first counted request, and blocked requests do not extend it. + +That counter is shared rather than per-endpoint. `POST /api/auth/login`, `POST /api/auth/register`, `GET /api/auth/invite/:token` and the passkey sign-in ceremony each allow 10 requests from it; `PUT /api/auth/me/password`, `POST /api/auth/mfa/disable`, `POST /api/auth/mcp-tokens` and passkey deletion draw on the same counter but are rejected once it reaches 5. Unrelated activity from the same IP therefore eats into the allowance — ten registration attempts will 429 the next login, and five requests of any kind will 429 a password change. MFA verification attempts are rate-limited separately to **5 attempts per 15-minute window** per IP address. diff --git a/wiki/MCP-Addon-Tools.md b/wiki/MCP-Addon-Tools.md index aec122211..7e821e5ea 100644 --- a/wiki/MCP-Addon-Tools.md +++ b/wiki/MCP-Addon-Tools.md @@ -18,8 +18,10 @@ Requires `packing:read` or `packing:write` scope. | `delete_packing_item` | Remove a packing item. | | `reorder_packing_items` | Set the display order of packing items within a trip. | | `bulk_import_packing` | Import multiple packing items at once from a list (with optional quantity). | +| `list_packing_templates` | List the reusable packing templates (id, name, item count) so one can be applied. | | `apply_packing_template` | Apply a saved packing template to a trip. | -| `save_packing_template` | Save the current packing list as a reusable template. | +| `save_packing_template` | Save the current packing list as a reusable template. Templates are global, so this is admin only — a non-admin gets `Admin access required`. | +| `delete_packing_template` | Delete a reusable packing template. Global too, so admin only. | | `list_packing_bags` | List all packing bags for a trip. | | `create_packing_bag` | Create a new packing bag (e.g. "Carry-on", "Checked bag"). | | `update_packing_bag` | Rename or recolor a packing bag. | @@ -64,6 +66,14 @@ Requires `atlas:read` or `atlas:write` scope. Requires `collab:read` or `collab:write` scope. +The addon alone is not enough. Collab has four sub-features an admin switches on and off independently in Admin → Addons — Notes, Polls, Chat and What's Next, all on by default — and three of them gate MCP entries. A tool or resource is only registered when its own sub-feature is enabled on top of the addon, so switching one off makes its entries vanish from the tool list rather than return an error. What's Next has no MCP tools or resources of its own, so toggling it changes nothing here. + +| Sub-feature | Tools it gates | Resource it gates | +|---|---|---| +| Notes | `create_collab_note`, `update_collab_note`, `delete_collab_note` | `trek://trips/{tripId}/collab-notes` | +| Polls | `list_collab_polls`, `create_collab_poll`, `vote_collab_poll`, `close_collab_poll`, `delete_collab_poll` | `trek://trips/{tripId}/collab/polls` | +| Chat | `list_collab_messages`, `send_collab_message`, `delete_collab_message`, `react_collab_message` | `trek://trips/{tripId}/collab/messages` | + | Tool | Description | |---|---| | `create_collab_note` | Create a shared note visible to all trip members. Supports title, content, category, and color. | @@ -79,6 +89,38 @@ Requires `collab:read` or `collab:write` scope. | `delete_collab_message` | Delete a chat message (own messages only). | | `react_collab_message` | Toggle a reaction emoji on a chat message. | +### Collections _(Collections addon required)_ + +Requires `collections:read` or `collections:write` scope. + +| Tool | Description | +|---|---| +| `list_collections` | List the saved-place collections the user owns or has accepted a share for, plus any pending incoming invites. | +| `get_collection` | Get one collection with its members, labels, and all saved places, including the average rating and each member's vote. | +| `available_collection_users` | List users who can still be invited to a collection (excludes current members and guests). | +| `create_collection` | Create a new saved-place collection owned by the user. | +| `update_collection` | Update a collection's name, description, colour, icon, cover, links, or sort order. Owner/admin only. | +| `delete_collection` | Permanently delete a collection and all its saved places. Owner only, and it cannot be undone. | +| `reorder_collections` | Reorder the user's collections — pass every collection id in the desired order. | +| `save_place_to_collection` | Save a place into a collection from a raw payload. Returns a duplicate marker instead of saving when a similar place already exists, unless `force` is true. | +| `save_trip_places_to_collection` | Copy one or more existing trip places into a collection. Duplicates are skipped unless `force` is true. | +| `update_collection_place` | Update a saved place's name, address, coordinates, description, notes, status, category, links, tags, labels, image, or move it to another collection. | +| `set_collection_place_status` | Set a saved place's status: `idea`, `want`, or `visited`. | +| `rate_collection_place` | Set or clear the current user's 1-5 star rating on a saved place. Every member rates independently; pass `null` to remove the vote. | +| `delete_collection_place` | Remove a saved place from its collection. Requires delete permission on the list. | +| `copy_collection_places_to_trip` | Copy one or more saved places into a trip, ratings included. Requires edit access to the target trip. | +| `create_collection_label` | Create a custom per-collection label (name plus optional hex colour) for grouping and filtering places. | +| `update_collection_label` | Rename or recolour a collection label, or change its sort order. | +| `delete_collection_label` | Delete a collection label; its assignments on places are cleared. | +| `assign_collection_labels` | Add labels across a set of saved places, or take them away with `remove=true`. | +| `invite_to_collection` | Invite a user to collaborate on a collection as `viewer`, `editor`, or `admin` (default `editor`). Owner only. | +| `set_collection_member_role` | Change an accepted member's role. Owner only. | +| `remove_collection_member` | Remove an accepted member from a shared collection. Owner only. | +| `cancel_collection_invite` | Cancel a pending invite you sent for a collection. Owner only. | +| `accept_collection_invite` | Accept a pending invite to join a shared collection. | +| `decline_collection_invite` | Decline a pending invite to a shared collection. | +| `leave_collection` | Leave a shared collection you are a member of. The owner cannot leave — delete the list instead. | + ### Vacay _(Vacay addon required)_ Requires `vacay:read` or `vacay:write` scope. diff --git a/wiki/MCP-Overview.md b/wiki/MCP-Overview.md index 92360666e..9c645dd79 100644 --- a/wiki/MCP-Overview.md +++ b/wiki/MCP-Overview.md @@ -31,7 +31,7 @@ See [MCP-Setup](MCP-Setup) for step-by-step instructions for each method. ## Requirements - **MCP addon enabled** — an administrator must enable the MCP addon (`mcp`) from the Admin Panel before the `/mcp` endpoint becomes available and the MCP section appears in user settings. -- **`APP_URL` set** — the `APP_URL` environment variable must be configured to your TREK instance's public URL so that OAuth discovery works correctly. Without it, clients that use OAuth 2.1 cannot complete authentication. +- **`APP_URL` set** — set the `APP_URL` environment variable to your TREK instance's public URL so OAuth discovery advertises the right issuer and endpoints. If it is unset or not a valid URL, TREK falls back to the first entry of `ALLOWED_ORIGINS`, and then to `http://localhost:{PORT}`. Only an `https://` origin or `localhost` / `127.0.0.1` is accepted as the issuer — anything else is replaced by `http://localhost:{PORT}`, which remote OAuth clients cannot reach. ## Rate limits and session limits diff --git a/wiki/MCP-Scopes.md b/wiki/MCP-Scopes.md index 3f48f03f9..ec4037c2e 100644 --- a/wiki/MCP-Scopes.md +++ b/wiki/MCP-Scopes.md @@ -6,16 +6,18 @@ OAuth scopes control exactly which data your AI client can read or write in TREK ## All scopes -TREK defines 27 scopes across 13 groups. +TREK defines 29 scopes across 14 groups. | Group | Scope | Permission | |---|---|---| | **Trips** | `trips:read` | View trips, days, day notes, and members | -| | `trips:write` | Create, update, and delete trips, days, day notes, and accommodations; manage members; duplicate trips | +| | `trips:write` | Create and update trips; create, update, and delete days, day notes, and accommodations; manage members; duplicate trips | | | `trips:delete` | Permanently delete entire trips (irreversible) | | | `trips:share` | Create, update, and revoke public share links for trips | | **Places** | `places:read` | Read places, day assignments, tags, and categories | | | `places:write` | Create, update, and delete places, assignments, and tags | +| **Collections** | `collections:read` | Read saved-place collections, their places, ratings, labels, and members | +| | `collections:write` | Create and edit collections, save, rate, label and copy places, and share lists | | **Atlas** | `atlas:read` | Read visited countries, regions, and bucket list | | | `atlas:write` | Mark countries and regions visited, manage bucket list | | **Packing** | `packing:read` | Read packing items, bags, and category assignees | @@ -45,7 +47,7 @@ TREK defines 27 scopes across 13 groups. - `journey:read` or `journey:write` grants journey read access. `journey:share` alone does **not** grant read access — it only enables managing public share links. - `list_trips` and `get_trip_summary` are always available regardless of scope — they are navigation tools. - Static tokens and web session JWTs have full access equivalent to all scopes. -- Addon-gated tools (Atlas, Collab, Vacay, Journey) require both the relevant scope **and** the corresponding addon to be enabled by an admin. +- Addon-gated tools (Packing, To-dos, Budget, Collections, Atlas, Collab, Vacay, Journey) require both the relevant scope **and** the corresponding addon to be enabled by an admin. The to-do tools ride the **Packing** addon, not an addon of their own. ## Choosing the right scopes diff --git a/wiki/MCP-Setup.md b/wiki/MCP-Setup.md index 95b936034..9fa95c8c3 100644 --- a/wiki/MCP-Setup.md +++ b/wiki/MCP-Setup.md @@ -4,7 +4,7 @@ This page explains how to connect an AI assistant to your TREK instance. TREK su ![MCP Setup](assets/MCPConfig.png) -> **Cloudflare users:** If your TREK instance is proxied through Cloudflare, Bot Fight Mode and Super Bot Fight Mode will block MCP requests from ChatGPT. Claude.ai is not affected. See [Troubleshooting → MCP requests blocked by Cloudflare WAF](#mcp-requests-blocked-by-cloudflare-waf-bot-fight-mode) for the fix. +> **Cloudflare users:** If your TREK instance is proxied through Cloudflare, Bot Fight Mode and Super Bot Fight Mode will block MCP requests from ChatGPT. Claude.ai is not affected. See [Troubleshooting → MCP requests blocked by Cloudflare WAF](Troubleshooting#mcp-requests-blocked-by-cloudflare-waf-bot-fight-mode) for the fix. ## Option A: OAuth 2.1 (recommended) @@ -14,7 +14,7 @@ OAuth 2.1 is the preferred connection method. You grant specific scopes during t Claude.ai (web) supports native MCP connections — no JSON config file required: -1. In TREK, go to **Settings → Integrations → MCP → OAuth Clients** and click **Create**. +1. In TREK, go to **Settings → Integrations → MCP → OAuth 2.1 Clients** and click **New Client**. 2. Select the **Claude.ai** preset. This fills in the redirect URI (`https://claude.ai/api/mcp/auth_callback`) and a default scope set. 3. Give the client a name, adjust scopes if needed, and save. Copy the client ID and client secret (`trekcs_` prefix) — the secret is shown only once. 4. In Claude.ai, open the MCP settings and add a new server using your TREK URL (`https:///mcp`). Claude.ai will open your browser to complete the OAuth consent flow. @@ -23,7 +23,7 @@ Claude.ai (web) supports native MCP connections — no JSON config file required Claude Desktop supports native MCP connections — no JSON config file required: -1. In TREK, go to **Settings → Integrations → MCP → OAuth Clients** and click **Create**. +1. In TREK, go to **Settings → Integrations → MCP → OAuth 2.1 Clients** and click **New Client**. 2. Select the **Claude Desktop** preset. This fills in the redirect URI and a default scope set. 3. Give the client a name, adjust scopes if needed, and save. Copy the client ID and client secret — the secret is shown only once. 4. In Claude Desktop, open Settings → MCP and add a new server using your TREK URL (`https:///mcp`). Claude Desktop will open your browser to complete the OAuth consent flow. @@ -76,7 +76,7 @@ Create a client in TREK using the appropriate preset (Cursor, VS Code, Windsurf, ### Pre-created OAuth clients -**Settings → Integrations → MCP → OAuth Clients** lets you create named OAuth clients before connecting. This gives you: +**Settings → Integrations → MCP → OAuth 2.1 Clients** lets you create named OAuth clients before connecting. This gives you: - A fixed, named scope list defined up front - A client secret (`trekcs_` prefix, shown once) for confidential client mode @@ -94,7 +94,7 @@ Use this when your AI agent or automation script needs to authenticate silently ### Create a machine client -1. Go to **Settings → Integrations → MCP → OAuth Clients** and click **New Client**. +1. Go to **Settings → Integrations → MCP → OAuth 2.1 Clients** and click **New Client**. 2. Tick **Machine client (no browser login)**. The redirect URI field disappears — machine clients don't need one. 3. Give it a name, select scopes, and click **Register Client**. 4. Copy the `client_id` and `client_secret` shown — the secret is displayed only once. @@ -113,7 +113,7 @@ Machine clients are designed for **AI agent frameworks and custom MCP client imp > **Deprecated:** Static tokens will stop working in a future version of TREK. Migrate to OAuth 2.1 or machine clients. -Static tokens grant full access to all tools and resources with no scope restrictions. Sessions using a static token will receive deprecation warnings in the AI client on every tool call. +Static tokens grant full access to all tools and resources with no scope restrictions. A static-token session is warned about the deprecation once, not on every call: the notice rides along with the result of the first `list_trips` or `get_trip_summary` in that session — the trip-discovery tools an AI client normally reaches for first — for the client to surface to you. The tool's own payload still comes with it, and every later call in that session returns a plain result. The notice is also part of the session instructions the server sends when the connection initializes. 1. Go to **Settings → Integrations → MCP**, open the **API Tokens** sub-tab, and click **Create New Token**. 2. Give the token a name and copy it immediately — it is shown only once. The token starts with `trek_`. diff --git a/wiki/MCP-Tools-and-Resources.md b/wiki/MCP-Tools-and-Resources.md index 633781ad5..a842a3faa 100644 --- a/wiki/MCP-Tools-and-Resources.md +++ b/wiki/MCP-Tools-and-Resources.md @@ -2,7 +2,7 @@ TREK exposes **tools** (read and write actions) and **resources** (read-only `trek://` URIs). Tools are registered per-session based on OAuth scopes and enabled addons. -For addon-gated tools (Packing, To-Dos, Atlas, Collab, Vacay, Journey) and their resources, see [MCP-Addon-Tools](MCP-Addon-Tools). +For addon-gated tools (Packing, To-Dos, Atlas, Collab, Collections, Vacay, Journey) and their resources, see [MCP-Addon-Tools](MCP-Addon-Tools). ## Tools @@ -52,6 +52,7 @@ Requires `places:read` or `places:write` scope. | `list_places` | List places in a trip, optionally filtered by assignment status, category, tag, or search query. | | `create_place` | Add a place with name, coordinates, address, category, notes, website, phone, and optional `google_place_id` / `osm_id`. | | `update_place` | Update any field of an existing place including transport mode, timing, and price. | +| `rate_place` | Set or clear your own 1–5 star rating on a place. Every trip member rates independently and the place shows the average. Pass `null` to clear the vote. | | `bulk_update_places` | Update many places at once, applying the same field values (e.g. category, price, transport mode) to every listed place in a single call. | | `delete_place` | Remove a place from a trip. Also removes all day assignments. | | `bulk_delete_places` | Delete multiple places by ID. Removes all day assignments. Cannot be undone. | @@ -141,6 +142,11 @@ Requires `budget:read` or `budget:write` scope. Budget addon must be enabled. | `delete_budget_item` | Remove a budget item. | | `set_budget_item_members` | Set which members are splitting a budget item (replaces current list). | | `toggle_budget_member_paid` | Mark or unmark a member as having paid their share. | +| `get_settlement_summary` | Each member's net balance and the suggested payments to settle shared expenses, in the trip's base currency. Call this before recording a settlement. | +| `list_settlements` | List the recorded settle-up payments for a trip — who paid whom, how much, and when. | +| `create_settlement` | Record a settle-up payment: one member paid another the given amount in the trip's base currency. | +| `update_settlement` | Update a recorded settle-up payment (payer, recipient, and amount). | +| `delete_settlement` | Delete a recorded settle-up payment. This is the undo for `create_settlement` and restores the affected balances. | ### Tags @@ -189,14 +195,14 @@ Resources provide read-only access via `trek://` URIs. Read them to understand c |---|---|---| | `trek://trips` | `trips:*` | All trips you own or are a member of | | `trek://trips/{tripId}` | `trips:*` | Single trip with metadata and member count | -| `trek://trips/{tripId}/days` | `trips:*` | Days of a trip with their assigned places | +| `trek://trips/{tripId}/days` | `trips:read` | Days of a trip with their assigned places | | `trek://trips/{tripId}/places` | `places:read` | All places in a trip. Supports `?assignment=all\|unassigned\|assigned` | | `trek://trips/{tripId}/reservations` | `reservations:read` | Flights, hotels, restaurants, and other reservations | -| `trek://trips/{tripId}/days/{dayId}/notes` | `trips:*` | Notes for a specific day | -| `trek://trips/{tripId}/accommodations` | `trips:*` | Hotels and rentals with check-in/out details | +| `trek://trips/{tripId}/days/{dayId}/notes` | `trips:read` | Notes for a specific day | +| `trek://trips/{tripId}/accommodations` | `trips:read` | Hotels and rentals with check-in/out details | | `trek://trips/{tripId}/members` | `trips:*` | Owner and collaborators | | `trek://categories` | (any) | Available place categories (id, name, icon, color) | -| `trek://notifications/in-app` | `notifications:read` | Your in-app notifications (most recent 50, unread first) | +| `trek://notifications/in-app` | `notifications:read` | Your in-app notifications (most recent 50, newest first) | For addon-gated resources (Budget, Packing, To-Dos, Collab, Atlas, Vacay, Journey), see [MCP-Addon-Tools](MCP-Addon-Tools). diff --git a/wiki/Map-Features.md b/wiki/Map-Features.md index 73b79e6e7..0d9a7c638 100644 --- a/wiki/Map-Features.md +++ b/wiki/Map-Features.md @@ -6,14 +6,18 @@ The trip planner map shows your places, route lines, transport overlays, and you ## Map renderer -TREK uses **Leaflet** by default. If you configure a Mapbox access token in Settings → Map, the map upgrades to **Mapbox GL** with higher-quality tiles, 3D buildings, and terrain. If Mapbox GL is selected but no access token is present, TREK falls back to Leaflet automatically so the map is never blank. +TREK uses **Leaflet** by default. The renderer is picked in Settings → Map under **Map provider**: **Leaflet** for raster tiles, **MapLibre GL** for OpenFreeMap vector tiles (no token required), or **Mapbox GL** for vector tiles with 3D buildings and terrain, which additionally needs a Mapbox access token. If Mapbox GL is selected but no access token is present, TREK falls back to Leaflet automatically so the map is never blank. The scopes required for Mapbox GL are: - STYLES:TILES - STYLES:READ - FONTS:READ -- DATASETS:WRITE -- VISION:CREATE +- DATASETS:READ +- VISION:READ + +## Satellite view + +A round button in the bottom-left corner of the Leaflet map flips the base layer between the normal map tiles and **satellite** imagery (ESRI World Imagery — no API key needed, usable up to zoom 19). The icon always shows the layer it switches to. Your choice is stored on your account (`map_base_layer`), so it carries over to every trip and survives a reload. Markers, route lines, tracks and booking overlays are drawn on top of either layer. ## Place markers @@ -28,7 +32,9 @@ When zoomed out, nearby markers are grouped into clusters. Clicking a cluster zo ## Route lines -When you have a day selected, a dark dashed line connects consecutive places in that day's order. +A day's route is drawn as a solid blue line — a bright core over a darker casing, the look Apple Maps uses — through that day's stops in the order you arranged them. It is not on automatically: switch it on with the **Route** toggle on the day, in the day-plan sidebar on desktop or in the day sheet on mobile. The choice is remembered per trip in your browser, and the mobile map turns it on by default the first time you open a trip you have not decided on. + +A straight line is drawn immediately, then upgraded to real road geometry from a public OSRM router (or from a plugin route profile), each leg routed in the transport mode that leg carries. If routing fails, that leg stays a straight line between the two stops. ## GPX tracks @@ -42,7 +48,7 @@ Clicking a line on the map selects that track and opens its details — useful w ### Exporting a trip as GPX -The **GPX** button in the day sidebar's toolbar, next to PDF and ICS, hands the trip back as a `.gpx` file for offline maps such as Organic Maps, for a handheld GPS, or for any other tool that reads the format. On a phone the same download sits in the trip's **Export** sheet, under "More". +The **Export** button in the day sidebar's toolbar opens one dialog with every way a trip leaves TREK: the day plan as a PDF, the bookings as a calendar, and under **Maps & GPS · GPX** the trip as a `.gpx` file for offline maps such as Organic Maps, for a handheld GPS, or for any other tool that reads the format. On a phone the same downloads sit in the trip's **Export** sheet, under "More". Three scopes: @@ -52,20 +58,24 @@ Three scopes: Places carry their description and address, and their category travels along as the GPX symbol, so devices that support it can show a different icon per kind of stop. Elevation is written back for tracks that were imported with it. Exporting is a read, so every trip member can do it, not only those who may edit. -## Route time pills +## Travel times between stops -At zoom level 12 or higher, small pill-shaped labels appear between consecutive places and show the estimated **walking time** and **driving time** for each segment. Below zoom 12 they are hidden to keep the map clean. +Travel times are not drawn on the map — they sit in the day plan. Switch a day's route on and a slim connector row appears between each pair of consecutive stops with that leg's travel time and distance, and an icon for the mode it was routed in: a car for driving, a foot for walking, a bolt for a plugin route profile. On a phone the same rows sit in the day's plan timeline, where they are always shown and need no toggle. If the day has an accommodation and "optimize from accommodation" is on, two extra connectors bookend the day, naming the hotel with the drive out in the morning and back in the evening. + +Each leg carries its own mode, so a day routed by car can still have one leg you walk. If you may edit the day, clicking a connector opens the transport-mode menu for that leg alone. + +Car and foot times come from a public OSRM router, so they follow real roads and footpaths instead of straight-line estimates. A plugin route profile is answered by the plugin's own route provider instead, which is what lets it fold in things like charging stops; those legs can add a short note next to the distance. When routing is unavailable the leg falls back to a straight line and shows no time. ## Reservation and transport overlay -Flights, trains, cars, and cruises can be drawn as overlays between their endpoint places. Overlays are **off by default** — activate each reservation individually by clicking the small **Route** icon next to the booking row in the day sidebar, or use one of the bulk options below. The selection is remembered per trip in your browser. Click the icon again to hide it. +Flights, trains, cars, and cruises can be drawn as overlays between their endpoint places. Overlays are **off by default** — activate each reservation individually by clicking the small **Route** icon next to the booking row in the day sidebar, or use one of the bulk options below (automated public-transit journeys are the exception — see the transit bullet). The selection is remembered per trip in your browser. Click the icon again to hide it. -- **Flights and cruises** — geodesic great-circle arcs +- **Flights, cruises and ferries** — geodesic great-circle arcs - **Cars, buses, taxis and bicycles** — real routed lines that follow actual roads, fetched on demand from a public OSRM router (driving for car/bus/taxi, cycling for bicycle). A straight line is shown while the route loads and kept if routing fails or the trip is very long (~2000 km+) - **Trains** — a straight line between the endpoints; a multi-leg train draws its whole station chain (from → stop → to) +- **Automated public transit** — a journey added from the transit search draws its real rail and bus alignment instead of a straight line: each ride leg in its line's own colour over a white casing, walking transfers as a dotted grey line. A journey whose provider sent no shape falls back to a straight line. Unlike every other type it has no per-booking **Route** icon in the day sidebar — it is drawn when the day's own **Route** toggle is on and the journey runs on that day, and independently of that by the bulk button, the account-wide default, or the **On map** button in the journey's detail sheet on a phone. Because those are two separate gates, **hide all** does not clear a transit journey while that day's **Route** toggle is still on - **Antimeridian crossings** — routes that cross the date line now draw as one continuous arc instead of splitting into disconnected segments at the map edges - **Endpoint markers** — pill-shaped labels with the transport icon and the endpoint code (e.g. IATA airport code) or location name -- **Flight stats** — a floating label on the arc shows departure code → arrival code and, when times are available, the duration and great-circle distance. Stats labels are only rendered for flights. - **Confirmed reservations** — solid line; **Pending** — dashed line **Bulk options**, alongside the per-booking toggle: @@ -73,7 +83,7 @@ Flights, trains, cars, and cruises can be drawn as overlays between their endpoi - **Show all / hide all** — a route icon button in the day-plan toolbar (next to Undo/Reorder) flips every routable booking on the trip on or off at once, without affecting bookings you've toggled individually. - **Always show booking routes** (Settings → General → Travel & map) — an account-wide default that shows every booking's route automatically on any trip you haven't touched before. It sets the *default* only — a trip where you've already used the per-booking toggle or the bulk button keeps its own choice even if you change this setting afterwards. -> **Admin:** Whether endpoint text labels appear on the endpoint markers is controlled by the **Booking route labels** setting in Settings → General (`map_booking_labels`). +> **Tip:** Whether endpoint text labels appear on the endpoint markers is your own choice — the **Booking route labels** setting in Settings → General → Travel & map (`map_booking_labels`). It is off by default; with it off, the endpoint markers show only the transport icon. ## Plugin map markers @@ -99,6 +109,6 @@ If geolocation is denied or unavailable, the button turns red. Right-click anywhere on the **Leaflet** map to open the Place form with the clicked coordinates and a reverse-geocoded address already filled in. -On the **Mapbox GL** map, right-click is reserved for the built-in rotate/pitch gesture, so use **middle-click** instead to trigger the same Place form. +The **Mapbox GL** and **MapLibre GL** maps take the same right-click, and additionally **middle-click** and a **long-press** on touch. A right-button drag that rotates or pitches the map is not mistaken for a click, so the gesture and the shortcut coexist. **See also:** [Places-and-Search](Places-and-Search) · [Day-Plans-and-Notes](Day-Plans-and-Notes) · [Route-Optimization](Route-Optimization) · [Map-Settings](Map-Settings) · [Reservations-and-Bookings](Reservations-and-Bookings) diff --git a/wiki/Map-Settings.md b/wiki/Map-Settings.md index c5456c184..28f921b0b 100644 --- a/wiki/Map-Settings.md +++ b/wiki/Map-Settings.md @@ -72,7 +72,7 @@ You can also enter a custom `mapbox://styles/USER/ID` URL directly. ### 3D Buildings & Terrain -Enables pitch and building extrusions on all styles. Terrain elevation (DEM-based height) is additionally applied on satellite styles (`Satellite` and `Satellite Streets`). On non-satellite styles only building extrusions are added; terrain is intentionally omitted on those styles because the elevation data would cause route lines to visually drift away from the HTML place markers. +Enables pitch and 3D buildings on all styles. `Mapbox Standard` and `Standard Satellite` ship their own building volumes, so TREK adds no extrusion layer there; every other style gets one injected. Terrain elevation (DEM-based height) is additionally applied on `Satellite`, `Satellite Streets` and `Outdoors`; `Standard Satellite` brings its own terrain from Mapbox and is left alone. The remaining styles get buildings but no terrain — including plain `Mapbox Standard`, whose built-in terrain TREK switches off — because the elevation data would cause route lines to visually drift away from the HTML place markers. ### High Quality Mode *(Experimental)* diff --git a/wiki/My-Trips-Dashboard.md b/wiki/My-Trips-Dashboard.md index bf9030e9c..c6dd7a522 100644 --- a/wiki/My-Trips-Dashboard.md +++ b/wiki/My-Trips-Dashboard.md @@ -8,7 +8,7 @@ The dashboard at `/dashboard` is your home base — it lists all your trips, let Use the toggle button in the top toolbar to switch between **grid** (card thumbnails) and **list** (compact rows). Your preference is saved in `localStorage` under the key `trek_dashboard_view` and persists across sessions. -In grid mode the dashboard shows a large [Spotlight card](#spotlight-card) for your most relevant trip, with remaining trips in a responsive grid below. In list mode the Spotlight card is not shown separately — all trips (including the one that would be the spotlight) appear as uniform rows in the same sort order. +In both modes the dashboard shows a large [Spotlight card](#spotlight-card) for your most relevant trip at the top of the page — the toggle only changes how the trips below it are laid out. The spotlighted trip is taken out of that list when it earns the spot on its own: an ongoing trip, or the next upcoming one. If you have neither, the spotlight borrows your first trip for the header and that trip still shows up in the list below. ## Sort Order @@ -28,15 +28,17 @@ If you have no trips yet, the spotlight card is not shown. ## Archived Trips -Archived trips are hidden from the main list and collapsed into a separate **Archived** section at the bottom of the page. Click the section header to expand it. You can **Copy**, **Restore**, or permanently **Delete** an archived trip from the row actions. +Archived trips are kept out of the default list. The trips section header carries a **Planned / Archived / Completed** filter and the grid shows exactly one of those three sets — switch to **Archived** to see them. On desktop an archived card has the same four actions as any other card, with **Archive** turned into **Restore**. On mobile an archived card drops **Edit**: you can **Copy**, **Restore**, or permanently **Delete** the trip. -## Greeting (Mobile) +## Mobile Header -On mobile, the header shows a time-of-day greeting — "Good morning", "Good afternoon", or "Good evening" — along with your username and avatar. The greeting changes at 12:00 (noon) and 18:00. The mobile header also includes a **Notifications** button (bell icon) that navigates to `/notifications`. +On mobile, the header is a floating glass bar: the TREK logo on the left (tap it to scroll back to the top), a **Notifications** button (bell icon, with a dot when something is unread) that navigates to `/notifications`, and your avatar on the right. The avatar opens the user menu — settings, the admin panel for admins, the dark/light/auto theme cycle, and sign out. ## Dashboard Widgets Sidebar -On wide screens a sticky right column shows the **Currency Converter** and **Timezone Clock** widgets. Each can be toggled on or off via the Settings icon in the toolbar. On mobile, the widgets are available as a bottom sheet from the quick-action buttons at the top of the page. +On wide screens a sticky right column shows the **Currency**, **Collections** (only with the Collections addon enabled), **Timezones**, and **Upcoming reservations** widgets, plus any widget a plugin contributes. Each one is switched on or off separately for desktop and mobile under **Settings → Appearance → Dashboard widgets**; desktop also has a **Right sidebar** master toggle that removes the whole column, and the column disappears on its own once every widget in it is off. + +On mobile, the same widgets are inline panels below the spotlight card, not a bottom sheet. By default the trip list comes first and the widgets stack underneath it. How they stack — and where the trip list sits among them — is set on the phone under **Settings → Appearance → Mobile → Dashboard order**. See [Dashboard-Widgets](Dashboard-Widgets) for full usage details. @@ -51,11 +53,11 @@ On desktop, hover over a card (or open the row actions in list view) to reveal t | **Archive / Unarchive** | `trip_archive` on that trip | | **Delete** | `trip_delete` on that trip | -Actions not permitted for your role are hidden. Admins always see all actions. +Which actions appear depends on layout and archive state, not on your role — the dashboard draws the buttons without checking permissions. On mobile a grid card leaves **Archive** out (it lives in the edit sheet) while a list row includes it. The permissions above are enforced by the server: an action your role is not allowed to perform comes back as a 403 and you get an error message instead of the button being hidden. Permissions do take effect inside the trip dialog — without `trip_cover_upload` the cover-image panel is not shown, and without `trip_edit` the title, description, and currency fields are read-only. ## Empty State -When you have no trips, the dashboard shows an illustration and a **Plan your first trip** button that opens the [Creating-a-Trip](Creating-a-Trip) dialog. +When you have no trips, the dashboard shows the TREK mascot with the caption **No trips yet**. Start one from the **New Trip** button in the bottom-right corner or the dashed **New Trip** tile at the end of the grid; both open the [Creating-a-Trip](Creating-a-Trip) dialog. On mobile the empty state itself carries a **Create First Trip** button. ## Related Pages diff --git a/wiki/Notifications.md b/wiki/Notifications.md index 765f027c4..55533dedb 100644 --- a/wiki/Notifications.md +++ b/wiki/Notifications.md @@ -19,7 +19,7 @@ TREK ships four delivery channels, and a plugin can add more. Which channels app A plugin can register an **additional channel** — Gotify, Pushover, Telegram, anything that takes a message — by implementing the `notificationChannel` hook. See -[Plugin-Development](Plugin-Development.md#notification-channels). +[Plugin-Development](Plugin-Development#notification-channels). Once the admin installs the plugin and switches its channel on, it appears as a new column in the preferences matrix beside Email and In-App, and behaves like any other channel: each user @@ -43,8 +43,10 @@ The following events are configurable in user settings: | `trip_invite` | Someone invited you to a trip | | `booking_change` | A booking was added, updated, or removed in a trip you're part of | | `trip_reminder` | Reminder before a trip starts | +| `todo_due` | A to-do assigned to you, or in a trip you're part of, is due soon | | `vacay_invite` | You were invited to fuse vacation plans | | `vacay_share` | Someone shared their vacation calendar with you (view only) | +| `collection_invite` | Someone invited you to share a collection | | `photos_shared` | Photos were shared with a trip | | `collab_message` | A new message in a collaborative trip | | `packing_tagged` | You were assigned to a packing category in a trip | @@ -59,10 +61,11 @@ The following events are shown in the admin panel (Admin → Notifications) and | Event | Description | Channels | |-------|-------------|---------| | `version_available` | A new TREK version is available | in-app, email, webhook, ntfy | +| `replica_failure` | A write to a storage replica failed (repeats within the hour are suppressed and counted) | in-app, email, webhook, ntfy | -### System-only events +### In-app-only events -The following events are fired automatically and are not exposed as toggles in any settings panel: +The following event is fired automatically and can only be delivered in-app — it appears in the preferences matrix with an In-app toggle and a dash in every other column: | Event | Description | Channels | |-------|-------------|---------| diff --git a/wiki/OIDC-SSO.md b/wiki/OIDC-SSO.md index 69413bb0b..135515dc8 100644 --- a/wiki/OIDC-SSO.md +++ b/wiki/OIDC-SSO.md @@ -15,13 +15,23 @@ OpenID Connect (OIDC) lets users log in with an existing identity provider — G 5. The server issues a short-lived one-time code and redirects your browser to `/login?oidc_code=`. The frontend immediately exchanges that code at `GET /api/auth/oidc/exchange?code=` to obtain the session. 6. Your `trek_session` cookie is set and you land on the dashboard. +**Remember me.** The Remember-me switch on the login page is carried into SSO as a query flag on the login URL: `GET /api/auth/oidc/login?remember=1` (or `remember=0`), appended with `&` when an invite token is already present. Only `0` and `1` are accepted; any other value — and starting SSO from the register tab, where the switch is not shown — is treated as if the parameter were absent. The choice is held in the server-side login state, survives the provider round-trip and the one-time-code exchange, and decides both the session lifetime and the cookie lifetime: + +| `remember` | Session lifetime | `trek_session` cookie | +|---|---|---| +| `1` | `SESSION_DURATION_REMEMBER` (default `30d`) | persistent, `maxAge` matches | +| `0` | `SESSION_DURATION` (default `24h`) | browser-session cookie, cleared when the browser closes | +| omitted | `SESSION_DURATION` (default `24h`) | persistent, `maxAge` matches | + +If SSO sessions are dying at browser close, the login link is sending `remember=0`. + ## Prerequisites Set the following environment variables before starting the server: | Variable | Required | Description | |---|---|---| -| `APP_URL` | Yes | Base URL of your TREK instance (e.g. `https://trek.example.com`). Used to build the redirect URI. Can also be set via Admin → Settings. | +| `APP_URL` | Yes | Base URL of your TREK instance (e.g. `https://trek.example.com`). Used to build the redirect URI. **Env var only — not configurable via the admin panel.** If it is unset (or not a parseable URL, in which case it is ignored), TREK falls back to the first `ALLOWED_ORIGINS` entry, and only if that one is missing or unparseable to `http://localhost:{PORT}` — which produces a redirect URI your IdP will reject. A scheme-less `ALLOWED_ORIGINS=trek.example.com` does not parse, so it ends up on localhost too. | | `OIDC_ISSUER` | Yes | Issuer URL of your identity provider. Must use HTTPS in production. | | `OIDC_CLIENT_ID` | Yes | OAuth 2.0 client ID registered with your IdP. | | `OIDC_CLIENT_SECRET` | Yes | OAuth 2.0 client secret. | @@ -47,7 +57,7 @@ For example: `https://trek.example.com/api/auth/oidc/callback` ## New-user registration via SSO -When an SSO login matches an existing TREK account by email or OIDC subject (`sub`), that account is used and the OIDC identity is linked automatically. If no matching account exists, TREK attempts to create one. The outcome depends on the following: +When an SSO login matches an existing TREK account by OIDC subject (`sub`), that account is used directly. When it matches only by **email**, the OIDC identity is linked to that account only if the provider asserts `email_verified` for it; if the claim is missing or false the login is rejected with an `email_not_verified` error, so an unverified address can never take over a local account. Make sure your IdP includes `email_verified` in the userinfo response — it is part of the `email` scope. If no matching account exists, TREK attempts to create one. The outcome depends on the following: - **First user ever**: always created as admin, no invite required. - **Open SSO registration enabled** (admin panel toggle `oidc_registration`): account is created as a regular user. @@ -56,7 +66,7 @@ When an SSO login matches an existing TREK account by email or OIDC subject (`su ## Admin panel (runtime configuration) -OIDC can also be configured without environment variables via **Admin → SSO**. The following fields are settable at runtime: +OIDC can also be configured without environment variables via **Admin → Settings**, in the **Single Sign-On (OIDC)** card. The following fields are settable at runtime: | Field | Env var equivalent | |---|---| diff --git a/wiki/Offline-Mode-and-PWA.md b/wiki/Offline-Mode-and-PWA.md index 373458aad..54cace816 100644 --- a/wiki/Offline-Mode-and-PWA.md +++ b/wiki/Offline-Mode-and-PWA.md @@ -29,8 +29,9 @@ TREK uses Workbox service-worker caching plus an IndexedDB database (Dexie) for | Content | Cache name | Strategy | Duration | Max entries | |---------|------------|----------|----------|-------------| | CartoDB / OpenStreetMap map tiles | `map-tiles` | CacheFirst | 30 days | 12 288 | -| Mapbox GL style, glyphs, sprites and vector tiles | `mapbox-tiles` | StaleWhileRevalidate | 30 days | 3 000 | -| OpenFreeMap MapLibre style and tiles | `openfreemap-tiles` | StaleWhileRevalidate | 30 days | 3 000 | +| Mapbox GL and OpenFreeMap style documents | `gl-map-styles` | NetworkFirst (5 s timeout) | 30 days | 20 | +| Mapbox GL glyphs, sprites and vector tiles | `mapbox-tiles` | StaleWhileRevalidate | 30 days | 3 000 | +| OpenFreeMap MapLibre glyphs, sprites and vector tiles | `openfreemap-tiles` | StaleWhileRevalidate | 30 days | 3 000 | | Cover images and avatars (`/uploads/covers`, `/uploads/avatars`) | `user-uploads` | CacheFirst | 7 days | 300 | | App shell and every page of the app (HTML / JS / CSS) | precache | Precached | Until next deploy | — | @@ -43,8 +44,8 @@ TREK uses Workbox service-worker caching plus an IndexedDB database (Dexie) for On login, after each trip-list refresh, and on WebSocket reconnect, TREK runs a background sync that writes full trip bundles into IndexedDB: - Trips, days, places, packing items, to-dos, budget items, reservations, accommodations, trip members, tags, and categories. -- Non-photo file attachments (PDFs, documents, etc.) are downloaded and stored as blobs in IndexedDB. -- Map tiles are pre-fetched into the service-worker `map-tiles` cache for zoom levels 10–16 across each trip's bounding box (capped at ~50 MB of tiles per sync). +- File attachments that are neither photos nor videos (PDFs, documents, etc.) are downloaded and stored as blobs in IndexedDB. Videos are deliberately skipped — a single clip can be hundreds of megabytes and would evict the trip's real documents. +- Map tiles are pre-fetched into the service-worker `map-tiles` cache for zoom levels 10–16 across each trip's bounding box, stopping at the zoom level that would push the total past 12 288 tiles (roughly 180 MB). **Sync scope and eviction** @@ -82,7 +83,7 @@ The stats panel shows cached trips, pending changes, conflicts and failed change - A change you made offline that **deletes** an item wins over a concurrent server edit of that same item ("delete wins"); only edit-vs-edit conflicts are surfaced for resolution. - The conflict token has one-second resolution, so two edits to the same field within the same second can't be told apart and fall back to last-write-wins (only relevant to sub-second races; normal offline windows are unaffected). - New trips created while offline are queued and synced when connectivity is restored. -- Photo uploads require connectivity; non-photo file attachments are pre-cached automatically during sync. +- Photo uploads require connectivity. Photo and video attachments are not pre-cached; every other file attachment is pre-cached automatically during sync. - Real-time collaboration features require an active WebSocket connection. - Mapbox GL / vector tiles are not pre-downloaded; raster (Leaflet) tiles are. With map-tile storage off, individually viewed tiles may still be cached opportunistically by the service worker. diff --git a/wiki/PDF-Export.md b/wiki/PDF-Export.md index 971a7a3ca..21f6376aa 100644 --- a/wiki/PDF-Export.md +++ b/wiki/PDF-Export.md @@ -1,6 +1,6 @@ # PDF Export -TREK can generate two kinds of PDFs from your trip data: a structured **Trip Plan PDF** and a photo-book-style **Journey Book PDF**. Both render as HTML in a sandboxed iframe and open the browser's native print/save dialog — no server-side processing is involved. +TREK generates a structured **Trip Plan PDF** from your trip data. It renders as HTML in a sandboxed iframe and opens the browser's native print/save dialog — no server-side processing is involved. Journey entries no longer go through a fixed PDF template: they are laid out in **TREK Studio** and printed through the same browser mechanism (see below). ![PDF Export](assets/PDFTrip.png) @@ -10,7 +10,7 @@ TREK can generate two kinds of PDFs from your trip data: a structured **Trip Pla ### How to generate -Open the Day Plan sidebar in the trip planner. Click the **PDF** button in the toolbar at the top of the sidebar. A preview modal opens immediately; click **Save as PDF** to open your browser's print dialog and save the file. +Open the Day Plan sidebar in the trip planner and click the **Export** button in the toolbar at the top of the sidebar. The Export dialog opens with three groups — **Document**, **Calendar**, and **Maps & GPS**. Pick the PDF row under **Document**: a preview modal opens, and **Save as PDF** hands the document to your browser's print dialog. ### Cover page @@ -21,11 +21,13 @@ Open the Day Plan sidebar in the trip planner. Click the **PDF** button in the t - **Days** — total number of days in the trip - **Places** — total places in your trip's place list - **Planned** — number of unique places assigned to at least one day - - **Estimated cost** — sum of all assigned place prices, shown in the trip's currency (hidden if zero) + - **Cost** — sum of all assigned place prices, shown in the trip's currency (hidden if zero). Mixed currencies are converted at current rates and prefixed with "≈"; when a rate is missing, the tile shows a per-currency breakdown instead ### Per-day pages -Each day starts on a new page with a dark header bar showing the day number, day title, date, and the day's estimated cost. +Each day starts on a new page (unless you turn off **Page break per day**) with a dark header bar showing the day number, day title, date, and the day's estimated cost. + +The **Page break per day** toggle sits in the preview modal's header, next to Save as PDF. It is on by default; turn it off and the days flow into each other instead, which is worth doing on a trip of short days that would otherwise print one sheet per handful of lines. The choice is remembered in that browser for the next export. Below the header: @@ -51,47 +53,19 @@ Installed plugins can append their own sections to the Trip Plan PDF via the `pd --- -## Journey Book PDF +## Journey photo books -### How to generate +The Travel Journal has no fixed-template PDF export any more. Open a Journey entry and click **Studio** in the journal header (the book icon in the top bar on phones) to open **TREK Studio**, the photo-book designer. -Open a Journey entry in the Travel Journal. Click the **download icon** button in the journal's header area. A preview modal opens; click **Save as PDF** to print. +Studio lays the journey out as editable spreads instead of a fixed page template: five page presets (210 mm and 300 mm square, A4 landscape, A4 portrait, A5 landscape) or a custom size between 60 and 500 mm, and seven bundled font families. -![Journey Book PDF Preview](assets/PDFJourney.png) - -### Format - -A4 landscape (`@page { size: A4 landscape; margin: 0 }`). Font: Inter, loaded from Google Fonts. - -### Cover page - -- Hero image (journey cover image, or the first entry photo if none is set) -- Journey title and optional subtitle -- Stat tiles: Days, Entries, Photos - -### Entry pages - -One page per journal entry, in chronological order. The first entry of each date carries a day header (day number and full date) above the content. - -Photo layout adapts to the number of photos on the entry: - -| Photos | Layout | -|--------|--------| -| 1 | Single image, full width | -| 2 | Two images side by side | -| 3 or more | Large hero image on the left, two stacked images on the right | - -Below the photos: entry time and location, entry title, journal text (rendered from Markdown), and pros/cons verdict cards if present. - -### Closing page - -A dark "The End" card. +Printing works the same way as the Trip Plan PDF — the sheets are written into a sandboxed `srcdoc` iframe and handed to the browser's print dialog, so nothing is rendered on the server. A browser writes no TrimBox or BleedBox, so the sheets can carry crop marks for a print shop instead. **Single pages** or **Spreads**, and crop marks on or off, are chosen in Studio's export panel. --- ## How rendering works -Both PDFs use the same mechanism: the HTML document is written into a sandboxed `