From 8202d2a2578e752a6c78dc564455f736d6c47d23 Mon Sep 17 00:00:00 2001 From: Mischa Date: Sat, 25 Jul 2026 00:13:37 +0200 Subject: [PATCH] docs: reconcile documentation against the code; add a supersession ledger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- CLAUDE.md | 9 +- README.md | 95 +++++++---- docs/README.md | 7 +- docs/guides/posting.md | 91 ++++++++--- docs/reference/architecture.md | 41 ++++- docs/reference/design-system-light.md | 13 +- docs/reference/design-system.md | 25 ++- docs/reference/superseded-decisions.md | 54 +++++++ .../2026-07-25-doc-drift-recommendations.md | 149 ++++++++++++++++++ docs/working/README.md | 13 +- docs/working/milestones/milestone-1.md | 12 ++ docs/working/milestones/milestone-2.md | 15 ++ docs/working/milestones/milestone-3.md | 11 ++ docs/working/milestones/milestone-4.md | 14 ++ .../2026-07-23-post-form-location-override.md | 2 +- docs/working/pm-analysis.md | 9 ++ .../2026-07-25-docs-reconciliation-design.md | 109 +++++++++++++ docs/working/summary.md | 10 ++ 18 files changed, 616 insertions(+), 63 deletions(-) create mode 100644 docs/reference/superseded-decisions.md create mode 100644 docs/working/2026-07-25-doc-drift-recommendations.md create mode 100644 docs/working/specs/2026-07-25-docs-reconciliation-design.md diff --git a/CLAUDE.md b/CLAUDE.md index c6b706f..e296a87 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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/) | | 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. @@ -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 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 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. ## 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/`. - ⚠️ **Once `user/env//` 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//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`. @@ -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: -- **`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. - **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. diff --git a/README.md b/README.md index c7f9c87..1e373c1 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,23 @@ Two git repos: ## Local development setup ```bash -cp .env.example .env # fill in your values — never commit this file -make setup # start Docker container and install plugins +cp .env.example .env # fill in your values — never commit this file +git submodule update --init user +mkdir -p user/plugins user/data +make build && make start-grav && make install-plugins && make fix-perms ``` Site runs at http://localhost:8081. -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 -git clone $USER_REPO user/ -``` +> ⚠️ **Use `make start-grav`, not `make setup`, on a clean checkout.** `make setup` runs `make start` +> (`docker compose up -d`), which still tries to build the `travel-memories` service — but its source +> was moved to a separate project (`a80b0a9`) and `services/` is gitignored, so the build context is +> missing and the command fails. `make start-grav` brings up Grav only. Machines with a cached +> `travel-memories` image will not see this until their next rebuild. See +> [`docs/reference/superseded-decisions.md`](docs/reference/superseded-decisions.md) → R11. --- @@ -69,12 +75,12 @@ git clone $USER_REPO user/ **2. Run the install:** ```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. -**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 | |---------|-------------| -| `make setup` | First run: build → start → install plugins → fix perms | -| `make start` | Start the local Docker container | +| `make setup` | First run: build → start → install plugins → fix perms. ⚠️ Currently fails on a clean checkout — see the setup note above; use the `start-grav` sequence instead | +| `make start` | Start **all** compose services. ⚠️ Fails where `services/travel-memories` is absent | +| `make start-grav` | Start the Grav service only — the reliable option | | `make stop` | Stop the local Docker container | | `make install-plugins` | (Re)install plugins from plugins.txt, then apply local plugin patches | | `make apply-plugin-patches` | Idempotently re-apply the patches in `deploy/patches/` | @@ -137,35 +144,67 @@ Details and conventions: [`docs/reference/testing.md`](docs/reference/testing.md | `make worktree-new NAME=` | Create a worktree with its own `user/` checkout and an isolated dev server on port `8090+` | | `make worktree-rm NAME=` | 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-` when done; credentials must not persist on the server. | 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 | +| `make remote-env-setup-` | Write Gitea credentials to `~/.env-intotheeast` on the server | +| `make remote-env-remove-` | Delete `~/.env-intotheeast` from the server | +| `make remote-secrets-audit-` | Check the server for exposed secrets | +| `make remote-seed-api-salt-` | Generate the API/CSRF salt on the server | -Always run `make remote-env-remove` when done. Credentials must not persist on the server. - -### Remote server management +**Install and sync** | 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 | +| `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-fetch-content-` | Pull latest `user/` content on the server | +| `make remote-content-status-` | Show the server's content-repo state | +| `make remote-apply-env-` | Apply `deploy/env//` config into the server's env tree — **re-run after any fresh install** | +| `make remote-apply-plugin-patches-` | Re-apply `deploy/patches/` on the server | + +**Plugins and core** + +| Command | Description | +|---------|-------------| +| `make remote-install-plugins-` | Install plugins from local plugins.txt on the server | +| `make remote-update-plugins-` | Update installed plugins via GPM | +| `make remote-gpm-install-` | Install a single plugin via GPM | +| `make remote-upgrade-grav-` | Upgrade Grav core on the server (in place — servers have no image) | + +**Operations** + +| Command | Description | +|---------|-------------| +| `make remote-clean-` | Clear Grav cache on the server | +| `make remote-warmup-` | Clear **and warm** the cache after a deploy | +| `make remote-maintenance-on-` | Enable maintenance mode (visitors see offline page) | +| `make remote-maintenance-off-` | Disable maintenance mode | +| `make remote-diag-` | Diagnostics on the server | +| `make remote-git-sync-enable-` / `-disable-` | Toggle the remote-only git-sync plugin | +| `make remote-wipe-` | ⚠️ Destroy the server install | ### Typical upgrade workflow +Run against `test` first — it is a full dress rehearsal of prod. + ```bash -make remote-maintenance-on -make remote-upgrade-grav -make remote-install-plugins -make remote-clean -make remote-maintenance-off +make remote-maintenance-on-prod +make remote-upgrade-grav-prod +make remote-install-plugins-prod +make remote-apply-env-prod # env tree is not restored by anything else +make remote-warmup-prod +make remote-maintenance-off-prod ``` --- diff --git a/docs/README.md b/docs/README.md index 269e2e2..b0a761b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,9 +15,14 @@ **Design or architecture decisions?** → [`reference/`](reference/) - [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) +> 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 diff --git a/docs/guides/posting.md b/docs/guides/posting.md index 2643632..c337037 100644 --- a/docs/guides/posting.md +++ b/docs/guides/posting.md @@ -1,18 +1,23 @@ # 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 1. Open `/post` on your phone (login required) -2. Fill in **Title** and **Content** (required) -3. Tap **Get Location** → fills Lat/Lng automatically -4. Tap **Get Weather** → fills weather fields using your coordinates -5. Type **City** and **Country** (optional but nice) -6. Attach photos (optional) — first photo becomes the hero image -7. Tap **Submit** → entry appears in the feed immediately +2. **Attach 1–6 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. Fill in **Title** and **Content** (required) +4. Tap **Get Location** → fills Lat/Lng, then reverse-geocodes City + Country for you +5. Tap **Get Weather** → fills weather fields using those coordinates +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. 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 (1–6).** 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 | |---|---|---| +| Photos | ✅ | **1–6 per entry.** HEIC is converted to JPEG in the browser. First photo = hero; drag to reorder | | Title | ✅ | Entry headline | | Content | ✅ | Markdown body | | Date | ✅ | Defaults to now — adjust if posting later | -| Lat / Lng | — | Filled by Get Location; used for map marker | -| City | — | Shown as `📍 Kyoto, Japan` on feed cards | -| Country | — | Combined with City in location badge | +| Lat / Lng | — | Filled by Get Location, by place search, or by dragging the map pin | +| City | — | Auto-filled by reverse geocoding after Get Location; shown as `📍 Kyoto, Japan` on feed cards | +| Country | — | Combined with City in the location badge | | 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): `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) └─ Grav Form plugin validates fields └─ cache-on-save injects parent from site.active_trip - └─ add-page-by-form plugin - ├─ writes user/pages/01.trips//01.dailies//entry.md + └─ and sets overwrite_mode: edit when edit_path is filled, else false + └─ add-page-by-form plugin (patched — see deploy/patches/) + ├─ writes user/pages/01.trips//01.dailies/.entry/entry.md └─ moves uploaded photos into the page folder └─ cache-on-save plugin └─ 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/ └─ 2026-07-20-0930-first-day-in-kyoto.entry/ - ├─ entry.md ← frontmatter + markdown body - ├─ temple.jpg ← hero image (or set hero_image in frontmatter) - └─ market.jpg ← additional gallery image + ├─ entry.md ← frontmatter + markdown body + ├─ photo-01.jpg ← first in order, so this is the hero + └─ 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 -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` 2. **Pages → Add Page** @@ -96,7 +114,36 @@ Every entry supports these frontmatter fields: | `location_country` | string | e.g. `Japan` | | `weather_desc` | string | One of the allowed values above | | `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** → 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 1–6 photos. + **500 error after posting** → 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. diff --git a/docs/reference/architecture.md b/docs/reference/architecture.md index c577763..9a6d316 100644 --- a/docs/reference/architecture.md +++ b/docs/reference/architecture.md @@ -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 | | PHP session | `session.save_path = /tmp` | Set in `php/php-local.ini` | | 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 | --- @@ -54,7 +55,7 @@ Other notable plugins: | `api` (Grav API v1) | Used by /gpx-manager to list/upload/delete GPX files | | `admin2` | Admin panel at /admin | | `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` checks only write-permission (no trip scoping) and cannot renumber media to the `photo-NN` cover order | ### 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/feed-actions.js` | `js/feed-actions.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) | -**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/ -├─ base.html.twig ← site shell: nav, fonts, CSS tokens ├─ default.html.twig ← extends base; generic page ├─ 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) ├─ entry.html.twig ← extends base; single journal entry (gallery, badges, map) ├─ 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. -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`. @@ -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). -> 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`. --- diff --git a/docs/reference/design-system-light.md b/docs/reference/design-system-light.md index 2cb1738..2b52c32 100644 --- a/docs/reference/design-system-light.md +++ b/docs/reference/design-system-light.md @@ -1,6 +1,17 @@ # 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. --- diff --git a/docs/reference/design-system.md b/docs/reference/design-system.md index 74ef386..3d7f5e4 100644 --- a/docs/reference/design-system.md +++ b/docs/reference/design-system.md @@ -31,6 +31,13 @@ ### 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 | |---|---|---| | `--color-paper` | `#1A1814` | Page background — warm near-black | @@ -46,6 +53,20 @@ | `--color-accent-on` | `#FFFFFF` | Text on accent surfaces | | `--color-surface-raised` | `#2A2720` | Elevated surfaces: tooltips, hover | | `--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 @@ -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` - Active nav link: `--color-accent`, weight 600 - 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 @@ -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 | | Icons | Unicode + emoji (current) | No dependency, works everywhere | | 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 | **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. diff --git a/docs/reference/superseded-decisions.md b/docs/reference/superseded-decisions.md new file mode 100644 index 0000000..e2c0a5f --- /dev/null +++ b/docs/reference/superseded-decisions.md @@ -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). diff --git a/docs/working/2026-07-25-doc-drift-recommendations.md b/docs/working/2026-07-25-doc-drift-recommendations.md new file mode 100644 index 0000000..8ca0273 --- /dev/null +++ b/docs/working/2026-07-25-doc-drift-recommendations.md @@ -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 ` 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.** diff --git a/docs/working/README.md b/docs/working/README.md index f5b960d..7ebd7da 100644 --- a/docs/working/README.md +++ b/docs/working/README.md @@ -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/). +> ⚠️ **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 @@ -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/` | | `backlog.md` | Unscheduled ideas and wishes | | `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 | --- diff --git a/docs/working/milestones/milestone-1.md b/docs/working/milestones/milestone-1.md index 6413fff..638a801 100644 --- a/docs/working/milestones/milestone-1.md +++ b/docs/working/milestones/milestone-1.md @@ -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. +> **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** (1–6 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 diff --git a/docs/working/milestones/milestone-2.md b/docs/working/milestones/milestone-2.md index 2e7337b..0f8df8e 100644 --- a/docs/working/milestones/milestone-2.md +++ b/docs/working/milestones/milestone-2.md @@ -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. +> **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 diff --git a/docs/working/milestones/milestone-3.md b/docs/working/milestones/milestone-3.md index 8a600e4..d06728a 100644 --- a/docs/working/milestones/milestone-3.md +++ b/docs/working/milestones/milestone-3.md @@ -2,6 +2,17 @@ **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 diff --git a/docs/working/milestones/milestone-4.md b/docs/working/milestones/milestone-4.md index 685c1e3..a4fc9ca 100644 --- a/docs/working/milestones/milestone-4.md +++ b/docs/working/milestones/milestone-4.md @@ -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. +> **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 diff --git a/docs/working/plans/2026-07-23-post-form-location-override.md b/docs/working/plans/2026-07-23-post-form-location-override.md index b94f506..3168c59 100644 --- a/docs/working/plans/2026-07-23-post-form-location-override.md +++ b/docs/working/plans/2026-07-23-post-form-location-override.md @@ -11,7 +11,7 @@ execution: code # 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 diff --git a/docs/working/pm-analysis.md b/docs/working/pm-analysis.md index 96584dc..d31dfdc 100644 --- a/docs/working/pm-analysis.md +++ b/docs/working/pm-analysis.md @@ -2,6 +2,15 @@ *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 diff --git a/docs/working/specs/2026-07-25-docs-reconciliation-design.md b/docs/working/specs/2026-07-25-docs-reconciliation-design.md new file mode 100644 index 0000000..5f30faa --- /dev/null +++ b/docs/working/specs/2026-07-25-docs-reconciliation-design.md @@ -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. diff --git a/docs/working/summary.md b/docs/working/summary.md index 395c951..1ba3946 100644 --- a/docs/working/summary.md +++ b/docs/working/summary.md @@ -2,6 +2,16 @@ *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