diff --git a/templates/reader.templ b/templates/reader.templ index 130fd3a8..bdc0601f 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -350,6 +350,7 @@ templ ReaderChrome(metadata ReaderMetadata) { step="any" list="tick-marks" @input="goToFraction($event.target.value)" + @pointerup="$el.blur()" class="grow" /> @@ -392,6 +393,7 @@ templ ReaderChrome(metadata ReaderMetadata) { max="1" step="any" @input="goToFraction($event.target.value)" + @pointerup="$el.blur()" class="grow min-w-[3rem]" />
โ€”
โ€”
0/0
Zoom
Fit
Page position
Magnifier
Night mode
Pointer
Double page
Contents
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
โ€”
โ€”
0/0
Zoom
Fit
Page position
Magnifier
Night mode
Pointer
Double page
Contents
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -230,7 +230,7 @@ func drawerHeader(title string) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 580, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 583, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 020520f3..bd16a208 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -3993,6 +3993,8 @@ document.addEventListener("alpine:init", () => { // Escape stays live so popovers/drawers can still be dismissed from // the keyboard even mid-note. const t = event.target as HTMLElement | null; + const isProgressSlider = + t instanceof HTMLInputElement && t.type === "range" && t.id.startsWith("progress-slider"); const typing = t?.tagName === "INPUT" || t?.tagName === "SELECT" || @@ -4000,7 +4002,17 @@ document.addEventListener("alpine:init", () => { !!t?.isContentEditable; // NOTE: no chrome poke here โ€” keyboard page turns are reading // actions and must leave the chrome alone (handoff item 7). - if (typing && k !== "Escape") return; + if (typing && k !== "Escape") { + // A focused progress range swallows arrows as ~1% book-jumps โ€” + // with key repeat, holding an arrow crosses the entire book in + // seconds and the debounced saves persist every wild landing + // (2026-10-21 incident). Arrows are page turns everywhere in the + // reader: release the slider and fall through to the turn + // handlers. Every other key still belongs to the control. + if (!(isProgressSlider && (k === "ArrowLeft" || k === "ArrowRight"))) return; + event.preventDefault(); + t.blur(); + } if (k === "ArrowLeft" || k === "h") { if (event.altKey) { event.preventDefault();