## Outcome - Audited committed v5 preview `50d02e3` using only the deterministic synthetic fixture. - Reviewed `dashboard-v5.css`, `dashboard-v5.js`, `render.py`, provider/contracts/registry, fixtures, and browser/unit tests. - Generated and served a temporary synthetic dashboard through the hardened local server; fixture digest remained unchanged. - **Tests:** `16 passed` focused Python tests; `9 passed` Playwright tests after temporary `npm ci`. - **No files created or modified by me.** Temporary DB/server/dependencies/results were removed. ## Key findings 1. **Desktop shell is not actually wired up.** - CSS defines `.v5-shell`/`.v5-main` at `dashboard-v5.css:60–65`, but committed `render.py` emits neither. - At 1280px, content occupied the entire width and navigation rendered as a 1229×252px block after the page content. 2. **Navigation has a CSS cascade defect.** - `.bottom-nav { display:grid }` at CSS line 47 is overridden by later `[data-dashboard-nav] { display:flex; flex-direction:column }` at line 65. - This makes the supposed bottom navigation vertical on desktop and mobile. - At `<=900px` it becomes fixed while remaining ~five rows tall; `.app-view` reserves only 104px, so it can cover roughly 150px of content. - Existing browser tests verify overflow/target size, but **not fixed-navigation occlusion**. 3. **Today lacks Sprint-5A hierarchy and fallback behavior.** - `renderToday()` only shows `symptom.total`; without a complete symptom day it says `unbekannt`. - No freshness, short trend, or notices are rendered. - `contracts.py:223–224` explicitly requires `freshness` and `notices` to remain empty. - Equal-height three-column cards create large dead areas, while the capture action is visually detached below them. - Long medication text is not protected by `.v5-card`’s `overflow-wrap`; `.today-grid > article` needs equivalent wrapping. 4. **Cockpit is still a prototype.** - Six equal cards show value/date/baseline only: no sparkline, age, coverage, or quality. - `dashboard-v5.js:465` hard-codes `symptom.total`; there is no documented fallback metric. - Event track is correctly separate, but is a full-width low-density strip rather than a useful companion rail. 5. **Synthetic future data is selected as ordinary “latest.”** - Fixture `resting_heart_rate` is dated `2026-06-17` while bundle `today` is `2026-06-15`. - The rendered Cockpit displayed it as the latest Ruhepuls without an anomaly label. - Cause: `periodData()` has no upper date bound and `latestPoint()` simply chooses the last non-null row. - This directly conflicts with the acceptance matrix’s “future/check, not current” requirement. ## Concrete Sprint-5A redesign ### `dashboard_v5/render.py` Preserve existing IDs, tab roles, CSP-compatible external assets, and single `
`: ```text [data-dashboard-v5] .v5-shell aside.v5-rail product identity nav.bottom-nav[data-dashboard-nav][role=tablist] .v5-main header.v5-header view context .v5-periods #privacy-toggle / #more-toggle main#main ``` - **Today:** introduce `.today-layout`: - `.today-snapshot-card` — 8 columns - `.today-actions-card` / `#today-tasks` — 4 columns - medication — 8 columns - `#today-freshness` — 4 columns - `#today-notices` — full width, maximum two notices - Keep `#open-checkin`; present it prominently in Today and as the central mobile capture affordance. - **Cockpit:** introduce `.cockpit-layout`: - `#metric-cards` full width, 3×2 desktop - `.cockpit-chart-panel` 8 columns - `#event-track` 4-column companion rail - collapse chart/event rail to stacked layout below ~1180px. ### `dashboard-v5.css` - Fix selector conflict explicitly, e.g. `.bottom-nav[data-dashboard-nav]`. - Suggested breakpoints: - `>=1200px`: 216–232px sticky side rail; content capped around 1180px. - `768–1199px`: compact sticky horizontal top navigation; no fixed bottom bar. - `<=767px`: true five-column fixed bottom navigation plus safe-area padding and central `+ Erfassen`. - `<=320px` effective width/200% zoom: make navigation static or two-row to prevent content occlusion. - Apply `overflow-wrap:anywhere` to all Today/Cockpit cards, not only `.v5-card`. - Keep current focus, reduced-motion, print, and privacy rules; verify new sparkline canvases are masked in privacy mode. ### `dashboard-v5.js` - Replace hard-coded Cockpit selection with deterministic fallback: 1. `symptom.total` if a complete non-future point exists; 2. otherwise first registry-priority metric with a valid point; 3. otherwise explicit chart empty state. - Reject points after `bundle.today` from ordinary latest/baseline/chart selection; surface them as `future/check`. - Expand `renderMetricCards()` with: - value/unit, - date and deterministic age from `bundle.today`, - baseline comparison without directional medical judgment, - quality/coverage text, - sparkline preserving null gaps. - Reuse the existing `charts` lifecycle map; do not create duplicate Chart.js instances on period/view changes. - Update `aria-orientation` when navigation changes between side-rail and horizontal layouts. ### Contract/provider/fixtures - `contracts.py` uses exact object shapes, so adding coverage/freshness is a **contract change**, not only a renderer change. Prefer a versioned v3 bundle rather than silently extending v2. - Add fixed-domain freshness/anomaly enums and bounded numeric coverage; do not expose source names, paths, raw records, or free text. - Extend the synthetic fixture/tests with: - low coverage, - stale and future registry metrics, - no complete symptom day but another valid fallback metric, - long Today medication text, - completely empty Cockpit. - Add Playwright assertions for nav position/occlusion, fallback chart source, future anomaly labeling, sparkline null gaps, and 1920×1080. ## Exact implementation risks - Moving navigation without fixing the cascade leaves it vertical even in the new shell. - Changing tab visibility/roles would break keyboard roving-tab behavior and existing role-based tests. - Adding point fields without bumping/updating the exact bundle contract makes `safe_json()` fail. - Sparklines can increase Chart.js lifecycle leaks and can bypass privacy/print masking if canvases are not included. - Browser tests currently pass despite the navigation defect because they do not measure viewport coverage. - During the audit, concurrent uncommitted edits appeared in `data_provider.py`, `render.py`, tests, and the Sprint-5 doc. I did not alter them; final status was left intact.