Render on each journal card, gated by owner_can_edit (R1/R3/R5/R6, KTD8):
- Draft badge (amber) when the entry is unpublished.
- Edit link -> /post?edit=<route>&return=<feed-url> (D5: a save from the home
feed returns to home, not always the trip page); feed_return_url threaded from
trip.html.twig/home.html.twig (page.url).
- Delete control with inline Cancel/Confirm button-swap (no browser dialog),
data-entry-route carried for U6's delete JS, plus an aria-live status slot.
- 44x44px min tap targets (D8); titlerow wraps on narrow phones.
Verified on the container: anon sees no controls/badge; owner sees Edit+Delete on
all 13 cards with a Draft badge on the unpublished one; a non-owner authenticated
session (testrunner vs owner mischa) sees none and no drafts (V5). Browser
screenshot verification unavailable (no local Playwright); did a code-level
layout review instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
Owner-aware draft visibility (R5, KTD7/KTD8) on trip.html.twig and home.html.twig
(active-trip branch):
- Compute owner_can_edit = authenticated AND username == site.owner_username AND
(this is the active trip). The super-admin tester authenticates too, so the gate
is owner identity, not mere login.
- The feed list (all_items) uses an owner-aware journal collection: owner sees
drafts, everyone else (and every non-active-trip view) sees published only.
- journal_entries stays published-only — it feeds stats/counts. map_entries now
filters on item.page.published, so drafts get a feed card but no marker and no
stat contribution.
- Thread owner_can_edit into trip-feed-col (defaults false) for the U4 controls.
Between-trips home grid stays published-only. Stories untouched (journal drafts only).
Verified on the container: draft entry with coords shows in the owner's feed but
not the map or count; absent entirely for anonymous (V4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
trip.html.twig and home.html.twig hand-wrote near-identical map-column
markup + a MapUtils.initEntryMap() invocation. Extract both into
partials/entry-map.html.twig, parameterised (map id, global, entries,
gpx config, story markers). Callers now pass resolved header values.
The map engine (js/map.js initEntryMap) is unchanged; emitted config is
byte-equivalent to the previous inline scripts. Verified: trip-map,
home-map and window.tripMap/homeMap Playwright tests pass; both pages 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the duplicated MapLibre init orchestration (map construction,
marker/popup loop, bounds fit, GPX journey, fullscreen toggle) into one
config-driven MapUtils.initEntryMap(opts) in maplibre-utils.js, bundled
into map.js. Convert trip.html.twig and both home.html.twig branches to
call it; home active gains the flash-highlight + a fullscreen button to
match trip, and home highlights' marker click now navigates to the
article. Adds a markLatest opt (false for highlights) and exposes the
map as window.tripMap/window.homeMap (used by existing Playwright specs).
feed-map.html.twig and map.html.twig left on their inline init (deferred).
Plan: docs/working/plans/2026-06-27-map-init-consolidation.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
Addresses code-review finding: the pre_departure branch made trip-feed-col
dual-purpose. Pre-departure landing now lives in its own partial; home picks
it via {% if all_items|length == 0 %}, and trip-feed-col drops the
pre_departure param to be shared 1:1 by both callers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
After the CDN elimination refactor, maplibregl moved from an inline head
script to the bottom-group bundle — but home.html.twig still called
new maplibregl.Map() immediately, before map.js had loaded. Wraps both
the active-trip and between-trips script blocks in DOMContentLoaded,
matching the pattern already used in trip/map/feed-map templates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pswp.currSlide is a Slide instance whose DOM element is stored as
.container (.pswp__zoom-wrap). The .el property belongs to the
itemHolder wrapper, not the Slide — so currSlide.el was always
undefined, the null-guard exited early, and no animation played.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Previous approach (CSS transition + reflow trick) is unreliable in
Firefox. New approach: PhotoSwipe emits 'change' synchronously before
painting; we add a direction-aware CSS keyframe animation to the
incoming slide element, with animation-fill-mode:both so there is no
flash before the animation starts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
PhotoSwipe's goTo() moves slides instantly (no spring animation unlike
swipe). Intercepts keydown in capture phase, sets a CSS transition and
forces a reflow before PhotoSwipe moves the container, so the browser
animates from the old position to the new one. Cleans up on close.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Creates partials/entry-journal.html.twig and partials/entry-story.html.twig
so trip, dailies, and home all use the same up-to-date markup. Home page
gains PhotoSwipe, blurred fill, adaptive aspect ratio, and hash-based
marker scroll. Future changes only need to happen in one place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Replaces the boolean toggle with a select field offering:
on — connect all consecutive entries (chronological line)
manual — force_connect entries only (user-controlled connections)
intelligent_gpx — suppress connectors where GPX covers both endpoints;
force_connect overrides (original smart logic, restored)
off — no connectors at all; force_connect also ignored
buildJourneySegments gains an optional trackpointsPerFile param used
only by intelligent_gpx mode. renderGpxJourney extracts trackpoints
only when connectMode is intelligent_gpx. dailies.html.twig falls
back from intelligent_gpx → on (mini-map has no GPX tracks to
suppress against).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Adds two configurable toggles to the trip blueprint (Admin2 Trip tab):
- use_gpx: show/hide GPX tracks on all maps (default: enabled)
- autoconnect: draw connector lines between markers (default: enabled)
When use_gpx is off, GPX files are not fetched or rendered on any map
(home, map, trip, dailies). The stats panel in trip.html.twig still
reads GPX_URLS directly and is unaffected.
When autoconnect is off, buildJourneySegments suppresses all
auto-connectors; only entries with force_connect:true still draw a
line — making force_connect behaviour independent of both settings.
Also refactors the inline Promise.all in trip.html.twig to use the
shared renderGpxJourney utility (reducing duplication).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
The home map was drawing an initial addJourneyLine, then trying to remove
layer 'home-journey' in the Promise.all callback — but addJourneyLine names
the layer 'home-journey-line', so removeLayer was a no-op and removeSource
failed (layer still referencing the source), leaving a ghost line on top of
the GPX tracks.
Extract the Promise.all → GPX tracks → buildJourneySegments → addJourneySegments
pattern into MapUtils.renderGpxJourney() and replace both map.html.twig and
home.html.twig with the shared call. No upfront journey line is drawn — the
function handles the no-GPX case correctly via Promise.all([]).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
- Explicit height: 40vh on .home-map (not just 100% of parent) so Leaflet
can measure the container reliably before CSS inheritance is resolved
- align-self: stretch on .home-map-col so it spans full width in flex column
- setTimeout invalidateSize(100ms) on home and dailies maps as safety net
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