Skip to content

Bug fixes, browser compat, privacy messaging, and UX improvements#1

Open
hilaryduffrules-hash wants to merge 4 commits intomurdawkmedia:masterfrom
hilaryduffrules-hash:fix/improvements
Open

Bug fixes, browser compat, privacy messaging, and UX improvements#1
hilaryduffrules-hash wants to merge 4 commits intomurdawkmedia:masterfrom
hilaryduffrules-hash:fix/improvements

Conversation

@hilaryduffrules-hash
Copy link
Copy Markdown

Summary

This PR fixes several real bugs, improves cross-browser compatibility, adds clear privacy messaging, and improves error handling and mobile UX. All changes are based on a full read of every file in the repo.


Critical Bug Fixes

🐛 TypeScript type error: onInputChange required but never passed

TypingArea's interface declared onInputChange as a required prop, but App.tsx never passes it (speech handles input, not the keyboard) and the component never uses it. This was a compile-time TypeScript error. Fixed by making the prop optional (onInputChange?).

🐛 Recognition restart race condition on quote completion

When all words were matched, handleSpeechInput called recognition.stop(), which immediately triggered onend. But onend checked the stale isListening state (still true at that point) and tried to restart recognition — fighting itself. Fixed with a shouldRestartRef boolean that is explicitly set to false before any intentional stop, and re-enabled after a 150ms delay once the new quote has loaded.

🐛 Accuracy hardcoded to 100

const accuracy = 100; — the variable was set but never computed. Fixed: accuracy now tracks matchedWords / attemptedWords × 100 giving real feedback on voice recognition quality.

🐛 resetTest could repeat the same quote

getRandomQuote() was called without arguments, so it could pick the current quote again. Fixed: now passes quote.text to exclude it from the random pool.

🐛 alert() used for all errors

Browser alert() calls for mic denial and unsupported browsers block the UI and have terrible UX on mobile. Replaced with inline, dismissable error banners.


Browser Compatibility

Firefox doesn't support Web Speech API

Firefox has never shipped the Web Speech API. The app previously silently fell back to an alert() pop-up. Now:

  • Detects unsupported browsers on mount (not just on click)
  • Shows a clear amber warning banner with browser-specific guidance
  • Disables the Start button with disabled attribute
  • Tells Firefox users what to use instead (Chrome/Edge)

Safari support improvements

  • Added -webkit-backdrop-filter for nav blur in Safari
  • Safari-specific experimental feature hint in the unsupported browser message
  • -webkit-font-smoothing for correct font rendering

Explicit error handling for all SpeechRecognitionError types

Error Before After
not-allowed alert() Inline banner + instructions to re-enable mic in browser settings
audio-capture console.error Inline banner: no mic detected
network ignored Inline banner: network error
no-speech ignored Silent (let onend restart naturally)
aborted ignored Intentional stop, ignore

