cdd9e0c8b3
Reflects that demo now targets a single trip (italy-2026-demo / Tuscany 2026) rather than the old Japan/Korea 2026 + Italy 2025 pair.
199 lines
9.3 KiB
Markdown
199 lines
9.3 KiB
Markdown
# 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.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`
|
|
|
|
### 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.twig` has Home + Past Trips only — does not link to trip sub-sections
|
|
- Post form parent (`post-form.md` → `pageconfig.parent`) **must be kept in sync** with `active_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`, `/stories` on the trip page
|
|
- Stats are shown inline on the trip page via a toggle; the standalone `/stats` sub-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: true` in 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: true` in `user/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}`
|
|
- **Slugification:** filenames are slugified client-side before upload (spaces/special chars → hyphens, lowercase); the file is sliced to a plain `Blob` so the third argument to `FormData.append` is always used as the filename
|
|
- **Media type:** `.gpx` is registered in `user/config/media.yaml` so 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 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 content into `italy-2026-demo` trip (journal entries + stories + GPX)
|
|
- `make demo-reset` — remove demo entries (keeps trip page structure, removes entries only)
|
|
|
|
### 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/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
|
|
|
|
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:
|
|
|
|
```bash
|
|
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)
|
|
|
|
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:
|
|
|
|
```yaml
|
|
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.
|