Files
LexAI/CLAUDE.md
john kevin asprec acea99d7ad
Some checks failed
CI — Test & Build / Test & Build (push) Failing after 39s
feat: Implement Prompt Builder functionality in Popup and Options
- Added a new "Prompt Builder" tab in the Popup for generating AI prompts with customizable parameters.
- Introduced new state variables for managing prompt styles, personas, formats, and models.
- Enhanced the Options page to fetch and display models based on the provided API key.
- Updated the actions and types to include the new 'prompt' action and its associated parameters.
- Implemented migration logic for legacy plaintext API keys to encrypted storage.
- Updated the getSystemPrompt function to incorporate prompt parameters for better instruction generation.
- Added tests for the new functionality, including context menu entries and prompt generation logic.
2026-07-15 15:27:41 +08:00

12 KiB
Raw Blame History

CLAUDE.md — LexAI

Operating guide and control plane for Claude Code in this repo. Keep it durable and current, not a diary. Architecture/convention detail lives here and in docs/.

0. Project contract

Field Value
Project LexAI — Grammarly-like Chrome extension (Manifest V3), BYO-LLM-key
Outcome Select text on any page → AI action (fix/rephrase/shorten/expand/explain) → Replace or Copy, with no backend and no subscription
Non-goals No backend/account/subscription; no telemetry; no transmission of text/key except to the user's chosen provider; not a full editor
Primary user People who hold an LLM API key and want inline writing help without a SaaS subscription
Acceptance tests npm run typecheck + npm test -- --run pass; npm run build yields a loadable .output/chrome-mv3/; Replace works on textarea/input and contenteditable; key uses the encrypted path and is never logged/exfiltrated
Constraints WXT ^0.20 + React 18 + TS; Node 22; Tailwind inactive (inline styles); <all_urls> today; Gitea CI + Chrome Web Store
Source of truth This file + docs/; issue tracking in Plane (LEXAI)
Commands install: npm install · test: npm test -- --run · typecheck: npm run typecheck · build: npm run build

Definition of done

