docs: add home/trip view convergence design spec

Converge home-active feed-col chrome (date range, filter bar, stats/
cycling panels) with the trip page via a shared partial + shared
initTripStats() glue. Map convergence deferred to a later spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk
This commit is contained in:
2026-06-27 19:37:11 +02:00
co-authored by Claude Opus 4.8
parent 119e8e5a35
commit 301de51add
@@ -0,0 +1,129 @@
# Home / Trip View Convergence Design
**Date:** 2026-06-27
**Status:** Approved for implementation
## Problem
The home page in active-trip mode (`home.html.twig`, `config.site.travelling` branch) and the trip page (`trip.html.twig`) are meant to present the same experience — the same content, behaving near-identically. Today their feeds already match (both render journal + story entries via the shared `entry-journal`/`entry-story` partials), but the **feed-col chrome diverges**:
| Feature | Trip page | Home-active | Converge? |
|---|---|---|---|
| Feed lists journal + stories | ✅ | ✅ | already matches |
| Date-range header | ✅ | ❌ | **yes** |
| Filter bar (All / Journal / Stories) | ✅ | ❌ | **yes** |
| Stats panel | ✅ | ❌ | **yes** |
| Cycling panel | ✅ (if GPX) | ❌ | **yes** |
| Sort toggle button | ✅ | ❌ | **no — intended difference** |
| Default feed order | oldest→newest (sort flag 4) | its own (sort flag 3) | **no — intended difference** |
The chrome markup is the divergence. The supporting **behavior is already global**: `js/main.js` (loaded for every page via `base.html.twig:10`) runs `initFilterBar()`, `initPanelToggles()`, and `initSortButton('trip-sort-toggle', …)`, each a silent no-op when its markup is absent. The entry partials already emit `data-type`, which the filter relies on. So rendering the same markup on home is enough for the filter bar, panel toggles, and (where present) the sort button to work with **zero new JS**.
The single exception is the **stats/cycling computation glue** (writing distance/elevation values into `#stat-distance`, `#cyc-*`). That code is currently *inline* in `trip.html.twig` and not global, so the stats/cycling panels cannot function on home until it is shared.
## Goals
- Home-active gains the date-range header, filter bar, and stats/cycling panels — matching the trip page.
- The shared feed-col chrome lives in **one** place (a partial), so future header/chrome changes apply to both pages.
- Home-active keeps its own default feed order and has **no** sort button (the two intended differences).
- Stats/cycling computation works on both pages from a single shared JS function.
- No change to the trip page's rendered output (structural refactor only on that side).
## Non-goals
- **Map convergence is out of scope.** The home-active map omitting story markers, lacking a fullscreen button, and its hash-only click behavior are all deferred to a later map-init spec (see `project-map-init-refactor` memory). Both inline map scripts and both map-col markup blocks stay exactly as they are.
- Extracting the `all_items` / `map_entries` build loops to a macro — Twig macros output HTML, not arrays (established constraint; see `2026-06-23-template-refactor-design.md`). Each page keeps its own data-build loops.
- Any visual restyling of the chrome — home reuses the trip's existing CSS classes unchanged.
- Adding a sort button to home, or changing home's default order.
## Architecture
### 1. New shared partial: `templates/partials/trip-feed-col.html.twig`
Holds the entire `.home-feed-col` content currently inline in `trip.html.twig:70-120`:
- header (`.home-trip-header`): title, date range (when `trip_page.header.date_start` set), counts
- filter bar (`.trip-filter-bar`): All / Journal / Stories buttons
- the sort button (`#trip-sort-toggle`) — **rendered only when `show_sort` is true**
- panel toggles (`.trip-panel-toggles`): Stats, and Cycling (when `has_gpx`)
- `stats_panel(...)` and (when `has_gpx`) `cycling_panel(...)` macro calls
- the feed loop over `all_items` with the `#feed-filter-empty` sentinel
**Interface** (called via `{% include 'partials/trip-feed-col.html.twig' with {…} only %}`):
| Param | Type | Trip passes | Home-active passes |
|---|---|---|---|
| `trip_page` | Page | `page` | `trip` |
| `all_items` | array | sorted by date, flag 4 | sorted by date, flag 3 |
| `journal_entries` | array | dailies children | dailies children |
| `journal_count` | int | count | count |
| `story_count` | int | count | count |
| `has_gpx` | bool | `gpx_urls\|length > 0` | `home_gpx_urls\|length > 0` |
| `show_sort` | bool | `true` | `false` |
Because the partial is called with `only`, it must `{% import 'macros/stats.html.twig' %}` and `{% import 'macros/cycling.html.twig' %}` itself.
Both pages already build `all_items`, the counts, and `has_gpx` for their existing map data, so these are passed in rather than rebuilt — no new duplication is introduced.
### 2. Shared stats glue: `initTripStats(config)` in `js/src/main.js`
Extract the inline stats/cycling computation from `trip.html.twig:213-249` into a config-driven function. Current inline logic: if GPX present, `MapUtils.parseGpxFiles(urls, …)` fills `#stat-distance` and all `#cyc-*` fields; otherwise sum `haversineKm` over `gps_points` and write the `~`-prefixed estimate to `#stat-distance`.
```js
function initTripStats(config) {
// config: { gpxUrls: [], gpsPoints: [[lat,lng],...], hasGpx: bool }
// No-op if #stat-distance is absent (page has no stats panel).
}
```
Called from the boot block alongside the other inits. Each page provides the config via a small inline `<script>` that defines the data (the `*_GPX_URLS` / `gps_points` arrays are page-specific Twig output), then calls `initTripStats(...)` — or the boot reads globals the page sets. Implementation detail for the plan; the contract is: the function is selector-guarded and runs on any page that rendered a stats panel.
`js/main.js` is the built artifact; the asset pipeline rebuilds it from `js/src/main.js` (see `2026-06-22-asset-pipeline-design.md`).
### 3. Home-active data additions
Home-active currently builds `map_entries` (journal-only) and `home_gpx_urls`. For the stats panel's no-GPX fallback it must also build `gps_points` (journal entries with lat/lng), mirroring `trip.html.twig:27-32`.
### 4. Template wiring
**`trip.html.twig`**: replace the inline `.home-feed-col` block (`:70-120`) with the partial include; remove the inline stats script (`:213-249`) in favor of `initTripStats(...)`. Map script, fullscreen wiring, and map data-build stay untouched.
**`home.html.twig`** (active branch): replace the bespoke feed-col (`:60-83`) with the same partial include (`show_sort: false`); add `gps_points` build; wire `initTripStats(...)`. Map script and map-col stay untouched.
## Data / behavior flow after change
```
base.html.twig ──loads──> js/main.js (global)
├─ initFilterBar() ← works on both via .trip-filter-btn + [data-type]
├─ initPanelToggles() ← works on both via .trip-panel-toggle
├─ initSortButton('trip-sort-toggle', …) ← trip only (home omits button → no-op)
└─ initTripStats(cfg) ← works on both via #stat-distance guard
trip.html.twig ─include─> partials/trip-feed-col.html.twig (show_sort: true)
home.html.twig ─include─> partials/trip-feed-col.html.twig (show_sort: false)
└─ stats_panel(), cycling_panel(), feed loop
```
## Testing
No JS test harness exists in this project; verification is manual browser smoke testing at `http://localhost:8081`, consistent with prior template work.
**Trip page (regression — must be unchanged):**
1. Load the trip page. Confirm header, date range, filter bar, sort button, stats/cycling panels, and feed render identically to before.
2. Filter bar All/Journal/Stories filters the feed; sort button flips order; Stats/Cycling panels toggle open/closed.
3. Stats panel distance and cycling figures populate (GPX present) or show the `~` estimate (no GPX).
**Home page, active-trip mode (new behavior):**
4. With `config.site.travelling: true`, load `/`. Confirm date range, counts, filter bar (no sort button), and Stats panel (+ Cycling if the trip has GPX) now appear.
5. Filter bar filters the feed; panel toggles work; stats figures populate.
6. Confirm the feed default order is home's own order (unchanged from today) and that no sort button is present.
**Home page, between-trips mode (regression):**
7. With `config.site.travelling: false`, load `/`. Confirm the highlights layout is unaffected (this branch does not use the partial).
## Files touched
- **New:** `user/themes/intotheeast/templates/partials/trip-feed-col.html.twig`
- **Edit:** `user/themes/intotheeast/templates/trip.html.twig` (feed-col → include; remove inline stats script)
- **Edit:** `user/themes/intotheeast/templates/home.html.twig` (active branch feed-col → include; add `gps_points`; wire stats)
- **Edit:** `user/themes/intotheeast/js/src/main.js` (+`initTripStats`); rebuild `js/main.js`