- src/lib/types.ts: message contract (both ANALYZE_TEXT shapes preserved),
config/response types, storage-key constants.
- src/lib/messaging.ts: single safeStorageGet/safeStorageSet/safeSendMessage/
isExtensionValid implementation replacing the three divergent copies in
Options, Popup, and content. Content script keeps its refresh-toast
behavior via an onContextInvalidated callback.
- New '@lib' import alias (wxt force-overwrites '~' and '@' to srcDir, so
those cannot point at ./src); wired in wxt.config, tsconfig, vitest.
- tests/unit/messaging.test.ts: 14 unit tests over the wrappers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Uninstall zustand, tailwindcss, autoprefixer, tweetnacl-util (all
unused); README no longer advertises Tailwind/Zustand.
- Fix stale 'Tone' -> 'Explain' copy in Options.
- Add .nvmrc/engines/packageManager (Node 22 to match CI).
- wxt.config.ts reads manifest version from package.json so the two
can never drift.
- Ignore *.zip build artifacts.
- Skip the two e2e specs with literal [EXTENSION_ID] placeholders
(cannot pass; TODO references the e2e-ID-resolution task).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ci.yml: check out the PR head (or push SHA) instead of always cloning
main; PR checks now test the actual diff. Drop http.sslVerify=false.
Publish to the package registry only on push events.
- preview.yml/release.yml: use GITEATOKEN (GITEA_TOKEN is reserved by
Gitea, so these token steps were silently broken).
- deploy-chrome.yml/release.yml: parse JSON with node instead of
python3 (not installed in the container) and grep; fail loudly
instead of swallowing errors with 2>/dev/null.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selection text substrings no longer reach the host page's console in
production builds; logs only fire under 'wxt' dev serve.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- LEXAI-13: TweetNaCl secretbox encryption for API keys in Options + background
- getOrCreateEncKey() generates per-device 32-byte key stored in chrome.storage.local
- Keys saved as nonce+ciphertext (base64) under apiKeyEnc
- Backward compat: falls back to plaintext apiKey if no encrypted key found
- Lock icon 🔒 shown in Options label when key is encrypted
- LEXAI-11: Right-click context menu with Fix, Rephrase, Shorten, Expand, Tone
- Registered via onInstalled in background service worker
- Sends lexai-context-menu message to content script
- Content script listener added at bottom of main()
- LEXAI-12: Tone analysis button added to floating toolbar (teal #2dd4bf)
- Analysis-only: showModal called with null originalText = no Replace button
- Tone system prompt updated to pure analysis (no rewrite)
Root cause (dual bugs):
1. SCOPE BUG: snapStart/snapEnd/snapElement/snapRange were local vars inside
runAction() but showModal() is a sibling function — those snap vars were
never in scope inside the Replace button handler. The condition always
silently fell through, doing nothing.
2. TIMING BUG: Selection was only captured on document mouseup, which could
race against focus changes when clicking the toolbar.
Fix:
- showModal() now accepts snapStart/snapEnd/snapElement/snapRange as explicit
parameters — guaranteed in-scope, no closure ambiguity
- Each toolbar button mousedown calls captureForButton() BEFORE any focus
shift can occur (belt-and-suspenders, works even if e.preventDefault fails)
- Added console.log debug lines to Replace handler and runAction snapshot
- selectionLocked reset on Escape and overlay click (was missing)
- Use native git clone instead of actions/checkout
- Use node:22-bookworm container directly
- Remove dependency on actions/setup-node, upload-artifact
- Use curl for Gitea API calls directly
- Fixes CI failure on self-hosted runner