Status → Complete; document the markLatest opt added during execution, the window.tripMap/homeMap exposure, and the test outcome (38 passed; M6/H1 pre-existing and out of scope). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
271 lines
25 KiB
Markdown
271 lines
25 KiB
Markdown
# Map Init Consolidation — shared `MapUtils.initEntryMap()`
|
||
|
||
**Status:** ✅ Complete (2026-06-27)
|
||
|
||
> Plan type: `refactor` · Depth: Standard · Origin: deferred memory `project-map-init-refactor` (re-scoped 2026-06-27 after home/trip convergence)
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
The recent home/trip convergence work multiplied an already-duplicated pattern: there are now **five** near-identical MapLibre init blocks, each repeating ~50–130 lines of map construction, marker/popup loop, bounds-fitting, journey rendering, and fullscreen wiring. The shared `maplibre-utils.js` already centralizes marker *creation* and GPX/journey rendering, but **not the init orchestration** — that is what is copy-pasted and has since drifted into subtly inconsistent behavior.
|
||
|
||
This plan extracts the init orchestration into one config-driven function, `MapUtils.initEntryMap(opts)`, in `user/themes/intotheeast/js/maplibre-utils.js` (which esbuild already bundles into `js/map.js`, so every template that loads `map.js` picks it up). The two actively-used surfaces — the **trip page** and the **home active-trip view** — are converted to call it and become behaviorally identical. The **home highlights (between-trips) view** is converted too, with a deliberate small UX change: marker click navigates to the article instead of scrolling to a grid card (hover-title already exists). The dormant `feed-map.html.twig` partial and `map.html.twig` full-page map are **left untouched** this pass.
|
||
|
||
This is a refactor with two intentional, scoped behavior changes (both on the home page) — not byte-for-byte preservation.
|
||
|
||
---
|
||
|
||
## Problem Frame
|
||
|
||
`maplibre-utils.js` gives every map the same building blocks (`createDotMarker`, `createStoryMarker`, `renderGpxJourney`, `MAP_STYLE`), but each template still hand-writes the *assembly*: `new maplibregl.Map(...)`, attribution control, the `on('load')` marker loop with hover popup + click handler, `fitBounds`/`jumpTo`, and the fullscreen toggle IIFE. Five copies exist:
|
||
|
||
| Surface | Container | Marker click (today) | In active use? |
|
||
|---|---|---|---|
|
||
| `trip.html.twig` | `#trip-map` | scroll+flash `entry-` card, fullscreen-aware | ✅ active |
|
||
| `home.html.twig` active branch | `#home-map` | set hash to `entry-` card, **no flash, not fullscreen-aware** | ✅ active |
|
||
| `home.html.twig` highlights branch | `#home-map` | `scrollIntoView` to `highlight-` card | ✅ active |
|
||
| `partials/feed-map.html.twig` | `#feed-map` / `#stories-map` | scroll+flash else navigate, fullscreen-aware | ⚠️ dormant (dailies/stories) |
|
||
| `map.html.twig` | `#trip-map` (full page) | always navigate to URL | ⚠️ dormant |
|
||
|
||
**Consequences of the duplication:**
|
||
- The trip page and home active view are meant to be the same component but have already drifted (home active lacks the flash highlight and the fullscreen button trip has).
|
||
- Any future map change must be applied in up to five places, by hand, with no shared test surface.
|
||
- The click logic carries five subtly different implementations of just **two** real intents: *scroll to the matching card on this page*, or *navigate to the entry's own page*.
|
||
|
||
**Why now:** The deferral in `project-map-init-refactor` was justified by "dailies/stories/full-map aren't in active use." That still holds for those three — but trip and home are now both active and nearly identical, so the high-value consolidation is unblocked while the dormant surfaces stay out of scope.
|
||
|
||
---
|
||
|
||
## Scope Boundaries
|
||
|
||
**In scope:**
|
||
- New `MapUtils.initEntryMap(opts)` in `maplibre-utils.js` + esbuild rebuild.
|
||
- Convert `trip.html.twig` to call it (behavior preserved).
|
||
- Convert `home.html.twig` active branch to call it + add a fullscreen button so it matches the trip page exactly.
|
||
- Convert `home.html.twig` highlights branch to call it (click → navigate to article).
|
||
|
||
**Intentional behavior changes (both home page only):**
|
||
- Home active view **gains** the flash-highlight on card scroll and the fullscreen button/awareness it currently lacks → becomes identical to the trip page.
|
||
- Home highlights view marker click **changes** from `scrollIntoView` to the grid card → navigate to the article URL. Hover-title popup is unchanged (already present).
|
||
- **Both home maps' attribution restyles.** `initEntryMap` always constructs with `attributionControl: false` + a compact `AttributionControl` bottom-left, collapsed on load (mirroring trip). The home active and home highlights maps currently use MapLibre's default attribution (expanded, bottom-right), so both move to trip's compact collapsed bottom-left. For home active this is part of "identical to trip"; for home highlights — which is otherwise unchanged except for the click behavior — it is an *incidental* restyle. If highlights should keep the default attribution, parameterize attribution in `opts` (e.g. `attribution: { compact, position, collapse }`) rather than baking trip's treatment into every caller.
|
||
|
||
### Deferred to Follow-Up Work
|
||
- Converting `partials/feed-map.html.twig` (dailies/stories) onto `initEntryMap`. It is already a shared partial with low duplication cost and the pages are dormant; retrofit it when those feeds return to active use. The unified click rule already matches feed-map's current behavior, so this will be a near-drop-in later.
|
||
- Converting `map.html.twig` (full-page map) onto `initEntryMap`. Dormant; navigate-only behavior is the `cardPrefix: null` path, so it too will be a clean later conversion.
|
||
|
||
**Out of scope:** no CSS changes, no map-style change, no change to the Twig-side `map_entries`/`gpx_urls` computation, no change to `createDotMarker`/`createStoryMarker`/`renderGpxJourney`.
|
||
|
||
---
|
||
|
||
## Key Technical Decisions
|
||
|
||
**KTD1 — Init orchestration lives in `maplibre-utils.js`, not per-template.**
|
||
`maplibre-utils.js` is imported by `js/src/map.js` and bundled by esbuild into the minified `js/map.js` that every map-bearing template loads via `{% block map_assets %}`. Adding `initEntryMap` there + rebuilding makes it available everywhere with a single source of truth. This is the whole point of the refactor.
|
||
|
||
**KTD2 — One unified click rule, no click-mode enum.**
|
||
The function takes an optional `cardPrefix`. Click behavior is a single rule: *if `cardPrefix` is set and `document.getElementById(cardPrefix + slug)` exists, scroll to it (via `location.hash`) and flash `is-highlighted`, fullscreen-aware when a fullscreen target is configured; otherwise navigate to `entry.url`.* This single rule subsumes every behavior the in-scope surfaces need — trip and home active pass `cardPrefix: 'entry-'`; home highlights passes no prefix and gets navigate-on-click for free. It also happens to match the dormant feed-map/map.html behaviors, easing their later conversion. No `clickMode` parameter is introduced.
|
||
|
||
**Card-absent fallback — note the divergence from trip today.** The current trip handler does `if (!card) return;` (a no-op) when no card matches the slug; the unified rule instead **navigates** to `entry.url`. This is behavior-preserving on every in-scope surface **only under the invariant that every map marker has a matching feed card** (`entry-<slug>`, emitted by both the journal and story entry partials). Document that invariant where it is relied on (U2). If a future map entry can ever lack a feed card (a map-only POI, a new pin type), make the fallback per-surface — trip = no-op, highlights = navigate — rather than letting the shared default retroactively change trip's semantics.
|
||
|
||
**KTD3 — `map_entries` / `gpx_urls` stay computed in Twig.**
|
||
Per the Milestone 2 refactor decision, Twig macros cannot return arrays, so each template keeps its existing Twig loop that builds `map_entries` and serializes it to a JS var. The only change is replacing the inline init `<script>` body with a single `MapUtils.initEntryMap({...})` call inside `DOMContentLoaded`. Each template's map `<script>` shrinks from ~50–90 lines to ~10.
|
||
|
||
**KTD4 — Dormant surfaces excluded.**
|
||
`feed-map.html.twig` and `map.html.twig` keep their current inline init this pass (see Deferred). Reduces blast radius to the two active surfaces plus the home highlights view.
|
||
|
||
**KTD5 — Home active fullscreen button reuses existing CSS.**
|
||
The fullscreen button uses the same `.feed-map-fullscreen-btn` markup as the trip page, and the fullscreen target is `.home-map-col`. Both `.feed-map-fullscreen-btn` (`css/style.css:636`) and `.home-map-col.is-fullscreen` (`css/style.css:911`) already exist — no CSS changes required.
|
||
|
||
---
|
||
|
||
## High-Level Technical Design
|
||
|
||
**Who calls the shared function (after this plan):**
|
||
|
||
```
|
||
maplibre-utils.js ── MapUtils.initEntryMap(opts) ──┐
|
||
(bundled into js/map.js) │
|
||
├─ trip.html.twig → cardPrefix:'entry-', fullscreen, story markers
|
||
├─ home.html.twig (active) → cardPrefix:'entry-', fullscreen
|
||
└─ home.html.twig (highlights) → no cardPrefix (→ navigate)
|
||
|
||
feed-map.html.twig / map.html.twig → unchanged (own inline init, deferred)
|
||
```
|
||
|
||
**Unified marker-click rule** (the single behavior the function implements):
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[Marker clicked] --> B{cardPrefix set AND<br/>card #prefix+slug exists?}
|
||
B -- no --> C[navigate to entry.url]
|
||
B -- yes --> D{fullscreen target<br/>configured AND open?}
|
||
D -- yes --> E[close fullscreen,<br/>then scroll+flash after delay]
|
||
D -- no --> F[scroll to hash,<br/>flash is-highlighted]
|
||
```
|
||
|
||
**`initEntryMap(opts)` shape** (directional, not a signature spec):
|
||
|
||
| Option | Type | Used by | Notes |
|
||
|---|---|---|---|
|
||
| `container` | string | all | map div id (`'trip-map'`, `'home-map'`) |
|
||
| `entries` | array | all | already-parsed `map_entries` from Twig |
|
||
| `cardPrefix` | string \| null | trip, home active | `'entry-'`; omit/null → markers navigate to `entry.url` |
|
||
| `storyMarkers` | bool | trip | render `createStoryMarker()` for `type === 'story'`; default dot markers |
|
||
| `markLatest` | bool | trip, home active | enlarge the final non-story entry's dot (default `true`); home highlights passes `false` so no marker is singled out in the shuffled set (added during execution — preserves highlights' current all-equal dots) |
|
||
| `fullscreen` | `{ btnId, colSelector }` \| null | trip, home active | wires the fullscreen toggle; null → no fullscreen |
|
||
| `gpx` | `{ urls, use, autoconnect, sourcePrefix, journeyId }` \| null | trip, home active | forwarded to `renderGpxJourney`; null → skip |
|
||
| `fit` | `{ padding, maxZoom, singleZoom }` | all | defaults `{60, 11, 10}`; highlights uses `maxZoom: 8`, `singleZoom: 8` |
|
||
|
||
The function returns the map instance and internally does: construct map (`attributionControl: false`) + compact `AttributionControl` bottom-left; on `load` build bounds, loop entries → marker + hover popup + unified click handler, `fitBounds`/`jumpTo`, collapse the attribution `<details>`, call `renderGpxJourney` when `gpx` is set; wire the fullscreen IIFE when `fullscreen` is set; `setTimeout(map.resize, 100)`.
|
||
|
||
---
|
||
|
||
## Implementation Units
|
||
|
||
### U1. Add `MapUtils.initEntryMap(opts)` to `maplibre-utils.js`
|
||
|
||
**Goal:** Introduce the config-driven init function and rebuild the bundle. No template consumes it yet.
|
||
|
||
**Dependencies:** none.
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/js/maplibre-utils.js` (add `initEntryMap`, export it on the `global.MapUtils` object)
|
||
- Build artifact (regenerated, do not hand-edit): `user/themes/intotheeast/js/map.js`
|
||
|
||
**Approach:**
|
||
- Add `function initEntryMap(opts) { ... }` near the other public helpers; add `initEntryMap: initEntryMap` to the `global.MapUtils = { ... }` export block.
|
||
- Implement the full orchestration described in HTD: map construction, attribution control + collapse, the `on('load')` marker loop (hover popup identical to current `map-tip` popups; marker element via `createStoryMarker()` when `opts.storyMarkers && entry.type === 'story'`, else `createDotMarker(isLatest)` where `isLatest = (entry.type !== 'story') && (i === entries.length - 1)` — the final-indexed entry, and only when it is not a story, mirroring `trip.html.twig:110` exactly; note this enlarges *nothing* when the last entry is a story, which is the current trip behavior and must be preserved — do **not** reinterpret it as "the last non-story entry"), bounds fit (`fitBounds` with `opts.fit` defaults, `jumpTo` for single entry), `renderGpxJourney` when `opts.gpx`, the fullscreen toggle IIFE when `opts.fullscreen`, and the trailing `resize`.
|
||
- Implement KTD2's unified click rule exactly: resolve card by `opts.cardPrefix + entry.slug`; if absent → `window.location.href = entry.url`; if present → set `location.hash`, then after 350ms add `is-highlighted` for 700ms; when `opts.fullscreen` is configured and the col is `.is-fullscreen`, click the fullscreen button first and defer the scroll ~450ms (mirror the current trip handler timings).
|
||
- Keep the empty-entries behavior cheap: if `entries.length === 0`, still construct the map and return (the trip page's existing "no locations yet" copy is page-specific and stays in the template if needed — do not bake page copy into the util).
|
||
- Rebuild: run `make build-assets` so `js/map.js` regenerates from source. Never hand-edit `js/map.js`.
|
||
|
||
**Patterns to follow:** mirror the existing trip page handler (`trip.html.twig:100-171`) as the canonical behavior, since trip is the surface whose behavior is being preserved; reuse the existing module structure and IIFE export pattern already in `maplibre-utils.js`.
|
||
|
||
**Execution note:** This is the load-bearing unit. Implement it to faithfully reproduce the trip page's current behavior before any caller is switched, so U2 is behavior-preserving — a no-op under the card-matching invariant noted in KTD2; the card-absent navigate fallback is the one deliberate divergence and is unreachable on trip today.
|
||
|
||
**Test scenarios:**
|
||
- *Happy path (card present):* with a `cardPrefix` and a matching card in the DOM, clicking a marker sets `location.hash` to `prefix+slug` and toggles `is-highlighted` on the card (added after ~350ms, removed ~700ms later).
|
||
- *Happy path (no prefix):* with `cardPrefix` null/omitted, clicking a marker sets `window.location.href` to `entry.url`.
|
||
- *Fallback:* with a `cardPrefix` set but no matching card in the DOM, clicking navigates to `entry.url`.
|
||
- *Fullscreen-aware:* with `fullscreen` configured and the col `.is-fullscreen`, a marker click triggers the fullscreen button click first, then scrolls.
|
||
- *Bounds:* one entry → `jumpTo` at `fit.singleZoom`; multiple entries → `fitBounds` with `fit.padding`/`fit.maxZoom`.
|
||
- *Story markers:* with `storyMarkers: true` and an entry `type === 'story'`, a story marker is used and that entry is never treated as `isLatest`.
|
||
- *GPX:* with `gpx` set, `renderGpxJourney` is called with the forwarded urls/sourcePrefix/journeyId/connectMode; with `gpx` null it is not called.
|
||
- *Empty:* `entries: []` constructs the map without throwing and renders no markers.
|
||
- *Build:* after `make build-assets`, `js/map.js` is regenerated and `window.MapUtils.initEntryMap` is defined at runtime.
|
||
|
||
**Verification:** `MapUtils.initEntryMap` is exported and callable; `make build-assets` completes and updates `js/map.js`; no console errors when invoked.
|
||
|
||
---
|
||
|
||
### U2. Convert `trip.html.twig` to `initEntryMap` (behavior preserved)
|
||
|
||
**Goal:** Replace the trip page's inline ~85-line map `<script>` body with a single `initEntryMap` call; behavior is preserved — behaviorally equivalent under the card-matching invariant in KTD2 (not literally byte-for-byte, since the `<script>` body is rewritten and the card-absent fallback changes from no-op to navigate, which is unreachable on trip).
|
||
|
||
**Dependencies:** U1.
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/trip.html.twig`
|
||
|
||
**Approach:** Keep the Twig `map_entries`/`gpx_urls` computation and the `TRIP_ENTRIES`/`GPX_URLS`/`USE_GPX`/`AUTOCONNECT` JS var declarations. Replace everything inside `DOMContentLoaded` (the `new maplibregl.Map`, the `on('load')` loop, fit-bounds, `renderGpxJourney`, attribution collapse, the fullscreen IIFE, the trailing resize) with one call: `MapUtils.initEntryMap({ container: 'trip-map', entries: TRIP_ENTRIES, cardPrefix: 'entry-', storyMarkers: true, fullscreen: { btnId: 'trip-map-fullscreen', colSelector: '.home-map-col' }, gpx: { urls: GPX_URLS, use: USE_GPX, autoconnect: AUTOCONNECT, sourcePrefix: 'gpx', journeyId: 'trip-journey' }, fit: { padding: 60, maxZoom: 11, singleZoom: 10 } })`. Leave the fullscreen button markup and the `#trip-totop` button as-is.
|
||
|
||
**Patterns to follow:** existing `trip.html.twig` markup and var names; the include-call style already used for partials.
|
||
|
||
**Test scenarios:**
|
||
- *Markers + hover:* trip page renders one dot per entry plus story markers; hovering shows the `map-tip` title popup (unchanged).
|
||
- *Click → scroll+flash:* clicking a marker scrolls to its `entry-<slug>` feed card and flashes it.
|
||
- *Fullscreen:* the fullscreen button still expands `.home-map-col` and the marker-click-while-fullscreen path still closes then scrolls.
|
||
- *GPX:* GPX tracks + journey segments still render when `use_gpx` is on.
|
||
- *Regression:* visual diff against pre-change trip page shows no behavioral difference.
|
||
|
||
**Verification:** trip page at `localhost:8081/trips/<active_trip>` behaves identically to before — markers, popups, click-scroll-flash, fullscreen, GPX all intact; no console errors.
|
||
|
||
---
|
||
|
||
### U3. Convert `home.html.twig` active-trip branch + add fullscreen button (match trip page)
|
||
|
||
**Goal:** The home active-trip map becomes behaviorally identical to the trip page — it gains the flash-highlight and a working fullscreen button it currently lacks.
|
||
|
||
**Dependencies:** U1. Independent of U2.
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/home.html.twig` (active branch markup + script)
|
||
|
||
**Approach:**
|
||
- Markup: add the fullscreen button inside the active branch's `<div class="home-map" id="home-map">` (currently `home.html.twig:64`), reusing the exact `.feed-map-fullscreen-btn` markup from `trip.html.twig:61-67` with `id="home-map-fullscreen"`. No CSS changes (KTD5).
|
||
- Script: keep the `HOME_ENTRIES`/`HOME_GPX_URLS`/`USE_GPX`/`AUTOCONNECT` var declarations; replace the inline `new maplibregl.Map` + `on('load')` body with `MapUtils.initEntryMap({ container: 'home-map', entries: HOME_ENTRIES, cardPrefix: 'entry-', fullscreen: { btnId: 'home-map-fullscreen', colSelector: '.home-map-col' }, gpx: { urls: HOME_GPX_URLS, use: USE_GPX, autoconnect: AUTOCONNECT, sourcePrefix: 'home-gpx', journeyId: 'home-journey' }, fit: { padding: 60, maxZoom: 11, singleZoom: 10 } })`.
|
||
- Note: `storyMarkers` is omitted (home active currently uses dot markers only — preserved).
|
||
|
||
**Patterns to follow:** the trip page conversion (U2) and the trip fullscreen button markup.
|
||
|
||
**Test scenarios:**
|
||
- *Parity:* home active map renders markers, hover popups, and click-scroll **with flash** (previously no flash) to `entry-<slug>` cards.
|
||
- *Fullscreen (new):* the new `home-map-fullscreen` button expands `.home-map-col`, and a marker click while fullscreen closes then scrolls — matching trip.
|
||
- *GPX:* home GPX journey still renders (`home-gpx` / `home-journey` source ids preserved).
|
||
- *No story markers:* dot markers only, as before.
|
||
|
||
**Verification:** home page (travelling/active state) map matches the trip page in every interaction; fullscreen button visible and functional on mobile widths; no console errors.
|
||
|
||
---
|
||
|
||
### U4. Convert `home.html.twig` highlights branch (click → navigate)
|
||
|
||
**Goal:** The between-trips highlights map uses the shared init, and marker click opens the article instead of scrolling to a grid card. Hover-title popup unchanged.
|
||
|
||
**Dependencies:** U1. Lands naturally alongside U3 (same file) but is a distinct behavior change.
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/home.html.twig` (highlights branch script, `home.html.twig:245-289`)
|
||
|
||
**Approach:** Keep the `HIGHLIGHTS_ENTRIES` var declaration. Replace the inline `new maplibregl.Map` + `on('load')` body (including the current `scrollIntoView` click handler) with `MapUtils.initEntryMap({ container: 'home-map', entries: HIGHLIGHTS_ENTRIES, fit: { padding: 60, maxZoom: 8, singleZoom: 8 } })` — no `cardPrefix`, no `fullscreen`, no `gpx`. The absent `cardPrefix` yields navigate-on-click per KTD2; the `map-tip` hover popup is provided by the shared loop, so hover-title is preserved with no extra code. Note: this also restyles the highlights map's attribution to trip's compact collapsed bottom-left (see Scope Boundaries → "Both home maps' attribution restyles") — an incidental change; pass an attribution `opts` override if the MapLibre default should be retained here.
|
||
|
||
**Patterns to follow:** the navigate path of the unified click rule (KTD2).
|
||
|
||
**Test scenarios:**
|
||
- *Hover:* hovering a highlights marker shows the article title popup (preserved).
|
||
- *Click → navigate:* clicking a highlights marker navigates to `entry.url` (changed from `scrollIntoView`).
|
||
- *Bounds:* highlights map still fits at the wider zoom (`maxZoom`/`singleZoom` 8).
|
||
- *No fullscreen / no GPX:* no fullscreen button appears and no GPX journey renders on the highlights map.
|
||
|
||
**Verification:** between-trips home state shows the highlights map; hovering a pin shows its title, clicking it opens the article; no console errors.
|
||
|
||
---
|
||
|
||
## Risks & Dependencies
|
||
|
||
- **Regression on the two live surfaces.** Trip and home active are the primary UI. Mitigation: U2 is a strict behavior-preserving change verified by visual diff; U1 is built to reproduce the trip handler exactly before any caller switches. Check existing Playwright map coverage (see `docs/working/plans/2026-06-22-align-maps-tests.md` / `2026-06-21-playwright-tests.md`) and run it after U2–U4.
|
||
- **Stale bundle.** `js/map.js` is generated; forgetting `make build-assets` ships old behavior. Mitigation: U1 explicitly includes the rebuild and a runtime check that `MapUtils.initEntryMap` is defined.
|
||
- **Duplicate element id.** The new `home-map-fullscreen` button must exist only in the active branch (highlights branch has no fullscreen). The two `#home-map` containers are already in mutually-exclusive Twig branches, so no real-DOM collision occurs.
|
||
- **Sequencing:** U2, U3, U4 all depend only on U1. U3 and U4 touch the same file and will typically land in one commit.
|
||
- **Known limitation — filter-hidden card click (accepted).** The unified rule keys on the card *existing* (`getElementById`), not on it being visible. When the feed filter bar (All/Journal/Stories) has hidden the target card (`display:none`), a marker click sets the hash and flashes an off-screen card, so the map appears unresponsive. This is a pre-existing rough edge being carried forward deliberately (not a regression introduced here) — accepted as-is for this pass rather than adding filter-reset or navigate-fallback handling.
|
||
|
||
---
|
||
|
||
## Verification Strategy
|
||
|
||
1. After U1: `make build-assets` succeeds; `js/map.js` updated; `window.MapUtils.initEntryMap` defined.
|
||
2. After U2: trip page (`/trips/<active_trip>`) — markers, hover, click-scroll-flash, fullscreen, GPX all unchanged.
|
||
3. After U3: home active state — identical to trip, including the new fullscreen button and flash.
|
||
4. After U4: home between-trips state — hover-title + click-to-open; wider zoom; no fullscreen/GPX.
|
||
5. Run existing Playwright map tests; confirm no new failures.
|
||
6. Confirm net line reduction across `trip.html.twig` + `home.html.twig` (the duplication is gone) and that `maplibre-utils.js` is the single source of init truth.
|
||
|
||
---
|
||
|
||
## Execution Outcome (2026-06-27)
|
||
|
||
All four units landed. `MapUtils.initEntryMap(opts)` added to `maplibre-utils.js` and bundled via `make build-assets`; `trip.html.twig`, both `home.html.twig` branches converted. Two notes from execution:
|
||
|
||
- **`markLatest` opt added** — the highlights branch rendered all dots equal (`createDotMarker(false)`), but the shared `isLatest = (i === length-1)` would have enlarged the last (shuffled) highlight. Added a `markLatest` flag (default `true`; highlights passes `false`) to preserve that.
|
||
- **Map instance exposed as `window.tripMap` / `window.homeMap`** — `initEntryMap` returns the map, and the templates assign it to these globals. This is the affordance the existing Playwright specs (M7, M8) already assumed; wiring it up turned two perma-failing tests green, giving real regression coverage on the converted surfaces.
|
||
|
||
**Test status:** `tests/ui/maps`, `tests/ui/home`, `tests/ui/trip`, `tests/ui/gpx` — 38 passed. Remaining failures are pre-existing and out of scope: **M6** asserts `window.map` on the deferred `map.html.twig` (untouched this pass); **H1** is a parallel-load timing flake (passes 4/4 in isolation). Both fail identically on the pre-refactor baseline.
|
||
|
||
## Sources & Research
|
||
|
||
- Origin: memory `project-map-init-refactor` (deferral), re-scoped after `project-homepage-redesign` / home-trip convergence.
|
||
- Milestone 2 refactor decision that `map_entries` stays Twig-side: memory `project-template-refactor-milestone2`, plan `docs/working/plans/2026-06-23-template-refactor.md`.
|
||
- Current behavior read directly from: `trip.html.twig:83-174`, `home.html.twig:87-139` (active) and `:245-289` (highlights), `partials/feed-map.html.twig`, `map.html.twig`, `js/maplibre-utils.js`, build config `package.json` `build` script.
|
||
- No external research — strong local patterns; behavior is fully specified by the existing code.
|