aab783384f
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
116 lines
6.3 KiB
Markdown
116 lines
6.3 KiB
Markdown
# 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 → `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 <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
|