# Template Refactor Design **Date:** 2026-06-23 **Status:** Approved for implementation ## Problem `trip.html.twig` (386 lines) mixes three concerns: 50 lines of stats computation, 80 lines of panel HTML, and 130 lines of map/JS logic. Changes to stats logic or panel structure require reading through all three to understand what's where. Four templates also duplicate the `map_entries` collection loop, and date range formatting is independently reimplemented in `story.html.twig` and `stories.html.twig`. Two inactive templates (`map.html.twig`, `feed-map.html.twig`) have bugs that would surface as soon as they're activated. ## Goals - Reduce `trip.html.twig` from 386 to ~260 lines by extracting stats computation and panel HTML to macros - Extract date range formatting to one macro used by both story templates - Fix two latent bugs in inactive templates (wrong DOMContentLoaded timing, CSS not making it into ``) - Zero visual change — this is structural only ## Non-goals - Moving stats computation to PHP (performance difference at 60-80 entries is ~5-20ms, only on uncached loads; not worth a plugin) - Extracting `map_entries` loops to a macro (Twig macros output HTML, not data; they can't return arrays) - Changing any JS logic — DOMContentLoaded wrappers are structural fixes only (no logic changes) - Fixing `dailies.html.twig`'s CDN PhotoSwipe — Milestone 1 leftover, inactive page, separate concern - Any visual layout changes ## Key constraint: Twig macros vs data Twig macros produce rendered HTML output, not return values. This means: - **Stats + cycling panels** → good macro candidates (compute internally, render HTML) - **Date range string** → good macro candidate (outputs text) - **`map_entries` array** → cannot be a macro; each template's loop stays inline ## Architecture ### New macros **`templates/macros/stats.html.twig`** Signature: `stats_panel(journal_entries, page, journal_count, has_gpx)` Computes internally: `days_on_road`, `country_display[]`, `city_display[]`, `temp_min`, `temp_max`. Outputs the full `
` HTML with Twig-computed values baked in and JS placeholder IDs (`id="stat-distance"`) for the distance stat filled by JS after page load. **`templates/macros/cycling.html.twig`** Signature: `cycling_panel()` No computation needed — all values are JS placeholders (`id="cyc-distance"` etc.). Outputs `
` HTML. **`templates/macros/date-range.html.twig`** Signature: `format_date_range(start_date, end_date)` Condensed smart formatting extracted from `story.html.twig`: same month → `12–15 Jun 2026`; same year → `12 Jun – 3 Jul 2026`; different years → `28 Dec 2025 – 3 Jan 2026`. If `end_date` is empty or equals `start_date`, outputs a single date. Used by both `story.html.twig` and `stories.html.twig` (unifies the two currently divergent implementations). ### Data flow in trip.html.twig ```twig {% import 'macros/stats.html.twig' as m %} {% import 'macros/cycling.html.twig' as mc %} {# Data building stays inline (feeds both HTML and JS) #} {% set journal_entries = ... %} {% set gpx_urls = [...] %} {% set gps_points = [...] %} {# for STATS_GPS in ``` ### Latent bug fixes **Bug 1 — CSS not reaching `` in feed-map consumers** `feed-map.html.twig` calls `{% do assets.addCss('map.css') %}` inside `{% block content %}`, but `base.html.twig` renders `{{ assets.css() }}` in `` before content. The CSS is added too late and never appears in ``. Fix: remove `assets.addCss/addJs` from `feed-map.html.twig`. Add `{% block map_assets %}` to `dailies.html.twig` and `stories.html.twig` — this block is declared at line 11 of `base.html.twig`, before `{{ assets.css() }}`, so it registers correctly. **Bug 2 — `maplibregl is not defined` in `map.html.twig` and `feed-map.html.twig`** These templates call `new maplibregl.Map()` in inline `