From 13f38beb44fe71c28852eafb93d5f8ff061866b8 Mon Sep 17 00:00:00 2001 From: froggychips Date: Tue, 19 May 2026 11:25:20 +0300 Subject: [PATCH] Docs: CHANGELOG, ARCHITECTURE, CONTRIBUTING, TROUBLESHOOTING, CWS_REVIEW (Phase 8 of code review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Документации почти не было: README + PRIVACY + SECURITY и пара docs/LAUNCH/RECORDING.md. Для CWS submission и внешних контрибьюторов нужен минимальный набор. - CHANGELOG.md (Keep a Changelog format): Unreleased секция содержит всё что прошло через PR-1..PR-7 — security/security/i18n/providers/ selectors/SW-keepalive/MCP-cache/tooling. CWS reviewers смотрят changelog между версиями. - CONTRIBUTING.md: layout проекта, local dev, code style, как добавлять провайдеры / DOM-стратегии / i18n-строки, commit/PR conventions, bug-report format с обязательным dump tta_selector_health. - docs/ARCHITECTURE.md: диаграмма data-flow content_script <-> SW <-> AI providers / MCP, описание компонентов, persistence layout (storage.sync / .local / .session / in-memory). "Что лежит off-disk" явно: ничего, no telemetry. - docs/TROUBLESHOOTING.md: pathways когда X сломался, ключ битый, SW засыпает, rate-limit, MCP offline. Каждая секция указывает на конкретные DevTools-команды и storage-ключи для диагностики. - docs/CWS_REVIEW.md: внутренний чеклист submission — manifest hygiene, code checks, store-listing assets, permission justifications таблицей (для соответствующего поля в dev console). - README.md: секция Releasing переписана с ручного zip на npm run package. Ссылки на CONTRIBUTING/ARCHITECTURE/TROUBLESHOOTING. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 38 ++++++++++++++++ CONTRIBUTING.md | 93 ++++++++++++++++++++++++++++++++++++++ README.md | 17 +++---- docs/ARCHITECTURE.md | 98 +++++++++++++++++++++++++++++++++++++++++ docs/CWS_REVIEW.md | 76 ++++++++++++++++++++++++++++++++ docs/TROUBLESHOOTING.md | 78 ++++++++++++++++++++++++++++++++ 6 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/CWS_REVIEW.md create mode 100644 docs/TROUBLESHOOTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c9dee2b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to TweAI are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project loosely follows [SemVer](https://semver.org/). + +## [Unreleased] + +### Tooling +- `package.json` + `eslint.config.js` + `.prettierrc.json` (PR #9) +- `tools/build.mjs` packages `dist/` and `tweai-v.zip` without `tweai-mcp-server/` or `docs/` +- `.github/workflows/checks.yml` runs lint + format + build on every PR +- `.github/workflows/release.yml` builds and publishes a GitHub release with the zip when a `v*` tag is pushed + +### Architecture +- `selectors.js` introduces `window.TTASelectors` with multi-strategy DOM lookups (testid → semantic → structural) and a ring-buffer health counter in `chrome.storage.local.tta_selector_health` (PR #7) +- Provider abstraction: `callOpenAI` / `callGrok` / `callGemini` merged into a `PROVIDERS` registry + `aiFetch()` with `AbortController` timeout and 429/5xx retry with exponential backoff (PR #6) +- Service worker keep-alive via `chrome.alarms` while AI requests are in-flight; MCP responses cached in-memory for 5 min (PR #8) +- Gemini API key is now sent via `x-goog-api-key` header instead of `?key=` URL param + +### Internationalization +- `_locales/en/messages.json` and `_locales/ru/messages.json` cover ~110 UI keys; `manifest.default_locale: "en"` (PR #5) +- `options.html` strings bound via `data-i18n` / `data-i18n-placeholder` / `data-i18n-html` / `data-i18n-aria-label` +- Built-in personas now ship with English `label` / `hint` (fallback if `chrome.i18n` is unavailable) + +### Security +- `chrome.runtime.onMessage` validates `sender`: only own extension pages and `*.x.com` / `*.twitter.com` frames are allowed +- `http://localhost/*` / `http://127.0.0.1/*` moved from `host_permissions` to `optional_host_permissions` +- Dev-logger no longer reads `?tta_debug=1` or `localStorage 'tta_debug'` (both forgeable from x.com) +- `boot()` runs only in the top frame +- `document.execCommand('insertText')` consolidated into a single helper with InputEvent → execCommand → direct-assignment fallback chain +- Persona hint renders via DOM API (`replaceChildren`, `textContent`) instead of `innerHTML` + +## [1.8.1] - 2026-05-19 + +Security & CWS hygiene patch (see [release notes](https://github.com/froggychips/tweai/releases/tag/v1.8.1)). + +## [1.8] - 2026-05-17 + +Open-source launch. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..59345da --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Contributing to TweAI + +Thanks for considering a contribution! TweAI is a single-developer side-project that turned out useful enough to open up. PRs are welcome but I review at a hobby cadence. + +## Project layout + +``` +. +├── manifest.json # MV3 manifest +├── background.js # service worker — AI calls, MCP, message dispatch +├── content_script.js # UI injection on x.com / twitter.com +├── selectors.js # window.TTASelectors with fallback DOM strategies +├── dev-logger.js # opt-in floating log overlay (dev/debug) +├── ad-blocker.js # promoted-post hider (separate content_scripts entry) +├── profile-scraper.js # author profile sniffer +├── options.html / .js / .css +├── styles.css # shared CSS injected into x.com +├── _locales/{en,ru}/messages.json +├── tools/build.mjs # dist/ + zip packager +├── tweai-mcp-server/ # Node MCP gateway (separate package; not bundled) +└── docs/ + ├── ARCHITECTURE.md + ├── TROUBLESHOOTING.md + ├── CWS_REVIEW.md + └── ... +``` + +`tweai-mcp-server/` is a separate Node project for an optional local gateway. It has its own `package.json` and is excluded from the extension zip. + +## Local development + +```bash +git clone https://github.com/froggychips/tweai.git +cd tweai +npm install +npm run lint +npm run format +npm run package # produces dist/ + tweai-v.zip +``` + +Load `dist/` (or the repo root) via `chrome://extensions` → Developer mode → Load unpacked. + +After every file change in the extension, click the reload icon on the TweAI card in `chrome://extensions`. Content scripts also need a tab reload on x.com to re-inject. + +## Style + +- Prettier handles formatting (`npm run format:fix` to apply). 100-col, single quotes, trailing commas, LF. +- ESLint is intentionally light: it catches typos and unused vars but doesn't enforce style. +- Comments: explain *why* something is non-obvious, not *what* the code does. Skip comments for code that reads cleanly on its own. + +## DOM selectors + +If you touch DOM lookups on x.com, **add or update a strategy in `selectors.js`** rather than embedding `data-testid="…"` in `content_script.js` directly. The selector module: + +1. tries `data-testid` (level 1), +2. falls back to ARIA/semantic attributes (level 2), +3. then to structural selectors (level 3), +4. records every call into a ring buffer in `chrome.storage.local.tta_selector_health`. + +This lets us tell *which* lookup broke after an X redesign without staring at silent failures. + +## i18n + +Strings that the user sees go through `_locales/{en,ru}/messages.json`. In HTML, use `data-i18n="key"` (or `data-i18n-placeholder` / `data-i18n-html` / `data-i18n-aria-label`). In JS, use the `i18n('key')` helper in `options.js` or `tta_i18n('key')` in `content_script.js` — both have an English fallback if the message is missing. + +New strings: add the key to both `en/messages.json` and `ru/messages.json`. Don't ship hardcoded Russian text in default-locale UI — that's a Chrome Web Store blocker. + +## Provider additions + +To add a new AI provider, register it in the `PROVIDERS` registry in `background.js` with `keyField`, `label`, `buildRequest(apiKey, model, messages, temperature)` and `parseResponse(j)`. The dispatcher `callAI()` picks it up automatically. Make sure the new key field is also in `baseDefaults` and surfaced in `options.html`. + +## Commits & PRs + +- One logical change per PR. If something feels like two PRs, it is. +- Title imperative: "Add X" / "Fix Y" / "Refactor Z". Body explains *why*, not *what*. +- For DOM changes, paste a screenshot of x.com before/after. +- For provider/API changes, describe how you verified the wire format (DevTools Network screenshot is fine). +- CI must be green (lint, format, build). Don't `--no-verify` past it; ask if the hook is wrong. + +## Reporting bugs + +[GitHub Issues](https://github.com/froggychips/tweai/issues). Include: + +- Chrome version +- TweAI version (`manifest.json` or `chrome://extensions`) +- Reproduction steps +- DevTools console output if there's a JS error +- Output of "Run health check" in TweAI options +- For DOM-breakage reports: also dump `chrome.storage.local.get('tta_selector_health', console.log)` so we see which selector level fell over + +## Security issues + +See [SECURITY.md](SECURITY.md). Don't open public issues for vulnerabilities. diff --git a/README.md b/README.md index e4c13d0..a208744 100644 --- a/README.md +++ b/README.md @@ -108,21 +108,22 @@ The extension wraps tweet text in delimiters and explicitly instructs the model ## Releasing -Build a Chrome Web Store zip: - ```bash -zip -r tweai-v1.7.zip . \ - -x '.git/*' '.gitignore' 'docs/*' 'env.json' \ - '*.md' 'env.template.json' 'package.json' 'node_modules/*' +npm install +npm run lint +npm run format +npm run package # → dist/ + tweai-v.zip ``` -Launch-day copy (Chrome Store listing, Product Hunt, tech-twitter thread, HN, Reddit) lives in [`docs/LAUNCH.md`](docs/LAUNCH.md). Demo GIF instructions are in [`docs/RECORDING.md`](docs/RECORDING.md). +`tools/build.mjs` builds the CWS-ready zip with the right inclusions (no `tweai-mcp-server/`, no `docs/`). The same script runs in [`.github/workflows/release.yml`](.github/workflows/release.yml) — pushing a `v*` tag uploads a GitHub release with the zip attached automatically. + +CWS submission checklist: [`docs/CWS_REVIEW.md`](docs/CWS_REVIEW.md). Launch-day copy: [`docs/LAUNCH.md`](docs/LAUNCH.md). Demo GIF: [`docs/RECORDING.md`](docs/RECORDING.md). ## Contributing -PRs welcome. Run `npx --yes web-ext lint --source-dir .` before opening one — the only acceptable lint output is the 2 Firefox-specific errors and 2 warnings (we target Chrome MV3 only). +See [CONTRIBUTING.md](CONTRIBUTING.md) for project layout, local dev, and PR conventions. Architecture overview lives in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). If something on x.com stops working, start with [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md). -The project has zero runtime dependencies. Source is plain MV3 JavaScript, no bundler, no minifier. +The project has zero runtime dependencies. Source is plain MV3 JavaScript; only `eslint` and `prettier` ship as dev-deps. ## Contact diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3ab587d --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,98 @@ +# Architecture + +A bird's-eye view of how TweAI's moving parts fit together. The goal here is to give a maintainer enough mental model to find the right file when something breaks. + +## Top-level flow + +``` + ┌────────────────────┐ + user on x.com│ content_script.js │ (per-tab, isolated world) + │ + selectors.js │ + │ + dev-logger.js │ + │ + ad-blocker.js │ + │ + profile-scraper │ + └────────┬───────────┘ + │ chrome.runtime.sendMessage + ▼ + ┌────────────────────┐ + │ background.js │ service worker + │ (MV3 SW) │ + └────┬───────────┬───┘ + │ │ + AI providers │ │ MCP gateway (optional) + ┌─────────────────┴┐ ┌┴──────────────────────┐ + │ api.openai.com │ │ 127.0.0.1:/... │ + │ api.x.ai (Grok) │ │ tweai-mcp-server/ │ + │ generativelang… │ │ │ + │ (Gemini) │ └───────────────────────┘ + └──────────────────┘ +``` + +## Components + +### Service worker (`background.js`) + +- **Settings store** (`baseDefaults` + `chrome.storage.sync`) +- **Persona prompts** (`PERSONAS` constant) +- **Provider registry** — `PROVIDERS = { openai, grok, gemini }`. Each entry has `keyField`, `label`, `buildRequest`, `parseResponse`. The dispatcher `callAI(provider, model, messages, temperature)` picks the right entry by name. +- **`aiFetch()`** — fetch wrapper with `AbortController` timeout (30s default) + exponential backoff retry on 429/5xx. +- **Token budgeting** (`checkBudget` / `addUsage`) — daily quota stored under `usage:YYYY-MM-DD` in `chrome.storage.local`. +- **MCP client** — `mcpFetch` with 5-min in-memory cache; `mcpGetProfile`, `mcpGetRecentTweets`. +- **SW keep-alive** — `chrome.alarms` registered while `inflightCount > 0`; `chrome.storage.session.tta_inflight_requests` tracks pending calls for restart visibility. +- **Message handlers** (`handlers` object) — `TTA_GET_PREFS`, `TTA_TRANSLATE_TWEET`, `TTA_EXPLAIN_TWEET`, `TTA_GENERATE_REPLY`, `TTA_LIST_PERSONAS`, `TTA_TEST_KEY`, `TTA_HEALTH_CHECK`, `TTA_MCP_*`, etc. Each handler is `async msg => result`; the listener validates `sender` origin before dispatch. + +### Content script (`content_script.js`) + +Injected on `x.com` / `twitter.com` at `document_start`. Runs only in the top frame (nested iframes skip `boot()`). + +- **`boot()`** wires `MutationObserver` for the timeline, plus `scroll` / `popstate` / `visibilitychange` listeners (all funnel into `scheduleScan`). +- **`scan()`** finds every `
` and DM composer and attaches UI (translate label, AI explain/reply submenu, compose box, etc). +- **DOM lookups go through `window.TTASelectors`** — see `selectors.js`. Never embed `data-testid="…"` directly in this file. +- **AI calls** go to `background.js` via `chrome.runtime.sendMessage({ type: 'TTA_…' })`. The content script never talks to AI providers directly. + +### `selectors.js` + +`window.TTASelectors` is the only place that knows X's DOM. Each function tries: + +1. `data-testid` (level 1 — precise, fragile), +2. ARIA / `lang` / `dir` attributes (level 2 — semantic), +3. structural CSS path (level 3 — last resort). + +Every call records success level into a local ring buffer, persisted to `chrome.storage.local.tta_selector_health` for diagnostics. See [TROUBLESHOOTING.md](TROUBLESHOOTING.md#dom-selectors-have-degraded) for how to read it. + +### Options page (`options.html` + `options.js`) + +Single page, no modules. Reads/writes `chrome.storage.sync` via `storageGet` / `storageSet`. UI text comes from `_locales//messages.json` via `data-i18n*` attributes and `applyI18n()` on `DOMContentLoaded`. + +Onboarding wizard (`#tta-onboarding`) overlays on first run if `onboardingDone` is unset. + +### Dev tooling + +- `dev-logger.js` — floating log overlay; opt-in via `chrome.storage.local.ttaDebugLogs` or unpacked dev build (no `update_url`). +- `profile-scraper.js` — listens for `history.pushState` to detect profile navigation, scrapes basic data, stores in `chrome.storage.local.profileData`. +- `ad-blocker.js` — separate content script entry; reads `chrome.storage.local.adBlockerEnabled` and removes promoted posts. + +### MCP gateway (optional, separate repo folder) + +`tweai-mcp-server/` is a tiny Node + Express service that wraps X's GraphQL endpoints. The extension talks to it via `mcpFetch` when `prefs.mcpUrl` is set. The fallback chain in `content_script.js` is **DOM scan → MCP (background-side cached) → local storage cache**. + +The MCP server is excluded from the Chrome Web Store package — users who want it install it themselves. + +## Data persistence + +| Where | What | +|---|---| +| `chrome.storage.sync` | User settings: API keys (encrypted by Chrome), models, persona, custom personas, language, MCP URL | +| `chrome.storage.local` | Token usage per day (`usage:YYYY-MM-DD`), profile cache, ad-blocker stats, dev-logger toggle, selector-health ring buffer | +| `chrome.storage.session` | In-flight request tracker (cleared on SW restart) | +| In-memory (SW) | MCP response cache (5 min TTL), inflight counter | + +## What lives off-disk + +Nothing. There is no backend server, no telemetry, no analytics. The extension's only outbound calls are: + +- The AI provider you chose (OpenAI / Grok / Gemini), +- Google Translate (only if you enabled that path with a key), +- Your own MCP server (only if you configured `mcpUrl`). + +If you see TweAI calling anything else in DevTools Network, that's a bug — please [file an issue](https://github.com/froggychips/tweai/issues). diff --git a/docs/CWS_REVIEW.md b/docs/CWS_REVIEW.md new file mode 100644 index 0000000..d11c3ad --- /dev/null +++ b/docs/CWS_REVIEW.md @@ -0,0 +1,76 @@ +# Chrome Web Store submission checklist + +Internal checklist before pushing a new version to the Chrome Web Store. Walk through this top to bottom; every item should be either ✅ or have a comment on why it's skipped. + +## Pre-submission + +### Manifest hygiene + +- [ ] `manifest_version: 3` +- [ ] `version` bumped from previous release (numeric SemVer, e.g. `1.8.1`) +- [ ] `default_locale: "en"` set; `_locales/en/messages.json` exists +- [ ] `name` and `description` reference `__MSG_*__` not hardcoded text +- [ ] `host_permissions` contains only what the extension actually calls +- [ ] `optional_host_permissions` used for everything user-toggled (MCP, localhost) +- [ ] No `` in matches or host_permissions +- [ ] Icons present at 16/48/128 px in `icons/` and referenced from `action` + top-level `icons` + +### Code + +- [ ] `npm run lint` passes (no errors) +- [ ] `npm run format` passes +- [ ] `npm run package` succeeds; resulting zip is < 10 MB +- [ ] Test the zip in `chrome://extensions` → Load unpacked from `dist/` +- [ ] No `console.log` spam in production paths (`dev-logger` is opt-in only) +- [ ] No hardcoded API keys, tokens, or URLs to dev infrastructure + +### Documentation + +- [ ] `CHANGELOG.md` has an entry for the new version +- [ ] `README.md` install instructions still work +- [ ] `PRIVACY.md` accurately describes data flow for any new features +- [ ] `SECURITY.md` threat model matches reality + +### Store listing assets + +- [ ] **At least one screenshot**, 1280×800 or 640×400 PNG/JPEG, showing TweAI in action on x.com (CWS rejects without this) +- [ ] Promotional tile 440×280 (optional but recommended) +- [ ] Updated short description (132 chars max) +- [ ] Updated long description (up to 16,384 chars; usually mirrors README "Features" section) +- [ ] **Privacy practices form** filled out in the dev dashboard: + - Does not collect personally identifiable information ✅ + - Does not collect health information ✅ + - Does not collect financial info ✅ + - Authentication info: API keys stored locally only (declare this) + - User activity: not collected + - Web content: tweet text is sent to user-configured AI provider only + +### Permissions justification + +For every permission in `manifest.json`, have a 1-sentence reason ready (CWS asks for this): + +| Permission | Justification | +|---|---| +| `storage` | Persist user settings, API keys, custom personas, token usage | +| `scripting` | Required for `chrome.scripting` calls from background script | +| `activeTab` | Quick action on the current X tab without `` | +| `webNavigation` | Detect SPA navigation on x.com to refresh injected UI | +| `clipboardWrite` | Copy generated replies to clipboard on user action | +| `tabs` | Open options page; iterate X tabs to broadcast settings updates | +| `alarms` | Keep service worker alive during long AI requests (>30s) | +| host_permissions: `api.openai.com`, `api.x.ai`, `generativelanguage.googleapis.com` | The user's AI provider endpoints | +| host_permissions: `x.com`, `*.x.com`, `twitter.com`, `*.twitter.com` | Inject UI on the target sites | +| optional `127.0.0.1`, `localhost` | User-toggled MCP gateway; requested at runtime if enabled | + +## Submission + +- [ ] Upload the zip from `npm run package` +- [ ] Fill out all required listing fields +- [ ] Submit for review + +## Post-submission + +- [ ] Tag the release in git: `git tag v && git push origin v` +- [ ] CI release workflow attaches the zip to a GitHub release automatically +- [ ] Monitor [CWS dev dashboard](https://chrome.google.com/webstore/devconsole/) for review feedback (typically 1–3 business days) +- [ ] Once approved, update README install link to the CWS listing diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..9c9471d --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,78 @@ +# Troubleshooting + +If TweAI suddenly stops working, walk through these in order. Most issues fall into one of three buckets: **X changed its DOM**, **API key issue**, or **the service worker fell asleep**. + +## First aid: Run health check + +`chrome://extensions` → TweAI → Details → Extension options → scroll to **Diagnostics** → **Run health check**. It will probe each configured API key and report what's reachable. If everything is green here but the UI on x.com is dead, jump to "DOM selectors have degraded" below. + +## TweAI's buttons don't appear on x.com + +1. **Check the URL.** TweAI matches `https://x.com/*`, `https://*.x.com/*`, `https://twitter.com/*`. If you're on a subdomain that isn't whitelisted, nothing will inject. +2. **Reload the tab.** Content scripts don't retroactively inject into already-open tabs after an extension update. +3. **Top-frame guard.** Since v1.8.1, TweAI runs only in the top frame. If x.com is somehow embedded in an iframe (unusual), TweAI won't load there. +4. **DOM selectors have degraded.** See below. + +## DOM selectors have degraded + +X periodically renames `data-testid` attributes. TweAI has fallback strategies (semantic and structural) but they're noisier and may still miss. + +**Diagnose:** + +1. Open DevTools on `x.com`. +2. In the console: `chrome.storage.local.get('tta_selector_health', console.log)`. +3. You'll see counts like `tweetText: { l1: 200, l2: 0, l3: 0, miss: 0 }`. Healthy state is `l1 >> 0` everywhere. If you see `miss > 0` on a key consistently, that selector strategy is failing. + +**Fix:** + +- If this is your repo: update the relevant `find*` function in `selectors.js`, add a new fallback strategy at level 2 or 3. +- If you're a user: open a [GitHub issue](https://github.com/froggychips/tweai/issues) with the output of the `tta_selector_health` dump. + +## "Daily token budget exceeded" + +You hit the daily cap configured in Options → Advanced → Spending limit. + +- Reset the counter: Options → Spending limit → **Reset counter**. +- Or raise the limit (or set it to `0` for unlimited). + +## API key looks invalid / 401 errors + +- **OpenAI**: keys start with `sk-`. Generate at [platform.openai.com/api-keys](https://platform.openai.com/api-keys). +- **Grok (xAI)**: keys start with `xai-`. Generate at [console.x.ai](https://console.x.ai). +- **Gemini**: keys start with `AIza…`. Generate at [aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey). + +After pasting, run the key test in Options → Provider card → Configure. + +## Service worker dies during long requests + +Symptom: a slow AI request (>30s) sometimes returns `Generation failed` silently. + +- Since v1.8.1+, TweAI uses `chrome.alarms` to keep the service worker alive while AI calls are in-flight. If you still see this, check `chrome://serviceworker-internals/` — find the TweAI worker and look at the "Status". +- A failed alarm-keepalive in Chrome is usually a deeper Chrome bug; retry the request once. + +## Rate-limit (429) errors + +`aiFetch` retries 429/5xx automatically twice with exponential backoff (~400ms → 1.6s). If you're consistently hitting 429: + +- Check provider quota dashboard. +- For Gemini: free tier is 1M tokens/day, but RPM (requests per minute) is also throttled — auto-translate of the whole timeline can burst. + +## MCP gateway shows offline + +- Options → Advanced → For developers → MCP Gateway → **Test**. +- Confirm `mcpUrl` matches what your local MCP server is listening on. +- TweAI 1.8.1+ moved localhost into `optional_host_permissions`. The first time you set an MCP URL, Chrome will prompt you to grant `http://127.0.0.1/*` permission — accept it. If you didn't, the request will fail without a clear error. + +## Translation appears in the wrong language + +- Options → Translate tweets → **Translate to**. `Browser language (auto)` resolves to your Chrome UI language; pick an explicit target if you've set Chrome to a language you don't read. + +## How do I file a useful bug report + +Run health check, capture its output, and include: + +- Chrome version (`chrome://version`) +- TweAI version (manifest.json or extensions page) +- Steps to reproduce +- DevTools Console output (filter to "TweAI" or "TTA") +- `chrome.storage.local.get('tta_selector_health', console.log)` dump if X UI looks broken