[P3] Timeline range inputs give screen readers raw sample indices or fractional minutes instead of the formatted time #119

Open
opened 2026-10-31 19:47:41 +00:00 by pedro · 0 comments
Owner

Priority: P3
Type: bug
Source: full codebase review requested 2026-10-31 (independently reproduced against 2cac7cf)

Location: src/replay/DiveSummary.tsx:207-217, src/import/DiveImport.tsx:513-523, src/replay/DiveReplayComparison.tsx:606-622, src/planner/ProfilePlanner.tsx:830-843

Problem and evidence

Each of the four timeline sliders sits inside a <label> whose visible text holds the formatted time (for example "Timeline · 1:21"). Each also sets a static aria-label. Under accessible-name computation, aria-label takes precedence over the native <label>, so the visible time never becomes part of the name. None of the four sets aria-valuetext, so assistive technology announces the raw value:

  • DiveSummary and DiveImport: the sample index (max={samples.length-1}, step="1").
  • DiveReplayComparison: unrounded fractional minutes (step="any", so values like 0.2176...).
  • ProfilePlanner: minutes to two decimal places (step="0.01").
    The codebase already uses aria-valuetext in other places (App.tsx:2932 conservativeness slider, TankPressureGauge.tsx:37), which shows this is the intended pattern and these four are inconsistent with it.

Read all four sites at HEAD 2cac7cf:

  • DiveSummary.tsx:207-217: <label>Timeline · {formatTime(...)}<input type="range" aria-label="Dive summary timeline" max={samples.length - 1} step="1" value={position}/>
  • DiveImport.tsx:513-523: same pattern with aria-label="Imported dive timeline" and the value as a sample index.
  • DiveReplayComparison.tsx:606-622: aria-label="Comparison replay timeline", step="any", value = time in minutes.
  • ProfilePlanner.tsx:830-843: aria-label="Profile timeline", step="0.01".
    grep -rn aria-valuetext src matches only App.tsx and TankPressureGauge.tsx. None of the four inputs has aria-describedby. The accessible name therefore comes from aria-label alone, and the announced value is the numeric value. I did not rerun the reviewer's Playwright snapshot. The result follows from the markup and the accname precedence rules, and it matches the reviewer's ariaSnapshot (slider "Comparison replay timeline": "0.217620266666666").

Suggested correction

Add aria-valuetext to each slider:

  • DiveSummary: formatTime(sample.time - first.time)
  • DiveImport: time(sample.seconds / 60)
  • DiveReplayComparison: formatTime(time)
  • ProfilePlanner: formatTime(position)
    Optionally add context such as "of {formatTime(duration)}". Keep the existing aria-labels so test selectors stay stable, or drop them so the visible label supplies the name. For the replay slider, consider a finite step (for example 1/60 minute) so arrow keys move by whole seconds instead of arbitrary fractions.

Acceptance criteria

Each of the four sliders exposes an aria-valuetext with the same formatted m:ss time its visible label shows. A Playwright ariaSnapshot after arrowing a slider shows slider "<name>": "m:ss"-style text, not a raw index or decimal. Regression test: a component or Playwright test per slider that changes the value and checks that the aria-valuetext attribute equals the formatted time text in the label. pnpm verify passes.

Scope: maintenance of the educational simulator. Preserve historical R1 acceptance and operational-validation limits.

Priority: P3 Type: bug Source: full codebase review requested 2026-10-31 (independently reproduced against `2cac7cf`) Location: `src/replay/DiveSummary.tsx:207-217`, `src/import/DiveImport.tsx:513-523`, `src/replay/DiveReplayComparison.tsx:606-622`, `src/planner/ProfilePlanner.tsx:830-843` Problem and evidence Each of the four timeline sliders sits inside a `<label>` whose visible text holds the formatted time (for example "Timeline · 1:21"). Each also sets a static `aria-label`. Under accessible-name computation, `aria-label` takes precedence over the native `<label>`, so the visible time never becomes part of the name. None of the four sets `aria-valuetext`, so assistive technology announces the raw `value`: - DiveSummary and DiveImport: the sample index (`max={samples.length-1}`, `step="1"`). - DiveReplayComparison: unrounded fractional minutes (`step="any"`, so values like 0.2176...). - ProfilePlanner: minutes to two decimal places (`step="0.01"`). The codebase already uses `aria-valuetext` in other places (App.tsx:2932 conservativeness slider, TankPressureGauge.tsx:37), which shows this is the intended pattern and these four are inconsistent with it. Read all four sites at HEAD 2cac7cf: - DiveSummary.tsx:207-217: `<label>Timeline · {formatTime(...)}<input type="range" aria-label="Dive summary timeline" max={samples.length - 1} step="1" value={position}/>` - DiveImport.tsx:513-523: same pattern with `aria-label="Imported dive timeline"` and the value as a sample index. - DiveReplayComparison.tsx:606-622: `aria-label="Comparison replay timeline"`, `step="any"`, value = time in minutes. - ProfilePlanner.tsx:830-843: `aria-label="Profile timeline"`, `step="0.01"`. `grep -rn aria-valuetext src` matches only App.tsx and TankPressureGauge.tsx. None of the four inputs has `aria-describedby`. The accessible name therefore comes from `aria-label` alone, and the announced value is the numeric `value`. I did not rerun the reviewer's Playwright snapshot. The result follows from the markup and the accname precedence rules, and it matches the reviewer's ariaSnapshot (`slider "Comparison replay timeline": "0.217620266666666"`). Suggested correction Add `aria-valuetext` to each slider: - DiveSummary: `formatTime(sample.time - first.time)` - DiveImport: `time(sample.seconds / 60)` - DiveReplayComparison: `formatTime(time)` - ProfilePlanner: `formatTime(position)` Optionally add context such as "of {formatTime(duration)}". Keep the existing aria-labels so test selectors stay stable, or drop them so the visible label supplies the name. For the replay slider, consider a finite step (for example 1/60 minute) so arrow keys move by whole seconds instead of arbitrary fractions. Acceptance criteria Each of the four sliders exposes an aria-valuetext with the same formatted m:ss time its visible label shows. A Playwright ariaSnapshot after arrowing a slider shows `slider "<name>": "m:ss"`-style text, not a raw index or decimal. Regression test: a component or Playwright test per slider that changes the value and checks that the `aria-valuetext` attribute equals the formatted time text in the label. pnpm verify passes. Scope: maintenance of the educational simulator. Preserve historical R1 acceptance and operational-validation limits.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: pedro/DiveSim#119
No description provided.