Skip to content

fix(go): align deck lock with #deck transition-duration (60ms floor) - #38

Open
raawaa wants to merge 1 commit into
op7418:mainfrom
raawaa:fix/deck-lock-follows-transition
Open

fix(go): align deck lock with #deck transition-duration (60ms floor)#38
raawaa wants to merge 1 commit into
op7418:mainfrom
raawaa:fix/deck-lock-follows-transition

Conversation

@raawaa

@raawaa raawaa commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Bug: in static mode (B key), rapid keypresses are eaten because go()'s hard-coded 700ms lock outlives the deck's 0s CSS transition. Users have to press twice to advance the slide.

Fix: replace the literal 700 with a deckLockMs() helper that reads #deck's actual CSS transition-duration and applies a 60ms floor (to block keyboard auto-repeat at ~30ms). The lock now follows the transition in both static and motion modes.

Scope: 2 files — assets/template.html (Style A) and assets/template-swiss.html (Style B). 32 lines added, 2 removed. No other files touched.

Repro

  1. Open any deck built from assets/template.html (Style A) or assets/template-swiss.html (Style B).
  2. Press B to enter static mode (body.low-power, transition: none !important).
  3. Press four times with ~200ms between presses.
  4. Observed: only the first press advances; the next three are eaten by the 700ms lock and do nothing. The user has to press twice to advance the slide. Affects every upstream user who toggles to static mode.

Root cause

go(n) has a hard-coded 700ms lock at the top:

if(lock)return;
// ...
lock=true; setTimeout(()=>lock=false, 700);

But #deck's CSS transition-duration is mode-dependent:

  • Static mode (body.low-power): 0s (the transition is none !important)
  • Motion mode (default): .9s (900ms)

The lock and the transition are not aligned. In static mode the 700ms lock outlives the 0ms transition by 700ms, swallowing any rapid follow-up press.

Fix

Read #deck's transitionDuration via getComputedStyle, take the max across comma-separated components, and apply a 60ms floor:

function deckLockMs(){
  const deckEl = document.getElementById('deck');
  if (!deckEl) return 60;
  const cs = getComputedStyle(deckEl);
  const d = cs.transitionDuration || '0s';
  let max = 0;
  for (const p of d.split(',')) {
    const t = p.trim();
    if (!t) continue;
    if (t.endsWith('ms')) max = Math.max(max, parseFloat(t));
    else if (t.endsWith('s')) max = Math.max(max, parseFloat(t) * 1000);
  }
  return Math.max(60, max);
}

In go(), replace setTimeout(()=>lock=false, 700) with setTimeout(()=>lock=false, deckLockMs()). Applied identically to assets/template.html and assets/template-swiss.html.

Why 60ms floor: blocks keyboard auto-repeat at ~30ms, doesn't block normal human keypress at >120ms.

Why CSS-driven (not __lowPowerMode-driven): CSS is the single source of truth for transition length. If someone customizes the transition to 600ms or adds a will-change, the lock follows automatically. Reading the JS flag would duplicate config.

Evidence

Repro on a 2-slide throwaway deck (served locally, opened in browser). Press four times with ~200ms between presses, in both modes.

Static mode (B key, body.low-power):

  • Old code: the first press advances; the next three are eaten by the 700ms lock and do nothing. The user has to press twice to advance.
  • New code: all four presses advance the slide. The lock is 60ms, well below normal human keypress intervals.

Motion mode (default, ~900ms CSS transition):

  • Old code: the first press advances; the next three are throttled by the 700ms lock.
  • New code: the first press advances; the next three are throttled by the ~900ms lock. Behavior preserved — no regression in the default mode.

Unit tests for deckLockMs() (mocked getComputedStyle):

CSS transitionDuration Expected return (ms) Got
0s (static mode) 60 60
0.9s (motion mode) 900 900
900ms 900 900
0.9s, 0.3s (multi-component) 900 (max) 900
200ms, 0.5s (mixed units) 500 (max) 500
100ms, 200ms, 50ms (ms only) 200 (max) 200
0.25s (float seconds) 250 250
#deck element missing 60 (floor) 60

All 8 cases pass.

A manual browser test (in-app browser against the throwaway deck) confirmed: 4 rapid presses in static mode all advance; switching back to motion mode preserves the throttle.

Notes

  • The 450ms __playSlide recipe trigger (if(window.__playSlide) setTimeout(()=>window.__playSlide(idx), 450)) is unchanged. If the transition length is ever customized, that trigger could become transitionMs / 2 — separate concern, out of scope.
  • __lowPowerMode / body.low-power still only control visual / animation, not lock timing.
  • No changes to references/checklist.md or any other file; this PR is the minimum surface area for the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant