Typing accuracy, UX improvements, and bug fixes#1
Open
hilaryduffrules-hash wants to merge 5 commits intomurdawkmedia:masterfrom
Open
Typing accuracy, UX improvements, and bug fixes#1hilaryduffrules-hash wants to merge 5 commits intomurdawkmedia:masterfrom
hilaryduffrules-hash wants to merge 5 commits intomurdawkmedia:masterfrom
Conversation
Add InputMode enum (VOICE | KEYBOARD) to support the new dual-input mode. Add wordsTyped to TypingStats for richer results display.
…, a11y - Make onInputChange optional — it was required in the interface but never passed from App.tsx, causing a TypeScript error in strict mode. - Add onKeystroke prop for per-keystroke accuracy tracking in keyboard mode. - Add hidden <input> for keyboard capture with proper mobile attributes (autoComplete/Correct/Capitalize off, enterKeyHint, touch-action). - Apply cursor-blink class correctly using the CSS animation name. - Block Tab in onKeyDown so global reset handler can intercept it. - Add role, aria-label, and aria-describedby for screen reader support. - enableKeyboard prop auto-focuses the hidden input when test starts.
…, a11y - Format time as M:SS (e.g. '1:02') for durations >= 60 s so the 120 s test is readable at a glance instead of showing a bare second count. - Color-code WPM: red < 30, amber 30-59, green >= 60 for instant feedback. - Accept totalDuration prop needed to decide the time format. - Add role='status', aria-live='polite', and individual aria-label attrs so screen readers announce stat changes.
…le closure
Bug fixes:
- CRITICAL: Speech transcript was joined with ''.join('') — segments like
'hello' and 'world' were concatenated as 'helloworld' instead of
'hello world'. Fixed: trim each segment and join with ' '.
- CRITICAL: latestRef was updated via useEffect (async) — a speech result
arriving before the effect ran would see stale state. Fixed by updating
latestRef.current synchronously in the render body.
- WPM calculation: used sessionStats from outer closure which could be
stale. Fixed: moved update inside setSessionStats updater to always
read fresh prev state.
- recognition.stop() in finishTest was not wrapped in try/catch — could
throw if recognition was already stopped.
- TestStatus.STARTING was declared but never used; startTest now has a
clean path without dead states.
New features:
- Keyboard mode (⌨️ Keys): full keyboard typing test alongside voice,
switchable via nav toggle. Resets the test on switch.
- Real per-keystroke accuracy in keyboard mode: tracked via onKeystroke
callback from TypingArea's onKeyDown (before the input value changes),
giving accurate error counts including backspace-corrected mistakes.
- Words-typed counter in final results.
- Improved final results card: Speed, Accuracy, Words, Chars, Errors/Mode.
- Voice accuracy correctly shows 100% (voice only advances on exact match).
UX improvements:
- Duration buttons and mode toggle both use aria-pressed.
- Start button has aria-label describing the active mode.
- 'Listening…' indicator has role='status' and aria-label.
- Footer updated to emphasise privacy-first, local-only processing.
- Nav is responsive with flex-wrap for small screens.
- Restart button label simplified to 'Restart'.
- Tab-key hint uses <kbd> element for semantics.
…blink - Rename title from 'SwiftVoice' to 'SwiftType' (consistent with repo name). - Update OG/Twitter/JSON-LD titles and descriptions to mention both voice and keyboard modes, and emphasise privacy-first local processing. - Fix cursor-blink @Keyframes name to match the CSS class name (was 'blink', class was 'cursor-blink' — animation never fired). - Change @Keyframes to use 'inherit' for border-color so it works with both dark (blue-400) and light (blue-500) themes, instead of hardcoded #0071e3 which only matched the light theme. - Add 'touch-action: manipulation' on buttons to prevent 300ms tap delay and accidental double-tap zoom on mobile. - Add '-webkit-text-size-adjust: 100%' to prevent iOS font size inflation on landscape rotation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes several real bugs that affect core functionality, adds keyboard typing mode alongside voice, and improves accessibility and mobile handling — all while keeping the privacy-first, local-only architecture intact.
🐛 Bug Fixes
Critical: Speech transcript concatenated without spaces (App.tsx)
The original code joined all speech recognition segments with
''.join(''):This caused the word-matching logic to almost never fire correctly when the recognizer split the transcript across multiple segments (which happens on any pause in speech).
Critical: Stale closure on latestRef (App.tsx)
latestRefwas updated inside auseEffect, which runs asynchronously after paint. A speech recognition result arriving in that window would call the oldhandleSpeechInputwith stalequotestate — causing it to match against the previous quote.Fixed by updating
latestRef.currentdirectly in the render function body (synchronous, no effect needed). React refs are safe to write during render.TypeScript error: onInputChange required but never passed (TypingArea.tsx)
onInputChangewas a required prop in the interface butApp.tsxnever passed it. Added?to make it optional. Added companiononKeystroke?for per-keystroke accuracy.cursor-blink animation never fired (TypingArea.tsx + index.html)
The CSS declared
@keyframes blinkbut the class wascursor-blink. Name mismatch meant the cursor never blinked. Fixed by renaming to@keyframes cursor-blinkand changingborder-colortoinheritso it works with both dark and light theme border colours (was hardcoded to#0071e3).WPM could produce NaN or Infinity (App.tsx)
The WPM
useEffectran immediately whenstatuschanged toRUNNING, before any time had elapsed, producing0 / 0 = NaN. Added atimeElapsedMin <= 0guard.recognition.stop() could throw on reset (App.tsx)
resetTestcalledrecognitionRef.current.stop()without a try/catch. If recognition had already ended (e.g. silence timeout), this would throw an uncaught exception. Wrapped in try/catch.✨ New Features
Keyboard typing mode (⌨️ Keys)
Added a Voice / Keys toggle in the nav. Keyboard mode:
<input>insideTypingAreathat captures keystrokes on all platforms including mobile (withautoComplete off,autoCorrect off,autoCapitalize off,enterKeyHint='next').onKeyDown(before the value changes), so backspace-corrected errors are still counted.Real accuracy tracking in keyboard mode
Accuracy = (correct keystrokes / total keystrokes) × 100, updated live. Voice mode continues to show 100% which is correct by design (only exact word matches advance the cursor).
Richer results card
Final results now show: Speed (WPM), Accuracy, Words typed, Characters typed, and Error count (keyboard) / mode icon (voice).
WPM color coding in StatsOverlay
Formatted time display
Durations ≥ 60 s now show as
M:SS(e.g.1:02) instead of a bare second count.♿ Accessibility
aria-pressedon mode and duration toggle buttons.role='status'+aria-live='polite'on the stats overlay.aria-labelon each stat value.role='region'+aria-labelon the typing area.aria-labelon dark/light toggle, start button, and listening indicator.aria-hidden='true'.📱 Mobile
touch-action: manipulationon all buttons — eliminates 300 ms tap delay and prevents accidental double-tap zoom.-webkit-text-size-adjust: 100%prevents iOS from inflating font sizes on landscape rotation.autoCapitalize,enterKeyHint, etc.).flex-wrap.🏷️ Branding / Meta
SwiftVoice→SwiftType(consistent with repo name and project intent).Privacy
No changes to the privacy model. All speech recognition uses the browser-native Web Speech API. No data is sent to any external service. The keyboard path is fully local.