From 301de51addc2d606cd86695cdb0961dfde557b52 Mon Sep 17 00:00:00 2001 From: Mischa Date: Sat, 27 Jun 2026 19:37:11 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01BftDn9vu9SonFAY4vxu4uk --- ...06-27-home-trip-view-convergence-design.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/working/specs/2026-06-27-home-trip-view-convergence-design.md diff --git a/docs/working/specs/2026-06-27-home-trip-view-convergence-design.md b/docs/working/specs/2026-06-27-home-trip-view-convergence-design.md new file mode 100644 index 0000000..1186d26 --- /dev/null +++ b/docs/working/specs/2026-06-27-home-trip-view-convergence-design.md @@ -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 `