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 6f5b0d2..c4eb902 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
@@ -19,13 +19,11 @@ Also landed after the review: maplibre's stylesheet is now lazy-``ed at pa
**Still open before merge:**
- The `user/` submodule commits are unpushed by choice (git-sync would deploy to prod), so the pin must be pushed and re-pointed at merge time.
-- This worktree's `user/` branch has diverged from `user/`'s `main`, which is *ahead* on content — notably `denmark-2026` is `published: false` here but `true` on main. That 404s the active trip and cascades through the post specs, so the worktree carries an uncommitted local `published: true` for testing. Bring `user/` up to `main` before merging rather than committing that flip.
+- ~~This worktree's `user/` branch has diverged from `user/`'s `main`~~ **Done** — `user/main` merged in (`7903432`). It was ahead on both content and theme fixes; `denmark-2026 published: true` came with it, so the local testing flip is gone. The one conflict was `js/post/post-form.js`, a generated bundle, resolved by rebuilding rather than hand-merging minified output.
+- The `~/Projects` clone's `user/` carries two commits this clone cannot see (the leg-connection map fix and the U+200E coordinate strip) — separate clone, not a worktree. They need to reach `user/main` before the pin is bumped.
- Remaining UI failures are pre-existing on `main`, not from this branch: `site.yaml` pins `owner_username` to a real account while the suite authenticates as `testrunner`, so owner-only controls never render for it. Only `trip-publish.spec.js` patches that; `delete-flow`, `edit-mode` and `anon-view` do not. Separate branch.
-- `make` is entirely broken in the **main** checkout: `-include .env` parses `.env` as makefile syntax and line 6 aborts with `*** missing separator`. Needs a value on that line fixed (or the loading approach changed) — not readable from here by policy.
-- **Three specs under `tests/ui/post/` fail on real, pre-existing defects** (found while reviewing main's uncommitted work; headers now record each, left red rather than skipped):
- - **UG2 — unguarded silent data loss, the one worth fixing first.** A photo whose upload fails keeps its thumbnail, satisfies the ≥1-photo validation, and the entry posts without the photo. `post-form.js`'s only create-form submit guard is `converting > 0` (pre-FilePond HEIC conversion); FilePond item state is never consulted at submit time. Directly relevant to posting from the road on a weak connection.
- - **UG1** — same missing gate, in-flight rather than failed. Both fail as `.photo-convert-status` "element(s) not found", because `photoStatusEl()` only ever runs from the HEIC paths.
- - **LD1** — EXIF-rotated photos still squeeze in the lightbox. `partials/entry-journal.html.twig:48-49` emits `{{ img.width }}`/`{{ img.height }}` (raw `getimagesize()`, orientation ignored) while the slide links the original, which browsers render rotated. Needs dimensions from a medium Grav has already oriented; **not verifiable locally** — the dev container has no php-exif, so `auto_fix_orientation` never applies.
+- Every `make` target aborts with `.env:6: *** missing separator` in **both** non-worktree clones (`~/Projects` and `~/Nextcloud/Projects`; reproduced with `make test-config` in each). Worktrees are unaffected only because `worktree-new` creates no `.env`, so the `-include` silently skips — which is why all the testing above ran. The env layering itself is correct and intended: `.env` global, `-include .env.$(ENV)` per-environment, `ENV` set automatically by the generated env-suffixed remote targets (`make remote-install-prod`). The fragility is narrower — because `.env` is pulled in with `-include`, it must be valid **makefile** syntax as well as valid dotenv, and line 6 currently is not. Typical causes: a leading tab (make reads it as a recipe line), a value spanning multiple lines, or a line without `=`.
+- **UG1, UG2 and LD1 under `tests/ui/post/` now pass** — they had been failing only because this branch predated `e17a5dc` ("block submit on unfinished photo uploads; un-squeeze EXIF portraits in lightbox"). Merging `user/main` in brought the upload gate and the oriented-derivative slide dims those specs assert, and all three went green with no product change. A first pass mistook them for live defects; the lesson is to check the submodule branch point before reading a red spec on a feature branch as a real bug.
## Goal Capsule
diff --git a/tests/ui/post/lightbox-dims.spec.js b/tests/ui/post/lightbox-dims.spec.js
index 0efc762..e6864dc 100644
--- a/tests/ui/post/lightbox-dims.spec.js
+++ b/tests/ui/post/lightbox-dims.spec.js
@@ -3,19 +3,15 @@
// browser actually renders for the linked image (BUG 2026-07-09: portrait
// iPhone JPEGs squeezed to landscape in the fullscreen lightbox).
//
-// Root cause: entry-journal.html.twig feeds `img.width`/`img.height` (raw
+// Root cause: entry-journal.html.twig fed `img.width`/`img.height` (raw
// getimagesize() of the ORIGINAL file — EXIF orientation ignored) into
-// data-pswp-*, while the slide href points at that original, which browsers
-// display EXIF-rotated. For a stored-landscape portrait photo the attrs say
-// landscape while the pixels render portrait → PhotoSwipe squeezes them.
+// data-pswp-*, while the slide href pointed at that original, which browsers
+// display EXIF-rotated. For a stored-landscape portrait photo the attrs said
+// landscape while the pixels rendered portrait → PhotoSwipe squeezed them.
//
-// ⚠️ THIS SPEC CURRENTLY FAILS — the root cause above is still live.
-// entry-journal.html.twig:48-49 remains `{{ img.width }}` / `{{ img.height }}`,
-// so the fixture (stored 800x600, EXIF Orientation=6) reports 800 while the
-// browser renders 600. Fixing it means sourcing the dimensions from a medium
-// Grav has already oriented rather than the raw original — which cannot be
-// verified locally, since the dev container has no php-exif and so never
-// applies auto_fix_orientation. Left failing so the squeeze stays visible.
+// Fixed in e17a5dc: slides now link a 2000px fit-within derivative and measure
+// THAT file, and derivatives are re-encoded upright, so the attrs and the
+// rendered pixels agree.
//
// The invariant tested here is environment-proof: whatever file the slide
// links to, its browser-rendered natural size must equal the data-pswp-*
diff --git a/tests/ui/post/upload-gate.spec.js b/tests/ui/post/upload-gate.spec.js
index 2b5e553..d873205 100644
--- a/tests/ui/post/upload-gate.spec.js
+++ b/tests/ui/post/upload-gate.spec.js
@@ -13,19 +13,11 @@
// post-form.js owns the complete gate (theme code; the form plugin is
// GPM-managed and not patchable in-repo).
//
-// ⚠️ BOTH CASES CURRENTLY FAIL — the gate they specify is NOT implemented.
-// post-form.js's only create-form submit guard is `converting > 0` (the
-// pre-FilePond HEIC conversion, "Hang on — a photo is still converting."). It
-// never inspects FilePond's item state at submit time. `.photo-convert-status`
-// is created lazily by photoStatusEl(), which only runs from setStatus() on the
-// HEIC paths — so for a plain JPEG the element never exists and both
-// expectations below fail as "element(s) not found", not as a wrong message.
-// refreshCollapse() does read data-filepond-item-state, but only to word the
-// ("Uploading N photos…"); it gates nothing.
-// These are therefore red specs describing intended behaviour. UG2 is the one
-// that matters: a failed upload keeping its thumbnail is a silent-data-loss
-// path with no guard. Left failing rather than skipped so the gap stays visible
-// — see the plan's open items.
+// The gate lives in e17a5dc: submit is blocked unless EVERY FilePond item is
+// processing-complete, with distinct messages for the failed and still-uploading
+// cases. Both assert on .photo-convert-status, which post-form.js's setStatus()
+// creates via photoStatusEl() — so a passing expectation here proves the THEME
+// gate fired, not the form plugin's, whose own guard only raises alert().
const { test, expect } = require('@playwright/test');
const { fillEditor, findEntry, cleanupEntry, TEST_PHOTO } = require('../helpers');