chore(aegis): 0.8.4 — network chip on its own row + coin-catalogue search

Two follow-ups on 0.8.0's currency picker after a testing pass:

- The network label was a tiny gray suffix next to the wallet name in
  the header, so "which network am I on" wasn't obvious at a glance.
  Now it's a separate row of one clickable chip under the coin ticker;
  clicking jumps into the browse:chain view where the network strip
  actually switches network.

- Clicking the header opened a "Pick a coin" pane that only listed
  coins the user already had a wallet for — a first-time user with a
  single BCH wallet would see one row and no way to add more. Now it
  shows every supported coin behind a search box; owned coins jump to
  the wallet list, unowned coins jump straight into the create-wallet
  flow with that coin's network group pre-expanded.
This commit is contained in:
Local Dev 2026-09-22 21:19:36 +02:00
parent b32b353db7
commit e03c728116
3 changed files with 143 additions and 29 deletions

View file

@ -1,7 +1,7 @@
{
"id": "aegis",
"name": "Aegis Wallet",
"version": "0.8.3",
"version": "0.8.4",
"category": "plugin",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
"author": "Silent Mode",

View file

@ -81,7 +81,24 @@
#drop .brnet .hint { color: currentColor; opacity: .65; font-size: 11px; }
.ttag { display: inline-block; font-size: 9.5px; letter-spacing: .06em; padding: 1px 5px; border-radius: 3px;
background: rgba(224,179,65,.18); color: #e0b341; font-weight: 700; vertical-align: middle; margin-left: 2px; }
#hNet { color: var(--dim); font-size: 11px; margin-left: 4px; font-weight: 500; }
/* 0.8.4: hNet became a proper chip on its own row (.netrow) so the
network the wallet is on is easy to spot AND easy to switch. */
.netrow { display: flex; margin-top: 6px; padding: 0 2px; }
.netchip { background: rgba(255,255,255,.05); border: 1px solid var(--line);
color: var(--mut); border-radius: 999px; padding: 3px 10px 3px 8px;
font: inherit; font-size: 11.5px; cursor: pointer; display: inline-flex;
align-items: center; gap: 6px; line-height: 1.2; }
.netchip:hover { border-color: var(--acid, #d6ff3d); color: var(--acid, #d6ff3d); }
.netchip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .7; }
/* Currency-picker search + all-coins catalogue (0.8.4). */
#drop .pickersearch { padding: 8px; border-bottom: 1px solid var(--line); }
#drop .pickersearch input { width: 100%; box-sizing: border-box; background: rgba(255,255,255,.04);
border: 1px solid var(--line); color: var(--ink); border-radius: 7px;
padding: 7px 10px; font: inherit; font-size: 12.5px; outline: none; }
#drop .pickersearch input:focus { border-color: rgb(from var(--acid, #d6ff3d) r g b / .55); }
#drop .catgroup { padding: 4px 6px 2px 10px; color: var(--dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; }
#drop .row.unowned { opacity: .8; }
#drop .row.unowned .v { color: var(--acid, #d6ff3d); font-weight: 600; font-size: 11px; }
.fiat { color: var(--dim); font-size: 12.5px; margin-left: 10px; font-weight: 500; letter-spacing: .2px; }
.portfolio { margin-top: 6px; color: var(--mut); font-size: 11.5px; }
.portfolio b { color: var(--ink); font-weight: 600; }
@ -500,7 +517,6 @@
<div class="t">
<span class="badge" id="hBadge"></span>
<span class="lbl" id="hLabel">Aegis Wallet</span>
<span id="hNet"></span>
<!-- Edit chip sits inline with the wallet name so it reads as
"edit THIS wallet". Was in picker-actions on the far right
until 0.7.2, which mixed poorly with the global + button. -->
@ -514,6 +530,12 @@
<button type="button" id="hAdd" class="chip" title="Add or import a wallet">+</button>
</div>
</div>
<!-- 0.8.4: network picker is its own row below the coin label so it
reads as "the network I'm on right now" instead of a tiny suffix
next to the wallet name. Click cycles / opens the network menu. -->
<div class="netrow" id="hNetRow" hidden>
<button type="button" class="netchip" id="hNet" title="Switch network"></button>
</div>
<div id="drop" hidden></div>
<div class="bal">
<div class="big">

View file

@ -378,6 +378,19 @@ $("pickerBtn").addEventListener("click", (e) => {
d.hidden = false;
fillPicker();
});
// 0.8.4: separate netchip row. Click jumps straight to the browse:chain
// view for the current wallet's chain — the network-chip strip at the
// top of that view is what actually switches networks.
$("hNet").addEventListener("click", (e) => {
e.stopPropagation();
const w = (state?.wallets || []).find((x) => x.id === state?.selectedWalletId);
if (!w) return;
pickerTab = "browse:" + w.chain;
const d = $("drop");
positionDropBelowTabs(d);
d.hidden = false;
fillPicker();
});
// + Add and ⋯ More chips moved from the wallet strip into the header
// (0.6.31). Same handlers as before — fillPicker for the Add-only picker,
// openMoreMenu for Import/Connect/About. Each stopsPropagation so the
@ -426,6 +439,11 @@ document.addEventListener("click", (e) => {
// user who opens the picker → picks Import → cancels → reopens returns to
// Wallets (the sane default).
let pickerTab = "wallets";
// 0.8.4: coin catalogue search query + one-shot preselected chain for the
// Add pane (set when the browse view routes into "add" for a specific
// unowned coin).
let browseQuery = "";
let pickerAddChain = null;
function fillPicker() {
const d = $("drop");
@ -508,42 +526,94 @@ function fillPicker() {
walletsByChain.get(w.chain).push(w);
}
if (!focused) {
// COINS: one row per chain user has a wallet for.
const rows = [];
for (const [chain, ws] of walletsByChain) {
// 0.8.4: full catalogue with search. Every supported chain is
// listed, whether the user already has a wallet on it or not.
// Rows for owned coins jump to that coin's wallet list; rows for
// unowned coins jump straight into the Add-wallet flow for that
// coin, so the header picker doubles as a fast on-ramp.
const q = String(browseQuery || "").trim().toLowerCase();
const matches = (c) => !q
|| String(c.chain || "").toLowerCase().includes(q)
|| String(c.label || "").toLowerCase().includes(q)
|| String(c.ticker || "").toLowerCase().includes(q)
|| String(c.short || "").toLowerCase().includes(q);
const ownedRows = [];
const otherRows = [];
for (const c of coins) {
if (!matches(c)) continue;
const ws = walletsByChain.get(c.chain) || [];
if (ws.length > 0) {
const first = ws[0];
const nets = Array.from(new Set(ws.map((w) => w.network)));
let active = activeNetworkByChain.get(chain);
let active = activeNetworkByChain.get(c.chain);
if (!active || !nets.includes(active)) active = nets.includes("mainnet") ? "mainnet" : nets[0];
const netLbl = networkLabelFor(chain, active, active);
const netLbl = networkLabelFor(c.chain, active, active);
const totalUnits = sumGroupUnits(ws.filter((w) => w.network === active));
const dec = first.decimals || 8;
const bal = fmtBig(totalUnits || 0, dec) + " " + esc(first.ticker || chain.toUpperCase());
const usd = usdOf(chain, totalUnits || 0, dec);
const dec = first.decimals || c.decimals || 8;
const bal = fmtBig(totalUnits || 0, dec) + " " + esc(first.ticker || c.ticker || c.chain.toUpperCase());
const usd = usdOf(c.chain, totalUnits || 0, dec);
const fiat = usd != null ? `<div class="fs">${esc(fmtFiat(usd))}</div>` : "";
const activeMark = ws.some((w) => w.id === state?.selectedWalletId) ? "on" : "";
rows.push(`<div class="row ${activeMark}" data-browse-chain="${esc(chain)}" style="cursor:pointer">
${logoSvg(first.logo, 22)}
ownedRows.push(`<div class="row ${activeMark}" data-browse-chain="${esc(c.chain)}" style="cursor:pointer">
${logoSvg(c.logo, 22)}
<div class="m">
<div class="l">${esc(first.coinLabel || chain.toUpperCase())} <span class="hint" style="font-weight:400">· ${ws.length} wallet${ws.length === 1 ? "" : "s"}${nets.length > 1 ? ` · ${esc(netLbl)}` : ""}</span></div>
<div class="s mono">${esc(chain)}</div>
<div class="l">${esc(c.label)} <span class="hint" style="font-weight:400">· ${ws.length} wallet${ws.length === 1 ? "" : "s"}${nets.length > 1 ? ` · ${esc(netLbl)}` : ""}</span></div>
<div class="s mono">${esc(c.ticker || c.chain)}</div>
</div>
<div class="v"><div>${bal}</div>${fiat}</div>
</div>`);
} else {
otherRows.push(`<div class="row unowned" data-browse-add="${esc(c.chain)}" style="cursor:pointer">
${logoSvg(c.logo, 22)}
<div class="m">
<div class="l">${esc(c.label)}</div>
<div class="s mono">${esc(c.ticker || c.chain)}</div>
</div>
<div class="v">+ Add</div>
</div>`);
}
}
d.innerHTML = `
<div class="droptabs">
<span style="flex:1;font-size:12.5px;color:var(--dim);padding-left:10px">Pick a coin</span>
<button class="closex" id="pickerClose" title="Close">✕</button>
</div>
<div class="pickersearch"><input id="pickerSearch" type="search" autocomplete="off" spellcheck="false"
placeholder="Search coins by name or ticker…" value="${esc(q)}"></div>
<div class="droppane" style="padding:6px">
${rows.length ? rows.join("") : `<div class="hint" style="padding:14px;text-align:center">No wallets yet. Use + to create one.</div>`}
${ownedRows.length ? `<div class="catgroup">Your wallets</div>${ownedRows.join("")}` : ""}
${otherRows.length ? `<div class="catgroup" style="margin-top:6px">Add a new wallet</div>${otherRows.join("")}` : ""}
${(!ownedRows.length && !otherRows.length) ? `<div class="hint" style="padding:14px;text-align:center">No coins match "${esc(q)}".</div>` : ""}
</div>`;
d.querySelectorAll("[data-browse-chain]").forEach((row) => row.addEventListener("click", (e) => {
e.stopPropagation();
pickerTab = "browse:" + row.dataset.browseChain;
fillPicker();
}));
d.querySelectorAll("[data-browse-add]").forEach((row) => row.addEventListener("click", (e) => {
e.stopPropagation();
const chain = row.dataset.browseAdd;
// Jump straight into the create-wallet flow for that coin. The
// Add pane keys off `pickerTab = "add"` and reads the target
// chain from a scratch field the coin-picker fills.
pickerTab = "add";
pickerAddChain = chain;
fillPicker();
}));
const searchEl = d.querySelector("#pickerSearch");
if (searchEl) {
searchEl.addEventListener("input", () => {
browseQuery = searchEl.value;
// Preserve caret across re-render.
const caret = searchEl.selectionStart;
fillPicker();
const s2 = d.querySelector("#pickerSearch");
if (s2) { s2.focus(); try { s2.setSelectionRange(caret, caret); } catch (_e) {} }
});
// Autofocus on first render, but not on every re-render — the
// re-focus above handles that. Guard with a data flag.
if (!searchEl.dataset.autof) { searchEl.dataset.autof = "1"; searchEl.focus(); }
}
const closeBtn = d.querySelector("#pickerClose");
if (closeBtn) closeBtn.addEventListener("click", (e) => { e.stopPropagation(); d.hidden = true; });
return;
@ -734,6 +804,21 @@ function fillPicker() {
group.hidden = !group.hidden;
r.querySelector(".caret").textContent = group.hidden ? "▸" : "▾";
}));
// 0.8.4: if the browse view routed here with a preselected chain (user
// clicked "+ Add" on an unowned coin), expand that coin's network
// group AND scroll it into view so the user sees which networks
// exist without a second click.
if (pickerTab === "add" && pickerAddChain) {
const target = pickerAddChain;
pickerAddChain = null;
const row = d.querySelector(`.coinrow[data-coin="${target.replace(/["\\]/g, "")}"]`);
const group = d.querySelector(`#netgroup-${target.replace(/["\\]/g, "")}`);
if (row && group) {
group.hidden = false;
const caret = row.querySelector(".caret"); if (caret) caret.textContent = "▾";
row.scrollIntoView({ block: "nearest" });
}
}
d.querySelectorAll("[data-add]").forEach((r) => r.addEventListener("click", async () => {
const [c, n] = r.dataset.add.split(":");
d.hidden = true;
@ -2586,9 +2671,16 @@ function render() {
// <wallet label> <coin · network + optional TEST tag>
$("hBadge").innerHTML = s?.meta?.logo ? logoSvg(s.meta.logo, 22) : logoSvg(null, 22);
$("hLabel").textContent = s?.label || "Aegis Wallet";
$("hNet").innerHTML = s?.meta
? `${esc(s.meta.coinLabel)} · ${esc(s.meta.networkLabel)}${s.meta.testnet ? " " + testnetTag() : ""}`
: "";
// 0.8.4: hNet is a chip on its own row. Show only the network name
// (coin name is already in hLabel above); hide the row when we don't
// have any wallet-context to describe yet.
if (s?.meta) {
$("hNet").innerHTML = `${esc(s.meta.networkLabel)}${s.meta.testnet ? " " + testnetTag() : ""}`;
$("hNetRow").hidden = false;
} else {
$("hNet").innerHTML = "";
$("hNetRow").hidden = true;
}
if (showLock) {
renderLockScreen(phase);
}