v1.6.0 (web, UI + E2E) : chip Prolonger, split LAB/LABX, avertissement, WEB_VERSION 1.6.0
- chart.js : chip « Prolonger » (off par défaut, désactivé tant que Tracé labs est off), split série "LAB" (ancrée) / "LABX" (prolongée, rose atténué rgba(194,24,91,0.55)), légendes dédiées (clés LAB ET LABX skippées de la boucle — miroir leçon #63), AVERTISSEMENT « simple simulation, sans garantie de correspondre au réel, labs potentiellement erronés » affiché sous la légende tant que la prolongation est visible (condition factorisée : jamais de courbe prolongée sans avertissement). - i18n : show_lab_track_extend, legend_lab_track_extend, lab_track_extend_warning (FR/EN synchronisées). - E2E : scénario v1.6.0 (chip désactivé → activation → légende + avertissement → disparition à la désactivation) ; assertions version 1.6.0.
This commit is contained in:
parent
bd338decbf
commit
551d8fa29e
@ -76,6 +76,13 @@ export function renderChart(container, ctx) {
|
||||
showForecast: false,
|
||||
showExtrema: false,
|
||||
showLabTrack: false, // v1.5.0 — courbe ancrée sur les labs, OFF par défaut (choix v1.5.0)
|
||||
// v1.6.0 : PROLONGATION du tracé labs au-delà du dernier lab (ρ du
|
||||
// dernier lab constant × modèle, bornée par l'extinction du modèle —
|
||||
// cf LabTrajectoryModel.extensionHorizonEndMs). OFF par défaut : la
|
||||
// partie prolongée est une ESTIMATION non ancrée, elle doit être
|
||||
// demandée explicitement ; le chip n'est actif que si Lab track est ON
|
||||
// (miroir ChartScreen.kt).
|
||||
showLabTrackExtend: false,
|
||||
panResidualHours: 0.0, // résiduel fractionnaire du pan (fix #62)
|
||||
togglesInitialized: false,
|
||||
};
|
||||
@ -208,18 +215,39 @@ export function renderChart(container, ctx) {
|
||||
modelRow.appendChild(filterChip(t('model_whs'), state.showWhs, () => { state.showWhs = !state.showWhs; redraw(); }));
|
||||
// v1.5.0 : courbe hybride ancrée sur les labs (off par défaut)
|
||||
modelRow.appendChild(filterChip(t('show_lab_track'), state.showLabTrack, () => { state.showLabTrack = !state.showLabTrack; redraw(); }));
|
||||
// v1.6.0 : prolongation du tracé labs au-delà du dernier lab (off par
|
||||
// défaut, SANS EFFET — donc désactivé — tant que Lab track est off :
|
||||
// la prolongation n'existe que comme extension de CETTE courbe)
|
||||
modelRow.appendChild(filterChip(t('show_lab_track_extend'), state.showLabTrackExtend, () => { state.showLabTrackExtend = !state.showLabTrackExtend; redraw(); }, !state.showLabTrack));
|
||||
|
||||
// ── Courbes par modèle sélectionné ─────────────────────────────────────
|
||||
const curves = [];
|
||||
// ── v1.5.0 : courbe « Tracé labs » ───────────────────────────────────────
|
||||
// Indépendante des toggles modèles (modèle STOCKÉ — sémantique Home) et
|
||||
// de la calibration (elle EST sa propre calibration continue — épinglé).
|
||||
//
|
||||
// ── v1.6.0 : split ancré / prolongé ──────────────────────────────────────
|
||||
// Avec « Prolonger », la série est SPLITTÉE au dernier lab significatif
|
||||
// (lastAnchorMs) : "LAB" = partie ancrée (passe par les labs), "LABX" =
|
||||
// partie prolongée (M(t) × ρ_last — une estimation). Deux traits
|
||||
// distincts (LABX en atténué) préservent l'honnêteté de l'affichage.
|
||||
// ⚠️ Les DEUX clés doivent être SKIPPÉES par la boucle de légende
|
||||
// (leçon #63 Android : sinon le catchall imprime la légende TFS en
|
||||
// doublon) et avoir leurs entrées dédiées plus bas.
|
||||
if (state.showLabTrack) {
|
||||
const step = stepForRange(state.rangeHours);
|
||||
const anchored = computeLabAnchoredCurve(
|
||||
const labCurve = computeLabAnchoredCurve(
|
||||
treatments, doseLogs, labResults, startMs, endMs, step, null,
|
||||
state.showLabTrackExtend,
|
||||
);
|
||||
curves.push(['LAB', anchored.points]);
|
||||
if (state.showLabTrackExtend && labCurve.lastAnchorMs !== null) {
|
||||
const anchored = labCurve.points.filter((p) => p.timestamp <= labCurve.lastAnchorMs);
|
||||
const extended = labCurve.points.filter((p) => p.timestamp > labCurve.lastAnchorMs);
|
||||
curves.push(['LAB', anchored]);
|
||||
if (extended.length > 0) curves.push(['LABX', extended]);
|
||||
} else {
|
||||
curves.push(['LAB', labCurve.points]);
|
||||
}
|
||||
}
|
||||
if (state.showEse || state.showTfs || state.showWhs) {
|
||||
// Home utilise le modèle stocké ; le graphique calibre chaque modèle
|
||||
@ -288,6 +316,18 @@ export function renderChart(container, ctx) {
|
||||
// (série E2-only) et hors pics/creux (showExtrema=false)
|
||||
return { points: pts, e2Style: { color: COLORS.chartLabTrajectory, dashed: true }, tStyle: null, showExtrema: false };
|
||||
}
|
||||
if (model === 'LABX') {
|
||||
// v1.6.0 : partie PROLONGÉE — même couleur ATTÉNUÉE (alpha 0,55,
|
||||
// = #c2185b en rgba) pour distinguer l'estimation non ancrée de
|
||||
// la partie qui passe par les labs. Mêmes exclusions (pas de T,
|
||||
// pas d'extrema).
|
||||
return {
|
||||
points: pts,
|
||||
e2Style: { color: 'rgba(194,24,91,0.55)', dashed: true },
|
||||
tStyle: null,
|
||||
showExtrema: false,
|
||||
};
|
||||
}
|
||||
const st = seriesStyles(model);
|
||||
return { points: pts, e2Style: st.e2, tStyle: st.t };
|
||||
}),
|
||||
@ -310,9 +350,10 @@ export function renderChart(container, ctx) {
|
||||
|
||||
// Légende dynamique (miroir du bloc Compose)
|
||||
for (const [model] of curves) {
|
||||
// ⚠️ La clé 'LAB' est SKIPPÉE (légende dédiée plus bas, fix #63
|
||||
// Android-miroir : sinon le `else` imprime la légende TFS en doublon)
|
||||
if (model === 'LAB') continue;
|
||||
// ⚠️ Les clés 'LAB' (v1.5.0) et 'LABX' (v1.6.0) sont SKIPPÉES
|
||||
// (légendes dédiées plus bas, fix #63 Android-miroir : sinon le
|
||||
// catchall imprime la légende TFS en doublon)
|
||||
if (model === 'LAB' || model === 'LABX') continue;
|
||||
const st = seriesStyles(model);
|
||||
if (model === 'ESE') {
|
||||
legend.appendChild(legendLine(t('legend_ese_e2'), st.e2.color));
|
||||
@ -335,6 +376,16 @@ export function renderChart(container, ctx) {
|
||||
if (state.showLabTrack && curves.some(([m, pts]) => m === 'LAB' && pts.length >= 2)) {
|
||||
legend.appendChild(legendLine(t('legend_lab_track'), COLORS.chartLabTrajectory));
|
||||
}
|
||||
// v1.6.0 : légende de la partie PROLONGÉE + AVERTISSEMENT — seulement
|
||||
// si la série "LABX" existe (chip Prolonger actif ET horizon atteint
|
||||
// dans la fenêtre visible). Même condition pour les deux (factorisée) :
|
||||
// il ne peut jamais y avoir de courbe prolongée sans son avertissement.
|
||||
const labExtendVisible = state.showLabTrack && state.showLabTrackExtend
|
||||
&& curves.some(([m, pts]) => m === 'LABX' && pts.length >= 2);
|
||||
if (labExtendVisible) {
|
||||
legend.appendChild(legendLine(t('legend_lab_track_extend'), 'rgba(194,24,91,0.55)'));
|
||||
legend.appendChild(el('div', { class: 'legend-muted' }, t('lab_track_extend_warning')));
|
||||
}
|
||||
legend.appendChild(el('div', { class: 'legend-muted' }, t('drag_hint')));
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ import { el, showDialog, field, switchRow, filterChip, toast } from './component
|
||||
* (APK + web au MÊME numéro, tags appariés `vX.Y.Z` + `vX.Y.Z-web`).
|
||||
* v1.4.10 = portage initial de l'Android v1.4.10.
|
||||
*/
|
||||
export const WEB_VERSION = '1.5.0';
|
||||
export const WEB_VERSION = '1.6.0';
|
||||
|
||||
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
|
||||
function parseNumOrNull(text) {
|
||||
|
||||
@ -182,6 +182,10 @@ const EN = {
|
||||
show_forecast: 'Forecast',
|
||||
show_extrema: 'Peaks & troughs',
|
||||
show_lab_track: 'Lab track',
|
||||
// v1.6.0 : prolongation du tracé labs au-delà du dernier lab (opt-in,
|
||||
// actif seulement quand Lab track est ON — miroir strings.xml Android)
|
||||
show_lab_track_extend: 'Extend',
|
||||
lab_track_extend_warning: '⚠ Simulation only, with no guarantee of matching reality: the extension extrapolates your model from your lab results — which can themselves be inaccurate. Trust your next blood test, not this curve.',
|
||||
back_to_present: 'Back to present',
|
||||
legend_ese_e2: 'E2 — Estrannaise (blue)',
|
||||
legend_ese_t: 'T — Estrannaise (pink)',
|
||||
@ -193,6 +197,8 @@ const EN = {
|
||||
legend_forecast_dose: 'Dashed line + ▼ = projected dose',
|
||||
legend_extrema: '▲▼ = local peaks & troughs (with estimated values)',
|
||||
legend_lab_track: 'Lab-anchored curve — model shape forced through your labs (E2 only, between first and last lab)',
|
||||
// v1.6.0 : légende de la partie PROLONGÉE du tracé labs (série "LABX")
|
||||
legend_lab_track_extend: '-- Lab track extended past your last lab (model × last lab ratio — estimate, not anchored)',
|
||||
drag_hint: 'Drag horizontally to pan (right = past, left = future with forecast). Pinch or − / + to zoom (6 h → 300 d).',
|
||||
// Rappels (bannière + notification)
|
||||
reminder_notif_title: 'HormoneTrack — dose reminder',
|
||||
@ -360,6 +366,10 @@ const FR = {
|
||||
show_forecast: 'Prévision',
|
||||
show_extrema: 'Pics & creux',
|
||||
show_lab_track: 'Tracé labs',
|
||||
// v1.6.0 : prolongation du tracé labs au-delà du dernier lab (opt-in,
|
||||
// actif seulement quand Tracé labs est ON — miroir strings.xml Android)
|
||||
show_lab_track_extend: 'Prolonger',
|
||||
lab_track_extend_warning: '⚠ Simple simulation, sans garantie de correspondre au réel : la prolongation extrapole ton modèle à partir de tes résultats de laboratoire — qui peuvent eux-mêmes être erronés. Fie-toi à ta prochaine prise de sang, pas à cette courbe.',
|
||||
back_to_present: 'Revenir à maintenant',
|
||||
legend_ese_e2: 'E2 — Estrannaise (bleu)',
|
||||
legend_ese_t: 'T — Estrannaise (rose)',
|
||||
@ -371,6 +381,8 @@ const FR = {
|
||||
legend_forecast_dose: 'Ligne pointillée + ▼ = dose projetée',
|
||||
legend_extrema: '▲▼ = pics/creux locaux (avec valeurs estimées)',
|
||||
legend_lab_track: 'E2 ancrée sur tes labs — forme du modèle forcée de passer par chaque prise de sang (entre le 1er et le dernier lab)',
|
||||
// v1.6.0 : légende de la partie PROLONGÉE du tracé labs (série "LABX")
|
||||
legend_lab_track_extend: '-- Tracé labs prolongé après ton dernier lab (modèle × ratio du dernier lab — estimation, plus ancrée)',
|
||||
drag_hint: 'Glisser horizontalement pour panoramique (droite = passé, gauche = futur avec la prévision). Pincer ou − / + pour zoomer (6 h → 300 j).',
|
||||
reminder_notif_title: 'HormoneTrack — rappel de dose',
|
||||
reminder_notif_body: '{0} · {1}{2} — prévu à {3}',
|
||||
|
||||
@ -131,7 +131,7 @@ async function main() {
|
||||
ok(dialog !== null, 'dialog « Nouveautés » au premier lancement (markdown rendu)');
|
||||
if (dialog) {
|
||||
const dlgText = await dialog.textContent();
|
||||
ok(dlgText.includes('Version 1.5.0'), 'dialog : section de la version courante');
|
||||
ok(dlgText.includes('Version 1.6.0'), 'dialog : section de la version courante');
|
||||
const hasRenderedMd = await dialog.$('.md-body h3, .md-body strong');
|
||||
ok(hasRenderedMd !== null, 'dialog : markdown RENDU (titres/gras), pas de brut');
|
||||
await page.click('.dialog .actions button');
|
||||
@ -167,6 +167,57 @@ async function main() {
|
||||
ok(legendText.includes('Glisser horizontalement'), 'hint panoramique');
|
||||
await page.screenshot({ path: join(SHOTS, 'e2e-chart.png') });
|
||||
|
||||
// ── 2.bis Tracé labs PROLONGÉ (v1.6.0) : chips, légende, avertissement ──
|
||||
// La démo place le dernier lab à now − 7 j : on dézoome (24 h → 32 j)
|
||||
// pour que la partie ANCRÉE soit dans la fenêtre, puis on active
|
||||
// « Tracé labs » puis « Prolonger ». ⚠️ Chaque interaction re-rend le
|
||||
// DOM (redraw) → re-chercher le chip avant CHAQUE clic. ⚠️ Les chips
|
||||
// filterChip sont des <span class="chip"> (onclick inline), PAS des
|
||||
// <button> — seuls − / + sont de vrais boutons ; « disabled » est
|
||||
// simulé par style opacity:0.4 (pas d'attribut disabled).
|
||||
console.log('── Tracé labs prolongé (#chart, v1.6.0) ──');
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.evaluate(() => {
|
||||
[...document.querySelectorAll('button.chip')].find((x) => x.textContent.trim() === '−')?.click();
|
||||
});
|
||||
}
|
||||
await page.waitForTimeout(400);
|
||||
// 1) Le chip « Prolonger » est DÉSACTIVÉ tant que « Tracé labs » est off
|
||||
const extendDisabledBefore = await page.evaluate((lbl) => {
|
||||
const b = [...document.querySelectorAll('.chip')].find((x) => x.textContent.trim() === lbl);
|
||||
return b ? /opacity:\s*0\.4/.test(b.getAttribute('style') || '') : null;
|
||||
}, 'Prolonger');
|
||||
ok(extendDisabledBefore === true, 'chip « Prolonger » désactivé tant que Tracé labs est off');
|
||||
// 2) Active Tracé labs → la légende ancrée apparaît (sans avertissement)
|
||||
await page.evaluate((lbl) => {
|
||||
[...document.querySelectorAll('.chip')].find((x) => x.textContent.trim() === lbl)?.click();
|
||||
}, 'Tracé labs');
|
||||
await page.waitForTimeout(400);
|
||||
const labTrackText = await page.textContent('main.content');
|
||||
ok(labTrackText.includes('E2 ancrée sur tes labs'), 'légende « Tracé labs » (ancrée) affichée');
|
||||
ok(!labTrackText.includes('Simple simulation, sans garantie'),
|
||||
'pas d\'avertissement tant que la prolongation est off');
|
||||
// 3) Active « Prolonger » → légende prolongée + AVERTISSEMENT VISIBLE
|
||||
await page.evaluate((lbl) => {
|
||||
[...document.querySelectorAll('.chip')].find((x) => x.textContent.trim() === lbl)?.click();
|
||||
}, 'Prolonger');
|
||||
await page.waitForTimeout(400);
|
||||
const extendText = await page.textContent('main.content');
|
||||
ok(extendText.includes('Tracé labs prolongé'), 'légende de la partie prolongée affichée');
|
||||
ok(extendText.includes('Simple simulation, sans garantie'),
|
||||
'AVERTISSEMENT « simulation sans garantie » visible');
|
||||
ok(extendText.includes('qui peuvent eux-mêmes être erronés'),
|
||||
'avertissement : les résultats de lab peuvent être erronés');
|
||||
// 4) Désactive « Prolonger » → avertissement ET légende prolongée disparaissent
|
||||
await page.evaluate((lbl) => {
|
||||
[...document.querySelectorAll('.chip')].find((x) => x.textContent.trim() === lbl)?.click();
|
||||
}, 'Prolonger');
|
||||
await page.waitForTimeout(400);
|
||||
const offText = await page.textContent('main.content');
|
||||
ok(!offText.includes('Tracé labs prolongé') && !offText.includes('Simple simulation, sans garantie'),
|
||||
'désactivation → légende prolongée + avertissement disparaissent');
|
||||
await page.screenshot({ path: join(SHOTS, 'e2e-chart-extend.png') });
|
||||
|
||||
// ── 3. Navigation : les 5 onglets + settings ────────────────────────────
|
||||
console.log('── Navigation ──');
|
||||
await page.goto(`${base}/#doses`, { waitUntil: 'load' });
|
||||
@ -192,7 +243,7 @@ async function main() {
|
||||
await page.waitForTimeout(300);
|
||||
text = await page.textContent('main.content');
|
||||
ok(text.includes('Langue'), 'Paramètres : carte langue');
|
||||
ok(text.includes('Version 1.5.0 (web)'), 'Paramètres : version affichée');
|
||||
ok(text.includes('Version 1.6.0 (web)'), 'Paramètres : version affichée');
|
||||
ok(text.includes('100 % local'), 'Paramètres : note vie privée');
|
||||
await page.screenshot({ path: join(SHOTS, 'e2e-settings.png') });
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user