m038andClaude Opus 5 7c9c140a1b Merge main into feat/docs-reconcile; defer to main on two overlapping fixes
main advanced 13 commits while this documentation audit ran — the
location-override work was merged into the outer repo — and it independently
fixed two of the audit's findings. Without this merge the branch would have
REVERTED them, which is the worst outcome for a cleanup pass because it arrives
disguised as an improvement.

Both conflicts resolved in main's favour; main's wording was better informed:

- CLAUDE.md, the single-map-path rule: main's carve-out (829325c) states the
  exception as its own top-level bullet, names MAP_STYLE as the one shared
  thing, and spells out both prohibitions ("do not fold it into initEntryMap",
  "do not add a third path"). Taken verbatim over the version drafted here.
- 2026-07-23-post-form-location-override.md: main (a517331) had already set the
  status to Complete, with far richer detail — the multi-agent review findings,
  the green-run numbers, the DEL4 regression still open, and the merge SHAs.
  Taken in full; the audit's claim that the status "lagged" was dropped, since
  it was true only of this branch's older branch point.

Submodule pin: main bumped user/ to dd19995 and this merge preserves that. The
audit's own no-gitlink-commit discipline applies to bumping the pin as a side
effect of routine work, not to discarding a bump main already made.

main touched none of the other nine corrected documents, so the remaining 18
findings stand unchanged.

Audit notes corrected to match reality rather than left overstated:

- superseded-decisions.md R13 now dates the carve-out to 2026-07-24 (829325c)
  rather than implying this pass introduced it.
- The reconciliation spec gains an "audit baseline moved twice" section: the
  submodule pin lagged real HEAD, and then the base branch advanced mid-audit.
- The compounded learning's section 6 is rewritten from "audit the current
  state" to "re-check the baseline before publishing, not only before starting",
  with the two habits that actually follow: merge the base branch in before
  opening the PR and read conflicts as findings, and when the incoming version
  is better, take it wholesale. An audit has no special authority over the work
  it audits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 00:21:47 +02:00

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, tests, docs, plugins.txt ./
intotheeast.com-content Site config, pages, theme user/ (git submodule)

user/ is tracked by this repo as a git submodule — it has its own Gitea remote and its own push/pull cadence (make content-push / make content-pull), and this repo pins an exact user/ commit. The Git Sync plugin on the server pulls from Gitea automatically when content is pushed. A persistent M user / m user in git status is normal, not a problem; see docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md.

Folder map

Path Contents
user/ Site content, config, pages, theme (the content submodule)
user/themes/intotheeast/js/src/ JS sources — esbuild inputs; run make build-assets after editing. Note js/maplibre-utils.js and js/nav.js are also sources, despite sitting beside the generated bundles
deploy/env/ Per-environment Grav config overrides (e.g. prod Twig settings)
deploy/patches/ Tracked patches for third-party plugins, which are otherwise git-ignored
scripts/ Server install and maintenance scripts
tests/ Playwright suite — see docs/reference/testing.md
php/ Local PHP ini overrides
docs/ All project documentation — start at docs/README.md
docs/guides/ Operational how-tos (posting, GPX, trip switching, setup, deploy cycle)
docs/reference/ Stable facts: architecture, design system, testing
docs/solutions/ Write-ups of bugs and workflow traps already hit, with YAML frontmatter (module, tags, problem_type)
docs/working/ Specs, plans, backlog, QA — work in flight
CONCEPTS.md Shared domain vocabulary (Trip, Entry, Story, Active Trip)
CLAUDE.md Rules and gotchas loaded into every Claude Code session

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
git submodule update --init user
mkdir -p user/plugins user/data
make build && make start-grav && make install-plugins && make fix-perms

Site runs at http://localhost:8081.

user/ is a git submodule — initialise it with git submodule update --init user. Do not git clone into user/ by hand; that detaches it from the pin the outer repo tracks.

