9.0 KiB
CLAUDE.md
0. Project specifics
Only ever write changes in this folder (travel-blog-intotheeast/) or its subfolders.
Folder explanation
- ./: 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.10 (baked into the custom Docker image via
Dockerfile) - Admin: Admin2 v2.0.0-rc.15 (plugin slug:
admin2, NOTadmin) - Docker image:
getgrav/gravwithGRAV_CHANNEL=beta - PHP session:
session.save_path = /tmpset inphp/php-local.ini
Dev server
The Docker dev server runs at http://localhost:8081 (mapped from container port 80 in docker-compose.yml).
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/ - Site nav in
base.html.twighas Home + Past Trips only — does not link to trip sub-sections - Post form parent (
post-form.md→pageconfig.parent) must be kept in sync withactive_trip - The trip page (
trip.html.twig) uses a client-side filter bar (All content / Journal / Stories) — do NOT add nav links back to/dailies,/stats,/storieson the trip page - Stats are shown inline on the trip page via a toggle; the standalone
/statssub-page still exists as a URL but is not linked from the trip page - GPX route files live as media on the trip page itself, served via leaflet-gpx CDN
- Manage GPX files (view/upload/delete) at
/gpx-manager— requires admin login; filenames are auto-slugified on upload
GPX file management
GPX files are stored as page media on the trip page (user/pages/01.trips/<slug>/). They are picked up automatically by map.html.twig via trip_page.media.all.
The GPX manager page (user/pages/03.gpx-manager/) provides a browser UI at /gpx-manager:
- Auth: enforced by Login plugin via
access.admin.login: truein frontmatter — shows login form if not authenticated - Template:
user/themes/intotheeast/templates/gpx-manager.html.twig - API: uses Grav API v1 with session cookie auth (
session_enabled: trueinuser/plugins/api/api.yaml)- List:
GET /api/v1/pages{route}/media - Upload:
POST /api/v1/pages{route}/media(multipart) - Delete:
DELETE /api/v1/pages{route}/media/{filename}
- List:
- Slugification: filenames are slugified client-side before upload (spaces/special chars → hyphens, lowercase); the file is sliced to a plain
Blobso the third argument toFormData.appendis always used as the filename - Media type:
.gpxis registered inuser/config/media.yamlso Grav serves and tracks these files
To add GPX files without the browser UI, drop them directly into user/pages/01.trips/<slug>/ and run make content-push.
Switching to a new trip
Two places hardcode the active trip slug. Grav's config and page frontmatter are static YAML — no variable substitution is possible, so these cannot read from site.yaml automatically. Both must be updated together when starting a new trip, or entries will be posted to the wrong folder.
| File | Key | Example value |
|---|---|---|
user/config/site.yaml |
active_trip |
italy-2027 |
user/pages/02.post/post-form.md |
pageconfig.parent |
/trips/italy-2027/dailies |
Note: system.yaml home.alias is permanently set to /home (the real home page) and does not need to change when switching trips.
After updating, also create the new trip's page tree under user/pages/01.trips/<new-slug>/ with the standard four subfolders.
Environment
Never read .env — it contains sensitive credentials. You may pass it to commands (e.g. docker compose, make) but never read its contents directly. Ask the user if you need environment-specific information.
Remote operations
Always use make commands for anything on the production server (make remote-install-plugins, make remote-clean, etc.) — never SSH directly since credentials live in .env. If a remote operation isn't covered by an existing make command, either ask the user to run it manually or suggest adding a new make command if it seems reusable.
Content sync
make content-push— commit and pushuser/to Gitea (triggers production pull via webhook)make content-pull— pull latest from Gitea to localplugins.txtis manually maintained — installing a plugin via Admin does NOT update itmake demo-load— load demo content intoitaly-2026-demotrip (12 journal entries + 4 stories + 7 GPX files); source inuser/docs/demo/trips/italy-2026-demo/make demo-reset— remove the entireitaly-2026-demopages folder and clear cache (full reset; re-run demo-load to restore)
User repo gitignore
Only these folders are tracked in the user/ Git repo: pages/, config/, accounts/, themes/. The plugins/ and data/ folders are excluded.
1. Environment modes
Rule: do not switch modes during development
Never toggle between development and production mode mid-session. If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than temporarily flipping a mode flag to work around it. Mode switches introduce inconsistent state and make bugs harder to reproduce.
Development mode (current)
Active settings in user/config/system.yaml:
| Setting | Dev value | Why |
|---|---|---|
twig.cache |
false |
Theme file edits take effect immediately; no stale compile errors |
With these settings, Grav rebuilds templates on every request. This is intentionally slower but means you never need to flush cache after editing a .html.twig file.
Production mode (not yet configured)
Before going live, change in user/config/system.yaml:
| Setting | Prod value | Why |
|---|---|---|
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 /trips/italy-2026-demo/dailies immediately. This verifies the cache-on-save plugin (BUG-001 fix) works correctly with caching enabled.
What the cache-on-save plugin handles
The custom plugin at user/plugins/cache-on-save/ clears Grav's page-tree cache on every new-entry form submission. This ensures new posts appear in the tracker feed immediately in both modes — it does not depend on whether Twig caching is on or off.
2. Local development setup
First-time setup after cloning
user/plugins/ and user/data/ are excluded from git but Grav requires them to exist. Create them once after cloning:
mkdir -p user/plugins user/data
Then run make setup (starts Docker + installs plugins).
After make install-plugins: fix cache permissions
If the site returns a 500 error after plugin installation or after recreating the container,
run make fix-perms. This creates uid 1000 in the container, chowns /var/www/html to 1000:1000,
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)
Grav 2.0 is baked into the custom Docker image via Dockerfile. The base getgrav/grav image ships 1.7 — the Dockerfile downloads the 2.0-rc.10 bundle from GitHub and overwrites the core files at build time, so the image always contains 2.0.
make setup = build → start → install-plugins → fix-perms. After any docker compose down, run make setup to get back to a fully working state. docker compose restart (soft restart) also preserves the image, so Grav 2.0 stays.
To upgrade to a newer RC: update the URL in Dockerfile and run make setup — Docker rebuilds the image layer automatically.
After upgrading, ensure these settings in user/config/system.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):
# 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:
# 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:
languages:
supported: [en]
include_default_lang: false
Without include_default_lang: false, Grav adds a language prefix to all URLs even for single-language sites.