Compare commits

1 Commits
Author SHA1 Message Date
m038andClaude Opus 5 8202d2a257 docs: reconcile documentation against the code; add a supersession ledger
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>
2026-07-25 00:13:37 +02:00
18 changed files with 616 additions and 63 deletions
+6 -3
View File
@@ -9,6 +9,7 @@ Rules, gotchas, and entry points — the things that must change what you do *be
| Doing something operational — posting, GPX, switching trips, local setup, deploying | [`docs/guides/`](docs/guides/) | | 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) | | 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 | | 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) | | 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. The site is Grav (flat-file PHP CMS, no database) in Docker, with content and theme in the `user/` submodule.
@@ -20,12 +21,13 @@ The site is Grav (flat-file PHP CMS, no database) in Docker, with content and th
- **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 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. - **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`. - Everything in `js/` is **generated** *except* `js/src/`, `js/maplibre-utils.js` and `js/nav.js`.
- `css-compiled/` and `fonts/` are generated (sources: `css/style.css`, `css/tokens.css`); so is `templates/partials/weather-icons.html.twig` (source: `scripts/gen-weather-icons.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. - **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 environment
- Dev server: **http://localhost:8081** (`make setup` on a first run, `make start` / `make stop` after). A second service, `travel-memories`, runs on :8082. A worktree gets its own container and port `8090+` from its `.worktree-env` — pass `GRAV_BASE_URL` when pointing tests at one. - 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/`. - `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). - ⚠️ **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`. - The Admin plugin slug is **`admin2`**, not `admin`.
@@ -44,7 +46,8 @@ The site is Grav (flat-file PHP CMS, no database) in Docker, with content and th
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: 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 map path** — 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 second map implementation; an older three-variant setup was deliberately consolidated away. - **`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. - 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. - **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.
+67 -28
View File
@@ -48,17 +48,23 @@ Two git repos:
## Local development setup ## Local development setup
```bash ```bash
cp .env.example .env # fill in your values — never commit this file cp .env.example .env # fill in your values — never commit this file
make setup # start Docker container and install plugins 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. Site runs at http://localhost:8081.
Clone the user content repo into `user/` if not already present: `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.
```bash > ⚠️ **Use `make start-grav`, not `make setup`, on a clean checkout.** `make setup` runs `make start`
git clone $USER_REPO user/ > (`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`](docs/reference/superseded-decisions.md) → R11.
--- ---
@@ -69,12 +75,12 @@ git clone $USER_REPO user/
**2. Run the install:** **2. Run the install:**
```bash ```bash
make remote-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. 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. **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.
--- ---
@@ -101,8 +107,9 @@ make content-push # push local user/ commits → Gitea
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `make setup` | First run: build → start → install plugins → fix perms | | `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 the local Docker container | | `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 stop` | Stop the local Docker container |
| `make install-plugins` | (Re)install plugins from plugins.txt, then apply local plugin patches | | `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 apply-plugin-patches` | Idempotently re-apply the patches in `deploy/patches/` |
@@ -137,35 +144,67 @@ Details and conventions: [`docs/reference/testing.md`](docs/reference/testing.md
| `make worktree-new NAME=<feature>` | Create a worktree with its own `user/` checkout and an isolated dev server on port `8090+` | | `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) | | `make worktree-rm NAME=<feature>` | Tear one down cleanly (compose down → submodule deinit → worktree remove → prune) |
### Remote credentials ### 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`](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 | | Command | Description |
|---------|-------------| |---------|-------------|
| `make remote-env-setup` | Write Gitea credentials to `~/.env-intotheeast` on the server | | `make remote-env-setup-<env>` | Write Gitea credentials to `~/.env-intotheeast` on the server |
| `make remote-env-remove` | Delete `~/.env-intotheeast` from 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 |
Always run `make remote-env-remove` when done. Credentials must not persist on the server. **Install and sync**
### Remote server management
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `make remote-install` | First-time install: download Grav, clone both repos, install plugins | | `make remote-install-<env>` | 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-fetch-<env>` | 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-fetch-content-<env>` | Pull latest `user/` content on the server |
| `make remote-upgrade-grav` | Upgrade Grav core on the server | | `make remote-content-status-<env>` | Show the server's content-repo state |
| `make remote-clean` | Clear Grav cache on the server | | `make remote-apply-env-<env>` | Apply `deploy/env/<env>/` config into the server's env tree — **re-run after any fresh install** |
| `make remote-maintenance-on` | Enable maintenance mode (visitors see offline page) | | `make remote-apply-plugin-patches-<env>` | Re-apply `deploy/patches/` on the server |
| `make remote-maintenance-off` | Disable maintenance mode |
**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 ### Typical upgrade workflow
Run against `test` first — it is a full dress rehearsal of prod.
```bash ```bash
make remote-maintenance-on make remote-maintenance-on-prod
make remote-upgrade-grav make remote-upgrade-grav-prod
make remote-install-plugins make remote-install-plugins-prod
make remote-clean make remote-apply-env-prod # env tree is not restored by anything else
make remote-maintenance-off make remote-warmup-prod
make remote-maintenance-off-prod
``` ```
--- ---
+6 -1
View File
@@ -15,9 +15,14 @@
**Design or architecture decisions?** → [`reference/`](reference/) **Design or architecture decisions?** → [`reference/`](reference/)
- [Design system](reference/design-system.md) - [Design system](reference/design-system.md)
- [Architecture overview](reference/architecture.md) - [Architecture overview](reference/architecture.md) — the site as it actually is
- [Superseded decisions](reference/superseded-decisions.md) — what was planned, then reversed, and why
- [Testing](reference/testing.md) - [Testing](reference/testing.md)
> Documents under [`working/`](working/) are historical records. If one describes something that no
> longer exists, [`reference/superseded-decisions.md`](reference/superseded-decisions.md) says what
> replaced it.
--- ---
## If you're Claude ## If you're Claude
+73 -18
View File
@@ -1,18 +1,23 @@
# Posting a Journal Entry # Posting a Journal Entry
Two ways to post: the **mobile form** at `/post` (quick, phone-friendly) or the **Admin panel** at `/admin` (drafts, scheduling, editing). Two ways to post: the **mobile form** at `/post` (quick, phone-friendly) or the **Admin panel** at `/admin` (scheduling, bulk edits). The `/post` form also **edits** existing entries — see below.
--- ---
## Quick start — mobile form ## Quick start — mobile form
1. Open `/post` on your phone (login required) 1. Open `/post` on your phone (login required)
2. Fill in **Title** and **Content** (required) 2. **Attach 16 photos** — photos come first because they anchor what you write. **At least one is required**; the form collapses them into a summary bar once uploaded
3. Tap **Get Location** → fills Lat/Lng automatically 3. Fill in **Title** and **Content** (required)
4. Tap **Get Weather** → fills weather fields using your coordinates 4. Tap **Get Location** → fills Lat/Lng, then reverse-geocodes City + Country for you
5. Type **City** and **Country** (optional but nice) 5. Tap **Get Weather** → fills weather fields using those coordinates
6. Attach photos (optional) — first photo becomes the hero image 6. Optional: open **More location details** to search for a place by name, or drag the pin on the map to place it exactly
7. Tap **Submit** → entry appears in the feed immediately 7. Optional: open **More options** for transport mode, publish state, connector and highlight toggles
8. Tap **Submit** → entry appears in the feed immediately
> **Photos are mandatory (16).** This changed during the 2026-07 post-form work — an entry with no
> photo will not submit. The first photo in the grid is the hero; reorder by dragging to change it.
> See [`../reference/superseded-decisions.md`](../reference/superseded-decisions.md) → R7, R8.
--- ---
@@ -20,14 +25,20 @@ Two ways to post: the **mobile form** at `/post` (quick, phone-friendly) or the
| Field | Required | Notes | | Field | Required | Notes |
|---|---|---| |---|---|---|
| Photos | ✅ | **16 per entry.** HEIC is converted to JPEG in the browser. First photo = hero; drag to reorder |
| Title | ✅ | Entry headline | | Title | ✅ | Entry headline |
| Content | ✅ | Markdown body | | Content | ✅ | Markdown body |
| Date | ✅ | Defaults to now — adjust if posting later | | Date | ✅ | Defaults to now — adjust if posting later |
| Lat / Lng | — | Filled by Get Location; used for map marker | | Lat / Lng | — | Filled by Get Location, by place search, or by dragging the map pin |
| City | — | Shown as `📍 Kyoto, Japan` on feed cards | | City | — | Auto-filled by reverse geocoding after Get Location; shown as `📍 Kyoto, Japan` on feed cards |
| Country | — | Combined with City in location badge | | Country | — | Combined with City in the location badge |
| Weather | — | Filled by Get Weather (Open-Meteo, free, no key) | | Weather | — | Filled by Get Weather (Open-Meteo, free, no key) |
| Photos | — | All uploaded files appear in the gallery; first = hero | | How I got here | — | `transport_mode`: walking · bicycle · bus · train · car · plane |
| Published | — | Advanced. Default **Yes**. Set No to keep a draft, or to unpublish on edit |
| Force connector line | — | Advanced. Default No. Forces a map connector to this entry even when suppressed |
| Featured highlight | — | Advanced. Default No. Opts the entry into the home highlights grid |
The advanced three sit behind **More options**. There is **no `hero_image` field** — see the note above.
**Weather descriptions** (must be one of these if entered manually): **Weather descriptions** (must be one of these if entered manually):
`Sunny` · `Partly cloudy` · `Cloudy` · `Foggy` · `Drizzle` · `Rain` · `Snow` · `Thunderstorm` `Sunny` · `Partly cloudy` · `Cloudy` · `Foggy` · `Drizzle` · `Rain` · `Snow` · `Thunderstorm`
@@ -40,8 +51,9 @@ Two ways to post: the **mobile form** at `/post` (quick, phone-friendly) or the
Browser → /post (post-form.md) Browser → /post (post-form.md)
└─ Grav Form plugin validates fields └─ Grav Form plugin validates fields
└─ cache-on-save injects parent from site.active_trip └─ cache-on-save injects parent from site.active_trip
└─ add-page-by-form plugin └─ and sets overwrite_mode: edit when edit_path is filled, else false
├─ writes user/pages/01.trips/<active_trip>/01.dailies/<slug>/entry.md └─ add-page-by-form plugin (patched — see deploy/patches/)
├─ writes user/pages/01.trips/<active_trip>/01.dailies/<slug>.entry/entry.md
└─ moves uploaded photos into the page folder └─ moves uploaded photos into the page folder
└─ cache-on-save plugin └─ cache-on-save plugin
└─ calls $grav['cache']->deleteAll() → entry visible immediately └─ calls $grav['cache']->deleteAll() → entry visible immediately
@@ -55,16 +67,22 @@ Example: `2026-07-20-0930-first-day-in-kyoto.entry`
``` ```
user/pages/01.trips/denmark-2026/01.dailies/ user/pages/01.trips/denmark-2026/01.dailies/
└─ 2026-07-20-0930-first-day-in-kyoto.entry/ └─ 2026-07-20-0930-first-day-in-kyoto.entry/
├─ entry.md ← frontmatter + markdown body ├─ entry.md ← frontmatter + markdown body
├─ temple.jpg hero image (or set hero_image in frontmatter) ├─ photo-01.jpg ← first in order, so this is the hero
└─ market.jpg ← additional gallery image └─ photo-02.jpg ← additional gallery image
``` ```
Photos are stored as `photo-01…NN` in display order — the numbering *is* the order, so reordering in
the form renames files on disk, and `photo-01` is always the hero. Names are **zero-padded** so
lexical sort matches numeric order (otherwise `photo-1, photo-10, photo-2…`); the pad width grows for
100+ photos. `PhotoRenumberer` in `cache-on-save` is the single source of truth for this invariant and
is shared with `entry-actions`.
--- ---
## Admin panel — drafts and scheduling ## Admin panel — drafts and scheduling
Use the Admin panel at `/admin` for drafts, scheduled posts, or editing existing entries. Use the Admin panel at `/admin` for **scheduling** (`publish_date`) and bulk or structural edits. For ordinary edits — text, photos, location, publish state — the `/post` form is quicker; see [Editing an entry](#editing-an-entry).
1. Log in at `/admin` 1. Log in at `/admin`
2. **Pages → Add Page** 2. **Pages → Add Page**
@@ -96,7 +114,36 @@ Every entry supports these frontmatter fields:
| `location_country` | string | e.g. `Japan` | | `location_country` | string | e.g. `Japan` |
| `weather_desc` | string | One of the allowed values above | | `weather_desc` | string | One of the allowed values above |
| `weather_temp_c` | number | Celsius, displayed rounded | | `weather_temp_c` | number | Celsius, displayed rounded |
| `hero_image` | string | Filename to pin as hero (e.g. `temple.jpg`); auto-selects first image if blank | | `transport_mode` | string | `walking` · `bicycle` · `bus` · `train` · `car` · `plane` |
| `force_connect` | bool | Force a map connector line to this entry even where it would be suppressed |
| `featured` | bool | Opt into the home page highlights grid |
> **No `hero_image` on journal entries.** The hero is whichever photo sorts first
> (`entry-journal.html.twig` uses `entry.media.images|first`), which the owner controls by
> reordering photos. **Stories still use `hero_image`** — they are not posted through this form.
> See [`../reference/superseded-decisions.md`](../reference/superseded-decisions.md) → R7.
---
## Editing an entry
The `/post` form doubles as the editor — you do not need Admin for ordinary edits.
1. Open the entry (or find it in the feed) while logged in
2. Use the entry's **Edit** action → `/post` opens pre-filled, with the hidden `edit_path` set to that
entry's path
3. Existing photos load into the grid. You can **add**, **remove**, and **drag to reorder** them
4. Submit → `cache-on-save` sets `overwrite_mode: edit`, so the entry is rewritten **in place**
rather than creating a new dated folder
Photo files on disk are renumbered to `photo-1…N` to match the displayed order, so the first photo is
always the hero. Reordering is a real file rename, handled server-side by `PhotoRenumberer` in the
`entry-actions` plugin via `POST /api/v1/entry/{slug}/photos/order`.
To **unpublish** an entry, edit it and set **Published** to No under *More options*.
Deleting an entry is also an entry action (`DELETE /api/v1/entry/{slug}`), owner-only and scoped to
the active trip.
--- ---
@@ -111,5 +158,13 @@ Every entry supports these frontmatter fields:
**Photos not showing in gallery** **Photos not showing in gallery**
→ Verify files were uploaded (check the entry folder in Admin → Media). Only jpg, jpeg, png, webp, gif are rendered. → Verify files were uploaded (check the entry folder in Admin → Media). Only jpg, jpeg, png, webp, gif are rendered.
**Submit button does nothing**
→ Check you have at least one photo attached, and that every upload has finished. The form blocks
submit while an upload is still in flight, and requires 16 photos.
**500 error after posting** **500 error after posting**
→ Run `make fix-perms` to restore container file ownership. → Run `make fix-perms` to restore container file ownership.
**Edits create a new entry instead of updating**
→ The hidden `edit_path` was empty, so `overwrite_mode` fell back to `false`. Re-enter via the entry's
Edit action rather than opening `/post` directly.
+33 -8
View File
@@ -14,7 +14,8 @@ How the intotheeast site hangs together.
| Container | Docker (`getgrav/grav` base + custom `Dockerfile`) | Grav 2.0 baked in at build time | | Container | Docker (`getgrav/grav` base + custom `Dockerfile`) | Grav 2.0 baked in at build time |
| PHP session | `session.save_path = /tmp` | Set in `php/php-local.ini` | | PHP session | `session.save_path = /tmp` | Set in `php/php-local.ini` |
| Dev URL | http://localhost:8081 | Mapped from container port 80 | | Dev URL | http://localhost:8081 | Mapped from container port 80 |
| Maps | MapLibre GL JS | Replaced Leaflet; one shared map path (`MapUtils.initEntryMap`) on trip + home | | Maps | MapLibre GL JS | Replaced Leaflet. One shared *display* path (`MapUtils.initEntryMap`) on trip + home, plus one sanctioned *editor* (`js/src/location-map.js`) for the `/post` pin picker |
| Basemap | CartoDB dark-matter | Style URL single-sourced as `MAP_STYLE` in `js/src/map-style.js`, imported by both map paths so they cannot drift |
| GPX rendering | toGeoJSON (bundled in `js/map.js`) | Parses GPX → GeoJSON route layers client-side; no CDN | | GPX rendering | toGeoJSON (bundled in `js/map.js`) | Parses GPX → GeoJSON route layers client-side; no CDN |
--- ---
@@ -54,7 +55,7 @@ Other notable plugins:
| `api` (Grav API v1) | Used by /gpx-manager to list/upload/delete GPX files | | `api` (Grav API v1) | Used by /gpx-manager to list/upload/delete GPX files |
| `admin2` | Admin panel at /admin | | `admin2` | Admin panel at /admin |
| `story-blocks` (custom) | Storytelling shortcode blocks for long-form stories (needs `shortcode-core`) | | `story-blocks` (custom) | Storytelling shortcode blocks for long-form stories (needs `shortcode-core`) |
| `entry-actions` (custom) | Owner-only, active-trip-scoped journal entry actions (delete) via the Grav API | | `entry-actions` (custom) | Owner-only, active-trip-scoped actions via the Grav API. Three routes: `DELETE /entry/{slug}`, `POST /entry/{slug}/photos/order`, `POST /trip/{slug}/publish`. Exists because stock `DELETE /api/v1/pages<route>` checks only write-permission (no trip scoping) and cannot renumber media to the `photo-NN` cover order |
### Plugin management model ### Plugin management model
@@ -76,10 +77,15 @@ Three categories, by how each plugin is installed and maintained:
| `js/src/map.js` | `js/map.js` + `css-compiled/map.css` — bundles `maplibre-gl`, `@mapbox/togeojson`, and `js/maplibre-utils.js` | | `js/src/map.js` | `js/map.js` + `css-compiled/map.css` — bundles `maplibre-gl`, `@mapbox/togeojson`, and `js/maplibre-utils.js` |
| `js/src/feed-actions.js` | `js/feed-actions.js` | | `js/src/feed-actions.js` | `js/feed-actions.js` |
| `js/src/trip-publish.js` | `js/trip-publish.js` | | `js/src/trip-publish.js` | `js/trip-publish.js` |
| `js/src/post-form.js` | `js/post/` (ESM + code splitting) + `css-compiled/post-form.css` | | `js/src/post-form.js` | `js/post/` (ESM + code splitting) + `css-compiled/post-form.css` — also pulls in `location-map.js` and `map-style.js` |
| `node_modules/maplibre-gl/dist/maplibre-gl.css` | `css-compiled/maplibre-gl.css` — built standalone so `location-map.js` can inject it on demand without a static import defeating its lazy load |
| `scripts/gen-weather-icons.js` | `templates/partials/weather-icons.html.twig` (Lucide SVGs inlined into a Twig map) | | `scripts/gen-weather-icons.js` | `templates/partials/weather-icons.html.twig` (Lucide SVGs inlined into a Twig map) |
**The trap:** `js/` holds both bundles *and* hand-authored sources. `js/maplibre-utils.js` (the `MapUtils` map engine, a plain IIFE imported by `js/src/map.js`) and `js/nav.js` are sources despite sitting beside the minified bundles. `css/style.css` and `css/tokens.css` are hand-authored too — only `css-compiled/` is generated. The table lists esbuild **entry points**. Other files in `js/src/` (`api-utils.js`, `location-map.js`, `map-style.js`, `post-form.css`) are sources too — they are imported into a bundle rather than being built directly.
**The trap:** `js/` holds both bundles *and* hand-authored sources. `js/maplibre-utils.js` (the `MapUtils` map engine, a plain IIFE imported by `js/src/map.js`) and `js/nav.js` are sources despite sitting beside the minified bundles.
**The second trap:** `css/` is **not** the source of `css-compiled/`. `css/style.css` and `css/tokens.css` are hand-authored and served *directly* via `assets.addCss('theme://css/…')` in `partials/base.html.twig` — they are never compiled. `css-compiled/` is esbuild output from the CSS imports inside `js/src/*.js` (fontsource + PhotoSwipe → `main.css`; maplibre → `map.css`) plus the standalone maplibre build above.
--- ---
@@ -89,18 +95,24 @@ All page templates extend `base.html.twig`:
``` ```
templates/ templates/
├─ base.html.twig ← site shell: nav, fonts, CSS tokens
├─ default.html.twig ← extends base; generic page ├─ default.html.twig ← extends base; generic page
├─ home.html.twig ← extends base; context-aware two-column layout ├─ home.html.twig ← extends base; context-aware two-column layout
├─ trips.html.twig ← extends base; trip list (with the owner's publish toggle)
├─ trip.html.twig ← extends base; trip page with filter bar (All/Journal/Stories) ├─ trip.html.twig ← extends base; trip page with filter bar (All/Journal/Stories)
├─ entry.html.twig ← extends base; single journal entry (gallery, badges, map) ├─ entry.html.twig ← extends base; single journal entry (gallery, badges, map)
├─ story.html.twig ← extends base; single story (Ken Burns hero, shortcodes) ├─ story.html.twig ← extends base; single story (Ken Burns hero, shortcodes)
gpx-manager.html.twig ← extends base; admin UI for GPX file management post-form.html.twig ← extends base; the /post journal form
├─ gpx-manager.html.twig ← extends base; admin UI for GPX file management
├─ forms/ ← field overrides (e.g. forms/fields/datetime/datetime.html.twig)
├─ macros/ ← cover, cycling, date-range, stats
└─ partials/ ← base.html.twig lives HERE, not at templates/ root
``` ```
**`base.html.twig` is a partial** (`templates/partials/base.html.twig`), despite being the shell every page template extends.
The standalone `dailies.html.twig`, `map.html.twig`, `stats.html.twig` and `stories.html.twig` view templates were **removed** in the 2026-07-04 standalone-page cleanup — the trip page (`trip.html.twig`) consolidated the feed, inline map, and inline stats. The standalone `dailies.html.twig`, `map.html.twig`, `stats.html.twig` and `stories.html.twig` view templates were **removed** in the 2026-07-04 standalone-page cleanup — the trip page (`trip.html.twig`) consolidated the feed, inline map, and inline stats.
Site nav (in `base.html.twig`) is deliberately minimal — **Home + Past Trips only**. It does not link to trip sub-sections, because those standalone views no longer exist. Site nav (in `partials/base.html.twig`) is deliberately minimal — **Home + Trips**, plus **New Post** when `grav.user.authenticated`. It does not link to trip sub-sections, because those standalone views no longer exist.
Partials live in `templates/partials/` (plus macros in `templates/macros/`). Key partials: `base.html.twig` (site shell extended by all page templates), `entry-map.html.twig` (shared map column + `initEntryMap` call, used by trip + home), `trip-feed-col.html.twig` (feed column chrome, shared by trip + home), `home-predeparture.html.twig`, `entry-journal.html.twig` / `entry-story.html.twig` (feed cards), `trip-publish-toggle.html.twig`, and `weather-icons.html.twig`. Partials live in `templates/partials/` (plus macros in `templates/macros/`). Key partials: `base.html.twig` (site shell extended by all page templates), `entry-map.html.twig` (shared map column + `initEntryMap` call, used by trip + home), `trip-feed-col.html.twig` (feed column chrome, shared by trip + home), `home-predeparture.html.twig`, `entry-journal.html.twig` / `entry-story.html.twig` (feed cards), `trip-publish-toggle.html.twig`, and `weather-icons.html.twig`.
@@ -147,7 +159,20 @@ The column **beside** the map: date-range header, filter bar, stats/cycling pane
**Stats/cycling JS glue:** the partial emits an inline `DOMContentLoaded` script calling `window.initTripStats({ gpxUrls, gpsPoints, hasGpx })` — one shared function in `js/src/main.js`. It no-ops when `#stat-distance` is absent, populates exact distance + cycling stats from GPX, and falls back to a `~`-prefixed haversine estimate (or `—` for `<2` points) when there is no GPX. It depends on `window.MapUtils` from `map.js` (loaded in the `bottom` asset group on both pages). **Stats/cycling JS glue:** the partial emits an inline `DOMContentLoaded` script calling `window.initTripStats({ gpxUrls, gpsPoints, hasGpx })` — one shared function in `js/src/main.js`. It no-ops when `#stat-distance` is absent, populates exact distance + cycling stats from GPX, and falls back to a `~`-prefixed haversine estimate (or `—` for `<2` points) when there is no GPX. It depends on `window.MapUtils` from `map.js` (loaded in the `bottom` asset group on both pages).
> History: the map setup replaced an older three-variant arrangement (a `feed-map.html.twig` partial with its own inline init, plus a full-page `map.html.twig`), deleted in the 2026-07-04 standalone-page cleanup. > History: the map setup replaced an older three-variant arrangement (a `feed-map.html.twig` partial with its own inline init, plus a full-page `map.html.twig`), deleted in the 2026-07-04 standalone-page cleanup. See [`superseded-decisions.md`](superseded-decisions.md) → R12.
#### The one non-`entry-map` map: the `/post` pin editor
`js/src/location-map.js` (`getOrCreateLocationMap()`) is a deliberately separate, minimal engine for the post form's "More location details" panel — **an editor, not a display map**, so it shares none of `initEntryMap`'s concerns:
| | `initEntryMap` (display) | `location-map.js` (editor) |
|---|---|---|
| Markers | many, from entries | exactly one, **draggable** |
| Popups / GPX / bounds-fitting | yes | none |
| `maplibre-gl` | bundled into `js/map.js` | **lazy-imported** on first open, so a GPS-only submit never fetches it |
| Stylesheet | via `js/src/map.js`'s CSS import | injects `css-compiled/maplibre-gl.css` on demand (a static import would defeat the lazy load) |
The two share exactly one thing: `MAP_STYLE` from `js/src/map-style.js`. Adding a *third* map path is forbidden — see `CLAUDE.md`.
--- ---
+12 -1
View File
@@ -1,6 +1,17 @@
# Design System — Light Mode Color Palette # Design System — Light Mode Color Palette
Light-mode counterpart to `design-system.md`. Only color tokens differ between themes — typography, spacing, radius, shadows, and layout are identical. > **Superseded — light mode is not implemented, and this palette is not in the code.**
>
> The site is **dark only**. `css/tokens.css` has a single `:root` block; there is no
> `prefers-color-scheme` query, no `data-theme` switch, and none of the light hex values below appear
> anywhere in `css/`. Dark mode shipped as *the* theme rather than as one of two
> (`../working/plans/2026-06-19-dark-mode.md`, 2026-06-20).
>
> Keep this file as the record of the pre-dark-mode palette and as the starting point if a light
> theme is ever built — but do not read the "Light" column as describing the running site. See
> [`superseded-decisions.md`](superseded-decisions.md) → R9.
Light-mode counterpart to `design-system.md`, as originally specified. Only color tokens were intended to differ between themes — typography, spacing, radius, shadows, and layout are identical.
--- ---
+23 -2
View File
@@ -31,6 +31,13 @@
### Palette (dark theme — as implemented) ### Palette (dark theme — as implemented)
**Dark is the only theme.** `css/tokens.css` has a single `:root` block; there is no
`prefers-color-scheme` query and no `data-theme` switch. `design-system-light.md` records the
pre-dark-mode palette, which was never implemented as a switchable theme — see
[`superseded-decisions.md`](superseded-decisions.md) → R9.
The authoritative list is `user/themes/intotheeast/css/tokens.css`.
| Token | Hex | Usage | | Token | Hex | Usage |
|---|---|---| |---|---|---|
| `--color-paper` | `#1A1814` | Page background — warm near-black | | `--color-paper` | `#1A1814` | Page background — warm near-black |
@@ -46,6 +53,20 @@
| `--color-accent-on` | `#FFFFFF` | Text on accent surfaces | | `--color-accent-on` | `#FFFFFF` | Text on accent surfaces |
| `--color-surface-raised` | `#2A2720` | Elevated surfaces: tooltips, hover | | `--color-surface-raised` | `#2A2720` | Elevated surfaces: tooltips, hover |
| `--color-ink-inverse` | `#17171A` | Text on accent-coloured buttons | | `--color-ink-inverse` | `#17171A` | Text on accent-coloured buttons |
| `--color-error` | `#c0392b` | Validation errors, form error status |
| `--color-draft-accent` | `#E0A458` | Warm amber — draft/unpublished badges |
#### Glass overlays
Paper colour at opacity, used by the story components. Computed with `color-mix()` rather than fixed
hex, so they track `--color-paper` automatically.
| Token | Value | Usage |
|---|---|---|
| `--color-paper-glass-low` | `color-mix(in srgb, var(--color-paper) 8%, transparent)` | Faintest scrim |
| `--color-paper-glass-mid` | `color-mix(in srgb, var(--color-paper) 25%, transparent)` | Standard overlay |
| `--color-paper-glass-high` | `color-mix(in srgb, var(--color-paper) 55%, transparent)` | Heavy scrim over imagery |
| `--color-paper-glass-hover` | `color-mix(in srgb, var(--color-paper) 80%, transparent)` | Hover state on a glass surface |
### Rationale for accent color ### Rationale for accent color
@@ -150,7 +171,7 @@ DM Serif Display has a calligraphic quality — slightly editorial, authoritativ
- Nav links: DM Sans, `--text-sm`, weight 500, `--color-ink-2` - Nav links: DM Sans, `--text-sm`, weight 500, `--color-ink-2`
- Active nav link: `--color-accent`, weight 600 - Active nav link: `--color-accent`, weight 600
- Mobile: same layout, title slightly smaller, nav links compact - Mobile: same layout, title slightly smaller, nav links compact
- Background: `--color-canvas` (white), bottom border `1px solid var(--color-border)` - Background: `--color-canvas` (`#22201B` in the dark theme), bottom border `1px solid var(--color-border)`
### 5.2 Entry Feed Card — With Photo ### 5.2 Entry Feed Card — With Photo
@@ -342,7 +363,7 @@ Minimal changes — the map itself is good. Style improvements:
| JS | Vanilla JS — unchanged | Current JS is well-structured, scope doesn't justify a framework | | JS | Vanilla JS — unchanged | Current JS is well-structured, scope doesn't justify a framework |
| Icons | Unicode + emoji (current) | No dependency, works everywhere | | Icons | Unicode + emoji (current) | No dependency, works everywhere |
| Fonts | Google Fonts via CDN | Two fonts, display-swap, negligible impact | | Fonts | Google Fonts via CDN | Two fonts, display-swap, negligible impact |
| Maps | MapLibre GL JS | Replaced Leaflet; all 3 map templates use it | | Maps | MapLibre GL JS | Replaced Leaflet. One shared display-map partial (`partials/entry-map.html.twig`), not three templates — see [`superseded-decisions.md`](superseded-decisions.md) → R12 |
| Build | None — no build pipeline | Grav's asset pipeline handles minification if needed | | Build | None — no build pipeline | Grav's asset pipeline handles minification if needed |
**No Alpine.js, no TypeScript, no Tailwind.** The site has clean vanilla JS and CSS today; a redesign is about visual quality, not framework migration. Introducing a build pipeline on a 3-week timeline is a distraction. **No Alpine.js, no TypeScript, no Tailwind.** The site has clean vanilla JS and CSS today; a redesign is about visual quality, not framework migration. Introducing a build pipeline on a 3-week timeline is a distraction.
+54
View File
@@ -0,0 +1,54 @@
# Superseded decisions
Things this project planned, built, and then deliberately reversed. One row per reversal.
**Why this file exists.** The plans and milestones under `docs/working/` are historical records — they
say what was decided *then*, and they are correct as records. But a reader who opens
`milestones/milestone-2.md` finds a confident present-tense description of a Leaflet `/map` page that
has not existed since 2026-07-04. This file is the changelog of "what did we change our mind about",
so that question has one answer instead of requiring a re-derivation from the code.
**How to use it.** Each superseded section in the old docs carries a `> **Superseded …**` note
pointing back here. If you are about to re-create something you found in an old plan, check here
first — the reversal is usually deliberate, and several are load-bearing rules in
[`CLAUDE.md`](../../CLAUDE.md).
**Keep it current.** When a decision is reversed, add a row *in the same commit as the reversal*. A
ledger that lags is worse than no ledger, because it is trusted.
---
## The reversals
| # | Originally planned | Planned in | True now | Changed | Why |
|---|---|---|---|---|---|
| R1 | A standalone `/map` page — full-height Leaflet map, marker per entry, popups | `milestones/milestone-2.md` (whole doc); `summary.md` | No `/map` route. The map renders **inline on the trip page** through the single shared `partials/entry-map.html.twig` | 2026-07-04 | `plans/2026-07-04-standalone-page-cleanup.md`. One consolidated trip page beat four thin views; a separate map page meant a second map implementation to keep in sync |
| R2 | A standalone `/stats` page — days on the road, entries, countries, distance | `milestones/milestone-3.md` (whole doc); `summary.md` | No `/stats` route. Stats render **inline on the trip page** behind a toggle, via `initTripStats()` | 2026-07-04 | Same cleanup. The numbers are trip context, not a destination |
| R3 | A `/tracker` feed route as the entry list | `milestones/milestone-1.md` §1.6; `milestone-2.md`; `milestone-3.md`; `summary.md` | No `/tracker`. The feed is the **trip page** plus the home active-trip view, sharing `partials/trip-feed-col.html.twig` | Restructured 2026-06-19 (`plans/2026-06-19-trip-entity.md`), fully retired 2026-07-04 | The Trip entity became the organising unit, so a global tracker had nothing to track |
| R4 | Leaflet.js with OpenStreetMap tiles | `milestone-2.md`; `milestone-4.md`; `summary.md`; `pm-analysis.md` | **MapLibre GL JS**, CartoDB dark-matter basemap. Style URL is single-sourced as `MAP_STYLE` in `js/src/map-style.js` | 2026-06-20 | `plans/2026-06-19-maplibre-migration.md`. Vector tiles, GPU rendering, and a dark basemap that suits the dark theme |
| R5 | A standalone `/dailies` journal view and `/stories` story view | `plans/2026-06-19-trip-entity.md` era | Both routes retired. `01.dailies/` and `04.stories/` survive as `routable:false` **data containers** whose children the trip page aggregates | 2026-07-04 | Same cleanup. **The folders are load-bearing** — retiring a view must never delete its container (see [`CONCEPTS.md`](../../CONCEPTS.md) → Container) |
| R6 | Site nav "Journal · Map · Stats" | `summary.md` | **Home · Trips**, plus **New Post** when authenticated (`partials/base.html.twig:27-31`) | Sub-views retired 2026-07-04; "Past Trips" renamed "Trips" 2026-07 (`6cf5092`) | Nav should not link to views that no longer exist |
| R7 | `hero_image` frontmatter on entries, to pin a feed-card hero | `milestone-1.md` §1.6; `summary.md`; `pm-analysis.md` | **No `hero_image` field on journal entries.** The hero is the first uploaded photo (`entry-journal.html.twig` uses `entry.media.images\|first`). Photo order is owner-controlled, so an explicit filename was redundant. **Stories still use `hero_image`** | 2026-07 | `plans/2026-07-05-photo-editor-media-api.md` gave the owner drag-reorder over photos, which made "first photo" a deliberate choice rather than an accident |
| R8 | Photos optional on an entry | `milestone-1.md` §1.5; `guides/posting.md` (pre-2026-07-25) | Photos are **required — minimum 1, maximum 6** (`post-form.md:35-46`, enforced in `post-form.js` `initValidation`) | 2026-07 | `plans/2026-07-04-journal-post-form.md`. Photos come first in the form because they anchor what you write |
| R9 | A light-mode colour palette alongside dark | `reference/design-system-light.md` (whole doc); `plans/2026-06-19-dark-mode.md` | **Dark only.** `css/tokens.css` has a single `:root` block; there is no `prefers-color-scheme` or `data-theme` switch, and no light-palette hex appears in `css/` | 2026-06-20 | Dark mode shipped as *the* theme, not as one of two. The light palette was the pre-dark-mode original and was never re-implemented as a switchable theme |
| R10 | `shortcode-gallery-plusplus` as the entry photo gallery | `pm-analysis.md` | Galleries are **PhotoSwipe**, wired in `js/src/main.js` against `.pswp-gallery` markup emitted by `partials/entry-journal.html.twig`. No `[gallery]` shortcode is used anywhere in `templates/` or `pages/` | 2026-06-21 (`30c8937`, "replace custom lightbox with PhotoSwipe v5") | A lightbox the theme controls beat a plugin's markup. ⚠️ The plugin is **still listed in `plugins.txt`** with no consumer — see recommendations |
| R11 | `travel-memories` as an in-repo service on :8082, built from `./services/travel-memories` | `plans/2026-06-21-travel-memories.md`; `specs/2026-06-21-travel-memories-design.md`; `working/2026-06-21-travel-memories-handover.md` | **Extracted to a separate project.** `services/` is gitignored and the source is absent from this repo | `a80b0a9` — "remove travel-memories service from repo (moved to separate project)" | It was an independent Flask app with its own lifecycle. ⚠️ `docker-compose.yml` **still declares the service**, so `make start` fails on a clean checkout — see recommendations |
| R12 | Three map template variants (`feed-map.html.twig` partial with inline init, plus full-page `map.html.twig`) | pre-2026-06-27 templates | **One display map path**`MapUtils.initEntryMap()` in `js/maplibre-utils.js`, invoked through `partials/entry-map.html.twig` | Consolidated 2026-06-27, variants deleted 2026-07-04 | `plans/2026-06-27-map-init-consolidation.md`. Three implementations drifted apart |
| R13 | A single map code path, no exceptions | `CLAUDE.md` (pre-2026-07-25 wording) | One **display** path (R12) **plus one sanctioned editor**`js/src/location-map.js` for the `/post` pin picker: one draggable marker, no popups/GPX/bounds, `maplibre-gl` lazy-imported. Shares only `MAP_STYLE` with the display path | 2026-07-24 (`dd19995`) | `plans/2026-07-23-post-form-location-override.md`. An editor map has none of a display map's concerns; folding them together would have compromised both |
| R14 | `post-form.md` carries a static `pageconfig.parent` naming the write target | pre-2026-07 form config | **No `parent` in `post-form.md`.** `cache-on-save` derives it from `site.active_trip` at submit time | 2026-07 | The two settings silently desynced. **Never re-add it** — this is a hard rule in [`CLAUDE.md`](../../CLAUDE.md) |
---
## Decisions that were *not* reversed
Worth stating, because their planning docs are old enough to look suspect:
- **The SKIP list in [`pm-analysis.md`](../working/pm-analysis.md) still holds.** Background GPS
tracking, followers, comments, social discovery, reactions, trip reels, 3D flyover, printed books,
and AI itinerary building were all deliberately rejected for a solo flat-file blog. That reasoning
has not changed — only some of the *BUILD* items' delivery mechanisms did (R1, R2, R4, R7, R10).
- **Weather via Open-Meteo**, no API key, with the eight allowed `weather_desc` values — still exactly
as planned in `milestone-1.md` §1.2, and still matching the blueprint and the post form.
- **Location badge** (`📍 City, Country`) on cards and entry pages — as planned.
- **Distance/stats computation from frontmatter and GPX** — the numbers survived; only their
*location* moved from a `/stats` page to the trip page (R2).
@@ -0,0 +1,149 @@
# Recommendations from the 2026-07-25 documentation reconciliation
**Status:** 📋 Proposed — nothing here has been acted on. Decide per item.
The reconciliation pass (see [`specs/2026-07-25-docs-reconciliation-design.md`](specs/2026-07-25-docs-reconciliation-design.md))
corrected the documentation. It also surfaced problems that are **not** documentation problems, plus
process changes that would stop this drift recurring. Those are collected here rather than mixed into
a docs diff.
Ordered by what I would do first.
---
## P1 — `make start` and `make setup` are broken on any clean checkout
**What.** `docker-compose.yml` still declares a `travel-memories` service with
`build: ./services/travel-memories`. That source was removed in `a80b0a9` ("moved to separate
project") and `services/` is gitignored, so the build context does not exist. `make start` is
`docker compose up -d` (all services), and `make setup` calls it.
**Proof.**
```
$ docker compose build travel-memories
unable to prepare context: path ".../services/travel-memories" not found
```
**Why it has stayed hidden.** A machine that built the image before `a80b0a9` still has
`travel-blog-intotheeast-travel-memories:latest` cached, so `docker compose up -d` reuses it and never
rebuilds. It breaks for a fresh clone, for every new worktree (different `COMPOSE_PROJECT_NAME`
different image name → forced rebuild), and on the main checkout after any `docker image prune`. This
is why `make worktree-new` calls `start-grav`, not `start`.
**Options.**
1. **Delete the service from `docker-compose.yml`** (recommended). It lives in another project now. If
that project needs to run alongside Grav, it can carry its own compose file.
2. Move it into a compose profile (`profiles: [tools]`) so `docker compose up -d` skips it by default.
3. Keep it and point `build` at the new location — only if you actually want the two coupled again.
Until this is decided, `CLAUDE.md` and `README.md` now warn to use `make start-grav`. That is a
signpost around a bug, not a fix.
---
## P2 — A repeatable drift check
Deliberately out of scope for the one-time pass; this is the item that stops the whole problem
recurring. Every defect found was mechanically checkable — a route, a path, a token name, a make
target, a field rule.
**Proposal.** A `make docs-check` target that fails loudly when the present-tense docs assert
something the code contradicts:
- Grep `CLAUDE.md`, `docs/reference/`, `docs/guides/`, `README.md`, `CONCEPTS.md` for references to
retired routes (`/map`, `/stats`, `/tracker`, `/dailies`, `/stories`) and dead tech (`Leaflet`).
These are already forbidden by `CLAUDE.md`, so any hit is a defect.
- Assert every `templates/*.html.twig` and `templates/partials/*.html.twig` named in
`architecture.md` exists, and flag templates that exist but are undocumented. Both directions of
drift were present this pass.
- Diff the `--color-*` token names in `design-system.md` against `css/tokens.css`. Six were missing.
- Assert every `make <target>` mentioned in `README.md` is a real target, **and** that no bare
`remote-*` target is documented without an env suffix. This alone would have caught P4.
- Assert file paths cited in `CLAUDE.md` exist. A prior pass shipped a path to
`js/src/maplibre-utils.js`, which never existed.
Deliberately **excluded**: `docs/working/`. Those documents are records and are supposed to drift;
scanning them would produce permanent noise.
Sequence this after P1 — otherwise the first thing the check reports is P1.
---
## P3 — Plan status can silently lag a merge
`plans/2026-07-23-post-form-location-override.md` read `📋 Not started` while the feature was merged
in `user/` as `dd19995`. Nothing connects a plan's status line to the commit that lands it, so the
convention depends entirely on remembering.
**Options.**
1. **Add the plan path to the feature's commit or PR body**, so `git log --grep` can find plans whose
work landed but whose status never moved. Cheapest, no tooling.
2. Extend the P2 check: for each plan not `✅ Complete`/`❌ Abandoned`, look for a merged branch whose
name matches the plan slug and warn. Catches it automatically; some false positives.
3. Accept it and rely on the convention. Reasonable — this was one miss across 41 plans.
---
## P4 — `README.md` was designated authoritative for a list it did not hold
`CLAUDE.md`'s entry-point table sends readers to `README.md` for "the full `make` command list".
Before this pass, README documented 7 of ~20 `remote-*` targets, and documented all of them **without
the `-test`/`-prod` suffix that `guard-env` requires** — so its server runbook was not executable.
Corrected now, but the structural point stands: **a doc promoted to "the authoritative list of X"
acquires a completeness obligation it did not have as prose.** The `Makefile` is the real source of
truth. Consider either generating the command tables from `Makefile` comments, or softening the
CLAUDE.md pointer to "common commands" and letting `make help` be authoritative.
Related: `docs/guides/deploy-cycle.md` had the env-suffix rule right the whole time. The defect was
README duplicating the same knowledge and drifting. Fewer copies would have prevented it.
---
## P5 — `shortcode-gallery-plusplus` is installed with no consumer
`plugins.txt` lists it, but there is no `[gallery]` shortcode anywhere in `templates/` or `pages/`.
Entry galleries are PhotoSwipe, wired in `js/src/main.js` against `.pswp-gallery` markup from
`partials/entry-journal.html.twig`.
**Careful before removing it.** `plugins.txt` does **not** list `shortcode-core`, which is present as
a GPM dependency — and `story-blocks` needs `shortcode-core`. Dropping
`shortcode-gallery-plusplus` could take `shortcode-core` with it and break stories.
**Recommendation.** Add `shortcode-core` to `plugins.txt` as an explicit, first-class dependency
*first*, then remove `shortcode-gallery-plusplus` and verify a story page still renders. Do not do
these in one step.
---
## P6 — Demo fixtures still contain retired views
`user/docs/demo/trips/italy-2025/` ships `map.md`, `stats.md` and `stories.md` — pages for views
retired on 2026-07-04. The newer `italy-2026-demo` fixture has no `map.md`/`stats.md`, so the fixtures
disagree with each other.
Low impact (demo trips are gitignored in the pages tree and loaded on demand), but `make demo-load`
copies them in, so a demo trip can materialise pages for views that no longer exist. Delete
`map.md` and `stats.md` from `italy-2025`; keep `stories.md` only if the container is still needed.
This is a `user/` submodule change, which is why it was left out of this pass.
---
## P7 — Structural notes worth a decision
**`design-system-light.md` is a record, not a reference.** It documents an unimplemented palette and
now carries a banner saying so, but it still sits in `reference/` — the "stable facts" tier. Moving it
to `docs/working/` would make its status structural rather than dependent on a reader seeing the
banner. Counter-argument: it is the natural starting point if a light theme is ever built, and
`reference/` is where someone would look. Either is defensible; the banner makes it safe for now.
**`milestone2-template-refactor-brief.md` sits loose in `docs/working/`** while the milestone docs live
in `working/milestones/`. Cosmetic, but it is the kind of thing that makes a folder stop being
self-explanatory.
**The `summary.md` lesson generalises.** The single most misleading line in the tree was
`working/README.md` advertising `summary.md` as "current state". A stale document is survivable; an
*index* that points at a stale document as authoritative is not, because it defeats the reader's
judgement. Worth remembering the next time an index gets written: **describing a document's role is
itself a factual claim that can rot.**
+12 -1
View File
@@ -4,6 +4,17 @@ Everything here is a live working document: specs being built from, plans being
Stable facts belong in [`../reference/`](../reference/); how-to procedures in [`../guides/`](../guides/); write-ups of bugs already solved in [`../solutions/`](../solutions/). Stable facts belong in [`../reference/`](../reference/); how-to procedures in [`../guides/`](../guides/); write-ups of bugs already solved in [`../solutions/`](../solutions/).
> ⚠️ **Everything here is written in the past tense, even when it reads present-tense.** A completed
> plan describes the code *as it was when the plan landed* — that is what makes it a useful record,
> and it is not a defect when it no longer matches. Several documents here describe features that were
> later deliberately reversed: there is no `/map` page, no `/stats` page, no `/tracker`, no Leaflet, no
> light theme, and no `hero_image` on entries.
>
> **Before re-creating anything you find in this folder, check
> [`../reference/superseded-decisions.md`](../reference/superseded-decisions.md).** Superseded sections
> also carry an inline `> **Superseded …**` note pointing there. For the site as it is, read
> [`../reference/architecture.md`](../reference/architecture.md).
--- ---
## What's in here ## What's in here
@@ -18,7 +29,7 @@ Stable facts belong in [`../reference/`](../reference/); how-to procedures in [`
| `learnings/` | Retrospective notes worth keeping but not yet promoted to `../solutions/` | | `learnings/` | Retrospective notes worth keeping but not yet promoted to `../solutions/` |
| `backlog.md` | Unscheduled ideas and wishes | | `backlog.md` | Unscheduled ideas and wishes |
| `bugs-and-fixes.md` | Running log of bugs found and what fixed them | | `bugs-and-fixes.md` | Running log of bugs found and what fixed them |
| `summary.md` | Project summary / current state | | `summary.md` | **Historical** wrap-up of the original four-milestone branch (2026-06-21). *Not* the current state — for that read [`../reference/architecture.md`](../reference/architecture.md) |
| `pm-analysis.md`, `git-sync-notes.md`, dated one-offs | Standalone notes, kept for reference | | `pm-analysis.md`, `git-sync-notes.md`, dated one-offs | Standalone notes, kept for reference |
--- ---
+12
View File
@@ -2,6 +2,18 @@
**Goal:** Every entry is richer out of the box — location name shown, weather auto-captured, photos in a proper gallery, hero image visible on the feed. **Goal:** Every entry is richer out of the box — location name shown, weather auto-captured, photos in a proper gallery, hero image visible on the feed.
> **Historical — written 2026-06-21. Mostly shipped as specified; three details reversed.**
>
> Still true: the location badge, Open-Meteo weather auto-fetch with its eight `weather_desc` values,
> and the entry photo gallery. Reversed since:
> - **§1.5 gallery** is PhotoSwipe, not `shortcode-gallery-plusplus` (R10).
> - **§1.6 `hero_image`** no longer exists on entries — the hero is the first uploaded photo, and the
> owner controls photo order by drag-reorder (R7). Stories still use `hero_image`.
> - **Photos are now required** (16 per entry), not optional (R8).
> - **"Tracker feed"** is the trip page and the home active-trip view; there is no `/tracker` (R3).
>
> Details: [`../../reference/superseded-decisions.md`](../../reference/superseded-decisions.md).
--- ---
## User Stories ## User Stories
+15
View File
@@ -2,6 +2,21 @@
**Goal:** A `/map` page shows all entries as markers on an interactive Leaflet.js map, connected by a chronological route line, with popups linking to entries. **Goal:** A `/map` page shows all entries as markers on an interactive Leaflet.js map, connected by a chronological route line, with popups linking to entries.
> **Superseded — written 2026-06-21. Neither the `/map` page nor Leaflet exists.**
>
> - **No `/map` route.** The map renders inline on the trip page via the single shared partial
> `templates/partials/entry-map.html.twig` (R1, retired 2026-07-04). `CLAUDE.md` forbids
> re-creating it or linking to it.
> - **Leaflet + OpenStreetMap tiles → MapLibre GL JS** with a CartoDB dark-matter basemap (R4,
> 2026-06-20).
> - **§2.6 nav link** is gone with the page (R6).
>
> The *substance* of this spec survived — markers per entry, chronological route line, popups linking
> to entries, bounds fitting, mobile touch handling — it all lives in `MapUtils.initEntryMap()` in
> `user/themes/intotheeast/js/maplibre-utils.js`. Only the page and the library changed.
>
> Details: [`../../reference/superseded-decisions.md`](../../reference/superseded-decisions.md).
--- ---
## User Stories ## User Stories
+11
View File
@@ -2,6 +2,17 @@
**Goal:** A `/stats` page showing key trip numbers: days on the road, entries posted, countries visited, and approximate distance traveled. **Goal:** A `/stats` page showing key trip numbers: days on the road, entries posted, countries visited, and approximate distance traveled.
> **Superseded — written 2026-06-21. There is no `/stats` page.**
>
> The stats themselves shipped and still work — days on the road, entries posted, countries visited,
> distance (exact from GPX, or a `~`-prefixed haversine estimate without it). They render **inline on
> the trip page** behind a toggle, computed by `window.initTripStats()` in `js/src/main.js`
> (R2, retired 2026-07-04). `CLAUDE.md` forbids re-creating the standalone view.
>
> Also reversed: **§3.7 nav link** (R6), and the `/tracker` references (R3).
>
> Details: [`../../reference/superseded-decisions.md`](../../reference/superseded-decisions.md).
--- ---
## User Stories ## User Stories
+14
View File
@@ -2,6 +2,20 @@
**Goal:** Embed a compact interactive map above the entry feed on the tracker page, showing recent entry positions and the current location, giving readers immediate spatial context. **Goal:** Embed a compact interactive map above the entry feed on the tracker page, showing recent entry positions and the current location, giving readers immediate spatial context.
> **Superseded — written 2026-06-21. The idea won; this implementation did not.**
>
> A map beside the feed is exactly what the site does now — but not as a separate "mini-map":
> - **No `/tracker` page** to embed it above (R3). The map sits in a column on the trip page and the
> home active-trip view.
> - **No second map implementation.** This spec's `feed-map` variant with its own inline init was
> deleted; everything goes through the one shared `partials/entry-map.html.twig` +
> `MapUtils.initEntryMap()` path (R12, consolidated 2026-06-27). Adding a second display map is
> forbidden by `CLAUDE.md`.
> - **Leaflet → MapLibre GL JS** (R4), so §4.1's `if (typeof L === 'undefined')` guard is obsolete.
> - **No "View full map →" link** — there is no full map page to link to (R1).
>
> Details: [`../../reference/superseded-decisions.md`](../../reference/superseded-decisions.md).
--- ---
## User Stories ## User Stories
@@ -11,7 +11,7 @@ execution: code
# Post Form Location Override - Plan # Post Form Location Override - Plan
**Status:** 📋 Not started **Status:** ✅ Complete (2026-07-24) — merged to `user/` `main` as `dd19995` ("Merge feat/post-location-override into main"). Delivered `js/src/location-map.js` (the lazy-imported single-draggable-marker pin editor) and `js/src/map-style.js` (`MAP_STYLE`, now the single source of the basemap URL for both map paths), plus the "More location details" disclosure with city/country search-by-lookup in `js/src/post-form.js`. The status line lagged the merge and was corrected during the 2026-07-25 documentation reconciliation; the second map engine is now recorded as the one sanctioned exception to the single-map-path rule in `CLAUDE.md` and in [`../../reference/superseded-decisions.md`](../../reference/superseded-decisions.md) → R13.
## Goal Capsule ## Goal Capsule
+9
View File
@@ -2,6 +2,15 @@
*Role: Senior Product Manager. Audience: one solo traveler (Mischa), platform: Grav CMS flat-file PHP, no native app.* *Role: Senior Product Manager. Audience: one solo traveler (Mischa), platform: Grav CMS flat-file PHP, no native app.*
> **Historical — written 2026-06-21. The verdicts still hold; some delivery mechanisms do not.**
>
> The **SKIP** column is still the standing decision and has not been revisited — background GPS,
> followers, comments, social discovery, reactions, reels, 3D flyover, print, and AI itineraries
> remain deliberately out of scope. What changed is *how* some **BUILD** items shipped: the map and
> stats render inline on the trip page rather than as `/map` and `/stats`, MapLibre replaced Leaflet,
> galleries use PhotoSwipe rather than `shortcode-gallery-plusplus`, and `hero_image` was dropped for
> entries. See [`../reference/superseded-decisions.md`](../reference/superseded-decisions.md).
--- ---
## Starting position ## Starting position
@@ -0,0 +1,109 @@
# Docs Reconciliation — Design
**Date:** 2026-07-25
**Status:** Implemented
Reconcile the documentation against the code after five weeks of undocumented evolution, so that a
repeat review returns "ok".
---
## Problem
Documentation for this project began as thoughts and plans. The app then changed — features were
built differently, some were dropped, and the owner changed his mind about what he needed. Those
decisions were recorded ad hoc or not at all. The result is a tree where some docs describe a site
that no longer exists, and nothing marks them as historical.
Concretely, before this pass:
- `docs/working/README.md` advertised `summary.md` as the project's **current state**, while
`summary.md` described Leaflet, a `/tracker` feed, a `/map` page, a `/stats` page, and a
"Journal · Map · Stats" nav — none of which exist.
- `docs/reference/design-system-light.md` documented a light-mode palette in present tense. No light
mode is implemented anywhere: `tokens.css` has a single `:root` block and no
`prefers-color-scheme` / `data-theme` mechanism.
- `CLAUDE.md` — the always-loaded file — asserted a source relationship that does not exist
(`css-compiled/` generated from `css/style.css` + `css/tokens.css`).
- `README.md`'s server runbook documented every `make remote-*` command without the `-test`/`-prod`
suffix that `guard-env` requires, so the documented commands cannot run.
- `docker-compose.yml` still defines a `travel-memories` service whose source was deleted in
`a80b0a9` ("moved to separate project"), so `make start` fails on any clean checkout.
## Root cause
Per [`docs/solutions/conventions/claude-md-content-tiering.md`](../../solutions/conventions/claude-md-content-tiering.md),
descriptions drift because the code moves and the prose does not; rules do not drift, because they
encode intent rather than state. This pass confirms that finding again: every defect found was a
description of code, config, or a command — not one was a rule that had become wrong on its own.
The compounding factor is **tense**. The tree mixes two kinds of document with no marker
distinguishing them:
| Kind | Files | Staleness is |
|---|---|---|
| Present-tense — "this is how it is" | `CLAUDE.md`, `reference/`, `guides/`, `README.md`, `CONCEPTS.md` | a defect |
| Past-tense — "this is what we decided then" | `working/plans/`, `working/specs/`, `working/milestones/`, `summary.md`, `pm-analysis.md` | correct and expected |
A completed plan *should* be stale — it is a record. It only becomes a problem when nothing tells a
reader it is a record. `milestones/milestone-2.md` opens by describing a Leaflet `/map` page in
confident present tense with no date qualifier.
## Approach
Two mechanisms, combined:
**A — one authoritative supersession ledger.** `docs/reference/superseded-decisions.md` records every
reversal in one table: what was planned, where it was planned, what is true now, when it changed, and
why. This answers "what did I change my mind about?" in a single place, which is the question a
review actually asks.
**B — inline notes at the point of staleness.** Every superseded section carries a
`> **Superseded …**` blockquote where the stale claim sits, so the claim can never be read
un-corrected. This pattern is not invented here — `docs/reference/architecture.md` and
`docs/guides/trip-switching.md` already use `> History:` and `> **Changed 2026-07:**` notes.
A alone has an indirection problem (a pointer you may not follow). B alone has a completeness problem
(no changelog view, and coverage is only as good as the annotation pass). Together each covers the
other's gap.
### Scope, split by tense
- **Present-tense docs are corrected against the code.** The code is the source of truth. Every
factual claim was verified by reading the code, config, or `Makefile` — not inferred.
- **Past-tense docs are annotated only, never rewritten.** 41 plans and 25 specs, ~30k lines. Their
`✅ Complete` trailing notes are good records; rewriting them would destroy the audit trail and is
unbounded work.
- **Code-side inconsistencies are logged, not fixed.** Mixing behaviour changes into a documentation
diff would make it unreviewable. They go to
`docs/working/2026-07-25-doc-drift-recommendations.md` for a separate decision.
### Out of scope
- A repeatable drift check (script with an exit code). Deliberately deferred — the owner asked for the
one-time reconciliation first. It is the lead recommendation in the recommendations doc.
- Fixing the `travel-memories` / `docker-compose.yml` breakage, the unused
`shortcode-gallery-plusplus`, and the `italy-2025` demo fixtures. All logged as recommendations.
## Verification
Claims were checked against, not assumed from:
| Claim area | Verified against |
|---|---|
| Nav labels | `templates/partials/base.html.twig:27-31` |
| Template + partial inventory | `ls templates/`, `ls templates/partials/` |
| Asset sources → outputs | `user/themes/intotheeast/package.json` build script |
| `css-compiled/` provenance | CSS imports in `js/src/*.js`; `assets.addCss` in `base.html.twig:7-8` |
| Design tokens | `css/tokens.css` |
| Light mode | absence of `prefers-color-scheme` / `data-theme` and of light hex values in `css/` |
| Photo field rules | `user/pages/02.post/post-form.md:35-46` |
| `hero_image` removal | `post-form.md:149-151` |
| `entry-actions` routes | `user/plugins/entry-actions/entry-actions.php:63-73` |
| `make` targets + env guard | `Makefile` (`guard-env:41-43`, `make-env-target:45-46`) |
| `travel-memories` removal | `git log -- services/``a80b0a9`; `docker compose build` failure |
| Plan status vs reality | `user/` HEAD `dd19995` |
The `user/` submodule was moved off the outer repo's pin to its real HEAD (`dd19995`) before
auditing, because the pin lagged and would have produced findings against a state that is no longer
current.
+10
View File
@@ -2,6 +2,16 @@
*Branch: `experimental-polar-steps`. Ready for morning review.* *Branch: `experimental-polar-steps`. Ready for morning review.*
> **Historical — written 2026-06-21. This is not the current state of the site.**
>
> This was the wrap-up of the four-milestone experimental branch. Much of what it describes has since
> been deliberately reversed: there is no `/map` page, no `/stats` page, no `/tracker` feed, no
> Leaflet, and the nav is not "Journal · Map · Stats". Every reversal is listed in
> [`../reference/superseded-decisions.md`](../reference/superseded-decisions.md).
>
> For the site as it actually is, read
> [`../reference/architecture.md`](../reference/architecture.md).
--- ---
## What Was Done ## What Was Done