docs(wiki): the pictures that were missing, and the follow-ups of the rewrite

The wiki pictures of the trip are taken again with the fixtures the help
guides use, so the Bookings tab shows a booking of every kind, Files has
documents and What's Next has something ahead. New pictures: the map's
hover card, the Subscribe to calendar dialog, the PDF preview, the To-Do
view, and the Transports tab's own timeline and booking detail. Day
selection in the screenshot run clicks the day's number, now that the
middle of a day header is the booked night.

Home, Real-Time Collaboration, Road Trip, Map Features and the bookings
page are brought in line with the rewritten pages, and sixteen assets no
page uses any more (mostly old GIFs, 22 MB) are removed.
This commit is contained in:
Maurice
2026-09-28 12:19:49 +02:00
parent 146fe4a063
commit af9710c618
73 changed files with 199 additions and 53 deletions
+4
View File
@@ -1,4 +1,5 @@
import { test, clearNotices, expect, seed } from './shot'
import { PICTURE_DAY } from '../help/guide'
import type { Page, Locator } from '@playwright/test'
/**
@@ -31,6 +32,9 @@ function card(page: Page, contains: string): Locator {
}
test.beforeEach(async ({ page }) => {
// What's Next lists only what is still ahead, and shots.setup.ts timed its stop
// against the picture day's morning, the instant the help pictures are taken at.
await page.clock.setFixedTime(PICTURE_DAY)
await page.goto(`/trips/${seed.tripId}`)
await clearNotices(page)
await page.getByRole('button', { name: 'Collab', exact: true }).first().click()
+6 -2
View File
@@ -26,7 +26,8 @@ test('trip planner', async ({ page, shot }) => {
await clearNotices(page)
// The seeded trip is running, so the plan opens on today, which is empty; the
// picture shows the first days, where the plan has places, a note and a flight.
await page.locator('.dp-day-header').first().click()
// On the day's number: the middle of the header is the booked night's pill.
await page.locator('.dp-day-header').first().click({ position: { x: 22, y: 20 } })
await page.waitForTimeout(1500)
await page.locator('.dp-day-header').first().evaluate(el => el.closest('.overflow-y-auto')?.scrollTo({ top: 0 }))
await page.waitForTimeout(600)
@@ -64,6 +65,9 @@ test('in-app help', async ({ page, shot }) => {
})
test('files', async ({ page, shot }) => {
await page.goto(`/trips/${seed.tripId}/files`)
await page.goto(`/trips/${seed.tripId}`)
await clearNotices(page)
await page.getByRole('button', { name: /^Files/ }).first().click()
await page.waitForTimeout(1200)
await shot.page_('Files')
})
+105 -23
View File
@@ -1,5 +1,6 @@
import type { Page } from '@playwright/test'
import { test, clearNotices, expect, seed } from './shot'
import { TOKYO_HOTEL } from '../help/fixtures'
/**
* The Plan tab and the dialogs of the trip page, as the wiki pages for the day
@@ -23,10 +24,19 @@ async function openTab(page: Page, label: string) {
await page.waitForTimeout(800)
}
async function selectFirstDay(page: Page) {
await page.locator('.dp-day-header').first().click()
// On the day's number at the left edge: the middle of the header is the booked
// night's pill once the day has a stay, and a click there opens the hotel instead.
async function selectDay(page: Page, index: number) {
await page.locator('.dp-day-header').nth(index).click({ position: { x: 22, y: 20 } })
await page.waitForTimeout(900)
}
const selectFirstDay = (page: Page) => selectDay(page, 0)
/** Out of the way, so no tooltip of the button just clicked stays in the picture. */
const parkPointer = (page: Page) => page.mouse.move(1, 1)
/** A row of the places column, by the place's name. */
const placeOption = (page: Page, name: string) => page.getByRole('option', { name: new RegExp(`^${name}`) }).first()
test('plan: a day card', async ({ page, shot }) => {
await shot.element('PlanDayCard', firstDayCard(page))
@@ -52,13 +62,13 @@ test('plan: the places column', async ({ page, shot }) => {
})
test('plan: the place inspector', async ({ page, shot }) => {
await page.getByRole('option').first().click()
await placeOption(page, 'Senso-ji Temple').click()
await page.waitForTimeout(1200)
await shot.page_('PlaceInspector')
})
test('plan: the edit place dialog', async ({ page, shot }) => {
await page.getByRole('option').first().click()
await placeOption(page, 'Senso-ji Temple').click()
await page.waitForTimeout(1000)
await page.getByRole('button', { name: 'Edit', exact: true }).first().click()
await expect(dialog(page)).toBeVisible()
@@ -72,18 +82,42 @@ test('plan: the day details', async ({ page, shot }) => {
})
test('plan: the stay editor', async ({ page, shot }) => {
await selectFirstDay(page)
const edit = page.getByRole('button', { name: /^(Edit accommodation|Add accommodation)$/ }).first()
await edit.click()
// Day 9 has no stay yet, so its details offer Add accommodation.
await selectDay(page, 8)
await page.getByRole('button', { name: 'Add accommodation' }).first().click()
await expect(dialog(page)).toBeVisible()
// A hotel picked, so the head band carries its name.
await dialog(page).getByText('Hotel Granvia Kyoto', { exact: true }).click()
await page.waitForTimeout(500)
await shot.element('StayEditor', dialog(page))
})
test('plan: the map hover card', async ({ page, shot }) => {
const marker = page.locator('.maplibregl-marker, .mapboxgl-marker, .leaflet-marker-icon').first()
await marker.hover()
await expect(page.getByTestId('tooltip')).toBeVisible()
await shot.page_('MapHoverCard')
// With no day open every stop is folded into a cluster; the open day's stops are
// pins. Its details panel stays: closing it would close the day as well.
await selectFirstDay(page)
// A place pin, not a cluster bubble or an explore result (only those carry a
// name). The columns float over the map's edges and pins stack, so the first
// one in the DOM is often one the pointer cannot reach.
const pins = page.locator('#trek-map .leaflet-marker-icon:not(:has(.marker-cluster-custom)):not([aria-label])')
await expect(pins.first()).toBeAttached()
const total = await pins.count()
for (let i = 0; i < total; i++) {
if (await pins.nth(i).hover({ trial: true, timeout: 1500 }).then(() => true, () => false)) {
await pins.nth(i).hover()
break
}
}
const card = page.getByTestId('tooltip')
await expect(card).toBeVisible()
await page.waitForTimeout(600)
// The card and the pin it belongs to with some map around them, not the whole planner.
const box = (await card.boundingBox())!
const width = 760
const height = 440
const x = Math.max(0, Math.min(1440 - width, box.x + box.width / 2 - width / 2))
const y = Math.max(0, Math.min(900 - height, box.y + box.height / 2 - height / 2))
await shot.region('MapHoverCard', { x, y, width, height })
})
test('plan: the export dialog', async ({ page, shot }) => {
@@ -92,30 +126,77 @@ test('plan: the export dialog', async ({ page, shot }) => {
await shot.element('ExportDialog', dialog(page))
})
// The demo trip books only its flight, so the booking views are shown on the Transports tab.
test('plan: the subscribe to calendar dialog', async ({ page, shot }) => {
await page.getByRole('button', { name: 'Export' }).first().click()
await dialog(page).getByRole('button', { name: /^Subscribe to calendar/ }).click()
const subscribe = page.getByRole('dialog').filter({ hasText: 'Subscribe to calendar' }).last()
await subscribe.getByRole('button', { name: 'Enable calendar subscription' }).click()
// Enabled, the dialog shows the ways to subscribe and its Regenerate and Turn off.
await expect(subscribe.getByRole('button', { name: 'Turn off' })).toBeVisible()
await shot.element('IcsSubscribe', subscribe)
await subscribe.getByRole('button', { name: 'Turn off' }).click()
})
test('plan: the PDF preview', async ({ page, shot }) => {
await page.getByRole('button', { name: 'Export' }).first().click()
await dialog(page).getByRole('button', { name: /^PDF/ }).click()
const preview = page.locator('#pdf-preview-overlay > div')
await expect(preview).toBeVisible({ timeout: 30_000 })
await parkPointer(page)
// The document is a srcdoc frame; its photos and the route map load after it.
await page.waitForTimeout(4000)
await shot.element('PDFPreview', preview)
await page.locator('#pdf-close-btn').click()
})
// The Bookings tab holds a booking of every kind here (shots.setup.ts), so the
// views are shown on it; each test puts the view back to the cards.
async function bookingsView(page: Page, view: 'Cards' | 'List' | 'Timeline') {
await openTab(page, 'Book')
await page.getByRole('button', { name: view, exact: true }).first().click()
await parkPointer(page)
await page.waitForTimeout(800)
}
test('bookings: list view', async ({ page, shot }) => {
await openTab(page, 'Transports')
await page.getByRole('button', { name: 'List', exact: true }).first().click()
await page.waitForTimeout(600)
await bookingsView(page, 'List')
await shot.page_('BookingsList')
await page.getByRole('button', { name: 'Cards', exact: true }).first().click()
})
test('bookings: timeline view', async ({ page, shot }) => {
await bookingsView(page, 'Timeline')
await shot.page_('BookingsTimeline')
await page.getByRole('button', { name: 'Cards', exact: true }).first().click()
})
test('bookings: the detail popup and the editor', async ({ page, shot }) => {
await bookingsView(page, 'Cards')
await page.locator('article').filter({ hasText: TOKYO_HOTEL }).first().click()
await expect(dialog(page)).toBeVisible()
await page.waitForTimeout(600)
await shot.element('BookingDetail', dialog(page))
await dialog(page).getByRole('button', { name: 'Edit', exact: true }).click()
await page.waitForTimeout(800)
await shot.element('BookingEditor', dialog(page))
})
test('transports: timeline view', async ({ page, shot }) => {
await openTab(page, 'Transports')
await page.getByRole('button', { name: 'Timeline', exact: true }).first().click()
await parkPointer(page)
await page.waitForTimeout(800)
await shot.page_('BookingsTimeline')
await shot.page_('TransportsTimeline')
await page.getByRole('button', { name: 'Cards', exact: true }).first().click()
})
test('bookings: the detail popup and the editor', async ({ page, shot }) => {
test('transports: the detail popup', async ({ page, shot }) => {
await openTab(page, 'Transports')
await page.getByRole('button', { name: 'Cards', exact: true }).first().click().catch(() => {})
await page.locator('article').first().click()
await page.getByRole('button', { name: 'Cards', exact: true }).first().click()
await page.locator('article').filter({ hasText: 'LH716' }).first().click()
await expect(dialog(page)).toBeVisible()
await shot.element('BookingDetail', dialog(page))
await dialog(page).getByRole('button', { name: 'Edit', exact: true }).click()
await page.waitForTimeout(800)
await shot.element('BookingEditor', dialog(page))
await page.waitForTimeout(600)
await shot.element('TransportDetail', dialog(page))
})
test('transports: the add transport dialog', async ({ page, shot }) => {
@@ -136,6 +217,7 @@ test('costs: the table view', async ({ page, shot }) => {
await openTab(page, 'Costs')
await page.getByRole('button', { name: 'Table', exact: true }).click()
await expect(page.getByRole('table')).toBeVisible()
await parkPointer(page)
await shot.page_('CostsTable')
// Back to the list for every other picture of the tab.
await page.getByRole('button', { name: 'List', exact: true }).click()
+10
View File
@@ -30,6 +30,16 @@ test('lists — packing', async ({ page, shot }) => {
await shot.page_('PackingList')
})
test('lists: to-do with a task open', async ({ page, shot }) => {
await openTab(page, 'Lists')
await page.getByRole('button', { name: /^To-Do/ }).first().click()
await page.waitForTimeout(600)
await page.getByText('Activate JR Pass', { exact: true }).first().click()
await page.waitForTimeout(700)
await shot.page_('Todos')
await page.getByRole('button', { name: /^Packing List/ }).first().click()
})
test('transports', async ({ page, shot }) => {
await openTab(page, 'Transports')
await shot.page_('Transports')
+6
View File
@@ -83,6 +83,12 @@ export class Shot {
await this.page.screenshot({ path: path.join(OUT_DIR, `${name}.png`) })
}
/** Capture a region of the page, for a detail with no element of its own (a card over the map). */
async region(name: string, clip: { x: number; y: number; width: number; height: number }): Promise<void> {
await this.settle()
await this.page.screenshot({ path: path.join(OUT_DIR, `${name}.png`), clip })
}
/** Capture one element — preferred for dialogs, panels and cards. */
async element(name: string, target: Locator): Promise<void> {
await this.settle()
+22
View File
@@ -0,0 +1,22 @@
import { test as setup } from '@playwright/test'
import {
ensureBookingsFixtures, ensureCollabFixtures, ensureDayDetailFixtures, ensureFilesFixtures,
ensureListsFixtures, ensureMapFixtures, ensureTransportFixtures,
} from '../help/fixtures'
/**
* The demo trip as the wiki pictures show it: the seed plus the fixtures the help
* guides add, so the Bookings tab holds a booking of every kind, Files has
* documents, Collab has links and What's Next something ahead, and the days carry
* their stays and transports. Its own project between the seed and the capture:
* the help-media run adds these per guide and has to start from the bare seed.
*/
setup('fill the demo trip for the wiki pictures', async ({ page }) => {
await ensureDayDetailFixtures(page.request)
await ensureBookingsFixtures(page.request)
await ensureTransportFixtures(page.request)
await ensureMapFixtures(page.request)
await ensureFilesFixtures(page.request)
await ensureListsFixtures(page.request)
await ensureCollabFixtures(page.request)
})
+10 -1
View File
@@ -74,6 +74,15 @@ export default defineConfig({
use: { ...devices['Desktop Chrome'], storageState: E2E_STATE_FILE },
dependencies: ['setup'],
},
// The wiki pictures show the demo trip filled in with the fixtures the help
// guides use (a booking of every kind, documents, links); help-media adds
// them guide by guide and so starts from the bare seed.
{
name: 'shot-fixtures',
testMatch: /shots\.setup\.ts/,
use: { ...devices['Desktop Chrome'], storageState: E2E_STATE_FILE },
dependencies: ['seed'],
},
{
name: 'screenshots',
testMatch: /\.shot\.ts/,
@@ -83,7 +92,7 @@ export default defineConfig({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
},
dependencies: ['seed'],
dependencies: ['shot-fixtures'],
},
// Help-center media (`npm run help:media`): pictures and walkthroughs for
// the in-app guides, generated from the guide definitions in src/help/.
+2
View File
@@ -36,6 +36,8 @@ The all-trips feed merges every trip that qualifies into one calendar, sorted by
- **Add to Apple Calendar / Outlook** is a `webcal://` link that your system passes to its default calendar app.
- **Or copy a link manually** unfolds both addresses with a copy button each: the `https://` one for a *From URL* field, and the `webcal://` one.
![The Subscribe to calendar dialog once the feed is on: Add to Google Calendar, Add to Apple Calendar / Outlook and the manual links, with Regenerate and Turn off at its foot](assets/IcsSubscribe.png)
The address is built from `APP_URL` when it is set; otherwise TREK uses the host you are browsing from. Behind a reverse proxy, set `APP_URL` so the link is one your calendar app can reach. See [Environment-Variables](Environment-Variables) and [Reverse-Proxy](Reverse-Proxy).
## The token, and who can read the feed
+1 -1
View File
@@ -2,7 +2,7 @@
Talk with everyone on the trip in real time, without leaving the planner.
![The trip chat: your own messages in the accent colour on the right, the others as cards on the left, with reactions, a reply and a link preview](assets/CollabChat.png)
![The trip chat: your own messages in the accent colour on the right, the others as cards on the left with their name and time, and the message field at the foot](assets/CollabChat.png)
## Where to find it
+1 -1
View File
@@ -2,7 +2,7 @@
Let the group decide together: where to eat, which hotel to book, what to do on the free afternoon.
![The Polls card in the Collab tab: open polls as cards with the question in the head band, the options as bars, and a closed poll below](assets/CollabPolls.png)
![The Polls card in the Collab tab: each poll as a card with the question and its votes in the head band, the lock and the bin beside them, and the options below](assets/CollabPolls.png)
## Where to find it
+1 -1
View File
@@ -2,7 +2,7 @@
The days column on the left of the Plan tab holds the itinerary: one card per day, with the places, bookings, transports and notes of that day in order.
![An expanded day card: the head band with the day number, the forecast, the date and the stay, cost and rental car pills, then the places, a transport row and a note](assets/PlanDayCard.png)
![An expanded day card: the head band with the day number, the forecast, the date and the stay and cost pills, then a note, the places and the bookings of the day](assets/PlanDayCard.png)
## The Day Plan sidebar
+1 -1
View File
@@ -2,7 +2,7 @@
Keep tickets, confirmations, vouchers and photos with the trip, and tie each one to the places and bookings it belongs to.
![The Files tab: the bar with the type filters, Document sync and the trash, the upload area and the list of files with their links to places and bookings](assets/Files.png)
![The Files tab: the bar with the type filters and the trash, the upload area and the list of files, each with its actions](assets/Files.png)
## Where to find it
+4 -4
View File
@@ -10,13 +10,13 @@ TREK is a self-hosted, real-time collaborative travel planner licensed under AGP
- **Drag & Drop Planner**: organize places into day plans with reordering and cross-day moves
- **Interactive Map**: Leaflet map with photo markers, clustering, route visualization, and customizable tile sources
- **Place Search**: TREK's own place index and OpenStreetMap, asked together, with no API key and no quota; Google or Amap answer only where both come up empty (see [TREK Places API](TREK-Places-API))
- **Day Notes**: timestamped, icon-tagged notes per day
- **Route Optimization**: auto-optimize place order and export to Google Maps
- **Day Notes**: notes between a day's stops, each with a title, Markdown text, an icon and a colour
- **Route Optimization**: auto-optimize place order and hand the day to Google Maps or CoMaps
- **Weather Forecasts**: 16-day forecasts via Open-Meteo (no API key required), historical climate averages as fallback
### Travel Management
- **Reservations & Bookings**: track flights, accommodations, restaurants with confirmation numbers and file attachments
- **Budget Tracking**: category-based expenses with pie chart, per-person/per-day splitting, multi-currency support (see [Currencies](Currencies))
- **Reservations & Bookings**: hotels, restaurants, tickets and every other booking with its confirmation code, travelers, costs and files, shown as cards, as a list or on a timeline; flights, trains and cars have their own Transports tab
- **Budget Tracking**: an expense ledger with who paid and itemised splits, settle-up, receipts, a table view with per person and per day figures, multi-currency support (see [Currencies](Currencies))
- **Packing Lists**: category-based checklists with user assignment, templates, and progress tracking
- **Document Manager**: attach documents, tickets, and PDFs to trips, places, or reservations (up to 50 MB per file)
- **Document Sync** _(admin-enabled)_: keep a trip's documents in step with a self-hosted Paperless-ngx, Papra, Nextcloud, OpenCloud or Synology NAS, in both directions, under one connection the trip owner sets up for every member (see [Document Sync](Document-Sync))
+4 -2
View File
@@ -24,7 +24,7 @@ Each place is a round marker:
- **Photo marker:** the place's photo fills the circle, your own uploaded picture first, otherwise the photo TREK found for it.
- **Icon marker:** without a photo, the category's icon in the category's colour.
- **Selected place:** the active place has a larger marker.
- **Order badge:** a small badge at the bottom right shows the place's position in the day's plan. A place on several days shows all its positions, separated by `·`.
- **Order badge:** while a day is open, a small badge at the bottom right shows the stop's position in that day's plan, and a place that comes up twice in the day carries both numbers. Without an open day the badge shows the place's rating instead, where it has one.
- **Rating:** a place that members rated and that carries no order badge shows its average as a small disc in its corner.
Click a marker to open the [place inspector](Places-and-Search#the-place-inspector).
@@ -37,6 +37,8 @@ In the [Road trip](Road-Trip) view, results of the search along the route group
Rest the pointer on a place marker and a card follows the cursor with the place's photo (or its category tile), its name, the average member rating, the category and the address. It is the same card on every renderer and in [Collections](Collections), and it never gets in the way of a click.
![The hover card beside a place marker: the place's photo, its name and its address](assets/MapHoverCard.png)
Markers that [plugins](Places-and-Search#categories-from-plugins) put on the map show their own name and up to six rows of the plugin's facts instead.
## Route lines
@@ -47,7 +49,7 @@ A straight line is drawn immediately, then upgraded to real road geometry from a
### The whole trip at once
The **Show whole trip** button in the bottom-right corner of the map swaps the single day for every travel day of the trip, each drawn in its own colour over a white casing so neighbouring days stay apart on any basemap. It is on desktop and on the phone, and your choice is remembered per trip for the rest of the session. **Hide whole trip** goes back to the single day.
The **Show whole trip** button in the bottom-right corner of the map swaps the single day for every travel day of the trip, each drawn in its own colour over a darker casing of the same colour, so neighbouring days stay apart on any basemap. It is on desktop and on the phone, and your choice is remembered per trip for the rest of the session. **Hide whole trip** goes back to the single day.
A card above the button lists the days: each one by its title, or by its number when it has none, with an icon per travel mode it is actually driven or walked in and the distance covered that day. The trip's **Total distance** sits at the top. Picking a day in the list selects it, the same as picking it anywhere else.
+2
View File
@@ -12,6 +12,8 @@ TREK turns a trip into a printable **Trip Plan PDF**: a cover, a map of the whol
2. Under **Document**, click **PDF** (*Export day plan as PDF*). The row shows a spinner while the document is put together, then the dialog closes and a preview opens.
3. In the preview, click **Save as PDF**. Your browser's print dialog opens; pick *Save as PDF* as the destination, or send it to a printer.
![The PDF preview: the trip's name, Page break per day and Save as PDF in its head, and the cover page of the document below](assets/PDFPreview.png)
![Export dialog with the PDF under Document, Download .ics and Subscribe to calendar under Calendar, and the three GPX downloads under Maps & GPS](assets/ExportDialog.png)
The other groups of the same dialog are covered elsewhere: **Calendar** in [Calendar-Feeds](Calendar-Feeds), and the GPX downloads under **Maps & GPS** (*Whole trip*, *Places only*, *Days as routes*) in [Map-Features](Map-Features#exporting-a-trip-as-gpx).
+1 -1
View File
@@ -2,7 +2,7 @@
Build the trip's packing list together: items grouped into lists, ticked off as they go into the bag, shared with the group or kept to yourself, and, if you like, weighed per bag.
![The Packing List in the Lists tab: the Lists bar, the progress card, the Shared and My list switch with the filters, the lists as cards and the Bags card on the right](assets/PackingList.png)
![The Packing List in the Lists tab: the Lists bar, the progress card, the Shared and My list switch with the filters, and the lists as cards](assets/PackingList.png)
## Where to find it
+2 -2
View File
@@ -2,7 +2,7 @@
Places are the building blocks of a trip: sights, restaurants, hotels, stations, trailheads. You add them by searching, by pasting a map link, by typing coordinates, from your saved places, by right-clicking the map or by importing a file or a list.
![The places column: the head band with Add Place/Activity and the import button, the search field with the select button, the Show, category and rating filters, and the list of places](assets/PlacesColumn.png)
![The places column while a day is open: the head band with New place, To day and the import button, the search field with the select button, the Show, category and rating filters, and the list of places](assets/PlacesColumn.png)
## The places column
@@ -229,7 +229,7 @@ Amap links work the same way, as long as the link carries a coordinate: `uri.ama
Click a place in the places column, in a day, or on the map, and its inspector opens over the map.
![The place inspector: the photo and name in the head band with the address and the open, category and rating pills, then the rating, a booking card, opening hours and files](assets/PlaceInspector.png)
![The place inspector over the map: the photo, name and address in its head, then the rating, the description, the notes and the files, with Add to Day, Navigation, Save to Collection and Edit at its foot](assets/PlaceInspector.png)
- **The head band** shows the photo, with a green ring while the place is open and a red one while it is closed, the name (double-click it to rename the place), the address on one line with the full address in a tooltip, and pills: **Open** or **Closed**, the category, the time of the visit, the Google rating, the phone number to call, and the coordinates. **X** closes the inspector.
- **Rating**: every member's vote, see [Rating a place](#rating-a-place).
+3 -3
View File
@@ -2,7 +2,7 @@
TREK keeps every trip in sync across all connected members without requiring a page refresh. A dedicated **Collab addon** adds a second layer on top of that sync: group chat, shared notes, shared links, polls, and a "What's Next" widget showing upcoming assigned places.
![The Collab tab: chat, shared notes, polls and the What's Next widget side by side](assets/Collab.png)
![The Collab tab: chat, shared notes, links, polls and the What's Next widget side by side](assets/Collab.png)
## Real-time sync
@@ -40,11 +40,11 @@ The Collab addon (`collab`) must be enabled by an admin before the panel is visi
> **Admin:** enable the Collab addon and individual sub-features in [Admin-Addons](Admin-Addons). Links is on by default, like the other four.
On **desktop** the panel shows Chat as a fixed 380 px column on the left when other sub-features are also enabled; if only Chat is on, it expands to fill the full width. Notes, Links, Polls, and What's Next share the remaining space on the right. On **mobile** a tab bar at the top lets you switch between the enabled sub-features one at a time. Disabled sub-features are hidden from the tab bar.
On a **desktop** (1024 px and wider) each sub-feature is a card with a head band that carries its name, its count and its actions. Chat is a fixed 380 px column on the left when other sub-features are also enabled; if only Chat is on, it fills the full width. Notes, Links, Polls and What's Next share the space on the right; with three or more of them, Notes and Links form the upper row and Polls and What's Next the lower one. On a narrower window a tab bar at the top switches between the enabled sub-features one at a time, and the phone app has its own Collab tab. Disabled sub-features are hidden.
### Links
The **Links** tab collects the web addresses a trip runs on: the booking portal, the shared photo album, the restaurant's menu. **Add link** asks for a title and an `http(s)` address; anything else is refused. Each link is a chip with the site's favicon, the title and the host, two to a row where the panel is wide enough and one to a row where it is narrow, as in the Collab tab on the phone; a pinned chip is drawn in the accent tint. The whole chip is the link and opens the address in a new tab. Members with `collab_edit` can **edit** it in place (the pencil, for title and address alike), **pin** it to the top of the list or **delete** it, which asks first because the link goes for every member; the three sit faint in the chip's tail and come forward on hover, and are always shown on touch. Pinned links come first, then the newest. Adding, editing, pinning and deleting show up for every connected member at once.
The **Links** panel collects the web addresses a trip runs on: the booking portal, the shared photo album, the restaurant's menu. **Add link** in its head band opens a small dialog for a **Link title** and an `http(s)` address; anything else is refused. Each link is a chip with the site's favicon, the title and the host, two to a row where the panel is wide enough and one to a row where it is narrow; a pinned chip is drawn in the accent tint. The whole chip is the link and opens the address in a new tab. Members with `collab_edit` find three buttons in the chip's tail, faint until the pointer is on the chip and always shown on touch: **Edit link** opens the same dialog with the title and the address filled in, **Pin link** moves it to the top of the list, and **Delete link** asks first, because the link goes for every member. Pinned links come first, then the newest. Adding, editing, pinning and deleting show up for every connected member at once.
## Conflict handling
+3 -3
View File
@@ -114,11 +114,11 @@ Clicking a booking opens its detail over the page. The same popup opens wherever
- the **Reservations** list in a day's details, the **Bookings** cards in the place inspector,
- a booking's endpoint on the map, and the rides on the [Road Trip](Road-Trip) rail.
![Booking detail of a flight: status, type and day pills, the booking code with a copy button, tiles for departure, arrival, platform and seat, then fields, travelers, notes and files](assets/BookingDetail.png)
![Booking detail of a hotel stay: the status, type and day pills and the booking code with a copy button in the head, tiles for check-in, check-out and the nights, the accommodation, and On map, the bin and Edit at the foot](assets/BookingDetail.png)
The head band carries the type, the title and a row of pills: the status (click it to switch), the type, the day, and the booking code with a copy button. If you may edit bookings, click the title to rename it.
Below it come tiles with the key figures: departure and arrival times with their places, or check-in, check-out and the number of nights for a stay, plus duration, transfers and walking time for a public transit journey, and platform and seat. After them: the itinerary of a transit journey, the route with its stop times when there are more than two stops, the segment codes, every other field, travelers, notes, the linked expenses (click one to edit it in Costs), the files with **Show in files**, and any plugin panels.
Below it come tiles with the key figures: departure and arrival times with their places, or check-in, check-out and the number of nights for a stay, plus duration, transfers and walking time for a public transit journey, and platform and seat. After them: the itinerary of a transit journey, the route with its stop times when there are more than two stops, the segment codes, every other field, travelers, notes, the linked expenses under **Costs** (click one to open it in the expense dialog), the files with **Show in files**, and any plugin panels.
The bar at the foot holds:
@@ -128,7 +128,7 @@ The bar at the foot holds:
## Creating a reservation
![Edit Reservation dialog: the title typed in the head band with the status and type pills, then date and time, place, booking code and travelers, notes, link and files, and costs](assets/BookingEditor.png)
![Edit Reservation dialog of a hotel: the title in the head band with the status and type pills, then the accommodation with its days and its check-in and check-out times, the address, booking code and travelers, notes, link and files, and costs](assets/BookingEditor.png)
1. Click **Manual Booking**. The editor opens as **New Reservation**.
2. Type the title into the head band. It is the one required field; **Add** stays disabled until it has one.
+5 -4
View File
@@ -71,6 +71,7 @@ The stay is only a starting point. Change it on the stop, see [How long you stay
- **Make it a stop on the way:** click the number of a numbered stop and pick a kind. The number turns into the kind's icon and the stops below it are renumbered.
- **Change what kind of stop this is:** click the icon of a stop on the way. **Back to a destination** gives it its number again.
- A fuel or charging stop that refills your vehicle carries a fill badge (see [Driving limits and range](#driving-limits-and-range)). Click it to set how full this particular stop fills, or **Use my default**.
- **Edit it:** while the Road trip view is on, **Edit** on a stop on the way or on the place of a booked night, from its row, its place details or the map, opens the same compact dialog the search uses, with its kind and its stay. **More details** in that dialog opens the full place form.
Stops on the way also show under **Days** and on the day's route there. To keep them in Road trip only, switch off **Show in Days too** under **Service stops** in the **Driving settings**. The switch is on by default, belongs to the trip, and applies to every stop on the way, including existing ones. A stop that a booked night put on the drive is always hidden under **Days**, whatever the switch says, because the day already shows that booking.
@@ -167,8 +168,8 @@ Where the range runs out, the rail draws a band across the leg: **Tank runs out
The **Along the route** panel searches the road actually driven, not the straight line between stops. That is the difference between one petrol station and forty.
1. Pick the **Day** at the top of the panel.
2. Under **Looking for**, pick one or more of **Fuel**, **Charging**, **Rest area**, **Campsite**, **Accommodation**, **Food** and **Sights**. It opens on **Charging** when the trip's vehicle is electric, and on **Fuel** otherwise.
1. Under **Looking for**, open the list and tick one or more of **Fuel**, **Charging**, **Rest area**, **Campsite**, **Accommodation**, **Food** and **Sights**. It opens on **Charging** when the trip's vehicle is electric, and on **Fuel** otherwise.
2. Pick the **Day** in the dropdown at the end of the same line. **Search** waits until that day has routed.
3. Under **Within**, pick 2, 5 or 10 km either side of the road.
4. Press **Search**.
@@ -211,7 +212,7 @@ The handles show once you zoom in far enough to aim at a road (zoom level 9). A
## Other ways and avoidance
Click a drive band in the rail to see **Ways to drive this leg**. TREK asks the router for alternatives and lists them: the **Fastest**, how much slower or quicker each other one is, and on a leg you already reshaped, the road you are on as **Current**. Hover an entry to light it up on the map, click it to drive that way. Choosing a different road places a via on the leg, replacing any the leg already had; choosing the router's own road removes them.
Click a drive band in the rail to see **Ways to drive this leg**. TREK asks the router for alternatives and lists them: the **Fastest**, how much slower or quicker each other one is, and on a leg you already reshaped, the road you are on as **Current**. Hover an entry to light it up on the map, click it to drive that way. Choosing a different road first checks it with the road trip's router, then holds the leg on it with as few vias as it takes, replacing any the leg already had; a road the router will not follow is not saved. Choosing the router's own road removes them again.
The list can also offer **No motorway**, **No tolls** and **No ferry**. These come from the second routing engine (see below), which prices roads with its own speed model, so they are marked *Timed by the avoidance router, not the main one* and are not compared with the others. When the router knows only one way, the list says *Only one sensible way to drive this one.*
@@ -268,7 +269,7 @@ The badge is then tinted, and its tooltip names the track the day follows. **Dro
Switch on **Show hazard areas** under **Current warnings** in the Driving settings. It is off by default and shared by the trip.
- **DWD** weather warnings cover Germany. **GDACS** reports disasters worldwide; up to twelve flood, wildfire or cyclone events are drawn with their affected area.
- Click an area or an event for the source report and when it was last updated, and for GDACS its alert score. An event given only as a point says *Location only; affected area unknown.*
- Click an area or an event for the source report and when it was last updated, and for GDACS its alert score: the score of the event's current episode, or the event's overall score where GDACS gives none for the episode. An event given only as a point says *Location only; affected area unknown.*
- The feeds are refreshed every ten minutes, and a source that is incomplete or unreachable is labelled as such. Offline, live warnings are unavailable.
- **Warnings are not road closures. Routes stay unchanged.** They are current notices, not forecasts for your travel dates, and the coverage is not exhaustive.
+2
View File
@@ -10,6 +10,8 @@ Open the **Lists** tab inside the trip planner and pick **To-Do** in its bar. Th
## Layout
![The To-Do view of the Lists tab: the sidebar with progress, filters and lists, the task list, and a task open in the detail pane](assets/Todos.png)
The to-do view has two columns, and a third while a task is open:
- **Left sidebar**: a progress card (done of all, as a count, a percentage and a bar), the filters under **Tasks**, one row per list under **Lists**, and **Add list**, which asks for the name in a small dialog.
+2 -2
View File
@@ -28,7 +28,7 @@ The tab works exactly like the Bookings tab: the same bar with search, **Filter*
- Public transit journeys have no status. They are tinted blue, and in the cards view they sit among the confirmed entries in time order. **View options → Public transit as its own section** gives them a section of their own, **Automated public transit**. The list always does that when it is grouped by status.
- On the timeline, **Show the other tab** draws the Bookings tab's entries in a dimmed lane at the top.
![Transports on the timeline: one lane per transport type across the trip's days](assets/BookingsTimeline.png)
![Transports on the timeline: one lane per transport type across the trip's days](assets/TransportsTimeline.png)
## The transport editor
@@ -113,7 +113,7 @@ Self-hosters can point the `TRANSIT_API_URL` environment variable at their own M
Click a transport anywhere, on its card, in the day plan, on the map or in the road trip rail, and its detail opens.
![Booking detail of a flight: status, type and day pills, the booking code with copy, tiles for departure and arrival with their airports, platform and seat](assets/BookingDetail.png)
![Booking detail of a flight: status, type and day pills, the booking code with copy, tiles for departure and arrival with their airports, platform and seat](assets/TransportDetail.png)
For a transport, the tiles at the top show the departure and arrival times with their airports or stations, and the platform and seat when they are set. A route with stops lists every stop with its time. The foot of the detail has **On map**, which switches the route's line on and opens the plan on its day, **Delete** and **Edit**. See [The booking detail](Reservations-and-Bookings#the-booking-detail) for everything else it shows.
+1 -1
View File
@@ -62,7 +62,7 @@ A booking or transport opens its detail first, wherever you click it on the Plan
The detail shows status, type and day, the booking code with a copy button, the times, travelers, notes, files and linked expenses. Its footer holds **On map**, **Change route** for a public transit journey, **Delete** and **Edit**. **Edit** opens the editor. See [Reservations and Bookings](Reservations-and-Bookings) for everything the detail shows.
![The booking detail of a flight or train, with status, type and day pills, the booking code, stat tiles for the times and the fields below](assets/BookingDetail.png)
![The booking detail of a hotel stay, with status, type and day pills, the booking code, tiles for check-in, check-out and the nights, and the fields below](assets/BookingDetail.png)
## Dialogs
+1 -1
View File
@@ -2,7 +2,7 @@
The What's Next card lists the next stops planned on the trip, across all days, so the group can see at a glance where it is heading and who is going.
![The What's Next card: the next stops as cards grouped under Today, Tomorrow and the following dates, each with its time, address and the members going](assets/WhatsNext.png)
![The What's Next card: the next stops as cards under Today, each with its time (or TBD), its address and the members going](assets/WhatsNext.png)
## Where to find it
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 916 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 823 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 KiB

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 41 KiB