- src/lib/actions.ts: canonical ACTIONS/ACTION_LABELS/WRITING_STYLES;
context-menu styles derived (WRITING_STYLES minus Default). Replaces
the three duplicated style arrays in popup, content, and background.
- src/lib/theme.ts: canonical palette. Existing hex literals in the
entrypoints are swapped wholesale when light/dark theming lands, so
the color plumbing is only rewritten once.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
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)