From 7f8270a048afd70521a18dec0bd4dcd958973887 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 8 Sep 2026 02:38:46 +0200 Subject: [PATCH 1/4] Replace threshold placeholder link with a real settings panel Adds a "Loss thresholds" panel to the popup backed by the backend's new GET/PATCH /me/settings endpoints, converting between the wire's fractional percentages and whole-number form inputs only at that boundary. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01RTorFQXMR9ZQtCKcMTXLHp --- CLAUDE.md | 3 +- src/background/settings-client.ts | 31 +++++ src/entrypoints/background.ts | 15 ++- src/entrypoints/popup/App.tsx | 205 ++++++++++++++++++++++++++++-- src/entrypoints/popup/style.css | 62 ++++++++- src/shared/messaging.ts | 11 +- src/shared/types.ts | 25 ++++ 7 files changed, 335 insertions(+), 17 deletions(-) create mode 100644 src/background/settings-client.ts diff --git a/CLAUDE.md b/CLAUDE.md index 620a57f..dc9d258 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,6 +93,7 @@ Note the naming mismatch with the wire protocol below: the frontend's internal ` - `src/background/wallet-auth.ts` — `handleWalletConnected()` runs the REST auth flow (`POST /auth/nonce` → Phantom signature → `POST /auth/verify` → session token) once a content script reports a connected wallet. Also `requestWalletReconnect()` (silent reconnect after the backend invalidates a session), `requestWalletDisconnect()` (sign-out), `requestAccountSwitch()` (explicit account switch) — all three just message whichever tabs are on a supported site; `wallet-connect.ts` in the content script does the actual work. - `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`. `connectWsClient()` returns a controller with `reconnectNow()` so the background script can short-circuit the backoff wait right after a fresh token arrives. Auth failures (`4001` close, `auth_expired`/`session_revoked` errors) do **not** auto-retry with backoff — they call `onAuthExpired()` instead, since retrying with a known-bad token can't succeed; only real disconnects (network drop, backgrounded browser) use the protocol's suggested backoff schedule. - `src/background/connection-status.ts` — separate from lock state; the popup surfaces this (connecting/connected/disconnected/auth-error) alongside the lock state so a broken connection isn't silently indistinguishable from "unlocked". +- `src/background/settings-client.ts` — `GET`/`PATCH /me/settings` (per-user loss-detection thresholds; see backend/CLAUDE.md, "Account settings"), same direct-fetch-with-bearer-token shape as `wallet-auth.ts`. Popup -> background dispatch is `nexa:get-settings`/`nexa:update-settings` in `background.ts`, responses shaped `{ settings } | { error }` (`SettingsResult` in `shared/messaging.ts`). The popup's "Loss thresholds" panel (`entrypoints/popup/App.tsx`) is the only place percent fields (`near_full_exit_fraction` and both loss thresholds) get converted between the backend's fractional wire representation and the whole-number percentages shown in the form — same "convert only at the seam" rule as `LockScope`'s kebab/snake split below. ### Wallet auth (Phantom) — implemented @@ -183,5 +184,5 @@ Single source of truth for the wire protocol between the Nexa backend (Rust/Axum - No cost-basis tracking logic (backend concern). - No full-site-block implementation beyond a stub module. -- No threshold-setting UI (placeholder link only). +- ~~No threshold-setting UI (placeholder link only)~~ — implemented, see "Backend connection" above. - Only the axiom.trade adapter needs to be functional; the architecture just needs to make adding more sites trivial. diff --git a/src/background/settings-client.ts b/src/background/settings-client.ts new file mode 100644 index 0000000..32034b6 --- /dev/null +++ b/src/background/settings-client.ts @@ -0,0 +1,31 @@ +import { BACKEND_HTTP_URL } from '@/shared/config'; +import type { ThresholdSettings } from '@/shared/types'; +import { getSessionToken } from './backend-client'; + +/** + * `GET`/`PATCH /me/settings` — same direct-fetch-with-bearer-token shape as + * wallet-auth.ts's REST calls. Throws on any failure (network, auth, or a + * validation rejection from the backend); callers decide how to surface that. + */ +export async function getSettings(): Promise { + const token = await getSessionToken(); + const res = await fetch(`${BACKEND_HTTP_URL}/me/settings`, { + headers: { authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`get settings failed: ${res.status}`); + return (await res.json()) as ThresholdSettings; +} + +export async function updateSettings(patch: Partial): Promise { + const token = await getSessionToken(); + const res = await fetch(`${BACKEND_HTTP_URL}/me/settings`, { + method: 'PATCH', + headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' }, + body: JSON.stringify(patch), + }); + if (!res.ok) { + const message = await res.text().catch(() => ''); + throw new Error(message || `update settings failed: ${res.status}`); + } + return (await res.json()) as ThresholdSettings; +} diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index 9b3eb8a..624d4d1 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -7,8 +7,9 @@ import { requestWalletDisconnect, requestWalletReconnect, } from '@/background/wallet-auth'; +import { getSettings, updateSettings } from '@/background/settings-client'; import { connectWsClient } from '@/background/ws-client'; -import type { NexaMessage } from '@/shared/messaging'; +import type { NexaMessage, SettingsResult } from '@/shared/messaging'; export default defineBackground(() => { const ws = connectWsClient({ @@ -71,6 +72,18 @@ export default defineBackground(() => { void requestAccountSwitch(); return false; + case 'nexa:get-settings': + getSettings() + .then((settings) => sendResponse({ settings } satisfies SettingsResult)) + .catch((err) => sendResponse({ error: String(err) } satisfies SettingsResult)); + return true; // keep the message channel open for the async response + + case 'nexa:update-settings': + updateSettings(message.settings) + .then((settings) => sendResponse({ settings } satisfies SettingsResult)) + .catch((err) => sendResponse({ error: String(err) } satisfies SettingsResult)); + return true; + default: return undefined; } diff --git a/src/entrypoints/popup/App.tsx b/src/entrypoints/popup/App.tsx index e20586a..e04d6f9 100644 --- a/src/entrypoints/popup/App.tsx +++ b/src/entrypoints/popup/App.tsx @@ -1,7 +1,14 @@ import { useEffect, useState } from 'react'; import { browser } from 'wxt/browser'; -import { DEFAULT_CONNECTION_STATUS, DEFAULT_LOCK_STATE, type ConnectionStatus, type LockState } from '@/shared/types'; -import type { NexaMessage } from '@/shared/messaging'; +import { + DEFAULT_CONNECTION_STATUS, + DEFAULT_LOCK_STATE, + DEFAULT_THRESHOLD_SETTINGS, + type ConnectionStatus, + type LockState, + type ThresholdSettings, +} from '@/shared/types'; +import type { NexaMessage, SettingsResult } from '@/shared/messaging'; const CONNECTION_LABELS: Record = { connecting: 'Connecting…', @@ -78,13 +85,193 @@ export function App() { )} - - Adjust loss thresholds → - + ); } + +/** + * Percent-shaped fields (`near_full_exit_fraction`, both loss thresholds) are + * edited here as whole numbers (e.g. "30") and converted to/from the + * backend's fractional wire representation only at this boundary — same + * "convert only at the seam" rule already applied to LockScope's kebab/snake + * case split (see ../../CLAUDE.md). + */ +type ThresholdForm = { + exitPercent: string; + windowHours: string; + streakLength: string; + singleLossPercent: string; + streakLossPercent: string; + cooldownMinutes: string; +}; + +function toForm(settings: ThresholdSettings): ThresholdForm { + return { + exitPercent: String(Math.round(settings.near_full_exit_fraction * 100)), + windowHours: String(settings.loss_window_hours), + streakLength: String(settings.streak_length), + singleLossPercent: String(Math.round(-settings.single_trade_loss_threshold * 100)), + streakLossPercent: String(Math.round(-settings.streak_sum_loss_threshold * 100)), + cooldownMinutes: String(settings.cooldown_minutes), + }; +} + +function fromForm(form: ThresholdForm): Partial { + return { + near_full_exit_fraction: Number(form.exitPercent) / 100, + loss_window_hours: Number(form.windowHours), + streak_length: Number(form.streakLength), + single_trade_loss_threshold: -Number(form.singleLossPercent) / 100, + streak_sum_loss_threshold: -Number(form.streakLossPercent) / 100, + cooldown_minutes: Number(form.cooldownMinutes), + }; +} + +function ThresholdsPanel() { + const [expanded, setExpanded] = useState(false); + const [form, setForm] = useState(() => toForm(DEFAULT_THRESHOLD_SETTINGS)); + const [status, setStatus] = useState<'idle' | 'loading' | 'saving'>('idle'); + const [error, setError] = useState(undefined); + + function expand(): void { + setExpanded(true); + if (status !== 'idle') return; + setStatus('loading'); + setError(undefined); + browser.runtime + .sendMessage({ type: 'nexa:get-settings' } satisfies NexaMessage) + .then((result: SettingsResult | undefined) => { + if (result && 'settings' in result) { + setForm(toForm(result.settings)); + setStatus('idle'); + } else { + setError(result?.error ?? 'Failed to load settings.'); + setStatus('idle'); + } + }); + } + + function field(key: keyof ThresholdForm, value: string): void { + setForm((prev) => ({ ...prev, [key]: value })); + } + + function save(): void { + setStatus('saving'); + setError(undefined); + browser.runtime + .sendMessage({ type: 'nexa:update-settings', settings: fromForm(form) } satisfies NexaMessage) + .then((result: SettingsResult | undefined) => { + setStatus('idle'); + if (result && 'settings' in result) { + setForm(toForm(result.settings)); + } else { + setError(result?.error ?? 'Failed to save settings.'); + } + }); + } + + if (!expanded) { + return ( + + ); + } + + return ( +
+

Loss thresholds

+ + + + + + + + + + + + + + {error &&

{error}

} + + +
+ ); +} diff --git a/src/entrypoints/popup/style.css b/src/entrypoints/popup/style.css index d381bde..d7dba78 100644 --- a/src/entrypoints/popup/style.css +++ b/src/entrypoints/popup/style.css @@ -100,11 +100,65 @@ header .subtitle { background: rgba(128, 128, 128, 0.12); } -.thresholds-link { +.thresholds-toggle { display: block; + width: 100%; margin-top: 14px; + padding: 0; + border: none; + background: none; color: inherit; - opacity: 0.5; - text-decoration: none; - pointer-events: none; + opacity: 0.75; + font: inherit; + text-align: left; + cursor: pointer; +} + +.thresholds-toggle:hover { + opacity: 1; +} + +.thresholds-panel { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid rgba(128, 128, 128, 0.25); +} + +.thresholds-panel h2 { + margin: 0 0 8px; + font-size: 13px; +} + +.thresholds-panel label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; + font-size: 12px; + opacity: 0.85; +} + +.threshold-input { + display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; +} + +.threshold-input input { + width: 48px; + padding: 3px 5px; + border: 1px solid rgba(128, 128, 128, 0.4); + border-radius: 4px; + background: transparent; + color: inherit; + font: inherit; + text-align: right; +} + +.thresholds-error { + margin: 4px 0 8px; + color: #dc2626; + font-size: 12px; } diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts index 90e19d1..eb55bc5 100644 --- a/src/shared/messaging.ts +++ b/src/shared/messaging.ts @@ -1,4 +1,4 @@ -import type { ConnectionStatus, LockState } from './types'; +import type { ConnectionStatus, LockState, ThresholdSettings } from './types'; /** browser.storage.local key backing the lock state seam in the background script. */ export const LOCK_STATE_STORAGE_KEY = 'nexa:lockState'; @@ -28,7 +28,14 @@ export type NexaMessage = | { type: 'nexa:switch-account-request' } // Sign-out / switch account (popup -> background). See entrypoints/popup/App.tsx. | { type: 'nexa:sign-out' } - | { type: 'nexa:switch-account' }; + | { type: 'nexa:switch-account' } + // Loss-threshold settings (popup -> background -> GET/PATCH /me/settings). + // See background/settings-client.ts and entrypoints/popup/App.tsx. + | { type: 'nexa:get-settings' } + | { type: 'nexa:update-settings'; settings: Partial }; + +/** Response shape for 'nexa:get-settings'/'nexa:update-settings', returned via sendResponse. */ +export type SettingsResult = { settings: ThresholdSettings } | { error: string }; /** Response shape for 'nexa:wallet-sign-request', returned via sendResponse (not a dispatched NexaMessage). */ export type WalletSignResult = { signature: string } | { error: string }; diff --git a/src/shared/types.ts b/src/shared/types.ts index 6f6ca52..78566b5 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -17,3 +17,28 @@ export const DEFAULT_LOCK_STATE: LockState = { export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'auth-error'; export const DEFAULT_CONNECTION_STATUS: ConnectionStatus = 'connecting'; + +/** + * Per-user loss-detection thresholds — mirrors the backend's + * `settings::Settings` field-for-field (wire is snake_case throughout, see + * backend/CLAUDE.md, "Relationship to the frontend"). Fetched/updated via + * `GET`/`PATCH /me/settings`, see background/settings-client.ts. + */ +export interface ThresholdSettings { + near_full_exit_fraction: number; + loss_window_hours: number; + streak_length: number; + single_trade_loss_threshold: number; + streak_sum_loss_threshold: number; + cooldown_minutes: number; +} + +/** Mirrors the backend's `Settings::default()` — shown before the real values load. */ +export const DEFAULT_THRESHOLD_SETTINGS: ThresholdSettings = { + near_full_exit_fraction: 0.95, + loss_window_hours: 24, + streak_length: 3, + single_trade_loss_threshold: -0.3, + streak_sum_loss_threshold: -0.5, + cooldown_minutes: 30, +}; From 3cc150508dc55a2154893db418f55a9cdfa78534 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 8 Sep 2026 11:57:59 +0200 Subject: [PATCH 2/4] Using osias backend --- src/shared/config.ts | 4 ++-- wxt.config.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/shared/config.ts b/src/shared/config.ts index 426f95b..0b0db0a 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -7,5 +7,5 @@ // Port 8080, deliberately not 3000 — that's WXT/Vite's dev server port for // this extension, and colliding with it breaks the dev popup silently (its // script tags point at Vite, but the backend answers instead). -export const BACKEND_HTTP_URL = 'http://localhost:8080'; -export const BACKEND_WS_URL = 'ws://localhost:8080/ws'; +export const BACKEND_HTTP_URL = 'https://api.osias.trade'; +export const BACKEND_WS_URL = 'https://api.osias.trade/ws'; diff --git a/wxt.config.ts b/wxt.config.ts index 4c3e1a2..5af1d14 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -1,3 +1,4 @@ +import { BACKEND_HTTP_URL } from '@/shared/config'; import { defineConfig } from 'wxt'; // See https://wxt.dev/api/config.html @@ -13,7 +14,7 @@ export default defineConfig({ // axiom.trade: the site adapter target. localhost:8080: the Nexa backend // (dev only — swap/extend for the real backend host before shipping). // Not 3000 — that's this extension's own Vite dev server port. - host_permissions: ['https://axiom.trade/*', 'http://localhost:8080/*'], + host_permissions: ['https://axiom.trade/*', BACKEND_HTTP_URL + "/*"], browser_specific_settings: { gecko: { // Placeholder id for local/dev builds; replace before publishing to AMO. From bee164ebf9581374f8052647c28f2bea9e3bd0b6 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 8 Sep 2026 12:02:44 +0200 Subject: [PATCH 3/4] Fix WebSocket URL scheme and drop stale dev-only CSP override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BACKEND_WS_URL was set to https://, but new WebSocket() requires a ws://wss:// scheme and throws a SyntaxError otherwise. Also removes the Firefox-only CSP override that worked around Firefox upgrading a plaintext ws:// dev connection to wss:// — now that the backend is real wss:// behind TLS, there's nothing left to upgrade. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tn7uDYjTuZEbLPwpEiSCUw --- CLAUDE.md | 4 ++-- src/shared/config.ts | 11 +++-------- wxt.config.ts | 18 +++--------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dc9d258..d2ac90c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -87,8 +87,8 @@ Note the naming mismatch with the wire protocol below: the frontend's internal ` ## Backend connection (implemented) -- `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, currently hardcoded to `localhost:8080` (dev only; `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here). Deliberately not `:3000` — that's this extension's own Vite dev server port (`npm run dev`), and running the backend on the same port breaks the dev popup silently: its script tags point at Vite, but the backend answers instead, so nothing ever renders. If you see a blank popup with `http://localhost:3000/...` script tags in "View Page Source" that 404 or return something unexpected, this port collision is the first thing to check. -- **Firefox-only CSP override** in `wxt.config.ts`: Firefox's *implicit* default extension-pages CSP includes `upgrade-insecure-requests`, which silently rewrites the WS client's `ws://localhost:8080/ws` connection to `wss://` and breaks it against the plaintext local dev backend (no TLS in dev, deliberately — see backend/CLAUDE.md). Symptom: `Content-Security-Policy: Upgrading insecure request 'ws://...' to use 'wss'` in the console, followed by a failed connection, no other error. Fixed by declaring an explicit `content_security_policy.extension_pages` (otherwise identical to Firefox's own default) for the Firefox build only — an explicit CSP replaces the implicit one entirely, dropping the upgrade directive. Chrome doesn't have this behavior, so the override is gated on `browser === 'firefox'` in the manifest function. If the real deployed backend ever moves to plain `ws://` too (vs. `wss://` behind a real domain), this override needs to travel with it; if the backend gets TLS, this whole override becomes unnecessary and should be removed rather than left as dead configuration. +- `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, pointed at the real deployed backend (`api.osias.trade`, TLS). `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here. +- The Firefox-only CSP override that used to live in `wxt.config.ts` (working around Firefox upgrading a plaintext `ws://` dev connection to `wss://`) has been removed now that the backend is real `wss://` behind TLS — `upgrade-insecure-requests` has nothing to upgrade. Re-add it, scoped to `browser === 'firefox'`, only if a plaintext dev backend comes back into the loop. - `src/background/backend-client.ts` — session storage only (`getSessionToken()`/`getStoredSession()`/`storeSession(token, walletAddress)`/`clearSessionToken()`). Stores the wallet address alongside the token, not just the token — see "Session storage tracks which wallet it belongs to" below for why. Getting a token in the first place is `wallet-auth.ts`'s job. - `src/background/wallet-auth.ts` — `handleWalletConnected()` runs the REST auth flow (`POST /auth/nonce` → Phantom signature → `POST /auth/verify` → session token) once a content script reports a connected wallet. Also `requestWalletReconnect()` (silent reconnect after the backend invalidates a session), `requestWalletDisconnect()` (sign-out), `requestAccountSwitch()` (explicit account switch) — all three just message whichever tabs are on a supported site; `wallet-connect.ts` in the content script does the actual work. - `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`. `connectWsClient()` returns a controller with `reconnectNow()` so the background script can short-circuit the backoff wait right after a fresh token arrives. Auth failures (`4001` close, `auth_expired`/`session_revoked` errors) do **not** auto-retry with backoff — they call `onAuthExpired()` instead, since retrying with a known-bad token can't succeed; only real disconnects (network drop, backgrounded browser) use the protocol's suggested backoff schedule. diff --git a/src/shared/config.ts b/src/shared/config.ts index 0b0db0a..6fff039 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -1,11 +1,6 @@ /** - * Nexa backend location. Dev-only default — swap for the real deployed - * backend host before shipping (see backend/CLAUDE.md, "Relationship to the - * frontend"). `host_permissions` in wxt.config.ts must be kept in sync with - * this host. + * Nexa backend location. `host_permissions` in wxt.config.ts must be kept in + * sync with this host. */ -// Port 8080, deliberately not 3000 — that's WXT/Vite's dev server port for -// this extension, and colliding with it breaks the dev popup silently (its -// script tags point at Vite, but the backend answers instead). export const BACKEND_HTTP_URL = 'https://api.osias.trade'; -export const BACKEND_WS_URL = 'https://api.osias.trade/ws'; +export const BACKEND_WS_URL = 'wss://api.osias.trade/ws'; diff --git a/wxt.config.ts b/wxt.config.ts index 5af1d14..d921c65 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -7,13 +7,11 @@ export default defineConfig({ modules: ['@wxt-dev/module-react'], // Target Manifest V3 on both Chromium and Firefox (modern Firefox / Zen support it). manifestVersion: 3, - manifest: ({ browser }) => ({ + manifest: { name: 'Nexa', description: 'Your blockchain powered agent to help with your trading emotions.', permissions: ['storage'], - // axiom.trade: the site adapter target. localhost:8080: the Nexa backend - // (dev only — swap/extend for the real backend host before shipping). - // Not 3000 — that's this extension's own Vite dev server port. + // axiom.trade: the site adapter target. The other entry is the Nexa backend. host_permissions: ['https://axiom.trade/*', BACKEND_HTTP_URL + "/*"], browser_specific_settings: { gecko: { @@ -21,15 +19,5 @@ export default defineConfig({ id: 'nexa-extension@nexa-sol.dev', }, }, - // Firefox's implicit default extension-pages CSP includes - // upgrade-insecure-requests, which silently rewrites our ws:// WS client - // connections to wss:// and breaks them against the plaintext local dev - // backend (no TLS in dev — see backend/CLAUDE.md). Declaring our own CSP - // (identical to the standard default otherwise) replaces Firefox's - // implicit one and drops that directive. Chrome doesn't have this - // behavior, so this is Firefox-only. - ...(browser === 'firefox' - ? { content_security_policy: { extension_pages: "script-src 'self'; object-src 'self'" } } - : {}), - }), + }, }); From e3d1e458433c2dc00f53e1a6f9ff129999d56dd7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Tue, 8 Sep 2026 12:07:21 +0200 Subject: [PATCH 4/4] Fix @ alias not resolving inside wxt.config.ts The @/ path alias is only wired up for the app bundle via WXT's generated tsconfig; wxt.config.ts itself runs outside that resolution, so the aliased import of BACKEND_HTTP_URL failed there. Use a relative import instead. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tn7uDYjTuZEbLPwpEiSCUw --- wxt.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wxt.config.ts b/wxt.config.ts index d921c65..492867f 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -1,4 +1,4 @@ -import { BACKEND_HTTP_URL } from '@/shared/config'; +import { BACKEND_HTTP_URL } from './src/shared/config'; import { defineConfig } from 'wxt'; // See https://wxt.dev/api/config.html