Five weeks of undocumented evolution left the docs describing a site that
partly no longer exists, with nothing marking which documents were historical.
The code is treated as the source of truth throughout; every claim below was
verified against code, config or the Makefile rather than inferred.
Two mechanisms, following patterns the repo already used:
- docs/reference/superseded-decisions.md (new) — one authoritative table of all
14 reversals: what was planned, where, what is true now, when, and why. Plus
a short list of decisions that were NOT reversed, since their planning docs
are old enough to look suspect.
- Inline "> **Superseded ...**" notes at each stale claim, so a claim can never
be read un-corrected. This mirrors the existing "> History:" notes in
architecture.md and "> **Changed 2026-07:**" in trip-switching.md.
Scope split by tense: present-tense docs (CLAUDE.md, reference/, guides/,
README.md, CONCEPTS.md) are corrected; past-tense records (plans/, specs/,
milestones/, summary.md, pm-analysis.md) are annotated only, never rewritten —
their staleness is what makes them records.
Present-tense corrections:
- CLAUDE.md asserted css-compiled/ is generated from css/style.css and
css/tokens.css. That source relationship does not exist: css/ is hand-authored
and served directly via assets.addCss in partials/base.html.twig, while
css-compiled/ is esbuild output from the CSS imports inside js/src/*.js.
Highest-severity finding — an always-loaded file inviting a hand-edit of a
generated bundle.
- README.md documented every remote-* target without the -test/-prod suffix
guard-env requires, so its entire server runbook was unrunnable, and listed
7 of ~20 targets while CLAUDE.md designates it authoritative for the full
list. Rewritten with all targets, grouped, and the suffix rule stated.
- README.md told readers to "git clone" into user/, which is a submodule.
- architecture.md: nav is Home + Trips + (authenticated) New Post, not
"Home + Past Trips only"; template tree omitted trips.html.twig,
post-form.html.twig and forms/, and placed base.html.twig at templates/ root
rather than in partials/; entry-actions has three API routes, not just delete;
added the undocumented css-compiled/maplibre-gl.css output and a section on
the /post pin editor as the one sanctioned non-entry-map map.
- design-system.md: documented 13 colour tokens against 19 in tokens.css
(missing --color-error, --color-draft-accent and four glass overlays); claimed
"all 3 map templates"; and described --color-canvas as "white".
- design-system-light.md documented a light palette in present tense. No light
mode exists — tokens.css has a single :root block, no prefers-color-scheme or
data-theme switch, and no light hex appears in css/. Banner added.
- posting.md said photos were optional; they are required, 1-6. It documented
hero_image, which was removed for entries (stories keep it). It had no mention
of the frontend edit flow or photo editor, both shipped 2026-07-08. Photo
files are photo-01..NN, zero-padded.
- working/README.md advertised summary.md as the project's "current state" while
summary.md describes Leaflet, /tracker, /map and /stats. Most misleading line
in the tree.
- CLAUDE.md: recorded js/src/location-map.js as the one sanctioned exception to
the single-map-path rule, and documented that make start/setup fail on a clean
checkout because docker-compose.yml still builds a travel-memories service
whose source moved out in a80b0a9.
Also: 2026-07-23-post-form-location-override.md read "Not started" while merged
in user/ as dd19995; status corrected.
Findings that are not documentation problems — the compose breakage, a
repeatable drift check, the unused shortcode-gallery-plusplus, and demo fixtures
for retired views — are collected in
docs/working/2026-07-25-doc-drift-recommendations.md and deliberately not acted
on. Design and verification method: docs/working/specs/2026-07-25-docs-reconciliation-design.md
The submodule gitlink is deliberately not bumped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
9.2 KiB
Markdown
78 lines
9.2 KiB
Markdown
# CLAUDE.md
|
|
|
|
Rules, gotchas, and entry points — the things that must change what you do *before* you open a file. Everything descriptive lives next to the code:
|
|
|
|
| Need | Read |
|
|
|---|---|
|
|
| How the site hangs together — stack, plugin roles, templates, partial contracts, data flows | [`docs/reference/architecture.md`](docs/reference/architecture.md) |
|
|
| Domain vocabulary — Trip, Entry, Story, Active Trip | [`CONCEPTS.md`](CONCEPTS.md) |
|
|
| Doing something operational — posting, GPX, switching trips, local setup, deploying | [`docs/guides/`](docs/guides/) |
|
|
| Test suite layout and conventions | [`docs/reference/testing.md`](docs/reference/testing.md) |
|
|
| A bug or workflow trap already hit and written up | [`docs/solutions/`](docs/solutions/) — grep the `module`/`tags`/`problem_type` frontmatter; check when working in a documented area |
|
|
| Why an old plan describes something that no longer exists | [`docs/reference/superseded-decisions.md`](docs/reference/superseded-decisions.md) — check before re-creating anything found in `docs/working/` |
|
|
| Folder map, prerequisites, the full `make` command list | [`README.md`](README.md) |
|
|
|
|
The site is Grav (flat-file PHP CMS, no database) in Docker, with content and theme in the `user/` submodule.
|
|
|
|
## Hard rules
|
|
|
|
- **Only ever write inside `travel-blog-intotheeast/`** or its subfolders.
|
|
- **Never read `.env`, `.env.prod`, `.env.test`** — they hold credentials. Pass them to commands (`make`, `docker compose`) but never read them; ask the user if you need a value.
|
|
- **Never SSH to a server directly** — use the `make remote-*` targets, since credentials live in `.env`. If no target covers what you need, ask the user to run it or propose a new target.
|
|
- **Never hand-edit build output** — sources and outputs share folders under `user/themes/intotheeast/` (paths below are relative to it), so know which is which. Run `make build-assets` after editing any source.
|
|
- Everything in `js/` is **generated** *except* `js/src/`, `js/maplibre-utils.js` and `js/nav.js`.
|
|
- `css-compiled/` and `fonts/` are **esbuild output from the imports inside `js/src/`** — *not* from `css/`. Everything in `css/` is hand-authored and served directly (`assets.addCss` in `partials/base.html.twig`), never compiled. So `templates/partials/weather-icons.html.twig` is also generated (source: `scripts/gen-weather-icons.js`).
|
|
- **Never toggle dev↔prod mode mid-session.** If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than flipping a mode flag — mode switches leave inconsistent state and make bugs harder to reproduce.
|
|
|
|
## Dev environment
|
|
|
|
- Dev server: **http://localhost:8081** (`make setup` on a first run, `make start` / `make stop` after). A worktree gets its own container and port `8090+` from its `.worktree-env` — pass `GRAV_BASE_URL` when pointing tests at one.
|
|
- ⚠️ **`make start` / `make setup` fail on a clean checkout** — `docker compose up -d` still tries to build a `travel-memories` service whose source was moved out of this repo, so the build context is missing. Use **`make start-grav`** (Grav only). Existing containers keep working from a cached image, which is why this hides until a rebuild.
|
|
- `user/config/system.yaml` is committed with **dev** values (`twig.cache: false`), so templates recompile per request and no cache flush is needed after editing a `.html.twig`. Prod values live in `deploy/env/prod/system.yaml` and **never** in `user/config/`.
|
|
- ⚠️ **Once `user/env/<hostname>/` exists on a server, Grav's Admin saves ALL config there** — system *and* plugin. So (a) config edited via Admin on the server is server-only and silently never reaches Gitea or local; (b) when reading or writing server config, check **both** `user/config/…` and `user/env/<host>/config/…` — **env wins**, so look there first. Mechanics: [`docs/guides/deploy-cycle.md`](docs/guides/deploy-cycle.md).
|
|
- The Admin plugin slug is **`admin2`**, not `admin`.
|
|
- `plugins.txt` is maintained by hand — installing a plugin via Admin does **not** update it. `git-sync` is **remote-only** and must never appear in it.
|
|
- Everything under `user/plugins/` is git-ignored and gets overwritten by `make install-plugins` — **except** the three site-owned plugins (`cache-on-save`, `story-blocks`, `entry-actions`). So a fix to a third-party plugin must be a tracked patch in `deploy/patches/`, never an in-place edit: [`deploy/patches/README.md`](deploy/patches/README.md).
|
|
|
|
## Content and trips
|
|
|
|
- The active trip lives in **one** place: `user/config/site.yaml` → `active_trip`, and its value is a **route** (`/trips/denmark-2026`), not a bare slug.
|
|
- `cache-on-save` derives the post write target from `active_trip` at submit time. **Never re-add a `pageconfig.parent` to `post-form.md`** — a static parent would override it and reintroduce the old silent-desync bug. Switching trips: [`docs/guides/trip-switching.md`](docs/guides/trip-switching.md).
|
|
- The standalone `/dailies`, `/map`, `/stats` and `/stories` trip views were **deleted** (2026-07-04) — map, stats, and filtering all render inline on the trip page. Do not re-create them or link to them. `01.dailies/` and `04.stories/` are `routable:false` data containers whose children are aggregated by the trip page.
|
|
- GPX routes are page media on the trip page, auto-detected — no manual linking. Manage them at `/gpx-manager` (admin login): [`docs/guides/gpx-manager.md`](docs/guides/gpx-manager.md).
|
|
- `make content-push` commits and pushes `user/` to Gitea, which triggers the production pull; `make content-pull` is the reverse.
|
|
|
|
## Two shared partials — the rules
|
|
|
|
Trip and home render the same map and feed chrome through two shared partials, both included `with {…} only`. Parameter contracts: [`docs/reference/architecture.md`](docs/reference/architecture.md) → "Shared partial contracts". What must not break:
|
|
|
|
- **`partials/entry-map.html.twig` is the only path for a *display* map** — the engine is `MapUtils.initEntryMap(opts)` in `js/maplibre-utils.js` (a hand-authored file, imported by `js/src/map.js`). Do not add a third; an older three-variant setup was deliberately consolidated away.
|
|
- **One sanctioned exception:** `js/src/location-map.js` is the `/post` pin *editor* — one draggable marker, no popups/GPX/bounds, `maplibre-gl` lazy-imported so a GPS-only submit never fetches it. It shares exactly one thing with the display path, the style URL in `js/src/map-style.js`. Do not fold them together.
|
|
- It must keep assigning **`window.tripMap` / `window.homeMap`** — the Playwright map specs assert those globals.
|
|
- **Keep `trip-feed-col.html.twig` single-purpose.** Its sibling `partials/home-predeparture.html.twig` is the home-only "Coming soon" state — do **not** fold the pre-departure branch back into it.
|
|
|
|
## Dual-repo submodule structure
|
|
|
|
`user/` is a git submodule with its own Gitea remote and its own cadence; the outer repo pins an exact commit. Full workflow, worktree mechanics, teardown: [`docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md`](docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md).
|
|
|
|
- **`M user` / `m user` is normal, not an error.** `M` = the pin differs from `user/` HEAD; `m` = the submodule working tree is dirty (e.g. a local-testing `site.yaml`). Do not "fix" either by committing the gitlink or that `site.yaml`.
|
|
- **Don't bump the pin for routine content changes.** Bump it once at the end of a cross-repo feature, to a commit reachable from `user/`'s published `main`, and **push `user/` before the outer repo**.
|
|
- **Use `make worktree-new NAME=<x>` / `make worktree-rm NAME=<x>`** — never a hand-rolled `git worktree add`. The targets initialise the submodule and an isolated dev server; skipping the deinit on teardown is what leaves orphaned `.worktrees/` dirs.
|
|
|
|
## Testing
|
|
|
|
`make test` runs everything (`test-config` → `test-post` → `test-ui`). **The dev server must be running** — every suite drives the live site over HTTP. Layout, helpers, and per-suite commands: [`docs/reference/testing.md`](docs/reference/testing.md).
|
|
|
|
- **Auth is a dependency project.** `auth.setup.js` writes `tests/.auth/user.json`, which the `chromium` project reuses as `storageState`. Never add per-test logins.
|
|
- The `testrunner` admin account is created automatically and is git-ignored — never commit it, and keep its password free of shell/Make/URL-special characters, since several consumers interpolate it.
|
|
- `retries: 0`, so a failing test is a real failure, not flake.
|
|
|
|
## Working docs
|
|
|
|
Specs go in `docs/working/specs/YYYY-MM-DD-<topic>-design.md`, plans in `docs/working/plans/YYYY-MM-DD-<topic>.md`. These paths override the `docs/superpowers/` default used by the brainstorming and writing-plans skills.
|
|
|
|
Every plan needs a `**Status:**` line immediately after its title heading: `📋 Not started` · `🔄 In progress — <note>` · `⏸️ Deferred — <reason>` · `✅ Complete (YYYY-MM-DD)` · `❌ Abandoned — <reason>`.
|
|
|
|
- **When asked what's open:** surface `Not started` and `In progress`; show `Deferred` but label it clearly; omit `Complete` and `Abandoned` unless explicitly asked.
|
|
- **When finishing a plan:** set its status to `✅ Complete (YYYY-MM-DD)` before closing the session — whether you executed it directly or via the executing-plans / subagent-driven-development skills.
|