Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/console/src/app/CommandBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function CommandBar() {
<>
<button
onClick={() => setOpen(true)}
className="flex h-[34px] w-[300px] flex-none items-center gap-2 whitespace-nowrap rounded-[9px] border border-border bg-bg px-3 text-[13px] text-[#9a9ea3] outline-none transition hover:border-[#cfd2cc] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20"
className="flex h-[34px] w-full flex-1 min-w-0 items-center gap-2 whitespace-nowrap rounded-[9px] border border-border bg-bg px-3 text-[13px] text-[#9a9ea3] outline-none transition hover:border-[#cfd2cc] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 w-full is redundant alongside flex-1 in a flex context. flex-1 min-w-0 is sufficient — the initial flex-basis: 0% from flex-1 already drives the growth, and w-full can cause the element to briefly claim 100% width before flex distribution kicks in. Removing w-full is cleaner.

Suggested change
className="flex h-[34px] w-full flex-1 min-w-0 items-center gap-2 whitespace-nowrap rounded-[9px] border border-border bg-bg px-3 text-[13px] text-[#9a9ea3] outline-none transition hover:border-[#cfd2cc] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20"
className="flex h-[34px] flex-1 min-w-0 items-center gap-2 whitespace-nowrap rounded-[9px] border border-border bg-bg px-3 text-[13px] text-[#9a9ea3] outline-none transition hover:border-[#cfd2cc] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20"
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/app/CommandBar.tsx
Line: 56

Comment:
`w-full` is redundant alongside `flex-1` in a flex context. `flex-1 min-w-0` is sufficient — the initial `flex-basis: 0%` from `flex-1` already drives the growth, and `w-full` can cause the element to briefly claim 100% width before flex distribution kicks in. Removing `w-full` is cleaner.

```suggestion
        className="flex h-[34px] flex-1 min-w-0 items-center gap-2 whitespace-nowrap rounded-[9px] border border-border bg-bg px-3 text-[13px] text-[#9a9ea3] outline-none transition hover:border-[#cfd2cc] focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/20"
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Cursor Fix in Codex

data-testid="command-open"
>
<Search size={15} className="flex-none" />
Expand Down
85 changes: 21 additions & 64 deletions apps/console/src/app/NetworkMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/**
* Top-bar network switcher + chain info. The badge is now a button: click it to
* switch Fuji / BenzoNet / mainnet and see the live chain details (id, RPC,
* explorer). Selection persists locally; each network is branded with its mark.
* Top-bar network indicator + chain info — **read-only, not a switcher**. Benzo
* Console runs on a single chain (the permissioned BenzoNet L1 by default; pinned at
* build time), so this shows the active network's identity, environment, and details
* (chain id / RPC / explorer). There is no network selection: the console isn't
* multi-chain, and a "switcher" that couldn't actually re-point the app would be a lie.
*/
import { useEffect, useRef, useState } from "react";
import { Check, ChevronDown } from "lucide-react";
import { ChevronDown } from "lucide-react";
import { AnimatePresence, motion } from "framer-motion";
import { BENZO_EXPLORER_BY_NETWORK, chainForNetwork, networkFromEnv, type BenzoNetwork } from "@benzo/config";
import { BENZO_EXPLORER_BY_NETWORK, chainForNetwork, type BenzoNetwork } from "@benzo/config";
import { NETWORK, NETWORK_ENV_BY_NETWORK, NETWORK_LABEL_BY_NETWORK, type NetworkEnv } from "../lib/network";
import { AvalancheMark, Logo } from "../ui/Logo";

Expand All @@ -17,7 +19,7 @@ function envToneCls(env: NetworkEnv): string {
: "border-warning/30 bg-warning/12 text-[#9a6b12]";
}

/** Explicit environment badge for each option in the picker — Testnet / Mainnet / Permissioned L1. */
/** Explicit environment badge — Testnet / Mainnet / Permissioned L1. */
function EnvBadge({ env }: { env: NetworkEnv }) {
return (
<span className={`flex-none rounded-full border px-1.5 py-px text-[10px] font-semibold uppercase tracking-wide ${envToneCls(env)}`}>
Expand All @@ -26,9 +28,6 @@ function EnvBadge({ env }: { env: NetworkEnv }) {
);
}

const NETWORKS: BenzoNetwork[] = ["fuji", "benzonet", "avalanche"];
const STORAGE_KEY = "benzo.console.network";

function chainInfo(n: BenzoNetwork) {
const c = chainForNetwork(n);
return {
Expand All @@ -53,28 +52,9 @@ function Mark({ network, size }: { network: BenzoNetwork; size: number }) {
}

export function NetworkMenu({ live }: { live: boolean }) {
const [network, setNetwork] = useState<BenzoNetwork>(() => {
// Defensive read: a tampered / stale localStorage value (older key format)
// must not throw out of the initializer and crash the whole shell — fall
// back to the build-time NETWORK, matching the guarded write below.
try {
const stored = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY) : null;
return stored ? networkFromEnv(stored) : NETWORK;
} catch {
return NETWORK;
}
});
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
try {
localStorage.setItem(STORAGE_KEY, network);
} catch {
/* ignore */
}
}, [network]);

useEffect(() => {
if (!open) return;
const onDown = (e: MouseEvent) => {
Expand All @@ -89,17 +69,12 @@ export function NetworkMenu({ live }: { live: boolean }) {
};
}, [open]);

const info = chainInfo(network);
// SAFETY: the persistent chip (tone, label, heartbeat) is locked to the
// BUILD-TIME `NETWORK` — never the user's dropdown selection. The dropdown only
// browses other networks' chain details (id/RPC/explorer); browsing to
// "Avalanche" on a Fuji build must NOT flip the safety signal to green while the
// app is still talking to the testnet. `env` here is the real environment.
// Everything is keyed to the single build-time NETWORK — there is no selection.
const info = chainInfo(NETWORK);
const env = NETWORK_ENV_BY_NETWORK[NETWORK];
// Chrome by ENVIRONMENT (never by liveness): amber for testnet / permissioned L1,
// green only for real-money mainnet. A green "Live" chip on a testnet is the bug we
// are killing. Liveness is a separate axis: a subtle heartbeat dot when connected,
// and a red "Offline · …" state when the chain is unreachable.
// green only for real-money mainnet. Liveness is a separate axis — a subtle heartbeat
// when connected, a red "Offline · …" when the chain is unreachable.
const chipTone = live ? envToneCls(env) : "border-danger/30 bg-danger/10 text-[#b4232a]";
const chipLabel = live ? env.chip : `Offline · ${env.badge}`;

Expand All @@ -125,37 +100,19 @@ export function NetworkMenu({ live }: { live: boolean }) {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -6, scale: 0.97 }}
transition={{ duration: 0.16 }}
role="menu"
data-testid="network-menu"
className="absolute right-0 top-full z-50 mt-2 w-72 origin-top-right rounded-2xl border border-border bg-surface p-3 shadow-[0_16px_40px_rgba(25,40,55,0.14)]"
>
<div className="mb-2 px-1 text-[11px] font-semibold uppercase tracking-[0.06em] text-[#a3a7ac]">Network</div>
<div className="flex flex-col gap-1">
{NETWORKS.map((n) => {
const on = n === network;
const ci = chainInfo(n);
return (
<button
key={n}
type="button"
role="menuitemradio"
aria-checked={on}
onClick={() => setNetwork(n)}
data-testid={`network-menu-${n}`}
className={`flex items-center gap-2.5 rounded-xl px-2.5 py-2 text-left outline-none transition focus-visible:ring-2 focus-visible:ring-primary/40 ${on ? "bg-primary/[0.08]" : "hover:bg-[#f4f3ef]"}`}
>
<Mark network={n} size={24} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate text-[13px] font-semibold text-fg">{ci.label}</span>
<EnvBadge env={NETWORK_ENV_BY_NETWORK[n]} />
</div>
<div className="truncate text-[11px] text-muted">{ci.kind}</div>
</div>
{on ? <Check size={15} className="flex-none text-primary" /> : null}
</button>
);
})}
<div className="flex items-center gap-2.5 rounded-xl bg-primary/[0.06] px-2.5 py-2">
<Mark network={NETWORK} size={24} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate text-[13px] font-semibold text-fg">{info.label}</span>
<EnvBadge env={env} />
</div>
<div className="truncate text-[11px] text-muted">{info.kind}</div>
</div>
</div>
<div className="mt-3 space-y-0.5 rounded-xl border border-border bg-bg p-2.5 text-[11.5px]" data-testid="network-chain-info">
<InfoRow k="Chain ID" v={String(info.id)} />
Expand Down
1 change: 0 additions & 1 deletion apps/console/src/app/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ export function Shell() {
</AnimatePresence>
</div>
<CommandBar />
<div className="flex-1" />
<NetworkMenu live={live} />
<button onClick={toggleMasked} aria-label="Toggle amount masking" data-testid="mask-toggle" className="flex h-[34px] w-[34px] flex-none items-center justify-center rounded-[9px] border border-border text-[#6b6f74] outline-none transition hover:bg-[#f4f3ef] focus-visible:ring-2 focus-visible:ring-primary/40 active:scale-95">
{masked ? <EyeOff size={17} /> : <Eye size={17} />}
Expand Down
6 changes: 3 additions & 3 deletions apps/console/src/lib/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ describe("console money formatting", () => {
expect(formatAddress("0x1234567890abcdef1234567890abcdef12345678")).toBe("0x12…5678");
expect(formatAddress("short")).toBe("short");
});
it("builds Avalanche Fuji explorer links by default", () => {
expect(explorerTxUrl("abc123")).toBe("https://testnet.snowtrace.io/tx/abc123");
expect(explorerContractUrl("0xabc")).toBe("https://testnet.snowtrace.io/address/0xabc");
it("builds BenzoNet explorer links by default (the console's L1)", () => {
expect(explorerTxUrl("abc123")).toBe("https://explorer.benzo.space/tx/abc123");
expect(explorerContractUrl("0xabc")).toBe("https://explorer.benzo.space/address/0xabc");
});
});
6 changes: 4 additions & 2 deletions apps/console/src/lib/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {

const env = import.meta.env as unknown as Record<string, string | undefined>;

/** "fuji" (default) | "benzonet" | "avalanche". */
export const NETWORK: BenzoNetwork = networkFromEnv(env.VITE_CHAIN_ENV ?? env.VITE_BENZO_NETWORK);
/** The network this console runs on. Benzo Console is a managed enterprise product
* on the permissioned **BenzoNet** L1, so that's the default when no build env pins
* one; "fuji" / "avalanche" are still selectable via VITE_CHAIN_ENV for other builds. */
export const NETWORK: BenzoNetwork = networkFromEnv(env.VITE_CHAIN_ENV ?? env.VITE_BENZO_NETWORK ?? "benzonet");

export const CHAIN = chainForNetwork(NETWORK);

Expand Down
3 changes: 1 addition & 2 deletions apps/console/src/screens/AuditLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,7 @@ export function AuditLog() {
<span className="text-muted">–</span>
<Input type="date" aria-label="To date" value={dateTo} onChange={(e) => setDateTo(e.target.value)} data-testid="audit-to" />
</div>
<div className="flex items-center justify-between gap-2 sm:col-span-2 lg:col-span-3">
<span className="t-helper">Times shown in your local timezone</span>
<div className="flex items-center justify-end gap-2 sm:col-span-2 lg:col-span-3">
<Button variant="outline" size="sm" onClick={exportCsv} disabled={filtered.length === 0} data-testid="audit-export">
<Download size={14} /> Export CSV
</Button>
Expand Down
Loading