fix(insights): keep mood correlation captions inside their tiles on phone widths

The correlation-card header's right slot kept its icon-era shrink-0
after v1.22 replaced the info glyph with an inline caption — the
unshrinkable badge+caption row escaped the card's right edge by
14–39 px at 390/360 across all five cards and squeezed the title to a
2-line 36–42 % sliver. The caption now stacks under the title below sm
(only the compact strength badge stays pinned right); on >=sm the
inline layout is unchanged.

Same surface: the discovered-relations stat caption ('{n} gepaarte
Tage · r · q') stacks below its finding sentence on phone widths
instead of wrapping to a 4-line sliver at ~25 % of the row, and the
subsection caveat caption stacks under the subheading instead of
wrapping 9 lines beside it.

The phone-width density guard now sweeps /insights/mood too, with
mocked correlation + discovery payloads pinning the populated worst
case (five cards, three mood pairs) so the guard cannot pass vacuously.

H6
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 12:06:36 +02:00
1 parent f0b16c1a39
commit 7db97eabca
4 files changed
+166 -7

No files matched your search

+119
View File
@@ -0,0 +1,119 @@
import type { Page } from "@playwright/test";
/**
* Route mocks for the /insights/mood density guard.
*
* The correlation cards and the discovered-relations list only render with
* data, and the seeded e2e account has none — an unmocked run would pass the
* tile-escape guard vacuously against empty states. These payloads pin the
* measured worst case: five populated correlation cards whose header caption
* ("269 gepaarte Tage · r = 0.37") used to escape the card edge by 14–39 px
* at 390/360, plus mood-involving discovered pairs whose stat caption wrapped
* to a 4-line sliver beside the finding sentence.
*/
function correlation(r: number, strength: string, n: number) {
return {
result: { r, strength, n },
n,
points: Array.from({ length: 24 }, (_, i) => ({
x: 1 + (i % 5),
y: 40 + ((i * 7) % 55),
})),
};
}
const MOOD_INSIGHTS_PAYLOAD = {
summary: { totalEntries: 269, inTargetPct: 62 },
heatmap: {
windowDays: 90,
cells: [
{ date: "2026-07-01", score: 4, samples: 2 },
{ date: "2026-07-02", score: 3, samples: 1 },
],
},
distribution: [],
weekday: [],
timeOfDay: { reliable: false },
stability: null,
tags: [],
structuredTags: [],
tagInfluence: { flat: [], structured: [] },
betterDays: [],
tagMetricCrosstab: [],
factorCrosstab: [],
narratives: [],
correlations: {
sleep: correlation(0.37, "schwach", 269),
steps: correlation(0.52, "moderat", 231),
pulse: correlation(-0.44, "moderat", 258),
weight: correlation(0.21, "schwach", 244),
bloodPressureSystolic: correlation(-0.73, "stark", 212),
},
};
const CORRELATION_DISCOVERY_PAYLOAD = {
discovered: [
{
behaviour: "SLEEP_DURATION",
outcome: "MOOD",
n: 269,
r: 0.37,
pValue: 0.001,
qValue: 0.012,
interpretation: "",
lagDays: 1,
},
{
behaviour: "MOOD",
outcome: "RESTING_HEART_RATE",
n: 231,
r: -0.41,
pValue: 0.002,
qValue: 0.031,
interpretation: "",
lagDays: 1,
},
{
behaviour: "TIME_IN_DAYLIGHT",
outcome: "MOOD",
n: 187,
r: 0.29,
pValue: 0.004,
qValue: 0.048,
interpretation: "",
lagDays: 0,
},
],
pairsTested: 42,
fdrQ: 0.1,
minPairs: 20,
};
/** Serve populated mood-insights + discovery payloads for the density guard. */
export async function mockMoodInsights(page: Page): Promise<void> {
await page.route(/\/api\/insights\/comprehensive(\?|$)/, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: { moodSummary: { count: 269 } },
error: null,
}),
});
});
await page.route(/\/api\/mood\/insights(\?|$)/, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: MOOD_INSIGHTS_PAYLOAD, error: null }),
});
});
await page.route(/\/api\/insights\/correlations(\?|$)/, async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: CORRELATION_DISCOVERY_PAYLOAD, error: null }),
});
});
}