- Prompt Builder: 12 named patterns (Direct/Reasoning/Agentic + Auto), live per-pattern hints in popup and in-page dialog, legacy promptStyle migration, and a 2048-token floor fix so longer patterns stop getting truncated. - CHANGELOG.md added (Keep a Changelog + SemVer); release.yml now builds the Gitea release body from the matching version section, with a generic fallback, plus a zip-root fix so manifest.json sits at the archive root. - package.json/package-lock.json bumped to 1.1.0; CLAUDE.md documents the CHANGELOG-gated release process.
89 lines
6.9 KiB
Markdown
89 lines
6.9 KiB
Markdown
# Decisions — LexAI
|
|
|
|
> Lightweight ADRs. One entry per material decision: what, why, what was rejected, when. Newest at top. Supersede rather than rewrite.
|
|
|
|
## Log
|
|
|
|
### D-2026-03-06-01 — BYO-LLM-key, no backend
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Hosted writing assistants cost a subscription and route user text through a third party. Target users already hold LLM API keys.
|
|
- **Decision:** No LexAI server. The background service worker calls the user's chosen provider (OpenAI/Anthropic/Groq/OpenRouter) directly with the user's key.
|
|
- **Alternatives considered:** A thin proxy backend (rejected: adds cost, privacy surface, and an account system); on-device browser AI only (rejected: too limited across providers — noted as a Proofly reference pattern).
|
|
- **Consequences:** Zero server cost and strong privacy story; shifts key handling and CORS entirely into the extension; no server-side rate limiting or abuse controls.
|
|
- **Verification:** Manual — confirm no network calls leave the extension except to the selected provider endpoint.
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-03-06-02 — Inline styles, no Tailwind
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Content-script UI must not be broken by host-page CSS; WXT PostCSS/Tailwind integration was not wired.
|
|
- **Decision:** Style all UI with inline style objects (`Object.assign(el.style, …)` / `style={{…}}`), using a dark Catppuccin-ish palette.
|
|
- **Alternatives considered:** Tailwind (left in devDependencies but inactive); shadow DOM + stylesheet (deferred — see D-...-03).
|
|
- **Consequences:** Reliable rendering on any host page; palette/button styles get duplicated across content/Options/Popup (refactor tracked in TASKS #5).
|
|
- **Verification:** Visual check on multiple sites.
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-03-06-03 — No shadow DOM; `data-lexai` guard instead
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Injected toolbar/modal could collide with host-page styles or re-trigger LexAI's own handlers.
|
|
- **Decision:** Inject directly into `document.body`; mark every LexAI node `data-lexai="true"` and skip events whose target is `closest('[data-lexai="true"]')`; use max `z-index` (2147483647).
|
|
- **Alternatives considered:** Shadow DOM (rejected for now: added complexity; revisit if style isolation issues appear).
|
|
- **Consequences:** Simple and working; weaker isolation than shadow DOM; highly customized editors (e.g. Google Docs) may not accept programmatic replace.
|
|
- **Verification:** Manual across textarea/input/contenteditable sites.
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-03-06-04 — Eager selection snapshot before async
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Focus shifts to the toolbar and the live selection is gone by the time an async provider response returns.
|
|
- **Decision:** Capture the active element + selection offsets eagerly (on `mouseup` and on button `mousedown`) and snapshot before any `await`; Replace uses the snapshot. Handle both textarea/input (`selectionStart/End`) and contenteditable/DOM (`Range` API).
|
|
- **Alternatives considered:** Re-reading selection after the response (rejected: selection no longer exists).
|
|
- **Consequences:** Replace works reliably; the pattern is fragile — editing content.ts must preserve snapshot-before-await. Zero automated coverage today (TASKS #10).
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-03-06-05 — Dual `ANALYZE_TEXT` message shapes; `return true` listener
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Content script/popup send `{ payload: {...} }`; other call sites send flat `{ text, action, style }`. Async responses need the message channel held open.
|
|
- **Decision:** The handler accepts both shapes; the `onMessage` listener returns `true`. `fix` normalizes to `grammar`.
|
|
- **Consequences:** Flexible but must be preserved in both forms; removing `return true` silently breaks all responses.
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-xx-xx-06 — tweetnacl secretbox for the API key (obfuscation, not protection)
|
|
|
|
- **Status:** accepted — flagged for revisit
|
|
- **Context:** Storing the raw key in `chrome.storage.local` looked bad; added tweetnacl `secretbox` encryption (`apiKeyEnc` + `encKey`).
|
|
- **Decision:** Prefer the encrypted path; keep plaintext `apiKey` as back-compat until a migration exists.
|
|
- **Known weakness:** `encKey` is stored next to `apiKeyEnc`, so anyone who can read storage can decrypt. This is obfuscation, not protection (RECOMMENDATIONS #2).
|
|
- **Alternatives to consider:** derive the key from `chrome.storage.session` / WebCrypto / a user passphrase; and be honest in the UI ("stored locally, obscured"). Tracked in TASKS #2.
|
|
- **Owner / date:** post-Phase 1
|
|
|
|
### D-2026-03-06-07 — Gitea CI + Chrome Web Store deploy
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Project hosts CI on Gitea, not GitHub Actions.
|
|
- **Decision:** `.gitea/workflows/ci.yml` (typecheck→test→build→zip to registry) and `deploy-chrome.yml` (on `v*.*.*` tag → CWS). Telegram notifications. Version must match in `package.json` and `wxt.config.ts`.
|
|
- **Known weakness:** workflows `git clone` into `/tmp` and set `http.sslVerify false` (RECOMMENDATIONS #17). Revisit for speed/security.
|
|
- **Owner / date:** Phase 1, 2026-03-06
|
|
|
|
### D-2026-08-12-08 — CHANGELOG.md drives Gitea release notes
|
|
|
|
- **Status:** accepted
|
|
- **Context:** Releases previously shipped a hardcoded release body (`## LexAI ${VERSION}` + generic install steps) that never said what actually changed in that version.
|
|
- **Decision:** Release notes live in `CHANGELOG.md` (Keep a Changelog format, Semantic Versioning). `.gitea/workflows/release.yml` extracts the section matching the pushed tag's version and uses it as the Gitea release body, with a generic fallback if no matching section exists. A version bump is not considered done until `CHANGELOG.md` has that version's section.
|
|
- **Alternatives considered:** Auto-generating notes from commit messages (rejected: commit history is not curated for user-facing wording); keeping the hardcoded body (rejected: uninformative to installers).
|
|
- **Consequences:** Every version bump now requires a `CHANGELOG.md` entry alongside the `package.json` bump; the release workflow degrades gracefully (generic body + logged warning) if that entry is missed rather than failing the release.
|
|
- **Verification:** `release.yml` reviewed by an independent critic; P2 findings fixed. Confirmed locally that the section-extraction logic matches `## [1.1.0]` and stops at the next `## [` heading.
|
|
- **Owner / date:** 2026-08-12
|
|
|
|
## Open / proposed
|
|
|
|
### D-PROPOSED — Narrow host permissions from `<all_urls>`
|
|
|
|
- **Status:** proposed (decide before serious Web Store push)
|
|
- **Context:** Content script injects into every frame of every site, including banking/email/internal apps; also the #1 CWS review slowdown (RECOMMENDATIONS #1).
|
|
- **Options:** `activeTab` + on-demand injection, or a user-configurable allowlist.
|
|
- **Verification:** confirm actions still work after narrowing; measure review outcome.
|