Spec and claude
This commit is contained in:
+140
@@ -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
|
||||
<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"):
|
||||
```html
|
||||
<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):
|
||||
```html
|
||||
<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.
|
||||
Reference in New Issue
Block a user