Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [3.5.6] - 2026-04-26

### Fixed
- AI features no longer fail silently when the Puter.js bridge fails to confirm `BRIDGE_READY` within 20 s. The translator now flips a `bridgeFailed` flag and dispatches `skillbridge:bridgeunavailable`; content.js shows a persistent banner asking the user to refresh.
- `history.pushState` / `history.replaceState` wrappers are now idempotent. Extension reloads (auto-update, dev refresh) used to capture the previous wrapper as the "original" and stack handlers, doubling `onRouteChange` calls per SPA nav and amplifying GT load.
- Cache-cleanup alarm now actually runs in active tabs. Background sent `{ type: 'CACHE_CLEANUP' }` but the content-script handler keys on `request.action`, so the 24 h alarm was dead code. Unified on `{ action: 'cacheCleanup' }` with a matching switch case.
- Google Translate rate-limit overflow no longer leaves elements in English. `_rateLimiter` now exposes an `acquire()` method that paces the batch instead of returning the source text (which content.js silently skips); the GT batch-item failure path also returns `null` for consistency.
- Translation progress bar and verify spinners are no longer stuck when the user switches language mid-batch. `processGTQueue` is now wrapped in `try/finally` so `hideTranslationProgress`, `pruneDetachedEntries`, and the gemini-block flush always run.
- Chat history is no longer silently dropped on `QuotaExceededError`. `saveConversation` now retries the `add()` once after `pruneOldHistory` deletes the oldest 20 entries, and `pruneOldHistory` resolves on the transaction's `oncomplete` so the retry sees the freed space.

### Changed
- Drop the `tabs` permission from `manifest.json`. Both `chrome.tabs.query` and `chrome.tabs.sendMessage` rely on `host_permissions` matching the active tab — the broader `tabs` permission was unused. Removes the "read your browsing history" warning string in the CWS install prompt.

## [3.5.5] - 2026-04-19

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<img src="assets/icons/icon128.png" alt="SkillBridge" width="90" />

# SkillBridge for Anthropic Academy <!-- VERSION_START -->v3.5.5<!-- VERSION_END -->
# SkillBridge for Anthropic Academy <!-- VERSION_START -->v3.5.6<!-- VERSION_END -->

> Available in multiple languages at the [project landing page](https://heznpc.github.io/skillBridge/).

Expand Down
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>SkillBridge — AI Course Translator for <!-- LANG_COUNT_START -->32+<!-- LANG_COUNT_END --> Languages</title>
<meta name="description" content="Free Chrome extension to translate Anthropic Academy courses into <!-- LANG_COUNT_START -->32+<!-- LANG_COUNT_END --> languages with AI-powered tutoring. No API keys needed.">
<meta property="og:title" content="SkillBridge — AI Course Translator">
<meta property="og:description" content="Learn Anthropic's free AI courses in your language. Powered by <!-- VERSION_START -->v3.5.5<!-- VERSION_END -->.">
<meta property="og:description" content="Learn Anthropic's free AI courses in your language. Powered by <!-- VERSION_START -->v3.5.6<!-- VERSION_END -->.">
<meta property="og:type" content="website">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
Expand Down Expand Up @@ -255,7 +255,7 @@
<body>
<section class="hero">
<div class="container">
<div class="hero-badge"><!-- VERSION_START -->v3.5.5<!-- VERSION_END --> &middot; Open Source &middot; Free &middot; No API Key</div>
<div class="hero-badge"><!-- VERSION_START -->v3.5.6<!-- VERSION_END --> &middot; Open Source &middot; Free &middot; No API Key</div>
<h1>Learn Anthropic Courses<br>In Your Language</h1>
<p>A Chrome extension that translates Anthropic Academy courses into <!-- LANG_COUNT_START -->32+<!-- LANG_COUNT_END --> languages with an AI-powered learning assistant.</p>
<p style="font-size:13px;opacity:0.85;margin-top:-16px;">Not affiliated with, endorsed by, or sponsored by Anthropic.</p>
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export default [
TERM_PREVIEW_LABELS: 'readonly',
FLASHCARD_COURSE_MAP: 'readonly',
FLASHCARD_COURSE_SLUGS_SORTED: 'readonly',
BRIDGE_UNAVAILABLE_LABELS: 'readonly',
CODE_COMMENT_PATTERNS: 'readonly',
COMMENT_TRANSLATE_LABELS: 'readonly',
// Classes from other content scripts
Expand Down
3 changes: 1 addition & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"description": "__MSG_extDescription__",
"version": "3.5.5",
"version": "3.5.6",
"minimum_chrome_version": "120",
"author": "SkillBridge Contributors",
"homepage_url": "https://github.com/heznpc/skillbridge",
"default_locale": "en",
"permissions": [
"storage",
"tabs",
"alarms"
],
"host_permissions": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skillbridge",
"version": "3.5.5",
"version": "3.5.6",
"private": true,
"scripts": {
"test": "jest --verbose",
Expand Down
28 changes: 24 additions & 4 deletions src/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ const _rateLimiter = {
this.timestamps.push(now);
return true;
},
/**
* Wait until a slot is available, up to maxWaitMs. Returns true if acquired,
* false on timeout. Lets large batches pace naturally instead of dropping
* items into the original-English passthrough that callers can't detect.
*/
async acquire(maxWaitMs = 60000) {
const start = Date.now();
while (!this.check()) {
if (Date.now() - start > maxWaitMs) return false;
await new Promise((r) => setTimeout(r, 200));
}
return true;
},
};

// ==================== EXPONENTIAL BACKOFF FETCH ====================
Expand Down Expand Up @@ -142,7 +155,7 @@ async function handleCacheCleanup() {
try {
const tabs = await chrome.tabs.query({ url: 'https://*.skilljar.com/*' });
for (const tab of tabs) {
chrome.tabs.sendMessage(tab.id, { type: 'CACHE_CLEANUP' }).catch(() => {
chrome.tabs.sendMessage(tab.id, { action: 'cacheCleanup' }).catch(() => {
// Tab may not have content script loaded — that is fine
});
}
Expand Down Expand Up @@ -295,15 +308,22 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
const tl = gtLangCode(targetLang);

Promise.all(
texts.map((text) => {
if (!_rateLimiter.check()) return text; // skip if rate limited
texts.map(async (text) => {
// Wait for a rate-limit slot. Falling back to the original English
// text would be silently dropped by content.js (translated === original
// is treated as no-op), so we pace instead.
const ok = await _rateLimiter.acquire();
if (!ok) {
console.warn('[SkillBridge] GT rate-limit acquire timed out');
return null;
}
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sl}&tl=${tl}&dt=t&q=${encodeURIComponent(text)}`;
return fetchWithRetry(url)
.then((resp) => resp.json())
.then((data) => parseGTResponse(data, text))
.catch((err) => {
console.warn('[SkillBridge] GT batch item failed:', err.message);
return text;
return null;
});
}),
)
Expand Down
Loading
Loading