# Trip Entity Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` to implement this plan task-by-task. **Goal:** Restructure the site around a Trip entity — tracker/map/stats/stories become children of `/trips/japan-korea-2026/`, GPX route files live as media on the trip page, and `site.yaml` holds an `active_trip` slug so the nav can switch trips via config. **Architecture:** Trip = a Grav page (`trip.html.twig`) at `/trips//`. Map/stats templates find the tracker via `page.parent().route ~ '/tracker'` instead of the hardcoded `/tracker` path. Leaflet-gpx (CDN) loads all `*.gpx` media files from the trip page. A `trips.html.twig` listing page provides the multi-trip root. Stories is stubbed with a placeholder template. **Tech Stack:** Grav CMS 1.7/2.0, Twig, Leaflet.js, leaflet-gpx (CDN, vanilla JS — consistent with existing inline JS pattern) ## Global Constraints - All content/theme edits go in `user/` — commit with `git -C user`, not main-repo git - Entry URLs change: `/tracker/` → `/trips/japan-korea-2026/tracker/` — acceptable pre-launch - `make test-post` (6/6) and `make test-ui` (25/25) must pass after every task - No new JS framework dependencies; leaflet-gpx is 3KB vanilla JS - `user/config/media.yaml` must whitelist `.gpx` so Grav serves it as a file - The `02.post/post-form.md` `pageconfig.parent` must stay in sync with the tracker path --- ### Task 1: Restructure pages under `/trips/` **Files:** - Create: `user/pages/01.trips/trips.md` - Create: `user/pages/01.trips/japan-korea-2026/trip.md` - Create: `user/pages/01.trips/japan-korea-2026/01.tracker/tracker.md` (copy from `user/pages/01.tracker/tracker.md`, no content change) - Move: all `*.entry/` folders from `user/pages/01.tracker/` → `user/pages/01.trips/japan-korea-2026/01.tracker/` - Create: `user/pages/01.trips/japan-korea-2026/02.map/map.md` (copy from `user/pages/03.map/map.md`) - Create: `user/pages/01.trips/japan-korea-2026/03.stats/stats.md` (copy from `user/pages/04.stats/stats.md`) - Create: `user/pages/01.trips/japan-korea-2026/04.stories/stories.md` - Delete: `user/pages/01.tracker/`, `user/pages/03.map/`, `user/pages/04.stats/` - Modify: `user/config/site.yaml` — add `active_trip: japan-korea-2026` - Modify (create if absent): `user/config/media.yaml` — whitelist GPX - [ ] **Step 1: Verify current structure before touching anything** ```bash find user/pages -name "*.md" | sort ``` Expected: entries under `01.tracker/`, map at `03.map/map.md`, stats at `04.stats/stats.md`. - [ ] **Step 2: Create trips hierarchy** ```bash mkdir -p user/pages/01.trips/japan-korea-2026/01.tracker mkdir -p user/pages/01.trips/japan-korea-2026/02.map mkdir -p user/pages/01.trips/japan-korea-2026/03.stats mkdir -p user/pages/01.trips/japan-korea-2026/04.stories ``` - [ ] **Step 3: Write `trips.md`** `user/pages/01.trips/trips.md`: ```yaml --- title: Trips template: trips content: items: '@self.children' order: by: date dir: desc --- ``` - [ ] **Step 4: Write `trip.md`** `user/pages/01.trips/japan-korea-2026/trip.md`: ```yaml --- title: 'Japan & Korea 2026' template: trip date: '2026-06-17' date_start: '2026-06-17' date_end: '' cover_image: '' content: items: '@self.children' --- ``` - [ ] **Step 5: Copy tracker.md, move entries** ```bash cp user/pages/01.tracker/tracker.md user/pages/01.trips/japan-korea-2026/01.tracker/tracker.md mv user/pages/01.tracker/*.entry user/pages/01.trips/japan-korea-2026/01.tracker/ ``` - [ ] **Step 6: Copy map.md and stats.md** ```bash cp user/pages/03.map/map.md user/pages/01.trips/japan-korea-2026/02.map/map.md cp user/pages/04.stats/stats.md user/pages/01.trips/japan-korea-2026/03.stats/stats.md ``` - [ ] **Step 7: Write stories stub** `user/pages/01.trips/japan-korea-2026/04.stories/stories.md`: ```yaml --- title: Stories template: stories published: true --- ``` - [ ] **Step 8: Delete old top-level pages** ```bash rm -rf user/pages/01.tracker user/pages/03.map user/pages/04.stats ``` - [ ] **Step 9: Add `active_trip` to site.yaml** Add to `user/config/site.yaml`: ```yaml active_trip: japan-korea-2026 ``` - [ ] **Step 10: Whitelist GPX in media.yaml** `user/config/media.yaml` (create if absent): ```yaml gpx: type: file extensions: ['gpx'] mime: application/gpx+xml ``` - [ ] **Step 11: Verify pages load at new URLs** ```bash curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips/japan-korea-2026/tracker # Expected: 200 curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips/japan-korea-2026/map # Expected: 200 curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips/japan-korea-2026/stats # Expected: 200 ``` - [ ] **Step 12: Commit** ```bash git -C user add pages/01.trips config/site.yaml config/media.yaml git -C user rm -r --cached pages/01.tracker pages/03.map pages/04.stats git -C user commit -m "feat: restructure pages under trips/japan-korea-2026 entity" ``` --- ### Task 2: Update templates for trip-relative paths + new trip/trips/stories templates **Files:** - Modify: `user/themes/intotheeast/templates/map.html.twig` — change hardcoded `/tracker` path - Modify: `user/themes/intotheeast/templates/stats.html.twig` — same - Modify: `user/themes/intotheeast/templates/partials/base.html.twig` — nav uses `active_trip` - Create: `user/themes/intotheeast/templates/trip.html.twig` - Create: `user/themes/intotheeast/templates/trips.html.twig` - Create: `user/themes/intotheeast/templates/stories.html.twig` **Interfaces:** - Consumes: `config.site.active_trip` from site.yaml (set in Task 1) - Produces: map/stats find entries via `page.parent().route ~ '/tracker'` - [ ] **Step 1: Fix `map.html.twig` — tracker path** Replace: ```twig {% set tracker_page = grav.pages.find('/tracker') %} {% set all_entries = tracker_page ? tracker_page.children.published() : [] %} ``` With: ```twig {% set tracker_page = grav.pages.find(page.parent().route ~ '/tracker') %} {% set all_entries = tracker_page ? tracker_page.children.published() : [] %} ``` - [ ] **Step 2: Fix `stats.html.twig` — tracker path** Same replacement as Step 1 (identical pattern in stats.html.twig). - [ ] **Step 3: Update `base.html.twig` nav** Replace hardcoded nav href values with `active_trip`-driven paths. The pattern in base.html.twig currently sets hrefs to `/tracker`, `/map`, `/stats`. Replace with: ```twig {% set active_trip = config.site.active_trip %} {% set trip_base = '/trips/' ~ active_trip %} ``` Nav links become: - Journal: `{{ trip_base }}/tracker` - Map: `{{ trip_base }}/map` - Stats: `{{ trip_base }}/stats` Active state detection: replace `page.url starts with '/tracker'` checks with `page.url starts with trip_base ~ '/tracker'` (and similarly for map/stats). - [ ] **Step 4: Create `trip.html.twig`** `user/themes/intotheeast/templates/trip.html.twig`: ```twig {% extends 'partials/base.html.twig' %} {% block content %} {% set tracker_page = grav.pages.find(page.route ~ '/tracker') %} {% set entries = tracker_page ? tracker_page.children.published() : [] %}