Mobile Support

  • Detect mobile UA and show "Tap Restart to reset" instead of the useless "Press Tab to reset" (Tab doesn't exist on mobile keyboards)
  • Added overscroll-behavior: none to prevent iOS elastic overscroll ruining the layout
  • Added viewport-fit=cover for proper iPhone notch handling

Privacy Messaging

Added a persistent privacy badge visible throughout the session:

🛡️ 100% private — all speech processing happens locally in your browser. No audio ever leaves your device.

This is the core value prop of SwiftVoice and it wasn't communicated anywhere in the UI.


index.html: Stale SwiftType metadata fixed

The HTML file was forked from a SwiftType typing test template and still contained:

  • Title: "SwiftType | Elegant Typing Test"
  • OG/Twitter meta pointing to swifttype.pages.dev
  • JSON-LD schema with name: "SwiftType"
  • Description about "typing test", not voice typing
  • Light background default contradicting dark-first UI

All updated for SwiftVoice.


README: Complete rewrite

The README was a copy of an AI Studio template that instructed users to set a GEMINI_API_KEY. This app uses zero external APIs. Rewrote with:

  • Accurate description + privacy explanation
  • Browser compatibility table
  • Correct setup instructions (just npm install && npm run dev, no env vars needed)
  • Feature list

Minor UX

  • Microphone emoji on the Start Speaking button for clarity
  • aria-label on dark/light toggle for accessibility
  • Proper Unicode ellipsis on "Listening…"
  • WPM poll interval: 100ms → 250ms (reduces visual jitter)
  • Cursor blink animation class applied via cursor-blink CSS class

The TypingArea component declared onInputChange as a required prop in its
TypeScript interface, but:
1. The parent App.tsx never passes it (speech handles input, not keyboard)
2. The component body never uses it

Making it optional (onInputChange?) eliminates the TypeScript type error
and properly reflects that this is a voice-driven app.

Also: apply cursor-blink CSS class to the active cursor span for a proper
blinking animation instead of a static border.
Critical Bug Fixes:
- Fix recognition restart race condition on quote completion. Previously,
  calling recognition.stop() inside handleSpeechInput would immediately
  trigger onend, which checked the stale isListening state (still true)
  and restarted recognition — fighting the intentional stop. Fixed with
  shouldRestartRef that is explicitly set to false before stopping and
  re-enabled 150ms after the next quote loads.

- Fix hardcoded accuracy=100. Accuracy now tracks correct matched words
  vs total attempted spoken words, giving real feedback.

- Fix resetTest not excluding current quote. resetTest now passes the
  current quote text to getRandomQuote() so you never immediately see
  the same quote again.

- Fix alert() for errors. All error states now render inline UI banners
  instead of blocking browser alerts — much better UX.

Browser Compatibility:
- Add getSpeechRecognitionClass() helper that checks both
  window.SpeechRecognition and window.webkitSpeechRecognition
- Detect unsupported browsers on mount and show a clear banner with
  browser-specific guidance (Chrome/Edge recommended, Firefox unsupported,
  Safari experimental feature note)
- Handle all SpeechRecognitionError types explicitly:
  not-allowed/permission-denied → show how to re-enable mic in browser settings
  audio-capture → no mic connected message
  network → network error message
  no-speech → silent, let onend restart naturally
  aborted → intentional stop, ignore

Mobile Support:
- Detect mobile user agent and show 'Tap Restart to reset' instead of
  'Press Tab to reset' (Tab key doesn't exist on touch keyboards)
- Add viewport-fit=cover and overscroll-behavior:none (in index.html) for
  proper mobile layout

Privacy Messaging:
- Add a persistent privacy badge at the bottom of main content:
  '100% private — all speech processing happens locally in your browser.
   No audio ever leaves your device.'
- Disable the Start Speaking button when browser is unsupported

UX:
- Add microphone emoji to Start Speaking button for clarity
- Add aria-label to dark/light mode toggle button
- Ellipsis on 'Listening…' (proper Unicode ellipsis)
- Update WPM polling interval from 100ms to 250ms (reduces jitter)
The index.html was copied from a SwiftType template and still contained:
- All og: and twitter: meta tags referencing SwiftType name/titles
- URLs pointing to swifttype.pages.dev (wrong domain)
- JSON-LD schema with name 'SwiftType'
- Description mentioning 'typing test' not voice typing
- Body background defaulting to light (#fbfbfd) contradicting dark-first UI

Changes:
- Updated all meta title/description/og/twitter tags for SwiftVoice
- Updated JSON-LD schema: name, description, featureList
- Corrected og:url / twitter:url placeholder to swiftvoice.murdawkmedia.com
- Added Permissions-Policy header for microphone access
- Added -webkit-backdrop-filter for Safari blur support
- Added overscroll-behavior: none to prevent iOS elastic overscroll
- Added -webkit-font-smoothing for proper macOS/iOS font rendering
- Set body background to dark default (#161617) to match default theme
- Removed stale GEMINI_API_KEY reference comment from importmap
…nsense

The README was a copy of an AI Studio template that:
- Instructed users to set a GEMINI_API_KEY (this app uses zero external APIs)
- Did not describe the app at all
- Had no browser compatibility table
- Had no privacy explanation

Rewrote with:
- Clear description of what SwiftVoice actually does
- Privacy explanation (Web Speech API, local processing)
- Browser compatibility table (Chrome/Edge/Safari/Firefox)
- Correct run instructions (just npm install && npm run dev, no env vars)
- Build instructions
- Feature list
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