Two of the five explorer brands pinned NEXT_PUBLIC_FONT_FAMILY to Geist off fonts.googleapis.com: every visitor to explore.lux.network and the SPC explorer was announced to Google on page load, and the two hosts rendered in a face the other three do not use. Dropping the pair leaves all five brands with the same env and the image's own faces. The branded matrix declared Basel Grotesk as the brand token for all four orgs, and the broker audit had already been switched to test for Zen while still calling the result hasGeist and printing it under a Geist label — a check whose name denied its own predicate. The README hero card asked for Inter.
153 lines
14 KiB
TypeScript
153 lines
14 KiB
TypeScript
// ---------------------------------------------------------------------------
|
||
// Branded web3-surface regression — the canonical surface × breakpoint table.
|
||
// ---------------------------------------------------------------------------
|
||
// ONE source of truth for every public web3 surface across all four orgs
|
||
// (Lux / Zoo / Hanzo / Pars). The spec (branded.spec.ts) is a pure function of
|
||
// this table: each surface is proven LIVE + FUNCTIONAL + correctly BRANDED at
|
||
// four breakpoints, with ZERO cross-brand leakage (the white-label invariant).
|
||
//
|
||
// A surface's CURRENT reality is encoded via `posture` + `flags`:
|
||
// • posture 'live' — a surface we own and operate; every check GATES
|
||
// (a new failure = a real regression → red) unless
|
||
// that check is listed in `flags`.
|
||
// • posture 'down' — documented-unreachable (e.g. SSL/DNS); nothing
|
||
// gates, the cell renders FLAG. Recovery is noted.
|
||
// • posture 'foreign-domain'— loads but serves a DIFFERENT company (owner
|
||
// domain-reclaim pending); nothing gates, FLAG.
|
||
// • flags[check] — a KNOWN, documented defect on a `live` surface.
|
||
// That single check is demoted from GATE→FLAG with
|
||
// a `reason` + a `flip` condition (when to delete
|
||
// the flag). Any OTHER check still gates. If a
|
||
// flagged check starts passing, the run reports the
|
||
// flag as STALE so the table self-cleans.
|
||
//
|
||
// This is the decomplected model: the spec never hard-codes "known broken"; it
|
||
// checks everything and consults this table to decide gate vs flag. Green stays
|
||
// green; documented defects carry a flip condition; anything new turns red.
|
||
|
||
export type Brand = 'Lux' | 'Zoo' | 'Hanzo' | 'Pars'
|
||
export type Posture = 'live' | 'down' | 'foreign-domain'
|
||
|
||
// The eight concrete checks the spec runs, mapping to the owner's 6-point rubric:
|
||
// http → (1) HTTP 200 / documented redirect
|
||
// title | favicon | leak → (2) correct branded <head> + ZERO cross-brand leak
|
||
// overflow → (3) no horizontal overflow (scrollWidth ≤ innerWidth)
|
||
// console → (4) no app-origin console errors (telemetry allow-listed)
|
||
// key → (5) the key interactive control is present + visible
|
||
// font → (6) brand font present (broken serif fallback = fail)
|
||
// Brand ACCENT (also rubric 6) is captured as an OBSERVATIONAL note, never gated —
|
||
// computed-accent extraction is inherently noisy across dark themes, so a false
|
||
// "accent" gate would be worse than useless. The note still surfaces real drift.
|
||
export type Check = 'http' | 'title' | 'favicon' | 'leak' | 'overflow' | 'console' | 'key' | 'font'
|
||
|
||
// A documented defect. `bps` optionally scopes the flag to specific breakpoints
|
||
// (e.g. an overflow that only occurs at mobile/tablet) — omit to cover all four,
|
||
// so any UN-covered breakpoint still HARD-gates that check.
|
||
export interface Flag { reason: string; flip: string; bps?: string[] }
|
||
|
||
export interface Surface {
|
||
id: string // stable slug — screenshot name + matrix row label
|
||
category: string // matrix section
|
||
brand: Brand
|
||
url: string
|
||
key: RegExp // accessible-name of the primary interactive control (visible at all breakpoints)
|
||
posture: Posture
|
||
flags: Partial<Record<Check, Flag>>
|
||
}
|
||
|
||
export const ORG_BRANDS: Brand[] = ['Lux', 'Zoo', 'Hanzo', 'Pars']
|
||
|
||
// Brand design tokens — used for the observational accent note + reporting.
|
||
export const BRAND_TOKENS: Record<Brand, { accent: string; font: string }> = {
|
||
Lux: { accent: 'violet', font: 'Zen' },
|
||
Zoo: { accent: 'green', font: 'Zen' },
|
||
Hanzo: { accent: 'orange', font: 'Zen' },
|
||
Pars: { accent: 'gold', font: 'Zen' },
|
||
}
|
||
|
||
// The four responsive breakpoints (implemented as Playwright projects).
|
||
export const BREAKPOINTS = [
|
||
{ name: '390', width: 390, height: 844 }, // mobile
|
||
{ name: '768', width: 768, height: 1024 }, // tablet
|
||
{ name: '1280', width: 1280, height: 800 }, // laptop
|
||
{ name: '1920', width: 1920, height: 1080 }, // desktop
|
||
] as const
|
||
|
||
// ── Documented flip condition ──────────────────────────────────────────────
|
||
// The webfont + html/body font rule shipped in @luxfi/bridge
|
||
// (pkg/bridge theme.css) and is LIVE on bridge.zoo.network + bridge.pars.network.
|
||
// It is NOT yet on bridge.lux.network: that host runs the Go all-in-one bridge
|
||
// image (bridge:e377ac59, ~370 commits stale) which go:embeds a pre-webfont SPA
|
||
// build, so <body> still falls back to Times. The other two bridges' font +
|
||
// description-leak flags are gone — fixed and verified live.
|
||
const BRIDGE_LUX_FONT: Flag = {
|
||
reason: 'body font falls back to Times — bridge.lux.network runs the stale Go all-in-one image (bridge:e377ac59) that embeds a pre-webfont SPA; the webfont fix is shipped in @luxfi/bridge and already live on the Zoo + Pars bridges',
|
||
flip: 'roll the lux-bridge deployment to a current cmd/bridge Go build (rebuilds + go:embeds app/bridge with the Zen webfont)',
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// THE TABLE
|
||
// ---------------------------------------------------------------------------
|
||
export const SURFACES: Surface[] = [
|
||
// ── Explorers ────────────────────────────────────────────────────────────
|
||
{ id: 'explore-lux', category: 'explorers', brand: 'Lux', url: 'https://explore.lux.network', key: /sign in|search|menu|chains|validators/i, posture: 'live', flags: {} },
|
||
{ id: 'explore-zoo', category: 'explorers', brand: 'Zoo', url: 'https://explore.zoo.network', key: /sign in|search|menu|chains|validators/i, posture: 'live', flags: {} },
|
||
{ id: 'explore-hanzo', category: 'explorers', brand: 'Hanzo', url: 'https://explore.hanzo.network', key: /sign in|search|menu|chains|validators/i, posture: 'live', flags: {} },
|
||
{ id: 'explore-pars', category: 'explorers', brand: 'Pars', url: 'https://explore.pars.network', key: /sign in|search|menu|chains|validators/i, posture: 'live', flags: {} },
|
||
|
||
// ── Wallets ──────────────────────────────────────────────────────────────
|
||
{ id: 'wallet-lux', category: 'wallets', brand: 'Lux', url: 'https://wallet.lux.network', key: /sign in with|connect|portfolio/i, posture: 'live', flags: {} },
|
||
{ id: 'wallet-zoo', category: 'wallets', brand: 'Zoo', url: 'https://wallet.zoo.network', key: /sign in with|connect|portfolio/i, posture: 'live', flags: {} },
|
||
|
||
// ── Identity ─────────────────────────────────────────────────────────────
|
||
{ id: 'id-lux', category: 'id', brand: 'Lux', url: 'https://id.lux.network', key: /sign in|create account|email/i, posture: 'live', flags: {} },
|
||
{ id: 'id-zoo', category: 'id', brand: 'Zoo', url: 'https://id.zoo.network', key: /sign in|create account|email/i, posture: 'live', flags: {} },
|
||
{ id: 'id-hanzo', category: 'id', brand: 'Hanzo', url: 'https://id.hanzo.network', key: /sign in|get started/i, posture: 'live', flags: {} },
|
||
|
||
// ── Votes / governance ───────────────────────────────────────────────────
|
||
{ id: 'lux-vote', category: 'votes', brand: 'Lux', url: 'https://lux.vote', key: /connect wallet|sign in/i, posture: 'live', flags: {
|
||
console: { reason: 'Safe getSafeInfo contract reads fail on every breakpoint; the shell renders and wallet control remains visible, but treasury metadata cannot load', flip: 'deploy the matching Safe contract/RPC configuration so getSafeInfo succeeds without app-origin console errors' },
|
||
} },
|
||
{ id: 'zoo-vote', category: 'votes', brand: 'Zoo', url: 'https://zoo.vote', key: /connect wallet|sign in/i, posture: 'live', flags: {
|
||
console: { reason: 'safe-client AxiosError on getTransfers/getNextNonce — treasury read fails (empty-key subgraph + safe-cgw lacks 200200 proposal route)', flip: 'point the Safe client at safe-cgw.lux.network with 200200 support + a real subgraph key so the treasury/nonce reads succeed' },
|
||
} },
|
||
{ id: 'hanzo-vote', category: 'votes', brand: 'Hanzo', url: 'https://hanzo.vote', key: /connect wallet|my daos/i, posture: 'live', flags: {
|
||
console: { reason: 'retiring Decent shell — 403/404 + safe-client errors, intermittent blank #root (Buffer-crash class)', flip: 'replaced by work.hanzo.network, or the shell is stabilized (no 4xx/pageerror on cold load)' },
|
||
key: { reason: 'retiring Decent shell currently renders without a visible wallet or DAO control at any supported breakpoint', flip: 'replace it with work.hanzo.network or restore a visible connect-wallet/My DAOs control' },
|
||
font: { reason: 'retiring Decent shell intermittently falls back to Times instead of the Hanzo brand face', flip: 'replace it with work.hanzo.network or ship and consistently apply the Zen webfont' },
|
||
} },
|
||
|
||
// ── Work boards ──────────────────────────────────────────────────────────
|
||
{ id: 'work-lux', category: 'work', brand: 'Lux', url: 'https://work.lux.network', key: /new suggestion|overview|combined board|open menu/i, posture: 'live', flags: {} },
|
||
{ id: 'work-hanzo', category: 'work', brand: 'Hanzo', url: 'https://work.hanzo.network', key: /new suggestion|overview|combined board|open menu/i, posture: 'live', flags: {} },
|
||
{ id: 'work-zoo', category: 'work', brand: 'Zoo', url: 'https://work.zoo.network', key: /new suggestion|overview|combined board|open menu/i, posture: 'live', flags: {} },
|
||
{ id: 'work-pars', category: 'work', brand: 'Pars', url: 'https://work.pars.network', key: /new suggestion|overview|combined board|open menu/i, posture: 'live', flags: {} },
|
||
|
||
// ── Bridges ──────────────────────────────────────────────────────────────
|
||
{ id: 'bridge-lux', category: 'bridges', brand: 'Lux', url: 'https://bridge.lux.network', key: /connect wallet/i, posture: 'live', flags: { font: BRIDGE_LUX_FONT } },
|
||
{ id: 'bridge-zoo', category: 'bridges', brand: 'Zoo', url: 'https://bridge.zoo.network', key: /connect wallet/i, posture: 'live', flags: {} },
|
||
{ id: 'bridge-pars', category: 'bridges', brand: 'Pars', url: 'https://bridge.pars.network', key: /connect wallet/i, posture: 'live', flags: {} },
|
||
|
||
// ── Safe ─────────────────────────────────────────────────────────────────
|
||
{ id: 'safe-lux', category: 'safes', brand: 'Lux', url: 'https://safe.lux.network', key: /connect wallet|watch any account/i, posture: 'live', flags: {} },
|
||
|
||
// ── Clouds ───────────────────────────────────────────────────────────────
|
||
{ id: 'lux-cloud', category: 'clouds', brand: 'Lux', url: 'https://lux.cloud', key: /start building|sign in|dashboard/i, posture: 'live', flags: {} },
|
||
{ id: 'zoo-cloud', category: 'clouds', brand: 'Zoo', url: 'https://zoo.cloud', key: /start building|sign in|dashboard/i, posture: 'live', flags: {} },
|
||
{ id: 'hanzo-cloud', category: 'clouds', brand: 'Hanzo', url: 'https://hanzo.cloud', key: /start building|sign in|dashboard/i, posture: 'foreign-domain', flags: {
|
||
http: { reason: "domain serves a DIFFERENT company — a Japanese 'HANZO' product (Meiryo font, JP email/password login), NOT Hanzo Cloud", flip: 'the domain is reclaimed/repointed to serve Hanzo Cloud (Hanzo brand + Zen)' },
|
||
} },
|
||
|
||
// ── Exchanges ────────────────────────────────────────────────────────────
|
||
{ id: 'exchange-lux', category: 'exchanges', brand: 'Lux', url: 'https://exchange.lux.network', key: /connect|trade|sell|buy|swap|explore/i, posture: 'live', flags: {
|
||
console: { reason: 'Vite preload dynamic-import failures + RPC backend errors; quotes render $0 / 0.00% (api.lux.exchange dead)', flip: 'restore chunk serving (no Vite preload error) + a live quote backend so prices are non-zero' },
|
||
overflow: { reason: 'Uniswap-fork marketing bands (220px 1fr grid + -ml-48) do not reflow below lg → ~220px overflow @390, ~165px @768', flip: 'stack/reflow the marketing bands below lg so scrollWidth ≤ viewport at 390/768', bps: ['390', '768'] },
|
||
} },
|
||
{ id: 'lux-exchange', category: 'exchanges', brand: 'Lux', url: 'https://lux.exchange', key: /connect|trade|swap/i, posture: 'down', flags: {
|
||
http: { reason: 'ERR_SSL_PROTOCOL_ERROR — safebrowse.io SNI blocklist on the domain (valid cert on a benign SNI; the domain itself is blocked)', flip: 'the domain is de-listed so it serves a real 200 TLS handshake' },
|
||
} },
|
||
{ id: 'hanzo-exchange', category: 'exchanges', brand: 'Hanzo', url: 'https://hanzo.exchange', key: /connect|trade|swap/i, posture: 'foreign-domain', flags: {
|
||
http: { reason: 'GoDaddy parking page ("parked free, courtesy of GoDaddy") — domain is not owned/pointed at Hanzo Exchange', flip: 'the domain is reclaimed and serves Hanzo Exchange' },
|
||
} },
|
||
]
|