From 68686f7b184172f6fab91903048962b5640d8664 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 6 Sep 2026 04:41:40 +0200 Subject: [PATCH] Spec and claude --- CLAUDE.md | 84 +++++++++++++++++++++++++++++++ NEXA_SPEC.md | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 CLAUDE.md create mode 100644 NEXA_SPEC.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..390a99c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository state + +This repo currently contains only a spec (`NEXA_SPEC.md`), `README.md`, and `LICENSE` — no source code, `package.json`, or `manifest.json` exist yet. There are no build/lint/test commands to run because nothing has been scaffolded. When implementation begins, this file should be updated with the actual commands (build, lint, test, load-unpacked instructions) once a package manager and bundler are chosen. + +## 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. diff --git a/NEXA_SPEC.md b/NEXA_SPEC.md new file mode 100644 index 0000000..1a38cb3 --- /dev/null +++ b/NEXA_SPEC.md @@ -0,0 +1,140 @@ +# 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 + .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"): +```html +
0.04
+``` +- 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): +```html +
+ +
+``` +- 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.