# 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 — its own git repo (`intotheeast-com-content.git`), tracked by the outer repo as a **git submodule** (pinned commit). See "Dual-repo submodule structure" below and `docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md` - **docs/**: All plans, specs, and project documentation (moved here from `user/docs/` on 2026-06-19) - **docs/solutions/**: documented solutions to past problems (bugs, patterns, workflow gotchas), organized by category with YAML frontmatter (`module`, `tags`, `problem_type`). Relevant when implementing or debugging in a documented area - **CONCEPTS.md** (repo root): shared domain vocabulary (Trip, Entry, Story, Active Trip). Relevant when orienting to the codebase or discussing domain concepts ### Current stack - **Grav:** 2.0.4 stable (baked into the custom Docker image via `Dockerfile`; server upgrades in place via `bin/gpm self-upgrade`) - **Admin:** Admin2 v2.0.10 (plugin slug: `admin2`, NOT `admin`) - **GPM channel:** `stable` — set in `user/config/system.yaml` → `gpm.releases` (authoritative). `GRAV_CHANNEL=production` in `docker-compose.yml` is cosmetic/consistency only - **Plugin management:** `admin2`, `api`, and `flex-objects` are now **GPM-managed via `plugins.txt`** (installed by `make install-plugins`), no longer hand-extracted from the core bundle. `git-sync` stays **remote-only** — never in `plugins.txt` - **Docker image:** `getgrav/grav` with `GRAV_CHANNEL=production` - **PHP session:** `session.save_path = /tmp` set in `php/php-local.ini` > Known issue (2026-07-04): Form 9.1.10 regressed the `filepond` upload field — on the post-submit re-render, `filepond.html.twig` runs `merge` on a string and 500s. The journal entry still saves correctly; only the browser re-render errors. This breaks the 6 `post.spec.js` UI specs. Being fixed separately in the form-to-page/image-upload rework — **do not** work around it here. ### 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//` - Each trip has two content subfolders: `01.dailies/` (journal entries) and `04.stories/` (stories). The former `02.map/` and `03.stats/` standalone views were **removed** (2026-07-04, see `docs/working/plans/2026-07-04-standalone-page-cleanup.md`) — map and stats now render inline on the trip page - `01.dailies/` and `04.stories/` are `routable:false` **data containers** — visiting `/trips//dailies` or `/stories` directly 404s/redirects; their children (entries/stories) render at their own detail URLs and are aggregated by the trip page - 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). The standalone `/dailies`, `/map`, `/stats`, `/stories` view pages no longer exist — do NOT try to re-create them or link to them. This filter bar + stats chrome is shared with the home active-trip view via the `trip-feed-col` partial (see "Shared trip-feed-col partial" below) - Stats are shown inline on the trip page via a toggle (the standalone `/stats` view was removed) - GPX route files live as media on the trip page itself, parsed client-side via toGeoJSON (bundled into `js/map.js`) and drawn on the trip/home map - Manage GPX files (view/upload/delete) at `/gpx-manager` — requires admin login; filenames are auto-slugified on upload ### One map path: `MapUtils.initEntryMap` + the `entry-map` partial There is a **single** map code path on the site. The engine is `MapUtils.initEntryMap(opts)` in `js/src/maplibre-utils.js` (bundled into `js/map.js` via `make build-assets` — never hand-edit `js/map.js`). It builds the MapLibre map, places markers/popups, fits bounds, draws the GPX journey, and wires the fullscreen toggle. The map **markup + invocation** is shared via one partial: - **Partial:** `user/themes/intotheeast/templates/partials/entry-map.html.twig` - **Used by:** `trip.html.twig` and the active branch of `home.html.twig` (both via `{% include ... with {...} only %}`) It renders the `.home-map-col` column (map div `#{{ map_id }}` + fullscreen button) and, when `entries` is non-empty, a thin `