docs: add trip entity implementation plan
This commit is contained in:
@@ -0,0 +1,538 @@
|
||||
# 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/<slug>/`. 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/<slug>` → `/trips/japan-korea-2026/tracker/<slug>` — 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() : [] %}
|
||||
|
||||
<div class="trip-hero">
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% if page.header.date_start %}
|
||||
<p class="trip-dates">
|
||||
{{ page.header.date_start|date('d M Y') }}
|
||||
{% if page.header.date_end %} — {{ page.header.date_end|date('d M Y') }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<nav class="trip-nav">
|
||||
<a href="{{ page.route }}/tracker">Journal</a>
|
||||
<a href="{{ page.route }}/map">Map</a>
|
||||
<a href="{{ page.route }}/stats">Stats</a>
|
||||
<a href="{{ page.route }}/stories">Stories</a>
|
||||
</nav>
|
||||
|
||||
{% if entries|length > 0 %}
|
||||
<section class="trip-recent">
|
||||
<h2>Recent entries</h2>
|
||||
{% for entry in entries|slice(0, 3) %}
|
||||
<a href="{{ entry.url }}">
|
||||
<span>{{ entry.date|date('d M Y') }}</span>
|
||||
{{ entry.title }}
|
||||
{% if entry.header.location_city %} · {{ entry.header.location_city }}{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Create `trips.html.twig`**
|
||||
|
||||
`user/themes/intotheeast/templates/trips.html.twig`:
|
||||
```twig
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% set trips = page.children.published() %}
|
||||
{% if trips|length == 0 %}
|
||||
<p>No trips yet.</p>
|
||||
{% else %}
|
||||
<ul class="trips-list">
|
||||
{% for trip in trips %}
|
||||
<li>
|
||||
<a href="{{ trip.url }}">
|
||||
<strong>{{ trip.title }}</strong>
|
||||
{% if trip.header.date_start %}
|
||||
<span>{{ trip.header.date_start|date('d M Y') }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Create `stories.html.twig` stub**
|
||||
|
||||
`user/themes/intotheeast/templates/stories.html.twig`:
|
||||
```twig
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p>Stories coming soon.</p>
|
||||
{% 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
|
||||
<script src="https://cdn.jsdelivr.net/npm/leaflet-gpx@2.1.2/gpx.min.js"></script>
|
||||
```
|
||||
|
||||
- [ ] **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 `<script>` block, after the map is initialized and before the entry markers loop, add:
|
||||
|
||||
```javascript
|
||||
// GPX route tracks
|
||||
const gpxUrls = {{ gpx_urls|json_encode|raw }};
|
||||
gpxUrls.forEach(url => {
|
||||
new L.GPX(url, {
|
||||
async: true,
|
||||
polyline_options: { color: '#1F6B5A', weight: 2, opacity: 0.7 },
|
||||
marker_options: { startIconUrl: null, endIconUrl: null, shadowUrl: null }
|
||||
}).addTo(map);
|
||||
});
|
||||
```
|
||||
|
||||
Disabling start/end markers keeps the map clean — the entry pins already mark key stops.
|
||||
|
||||
- [ ] **Step 4: Test with a sample GPX**
|
||||
|
||||
Create a minimal 3-point GPX file to test without a real Komoot export:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<gpx version="1.1" creator="test">
|
||||
<trk><trkseg>
|
||||
<trkpt lat="35.6762" lon="139.6503"><time>2026-03-25T10:00:00Z</time></trkpt>
|
||||
<trkpt lat="35.0116" lon="135.7681"><time>2026-03-27T10:00:00Z</time></trkpt>
|
||||
<trkpt lat="37.5665" lon="126.9780"><time>2026-04-01T10:00:00Z</time></trkpt>
|
||||
</trkseg></trk>
|
||||
</gpx>
|
||||
```
|
||||
|
||||
Upload via Grav Admin to the trip page media, then verify the map at `/trips/japan-korea-2026/map` renders the polyline. Remove the test file after verification.
|
||||
|
||||
- [ ] **Step 5: Verify map still works without GPX**
|
||||
|
||||
Confirm map renders normally when no `.gpx` files are present (gpxUrls = []).
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git -C user add themes/intotheeast/templates/map.html.twig
|
||||
git -C user commit -m "feat: add GPX route rendering to trip map via leaflet-gpx"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update post form, Makefile, demo content, and tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `user/pages/02.post/post-form.md` — `pageconfig.parent`
|
||||
- Modify: `Makefile` — `demo-load` and `demo-reset` paths
|
||||
- Modify: `scripts/test-post.sh` — `TRACKER` variable
|
||||
- Modify: `scripts/test-form-config.sh` — expected parent value
|
||||
- Modify: `tests/ui/tracker.spec.js` — any hardcoded `/tracker` URL references
|
||||
- Modify: `user/docs/demo/` — move demo entries to new path structure
|
||||
|
||||
- [ ] **Step 1: Update post form parent**
|
||||
|
||||
In `user/pages/02.post/post-form.md`, change:
|
||||
```yaml
|
||||
pageconfig:
|
||||
parent: '/tracker'
|
||||
```
|
||||
To:
|
||||
```yaml
|
||||
pageconfig:
|
||||
parent: '/trips/japan-korea-2026/tracker'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update demo content structure**
|
||||
|
||||
```bash
|
||||
mkdir -p user/docs/demo/trips/japan-korea-2026/tracker
|
||||
mv user/docs/demo/tracker/* user/docs/demo/trips/japan-korea-2026/tracker/
|
||||
rmdir user/docs/demo/tracker
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update Makefile demo targets**
|
||||
|
||||
In `Makefile`, update `demo-load` and `demo-reset`:
|
||||
|
||||
```makefile
|
||||
demo-load:
|
||||
cp -r user/docs/demo/trips/japan-korea-2026/tracker/. user/pages/01.trips/japan-korea-2026/01.tracker/
|
||||
docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache"
|
||||
|
||||
demo-reset:
|
||||
@for dir in user/docs/demo/trips/japan-korea-2026/tracker/*/; do \
|
||||
folder=$$(basename "$$dir"); \
|
||||
rm -rf "user/pages/01.trips/japan-korea-2026/01.tracker/$$folder"; \
|
||||
done
|
||||
docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Update `test-post.sh` TRACKER path**
|
||||
|
||||
Find the line setting `TRACKER=` in `scripts/test-post.sh` and change it to:
|
||||
```bash
|
||||
TRACKER="user/pages/01.trips/japan-korea-2026/01.tracker"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update `test-form-config.sh` expected parent**
|
||||
|
||||
Find the assertion that checks `parent: '/tracker'` and update to check for `parent: '/trips/japan-korea-2026/tracker'`.
|
||||
|
||||
- [ ] **Step 6: Check Playwright tests for hardcoded paths**
|
||||
|
||||
Search `tests/ui/` for any hardcoded `/tracker` URL references:
|
||||
```bash
|
||||
grep -rn "tracker\|/map\|/stats" tests/ui/
|
||||
```
|
||||
|
||||
Update any that reference the old paths to use the new trip-scoped paths.
|
||||
|
||||
- [ ] **Step 7: Run full test suite**
|
||||
|
||||
```bash
|
||||
make test-config && make test-post && make test-ui
|
||||
```
|
||||
Expected: all pass (14/14, 6/6, 25/25).
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
# Main repo changes (Makefile + test scripts)
|
||||
git add Makefile scripts/test-post.sh scripts/test-form-config.sh tests/
|
||||
git commit -m "fix: update paths for trips/japan-korea-2026 restructure"
|
||||
|
||||
# User repo changes
|
||||
git -C user add pages/02.post/post-form.md docs/demo/
|
||||
git -C user commit -m "fix: update post form parent and demo content paths for trip structure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Admin blueprint for trip page type
|
||||
|
||||
**Files:**
|
||||
- Create: `user/themes/intotheeast/blueprints/trip.yaml`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: "Trip" tab in Grav Admin when editing the trip page, with date range and cover image fields
|
||||
|
||||
- [ ] **Step 1: Create `trip.yaml` blueprint**
|
||||
|
||||
`user/themes/intotheeast/blueprints/trip.yaml`:
|
||||
```yaml
|
||||
title: 'Trip'
|
||||
'@extends':
|
||||
type: default
|
||||
context: blueprints://pages
|
||||
|
||||
form:
|
||||
fields:
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
fields:
|
||||
trip:
|
||||
type: tab
|
||||
title: Trip
|
||||
fields:
|
||||
header.date_start:
|
||||
type: date
|
||||
label: 'Start Date'
|
||||
placeholder: '2026-06-17'
|
||||
help: 'First day of the trip'
|
||||
|
||||
header.date_end:
|
||||
type: date
|
||||
label: 'End Date'
|
||||
placeholder: ''
|
||||
help: 'Leave blank if trip is ongoing'
|
||||
|
||||
header.cover_image:
|
||||
type: text
|
||||
label: 'Cover Image Filename'
|
||||
placeholder: 'cover.jpg'
|
||||
help: 'Used in the trips listing page'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify blueprint appears in Admin**
|
||||
|
||||
Open Grav Admin → Pages → Trips → Japan & Korea 2026 → Edit. Confirm the "Trip" tab appears with start date, end date, cover image fields.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git -C user add themes/intotheeast/blueprints/trip.yaml
|
||||
git -C user commit -m "feat: add Admin blueprint for trip page type"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
After all tasks, run end-to-end check:
|
||||
|
||||
1. `make test-config && make test-post && make test-ui` — all must pass
|
||||
2. Navigate to `http://localhost:8081/trips/japan-korea-2026/tracker` — entries display in date order
|
||||
3. Navigate to `http://localhost:8081/trips/japan-korea-2026/map` — entry pins render, GPX polyline renders if a `.gpx` file is present on the trip page
|
||||
4. Navigate to `http://localhost:8081/trips/japan-korea-2026/stats` — stats compute correctly
|
||||
5. Navigate to `http://localhost:8081/trips` — trip listing shows Japan & Korea 2026
|
||||
6. Submit a post via `/post` — new entry appears under `/trips/japan-korea-2026/tracker`
|
||||
7. Grav Admin: edit the trip page → "Trip" tab visible with date fields
|
||||
Reference in New Issue
Block a user