Fix UX audit gaps: tablet nav, footer, wallet connect, legacy demotion.
Some checks failed
Deploy Explorer Live / deploy (push) Failing after 13s
Validate Explorer / frontend (push) Successful in 1m29s
Validate Explorer / smoke-e2e (push) Failing after 2m27s

Close the 1024–1279px nav dead zone, align ops/footer labels, split homepage quick links, route successful wallet connect to /wallet with inline errors, add WETH to ops sub-nav, and demote legacy SPA with noindex plus banner.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-22 22:30:35 -07:00
parent b213c6547d
commit 4fac5e4856
8 changed files with 86 additions and 32 deletions

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<meta http-equiv="refresh" content="0; url=/">
<link rel="canonical" href="/">
<title>DBIS Explorer</title>
@@ -32,7 +33,7 @@
<h1>DBIS Explorer</h1>
<p>Redirecting to the current Chain 138 explorer…</p>
<p><a href="/">Continue to explorer</a></p>
<p><a href="/legacy/index.html">Open legacy SPA fallback</a></p>
<p class="legacy-fallback"><small>Legacy SPA fallback (deprecated): <a href="/legacy/index.html">/legacy/</a></small></p>
</main>
<script>
if (window.location.pathname === '/index.html') {

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
@@ -1217,6 +1218,9 @@
</style>
</head>
<body>
<div id="legacy-deprecation-banner" role="status" style="position:sticky;top:0;z-index:9999;padding:0.75rem 1rem;background:#7c2d12;color:#ffedd5;font:500 0.875rem/1.4 system-ui,sans-serif;text-align:center;border-bottom:1px solid #ea580c;">
Deprecated legacy explorer UI. <a href="/" style="color:#fdba74;text-decoration:underline;">Switch to the current DBIS Explorer</a>.
</div>
<script>
(function() {
function doToggleNav() {

View File

@@ -35,12 +35,22 @@ export default function Footer() {
<ul className="space-y-2 text-sm">
<li><Link className={footerLinkClass} href="/search">Search</Link></li>
<li><Link className={footerLinkClass} href="/docs">Documentation</Link></li>
<li><Link className={footerLinkClass} href="/bridge">Bridge Monitoring</Link></li>
<li><Link className={footerLinkClass} href="/liquidity">Liquidity Access</Link></li>
<li><Link className={footerLinkClass} href="/routes">Routes</Link></li>
<li><Link className={footerLinkClass} href="/operations">Operations Hub</Link></li>
<li><Link className={footerLinkClass} href="/blocks">Blocks</Link></li>
<li><Link className={footerLinkClass} href="/transactions">Transactions</Link></li>
<li><Link className={footerLinkClass} href="/tokens">Tokens</Link></li>
<li><Link className={footerLinkClass} href="/addresses">Addresses</Link></li>
<li><Link className={footerLinkClass} href="/watchlist">Watchlist</Link></li>
<li><Link className={footerLinkClass} href="/access">Account access</Link></li>
<li><Link className={footerLinkClass} href="/wallet">Wallet tools</Link></li>
<li><Link className={footerLinkClass} href="/operations">Operations hub</Link></li>
<li><Link className={footerLinkClass} href="/bridge">Bridge</Link></li>
<li><Link className={footerLinkClass} href="/routes">Routes</Link></li>
<li><Link className={footerLinkClass} href="/liquidity">Liquidity</Link></li>
<li><Link className={footerLinkClass} href="/pools">Pools</Link></li>
<li><Link className={footerLinkClass} href="/analytics">Analytics</Link></li>
<li><Link className={footerLinkClass} href="/operator">Operator</Link></li>
<li><Link className={footerLinkClass} href="/system">System</Link></li>
<li><Link className={footerLinkClass} href="/weth">WETH</Link></li>
<li><a className={footerLinkClass} href="/privacy.html">Privacy Policy</a></li>
<li><a className={footerLinkClass} href="/terms.html">Terms of Service</a></li>
<li><a className={footerLinkClass} href="/acknowledgments.html">Acknowledgments</a></li>

View File

@@ -364,6 +364,7 @@ function UiModeToggle({ mobile = false }: { mobile?: boolean }) {
function AccountButton({
walletSession,
connectingWallet,
connectError,
onConnect,
onCopyAddress,
onSwitchWallet,
@@ -371,6 +372,7 @@ function AccountButton({
}: {
walletSession: WalletAccessSession | null
connectingWallet: boolean
connectError?: string | null
onConnect: () => void
onCopyAddress: () => void
onSwitchWallet: () => void
@@ -385,7 +387,7 @@ function AccountButton({
},
{
href: '/wallet',
label: 'Settings',
label: 'Wallet tools',
description: 'Review network, token-list, and wallet configuration guidance.',
},
{
@@ -407,14 +409,21 @@ function AccountButton({
if (!walletSession) {
return (
<button
type="button"
onClick={onConnect}
className="inline-flex items-center gap-2 rounded-2xl bg-gray-950 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:bg-white dark:text-gray-950 dark:hover:bg-gray-100 dark:focus-visible:ring-offset-gray-900"
>
<span className="inline-flex h-2.5 w-2.5 rounded-full bg-emerald-400" aria-hidden />
<span>{connectingWallet ? 'Connecting…' : 'Connect Wallet'}</span>
</button>
<div className="flex flex-col items-end gap-1">
<button
type="button"
onClick={onConnect}
className="inline-flex items-center gap-2 rounded-2xl bg-gray-950 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 dark:bg-white dark:text-gray-950 dark:hover:bg-gray-100 dark:focus-visible:ring-offset-gray-900"
>
<span className="inline-flex h-2.5 w-2.5 rounded-full bg-emerald-400" aria-hidden />
<span>{connectingWallet ? 'Connecting…' : 'Connect Wallet'}</span>
</button>
{connectError ? (
<p role="alert" className="max-w-xs text-right text-xs text-red-600 dark:text-red-400">
{connectError}
</p>
) : null}
</div>
)
}
@@ -469,6 +478,7 @@ export default function Navbar() {
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false)
const [walletSession, setWalletSession] = useState<WalletAccessSession | null>(null)
const [connectingWallet, setConnectingWallet] = useState(false)
const [walletConnectError, setWalletConnectError] = useState<string | null>(null)
const mobilePanelId = useId()
const isExploreActive =
@@ -528,13 +538,14 @@ export default function Navbar() {
const handleConnectWallet = async () => {
try {
setConnectingWallet(true)
setWalletConnectError(null)
await accessApi.connectWalletSession()
router.push('/access')
setMobileMenuOpen(false)
router.push('/wallet')
} catch (error) {
console.error('Wallet connect failed', error)
router.push('/access')
setMobileMenuOpen(false)
const message = error instanceof Error ? error.message : 'Wallet connection failed.'
setWalletConnectError(message)
} finally {
setConnectingWallet(false)
}
@@ -588,13 +599,13 @@ export default function Navbar() {
)
const operationsItems: MenuItem[] = useMemo(
() => [
{ href: '/operations', label: 'Operations Hub', description: 'Open the consolidated operator surface for live support workflows.' },
{ href: '/bridge', label: 'Bridge Monitoring', description: 'Inspect relay lanes, queue posture, and bridge trace tooling.' },
{ href: '/operations', label: 'Operations hub', description: 'Open the consolidated operator surface for live support workflows.' },
{ href: '/bridge', label: 'Bridge', description: 'Inspect relay lanes, queue posture, and bridge trace tooling.' },
{ href: '/routes', label: 'Routes', description: 'Review live route coverage, same-chain lanes, and bridge paths.' },
{ href: '/liquidity', label: 'Liquidity', description: 'Check planner-backed route access and live liquidity posture.' },
{ href: '/system', label: 'System', description: 'Inspect topology, RPC capability, and public integration inventory.' },
{ href: '/operator', label: 'Operator Surface', description: 'Open planner, route, and relay shortcuts in one public page.' },
{ href: '/weth', label: 'WETH References', description: 'Review wrapped-asset references and bridge-oriented WETH context.' },
{ href: '/operator', label: 'Operator', description: 'Open planner, route, and relay shortcuts in one public page.' },
{ href: '/weth', label: 'WETH', description: 'Review wrapped-asset references and bridge-oriented WETH context.' },
{ href: '/chain138-command-center.html', label: 'Command Center', description: 'Open the visual command-center reference.', external: true },
],
[],
@@ -727,12 +738,13 @@ export default function Navbar() {
</div>
</nav>
<div className="ml-auto hidden items-center gap-3 lg:flex">
<div className="ml-auto hidden items-center gap-3 xl:flex">
<SearchControl active={isSearchActive} onSelect={() => setCommandPaletteOpen(true)} />
<UiModeToggle />
<AccountButton
walletSession={walletSession}
connectingWallet={connectingWallet}
connectError={walletConnectError}
onConnect={() => void handleConnectWallet()}
onCopyAddress={() => void handleCopyAddress()}
onSwitchWallet={() => void handleSwitchWallet()}
@@ -740,7 +752,7 @@ export default function Navbar() {
/>
</div>
<div className="ml-auto flex items-center gap-2 lg:hidden">
<div className="ml-auto flex items-center gap-2 xl:hidden">
{walletSession ? (
<Link
href="/access"
@@ -793,7 +805,7 @@ export default function Navbar() {
{mobileMenuOpen ? (
<div
id={mobilePanelId}
className="border-t border-gray-200 py-4 dark:border-gray-800 lg:hidden"
className="border-t border-gray-200 py-4 dark:border-gray-800 xl:hidden"
>
<div className="flex flex-col gap-4">
<SearchControl
@@ -806,6 +818,12 @@ export default function Navbar() {
/>
<UiModeToggle mobile />
{walletConnectError ? (
<p role="alert" className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-300">
{walletConnectError}
</p>
) : null}
<div className="grid gap-4">
<div>
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-500 dark:text-gray-400">

View File

@@ -36,7 +36,10 @@ export default function ContractVerificationCallout({ address, verified }: Contr
</Link>
</li>
<li>
<strong>Native Blockscout UI (LAN):</strong> use contract verification on the VM Blockscout instance when the custom explorer UI does not expose the form.
<strong>Explorer contract tab:</strong>{' '}
<Link href={`/addresses/${address}`} className="text-primary-600 hover:underline">
Open this address and review the Contract tab
</Link>
</li>
</ul>
</Card>

View File

@@ -1003,10 +1003,13 @@ export default function Home({
Wallet & MetaMask
</Link>
<Link href="/routes" className="rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-primary-600 hover:border-primary-400 dark:border-gray-800">
Liquidity & routes
Routes
</Link>
<Link href="/liquidity" className="rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-primary-600 hover:border-primary-400 dark:border-gray-800">
Liquidity
</Link>
<Link href="/bridge" className="rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-primary-600 hover:border-primary-400 dark:border-gray-800">
Bridge monitoring
Bridge
</Link>
<Link href="/analytics" className="rounded-xl border border-gray-200 px-4 py-3 text-sm font-semibold text-primary-600 hover:border-primary-400 dark:border-gray-800">
Analytics

View File

@@ -332,6 +332,11 @@ export const explorerOperationsSurfaces: ExplorerOperationsSurface[] = [
label: 'Liquidity',
description: 'PMM access points and planner capabilities.',
},
{
href: '/weth',
label: 'WETH',
description: 'Wrapped-asset references and bridge context.',
},
{
href: '/pools',
label: 'Pools',

View File

@@ -38,17 +38,26 @@ test.describe('Explorer sprint smoke', () => {
await expect(page.getByText(/CCIP route catalog/i).first()).toBeVisible({ timeout: 15000 })
})
test('operations hub shows surface navigation', async ({ page }) => {
test('operations hub shows WETH in surface navigation', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 720 })
await page.goto(`${EXPLORER_URL}/operations`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await expect(page.getByRole('navigation', { name: /Operations surfaces/i })).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('link', { name: /Bridge/i }).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('navigation', { name: /Operations surfaces/i }).getByRole('link', { name: /^WETH$/i })).toBeVisible({ timeout: 10000 })
})
test('footer lists public API endpoints', async ({ page }) => {
test('footer lists public API endpoints and explorer surfaces', async ({ page }) => {
await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 })
await expect(page.getByRole('contentinfo').getByText(/Public APIs/i)).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('contentinfo').getByRole('link', { name: /Blockscout stats/i })).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('contentinfo').getByRole('link', { name: /Wallet tools/i })).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('contentinfo').getByRole('link', { name: /Account access/i })).toBeVisible({ timeout: 10000 })
})
test('tablet viewport exposes mobile navigation menu', async ({ page }) => {
await page.setViewportSize({ width: 1100, height: 800 })
await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 })
await expect(page.getByRole('button', { name: /Open navigation menu/i })).toBeVisible({ timeout: 10000 })
await page.getByRole('button', { name: /Open navigation menu/i }).click()
await expect(page.getByRole('link', { name: /Operations hub/i }).first()).toBeVisible({ timeout: 10000 })
})
test('analytics page shows track 3 surface note', async ({ page }) => {
@@ -63,9 +72,10 @@ test.describe('Explorer sprint smoke', () => {
await expect(page.getByText(/Track 4 public surface/i).first()).toBeVisible({ timeout: 10000 })
})
test('legacy SPA fallback loads', async ({ page }) => {
test('legacy SPA fallback loads with deprecation banner', async ({ page }) => {
await page.goto(`${EXPLORER_URL}/legacy/index.html`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await expect(page).toHaveTitle(/DBIS Explorer/i)
await expect(page.getByText(/Deprecated legacy explorer UI/i)).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('heading', { name: /Latest Blocks/i })).toBeVisible({ timeout: 15000 })
})