A ui-test entry had survived into the active trip's dailies. Three independent
failures had to line up for that, and all three were real:
1. cleanupEntry() used host-side fs.rmSync. Grav's Apache workers run as root,
so every entry the form creates is root-owned and recursive removal needs
write permission on that directory — which the host user lacks. Cleanup had
never worked for form-created entries; it just threw inside a path nothing
checked. It now falls back to `docker exec … rm -rf` in the container that
actually serves USER_DIR.
2. globalTeardown's dailies sweep keyed off a `parent:` in post-form.md — a key
deliberately removed (the write target comes from site.yaml active_trip, and
CLAUDE.md forbids re-adding a static parent). The regex could never match, so
dailiesDir was always null and the sweep silently did nothing. It now reuses
helpers' own resolution instead of keeping a divergent copy.
3. Nothing pinned the suite to this checkout's server. playwright.config.js
defaults to :8081, so a worktree run hit the MAIN checkout — entries created
in one content tree while the specs asserted and cleaned up in another.
test-ui now passes GRAV_BASE_URL from GRAV_PORT, and globalSetup hard-fails
when the server's bind mount disagrees with the tree the specs read.
Also fixed, found on the way to a green run:
- test-account interpolated the password into an `sh -c` string, so a password
containing a shell metacharacter was re-parsed by the container's shell
(`sh: 2: <fragment>: not found`, no account, every UI run dead). It now
travels via `docker exec -e`, making the recipe indifferent to its contents.
- `make start` in a worktree always failed: travel-memories declares
`env_file: .env` and worktree-new creates none. It degrades to start-grav
there — a worktree with no server is what sent runs to :8081 in the first
place.
- test-form-config asserted a hero_image field that 8cf1145 deliberately
removed; it had been failing ever since.
Verified: config 22/22, post 6/6, location-override 20/20, and a full UI run
now leaves zero ui-test entries behind. The remaining UI failures are
pre-existing on main — site.yaml pins owner_username to a real account while
the suite logs in 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. Left for a separate branch.
The U5 guard asserted only `.notices` toHaveCount(0) and toHaveURL(/\/post/).
Both pass instantly, and both also hold for a *successful* submit — the form
posts to /post and only renders its notice after the round trip, and the click
is dispatched via evaluate(el => el.click()), which skips Playwright's
navigation-aware waiting. So the one test standing between a bad coordinate
and the server could not fail. It now proves the negative on disk via
findEntry() after a settling interval, registers the tag for cleanup before
the click, and asserts the flag and value survived.
CLAUDE.md's "one map path" section stated flatly that a single map code path
exists, which location-map.js now contradicts. Recorded it as the one
sanctioned exception (an editor, not a display map; lazy-imported; shares only
MAP_STYLE) rather than leaving the doctrine wrong.
The `user` gitlink is deliberately NOT bumped here: its pin already points at
an unpushed submodule commit, which must be pushed and re-pointed before this
branch merges.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds Playwright coverage for the four cross-reviewer-confirmed bugs
fixed in the user/ submodule (map-load race on rapid reopen, mismatch
flag clearing on blank, and submit blocked on unresolved mismatch),
and bumps the user/ pointer to the commit with those fixes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mocks the Open-Meteo geocoding endpoint via page.route() so the suite is
hermetic. Covers the panel's closed-by-default state, search happy path,
Paris/Texas disambiguation ranking, no-match/network-failure/in-flight
states, XSS-safe rendering, map canvas singleton behavior, drag sync, the
mismatch flag, the maplibre-gl lazy-load boundary, and a full submit
round-tripping lat/lng into the entry's frontmatter.
Regression specs for the two 2026-07-09 prod bugs (fixed in user/ e17a5dc):
- upload-gate.spec.js — UG1/UG2: create submit is blocked with a visible
message while a photo upload is in flight or after it FAILED; nothing may
land on disk. The form plugin's own guard misses LOADING and
PROCESSING_ERROR, which silently dropped a photo on a fast save.
- lightbox-dims.spec.js — LD1: a slide's data-pswp-* must equal the
browser-rendered natural size of the linked image. Fixture is an 800x600
JPEG with EXIF Orientation=6 (renders 600x800 portrait), planted on disk
in the demo trip (the active trip may be an unpublished draft that 404s).
New fixture: tests/fixtures/test-photo-exif-portrait.jpg.
Note: the suite currently needs GRAV_TEST_USER/GRAV_TEST_PASS overrides —
the .env GRAV_TEST_PASS contains shell-special chars that break `make
test-account` (see the Makefile comment requiring a plain password).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
Hoist the duplicated per-spec createEntry photo-fixture into a single
createPhotoEntry() in helpers.js (used by delete-flow, edit-mode, and the
anon-view draft). Register the tag for cleanup BEFORE the awaited 15s
success-toast assertion, so a create that lands on disk but whose toast
assertion times out no longer leaks an untracked entry. Add AE3b covering the
disclosure deviation branch (a non-default toggle auto-expands More options).
Code review F2 (leak), F3 (duplication), F6 (coverage).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
New specs: edit-mode (ES1 save round-trip + ES2/ES3 prefill 404/500 states),
delete-flow (DEL1-3 happy/cancel/failed), anon-view (AN1 no owner controls,
AN2 draft hidden from anon), photo-editor (live add/delete/reorder). Existing:
P3-P8 now attach a photo to satisfy the create photo-gate; V3 picker cap 4->6.
Full post suite 38/38, stable across parallel (3-worker) runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
The create path now routes through the shared PhotoRenumberer, which
zero-pads to photo-01..NN; update the Playwright assertions and title to
match the new naming.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hero_image blueprint field was dropped (journal heroes come from the
first uploaded photo), so AE3 now asserts the More-options disclosure
toggles open/closed rather than probing the hero input's visibility.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a test for the ≥1-photo requirement (submit blocked with a photo-section
error) and the max of 6 (FilePond maxFiles). Rework AE4: a corrupt HEIC is the
only "photo", so fail-closed now means submit is blocked rather than posting a
text-only entry. Give the success-CTA test a photo so it can post under the new
rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers the theme datetime override: the date field renders as
<input type="datetime-local">, is prefilled with the current local time in
the native value format, and clearing it blocks submit client-side (no
success notice) — the guard that keeps an invalid date from wiping the
FilePond photo list on a server re-render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers the reorder pipeline: two uploads produce photo-1.jpg/photo-2.jpg on the
posted entry, and the top-level photo_order POST key never appears in the entry
frontmatter. Adds a second image fixture so both slots are exercised.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retarget AE1's completed-item assertion (the item is now hidden once the
section auto-collapses) to the "✓ N photo ready" summary, and add a spec
covering collapse-after-upload + re-expand on tap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
waitForPhotoUpload waits on FilePond processing-complete; AE1/AE4 use
input.filepond--browser + the .photo-convert-status error status; V3/V4 back
to FilePond limit + non-image rejection; reauth hint -> .photo-reauth-hint;
test-form-config asserts type:filepond. Verified against a live server via a
browser smoke run (HEIC->JPEG attach, corrupt fail-closed, draft restore).
- test-form-config.sh: assert parent is NOT hardcoded (injected server-side),
active_trip set in site.yaml, and the new fields incl. custom 'photos' type.
- helpers.js: resolve active trip from site.active_trip (parent coupling gone);
fillEditor() drives EasyMDE via window.postFormEditor; waitForPhotoUpload()
waits on the new picker.
- post.spec / validation.spec: content via the editor, filepond selectors ->
the photo picker, P8 checks editor value, V3/V4 exercise the picker cap +
fail-closed non-image.
- post-form-ux.spec.js (new): AE3 disclosure, AE1 HEIC->JPEG, AE4 corrupt-HEIC
fail-closed, R18 weather gating, R20 draft restore.
- fixtures: real + corrupt .heic.
- test-post.sh: resolve dailies dir from active_trip.
Refs AE1-AE4, R18, R20, U7.
- P6: verify "Entry posted successfully!" toast after submit
- P7: verify server resolves default:now to a recent timestamp in saved frontmatter (Grav renders the literal "now" string in the HTML input; resolution happens server-side)
- P8: verify title/content fields empty after successful submit (form reset:true)
Also fix pre-existing helpers.js issues:
- TRACKER_DIR now resolves via docker inspect or GRAV_USER_DIR env var so tests find entries even when running from a worktree without a user/ directory
- DAILIES_URL exported and derived from post-form.md pageconfig.parent so P1/P2 navigate to the correct active-trip URL
- cleanupEntry/findEntry now guard against missing TRACKER_DIR
- P2 marked test.skip (was running and failing on missing fixture)