diff --git a/docs/current_state.md b/docs/current_state.md index 08311a9..7505443 100644 --- a/docs/current_state.md +++ b/docs/current_state.md @@ -395,7 +395,7 @@ unchanged at ten pre-existing errors: one `NewApi`, one `LocalContextConfigurationRead`, and eight bilingual `StringFormatMatches` diagnostics. This UI tranche adds no lint error and changes no lint policy. The next private daily Android candidate remains on development -`versionName=0.1.3`; only its strictly increasing `versionCode` may be supplied +`versionName=0.1.4`; only its strictly increasing `versionCode` may be supplied as a build-time override. No public tag or release is created by that private candidate process. diff --git a/docs/tests.md b/docs/tests.md index 1d296cc..497afcd 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -666,7 +666,7 @@ TUI product version, asset names, and release title must agree. Private update rollout may set `TRAINLOG_ANDROID_VERSION_CODE` to a positive integer above the installed package while retaining the current development -`versionName=0.1.3`. The version-code override is build-only and does not change +`versionName=0.1.4`. The version-code override is build-only and does not change any schema or exchange-format version. Private daily candidates must not downgrade the logical product version to the earlier 0.1.2 stable line. diff --git a/tests/test_sync_deployment_tools.py b/tests/test_sync_deployment_tools.py index 2892239..bdb1cdd 100644 --- a/tests/test_sync_deployment_tools.py +++ b/tests/test_sync_deployment_tools.py @@ -65,7 +65,7 @@ class DeploymentToolsTest(unittest.TestCase): ) self.assertEqual(result.returncode, 0, result.stderr) inventory = json.loads((output / "candidate-inventory.json").read_text()) - self.assertEqual(inventory["product_version"], "0.1.3") + self.assertEqual(inventory["product_version"], "0.1.4") self.assertEqual(inventory["desktop_schema"], 28) self.assertEqual(inventory["android_schema"], 25) self.assertIn("session-preparations-v2", inventory["protocols"]) @@ -88,7 +88,7 @@ class DeploymentToolsTest(unittest.TestCase): text=True, ) self.assertEqual(linked.returncode, 0, linked.stderr) - self.assertIn("trainlog 0.1.3", linked.stdout) + self.assertIn("trainlog 0.1.4", linked.stdout) imported = subprocess.run( [ "python3", diff --git a/tools/package_sync_candidate.py b/tools/package_sync_candidate.py index 46d45f9..e4b0f16 100644 --- a/tools/package_sync_candidate.py +++ b/tools/package_sync_candidate.py @@ -103,7 +103,7 @@ def main() -> int: "source_commit": subprocess.check_output( ["git", "rev-parse", "HEAD"], cwd=ROOT, text=True ).strip(), - "product_version": "0.1.3", + "product_version": "0.1.4", "desktop_schema": 28, "android_schema": 25, "protocols": [ diff --git a/web/src/dashboard/BodyZoneFigure.tsx b/web/src/dashboard/BodyZoneFigure.tsx index 354d716..091d744 100644 --- a/web/src/dashboard/BodyZoneFigure.tsx +++ b/web/src/dashboard/BodyZoneFigure.tsx @@ -24,9 +24,9 @@ export function muscleIntensityLevel(sessionCount: number, maximum: number): 0 | return 3 } -interface BodyZoneFigureProps { zones: WorkedZone[]; views?: 'front' | 'both' } +interface BodyZoneFigureProps { zones: WorkedZone[]; views?: 'front' | 'both'; language?: 'fr' | 'en' } -export function BodyZoneFigure({ zones, views = 'both' }: BodyZoneFigureProps) { +export function BodyZoneFigure({ zones, views = 'both', language = 'fr' }: BodyZoneFigureProps) { const [focused, setFocused] = useState(null) const byId = new Map(zones.map((zone) => [zone.zone_id, zone])) const maximum = Math.max(1, ...zones.map((zone) => zone.session_count)) @@ -37,25 +37,27 @@ export function BodyZoneFigure({ zones, views = 'both' }: BodyZoneFigureProps) { className: `body-region intensity-${muscleIntensityLevel(zone.session_count, maximum)}`, tabIndex: 0, role: 'button', - 'aria-label': `${zone.label} : ${count(zone.session_count, 'séance')}, ${count(zone.occurrence_count, 'occurrence')}, ${count(zone.set_count, 'série')}.`, + 'aria-label': language === 'fr' + ? `${zone.label} : ${count(zone.session_count, 'séance')}, ${count(zone.occurrence_count, 'occurrence')}, ${count(zone.set_count, 'série')}.` + : `${zone.label}: ${zone.session_count} ${zone.session_count === 1 ? 'session' : 'sessions'}, ${zone.occurrence_count} ${zone.occurrence_count === 1 ? 'exposure' : 'exposures'}, ${zone.set_count} ${zone.set_count === 1 ? 'associated set' : 'associated sets'}.`, onFocus: () => setFocused(zone), onBlur: () => setFocused(null), onMouseEnter: () => setFocused(zone), onMouseLeave: () => setFocused(null), } } return
- - {views === 'both' && } + + {views === 'both' && }
-
0FaibleIntermédiaireMaximum observé
- {focused &&
{focused.label}{count(focused.session_count, 'séance')} · {count(focused.occurrence_count, 'occurrence')} · {count(focused.set_count, 'série')}
} +
0{language === 'fr' ? 'Faible' : 'Low'}{language === 'fr' ? 'Intermédiaire' : 'Medium'}{language === 'fr' ? 'Maximum observé' : 'Observed maximum'}
+ {focused &&
{focused.label}{language === 'fr' ? `${count(focused.session_count, 'séance')} · ${count(focused.occurrence_count, 'occurrence')} · ${count(focused.set_count, 'série')}` : `${focused.session_count} sessions · ${focused.occurrence_count} exposures · ${focused.set_count} associated sets`}
}
} type RegionProps = (zoneId: MappedBodyZoneId) => Record -function BodyView({ title, side, zoneProps }: { title: string; side: 'front' | 'back'; zoneProps: RegionProps }) { - return
{title}
+function BodyView({ title, side, zoneProps, language }: { title: string; side: 'front' | 'back'; zoneProps: RegionProps; language: 'fr' | 'en' }) { + return
{title}
{side === 'front' ? <> diff --git a/web/src/routes/AnalysisPage.tsx b/web/src/routes/AnalysisPage.tsx index 10f7df9..f5302db 100644 --- a/web/src/routes/AnalysisPage.tsx +++ b/web/src/routes/AnalysisPage.tsx @@ -12,6 +12,12 @@ const metricLabels = { en: { weight: 'Weight', neck: 'Neck', shoulders: 'Shoulders', chest: 'Chest', waist: 'Waist', hips: 'Hips', left_arm: 'Left arm', right_arm: 'Right arm', left_forearm: 'Left forearm', right_forearm: 'Right forearm', left_thigh: 'Left thigh', right_thigh: 'Right thigh', left_calf: 'Left calf', right_calf: 'Right calf' }, } satisfies Record> +const zoneLabelsEn: Readonly> = { + full_body: 'Full body', upper_body: 'Upper body', chest: 'Chest', back: 'Back', + shoulders: 'Shoulders', arms: 'Arms', core: 'Core', lower_body: 'Lower body', + glutes: 'Glutes', thighs: 'Thighs', calves: 'Calves', +} + function safeInitialQuery() { const query = new URLSearchParams(window.location.search) const sectionValue = query.get('section') @@ -105,7 +111,7 @@ export function AnalysisPage() { function chooseMetric(value: MeasurementMetric) { setMetric(value); updateUrl('measurements', period, exerciseId, value); setSection('measurements') } const selectedSummary = snapshot?.measurements.summaries.find((item) => item.metric === metric) - const zoneFigure = snapshot?.body_zones.map((zone) => ({ zone_id: zone.zone_id, label: zone.label, session_count: zone.exposures, occurrence_count: zone.exposures, set_count: zone.associated_sets })) ?? [] + const zoneFigure = snapshot?.body_zones.map((zone) => ({ zone_id: zone.zone_id, label: language === 'en' ? zoneLabelsEn[zone.zone_id] ?? zone.zone_id : zone.label, session_count: zone.exposures, occurrence_count: zone.exposures, set_count: zone.associated_sets })) ?? [] return

{t.eyebrow}

{t.title}

{sections.map((id) => )}
@@ -116,11 +122,11 @@ export function AnalysisPage() { {section === 'overview' &&

{t.overview}

{snapshot.overview.duration_seconds !== null &&
{t.duration}
{duration(snapshot.overview.duration_seconds, language)}
}
{snapshot.overview.sessions === 0 &&

{t.noData}

} ({ timestamp: `${day.date}T00:00:00Z`, value: day.sessions }))} unit={t.sessions.toLowerCase()} language={language} label={t.sessions} />

{t.activeProgram}

{snapshot.active_program === null ?

{t.noProgram}

: <>

{snapshot.active_program.title}

{snapshot.active_program.completed_sessions}/{snapshot.active_program.total_sessions}

{snapshot.active_program.next_session_title &&

{t.nextSession}: {snapshot.active_program.next_session_title}

}Programmes}
-

{t.distribution}

{zoneFigure.length === 0 ?

{t.noData}

: }
+

{t.distribution}

{zoneFigure.length === 0 ?

{t.noData}

: }

{t.exercises}

{snapshot.exercise === null ?

{t.noExercise}

: <>

{snapshot.exercise.name}

}
} {section === 'exercise' &&
{snapshot.exercise === null ?

{t.noExercise}

: <>

{snapshot.exercise.name}

}
} - {section === 'body-zones' &&

{t.exposures}

{t.zonesHelp}

{zoneFigure.length === 0 ?

{t.noData}

:
    {snapshot.body_zones.map((zone) =>
  • {zone.label}{zone.exposures} {t.exposures.toLowerCase()} · {zone.associated_sets} {t.sets.toLowerCase()}
  • )}
}
} + {section === 'body-zones' &&

{t.exposures}

{t.zonesHelp}

{zoneFigure.length === 0 ?

{t.noData}

:
    {snapshot.body_zones.map((zone) =>
  • {language === 'en' ? zoneLabelsEn[zone.zone_id] ?? zone.zone_id : zone.label}{zone.exposures} {t.exposures.toLowerCase()} · {zone.associated_sets} {t.sets.toLowerCase()}
  • )}
}
} {section === 'measurements' &&

{metricLabels[language][metric]}

{selectedSummary === undefined || selectedSummary.count === 0 ?

{t.noData}

: <>
{t.first}
{selectedSummary.first === null ? '—' : `${formatNumber(selectedSummary.first, language)} ${selectedSummary.unit}`}
{t.last}
{selectedSummary.last === null ? '—' : `${formatNumber(selectedSummary.last, language)} ${selectedSummary.unit}`}
{t.delta}
{selectedSummary.delta === null ? '—' : `${selectedSummary.delta > 0 ? '+' : ''}${formatNumber(selectedSummary.delta, language)} ${selectedSummary.unit}`}
{selectedSummary.count === 1 &&

{t.onePoint}

}}
} }