⚠️ Use make start-grav, not make setup, on a clean checkout. make setup runs make start (docker compose up -d), which still tries to build the travel-memories service — but its source was moved to a separate project (a80b0a9) and services/ is gitignored, so the build context is missing and the command fails. make start-grav brings up Grav only. Machines with a cached travel-memories image will not see this until their next rebuild. See docs/reference/superseded-decisions.md → R11.


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-prod     # or -test

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-prod 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 setup First run: build → start → install plugins → fix perms. ⚠️ Currently fails on a clean checkout — see the setup note above; use the start-grav sequence instead
make start Start all compose services. ⚠️ Fails where services/travel-memories is absent
make start-grav Start the Grav service only — the reliable option
make stop Stop the local Docker container
make install-plugins (Re)install plugins from plugins.txt, then apply local plugin patches
make apply-plugin-patches Idempotently re-apply the patches in deploy/patches/
make fix-perms Reset file ownership inside the container
make build-assets Run esbuild over user/themes/intotheeast/js/src/required after editing any JS source
make content-push Push local user/ commits to Gitea (triggers the production pull)
make content-pull Pull latest user/ content from Gitea

Testing

Command Description
make test Everything: test-configtest-posttest-ui
make test-config Form/config sanity checks
make test-post End-to-end post submission
make test-ui Playwright suite

Details and conventions: docs/reference/testing.md.

Demo content and imports

Command Description
make demo-load Copy every fixture trip under user/docs/demo/trips/ into the pages tree (add a fixture by dropping a folder there — no Makefile edit needed)
make demo-reset Remove those demo trips from the pages tree and clear cache
make pixelfed-import Import posts from Pixelfed via scripts/pixelfed-import.py

Parallel work

Command Description
make worktree-new NAME=<feature> Create a worktree with its own user/ checkout and an isolated dev server on port 8090+
make worktree-rm NAME=<feature> Tear one down cleanly (compose down → submodule deinit → worktree remove → prune)

Remote targets — every one needs an environment suffix

All remote-* targets require -test or -prod. A bare make remote-fetch fails via guard-env with "no environment. Use an env-suffixed target". The suffixed variants are generated by a macro in the Makefile, so they will not show up in a grep for literal target names.

The runbook for shipping a change through test → prod is docs/guides/deploy-cycle.md. The tables below are the inventory.

Credentials — always run remote-env-remove-<env> when done; credentials must not persist on the server.

Command Description
make remote-env-setup-<env> Write Gitea credentials to ~/.env-intotheeast on the server
make remote-env-remove-<env> Delete ~/.env-intotheeast from the server
make remote-secrets-audit-<env> Check the server for exposed secrets
make remote-seed-api-salt-<env> Generate the API/CSRF salt on the server

Install and sync

Command Description
make remote-install-<env> First-time install: download Grav, clone both repos, install plugins
make remote-fetch-<env> Pull latest config repo (Makefile, scripts, plugins.txt) on the server
make remote-fetch-content-<env> Pull latest user/ content on the server
make remote-content-status-<env> Show the server's content-repo state
make remote-apply-env-<env> Apply deploy/env/<env>/ config into the server's env tree — re-run after any fresh install
make remote-apply-plugin-patches-<env> Re-apply deploy/patches/ on the server

Plugins and core

Command Description
make remote-install-plugins-<env> Install plugins from local plugins.txt on the server
make remote-update-plugins-<env> Update installed plugins via GPM
make remote-gpm-install-<env> Install a single plugin via GPM
make remote-upgrade-grav-<env> Upgrade Grav core on the server (in place — servers have no image)

Operations

Command Description
make remote-clean-<env> Clear Grav cache on the server
make remote-warmup-<env> Clear and warm the cache after a deploy
make remote-maintenance-on-<env> Enable maintenance mode (visitors see offline page)
make remote-maintenance-off-<env> Disable maintenance mode
make remote-diag-<env> Diagnostics on the server
make remote-git-sync-enable-<env> / -disable-<env> Toggle the remote-only git-sync plugin
make remote-wipe-<env> ⚠️ Destroy the server install

Typical upgrade workflow

Run against test first — it is a full dress rehearsal of prod.

make remote-maintenance-on-prod
make remote-upgrade-grav-prod
make remote-install-plugins-prod
make remote-apply-env-prod        # env tree is not restored by anything else
make remote-warmup-prod
make remote-maintenance-off-prod

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

  • .env is gitignored. Never commit it — it contains your server credentials and Gitea token.
  • GITEA_TOKEN exists only in .env locally, and in ~/.env-intotheeast on the server only during active sessions. Always run make remote-env-remove after use.
  • ~/.env-intotheeast has chmod 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.sh writes ~/.netrc for the initial clone only, and deletes it immediately after via a trap handler — 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.
S
Description
No description provided
Readme
1.5 MiB
Languages
JavaScript 81.7%
Makefile 10.4%
Shell 6.3%
Python 1.2%
Dockerfile 0.4%