docs: update CLAUDE.md, bugs log, and posting pipeline for Grav 2.0 + trip entity
- CLAUDE.md: add Grav 2.0 upgrade method, Admin2 setup, trip entity architecture, updated paths - bugs-and-fixes.md: fix stale paths, add BUG-004 (Admin2 empty dashboard) and BUG-005 (PHP session path) - posting-pipeline.md: update paths to trips/dailies structure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,24 @@
|
||||
- **./**: Grav CMS dev environment for intotheeast travel blog
|
||||
- **scripts/**: Server install and maintenance scripts
|
||||
- **user/**: Site content, config, pages, and theme (standalone git repo — do not modify from here)
|
||||
- **docs/**: All plans, specs, and project documentation (moved here from `user/docs/` on 2026-06-19)
|
||||
|
||||
### Current stack
|
||||
|
||||
- **Grav:** 2.0.0-rc.9 (installed manually — see §3 below)
|
||||
- **Admin:** Admin2 v2.0.0-rc.15 (plugin slug: `admin2`, NOT `admin`)
|
||||
- **Docker image:** `getgrav/grav` with `GRAV_CHANNEL=beta`
|
||||
- **PHP session:** `session.save_path = /tmp` set in `php/php-local.ini`
|
||||
|
||||
### Trip entity architecture
|
||||
|
||||
The site is structured around Trip entities. Key facts:
|
||||
- Active trip is set in `user/config/site.yaml` → `active_trip: japan-korea-2026`
|
||||
- Trip pages live at `user/pages/01.trips/<slug>/`
|
||||
- Each trip has: `01.dailies/`, `02.map/`, `03.stats/`, `04.stories/`
|
||||
- Nav in `base.html.twig` derives all links from `config.site.active_trip`
|
||||
- Post form parent (`post-form.md` → `pageconfig.parent`) **must be kept in sync** with `active_trip`
|
||||
- GPX route files live as media on the trip page itself, served via leaflet-gpx CDN
|
||||
|
||||
### Environment
|
||||
|
||||
@@ -23,6 +41,8 @@ Always use `make` commands for anything on the production server (`make remote-i
|
||||
- `make content-push` — commit and push `user/` to Gitea (triggers production pull via webhook)
|
||||
- `make content-pull` — pull latest from Gitea to local
|
||||
- `plugins.txt` is manually maintained — installing a plugin via Admin does NOT update it
|
||||
- `make demo-load` — load demo entries for both trips (Japan/Korea 2026 + Italy 2025 with real GPX)
|
||||
- `make demo-reset` — remove demo entries (keeps trip page structure, removes entries only)
|
||||
|
||||
### User repo gitignore
|
||||
|
||||
@@ -52,7 +72,7 @@ Before going live, change in `user/config/system.yaml`:
|
||||
|---|---|---|
|
||||
| `twig.cache` | `true` | Templates compiled once and reused; safe because theme files don't change at runtime |
|
||||
|
||||
**Pre-launch smoke test required:** with `twig.cache: true`, submit one post via `/post` and confirm the entry appears in `/tracker` immediately. This verifies the cache-on-save plugin (BUG-001 fix) works correctly with caching enabled.
|
||||
**Pre-launch smoke test required:** with `twig.cache: true`, submit one post via `/post` and confirm the entry appears in `/trips/japan-korea-2026/dailies` immediately. This verifies the cache-on-save plugin (BUG-001 fix) works correctly with caching enabled.
|
||||
|
||||
### What the cache-on-save plugin handles
|
||||
|
||||
@@ -77,6 +97,58 @@ run `make fix-perms`. This creates uid 1000 in the container, chowns `/var/www/h
|
||||
and reloads Apache. Always run `make setup` (not just `make start`) after `docker compose down && up`
|
||||
to ensure permissions are correct.
|
||||
|
||||
### Grav 2.0 upgrade (local)
|
||||
|
||||
GPM (`php bin/gpm selfupgrade`) does **not** serve Grav 2.0 RC — it still reports 1.7.x as latest even on the `testing` channel. To upgrade locally:
|
||||
|
||||
```bash
|
||||
# Download grav-admin bundle (includes Grav core + admin2 plugin)
|
||||
docker exec -w /tmp intotheeast_grav bash -c "
|
||||
curl -sL 'https://getgrav.org/download/core/grav-admin/2.0.0-rc.9?testing' -o grav-admin.zip && \
|
||||
unzip -q grav-admin.zip
|
||||
"
|
||||
# Copy core files only (not user/)
|
||||
docker exec -w /tmp intotheeast_grav bash -c "
|
||||
cp -rf grav-admin/{assets,bin,system,vendor,webserver-configs,index.php,composer.json,composer.lock,robots.txt,CHANGELOG.md,LICENSE.txt} /var/www/html/
|
||||
"
|
||||
# Install Admin2 from the bundle (it's named admin2, not admin)
|
||||
docker exec -w /tmp intotheeast_grav bash -c "
|
||||
cp -rf grav-admin/user/plugins/admin2 /var/www/html/user/plugins/admin2
|
||||
"
|
||||
make fix-perms
|
||||
docker exec -w /var/www/html intotheeast_grav php bin/grav cache --all
|
||||
# Cleanup
|
||||
docker exec intotheeast_grav rm -rf /tmp/grav-admin /tmp/grav-admin.zip
|
||||
```
|
||||
|
||||
After upgrading, ensure these settings in `user/config/system.yaml`:
|
||||
```yaml
|
||||
accounts:
|
||||
type: flex # required for Admin2 API
|
||||
pages:
|
||||
type: flex # required for Admin2 pages API
|
||||
```
|
||||
|
||||
And ensure the admin user account has `api.*` permissions (Admin2 uses a new permission namespace):
|
||||
```yaml
|
||||
# user/accounts/<username>.yaml
|
||||
access:
|
||||
admin:
|
||||
login: true
|
||||
super: true
|
||||
api:
|
||||
super: true
|
||||
access: true
|
||||
```
|
||||
|
||||
**Disable the old `admin` plugin** once `admin2` is installed — both route to `/admin` and conflict:
|
||||
```bash
|
||||
# In user/plugins/admin/admin.yaml:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
**JWT secret:** Leave `jwt_secret: ''` in `user/plugins/api/api.yaml` — it works for local dev and production installs generate a secure secret automatically.
|
||||
|
||||
### Language URL prefix
|
||||
|
||||
If Grav redirects to `/en/...` URLs, ensure `user/config/system.yaml` contains:
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# Bugs & Fixes
|
||||
|
||||
Backlog of confirmed bugs with root cause analysis and implementation spec for the fix.
|
||||
|
||||
---
|
||||
|
||||
## BUG-001 — New entry not visible after form submission
|
||||
|
||||
**Status:** fixed 2026-06-18
|
||||
**Reported:** 2026-06-18
|
||||
|
||||
### Symptom
|
||||
|
||||
After submitting a new post via `/post`, the entry page file is created correctly on disk but does not appear in the `/trips/<active_trip>/dailies` feed or in the Grav Admin panel until the cache is manually flushed.
|
||||
|
||||
### Root cause
|
||||
|
||||
Grav's page-tree cache (`cache/doctrine/`) is not invalidated when `add-page-by-form` writes a new page to disk. The tracker template uses `page.children`, which Grav serves from cache — so the new child page is invisible until the cache is cleared.
|
||||
|
||||
### Workaround (manual)
|
||||
|
||||
Run in terminal after each submission:
|
||||
|
||||
```bash
|
||||
docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache"
|
||||
```
|
||||
|
||||
### Fix spec
|
||||
|
||||
Wire cache-clear into the form process so it happens automatically on every successful submission.
|
||||
|
||||
**Approach — custom Grav plugin event hook:**
|
||||
|
||||
1. Create a small plugin `user/plugins/cache-on-save/` with one event listener:
|
||||
- Listen on `onFormProcessed`
|
||||
- When the form name is `new-entry`, call `$this->grav['cache']->deleteAll()` (note: `clear()` does not exist on `Grav\Common\Cache` in Grav 1.7)
|
||||
2. Enable the plugin in `user/config/plugins/cache-on-save.yaml`
|
||||
|
||||
This is the cleanest approach: it fires exactly once per successful submission, requires no changes to `post-form.md`, and works for any future forms too.
|
||||
|
||||
**Alternative — disable page cache entirely:**
|
||||
|
||||
Set `cache: { enabled: false }` in `system.yaml`. Simpler but degrades frontend performance; not recommended for production.
|
||||
|
||||
### Files to create/modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `user/plugins/cache-on-save/cache-on-save.php` | New plugin, ~30 lines |
|
||||
| `user/plugins/cache-on-save/cache-on-save.yaml` | Plugin manifest, enabled: true |
|
||||
| `user/config/plugins/cache-on-save.yaml` | Runtime config, enabled: true |
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
1. Submit a new post via `/post`
|
||||
2. Navigate to `/trips/<active_trip>/dailies` — the new entry is visible immediately, no manual cache flush needed
|
||||
3. Grav Admin also shows the new page immediately
|
||||
|
||||
---
|
||||
|
||||
## BUG-002 — Stale Twig cache after theme file changes
|
||||
|
||||
**Status:** fixed 2026-06-18
|
||||
**Reported:** 2026-06-18
|
||||
|
||||
### Symptom
|
||||
|
||||
After theme template files are added or modified (e.g., creating `partials/base.html.twig`), Grav's Twig compiled-template cache still holds the old compiled version. Pages that extend the changed file throw 500 errors like "Template partials/base.html.twig is not defined" even though the file exists on disk.
|
||||
|
||||
### Root cause
|
||||
|
||||
Grav caches compiled Twig templates in `cache/twig/`. When a new file is added, existing templates that reference it don't know to recompile — their cache entries are still valid from their own mtime perspective.
|
||||
|
||||
### Workaround (manual)
|
||||
|
||||
Run after any theme file is added or changed:
|
||||
|
||||
```bash
|
||||
docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache"
|
||||
```
|
||||
|
||||
### Fix spec
|
||||
|
||||
Disable Twig template caching in development via `user/config/system.yaml`:
|
||||
|
||||
```yaml
|
||||
twig:
|
||||
cache: false
|
||||
```
|
||||
|
||||
Acceptable for a single-user dev setup — eliminates both BUG-001's side-effect and this bug entirely. Performance cost is negligible at one-user scale. On production, leave Twig cache enabled (it's fine there because template files don't change at runtime).
|
||||
|
||||
**Files to change:**
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `user/config/system.yaml` | Add `twig: { cache: false }` under development section |
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
1. Add a new theme template file
|
||||
2. Reload any page — no 500 error, template works immediately without manual cache flush
|
||||
|
||||
---
|
||||
|
||||
## BUG-003 — One post per day limit; silent failure on duplicate date
|
||||
|
||||
**Status:** fixed 2026-06-18
|
||||
**Reported:** 2026-06-18
|
||||
|
||||
### Symptom
|
||||
|
||||
Submitting a second post with the same date as an existing entry shows "Entry posted successfully!" but creates no file. The user's post is silently discarded.
|
||||
|
||||
### Root cause
|
||||
|
||||
The `add-page-by-form` plugin built the page slug from date only (`Y-m-d`), producing folder names like `2026-06-18.entry`. With `overwrite_mode: false`, if that folder already exists the plugin skips page creation but does not abort — the `message` process step runs regardless, showing a false success.
|
||||
|
||||
### Fix
|
||||
|
||||
Change the slug template in `user/pages/02.post/post-form.md` to include time and title:
|
||||
|
||||
```twig
|
||||
{{ form.value.date|date('Y-m-d-Hi') }}-{{ form.value.title|lower|regex_replace('/[^a-z0-9]+/', '-')|trim('-') }}
|
||||
```
|
||||
|
||||
Example: title "Arrived in Tokyo" at 14:30 on 2026-06-18 → `2026-06-18-1430-arrived-in-tokyo`
|
||||
|
||||
The slug is locked at creation time. Renaming the title afterwards does not change the URL.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
1. Submit two posts on the same day with different times or titles — both appear in `/trips/<active_trip>/dailies` as separate entries
|
||||
2. Renaming a post's title in the frontmatter does not break its URL
|
||||
|
||||
---
|
||||
|
||||
## BUG-004 — Admin2 shows empty dashboard after Grav 2.0 upgrade
|
||||
|
||||
**Status:** fixed 2026-06-19
|
||||
|
||||
### Symptom
|
||||
|
||||
After installing Grav 2.0 + Admin2, logging in shows an empty dashboard with no sidebar navigation (only a Settings item visible). Pages and content are not accessible.
|
||||
|
||||
### Root causes (three separate issues)
|
||||
|
||||
**A) Wrong user account type.** `system.yaml` had `accounts.type: regular` (old file-based system). Admin2's API plugin uses the Flex user collection to look up accounts. With `regular`, the API saw zero users and entered setup-wizard mode.
|
||||
|
||||
**B) Wrong pages type.** `system.yaml` had `pages.type: regular`. Admin2's pages API requires `pages.type: flex` to serve the page tree.
|
||||
|
||||
**C) Missing `api.*` permissions on user account.** Grav 2.0 Admin2 uses a new `api.*` permission namespace (`api.super`, `api.access`, etc.) instead of the old `admin.super`. A user with only `access.admin.super: true` appears as a non-admin to Admin2.
|
||||
|
||||
### Fix
|
||||
|
||||
In `user/config/system.yaml`:
|
||||
```yaml
|
||||
accounts:
|
||||
type: flex
|
||||
pages:
|
||||
type: flex
|
||||
```
|
||||
|
||||
In `user/accounts/<username>.yaml`:
|
||||
```yaml
|
||||
access:
|
||||
admin:
|
||||
login: true
|
||||
super: true # keep for backward compat
|
||||
api:
|
||||
super: true # required by Admin2
|
||||
access: true # required by Admin2
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- `api.super: true` causes Admin2 to grant all sub-permissions automatically (`api.pages`, `api.config`, etc.)
|
||||
- JWT secret in `user/plugins/api/api.yaml` can stay empty — HMAC-SHA256 works with an empty key locally; production generates its own secure secret
|
||||
- The old `admin` plugin must be disabled (`enabled: false`) to avoid route conflict with `admin2`
|
||||
|
||||
---
|
||||
|
||||
## BUG-005 — PHP session fails after Grav 2.0 container upgrade
|
||||
|
||||
**Status:** fixed 2026-06-19
|
||||
|
||||
### Symptom
|
||||
|
||||
After replacing Grav core files inside the container, all pages return a CRITICAL error in `logs/grav.log`: `Failed to start session: session_start(): Failed to read session data: files (path: )`. Site is inaccessible.
|
||||
|
||||
### Root cause
|
||||
|
||||
The `getgrav/grav` Docker image's PHP configuration does not set `session.save_path`. Grav 1.7 worked because the image's default PHP config included it; the updated image layer did not.
|
||||
|
||||
### Fix
|
||||
|
||||
Add to `php/php-local.ini`:
|
||||
```ini
|
||||
session.save_path = /tmp
|
||||
```
|
||||
|
||||
Restart the container to pick up the change. This file is bind-mounted into the container so no image rebuild is needed.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,132 @@
|
||||
# Daily Entry Posting Pipeline
|
||||
|
||||
Two ways to create a daily entry: the mobile frontend form at `/post`, or directly from the Grav Admin2 panel. Both produce the same page structure under `user/pages/01.trips/<active_trip>/01.dailies/`.
|
||||
|
||||
The active trip is set in `user/config/site.yaml` → `active_trip`. The post form's `pageconfig.parent` in `post-form.md` must be kept in sync with this value.
|
||||
|
||||
---
|
||||
|
||||
## Frontmatter Reference
|
||||
|
||||
Every entry page (`template: entry`) supports these frontmatter fields:
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `title` | string | ✅ | Entry headline |
|
||||
| `date` | datetime | ✅ | Format: `Y-m-d H:i` (e.g. `2026-06-17 10:00`) |
|
||||
| `template` | string | ✅ | Always `entry` |
|
||||
| `published` | bool | ✅ | `true` to show in tracker feed |
|
||||
| `lat` | string | — | Latitude decimal degrees (e.g. `52.3676`) |
|
||||
| `lng` | string | — | Longitude decimal degrees (e.g. `4.9041`) |
|
||||
| `location_city` | string | — | City name shown under the title (e.g. `Kyoto`) |
|
||||
| `location_country` | string | — | Country name shown under the title (e.g. `Japan`) |
|
||||
| `weather_desc` | string | — | Condition label — must be one of the values below |
|
||||
| `weather_temp_c` | number | — | Temperature in Celsius (displayed rounded, e.g. `19`) |
|
||||
| `hero_image` | string | — | Filename of the hero image (e.g. `photo.jpg`). Leave blank to auto-select the first uploaded image. |
|
||||
|
||||
**`weather_desc` allowed values** (matched to emoji icons in `entry.html.twig`):
|
||||
`Sunny` · `Partly cloudy` · `Cloudy` · `Foggy` · `Drizzle` · `Rain` · `Snow` · `Thunderstorm`
|
||||
|
||||
**Page media (photos):** images are stored as files in the page folder (`user/pages/01.tracker/<slug>/`). All images in the folder are shown in the gallery. `hero_image` pins one as the full-width header.
|
||||
|
||||
**Example complete frontmatter:**
|
||||
```yaml
|
||||
---
|
||||
title: 'First Day in Kyoto'
|
||||
date: '2026-07-20 09:30'
|
||||
template: entry
|
||||
published: true
|
||||
lat: '35.0116'
|
||||
lng: '135.7681'
|
||||
location_city: 'Kyoto'
|
||||
location_country: 'Japan'
|
||||
weather_desc: 'Sunny'
|
||||
weather_temp_c: 28
|
||||
hero_image: 'temple.jpg'
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flow 1 — Mobile Frontend Form (`/post`)
|
||||
|
||||
This is the primary posting flow, designed for one-handed phone use.
|
||||
|
||||
```
|
||||
Browser → /post (post-form.md)
|
||||
└─ Grav Form plugin validates fields
|
||||
└─ add-page-by-form plugin (onFormProcessed)
|
||||
├─ reads pageconfig.parent (/trips/japan-korea-2026/dailies) and pageconfig.slug_field (date + title)
|
||||
├─ reads pagefrontmatter (template: entry, published: true)
|
||||
├─ merges form field values into new page frontmatter
|
||||
├─ writes user/pages/01.trips/<active_trip>/01.dailies/<slug>/entry.md
|
||||
└─ moves uploaded photos into the page folder
|
||||
└─ cache-on-save plugin (onFormProcessed)
|
||||
└─ calls $grav['cache']->deleteAll() so tracker feed shows the entry immediately
|
||||
└─ form shows success message, resets fields
|
||||
```
|
||||
|
||||
**The form fields and their mapping to frontmatter:**
|
||||
|
||||
| Form field | Frontmatter key | Notes |
|
||||
|---|---|---|
|
||||
| `title` | `title` | Required |
|
||||
| `date` | `date` | Defaults to current datetime |
|
||||
| `content` | page body (markdown) | Required |
|
||||
| `photos` | page media files | Uploaded to page folder |
|
||||
| `lat` | `lat` | Filled via "Get Location" button |
|
||||
| `lng` | `lng` | Filled via "Get Location" button |
|
||||
| `location_city` | `location_city` | Manual text entry |
|
||||
| `location_country` | `location_country` | Manual text entry |
|
||||
| `weather_temp_c` | `weather_temp_c` | Hidden — set by weather JS widget |
|
||||
| `weather_desc` | `weather_desc` | Hidden — set by weather JS widget |
|
||||
|
||||
**Slug format:** `<YYYY-MM-DD>.<slugified-title>` (controlled by `slug_field: 'date,title'` in `post-form.md`).
|
||||
|
||||
**Security:** the `/post` page requires `access: site.login: true` — anonymous visitors get redirected to login.
|
||||
|
||||
---
|
||||
|
||||
## Flow 2 — Admin Panel (sit-down workflow)
|
||||
|
||||
Use this for drafts, scheduled posts, or editing existing entries.
|
||||
|
||||
1. Log in at `/admin`
|
||||
2. Go to **Pages** → **Add Page**
|
||||
3. Set:
|
||||
- **Page Title:** your entry title
|
||||
- **Parent Page:** `/trips/japan-korea-2026/dailies` (adjust to active trip)
|
||||
- **Page Template:** `entry`
|
||||
4. Fill in the **Entry** tab fields (city, country, lat/lng, weather)
|
||||
5. Write content in the **Content** tab
|
||||
6. Upload photos via the **Media** tab
|
||||
7. Set `published: true` (or leave `false` for a draft)
|
||||
8. For scheduling: set `publish_date` in **Options** → **Scheduling**
|
||||
9. Save
|
||||
|
||||
The Admin form fields are defined by `user/themes/intotheeast/blueprints/entry.yaml`.
|
||||
|
||||
**Drafts:** set `published: false` — the entry won't appear in the tracker feed until you flip it to `true`. Useful for writing ahead of time on the road.
|
||||
|
||||
**Scheduling:** Grav supports `publish_date` and `unpublish_date` in page frontmatter. Set them in the Admin Options tab. Requires `pages.publish_dates: true` in `system.yaml` (already enabled).
|
||||
|
||||
---
|
||||
|
||||
## Page folder structure
|
||||
|
||||
```
|
||||
user/pages/01.trips/
|
||||
└─ japan-korea-2026/ ← trip entity (active_trip in site.yaml)
|
||||
├─ trip.md ← trip page (title, date_start, date_end, cover_image, album_url)
|
||||
├─ *.gpx ← GPX route files (served as media, rendered on map)
|
||||
├─ 01.dailies/
|
||||
│ └─ 2026-07-20-1430-first-day-in-kyoto.entry/
|
||||
│ ├─ entry.md ← frontmatter + markdown body
|
||||
│ ├─ temple.jpg ← hero image (referenced by hero_image)
|
||||
│ └─ market.jpg ← additional gallery image
|
||||
├─ 02.map/map.md
|
||||
├─ 03.stats/stats.md
|
||||
└─ 04.stories/stories.md
|
||||
```
|
||||
|
||||
The entry folder name follows `<YYYY-MM-DD-HHmm>-<slug>.entry`. Grav uses this for ordering and routing. The `.entry` suffix enables the `entry` template.
|
||||
Reference in New Issue
Block a user