{{ page.title }}

{% if page.header.date_start %}

{{ page.header.date_start|date('d M Y') }} {% if page.header.date_end %} — {{ page.header.date_end|date('d M Y') }}{% endif %}

{% endif %}
{% if entries|length > 0 %}

Recent entries

{% for entry in entries|slice(0, 3) %} {{ entry.date|date('d M Y') }} {{ entry.title }} {% if entry.header.location_city %} · {{ entry.header.location_city }}{% endif %} {% endfor %}
{% endif %} {% endblock %} ``` - [ ] **Step 5: Create `trips.html.twig`** `user/themes/intotheeast/templates/trips.html.twig`: ```twig {% extends 'partials/base.html.twig' %} {% block content %}

{{ page.title }}

{% set trips = page.children.published() %} {% if trips|length == 0 %}

No trips yet.

{% else %} {% endif %} {% endblock %} ``` - [ ] **Step 6: Create `stories.html.twig` stub** `user/themes/intotheeast/templates/stories.html.twig`: ```twig {% extends 'partials/base.html.twig' %} {% block content %}

{{ page.title }}

Stories coming soon.

{% endblock %} ``` - [ ] **Step 7: Verify templates render** ```bash curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips/japan-korea-2026 # Expected: 200 curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips # Expected: 200 curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/trips/japan-korea-2026/stories # Expected: 200 ``` Check nav links resolve correctly on tracker/map/stats pages. - [ ] **Step 8: Commit** ```bash git -C user add themes/intotheeast/templates/ git -C user commit -m "feat: add trip/trips/stories templates, update nav and map/stats to use trip-relative paths" ``` --- ### Task 3: Add GPX route support to map template **Files:** - Modify: `user/themes/intotheeast/templates/map.html.twig` **Interfaces:** - Consumes: `*.gpx` files uploaded as media to the trip page (`page.parent()`) - Produces: GPX tracks rendered as colored polylines on the Leaflet map, underneath entry pins - [ ] **Step 1: Add leaflet-gpx script tag** In `map.html.twig`, after the existing Leaflet script tag, add: ```html ``` - [ ] **Step 2: Collect GPX URLs from trip media** After the `{% set trip_page = page.parent() %}` line (add this at the top of the template, alongside the tracker_page lookup), add: ```twig {% set gpx_urls = [] %} {% for name, media in trip_page.media.all %} {% if name|split('.')|last == 'gpx' %} {% set gpx_urls = gpx_urls|merge([media.url]) %} {% endif %} {% endfor %} ``` - [ ] **Step 3: Pass GPX URLs to JavaScript** In the `