# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repository state Scaffolded with [WXT](https://wxt.dev) (Vite-based, TypeScript-first, built on top of `webextension-polyfill` for the cross-browser `browser.*` API) — the popular framework for building Manifest V3 extensions that target both Firefox and Chromium from one codebase. Package manager: npm. Commands: - `npm install` — install deps (also runs `wxt prepare` via `postinstall` to generate `.wxt/` types). - `npm run dev` — dev build + watch, targets Chromium by default. - `npm run dev:firefox` — dev build + watch targeting Firefox (use this for Zen). - `npm run build` / `npm run build:firefox` — production build, output in `.output/chrome-mv3/` or `.output/firefox-mv3/`. - `npm run compile` — `tsc --noEmit` type-check only. - `npm run zip` / `npm run zip:firefox` — production build packaged as a `.zip` for store upload. Load-unpacked: run a build, then in Chromium go to `chrome://extensions` → enable Developer Mode → "Load unpacked" → select `.output/chrome-mv3/`. In Firefox/Zen go to `about:debugging#/runtime/this-firefox` → "Load Temporary Add-on" → select any file inside `.output/firefox-mv3/` (or use `npm run dev:firefox`, which auto-opens a temporary profile with the extension loaded). There is no lint/test setup yet — add one when the project needs it rather than pre-emptively scaffolding. ## What this project is Nexa is a blockchain-powered trading companion focused on the emotional/behavioral side of trading, not the trades themselves. Trades are already visible on-chain, so Nexa observes wallet activity rather than requiring manual logging. This repo is the **open-source frontend**: a browser extension. The backend (wallet/RPC listening via Solana/Helius, loss detection, auth, WebSocket push) is a separate private service and is out of scope here — treat it as an external API. License: Apache 2.0. The backend stays closed-source; this frontend repo is the OSS component. Full behavioral spec lives in `NEXA_SPEC.md` — read it before implementing the lockout feature or any site adapter/intervention. Key points below are a summary, not a replacement. ## Target platform - Built and tested primarily on Zen (Firefox fork), must also work cross-platform on Firefox and Chromium-based browsers (WebExtensions API / Manifest V3 where possible). - Avoid browser-specific APIs unless behind a compatibility shim. ## Current milestone: "Lockout" feature When the extension receives a "locked" state, it visually disables (not hides) buy-action elements on supported trading sites, shows a non-dismissible-to-unlock reason toast, and intercepts clicks that reach the underlying element. Sell/manage actions are never touched. Unlocking reverses all DOM changes cleanly with no leftover overlays/classes. Escalation model: first disable new-position buy actions only; if circumvented, escalate to a full-site block (stub only for v1). ## Required architecture (modularity is the core constraint) This is the most important design rule in the spec: **lock/unlock decision-making, site-specific detection, and enforcement/intervention must be three fully decoupled concerns.** - Adding a new supported site = a new site adapter module, without touching core logic. - Adding a new intervention type (full-site block, delayed buy with countdown, confirmation modal) = a new intervention strategy module, without touching adapters. - The decision of *when/how long/why* to lock is decoupled from *how it's enforced on the page* and from *how it's detected on the page*. Planned structure (from `NEXA_SPEC.md` §3.2): ``` /src /background - owns WebSocket connection to backend, holds current lock state, messages content scripts /content-scripts /adapters axiom.ts - site-specific selectors + DOM strategy for axiom.trade adapter-interface.ts - shared type/interface all adapters implement /interventions blur-disable.ts - visual lock treatment (v1 default) full-block.ts - escalation: full page block overlay (stub for v1) intervention-interface.ts observer.ts - generic MutationObserver watching for adapter-declared selectors (SPA-safe) content-index.ts - wires adapter + intervention + background messages together /popup - status, reason for lock, threshold settings link /notifications - toast/banner injected into page or via browser notification API /shared messaging.ts - typed message contracts between background <-> content scripts <-> popup types.ts manifest.json ``` A site adapter's contract: `matches` (URL pattern), `findBuyElements()`, `getContainer(el)` (stable ancestor to style, so the whole control is styled rather than an inner span). ## Axiom.trade adapter specifics (first target site) - Axiom is a React SPA — buy elements mount/unmount without full page reloads. Use a `MutationObserver` on a stable root (e.g. `document.body`), not a one-time `querySelectorAll` on load. - Quick Buy button: find via `closest('.buy-click-container')`, not a hardcoded ancestor depth (DOM depth is not stable). - Noob Mode Buy button: match `button` elements with class `bg-increase` whose text matches `/^Buy\s/i`. Do not hardcode token names — they vary per token page. - Full selector details and example markup are in `NEXA_SPEC.md` §3.3. ## Lock state contract The lock/unlock state is a simple typed interface: `{ locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }`. For v1 (no real backend yet), this is driven by a mock/dev toggle (e.g. via popup or `browser.storage`). The background script must expose a single isolated seam (`getLockState()` / `setLockState()`) where the real WebSocket client will later plug in — do not scatter lock-state reads/writes across modules. ## Visual treatment for v1 (`blur-disable`) - Wrap or overlay the original element rather than mutating its classes destructively, so the original markup can be restored exactly on unlock. - `filter: blur(2-3px)`, ~0.5 opacity, `cursor: not-allowed`, a non-blurred centered lock icon overlay, and `pointer-events: none` (or a transparent click-catcher div). - A click on a locked element (or its click-catcher) must trigger the same reason toast as lock activation. ## Non-goals for this milestone - No real backend/WebSocket connection (mock state only). - No cost-basis tracking logic (backend concern). - No full-site-block implementation beyond a stub module. - No threshold-setting UI (placeholder link only). - Only the axiom.trade adapter needs to be functional; the architecture just needs to make adding more sites trivial.