Firefox's implicit default extension-pages CSP includes
upgrade-insecure-requests, which rewrites the WS client's plain
ws://localhost:8080/ws connection to wss:// -- which nothing is
listening on, since the local dev backend has no TLS (deliberately;
see backend/CLAUDE.md). Symptom was silent: a CSP console message
about the upgrade, then a failed connection with no other signal.
Declaring an explicit content_security_policy.extension_pages in
wxt.config.ts (otherwise identical to Firefox's own default) replaces
the implicit one and drops the upgrade directive. Gated to
browser === 'firefox' since Chrome doesn't have this behavior and its
MV3 CSP can't be loosened this way regardless.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
Popup is now a real React app (App.tsx/main.tsx via createRoot),
wired through @wxt-dev/module-react. tsconfig.json needed an explicit
jsx: "react-jsx" -- WXT's generated .wxt/tsconfig.json doesn't set
it, so tsc --noEmit failed on JSX syntax even though the Vite build
itself was fine.
Root cause of the actually-reported bug (blank popup, predating the
React port too): the backend defaulted to port 3000, the same port
WXT's dev server uses for this extension. With both running, the
popup's script tags pointed at the Vite dev server but the backend
answered instead, so main.tsx never loaded -- "View Page Source"
showed raw unbundled dev-mode HTML pointing at localhost:3000.
Backend now binds :8080 (see backend commit), and
BACKEND_HTTP_URL/BACKEND_WS_URL + host_permissions here follow it.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B
Adds the wire-protocol WebSocket client (ws-client.ts) and the REST
auth flow (backend-client.ts) against the now-live backend, using a
locally-generated ed25519 keypair (identity.ts) as a stand-in wallet
signer until real wallet-extension integration is built.
lock-state.ts's setLockState is now applyLockState, called only by
the WS client on an incoming lock_state message -- the backend is
the sole source of truth for lock state, so there's no other writer
anymore. The popup's dev mock controls are replaced with a live
connection-status + lock-state display.
Verified end-to-end against the real backend (nonce -> verify -> /me
-> ws lock_state) using the same tweetnacl/bs58 libs shipped in the
extension.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B