Files
intotheeast-com/docs/working/specs/2026-06-23-template-refactor-design.md
T
m038 aab783384f docs: add template refactor design spec (Milestone 2)
Three Twig macros (stats panel, cycling panel, date range), five template
modifications, and two latent bug fixes in inactive templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-23 23:38:54 +02:00

6.3 KiB
Raw Blame History

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 <head>)
  • 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 <div id="trip-stats-block"> 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 <div id="trip-cycling-block"> 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 → 1215 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

{% 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 <script> #}
{% set map_entries = [...] %}  {# for TRIP_ENTRIES in <script> #}

{# HTML: macro calls replace 130 lines of computation + panel HTML #}
{{ m.stats_panel(journal_entries, page, journal_count, has_gpx) }}
{% if has_gpx %}{{ mc.cycling_panel() }}{% endif %}

{# JS block: unchanged, uses Twig vars already in scope #}
<script>
var TRIP_ENTRIES = {{ map_entries|json_encode|raw }};
var STATS_GPS    = {{ gps_points|json_encode|raw }};
var GPX_URLS     = {{ gpx_urls|json_encode|raw }};
...
</script>

Latent bug fixes

Bug 1 — CSS not reaching <head> 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 <head> before content. The CSS is added too late and never appears in <head>.

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 <script> blocks inside {% block content %}. But map.js is in the bottom group, output by {{ assets.js('bottom') }} at the end of <body> — after the inline scripts have already run. MapLibre is not defined yet when the inline script executes.

Fix: wrap map init in document.addEventListener('DOMContentLoaded', function() { ... }). This is the same pattern trip.html.twig already uses correctly; DOMContentLoaded fires after all synchronous scripts (including bottom-group scripts) have run.

File map

Action File Change
Create templates/macros/stats.html.twig New macro: stats computation + panel HTML
Create templates/macros/cycling.html.twig New macro: cycling panel HTML
Create templates/macros/date-range.html.twig New macro: smart date range string
Modify templates/trip.html.twig Import + call macros; remove stats loops + panel HTML; ~130 lines shorter
Modify templates/story.html.twig Replace 15-line date logic with macro call
Modify templates/stories.html.twig Add {% block map_assets %}; replace date logic with macro call
Modify templates/map.html.twig Add {% block map_assets %} at top level; add DOMContentLoaded wrapper
Modify templates/partials/feed-map.html.twig Remove asset registration; wrap map init in DOMContentLoaded
Modify templates/dailies.html.twig Add {% block map_assets %} override

Testing

  • trip.html.twig: stats panel renders correctly (days, countries, cities, temp range); cycling panel appears when GPX present; map initialises; distance stat fills via JS
  • story.html.twig: date range displays correctly for single-day, same-month, same-year, cross-year entries
  • stories.html.twig: same date range validation; map loads without console errors
  • map.html.twig: no console errors on page load (was: maplibregl is not defined)
  • All inactive templates: no regressions on the active trip page