lightbox-dims.spec.js hardcoded ../../../user, so a run against a checkout
detached from the served tree planted its fixture in a different user/ than
Grav renders and LD1 failed as an opaque "card never appeared" timeout.
Take USER_DIR from helpers instead, which honours GRAV_USER_DIR.
The three specs in this folder that fail do so for real, pre-existing
reasons, and both files' headers implied otherwise:
- UG1/UG2 specify a submit gate that is not implemented. post-form.js's
only create-form guard is `converting > 0` (pre-FilePond HEIC
conversion); it never inspects FilePond item state at submit time, and
.photo-convert-status is created lazily by photoStatusEl() only from the
HEIC paths — so for a plain JPEG the element never exists and both
expectations fail as "element(s) not found". UG2 is the one that matters:
a failed upload keeping its thumbnail is unguarded silent data loss.
- LD1's header described its root cause in the past tense, reading as
fixed. entry-journal.html.twig:48-49 still emits {{ img.width }} /
{{ img.height }}, so EXIF-rotated photos still declare pre-rotation dims
and PhotoSwipe still squeezes them.
Left failing rather than skipped, per retries:0 — a red test here is a real
defect, and hiding these would lose both.
Co-Authored-By: Claude Opus 5 <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.