F10-F12: Documentation sweep + Prettier pass
- plan.md: overview now lists English, French & Danish; status header tracks the remediation work; date-formatting note updated to Intl - AGENTS.md: Build & Run section now lists lint, lint:fix, the test/ test:* scripts and the site scripts - technical docs (EN/FR): date formatting section rewritten for the Intl-based formatter (incl. Danish column), streak definition notes only completed logs count - user guide (EN/FR): delete sections mention the new usage counts in the confirmation dialogs - decision log entries for F1/F3/F4 landed with their fixes - npm run format over the touched markdown/code Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0bc20f81d8
commit
efeeb164b8
|
|
@ -54,6 +54,14 @@ website/ # Hugo project site (docs + blog), two separate sections
|
||||||
- `npm run preview` -- Preview production build
|
- `npm run preview` -- Preview production build
|
||||||
- `npm run format` -- Format code with Prettier
|
- `npm run format` -- Format code with Prettier
|
||||||
- `npm run format:check` -- Check formatting
|
- `npm run format:check` -- Check formatting
|
||||||
|
- `npm run lint` -- Lint `src/` with ESLint (CI runs this)
|
||||||
|
- `npm run lint:fix` -- Lint and auto-fix
|
||||||
|
- `npm run test` -- Playwright suite on Firefox (`tests/.venv` must exist)
|
||||||
|
- `npm run test:chromium` -- Playwright suite on Chromium
|
||||||
|
- `npm run test:parallel` -- Both browsers in parallel (xdist, per-file distribution)
|
||||||
|
- `npm run test:parallel:firefox` / `test:parallel:chromium` -- One browser in parallel
|
||||||
|
- `npm run test:parallel:n` -- Both browsers, worker count from `WORKERS` (default 4)
|
||||||
|
- `npm run site:dev` / `site:build` -- Hugo website dev server / minified build
|
||||||
|
|
||||||
## Plan
|
## Plan
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ are behaviour fixes; Phase 3 is code quality; Phase 4 is documentation drift.
|
||||||
2. Extend delete confirmations to include usage counts (new i18n keys ×3
|
2. Extend delete confirmations to include usage counts (new i18n keys ×3
|
||||||
languages).
|
languages).
|
||||||
3. On exercise delete, also `DELETE FROM session_item WHERE item_id = ? AND
|
3. On exercise delete, also `DELETE FROM session_item WHERE item_id = ? AND
|
||||||
item_type = 'exercise'` (and same for combo delete) so no orphans remain.
|
item_type = 'exercise'` (and same for combo delete) so no orphans remain.
|
||||||
- **Decision to validate first:** blocking vs. informative confirm — proposal:
|
- **Decision to validate first:** blocking vs. informative confirm — proposal:
|
||||||
keep it a confirm (informative), consistent with current UX.
|
keep it a confirm (informative), consistent with current UX.
|
||||||
- **Tests:** Extend `tests/test_step3_exercises.py` / `test_step4_combos.py`:
|
- **Tests:** Extend `tests/test_step3_exercises.py` / `test_step4_combos.py`:
|
||||||
|
|
@ -201,7 +201,7 @@ As Phase 1–3 fixes land, record the notable ones in `docs/decisions-log.md`
|
||||||
## Suggested execution order
|
## Suggested execution order
|
||||||
|
|
||||||
| Order | Fix | Why this order |
|
| Order | Fix | Why this order |
|
||||||
| ----- | --- | -------------- |
|
| ----- | ------- | ------------------------------------------------------------- |
|
||||||
| 1 | F2 | Trivial, immediately user-visible stat fix |
|
| 1 | F2 | Trivial, immediately user-visible stat fix |
|
||||||
| 2 | F1 | High-value, small, adds the missing re-import regression test |
|
| 2 | F1 | High-value, small, adds the missing re-import regression test |
|
||||||
| 3 | F3 | Small, defuses the migration time bomb before it's forgotten |
|
| 3 | F3 | Small, defuses the migration time bomb before it's forgotten |
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Record of architectural and technical decisions made during development.
|
||||||
|
|
||||||
## 2026-07-09 — DB restore no longer copies the backup's schema_version stamp (code review F3)
|
## 2026-07-09 — DB restore no longer copies the backup's schema_version stamp (code review F3)
|
||||||
|
|
||||||
**Decision:** `handleImportDb` in `src/db/worker.js` now excludes `schema_version` from the tables copied out of a restored backup. The live DB keeps its own version stamp, which is correct by construction: any adapt/migration scripts run against the temp DB *before* the copy, so the restored data always matches the current schema.
|
**Decision:** `handleImportDb` in `src/db/worker.js` now excludes `schema_version` from the tables copied out of a restored backup. The live DB keeps its own version stamp, which is correct by construction: any adapt/migration scripts run against the temp DB _before_ the copy, so the restored data always matches the current schema.
|
||||||
|
|
||||||
**Rationale:** This was a latent time bomb, dormant only because `SCHEMA_VERSION` is still 1. Once a v2 migration ships, restoring a v1 backup would have copied the old stamp into the live DB; the next launch would read migration-pending and re-run the migration against an already-adapted schema (e.g. a duplicate `ALTER TABLE ADD COLUMN` fails and strands the user on the migration-failed screen). Excluding the table was chosen over re-stamping after restore because it keeps the restore loop generic and adds no write. A regression test simulates an older stamp (version 0) in a backup and asserts the live stamp survives the restore; a comment in `src/db/releases.js` requires the first real v2 migration to ship a full end-to-end older-version restore test.
|
**Rationale:** This was a latent time bomb, dormant only because `SCHEMA_VERSION` is still 1. Once a v2 migration ships, restoring a v1 backup would have copied the old stamp into the live DB; the next launch would read migration-pending and re-run the migration against an already-adapted schema (e.g. a duplicate `ALTER TABLE ADD COLUMN` fails and strands the user on the migration-failed screen). Excluding the table was chosen over re-stamping after restore because it keeps the restore loop generic and adds no write. A regression test simulates an older stamp (version 0) in a backup and asserts the live stamp survives the restore; a comment in `src/db/releases.js` requires the first real v2 migration to ship a full end-to-end older-version restore test.
|
||||||
|
|
||||||
|
|
@ -18,11 +18,11 @@ Record of architectural and technical decisions made during development.
|
||||||
|
|
||||||
**Decision:** In `useJsonImport.js`, execution logs whose id already exists locally (`sameUserCollisions`) are now counted as skipped instead of being re-inserted with their original id. Previously each one hit a PRIMARY KEY violation whose message ended up in the import report's error list, making a routine "re-import my own full export" look like a partially failed import.
|
**Decision:** In `useJsonImport.js`, execution logs whose id already exists locally (`sameUserCollisions`) are now counted as skipped instead of being re-inserted with their original id. Previously each one hit a PRIMARY KEY violation whose message ended up in the import report's error list, making a routine "re-import my own full export" look like a partially failed import.
|
||||||
|
|
||||||
**Rationale:** Execution logs are immutable historical records — unlike exercises/combos/sessions there is nothing to Replace or merge, so a skip is always the correct resolution and no Replace/Skip UI is warranted. A regression test covers the exact scenario the existing round-trip test missed: re-importing a full export *without* deleting anything first must produce zero errors and no duplicate logs.
|
**Rationale:** Execution logs are immutable historical records — unlike exercises/combos/sessions there is nothing to Replace or merge, so a skip is always the correct resolution and no Replace/Skip UI is warranted. A regression test covers the exact scenario the existing round-trip test missed: re-importing a full export _without_ deleting anything first must produce zero errors and no duplicate logs.
|
||||||
|
|
||||||
## 2026-07-06 — Voice announcements via the Web Speech API (Step 26)
|
## 2026-07-06 — Voice announcements via the Web Speech API (Step 26)
|
||||||
|
|
||||||
**Decision:** Added `useSpeech.js`, a thin wrapper around `window.speechSynthesis` exposing `init()` (reads the new `voice_enabled` settings key, default enabled), `speak(text, { volume })` (cancels any pending utterance first, so a new announcement always preempts a stale one, and maps the current i18next language to a BCP-47 tag: `en` → `en-US`, `fr` → `fr-FR`, `da` → `da-DK`), `cancel()`, and `isSupported`. `useSessionExecution.js` calls `speak()` with the exercise title whenever a step becomes current (`loadStepState()`), and pre-announces the *next* exercise ("Next: X") in the two places the on-screen UI already does the same thing silently: 10 s before an EMOM slot ends (hooked into the existing `_emomSounds` tick handler) and the instant a TABATA rest phase opens (`_startRestPhase`). AMRAP and plain (NONE) combos only get the step-change announcement, matching their on-screen behavior of not showing a "next" preview until the step actually changes. To avoid the same exercise being spoken twice in quick succession, the queue index that was just pre-announced is tracked in a closure variable; `loadStepState()` skips the step-change announcement exactly once when it reaches that index. `speechSynthesis.cancel()` is also called on `exit()` and `_finish()` so nothing keeps talking after the user leaves the execution view.
|
**Decision:** Added `useSpeech.js`, a thin wrapper around `window.speechSynthesis` exposing `init()` (reads the new `voice_enabled` settings key, default enabled), `speak(text, { volume })` (cancels any pending utterance first, so a new announcement always preempts a stale one, and maps the current i18next language to a BCP-47 tag: `en` → `en-US`, `fr` → `fr-FR`, `da` → `da-DK`), `cancel()`, and `isSupported`. `useSessionExecution.js` calls `speak()` with the exercise title whenever a step becomes current (`loadStepState()`), and pre-announces the _next_ exercise ("Next: X") in the two places the on-screen UI already does the same thing silently: 10 s before an EMOM slot ends (hooked into the existing `_emomSounds` tick handler) and the instant a TABATA rest phase opens (`_startRestPhase`). AMRAP and plain (NONE) combos only get the step-change announcement, matching their on-screen behavior of not showing a "next" preview until the step actually changes. To avoid the same exercise being spoken twice in quick succession, the queue index that was just pre-announced is tracked in a closure variable; `loadStepState()` skips the step-change announcement exactly once when it reaches that index. `speechSynthesis.cancel()` is also called on `exit()` and `_finish()` so nothing keeps talking after the user leaves the execution view.
|
||||||
|
|
||||||
The Settings → Sound section gained a "Voice announcements" checkbox next to the existing volume slider, persisted the same way as `audio_volume`.
|
The Settings → Sound section gained a "Voice announcements" checkbox next to the existing volume slider, persisted the same way as `audio_volume`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# TrainUs — Implementation Plan (v3)
|
# TrainUs — Implementation Plan (v3)
|
||||||
|
|
||||||
> **Last updated:** 2026-07-06
|
> **Last updated:** 2026-07-09
|
||||||
> **Status:** Step 24 implemented (app-update modal) — awaiting manual validation; Step 25 implemented (headset media controls) — awaiting manual validation; Step 26 implemented (voice announcements) — awaiting manual validation. Full suite green on Firefox 2026-07-06 (374 passed, 1 skipped).
|
> **Status:** Step 24 implemented (app-update modal) — awaiting manual validation; Step 25 implemented (headset media controls) — awaiting manual validation; Step 26 implemented (voice announcements) — awaiting manual validation. Code-review remediation F1–F12 (see `docs/code-review-remediation-plan.md`) implemented — awaiting manual validation.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Greenfield PWA fitness app using **Vue 3 + Vite**, SQLite WASM (official) on OPFS, **i18next** (+ vue-i18next), Chart.js. Single user per device. English (default) + French. Version sourced from `package.json`, injected by Vite at build time, stored in DB. Two modes: Edit (CRUD) and Execution (timers, logging). Single deployment (latest version at site root) with a stable database location and automatic startup migration.
|
Greenfield PWA fitness app using **Vue 3 + Vite**, SQLite WASM (official) on OPFS, **i18next** (+ vue-i18next), Chart.js. Single user per device. English (default), French & Danish. Version sourced from `package.json`, injected by Vite at build time, stored in DB. Two modes: Edit (CRUD) and Execution (timers, logging). Single deployment (latest version at site root) with a stable database location and automatic startup migration.
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|
@ -427,7 +427,7 @@ ALTER TABLE execution_log_item ADD COLUMN round_number INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
- `StatsView.vue` at `#/stats`
|
- `StatsView.vue` at `#/stats`
|
||||||
- Displays database creation date: "You are using TrainUs since [formatted date]"
|
- Displays database creation date: "You are using TrainUs since [formatted date]"
|
||||||
- Date formatting uses centralized `dateFormatter.js` utility with locale-aware formatting (DD/MM/YYYY for French, MM/DD/YYYY for English)
|
- Date formatting uses centralized `dateFormatter.js` utility with locale-aware formatting via `Intl.DateTimeFormat` (MM/DD/YYYY English, DD/MM/YYYY French, DD.MM.YYYY Danish)
|
||||||
- `chart.js` + `vue-chartjs` v5
|
- `chart.js` + `vue-chartjs` v5
|
||||||
- **Summary cards:** total sessions, total exercises done, current day streak
|
- **Summary cards:** total sessions, total exercises done, current day streak
|
||||||
- **Date range filter:** quick tabs — 7d / 30d / 3m / All (default: 30d)
|
- **Date range filter:** quick tabs — 7d / 30d / 3m / All (default: 30d)
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,7 @@ async function onDelete() {
|
||||||
: t('exercises.deleteConfirm', { title: exercise.value.title })
|
: t('exercises.deleteConfirm', { title: exercise.value.title })
|
||||||
if (sessionCount > 0 || comboCount > 0) {
|
if (sessionCount > 0 || comboCount > 0) {
|
||||||
msg +=
|
msg +=
|
||||||
'\n\n' +
|
'\n\n' + t('exercises.deleteConfirmUsage', { sessions: sessionCount, combos: comboCount })
|
||||||
t('exercises.deleteConfirmUsage', { sessions: sessionCount, combos: comboCount })
|
|
||||||
}
|
}
|
||||||
const confirmed = window.confirm(msg)
|
const confirmed = window.confirm(msg)
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ trainUs/
|
||||||
Lines of code by area, measured with [cloc](https://github.com/AlDanial/cloc) (generated files, dependencies, and the Hugo theme excluded):
|
Lines of code by area, measured with [cloc](https://github.com/AlDanial/cloc) (generated files, dependencies, and the Hugo theme excluded):
|
||||||
|
|
||||||
| Area | Files | Code lines | Main languages |
|
| Area | Files | Code lines | Main languages |
|
||||||
| ------------------------ | ----: | ---------: | -------------------------------- |
|
| ---------------------- | ----: | ---------: | ------------------------ |
|
||||||
| `src/` (application) | 81 | 13,973 | Vue SFC, JavaScript, CSS |
|
| `src/` (application) | 81 | 13,973 | Vue SFC, JavaScript, CSS |
|
||||||
| `tests/` (Playwright) | 37 | 7,371 | Python |
|
| `tests/` (Playwright) | 37 | 7,371 | Python |
|
||||||
| `website/` (docs site) | 49 | 4,732 | Markdown, CSS, HTML |
|
| `website/` (docs site) | 49 | 4,732 | Markdown, CSS, HTML |
|
||||||
|
|
@ -794,7 +794,7 @@ Action mapping deliberately reuses the on-screen handlers rather than inventing
|
||||||
`useSessionExecution.js` calls `speak()` at two moments:
|
`useSessionExecution.js` calls `speak()` at two moments:
|
||||||
|
|
||||||
| Moment | Spoken text | Hook |
|
| Moment | Spoken text | Hook |
|
||||||
| ---------------------------------- | ------------------------------- | ------------------------------------------------------------------ |
|
| ----------------------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
|
||||||
| A step becomes current | The exercise title | `loadStepState()` — every step, including the first |
|
| A step becomes current | The exercise title | `loadStepState()` — every step, including the first |
|
||||||
| EMOM: 10 s left in the current slot | `"Next: {title}"` (translated) | `_emomSounds(remaining)`, alongside the existing beep thresholds |
|
| EMOM: 10 s left in the current slot | `"Next: {title}"` (translated) | `_emomSounds(remaining)`, alongside the existing beep thresholds |
|
||||||
| TABATA: rest phase opens | `"Next: {title}"` | `_startRestPhase(step)`, right where the on-screen "Next:" label is already shown |
|
| TABATA: rest phase opens | `"Next: {title}"` | `_startRestPhase(step)`, right where the on-screen "Next:" label is already shown |
|
||||||
|
|
@ -826,10 +826,10 @@ Four tabs control which period is shown: **7d**, **30d** (default), **3m**, **Al
|
||||||
#### Summary cards
|
#### Summary cards
|
||||||
|
|
||||||
| Card | Query |
|
| Card | Query |
|
||||||
| -------------- | ----------------------------------------------------------------------------------------- |
|
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Sessions | Count of `execution_log` rows in the period |
|
| Sessions | Count of `execution_log` rows in the period |
|
||||||
| Exercises done | Count of `execution_log_item` (completed) rows in the period |
|
| Exercises done | Count of `execution_log_item` (completed) rows in the period |
|
||||||
| Day streak | Consecutive calendar days (ending today) with at least one log; always across all history |
|
| Day streak | Consecutive calendar days (ending today) with at least one **completed** log (aborted runs don't count); always across all history |
|
||||||
|
|
||||||
Streak uses `setUTCHours(0,0,0,0)` for date comparison to match SQLite's `DATE(started_at)` UTC extraction.
|
Streak uses `setUTCHours(0,0,0,0)` for date comparison to match SQLite's `DATE(started_at)` UTC extraction.
|
||||||
|
|
||||||
|
|
@ -1014,12 +1014,12 @@ isStale = (effectiveDate absent) OR (now_utc − effectiveDate) > backup_reminde
|
||||||
|
|
||||||
#### Date formatting
|
#### Date formatting
|
||||||
|
|
||||||
`src/utils/dateFormatter.js` provides two locale-aware pure functions used throughout the app:
|
`src/utils/dateFormatter.js` provides two locale-aware pure functions used throughout the app. Both delegate to `Intl.DateTimeFormat` with a fixed app-language → BCP-47 map (`en` → `en-US`, `fr` → `fr-FR`, `da` → `da-DK`, same mapping as `useSpeech.js`) and 2-digit day/month, so output is deterministic per app language regardless of the OS locale:
|
||||||
|
|
||||||
| Function | English | French |
|
| Function | English | French | Danish |
|
||||||
| ----------------------------- | ------------------ | ---------------------- |
|
| ----------------------------- | ----------------------- | ----------------- | ----------------- |
|
||||||
| `formatDate(date, lang?)` | `MM/DD/YYYY` | `DD/MM/YYYY` |
|
| `formatDate(date, lang?)` | `MM/DD/YYYY` | `DD/MM/YYYY` | `DD.MM.YYYY` |
|
||||||
| `formatDateTime(date, lang?)` | `toLocaleString()` | `DD/MM/YYYY, HH:MM:SS` |
|
| `formatDateTime(date, lang?)` | date + `hh:mm:ss AM/PM` | date + `HH:MM:SS` | date + `HH.MM.SS` |
|
||||||
|
|
||||||
The optional `lang` parameter allows Vue computed properties to pass the reactive language ref, ensuring date displays update immediately on language switch.
|
The optional `lang` parameter allows Vue computed properties to pass the reactive language ref, ensuring date displays update immediately on language switch.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ trainUs/
|
||||||
Lignes de code par zone, mesurées avec [cloc](https://github.com/AlDanial/cloc) (fichiers générés, dépendances et thème Hugo exclus) :
|
Lignes de code par zone, mesurées avec [cloc](https://github.com/AlDanial/cloc) (fichiers générés, dépendances et thème Hugo exclus) :
|
||||||
|
|
||||||
| Zone | Fichiers | Lignes de code | Langages principaux |
|
| Zone | Fichiers | Lignes de code | Langages principaux |
|
||||||
| -------------------------- | -------: | --------------: | --------------------------------- |
|
| ------------------------- | -------: | -------------: | ------------------------ |
|
||||||
| `src/` (application) | 81 | 13 973 | Vue SFC, JavaScript, CSS |
|
| `src/` (application) | 81 | 13 973 | Vue SFC, JavaScript, CSS |
|
||||||
| `tests/` (Playwright) | 37 | 7 371 | Python |
|
| `tests/` (Playwright) | 37 | 7 371 | Python |
|
||||||
| `website/` (site de docs) | 49 | 4 732 | Markdown, CSS, HTML |
|
| `website/` (site de docs) | 49 | 4 732 | Markdown, CSS, HTML |
|
||||||
|
|
@ -766,10 +766,10 @@ Quatre onglets contrôlent la période affichée : **7j**, **30j** (par défaut)
|
||||||
#### Cartes de résumé
|
#### Cartes de résumé
|
||||||
|
|
||||||
| Carte | Requête |
|
| Carte | Requête |
|
||||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Séances | Nombre de lignes `execution_log` sur la période |
|
| Séances | Nombre de lignes `execution_log` sur la période |
|
||||||
| Exercices réalisés | Nombre de lignes `execution_log_item` (complétées) sur la période |
|
| Exercices réalisés | Nombre de lignes `execution_log_item` (complétées) sur la période |
|
||||||
| Série de jours | Jours calendaires consécutifs (jusqu'à aujourd'hui) avec au moins un journal ; toujours calculée sur tout l'historique |
|
| Série de jours | Jours calendaires consécutifs (jusqu'à aujourd'hui) avec au moins un journal **complété** (les séances abandonnées ne comptent pas) ; toujours calculée sur tout l'historique |
|
||||||
|
|
||||||
La série utilise `setUTCHours(0,0,0,0)` pour comparer les dates et correspondre à l'extraction UTC `DATE(started_at)` de SQLite.
|
La série utilise `setUTCHours(0,0,0,0)` pour comparer les dates et correspondre à l'extraction UTC `DATE(started_at)` de SQLite.
|
||||||
|
|
||||||
|
|
@ -956,12 +956,12 @@ isStale = (effectiveDate absente) OU (now_utc − effectiveDate) > backup_remind
|
||||||
|
|
||||||
#### Formatage des dates
|
#### Formatage des dates
|
||||||
|
|
||||||
`src/utils/dateFormatter.js` fournit deux fonctions pures, sensibles à la locale, utilisées partout dans l'application :
|
`src/utils/dateFormatter.js` fournit deux fonctions pures, sensibles à la locale, utilisées partout dans l'application. Les deux délèguent à `Intl.DateTimeFormat` avec une correspondance fixe langue → BCP-47 (`en` → `en-US`, `fr` → `fr-FR`, `da` → `da-DK`, la même que `useSpeech.js`) et jour/mois sur 2 chiffres, pour une sortie déterministe par langue quelle que soit la locale du système :
|
||||||
|
|
||||||
| Fonction | Anglais | Français |
|
| Fonction | Anglais | Français | Danois |
|
||||||
| ----------------------------- | ------------------ | ---------------------- |
|
| ----------------------------- | ----------------------- | ----------------- | ----------------- |
|
||||||
| `formatDate(date, lang?)` | `MM/DD/YYYY` | `DD/MM/YYYY` |
|
| `formatDate(date, lang?)` | `MM/DD/YYYY` | `DD/MM/YYYY` | `DD.MM.YYYY` |
|
||||||
| `formatDateTime(date, lang?)` | `toLocaleString()` | `DD/MM/YYYY, HH:MM:SS` |
|
| `formatDateTime(date, lang?)` | date + `hh:mm:ss AM/PM` | date + `HH:MM:SS` | date + `HH.MM.SS` |
|
||||||
|
|
||||||
Le paramètre optionnel `lang` permet aux propriétés calculées (`computed`) de Vue de passer la ref réactive de langue, pour que l'affichage des dates se mette à jour immédiatement lors d'un changement de langue.
|
Le paramètre optionnel `lang` permet aux propriétés calculées (`computed`) de Vue de passer la ref réactive de langue, pour que l'affichage des dates se mette à jour immédiatement lors d'un changement de langue.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ The edit form is identical to the create form but pre-populated with current dat
|
||||||
|
|
||||||
### Deleting an Exercise
|
### Deleting an Exercise
|
||||||
|
|
||||||
Click the **trash** icon in the action bar. A confirmation dialog appears. If the exercise has past execution entries, the dialog shows how many will be deleted with it. Confirming permanently deletes the exercise — along with any execution log entries that reference it — and returns you to the list. The rest of each affected session log (its other exercises) is unaffected.
|
Click the **trash** icon in the action bar. A confirmation dialog appears. If the exercise has past execution entries, the dialog shows how many will be deleted with it; if it is used in sessions or combos, the dialog also shows how many, since the exercise will be removed from them. Confirming permanently deletes the exercise — along with any execution log entries that reference it — and returns you to the list. The rest of each affected session log (its other exercises) is unaffected.
|
||||||
|
|
||||||
### Searching Exercises
|
### Searching Exercises
|
||||||
|
|
||||||
|
|
@ -249,7 +249,7 @@ Displays combo information including the ordered exercise list with reps and wei
|
||||||
|
|
||||||
### Deleting a Combo
|
### Deleting a Combo
|
||||||
|
|
||||||
Click the **trash** icon in the action bar. Confirming permanently deletes the combo.
|
Click the **trash** icon in the action bar. If the combo is used in sessions, the confirmation dialog shows how many, since the combo will be removed from them. Confirming permanently deletes the combo.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -312,7 +312,7 @@ The edit form is identical to the create form but pre-populated with current dat
|
||||||
|
|
||||||
### Deleting a Session
|
### Deleting a Session
|
||||||
|
|
||||||
Click the **trash** icon in the action bar. A confirmation dialog appears; if the session has past execution logs, it shows how many will be deleted with it. Confirming permanently deletes the session along with all of its execution history — this cannot be undone.
|
Click the **trash** icon in the action bar. A confirmation dialog appears; if the session has past execution logs, it shows how many will be deleted with it, and if the session belongs to collections, it shows how many, since the session will be removed from them. Confirming permanently deletes the session along with all of its execution history — this cannot be undone.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -511,7 +511,7 @@ This feature depends on your browser and OS supporting the Media Session API —
|
||||||
|
|
||||||
### Voice Announcements
|
### Voice Announcements
|
||||||
|
|
||||||
During execution, a synthesized voice announces the exercise title whenever a new step begins — handy when you're not looking at the screen. For EMOM and TABATA combos, the app also pre-announces the *next* exercise ("Next: Squat") a few seconds ahead: 10 seconds before an EMOM slot ends, and as soon as a TABATA rest phase starts.
|
During execution, a synthesized voice announces the exercise title whenever a new step begins — handy when you're not looking at the screen. For EMOM and TABATA combos, the app also pre-announces the _next_ exercise ("Next: Squat") a few seconds ahead: 10 seconds before an EMOM slot ends, and as soon as a TABATA rest phase starts.
|
||||||
|
|
||||||
The voice follows your selected language and shares the same volume as the audio beeps. You can turn it off in **Settings → Sound** with the **Voice announcements** checkbox; it depends on your browser supporting speech synthesis — where it isn't supported, the app works exactly as before, just silently.
|
The voice follows your selected language and shares the same volume as the audio beeps. You can turn it off in **Settings → Sound** with the **Voice announcements** checkbox; it depends on your browser supporting speech synthesis — where it isn't supported, the app works exactly as before, just silently.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ Le formulaire d'édition est identique au formulaire de création, pré-rempli a
|
||||||
|
|
||||||
### Supprimer un exercice
|
### Supprimer un exercice
|
||||||
|
|
||||||
Cliquez sur l'icône **corbeille** dans la barre d'action. Une boîte de dialogue de confirmation apparaît ; si l'exercice a déjà été exécuté, elle indique combien d'entrées de journal seront supprimées avec lui. En confirmant, l'exercice est supprimé définitivement — ainsi que les entrées de journal d'exécution qui le concernent — et vous revenez à la liste.
|
Cliquez sur l'icône **corbeille** dans la barre d'action. Une boîte de dialogue de confirmation apparaît ; si l'exercice a déjà été exécuté, elle indique combien d'entrées de journal seront supprimées avec lui, et s'il est utilisé dans des séances ou des combos, elle indique combien, car l'exercice en sera retiré. En confirmant, l'exercice est supprimé définitivement — ainsi que les entrées de journal d'exécution qui le concernent — et vous revenez à la liste.
|
||||||
|
|
||||||
> Le reste de chaque séance enregistrée (les autres exercices de ce même journal) reste intact.
|
> Le reste de chaque séance enregistrée (les autres exercices de ce même journal) reste intact.
|
||||||
|
|
||||||
|
|
@ -251,7 +251,7 @@ Affiche les informations du combo, y compris la liste ordonnée des exercices av
|
||||||
|
|
||||||
### Supprimer un combo
|
### Supprimer un combo
|
||||||
|
|
||||||
Cliquez sur l'icône **corbeille** dans la barre d'action. En confirmant, le combo est supprimé définitivement.
|
Cliquez sur l'icône **corbeille** dans la barre d'action. Si le combo est utilisé dans des séances, la boîte de confirmation indique combien, car le combo en sera retiré. En confirmant, le combo est supprimé définitivement.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -314,7 +314,7 @@ Le formulaire d'édition est identique au formulaire de création, pré-rempli a
|
||||||
|
|
||||||
### Supprimer une séance
|
### Supprimer une séance
|
||||||
|
|
||||||
Cliquez sur l'icône **corbeille** dans la barre d'action. Une boîte de dialogue de confirmation apparaît ; si la séance a des journaux d'exécution passés, elle indique combien seront supprimés avec elle. En confirmant, la séance est supprimée définitivement, ainsi que tout son historique d'exécution — cette action est irréversible.
|
Cliquez sur l'icône **corbeille** dans la barre d'action. Une boîte de dialogue de confirmation apparaît ; si la séance a des journaux d'exécution passés, elle indique combien seront supprimés avec elle, et si la séance appartient à des collections, elle indique combien, car la séance en sera retirée. En confirmant, la séance est supprimée définitivement, ainsi que tout son historique d'exécution — cette action est irréversible.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -513,7 +513,7 @@ Cette fonctionnalité dépend de la prise en charge de la Media Session API par
|
||||||
|
|
||||||
### Annonces vocales
|
### Annonces vocales
|
||||||
|
|
||||||
Pendant l'exécution, une voix synthétisée annonce le nom de l'exercice à chaque nouvelle étape — pratique quand vous ne regardez pas l'écran. Pour les combos EMOM et TABATA, l'application annonce aussi à l'avance le *prochain* exercice (« Suivant : Squat ») quelques secondes avant : 10 secondes avant la fin d'un créneau EMOM, et dès le début d'une phase de repos TABATA.
|
Pendant l'exécution, une voix synthétisée annonce le nom de l'exercice à chaque nouvelle étape — pratique quand vous ne regardez pas l'écran. Pour les combos EMOM et TABATA, l'application annonce aussi à l'avance le _prochain_ exercice (« Suivant : Squat ») quelques secondes avant : 10 secondes avant la fin d'un créneau EMOM, et dès le début d'une phase de repos TABATA.
|
||||||
|
|
||||||
La voix suit la langue sélectionnée et partage le même volume que les bips sonores. Vous pouvez la désactiver dans **Paramètres → Son** avec la case **Annonces vocales** ; elle dépend de la prise en charge de la synthèse vocale par votre navigateur — là où elle n'est pas prise en charge, l'application fonctionne exactement comme avant, simplement en silence.
|
La voix suit la langue sélectionnée et partage le même volume que les bips sonores. Vous pouvez la désactiver dans **Paramètres → Son** avec la case **Annonces vocales** ; elle dépend de la prise en charge de la synthèse vocale par votre navigateur — là où elle n'est pas prise en charge, l'application fonctionne exactement comme avant, simplement en silence.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue