Skip to content

Feature/timeline controls - #158

Open
erklu wants to merge 1 commit into
developfrom
feature/timeline-controls
Open

erklu wants to merge 1 commit into
developfrom
feature/timeline-controls

Conversation

@erklu

@erklu erklu commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Timeline controls: zoom, keyboard and wheel navigation, dock button; caption actions follow keyboard focus

Closes #127, closes #128, closes #134.

Everything here is in the caption editor (subtitle mode), where the Timeline and the per-caption action tray live.

Since these are new features, should wait for a proper feature release.

What changes for the reader

Timeline

  • The strip can be focused and moved from the keyboard. Left/Right move the playhead a fortieth of what is on the strip (half a second at the default zoom), Page Up/Page Down a whole strip, Home/End go to the ends of the recording. Shift + Up/Down, or + and -, zoom in and out; 0 resets. Ctrl/Cmd with these keys is left to the browser's own zoom.
  • Sideways scrolling over the strip (trackpad, or Shift + wheel) moves the recording under the fixed playhead (Feature Request: Navigate Timeline using horizontal scroll #128). Only sideways movement is taken; up and down still scrolls the page, except for the tail of a swipe (200 ms after the last sideways event), which would otherwise scroll the page mid-gesture. Ctrl + wheel is left to the browser. While the pointer is over the strip the page is told not to treat a sideways swipe as browser history navigation, so swiping backwards at the start of a recording does not go to the previous page.
  • Zoom (Feature Request: Zoom controls for Captions Timeline #134): five levels, doubling from a quarter to four times the current window (the window is still 20 s under the video and 60 s docked). It changes only how the strip is drawn, it is not remembered, and "Reset zoom" returns to the window the strip started with. Zooming out is refused once the whole recording is on the strip. The "Ctrl/Cmd + scroll" option in Feature Request: Zoom controls for Captions Timeline #134 is deliberately not built, since it would take the browser's page zoom away over the strip. The issue's "time markers adapt to the zoom": the strip now has a ruler along its top edge (below).
  • A ruler along the top edge of the strip: a time at each long tick, short ticks between, in the unit the zoom calls for (tenths of a second when zoomed right in, up to minutes or hours when zoomed out; labels never closer than 64 px, small ticks only when 7 px apart or more). Nothing is marked outside the recording. It is drawn in the canvas, so it is a picture only; the slider's aria-valuetext is what assistive technology gets.
  • Moving the strip with its grip shows where it will land (Feature Request: Show drop preview when moving Timeline #127): a dashed placeholder along the foot of the window when the pointer is low enough to dock, or back under the video, in the height the strip and its legend had there, when it is on its way back. Both say "Release to dock Timeline here". Nothing is shown while the pointer is over the place the strip is already in, the placeholder lets the drop through, and it is hidden from assistive technology (the dock button covers that for them). The line in the strip's own legend that said the same while the grip was held ("Release to dock along the bottom" / "Release to put it back under the video") is gone.
  • A small tray at the right end of the first legend row replaces the labelled "Add caption at playhead" button: dock, zoom out, zoom in, reset zoom, add caption at playhead. It is built like the caption action tray (surface, hairline, round buttons). The "Click a caption to go to it…" hint moves to a row of its own below, so the width of the tray never moves it. It is revealed by pointing at the strip, by keyboard focus on the strip or on one of its buttons, and always where nothing can point (hover: none). The dock button does what the grip does, for whoever cannot drag.

Caption actions

  • The tray beside each caption keeps showing on hover exactly as before. It is now also shown when the keyboard is in use (the existing scribe-user-is-tabbing body class) and the caret is in that caption, or one of its timestamps has focus. With a mouse nothing changes, so there is never a second tray on screen for the caption the caret is in.

Visible changes (appearance)

  • The labelled button in the Timeline legend is gone; the tray described above takes its place and is hidden until the strip is pointed at or focused.
  • The strip is 16 px taller (the ruler's band); the page's bottom padding when docked grows to match. Everything else on the strip keeps its proportions in the height that is left.
  • While the grip is held, the legend no longer gets an extra line of text; the placeholder says it instead.
  • The hint under the key is on its own row; docked, the legend is one text line taller than before.
  • The text that said which part of the recording the strip shows ("0:40–1:00 of 5:00") is removed. Nothing on the strip now says where in the recording it is, apart from the slider's value for assistive technology.
  • The strip shows a focus ring when it is reached with the keyboard (the existing global rule for [tabindex]).
  • The caption tray can now appear without the pointer over it (keyboard use only).

How it is built

  • seekTo() is the one place the playhead is moved from, for the keyboard and the wheel alike: the strip moves at once, the player is told once per frame, and it is clamped to the recording. follow() ignores the player while a move is waiting.
  • The strip has role="slider", a name, aria-valuemin/max/now and aria-valuetext in words ("0:12.3 of 5:00, in caption 14"). The value is not updated every frame: only on a move the reader made, when the recording stops or lands somewhere, and on focus, so a screen reader is not talked at while the recording plays.
  • The tray is a role="toolbar" with one tab stop and arrow keys between the buttons, like the caption actions. Icons only, so every button has an aria-label. The dock button has a fixed name and aria-pressed. A button at the end of its range is aria-disabled, not disabled, so it keeps focus. Zoom and dock changes are told to a polite status region.
  • Docking teleports the strip to body, and a moved node loses focus, so toggleDock() puts focus back on the button afterwards.
  • holdSwipe() / releaseSwipe() set overscroll-behavior-x: none on <html> and <body> while the pointer is over the strip (overscroll-behavior has no effect on the strip itself, which is not a scroll container).

WCAG: 2.1.1 (the dock and the playhead were mouse only), 1.1.1 and 4.1.2 (the strip had no name, role or value), 2.5.7 (WCAG 2.2, a keyboard alternative to dragging the playhead), 2.4.7.

Tests

  • New tests for the zoom rules, the keys and the wheel, the slider attributes, the tray, the focus-return after docking, and the reveal rules of both trays (tests/test_speech_timeline.py, tests/test_caption_editor_styles.py). Two old tests that fixed the earlier design were rewritten: "the zoom is not a control" and the labelled button's style.
  • Behaviour was also run against the real component file in a simulated browser (jsdom, Vue from NiceGUI's bundle): 86 checks of the keys (including Shift + Up/Down), zoom limits, wheel handling and coalescing, the swipe latch, the page's overscroll setting, the ruler's tick choice and labels, the drop placeholders, aria values while playing, the tray's keyboard handling, createatplayhead, and docking with focus returned. That harness is not part of the repository.
  • main (8b5d52c) already fails three tests in tests/test_validation.py::TestAddingACaptionForSpeech ('Transcript' object has no attribute 'editor', from the clamp in 2669077). They are not touched here; this branch adds no failures.

Tried in a browser, and not

Tried by hand in Chrome against a dev server (sample subtitles, a 1920 px wide window): keys, zoom (Shift + Up/Down), sideways wheel in both directions, the tray and its reveal on hover, adding a caption where one already is, the ruler at several zooms, in dark mode and docked (contrast measured: about 9.4:1 light and 10:1 dark against the strip), dragging the grip in both directions, and (by Erik, on a trackpad) a backward swipe over the strip, which no longer goes back to the previous page. The placeholders measured 182 px along the foot against the docked strip's 183, and the strip's legend does not change height while the grip is held. The placeholder under the video uses the height the strip measured there, or 180 px (what it measures at the default text size) if the page opened docked.

Not tried: a screen reader (NVDA/VoiceOver on the slider and the tray); a tablet (a tap should leave :hover in place and show the tray); the legend at 701 px; narrow windows for the ruler and the placeholders; whether the step sizes feel right on long real recordings; and the keyboard-modality reveal of the tray together with hover. Tooltips on the tray buttons are hover-only, as on the caption actions; the names are in aria-label.

@erklu
erklu requested a review from krihal October 18, 2026 00:24
…e-controls

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
@erklu
erklu changed the base branch from main to develop October 21, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant