# Handoff — LexAI

## Current state

- **Outcome:** Phase 1 complete (7 workitems, 2026-03-06). Codebase reviewed 2026-07-13 (`RECOMMENDATIONS.md`). Operating-system docs + agent roster aligned to the template 2026-07-15.
- **Delivered:** working MV3 extension — selection detection (textarea/input/contenteditable), floating toolbar, background LLM proxy with OpenAI/Anthropic/Groq/OpenRouter, Options page, result modal with Replace/Copy.
- **Shipped 2026-07-15 (consolidated):** refactor series (crypto consolidation, provider adapter table, context-menu registry, dev-gated debug logs); OpenAI `max_completion_tokens` + no-temperature for reasoning models; live model listing in Options (Provider → API Key → Model); new `prompt` action + Prompt Builder (popup tabs, in-page dialog, persona/style/format/model params shared via storage); security/perf pass (plaintext-key migration, `sender.id` guard, content-script listener leak fix, non-JSON error guard); CI fix — `postinstall: wxt prepare` (CI never ran it, so `.wxt/types` was missing and typecheck failed on `import.meta.env`) plus workflow hardening. All gated: typecheck + tests + build.
- **Fix (2026-07-23, Anthropic CORS):** `src/lib/providers.ts:172` — the Anthropic chat spec now sends `anthropic-dangerous-direct-browser-access: 'true'` (the model-list path at `:276` already did). Verified present in `.output/chrome-mv3/background.js`, which is the only bundle that reaches `api.anthropic.com`. **Unresolved for the user:** the CORS error still appears in their browser, which means the running extension is older than this build (a stale service worker, or a second copy installed from the pre-fix `.output/lexai-1.0.1-chrome.zip` dated 7/15). Next diagnostic: service-worker inspector → Network → `messages` → check Request Headers.
- **Fix (2026-07-23, Groq key rejected):** the Groq spec was correct; the Options flow was not. (1) `handleSave` set `modelsError` but the render gated it on `modelsStatus === 'error'`, so both save-time guards were invisible and Save silently no-opped — now rendered whenever set (amber for guidance, red for load errors). (2) `handleProviderChange` auto-listed models with the *stored* key after a provider switch, so Groq rejected the previous provider's key ("Invalid API Key") before any Groq key was entered — now tracked via `savedKeyProvider` ref; it prompts for the new key instead of guessing. (3) `listModels` errors now use `spec.label` (`Groq error: …`, matching the chat path) instead of the raw id (`groq error: …`).
- **Root cause + fix (2026-07-23, Groq "Invalid API Key"):** a stored key had no record of the provider it was entered for. `Options.handleSave` writes `{provider, model}` **without** the key when the field is blank and one is stored, so switching to Groq and saving left the OpenAI key attached to Groq — every call, and every stored-key model list, sent it and got that provider's own rejection while the field still showed 🔒. Fix: new `keyProvider` storage field (`types.ts`, in `CONFIG_STORAGE_KEYS`) written on every save; `keyProviderMismatch()` in background.ts blocks the send on the chat, COPY_AS, and stored-key LIST_MODELS paths with an actionable message (absent `keyProvider` = pre-upgrade key, allowed); Options drops the 🔒 badge and demands a new key when the saved one belongs to another provider or comes back rejected (`keyRejected` flag from `listModels` on 401/403); `callProvider` appends "open LexAI Settings and re-enter your API key" to 401/403 only.
- **Verified (2026-07-23):** `npm run typecheck` clean, `npm test -- --run` 58/58 (new: Groq bearer auth, labelled errors, the 401 hint, `keyRejected`, `providerLabel`), `npm run build` clean → `.output/chrome-mv3/` 281.72 kB. Options-page behavior is **not** covered by unit tests — a load-unpacked check of the Groq re-entry flow is still pending.
- **Open risks (ranked):**
  1. `<all_urls>` host permission — privacy surface + CWS review blocker (TASKS #1).
  2. Key "encryption" is obfuscation (`encKey` co-located) — TASKS #2.
  3. Tests don't cover real code paths (TASKS #8), DOM replace (TASKS #10), or any Options/Popup React flow.
- **Next smallest action:** reload the unpacked extension, then in Options **re-enter the Groq key** (this stamps `keyProvider` and replaces the mis-attached key) → ↻ Load → select model → Save, and confirm a real-page action. Then the quick wins: T-03, T-06/T-09/T-15/T-16. Do T-01/T-02 before any Chrome Web Store push.
