Files
copilot/NEXA_SPEC.md
T
2026-09-06 04:41:40 +02:00

10 KiB

Nexa — Frontend Browser Extension Spec

1. Overview

Nexa is a blockchain-powered trading companion that helps traders manage the emotional/behavioral side of trading rather than the trades themselves. Because trades are already visible on-chain, Nexa doesn't need the user to manually log anything — it observes wallet activity and reacts to it.

This spec covers only the frontend: an open-source browser extension. The backend (wallet/RPC listening, loss detection, auth) is a separate private service and is treated here as an external API the extension talks to.

Target browser: built and tested on Zen (Firefox fork), but must be cross-platform (Firefox + Chromium-based via WebExtensions API / manifest v3 where possible). Avoid browser-specific APIs unless behind a compatibility shim.

Current milestone: the "Lockout" feature — disabling quick-buy interactions on supported trading sites after a qualifying loss, with a visible reason/notification, built in a modular way so more sites and more intervention types can be added later.

2. Core Concept Recap (for context, not build scope)

  • User sets their own loss thresholds while calm (Ulysses-contract style).
  • Backend watches the user's wallet (Solana RPC / Helius) and detects when a threshold-qualifying loss occurs.
  • Backend pushes a lock/unlock signal to the extension (WebSocket).
  • Extension enforces the lock in the DOM of the trading site the user is currently on.
  • Escalation model: first disable new-position buy actions only (sell/manage stays enabled); if the user tries to circumvent it, escalate to fully blocking the site.

3. Scope of This Spec: The Lockout Feature (v1)

3.1 Behavior

When the extension receives a "locked" state (from backend, or a mocked/local state for UI dev purposes):

  1. Detect buy-action elements on the current page for the active site adapter (see Site Adapters below).
  2. Visually disable each detected buy element — do not just hide it. The user should see that an action exists but is blocked, not that it vanished. Preferred treatment: blur + reduced opacity + pointer-events: none, with a small lock icon overlay on hover/tap.
  3. Intercept any click that somehow still reaches the underlying element (defense in depth) and prevent default/propagation.
  4. Show a reason. On lock activation, surface a non-intrusive extension notification/popup (e.g. toast anchored near the browser action icon, or an injected banner) stating why it's locked, e.g. "Locked: -$62 loss on SOL/PIXELCAT crossed your $50 threshold." Include a way to view details (opens extension popup with more info) but no way to silently dismiss-and-unlock from that toast — unlocking is a deliberate action.
  5. Sell/manage actions remain untouched — only buy-side elements are targeted.
  6. When state changes to "unlocked," reverse all DOM modifications cleanly (no leftover classes/overlays).

3.2 Modularity Requirement

This must be built so that:

  • Adding a new supported site = adding a new "site adapter" module, not touching core logic.
  • Adding a new intervention type (e.g. full-site block, delayed buy with countdown, confirmation modal) = adding a new "intervention strategy" module, not touching site adapters.
  • The lock/unlock decision (when to lock, for how long, why) is fully decoupled from how it's enforced on the page and from how it's detected on the page.

Suggested architecture:

/src
  /background        - extension background/service worker: 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
      <future-site>.ts
      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, structure only)
      intervention-interface.ts
    observer.ts        - generic mutation observer that watches for adapter-declared selectors appearing/disappearing (SPA-safe)
    content-index.ts   - wires adapter + intervention + background messages together
  /popup              - extension popup UI: current status, reason for lock, threshold settings link
  /notifications      - toast/banner component injected into page or shown via browser notification API
  /shared
    messaging.ts       - typed message contracts between background <-> content scripts <-> popup
    types.ts
manifest.json

3.3 Site Adapter: Axiom.trade (first target)

An adapter declares:

  • matches: URL pattern(s) this adapter applies to.
  • findBuyElements(): returns all current buy-action DOM nodes on the page.
  • getContainer(el): given a buy element, returns the stable ancestor container to apply lock styling to (so we style the whole control, not just an inner span).

Known selectors/patterns to encode (from live markup on axiom.trade):

Quick Buy button (small pill-style buy amount button, e.g. "0.04"):

<div class="flex min-h-[28px] flex-1 cursor-pointer items-center justify-center rounded-full text-[14px] font-medium leading-[18px] outline-none border border-increase/50 text-increase hover:bg-increase/5 hover:text-increaseHover touch-callout-none select-none">0.04</div>
  • Detection: the reliable anchor is the ancestor with class buy-click-container — walk up from any .border-increase\/50.text-increase candidate, or more robustly, query .buy-click-container directly and treat it (or its clickable child) as the target.
  • The 4th ancestor being buy-click-container is a current DOM depth fact, not something to hardcode as "4 levels up" — instead use closest('.buy-click-container') so it survives markup depth changes.

