test: verify first visible vertical slice

Drives the full world/travel flow in Chromium via Playwright against the
live API and seeded Postgres, capturing the three required desktop
viewports and comparing them against the accepted design reference.

Fixes two evidenced defects found during verification:
- world-page scene sizing overflowed the viewport at all three required
  desktop sizes, pushing the footer (and, at 1440x900/1366x768, the travel
  button) below the fold; scene height now accounts for surrounding chrome
  so nothing is clipped.
- the generic HTTP-failure fallback message was hardcoded in English while
  the rest of the UI is German; now uses a matching German string.

Adds Playwright as a web devDependency for this and future browser
verification passes (no product-code dependency).
This commit is contained in:
Bastian Wagner
2026-08-19 10:13:40 +02:00
parent 2f53e5ba80
commit ec6c34e341
8 changed files with 231 additions and 2 deletions

View File

@@ -0,0 +1,180 @@
# First Visible Vertical Slice — Fidelity Ledger
Reference: `docs/references/world-travel-screen.png` (concept mock, Graufurt hub with
full RPG chrome: currencies, mail/friends/settings icons, "Shop" nav item, region
detail panel with encounters/rewards, "Gebiet erkunden" action).
Renders (this task, post-fix): `docs/verification/first-visible-slice-1920x1080.png`,
`docs/verification/first-visible-slice-1440x900.png`,
`docs/verification/first-visible-slice-1366x768.png`. All three captured with
Playwright/Chromium at `http://localhost:4200/world`, live API + seeded Postgres,
with Verbrannte Straße selected as the travel target (matches the reference's
state: travel panel populated).
Scope reminder: the reference is a full-game concept mock. This slice implements
only the demo character, two locations, and travel — currencies, mail/social,
settings, Shop, encounters/rewards, and "Gebiet erkunden" exploration are
out of scope by design (see Task 110 rulings) and are recorded below as
deliberate deviations, not defects.
## Comparison points
### 1. Shell proportions and persistent regions
- Reference: top bar (portrait, name/level, HP bar, currencies, icon row) +
left icon nav (Karte/Jagd/Quests/Inventar/Charakter/Shop) + center map +
right context panel + footer (chat/system line, world/online/time).
- Render: top bar (portrait, name/level, HP bar, brand wordmark) + left nav
(Karte enabled; Jagd/Quests/Inventar/Charakter present but disabled,
`aria-label="… ist noch nicht verfügbar"`) + center map + right
"Gebietsinfo" panel + footer (status dot + text, ornament, region name).
- Result: structural grid matches (top bar / nav+main+context / footer).
Currency row, mail/friends/settings/logout icons, and "Shop" nav item are
absent — deliberate, no such systems exist yet. No fix needed.
### 2. Artwork dominance and crop
- Reference: large painterly Graufurt gate artwork fills the map region,
`background-size: cover`-style dominant visual, quiet areas reserved for
path/nodes/panel.
- Render: `.world-page__scene` uses the project's own generated
Südtor/Aschenstraße artwork (`/images/backgrounds/Suedtor.png` /
`Aschestrasse.png`), `background-size: cover`, same compositional role.
Visible and readable at all three captured sizes.
- Result: matches intent. Different concrete artwork (project-specific,
generated per the style guide) is expected, not a defect.
### 3. Typography hierarchy and control typography
- Reference: large serif location title overlaid top-left of the map, green
safety subtitle, short descriptive paragraph in the same overlay; serif
headers in the side panel.
- Render: serif (`Georgia`) location title top-left of the map
(`.world-page__location-title`); description/safety/level facts live in
the right "Gebietsinfo" panel instead of a second overlay block on the
map. Control typography (buttons, dl labels) uses the app's sans body
font with small-caps-style eyebrows ("REISEN", "GEBIETSINFO").
- Result: title hierarchy matches (serif, large, top-left). The map-overlay
vs. side-panel placement of description text is an established
information-architecture choice from earlier tasks (Task 8/9), not a
regression introduced here — left as a recorded deviation, not fixed,
since re-architecting panel content is outside this task's evidenced-defect
scope.
### 4. Dark palette, bronze borders, and blue/current state
- Reference: anthracite/black panels, bronze/gold hairline borders, blue ring
on the current-location node, gold ring + gold path on selected/target.
- Render: `--ar-bg`/`--ar-panel` dark palette, `--ar-border`/
`--ar-border-highlight` bronze/gold borders on top bar, panels, and travel
box; `.location-node--current` renders a blue marker ring (confirmed in all
three captures, "Aktueller Ort" label green), selected/target node renders
gold ring + gold dashed path (confirmed, "Ausgewähltes Ziel" label gold).
- Result: matches the style guide's colour semantics (blue = current/active
navigation, gold = selection/important action). No fix needed.
### 5. Node/path legibility
- Reference: node names + short state label beneath each node, dashed gold
connecting path.
- Render: same pattern — `location-node__name` + `location-node__state`
("Aktueller Ort" / "Ausgewähltes Ziel" / "Erreichbar"), dashed gold SVG
path between the two nodes. Legible with sufficient text-shadow contrast
against the background artwork at all three captured sizes.
- Result: matches. No fix needed.
### 6. Travel panel placement and action prominence
- Reference: travel panel overlays the bottom edge of the map itself, as a
floating box, with a full-width primary blue button.
- Render: `.world-page__travel-panel` is a separate block directly beneath
the map (not overlaid on top of it), centered, `min(29rem, 100%)` wide,
with a full-width primary button (`Reise beginnen` / `Zur Verbrannte
Straße reisen?`) using the gold-bordered, blue-tinted button style.
- Result: placement differs (below vs. overlaid on the map) — an established
layout choice from earlier tasks, not introduced or regressed by this
task. Action prominence (full-width primary button, clear label, disabled
state while travelling) matches reference intent. Not treated as a
material mismatch requiring a redesign in this verification-only task.
### 7. Context-panel information density
- Reference: region title, recommended level, "Mögliche Begegnungen" icon
row, description, "Mögliche Belohnungen" icon row, "Gebiet erkunden"
button.
- Render: "GEBIETSINFO" eyebrow, location name, selected-target note,
artwork thumbnail, description paragraph, facts list (recommended level /
safety / hunting availability).
- Result: reduced density is intentional — encounters, rewards, and area
exploration are later-gameplay systems explicitly out of scope for this
slice (per the plan's deliberate limitations). Not a defect.
### 8. Responsive behaviour at all three sizes
- Evidence (pre-fix): measured via a Playwright DOM probe —
`document.documentElement.scrollHeight` vs. `window.innerHeight`:
- 1920×1080: docHeight 1098 vs. viewport 1080 (18px overflow; footer's
bottom 18px clipped below the fold).
- 1440×900: docHeight 997 vs. viewport 900 (97px overflow; travel button
and footer entirely below the fold).
- 1366×768: docHeight 909 vs. viewport 768 (141px overflow; travel button
and footer entirely below the fold, ~half the travel panel cut off).
- Root cause: `.world-page__scene { min-block-size: clamp(30rem, 67vh,
44rem); }` sized the map purely off viewport height without accounting
for the fixed chrome around it (top bar + paddings + travel panel +
footer ≈ 395px), so the three required desktop sizes all overflowed the
viewport and pushed the footer (and, at the two smaller sizes, the
travel button) out of view without scrolling.
- **Fixed** in `apps/web/src/app/features/world/world-page.component.scss`:
changed to `min-block-size: clamp(20rem, calc(100dvh - 25rem), 44rem)`,
which sizes the map to the remaining space after the known chrome height
(with a small safety margin), while keeping the existing 2044rem floor
and ceiling.
- Evidence (post-fix), same probe: 1920×1080 → 1080 vs. 1080 (0 overflow);
1440×900 → 900 vs. 900 (0 overflow); 1366×768 → 768 vs. 768 (0
overflow). Re-captured screenshots (see paths above) show the top bar,
nav, map with both nodes, full travel panel including the primary
button, context panel, and footer all fully visible with no clipping at
all three sizes.
- Result: **material mismatch found and fixed.**
## Above-the-fold copy diff
Every visible string across all three renders was checked against the
brief's allowed-copy categories (API content, requested navigation labels,
travel labels, restrained system/footer text):
| Text | Source | Category |
|---|---|---|
| "Aric Duskwalker", "Stufe 1", "100 / 100" | API (`/api/characters/me`) | API content |
| "Südtor von Graufurt", "Verbrannte Straße", description paragraph, "11", "Sicherer Ort", "Keine Jagd" | API (`/api/world/current-location`) | API content |
| "Verbrannte Straße", "00:00:10", "Niedrig" | API (`/api/world/current-location` connections) | API content |
| "Karte", "Jagd", "Quests", "Inventar", "Charakter" | side nav | requested navigation labels |
| "REISEN", "Zur … reisen?", "Ziel", "Reisezeit", "Gefahr", "Reise beginnen", "Ankunft in", "REISE LÄUFT", "Reise läuft", "Wähle einen erreichbaren Ort auf der Karte." | travel panel | travel labels |
| "GEBIETSINFO", "Ausgewähltes Ziel: …", "Empfohlene Stufe", "Sicherheit", "Jagd", "Aktueller Ort", "Ausgewähltes Ziel", "Erreichbar" | context panel / node labels | restrained system text |
| "Verbindung bereit", "Aschenfelder", "Ashen Realms" | footer / top bar | restrained system/footer text |
| "Weltzustand wird geladen…", "Weltkarte wird vorbereitet.", "Erneut versuchen" | loading/empty states | restrained system text |
| "Unable to load world state." | error fallback (`world.store.ts`) | **violation — English string in an otherwise fully German UI** |
**Fixed:** `apps/web/src/app/features/world/world.store.ts`,
`toErrorMessage()` fallback changed from `'Unable to load world state.'` to
`'Weltzustand konnte nicht geladen werden.'`, matching the German tone/case
used everywhere else in the app (e.g. `"Weltzustand wird geladen…"`).
Verified live: killed the API process, reloaded `/world`, confirmed the
in-shell error box now reads "Weltzustand konnte nicht geladen werden." with
no `window.alert()` dialog fired (Playwright `page.on('dialog', …)` listener
recorded zero dialogs across every step of the verification pass).
No invented, promotional, or placeholder copy was found anywhere in the
above-the-fold content.
## Summary
One functional/visual defect found and fixed (responsive overflow/clipping
at all three required desktop sizes — item 8). One copy defect found and
fixed (English fallback error string — copy diff table above). All other
comparison points either matched the reference's visual language directly,
or reflect deliberate, already-ruled-on scope reductions from earlier tasks
(no currencies/social/settings/Shop, reduced context-panel density, no
map-overlay description block, travel panel placed below rather than over
the map) that this verification-only task did not redesign.