Files
intotheeast-com/docs/working/plans/2026-06-20-inline-journal-feed.md
T

858 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Inline Journal Feed Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Status:** ✅ Complete (2026-06-20)
**Goal:** Replace click-through journal entry cards with fully inline posts (photo strip + full text) across the trip page, dailies page, and home page.
**Architecture:** Each journal entry becomes an `<article class="journal-post">` block that renders all its images in a CSS scroll-snap strip with dot indicators, followed by the full body text. The `id`, `data-type`, `data-lat`, `data-lng` attributes stay on the root so map targeting, filter JS, and flash animation continue to work. Story cards in all three feeds are unchanged.
**Tech Stack:** Grav 2.0 Twig templates, CSS scroll-snap (no library), vanilla JS IntersectionObserver-free dot sync via scroll event, Playwright tests
## Global Constraints
- All CSS values must use design tokens (`var(--...)`) — no hard-coded colours, sizes, or radii
- `id="entry-{{ entry.slug }}"` must remain on the journal post root (map scroll targeting)
- `data-type="journal"` must remain on the journal post root (filter bar JS)
- `data-lat` and `data-lng` must remain on the journal post root (map marker rendering)
- Story cards (`<a class="entry-card entry-card--story">`) are not touched by any task
- Two git repos: user content at `/home/mischa/Projects/travel-blog-intotheeast/user/` (separate git repo); outer repo at `/home/mischa/Nextcloud/Projects/travel-blog-intotheeast/`. Templates and CSS commit to the user subrepo; tests commit to the outer repo. Always update the outer repo's `user` submodule pointer in the same commit as the test changes.
- Dev server: http://localhost:8081
---
## File Map
| File | Change |
|---|---|
| `user/themes/intotheeast/css/style.css` | Add `.journal-post` component; remove journal-card-only rules; update `.is-highlighted` selector |
| `user/themes/intotheeast/templates/partials/base.html.twig` | Add photo-strip dot-sync JS before `</body>` |
| `user/themes/intotheeast/templates/dailies.html.twig` | Replace journal card block with `.journal-post`; add `weather_icons` |
| `user/themes/intotheeast/templates/trip.html.twig` | Replace journal card block with `.journal-post`; add `weather_icons` |
| `user/themes/intotheeast/templates/home.html.twig` | Replace journal card block with `.journal-post`; add `weather_icons` |
| `tests/ui/dailies.spec.js` | Update T1 selector; update T2 selectors |
| `tests/ui/maps.spec.js` | Update M7 selector |
| `tests/ui/home.spec.js` | New file — H1 test |
---
### Task 1: CSS foundation + dot-sync JS
Add all new `.journal-post` CSS and the photo-strip dot-sync JS. Remove CSS classes that are only used by the old journal entry card (not by story cards). This task has no template changes — existing tests must still pass at the end.
**Files:**
- Modify: `user/themes/intotheeast/css/style.css`
- Modify: `user/themes/intotheeast/templates/partials/base.html.twig`
**Interfaces:**
- Produces: `.journal-post`, `.journal-post-header`, `.journal-post-title`, `.journal-post-meta`, `.journal-post-permalink`, `.journal-post-location`, `.journal-post-weather`, `.journal-photo-strip`, `.journal-photo-slide`, `.journal-photo-dots`, `.journal-photo-dot.is-active`, `.journal-post-body`, `.journal-post.is-highlighted` — all usable by Tasks 24
- [x] **Step 1: Add `.journal-post` CSS block to `style.css`**
In `user/themes/intotheeast/css/style.css`, find the line:
```css
/* ── Single entry ────────────────────────────────────────────────────────────── */
```
Insert the following block **before** that comment:
```css
/* ── Journal post (inline feed) ─────────────────────────────────────────────── */
.journal-post {
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--space-12);
margin-bottom: var(--space-12);
}
.journal-post-header {
margin-bottom: var(--space-4);
}
.journal-post-title {
font-family: var(--font-display);
font-size: var(--text-xl);
font-weight: 400;
line-height: var(--leading-snug);
color: var(--color-ink);
margin-bottom: var(--space-2);
}
.journal-post-meta {
font-size: var(--text-xs);
color: var(--color-ink-muted);
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-2);
}
.journal-post-permalink {
color: var(--color-ink-muted);
text-decoration: none;
font-weight: 700;
letter-spacing: 0.07em;
}
.journal-post-permalink:hover { color: var(--color-accent); }
.journal-post-location,
.journal-post-weather {
color: var(--color-ink-muted);
}
.journal-photo-strip {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
scrollbar-width: none;
border-radius: var(--radius-md);
margin-bottom: var(--space-3);
}
.journal-photo-strip::-webkit-scrollbar { display: none; }
.journal-photo-slide {
flex: 0 0 100%;
scroll-snap-align: start;
aspect-ratio: 3 / 2;
overflow: hidden;
}
.journal-photo-slide img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.journal-photo-dots {
display: flex;
justify-content: center;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.journal-photo-dot {
width: 6px;
height: 6px;
border-radius: 9999px;
background: var(--color-border);
transition: background 0.2s;
}
.journal-photo-dot.is-active {
background: var(--color-ink-muted);
}
.journal-post-body {
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-ink-2);
}
.journal-post-body p { margin-bottom: var(--space-4); }
.journal-post-body p:last-child { margin-bottom: 0; }
.journal-post.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
```
- [x] **Step 2: Remove journal-card-only CSS rules from `style.css`**
These rules are only used by the old journal entry card. Story cards do not use them. Remove each block exactly as shown.
**Remove `.entry-card-photo-overlay` and its children:**
```css
.entry-card-photo-overlay {
position: absolute;
inset: auto 0 0 0;
padding: var(--space-5) var(--space-4) var(--space-3);
background: linear-gradient(to top, rgba(0,0,0,0.58) 0%, transparent 100%);
display: flex;
align-items: flex-end;
gap: var(--space-3);
flex-wrap: wrap;
}
.entry-date-overlay {
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: 0.08em;
color: rgba(255,255,255,0.92);
}
.entry-location-overlay {
font-size: var(--text-xs);
color: rgba(255,255,255,0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
```
Replace with nothing (delete the block entirely).
**Remove the text-only meta block and its comment:**
```css
/* Card: text-only variant */
.entry-card-textmeta {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
flex-wrap: wrap;
}
.entry-date-plain {
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: 0.07em;
color: var(--color-ink-muted);
}
.entry-location-plain {
font-size: var(--text-xs);
color: var(--color-ink-muted);
}
```
Replace with nothing.
**Remove `.entry-excerpt` and `.entry-read-more`:**
```css
.entry-excerpt {
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-ink-2);
margin-bottom: var(--space-3);
}
.entry-read-more {
font-size: var(--text-sm);
font-weight: 500;
color: var(--color-accent);
}
```
Replace with nothing.
**Replace `.entry-card.is-highlighted` with `.journal-post.is-highlighted`:**
Find:
```css
.entry-card.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
```
Replace with:
```css
.journal-post.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
```
- [x] **Step 3: Add dot-sync JS to `base.html.twig`**
In `user/themes/intotheeast/templates/partials/base.html.twig`, find:
```twig
{{ assets.js('bottom')|raw }}
</body>
```
Replace with:
```twig
{{ assets.js('bottom')|raw }}
<script>
(function () {
document.querySelectorAll('.journal-photo-strip').forEach(function (strip) {
var dots = strip.nextElementSibling;
if (!dots || !dots.classList.contains('journal-photo-dots')) return;
var dotEls = Array.from(dots.querySelectorAll('.journal-photo-dot'));
strip.addEventListener('scroll', function () {
var idx = Math.round(strip.scrollLeft / strip.offsetWidth);
dotEls.forEach(function (d, i) { d.classList.toggle('is-active', i === idx); });
}, { passive: true });
});
})();
</script>
</body>
```
- [x] **Step 4: Run existing tests to confirm nothing broke**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
```
Expected: all existing tests pass (M7 still passes because `trip.html.twig` has not changed yet — the JS still adds `is-highlighted` to `.entry-card` elements, and the old M7 selector `.entry-card.is-highlighted` finds the element).
- [x] **Step 5: Commit user subrepo**
```bash
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/css/style.css themes/intotheeast/templates/partials/base.html.twig
git commit -m "feat: add journal-post CSS component and dot-sync JS; remove stale journal-card-only rules"
```
---
### Task 2: dailies.html.twig + T1/T2 test updates
Replace the journal entry card in `dailies.html.twig` with the new `.journal-post` inline block. Update T1 and T2 tests to match the new structure.
**Files:**
- Modify: `user/themes/intotheeast/templates/dailies.html.twig`
- Modify: `tests/ui/dailies.spec.js`
**Interfaces:**
- Consumes: `.journal-post` CSS from Task 1
- Produces: `/trips/japan-korea-2026/dailies` renders `.journal-post` blocks; T1 and T2 pass with new selectors
- [x] **Step 1: Update T1 and T2 tests to their new selectors**
In `tests/ui/dailies.spec.js`, make the following changes:
**T1** — change `.entry-card` to `.journal-post`:
```js
// OLD
await expect(page.locator('.entry-card').first()).toBeVisible();
// NEW
await expect(page.locator('.journal-post').first()).toBeVisible();
```
**T2** — replace the entire card locator + index block with id-based selectors:
Find:
```js
// Both fixture entries must be visible on the page
const newerCard = page.locator(`.entry-card[href*="${NEWER_SLUG}"]`);
const olderCard = page.locator(`.entry-card[href*="${OLDER_SLUG}"]`);
await expect(newerCard).toBeVisible();
await expect(olderCard).toBeVisible();
// The newer entry should appear higher in the DOM (lower index)
const newerIdx = await newerCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
});
const olderIdx = await olderCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
});
```
Replace with:
```js
// Both fixture entries must be visible on the page
const newerCard = page.locator(`#entry-${NEWER_SLUG}`);
const olderCard = page.locator(`#entry-${OLDER_SLUG}`);
await expect(newerCard).toBeVisible();
await expect(olderCard).toBeVisible();
// The newer entry should appear higher in the DOM (lower index)
const newerIdx = await newerCard.evaluate(el => {
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
});
const olderIdx = await olderCard.evaluate(el => {
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
});
```
- [x] **Step 2: Run T1 and T2 to verify they fail**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T1:|T2:"
```
Expected: FAIL — `.journal-post` selector finds no elements (the page still renders `.entry-card`).
- [x] **Step 3: Add `weather_icons` map and replace journal card in `dailies.html.twig`**
In `user/themes/intotheeast/templates/dailies.html.twig`, find the line:
```twig
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
```
This `{% if item.type == 'journal' %}` block ends at `</a>` before `{% else %}`. Replace the entire journal card block (from `{% if item.type == 'journal' %}` through the closing `</a>` of the journal branch, leaving the `{% else %}` story branch intact) with:
```twig
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
```
The exact text to find and replace is the old journal branch. The old branch starts with:
```twig
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
```
- [x] **Step 4: Run T1 and T2 to verify they pass**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T1:|T2:"
```
Expected: PASS.
- [x] **Step 5: Run the full suite to check no regressions**
```bash
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
```
Expected: all pass.
- [x] **Step 6: Commit**
```bash
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/dailies.html.twig
git commit -m "feat: replace journal entry card with inline journal-post in dailies feed"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/dailies.spec.js user
git commit -m "test: update T1/T2 selectors for inline journal-post structure"
```
---
### Task 3: trip.html.twig + M7 test update
Replace the journal entry card in `trip.html.twig` with the `.journal-post` block. Update M7 which currently tests `.entry-card.is-highlighted` on the trip page.
**Files:**
- Modify: `user/themes/intotheeast/templates/trip.html.twig`
- Modify: `tests/ui/maps.spec.js`
**Interfaces:**
- Consumes: `.journal-post` CSS and `.journal-post.is-highlighted` from Task 1; journal-post HTML pattern from Task 2
- Produces: `/trips/japan-korea-2026` renders `.journal-post` blocks; M7 passes with `.journal-post.is-highlighted`
- [x] **Step 1: Update M7 to the new selector**
In `tests/ui/maps.spec.js`, find:
```js
// Within 500ms of click + delay, one entry-card should have is-highlighted
await expect(page.locator('.entry-card.is-highlighted')).toBeVisible({ timeout: 1500 });
```
Replace with:
```js
// Within 500ms of click + delay, one journal-post should have is-highlighted
await expect(page.locator('.journal-post.is-highlighted')).toBeVisible({ timeout: 1500 });
```
- [x] **Step 2: Run M7 to verify it fails**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
```
Expected: FAIL — `.journal-post.is-highlighted` not found (trip.html.twig still renders `<a class="entry-card">`).
- [x] **Step 3: Replace journal card in `trip.html.twig`**
In `user/themes/intotheeast/templates/trip.html.twig`, find and replace the journal branch of the `{% if item.type == 'journal' %}` block. The old branch to replace is:
```twig
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
```
Replace with:
```twig
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
```
- [x] **Step 4: Run M7 to verify it passes**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
```
Expected: PASS.
- [x] **Step 5: Run full suite**
```bash
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
```
Expected: all pass.
- [x] **Step 6: Commit**
```bash
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/trip.html.twig
git commit -m "feat: replace journal entry card with inline journal-post in trip feed"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/maps.spec.js user
git commit -m "test: update M7 selector for journal-post.is-highlighted"
```
---
### Task 4: home.html.twig + H1 test
Replace the journal entry card in `home.html.twig` and add a minimal home page test.
**Files:**
- Modify: `user/themes/intotheeast/templates/home.html.twig`
- Create: `tests/ui/home.spec.js`
**Interfaces:**
- Consumes: `.journal-post` CSS from Task 1; journal-post HTML pattern from Task 2
- [x] **Step 1: Write the failing H1 test**
Create `tests/ui/home.spec.js`:
```js
// @ts-check
// Tests: H1 — home page journal feed
const { test, expect } = require('@playwright/test');
// ── H1: Home page renders inline journal posts ─────────────────────────────────
test('H1: home page shows at least one inline journal-post block', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.journal-post').first()).toBeVisible();
await expect(page.locator('.site-header')).toBeVisible();
});
```
- [x] **Step 2: Run H1 to verify it fails**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/home.spec.js
```
Expected: FAIL — `.journal-post` not found (home page still renders `<a class="entry-card">`).
- [x] **Step 3: Replace journal card in `home.html.twig`**
In `user/themes/intotheeast/templates/home.html.twig`, find the journal branch:
```twig
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
```
Replace with:
```twig
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
```
Note: `home.html.twig` journal posts do **not** include `data-type` (the home page has no filter bar) — this matches the existing `<a class="entry-card">` on home which also had no `data-type`.
- [x] **Step 4: Run H1 to verify it passes**
```bash
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/home.spec.js
```
Expected: PASS.
- [x] **Step 5: Run the full suite**
```bash
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js tests/ui/home.spec.js
```
Expected: all pass.
- [x] **Step 6: Commit**
```bash
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/home.html.twig
git commit -m "feat: replace journal entry card with inline journal-post on home page"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/home.spec.js user
git commit -m "test: add H1 home page journal-post test"
```