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
This commit is contained in:
2026-06-23 23:38:54 +02:00
parent 5cf7e15219
commit aab783384f
@@ -0,0 +1,115 @@
# 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
```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