From abab85ca5c07d5cff5e4c25f50e53d37ef23e68b Mon Sep 17 00:00:00 2001 From: Mischa Date: Sat, 27 Jun 2026 23:43:03 +0200 Subject: [PATCH] docs: add map-init consolidation plan (reviewed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan for extracting duplicated MapLibre init into a shared MapUtils.initEntryMap(opts). Incorporates ce-doc-review findings: card-absent fallback invariant scoped (no-opβ†’navigate), exact isLatest predicate, and the home-map attribution restyle documented as an intentional change. Filter-hidden-card click left as a noted known limitation. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk --- .../2026-06-27-map-init-consolidation.md | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 docs/working/plans/2026-06-27-map-init-consolidation.md diff --git a/docs/working/plans/2026-06-27-map-init-consolidation.md b/docs/working/plans/2026-06-27-map-init-consolidation.md new file mode 100644 index 0000000..a00b2c6 --- /dev/null +++ b/docs/working/plans/2026-06-27-map-init-consolidation.md @@ -0,0 +1,260 @@ +# Map Init Consolidation β€” shared `MapUtils.initEntryMap()` + +**Status:** πŸ“‹ Not started + +> 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-`, 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 `