fix: v3.5.6 production hotfixes — 6 audit-surfaced bugs + drop tabs permission#92
Merged
fix: v3.5.6 production hotfixes — 6 audit-surfaced bugs + drop tabs permission#92
Conversation
A deep production-readiness audit (and four direct re-reads to verify) found
six bugs that get worse at scale. Two are user-visible right now; the rest
amplify with traffic.
1. Bridge timeout silent fail (CRITICAL)
`_injectPageBridge` resolved the readiness promise even on timeout, with
`isReady` left false. Every later `_sendRequest` rejected "Bridge not
ready" forever — tutor and Gemini block translation died until reload, no
UI signal. Now flips a `bridgeFailed` flag and dispatches
`skillbridge:bridgeunavailable`; content.js renders a persistent banner
asking the user to refresh.
2. pushState/replaceState wrapper stacking (CRITICAL)
The history monkey-patches had no idempotency guard, so an extension
reload (auto-update, dev refresh) captured the previous wrapper as the
"original" and stacked. Each reload doubled `onRouteChange` per nav,
amplifying GT load. Guard with `__sb_wrapped__`.
3. Cache-cleanup alarm dead code (HIGH)
Background sent `{ type: 'CACHE_CLEANUP' }` but the content-script
handler keys on `request.action`. The 24h alarm reached the default
"Unknown action" branch and silently no-op'd in long-pinned tabs. The
page-load fallback still ran, so the bug was hidden — but the alarm
itself is now unified on `{ action: 'cacheCleanup' }` with a real case.
4. GT rate-limit dropped translations (HIGH)
`googleTranslateBatch` returned the original English text on rate-limit,
which content.js silently skipped because `translated === source` looks
like a no-op. On large lessons over 120 strings/min users got a
half-translated page. Replaced with a polling `_rateLimiter.acquire()`
that paces the batch instead. Failed items now return `null` for
consistent skip-and-retry semantics.
5. Progress UI stuck on language switch (HIGH)
Mid-batch `gtGeneration` mismatches early-returned past
`hideTranslationProgress` and `pruneDetachedEntries`, leaving the
progress bar and verify spinners on screen. Wrapped the loop in
try/finally so cleanup always runs. Term-preview only fires when the
generation is still current.
6. Chat history quota silently dropped messages (MEDIUM)
`pruneOldHistory` deleted the oldest 20 entries on `QuotaExceededError`
but never retried the failed `add()`, losing the conversation. Now
`saveConversation` retries once after pruning, and `pruneOldHistory`
resolves on `tx.oncomplete` so the retry sees the freed space.
Plus one preventative cleanup:
- Drop the `tabs` permission from manifest. Both `chrome.tabs.query` and
`chrome.tabs.sendMessage` work via `host_permissions` matching the active
tab. The broader `tabs` permission was unused and triggered the "read
your browsing history" string in the CWS install prompt.
Version 3.5.5 → 3.5.6. Tests 309/309 pass; lint, format, selector health,
dicts, bg-sync, glossary, translate validate, firefox build, bundle build
all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 26, 2026
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
Deep production-readiness audit (delegated + four direct re-reads to verify) surfaced six bugs that get worse as user count grows. Two are user-visible now; the rest amplify with traffic. Plus one preventative permission cleanup.
isReady=false(translator.js:683)history.pushStatewrapper stacks across extension reloads (content.js:1259)onRouteChange→ 2× GT loadtype:, content.js switches onaction:(background.js:145)processGTQueueearly-returns skip cleanup (content.js:805)pruneOldHistorydoesn't retry failedadd()(sidebar-chat.js:527)QuotaExceededErrortabspermission unused (manifest.json:11)Notable design choices
isReady=falseand surface a banner rather than silently rejecting requests. Banner is i18n'd in 10 premium languages (BRIDGE_UNAVAILABLE_LABELS).__sb_wrapped__marker on the wrapper itself, not a closure flag — survives module re-execution._rateLimiter.acquire(maxWaitMs=60s). Large batches now pace naturally to 120/min instead of dropping. Failed items also returnnullfor consistent semantics.try/finallywraps the whole batch loop. Term-preview gated ongtGeneration === myGenerationso it doesn't fire on cancellation (the new generation triggers its own).pruneOldHistorynow resolves ontx.oncompleteso the retry sees the freed space (previously it kicked off an async cursor and returned immediately).Verification (local)
npm run lint/format:check/check:selectors/check:dicts/check:sync/glossary/validate— all greennpm run build:firefox/build:bundle— both pass; bundle 198.8 KB → 113.9 KB (43% reduction)tabsremoval; bothchrome.tabs.queryandchrome.tabs.sendMessageconfirmed to work viahost_permissionsalone (Chrome docs + popup.js code path)Test plan
🤖 Generated with Claude Code