Done means: the change is implemented, typecheck/test/build pass, behavior is verified (for DOM/selection/replace changes, a real-page load-unpacked check — unit tests don't cover DOM timing), the codebase invariants in ## Lessons are preserved, and docs/HANDOFF.md states what changed and how it was tested. Do not claim success from code inspection alone.

1. Operating principles

  1. Evidence before inference. Read the relevant entrypoints/tests before changing them; report actual command output.
  2. Smallest useful diff. Be surgical — match existing style, don't reformat or broaden scope.
  3. Artifacts beat chat. Record state in docs/ (brief, decisions, tasks, handoff) so a fresh session resumes from files, not history.
  4. Preserve the invariants. The message contract, snapshot-before-await, data-lexai guard, and key handling break silently — see ## Lessons.
  5. Use code for deterministic work. Prefer typecheck/test/build over reasoning about correctness.
  6. Escalate intentionally. Surface anything touching manifest permissions, storage schema, the key path, or a release.

2. Files that preserve context

docs/
  PROJECT_BRIEF.md     # outcome, non-goals, constraints
  ARCHITECTURE.md      # the three-context system and boundaries
  DECISIONS.md         # ADRs: BYO-key, inline styles, no shadow DOM, key story, Gitea CI
  TASKS.md             # active contracts derived from RECOMMENDATIONS.md
  EVALS.md             # standing gates + failure-derived checks
  LESSONS_LEARNED.md   # codebase invariants (detail behind the ## Lessons list)
  HANDOFF.md           # current state, risks, next action
  SELF_MODEL.md        # operator/project model, kept honest by audit
  attacksurface.md     # exposure inventory (permissions, key storage, CI)

Supporting: README.md (public), PHASE1_SUMMARY.md (build history), RECOMMENDATIONS.md (source of the task list), src/utils/REFERENCE_NOTES.md (reference-project patterns).

What LexAI is

A Grammarly-like Chrome Extension (Manifest V3) providing AI writing assistance (grammar fix, rephrase, shorten, expand, explain) on any webpage. Users bring their own LLM API key — there is no LexAI backend. The service worker calls the user's chosen provider directly.

  • Providers: OpenAI, Anthropic, Groq, OpenRouter (configured in entrypoints/background.ts).
  • No subscription, no server. The API key lives encrypted in chrome.storage.local.

Tech stack

  • WXT ^0.20 — extension framework (wraps Vite). Entrypoints in entrypoints/.
  • React 18 + TypeScript — Options and Popup pages only.
  • Zustand — a dependency, but state is currently local; not yet wired into a store.
  • tweetnacl / tweetnacl-utilsecretbox symmetric encryption for the API key.
  • Vitest (jsdom) unit tests, Playwright e2e.
  • Tailwind is in devDependencies but not active — all UI uses inline style objects (WXT PostCSS was never wired up). Do not assume Tailwind classes work.

Commands

npm install          # first-time setup (node_modules gitignored; not present by default)
npm run dev          # WXT dev server with hot reload
npm run build        # production build → .output/chrome-mv3/
npm run zip          # package for Chrome Web Store
npm test             # vitest (add `-- --run` for one-shot, non-watch)
npm run test:e2e     # Playwright (requires a prior `npm run build`)
npm run typecheck    # tsc --noEmit

Prerequisite: Node. CI pins Node 22 (node:22-bookworm). Run npm install before any npm run * — the binaries (tsc, vitest) come from node_modules/.bin.

Load unpacked in Chrome: npm run buildchrome://extensions → Developer Mode → Load unpacked → select .output/chrome-mv3.

Architecture

Three cooperating contexts, message-passed over chrome.runtime:

entrypoints/content.ts   (content script, injected into <all_urls>)
  • Detects text selection: textarea/input (selectionStart/End) vs contenteditable/DOM (Range API)
  • Renders the floating toolbar + result modal + toasts (inline-styled, appended to document.body)
  • Snapshots selection state BEFORE any async call, then Replace uses the snapshot
  • Sends { type: 'ANALYZE_TEXT', payload: {text, action, style} } to the background

entrypoints/background.ts (service worker — the LLM proxy)
  • onMessage: ANALYZE_TEXT and COPY_AS
  • Reads provider/apiKey/apiKeyEnc/encKey/model from chrome.storage.local
  • Decrypts the key (tweetnacl secretbox), routes to the correct provider's fetch call
  • Registers right-click context menus (action × style) on install

entrypoints/options/Options.tsx (settings page, React)
  • Provider + model + API key form; encrypts the key and writes apiKeyEnc/encKey to storage

entrypoints/popup/Popup.tsx (toolbar popup, React)
  • Standalone text box → same ANALYZE_TEXT flow; shows config status; links to Options

Content script and popup must not call provider APIs directly — CORS and key handling belong in the background service worker. Route everything through ANALYZE_TEXT/COPY_AS. See docs/ARCHITECTURE.md for the component table.

Message contract

  • ANALYZE_TEXT accepts both { payload: {text, action, style} } (content/popup) and flat { text, action, style }. Keep both shapes working if you touch the handler.
  • action values: grammar, rephrase, shorten, expand, explain. The context menu and popup emit fix, which getSystemPrompt normalizes to grammar.
  • The listener returns true to keep the async channel open — required; removing it silently breaks every response.

Key conventions & gotchas

  • data-lexai="true" is set on every LexAI-injected DOM node. Selection/click handlers check target.closest('[data-lexai="true"]') to avoid self-triggering. Preserve it on any new injected element.
  • Selection is captured eagerly (on mouseup and on button mousedown) because focus shifts and the live selection is gone by the time an async response returns. Keep the snapshot-before-await pattern intact.
  • z-index: 2147483647 (max) on toolbar/modal so they sit above host-page UI.
  • Provider code is duplicated: each provider has a callX (system-prompt from action) and a callXWithPrompt (arbitrary system prompt, used by COPY_AS). A change to request shape usually needs both. This is a known smell — refactor tracked in docs/TASKS.md (T-04).
  • API-key handling: prefer the encrypted path (apiKeyEnc + encKey); plaintext apiKey is legacy/back-compat only. Never log the key. Never add code that transmits it anywhere except the user's chosen provider endpoint.
  • Backward compat: don't drop the plaintext apiKey fallback without a migration.
  • Console [LexAI …] debug logs exist in content.ts's replace path — intentional for now, but should be gated behind a DEV flag before release (T-03).

Testing notes

  • tests/unit/setup.ts mocks global.chrome. Unit tests currently exercise storage mocks rather than importing the real handlers — see docs/TASKS.md T-08 for the gap.
  • Playwright e2e loads the built extension via --load-extension=.output/chrome-mv3; the test files still contain [EXTENSION_ID] placeholders and won't pass as-is (T-09).
  • Standing gates and how to run them: docs/EVALS.md.

CI / release (Gitea, not GitHub Actions)

Workflows live in .gitea/workflows/:

  • ci.yml — typecheck → test → build → publish zip to Gitea package registry (on push to main/develop, PRs).
  • deploy-chrome.yml — on v*.*.* tag: build → upload → publish to Chrome Web Store.
  • Both send Telegram notifications. Secrets: GITEATOKEN, CWS_*, TELEGRAM_*.

Version bumps: update version in both package.json and wxt.config.ts (the manifest version comes from wxt.config.ts). A v*.*.* git tag triggers the store deploy. (Single-sourcing tracked in T-16.)

Orchestration & agents

For coordinated work, run Fable as the main session (claude --agent fable-orchestrator) and let it route to specialists; see .claude/AGENTS.md for the roster. For LexAI code (entrypoints/, src/), prefer the lexai-extension-dev specialist over the generic builder — it knows the message contract, snapshot pattern, and key rules. Security-relevant changes (permissions, key path, dependencies) go through security-auditor.

On-demand skills in .claude/skills/: resume-project, continuous-improvement, dev-loop, attack-surface, prompt-injection-audit, self-model-audit. Invoke by name; keep each run bounded.

Quality gates by risk

Risk Examples in LexAI Required gates
Low docs, inline-style tweaks, copy contract + self-check (typecheck)
Medium new action, provider request change, Options/Popup UI typecheck + test -- --run + build + a separate verifier
High manifest permissions, key handling/storage schema, release/version, CWS listing written plan + security-auditor + independent critic + real-page verification + explicit owner authorization before release

When making changes

  • After editing an entrypoint, run npm run typecheck and npm test -- --run.
  • For behavior changes, npm run build and load unpacked to verify in a real page — the selection/replace logic is DOM-timing-sensitive and unit tests don't cover it.
  • Keep UI styling inline (no Tailwind) unless you're intentionally wiring PostCSS.

Lessons

Codebase invariants that break silently when violated (detail + evidence in docs/LESSONS_LEARNED.md):

  • Keep return true in the onMessage listener — else every async response is dropped.
  • Snapshot selection before any await; handle textarea/input and contenteditable/Range paths.
  • Keep both ANALYZE_TEXT shapes ({payload} and flat) and the fixgrammar normalization.
  • Set data-lexai="true" on every injected node; skip events on closest('[data-lexai="true"]').
  • Never log or transmit the API key except to the user's provider; keep the plaintext apiKey fallback until a migration exists.
  • Update both callX and callXWithPrompt when changing a provider's request shape.
  • Never fetch a provider from content/popup — route through the background worker.
  • Bump version in both package.json and wxt.config.ts.
  • Style inline; Tailwind classes do nothing until PostCSS is wired.

State continuity

  • On a fresh/compacted/interrupted session, invoke /resume-project before planning or editing.
  • Before ending a substantial task, update docs/HANDOFF.md (verified state, changed paths, checks, risks, next action).
  • After a verified recurring mistake or workflow gap, invoke /continuous-improvement; durable agent/skill changes go through system-steward.