feat(audio-player): gear-menu default speed + split shortcuts into Mouse/Touch (plan §12)
Release / build-and-push (push) Successful in 3m13s

The Player settings popover gains a 'Default playback speed' row
beside autoplay — a compact rate select, always visible (the Speed ▾
footer hides when current == default, which made the setting
undiscoverable at 1x/1×); same setDefaultSpeed PUT. The shortcuts
dialog splits its pointer section into MOUSE (click/drag/shift/wheel)
and TOUCH (drag/pull-away) — the bar has two scrub systems and the
sections now say so.
This commit is contained in:
John O'Keefe committed 2026-10-22 15:39:14 -04:00
1 parent 3576a4415a
commit 03c3e151dc
4 files changed
+119 -28

No files matched your search

+14 -5
View File
@@ -215,22 +215,31 @@ export function visibleKeymap(): KeyActionSpec[] {
// Pointer/wheel/touch interactions (plan §9) are not key events — the
// dispatcher never sees them — but they belong in the same dialog so
// every scrub gesture is discoverable in one place. templ x-fors this
// table through the component's `pointerGuide` property (same binding
// pattern as speeds/skipChoices).
// every scrub gesture is discoverable in one place. TWO sections,
// because the bar really does have two scrub systems (plan §12): the
// desktop-pointer family and the touch family. templ x-fors these
// through the component's `mouseGuide`/`touchGuide` properties (same
// binding pattern as speeds/skipChoices).
export interface PointerGuideRow {
// Badge label, e.g. "Shift + drag".
label: string;
description: string;
}
export const POINTER_GUIDE: readonly PointerGuideRow[] = [
export const MOUSE_GUIDE: readonly PointerGuideRow[] = [
{ label: "Click", description: "Seek — lands on a chapter line within a pixel's reach" },
{ label: "Drag", description: "Free scrub across the whole bar" },
{ label: "Shift + click", description: "Jump to the nearest chapter line" },
{ label: "Shift + drag", description: "Precision rail (±5 min) around the nearest line" },
{ label: "Wheel", description: "Nudge ±5s (Shift: ±60s)" },
{ label: "Touch + pull away", description: "Scrub, then drag off the bar for fine control" },
];
export const TOUCH_GUIDE: readonly PointerGuideRow[] = [
{ label: "Drag", description: "Free scrub across the whole bar" },
{
label: "Pull away",
description: "Drag off the bar while holding — the further you pull, the finer it gets",
},
];
// resolveShortcut maps a keydown's (key, altKey) to an action, or null
+10 -3
View File
@@ -15,7 +15,13 @@
import { Alpine } from "./alpine";
import { renderNoteHtml } from "./markdown";
import { showToast } from "./toast";
import { POINTER_GUIDE, resolveShortcut, visibleKeymap, type KeyAction } from "./audio-keys";
import {
MOUSE_GUIDE,
resolveShortcut,
TOUCH_GUIDE,
visibleKeymap,
type KeyAction,
} from "./audio-keys";
import {
SLEEP_CHOICES,
SLEEP_OFF,
@@ -202,8 +208,9 @@ Alpine.data("audioReader", () => ({
// Exposed for the templ dropdown's x-for — templ cannot import the
// const directly; this property is the binding.
speeds: SPEEDS,
// Same binding pattern, for the dialog's Mouse & touch section.
pointerGuide: POINTER_GUIDE,
// Same binding pattern, for the dialog's two scrub-system sections.
mouseGuide: MOUSE_GUIDE,
touchGuide: TOUCH_GUIDE,
skipSeconds: Number(localStorage.getItem("audio_skip") || 10),
// End-label display mode (§10): total → book remaining → chapter
// remaining, persisted per device.