Noob Mode Buy button (the big "Buy {token}" button):

<div class="flex min-h-[4px] flex-1 flex-row items-center justify-center px-[16px] pb-[16px]">
  <button type="button" class="bg-increase hover:bg-increase/90 rounded-8 flex max-h-[36px] min-h-[36px] flex-1 flex-row items-center justify-center rounded-full p-[4px] undefined">
    <span class="text-[14px] font-bold leading-[18px] text-background">
      <span class="text-[#090909]">Buy pixelcat</span>
    </span>
  </button>
</div>
  • Detection: match button elements whose class list includes bg-increase and whose text content matches /^Buy\s/i. Do not hardcode the token name ("pixelcat") — match the "Buy " prefix pattern instead, since it changes per token page.
  • Fallback/secondary check: the button's own class bg-increase (their "increase/green" action color) combined with being inside a quick-buy/trade panel region, to avoid accidentally matching unrelated green buttons elsewhere on the page.

Note for Claude Code: Axiom.trade is a React SPA — buy elements mount/unmount as the user navigates between tokens without full page reloads. Use a MutationObserver on a stable root (e.g. document.body or the main app root) rather than a one-time querySelectorAll on load, and re-apply/remove the lock treatment whenever matching elements appear.

3.4 Lock Enforcement — Visual Treatment (v1: blur-disable)

For each detected buy container:

  • Apply a wrapper/overlay (don't mutate the site's own classes destructively — wrap or overlay so original markup can be restored exactly on unlock).
  • Visual: filter: blur(2-3px), reduced opacity (~0.5), cursor not-allowed.
  • Overlay a small lock icon, centered, not blurred, so it reads clearly as "locked" rather than "broken."
  • pointer-events: none on the original element or a transparent click-catcher div on top of it.
  • Click-catcher (if used) triggers the same "show reason" notification on click/tap, so users who try to click get immediate feedback rather than nothing happening.

3.5 Notification/Reason UI

  • Small, dismissible toast, injected into the page (top-right corner, non-blocking) or via the extension's own popup badge/notification.
  • Content: short reason (e.g. threshold crossed, amount, token if relevant) + a "View details" affordance that opens the extension popup.
  • Must not offer a one-click unlock from the toast itself.
  • Extension popup (separate, already-scoped-elsewhere UI surface) shows: current lock status, reason, time locked, link to adjust thresholds (thresholds config itself is out of scope for this spec — just leave a hook/placeholder).

3.6 State Source for This Milestone

Since backend integration (WebSocket, RPC loss detection) is separate work:

  • Build the content-script/background lock state as consuming a simple typed interface, e.g. { locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }.
  • For UI development purposes, wire this to a mock/dev toggle (e.g. via the extension popup, or a chrome.storage/browser.storage flag) so the lock/unlock visuals can be demoed and iterated on before the real backend WebSocket is wired in.
  • The background script should have a clearly isolated function/module (getLockState()/setLockState()) that's the single seam where the future WebSocket client will plug in.

4. Non-Goals for This Milestone

  • No real backend connection yet (mock state only).
  • No cost-basis tracking logic (backend concern).
  • No full-site-block implementation detail beyond a stub module (structure only, so it's easy to fill in later).
  • No threshold-setting UI (placeholder link only).
  • Only one site adapter (axiom.trade) needs to be functional; architecture just needs to make adding more trivial.

5. Deliverable for This Pass

A working browser extension (loadable unpacked in Zen/Firefox, and ideally Chromium) that:

  1. On axiom.trade, detects quick-buy and noob-mode buy elements per section 3.3.
  2. Applies the blur/disable visual treatment per 3.4 when a dev-mode "locked" toggle is on.
  3. Shows a reason toast per 3.5 when lock activates, and on click of a locked element.
  4. Cleanly reverses all changes when toggled back to unlocked.
  5. Is structured per the modular architecture in 3.2 so a second site adapter and a second intervention type could be added without touching existing files beyond registration.

6. Repo / Licensing Context

  • This frontend repo is the OSS component; description: "Your blockchain powered Agent to help with your trading emotions."
  • License: Apache 2.0 (consistent with other OSS projects, and chosen partly to build trust given the extension interacts with a trading wallet).
  • Backend stays private/closed-source.