CLAUDE.md is loaded into context on every request, so every line has a recurring cost. Applies one rule to decide what earns its place: keep what changes behaviour (rules and gotchas Claude cannot discover before it acts); extract what merely describes code (Claude reads the code anyway, and prose about code silently drifts). The four stale facts fixed in the previous commit were all in the "describes code" class -- active_trip, the Admin2 version, demo-load's scope, the gitignore list. None were rules. That is the argument for moving this material next to what it documents. Extracted (kept as pointers): - entry-map + trip-feed-col parameter contracts (56 lines) -> reference/ architecture.md "Shared partial contracts". CLAUDE.md keeps only the invariants: single map path, must assign window.tripMap/homeMap, keep trip-feed-col single-purpose, initTripStats depends on MapUtils. - Prod override runbook (49 -> 9 lines) -> guides/deploy-cycle.md "The env override tree", incl. the Twig dev/prod table and WEB_HOST. CLAUDE.md keeps the two behavioural rules: never commit prod values, and Admin on the server writes to the env tree (so check both config paths, env wins). - GPX API routes, session auth and the Blob/FormData upload gotcha -> guides/gpx-manager.md "How the manager is wired". - Trip-switch procedure -> guides/trip-switching.md. CLAUDE.md keeps the one rule that matters: never re-add pageconfig.parent to post-form.md. - Also trimmed the dev-command table and custom-plugin table added in the previous commit; both largely restated the Makefile and blueprints. Fixed the guides being pointed into, so the pointers lead to truth: - trip-switching.md instructed editing a pageconfig.parent that no longer exists -- its whole "two files must be updated together" premise was obsolete and would have reintroduced the desync it warned about. - architecture.md: Grav 2.0.4->2.0.7, Admin2 2.0.10->2.0.12, corrected the posting pipeline to show cache-on-save injecting parent before the write, added entry-actions to the custom-plugin list. - japan-korea-2026 -> denmark-2026 across guides/reference (docs/solutions keeps its historical references intact -- those are incident records). Verified: every markdown link resolves, every referenced section heading exists, and each extracted item was confirmed present in its new home. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
into the east — Grav CMS
Grav CMS travel blog. Local dev via Docker; production on a VPS managed entirely through make.
Repository structure
Two git repos:
| Repo | Contents | Location |
|---|---|---|
intotheeast.com (this repo) |
Docker setup, Makefile, scripts, plugins.txt | ./ |
intotheeast.com-content |
Site config, pages, theme | user/ (standalone git repo) |
The user/ directory is a standalone git repo — its changes are pushed/pulled independently to Gitea. The Grav Sync plugin on the server automatically pulls from Gitea when content is pushed.
Prerequisites
- Docker (for local dev)
- SSH access to the production server
- Both Gitea repos created and accessible
- A Gitea personal access token with repo read/write access
Local development setup
cp .env.example .env # fill in your values — never commit this file
make setup # start Docker container and install plugins
Site runs at http://localhost:8081.
Clone the user content repo into user/ if not already present:
git clone $USER_REPO user/
First-time server setup
1. Fill in .env — copy .env.example and set all values including REMOTE_USER, REMOTE_HOST, USER_REPO, MAIN_REPO, and Gitea credentials.
2. Run the install:
make remote-install
This SSHes into the server, downloads Grav, clones both repos (user content + this config repo), installs plugins, and prints the server's SSH public key.
3. Add the SSH key to Gitea — copy the printed public key and add it as a read-only deploy key to both Gitea repos. After this, make remote-fetch works without credentials.
Content sync workflow
To pull editor changes locally:
make content-pull # pull latest user/ content from Gitea → local
To push local changes to Gitea (triggers server sync):
git -C user add -A && git -C user commit -m "content: describe change"
make content-push # push local user/ commits → Gitea
All commands
Local
| Command | Description |
|---|---|
make start |
Start the local Docker container |
make stop |
Stop the local Docker container |
make setup |
Start container and install all plugins from plugins.txt |
make install-plugins |
(Re)install plugins from plugins.txt in the local container |
make content-push |
Push local user/ commits to Gitea |
make content-pull |
Pull latest user/ content from Gitea |
Remote credentials
| Command | Description |
|---|---|
make remote-env-setup |
Write Gitea credentials to ~/.env-intotheeast on the server |
make remote-env-remove |
Delete ~/.env-intotheeast from the server |
Always run make remote-env-remove when done. Credentials must not persist on the server.
Remote server management
| Command | Description |
|---|---|
make remote-install |
First-time install: download Grav, clone both repos, install plugins |
make remote-fetch |
Pull latest config repo (Makefile, scripts, plugins.txt) on the server |
make remote-install-plugins |
Install/update plugins from local plugins.txt on the server |
make remote-upgrade-grav |
Upgrade Grav core on the server |
make remote-clean |
Clear Grav cache on the server |
make remote-maintenance-on |
Enable maintenance mode (visitors see offline page) |
make remote-maintenance-off |
Disable maintenance mode |
Typical upgrade workflow
make remote-maintenance-on
make remote-upgrade-grav
make remote-install-plugins
make remote-clean
make remote-maintenance-off
Plugins
Plugins are not committed to git. The full list is in plugins.txt — one plugin name per line.
- Locally:
make install-plugins - On server:
make remote-install-plugins
Template behaviour
Key design decisions that affect how pages render:
| Context | Sort order | Reason |
|---|---|---|
Trip page (trip.html.twig) |
Ascending (oldest first) | Trip reads as a narrative from start to finish |
Homepage active-trip feed (home.html.twig) |
Descending (newest first) | Visitors want to see what's happening right now |
Homepage modes — controlled by travelling in user/config/site.yaml:
travelling |
Homepage shows |
|---|---|
true |
Active trip map + chronological feed (newest first) |
false |
Map with highlight markers + curated highlights grid (max 6, 1 per trip, random) |
Entries and stories opt into the highlights grid via featured: true in their frontmatter. The active_trip field stores a full page route (e.g. /trips/italy-2026-demo), not a bare slug.
Per-trip map settings — configurable in Admin2 under the Trip tab:
| Setting | Values | Default | Notes |
|---|---|---|---|
use_gpx |
Yes / No | Yes | Draws uploaded GPX files as route lines on the map |
autoconnect |
off / on / manual / intelligent_gpx | on | Controls connector lines between location markers |
Connect markers behaviour:
| Value | Behaviour |
|---|---|
off |
No connector lines; force_connect on entries is also ignored |
on |
Dashed connector between every entry in date order |
manual |
No automatic lines; only entries with force_connect: true are linked |
intelligent_gpx |
Suppresses the connector where a GPX track covers the route; force_connect overrides. Requires use_gpx enabled — falls back to on if GPX is off or no files are present |
use_gpx and autoconnect are independent: you can show GPX tracks without connector lines or vice versa.
Security
.envis gitignored. Never commit it — it contains your server credentials and Gitea token.GITEA_TOKENexists only in.envlocally, and in~/.env-intotheeaston the server only during active sessions. Always runmake remote-env-removeafter use.~/.env-intotheeasthaschmod 600— readable only by the SSH user.- The server pulls from Gitea using its SSH deploy key (read-only). No long-lived token is stored on the server after initial install.
scripts/server-install.shwrites~/.netrcfor the initial clone only, and deletes it immediately after via atraphandler — even if the script fails.- Credentials are never passed as command-line arguments (they would appear in server process listings). They are passed as environment variables within the SSH session.