Files
LexAI/docs/DECISIONS.md
john kevin asprec 7adcb47584 release: v1.1.0 — prompt patterns, CHANGELOG-driven release notes
- 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.
2026-08-12 07:51:15 +08:00

6.9 KiB

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.