Footer: official CL8Y product links (homepage + Bridge) #663

Closed
opened 2026-08-26 04:13:31 +00:00 by PlasticDigits · 8 comments
PlasticDigits commented 2026-08-26 04:13:31 +00:00 (Migrated from gitlab.com)

Summary

Add a compact official CL8Y product row to the dApp shell footer: CL8Y homepage (https://cl8y.com/) and CL8Y Bridge (https://bridge.cl8y.com/). Traders who arrive on dex.cl8y.com have no in-app way to reach sibling products today.

This is one product change: homepage + Bridge share the same chrome, allowlist, and outbound-link rules. Do not split them. Do not implement bridge, GameFi, or cross-chain logic in this repo.

Current codebase

The global shell footer is only network risk + legal. It does not link any other CL8Y property.

frontend-dapp/src/components/common/Layout.tsx renders one footer.app-footer-shell on every route:

  1. EnvironmentRibbon — chain / env strip (local / testnet / mainnet). Desktop/tablet omit header NetworkBadge (#483); this strip carries network context.
  2. Title copy: CL8Y DEX · Terra Classic (plain <p>, not a link).
  3. LegalFooterNotice — only after RouteContentReadyProvider reports the lazy page mounted (#179 LCP; #138 NFA-after-nav). Failsafe: ROUTE_CONTENT_READY_FAILSAFE_MS.

ConnectedTermsGate (#517) wraps <Outlet> only. Header and footer stay mounted so an unsigned wallet can disconnect. There is no product-link component.

LegalFooterNotice is one paragraph:

Label Dest Notes
NFA sentence (text) NFA_SHORT
Security GitLab docs/security-posture.md SECURITY_POSTURE_DOC_URL, new tab (#387)
Incidents GitLab docs/user-incident-faq.md new tab (#390)
LP how-to same-origin /pool#lp-howto no target=_blank (#531)
Report GitLab security issue template new tab (#392)

Regression: LegalFooterNotice.test.tsx.

Header / More are in-app only

navItems.ts is DEX routes (Swap, Pool, Trade, More → Protocol / Tiers / Create Pair / Wrap / UST1 / Create Token / …). External origins must not be added to header More.

Header brand is logo + CL8Y DEX only. E2E forbids a “Terra Classic ecosystem” kicker (#136, e2e/navigation.spec.ts).

CSP / clickwrap (relevant, not blocking)

viteCsp.ts production CSP: form-action 'self', frame-ancestors 'none', connect-src is an allowlist (LCD, indexer, WalletConnect, api.terms.cl8y.com, terms.cl8y.com). A normal <a href="https://…"> navigation does not need connect-src. Do not add blanket https: to connect-src. Do not iframe bridge.cl8y.com.

Legal property stays dex.cl8y.com. Clickwrap is not required to leave the DEX.

Official destinations (live)

Product Canonical URL Role
CL8Y homepage https://cl8y.com/ Ecosystem landing (Bridge / DeFi / GameFi discovery)
CL8Y Bridge https://bridge.cl8y.com/ Cross-chain UI (Terra Classic ↔ EVM / Solana). Out of scope to implement here (historical review: bridge work is a different repo).

This DEX is already https://dex.cl8y.com — do not self-link. terms.cl8y.com is Legal, not a product tile.

Why this is needed

Users treat dex.cl8y.com as the CL8Y product they already trust. Sibling surfaces (homepage, Bridge) exist and are official, but the shell gives zero outbound path. People looking for wrap-across-chains or the rest of the ecosystem bounce or follow unofficial links.

The footer is the correct place: it is persistent, not a Swap lecture banner (#489 invariant 9), and does not crowd the sticky header (#136, #483).

Constraints / guardrails

  1. Hardcoded HTTPS allowlist only. Pin apex https://cl8y.com/ and https://bridge.cl8y.com/. Reject http:, //, javascript:, data:, query/hash rewrite, user/pass in URL, and lookalikes (cl8y.com.evil, homoglyphs). No VITE_* URL override on production (Coolify build-arg injection).
  2. New tab + rel="noopener noreferrer". Prevents tabnabbing (window.opener) and referrer leakage of DEX paths.
  3. Separate row from LegalFooterNotice. Do not splice “Bridge” into the NFA / Security / Report sentence. Legal links stay first-class; product links must not look like incident/security chrome.
  4. Short retail labels (≤ ~5 words): CL8Y (or Homepage) and Bridge. No whitepaper essay, no “via the canceler network” blurb (#489).
  5. Do not put these URLs in header More, mobile More, or a Swap/Pool banner.
  6. Do not reintroduce a header “Terra Classic ecosystem” kicker (#136).
  7. Do not iframe or window.open user-controlled URLs. No connect-src expansion. No form-action to those origins.
  8. LCP: product links are text only (no footer logo that can become LCP). Legal notice stays deferred until route-ready (#179). Product row may render immediately (small) or with the legal block — pick one and test; do not block first paint with remote fetches.
  9. Mobile: footer sits above the bottom tab bar (padding-bottom: calc(var(--app-mobile-nav-stack) + 16px)). Links wrap; no overlap with Swap/Trade/Pool/Limits/More. No nested card-glass / shell-panel inside the footer (#653).
  10. TermsGate: links remain usable when the connected wallet is unsigned (#517 — footer stays mounted).
  11. No GameFi / Telegram / X / CEX list in this ticket. Homepage is the discovery surface. Adding a third official product later means extending the same allowlist + tests, not a new chrome system.
  12. This repo does not ship the Bridge. Link only.

Relevant files

Path Why
frontend-dapp/src/components/common/Layout.tsx Footer shell; mount point
frontend-dapp/src/components/legal/LegalFooterNotice.tsx Keep legal paragraph intact
frontend-dapp/src/components/legal/EnvironmentRibbon.tsx Sibling chrome; do not merge
frontend-dapp/src/components/legal/legalCopy.ts NFA strings — do not mix product URLs here
frontend-dapp/src/index.css .app-footer*, .app-legal-footer*, mobile clearance
frontend-dapp/src/components/common/navItems.ts Do not add external items
frontend-dapp/viteCsp.ts Do not widen connect-src / form-action
frontend-dapp/src/utils/legalClickwrap.ts Property stays dex.cl8y.com
frontend-dapp/src/components/legal/__tests__/LegalFooterNotice.test.tsx Legal regressions
frontend-dapp/e2e/navigation.spec.ts Shell / NFA / header-brand E2E
docs/frontend.md § Risk surfacing / responsive shell Spec + invariants
skills/AGENTS_FRONTEND_RISK_DISCLAIMERS.md Footer legal placement
skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md Footer ribbon + no header kicker
skills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md Short labels; no lecture banners
docs/security-posture.md Footer Security dest (unchanged)

New (recommended): frontend-dapp/src/utils/cl8yProductLinks.ts (allowlist + types) + Cl8yProductLinks (or similar) + Vitest + make verify-issue-<iid>.

  1. Add a frozen allowlist module, e.g. CL8Y_PRODUCT_LINKS as as const { id, label, href }[] with only the two HTTPS origins above. Export a isAllowedCl8yProductHref(href) helper used by tests (and optionally by the renderer as a belt-and-suspenders check that returns null on mismatch).
  2. Render a nav (e.g. aria-label="CL8Y products") inside .app-footer-copy, below the title and beside or above LegalFooterNotice — not inside the NFA <p>. Use existing footer link styles (underline, --ink-subtle); no new gold marketing card.
  3. Each link: target="_blank", rel="noopener noreferrer", stable data-testid (footer-product-home, footer-product-bridge).
  4. Update docs/frontend.md (footer subsection) and a short playbook note (risk-disclaimers or a tiny AGENTS_FRONTEND_PRODUCT_LINKS.md) so agents do not dump these URLs into header More or Swap copy.
  5. Add make verify-issue-<iid> (Vitest + Playwright navigation slice). Keep 5 Playwright workers for smoke.

Acceptance criteria

  • Footer on every routed page shows Homepage (or CL8Y) → https://cl8y.com/ and Bridge → https://bridge.cl8y.com/ (exact origin + HTTPS, trailing slash optional but host/protocol exact).
  • Both open in a new tab with rel containing noopener and noreferrer.
  • LegalFooterNotice still shows NFA + Security + Incidents + LP how-to + Report with existing testids and destinations.
  • Header brand is still logo + CL8Y DEX; no “Terra Classic ecosystem” kicker; More menu still in-app only.
  • No iframe, no window.open of non-allowlisted URLs, no VITE_* product URL, no CSP connect-src / form-action change.
  • Dark + light; desktop (≥1280) and phone (375) — links visible, wrap, do not sit under the mobile tab bar; footer is still one chrome layer.
  • Product links remain clickable when Legal TermsGate is showing (header/footer usable).
  • Docs + skill mention the allowlist and the “footer only” rule.
  • make verify-issue-<iid> (or documented equivalent) is green.

Test plan (all paths)

Unit / RTL

  • Allowlist helper: accept only the two pinned HTTPS URLs; reject http://cl8y.com, https://cl8y.com.evil, https://evil.com/?u=https://cl8y.com, javascript:alert(1), data:text/html,…, //bridge.cl8y.com, https://user:pass@cl8y.com, https://bridge.cl8y.com.attacker, homoglyph host, empty, relative /bridge.
  • Component: both links present with href / target / rel / testids / accessible names.
  • LegalFooterNotice suite still passes unchanged.
  • Optional Layout RTL: footer contains product nav + legal notice after ready.

Playwright (e2e/navigation.spec.ts or sibling smoke)

  • Desktop 1280 and mobile 375: both product links visible in footer.app-footer-shell.
  • href + target=_blank + rel assertions (do not depend on a live third-party tab if CI is offline — assert attributes).
  • After Swap → Pool → Trade: product links and NFA still visible (ready-gate regression).
  • Header still has no “Terra Classic ecosystem”; More still has Protocol, not Bridge.
  • Theme toggle: links remain readable (contrast) on dark and light.
  • Mobile: bounding box of product links is above .app-mobile-nav-shell.

Manual / live (not a merge blocker if CI is offline)

  • Click Homepage and Bridge from https://dex.cl8y.com (or local Vite); destinations load the real properties.
  • Unsigned connected wallet: footer links still work.

Docs

  • docs/frontend.md footer table lists the two products.
  • Playbook one-liner: do not add these to navItems.ts.

Test plan (attack, hack, abuse)

Vector Expectation
Open redirect href is a compile-time constant, not searchParams, hash, or indexer field. Helper rejects any non-allowlisted string.
Lookalike / homograph domain cl8y.com.evil, xn-- punycode, cI8y.com fail the helper and never render.
Protocol smuggling javascript:, data:, vbscript:, http:, protocol-relative // never become href.
Tabnabbing rel includes noopener (and noreferrer). No window.open(url) without the same features.
Referrer leak noreferrer so dex.cl8y.com/trade/<pair> is not sent to the sibling origin.
Phishing chrome Product labels cannot be “Security”, “Connect Wallet”, or “Report”. Distinct from legal + wallet CTAs.
Env / Coolify injection No import.meta.env.VITE_CL8Y_*_URL for these hrefs. A poisoned build-arg must not retarget the footer.
CSP widening Diff must not add https: to connect-src or change form-action / frame-ancestors. Linking is navigation, not fetch.
Clickjacking / overlay Do not iframe Bridge. Production already has frame-ancestors 'none'.
XSS via label Labels are string literals, not markdown/HTML from a CMS.
Self-link / loop No https://dex.cl8y.com product tile (user is already here).
Scope creep as malware surface No Telegram/X/CEX URLs in this footer (unvetted third parties). Homepage owns that list.
Wallet session New tab must not receive window.opener access to the DEX (noopener). Footer must not auto-navigate the current tab away mid-sign.
TermsGate bypass confusion Links must not be presented as “accept terms here”; they leave the DEX property.

Verification criteria

Issue is done when:

  1. Targeted Vitest (allowlist + component + existing LegalFooterNotice tests) pass via scripts/with-node.sh.
  2. Playwright navigation smoke (5 workers) covers desktop + 375px visibility + NFA still present + header kicker still absent.
  3. make verify-issue-<iid> (or the commands listed in the MR) is the documented regression path.
  4. docs/frontend.md + skill updated; navItems.ts / CSP unchanged except unrelated drive-bys (there should be none).
  5. Manual spot-check: both live URLs still resolve over HTTPS (operator note if a property moves — update the allowlist in the same change).

Out of scope: implementing the Bridge, PROTOCASS/GameFi, social icons, whitepaper PDF in the footer, indexer/CORS changes, clickwrap property changes.

Owner / priority

  • Owner: frontend
  • Priority: P2 (discoverability; not a trading-path launch blocker)
  • Labels: frontend, UX, product, enhancement, docs, testing, e2e, security, missing-implementation
## Summary Add a compact **official CL8Y product** row to the dApp shell footer: **CL8Y homepage** (`https://cl8y.com/`) and **CL8Y Bridge** (`https://bridge.cl8y.com/`). Traders who arrive on `dex.cl8y.com` have no in-app way to reach sibling products today. This is **one** product change: homepage + Bridge share the same chrome, allowlist, and outbound-link rules. Do **not** split them. Do **not** implement bridge, GameFi, or cross-chain logic in this repo. ## Current codebase The global shell footer is only **network risk + legal**. It does not link any other CL8Y property. ### Shell footer (`Layout`) [`frontend-dapp/src/components/common/Layout.tsx`](frontend-dapp/src/components/common/Layout.tsx) renders one `footer.app-footer-shell` on **every** route: 1. [`EnvironmentRibbon`](frontend-dapp/src/components/legal/EnvironmentRibbon.tsx) — chain / env strip (`local` / `testnet` / `mainnet`). Desktop/tablet omit header `NetworkBadge` ([#483](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/483)); this strip carries network context. 2. Title copy: **`CL8Y DEX · Terra Classic`** (plain `<p>`, not a link). 3. [`LegalFooterNotice`](frontend-dapp/src/components/legal/LegalFooterNotice.tsx) — **only after** [`RouteContentReadyProvider`](frontend-dapp/src/contexts/RouteContentReadyContext.tsx) reports the lazy page mounted ([#179](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/179) LCP; [#138](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/138) NFA-after-nav). Failsafe: `ROUTE_CONTENT_READY_FAILSAFE_MS`. `ConnectedTermsGate` ([#517](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/517)) wraps `<Outlet>` only. Header and footer stay mounted so an unsigned wallet can disconnect. There is no product-link component. ### Legal notice links (keep) [`LegalFooterNotice`](frontend-dapp/src/components/legal/LegalFooterNotice.tsx) is one paragraph: | Label | Dest | Notes | |-------|------|--------| | NFA sentence | (text) | [`NFA_SHORT`](frontend-dapp/src/components/legal/legalCopy.ts) | | **Security** | GitLab `docs/security-posture.md` | `SECURITY_POSTURE_DOC_URL`, new tab ([#387](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/387)) | | **Incidents** | GitLab `docs/user-incident-faq.md` | new tab ([#390](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/390)) | | LP how-to | same-origin `/pool#lp-howto` | **no** `target=_blank` ([#531](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/531)) | | **Report** | GitLab security issue template | new tab ([#392](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/392)) | Regression: [`LegalFooterNotice.test.tsx`](frontend-dapp/src/components/legal/__tests__/LegalFooterNotice.test.tsx). ### Header / More are in-app only [`navItems.ts`](frontend-dapp/src/components/common/navItems.ts) is **DEX routes** (`Swap`, `Pool`, `Trade`, More → Protocol / Tiers / Create Pair / Wrap / UST1 / Create Token / …). External origins must **not** be added to header More. Header brand is logo + **CL8Y DEX** only. E2E forbids a **“Terra Classic ecosystem”** kicker ([#136](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/136), [`e2e/navigation.spec.ts`](frontend-dapp/e2e/navigation.spec.ts)). ### CSP / clickwrap (relevant, not blocking) [`viteCsp.ts`](frontend-dapp/viteCsp.ts) production CSP: `form-action 'self'`, `frame-ancestors 'none'`, `connect-src` is an allowlist (LCD, indexer, WalletConnect, `api.terms.cl8y.com`, `terms.cl8y.com`). A normal `<a href="https://…">` navigation does **not** need `connect-src`. Do **not** add blanket `https:` to `connect-src`. Do **not** iframe `bridge.cl8y.com`. Legal property stays **`dex.cl8y.com`**. Clickwrap is not required to *leave* the DEX. ### Official destinations (live) | Product | Canonical URL | Role | |---------|----------------|------| | CL8Y homepage | `https://cl8y.com/` | Ecosystem landing (Bridge / DeFi / GameFi discovery) | | CL8Y Bridge | `https://bridge.cl8y.com/` | Cross-chain UI (Terra Classic ↔ EVM / Solana). **Out of scope to implement here** (historical review: bridge work is a different repo). | This DEX is already `https://dex.cl8y.com` — do **not** self-link. `terms.cl8y.com` is Legal, not a product tile. ## Why this is needed Users treat `dex.cl8y.com` as the CL8Y product they already trust. Sibling surfaces (homepage, Bridge) exist and are official, but the shell gives **zero** outbound path. People looking for wrap-across-chains or the rest of the ecosystem bounce or follow unofficial links. The footer is the correct place: it is persistent, not a Swap lecture banner ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) invariant 9), and does not crowd the sticky header ([#136](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/136), [#483](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/483)). ## Constraints / guardrails 1. **Hardcoded HTTPS allowlist only.** Pin apex `https://cl8y.com/` and `https://bridge.cl8y.com/`. Reject `http:`, `//`, `javascript:`, `data:`, query/hash rewrite, user/pass in URL, and lookalikes (`cl8y.com.evil`, homoglyphs). No `VITE_*` URL override on production (Coolify build-arg injection). 2. **New tab + `rel="noopener noreferrer"`.** Prevents tabnabbing (`window.opener`) and referrer leakage of DEX paths. 3. **Separate row from `LegalFooterNotice`.** Do not splice “Bridge” into the NFA / Security / Report sentence. Legal links stay first-class; product links must not look like incident/security chrome. 4. **Short retail labels** (≤ ~5 words): **CL8Y** (or **Homepage**) and **Bridge**. No whitepaper essay, no “via the canceler network” blurb ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489)). 5. **Do not** put these URLs in header More, mobile More, or a Swap/Pool banner. 6. **Do not** reintroduce a header “Terra Classic ecosystem” kicker ([#136](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/136)). 7. **Do not** iframe or `window.open` user-controlled URLs. No `connect-src` expansion. No `form-action` to those origins. 8. **LCP:** product links are text only (no footer logo that can become LCP). Legal notice stays deferred until route-ready ([#179](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/179)). Product row may render immediately (small) or with the legal block — pick one and test; do not block first paint with remote fetches. 9. **Mobile:** footer sits above the bottom tab bar (`padding-bottom: calc(var(--app-mobile-nav-stack) + 16px)`). Links wrap; no overlap with Swap/Trade/Pool/Limits/More. No nested `card-glass` / `shell-panel` inside the footer ([#653](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/653)). 10. **TermsGate:** links remain usable when the connected wallet is unsigned ([#517](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/517) — footer stays mounted). 11. **No GameFi / Telegram / X / CEX list in this ticket.** Homepage is the discovery surface. Adding a third official product later means extending the **same** allowlist + tests, not a new chrome system. 12. **This repo does not ship the Bridge.** Link only. ## Relevant files | Path | Why | |------|-----| | [`frontend-dapp/src/components/common/Layout.tsx`](frontend-dapp/src/components/common/Layout.tsx) | Footer shell; mount point | | [`frontend-dapp/src/components/legal/LegalFooterNotice.tsx`](frontend-dapp/src/components/legal/LegalFooterNotice.tsx) | Keep legal paragraph intact | | [`frontend-dapp/src/components/legal/EnvironmentRibbon.tsx`](frontend-dapp/src/components/legal/EnvironmentRibbon.tsx) | Sibling chrome; do not merge | | [`frontend-dapp/src/components/legal/legalCopy.ts`](frontend-dapp/src/components/legal/legalCopy.ts) | NFA strings — do not mix product URLs here | | [`frontend-dapp/src/index.css`](frontend-dapp/src/index.css) | `.app-footer*`, `.app-legal-footer*`, mobile clearance | | [`frontend-dapp/src/components/common/navItems.ts`](frontend-dapp/src/components/common/navItems.ts) | Do **not** add external items | | [`frontend-dapp/viteCsp.ts`](frontend-dapp/viteCsp.ts) | Do not widen `connect-src` / `form-action` | | [`frontend-dapp/src/utils/legalClickwrap.ts`](frontend-dapp/src/utils/legalClickwrap.ts) | Property stays `dex.cl8y.com` | | [`frontend-dapp/src/components/legal/__tests__/LegalFooterNotice.test.tsx`](frontend-dapp/src/components/legal/__tests__/LegalFooterNotice.test.tsx) | Legal regressions | | [`frontend-dapp/e2e/navigation.spec.ts`](frontend-dapp/e2e/navigation.spec.ts) | Shell / NFA / header-brand E2E | | [`docs/frontend.md`](docs/frontend.md) § Risk surfacing / responsive shell | Spec + invariants | | [`skills/AGENTS_FRONTEND_RISK_DISCLAIMERS.md`](skills/AGENTS_FRONTEND_RISK_DISCLAIMERS.md) | Footer legal placement | | [`skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md`](skills/AGENTS_FRONTEND_RESPONSIVE_HEADER.md) | Footer ribbon + no header kicker | | [`skills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md`](skills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md) | Short labels; no lecture banners | | [`docs/security-posture.md`](docs/security-posture.md) | Footer Security dest (unchanged) | **New (recommended):** `frontend-dapp/src/utils/cl8yProductLinks.ts` (allowlist + types) + `Cl8yProductLinks` (or similar) + Vitest + `make verify-issue-<iid>`. ## Recommended direction 1. Add a frozen allowlist module, e.g. `CL8Y_PRODUCT_LINKS` as `as const` `{ id, label, href }[]` with **only** the two HTTPS origins above. Export a `isAllowedCl8yProductHref(href)` helper used by tests (and optionally by the renderer as a belt-and-suspenders check that returns `null` on mismatch). 2. Render a `nav` (e.g. `aria-label="CL8Y products"`) **inside** `.app-footer-copy`, **below** the title and **beside or above** `LegalFooterNotice` — not inside the NFA `<p>`. Use existing footer link styles (underline, `--ink-subtle`); no new gold marketing card. 3. Each link: `target="_blank"`, `rel="noopener noreferrer"`, stable `data-testid` (`footer-product-home`, `footer-product-bridge`). 4. Update [`docs/frontend.md`](docs/frontend.md) (footer subsection) and a short playbook note (risk-disclaimers or a tiny `AGENTS_FRONTEND_PRODUCT_LINKS.md`) so agents do not dump these URLs into header More or Swap copy. 5. Add `make verify-issue-<iid>` (Vitest + Playwright navigation slice). Keep 5 Playwright workers for smoke. ## Acceptance criteria - [ ] Footer on every routed page shows **Homepage** (or **CL8Y**) → `https://cl8y.com/` and **Bridge** → `https://bridge.cl8y.com/` (exact origin + HTTPS, trailing slash optional but host/protocol exact). - [ ] Both open in a new tab with `rel` containing `noopener` and `noreferrer`. - [ ] `LegalFooterNotice` still shows NFA + Security + Incidents + LP how-to + Report with existing testids and destinations. - [ ] Header brand is still logo + **CL8Y DEX**; no “Terra Classic ecosystem” kicker; More menu still in-app only. - [ ] No iframe, no `window.open` of non-allowlisted URLs, no `VITE_*` product URL, no CSP `connect-src` / `form-action` change. - [ ] Dark + light; desktop (≥1280) and phone (375) — links visible, wrap, do not sit under the mobile tab bar; footer is still one chrome layer. - [ ] Product links remain clickable when Legal TermsGate is showing (header/footer usable). - [ ] Docs + skill mention the allowlist and the “footer only” rule. - [ ] `make verify-issue-<iid>` (or documented equivalent) is green. ## Test plan (all paths) ### Unit / RTL - Allowlist helper: accept only the two pinned HTTPS URLs; reject `http://cl8y.com`, `https://cl8y.com.evil`, `https://evil.com/?u=https://cl8y.com`, `javascript:alert(1)`, `data:text/html,…`, `//bridge.cl8y.com`, `https://user:pass@cl8y.com`, `https://bridge.cl8y.com.attacker`, homoglyph host, empty, relative `/bridge`. - Component: both links present with href / target / rel / testids / accessible names. - `LegalFooterNotice` suite still passes unchanged. - Optional Layout RTL: footer contains product `nav` + legal notice after ready. ### Playwright (`e2e/navigation.spec.ts` or sibling smoke) - Desktop 1280 and mobile 375: both product links visible in `footer.app-footer-shell`. - href + `target=_blank` + `rel` assertions (do not depend on a live third-party tab if CI is offline — assert attributes). - After Swap → Pool → Trade: product links **and** NFA still visible (ready-gate regression). - Header still has no “Terra Classic ecosystem”; More still has Protocol, not Bridge. - Theme toggle: links remain readable (contrast) on dark and light. - Mobile: bounding box of product links is above `.app-mobile-nav-shell`. ### Manual / live (not a merge blocker if CI is offline) - Click Homepage and Bridge from `https://dex.cl8y.com` (or local Vite); destinations load the real properties. - Unsigned connected wallet: footer links still work. ### Docs - `docs/frontend.md` footer table lists the two products. - Playbook one-liner: do not add these to `navItems.ts`. ## Test plan (attack, hack, abuse) | Vector | Expectation | |--------|-------------| | **Open redirect** | href is a compile-time constant, not `searchParams`, hash, or indexer field. Helper rejects any non-allowlisted string. | | **Lookalike / homograph domain** | `cl8y.com.evil`, `xn--` punycode, `cI8y.com` fail the helper and never render. | | **Protocol smuggling** | `javascript:`, `data:`, `vbscript:`, `http:`, protocol-relative `//` never become `href`. | | **Tabnabbing** | `rel` includes `noopener` (and `noreferrer`). No `window.open(url)` without the same features. | | **Referrer leak** | `noreferrer` so `dex.cl8y.com/trade/<pair>` is not sent to the sibling origin. | | **Phishing chrome** | Product labels cannot be “Security”, “Connect Wallet”, or “Report”. Distinct from legal + wallet CTAs. | | **Env / Coolify injection** | No `import.meta.env.VITE_CL8Y_*_URL` for these hrefs. A poisoned build-arg must not retarget the footer. | | **CSP widening** | Diff must not add `https:` to `connect-src` or change `form-action` / `frame-ancestors`. Linking is navigation, not fetch. | | **Clickjacking / overlay** | Do not iframe Bridge. Production already has `frame-ancestors 'none'`. | | **XSS via label** | Labels are string literals, not markdown/HTML from a CMS. | | **Self-link / loop** | No `https://dex.cl8y.com` product tile (user is already here). | | **Scope creep as malware surface** | No Telegram/X/CEX URLs in this footer (unvetted third parties). Homepage owns that list. | | **Wallet session** | New tab must not receive `window.opener` access to the DEX (noopener). Footer must not auto-navigate the current tab away mid-sign. | | **TermsGate bypass confusion** | Links must not be presented as “accept terms here”; they leave the DEX property. | ## Verification criteria Issue is **done** when: 1. Targeted Vitest (allowlist + component + existing `LegalFooterNotice` tests) pass via `scripts/with-node.sh`. 2. Playwright navigation smoke (5 workers) covers desktop + 375px visibility + NFA still present + header kicker still absent. 3. `make verify-issue-<iid>` (or the commands listed in the MR) is the documented regression path. 4. `docs/frontend.md` + skill updated; `navItems.ts` / CSP unchanged except unrelated drive-bys (there should be none). 5. Manual spot-check: both live URLs still resolve over HTTPS (operator note if a property moves — update the allowlist in the same change). **Out of scope:** implementing the Bridge, PROTOCASS/GameFi, social icons, whitepaper PDF in the footer, indexer/CORS changes, clickwrap property changes. ## Owner / priority - **Owner:** frontend - **Priority:** P2 (discoverability; not a trading-path launch blocker) - **Labels:** frontend, UX, product, enhancement, docs, testing, e2e, security, missing-implementation
PlasticDigits commented 2026-08-26 06:45:10 +00:00 (Migrated from gitlab.com)

mentioned in commit 28e505b56b

mentioned in commit 28e505b56b7305a71c7899bb33eb309204d3b5de
PlasticDigits commented 2026-08-26 06:47:31 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1154

mentioned in merge request !1154
PlasticDigits commented 2026-08-26 06:48:43 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1156

mentioned in merge request !1156
PlasticDigits commented 2026-08-26 08:04:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 98b2da570f

mentioned in commit 98b2da570f2d482bafadc88e96cf60f8fe6ec876
PlasticDigits commented 2026-08-26 08:05:17 +00:00 (Migrated from gitlab.com)

mentioned in commit dc869a7fe8

mentioned in commit dc869a7fe8b02e3b9f7a10ec9a955961b42eea61
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-26 08:05:18 +00:00
PlasticDigits commented 2026-08-26 08:08:56 +00:00 (Migrated from gitlab.com)

mentioned in commit 723c426856

mentioned in commit 723c4268566cc6f8a16a616745ffcfdf7173ca0a
PlasticDigits commented 2026-08-26 09:30:49 +00:00 (Migrated from gitlab.com)

Merged to main via !1156. Catalog conflicts with later MRs were unioned (keep footer Homepage + Bridge).

Leftover: live footer still shows Homepage and Bridge after Coolify frontend rebuild. Optional Playwright e2e/footer-product-links-663.spec.ts when LocalTerra is up.

Merged to `main` via !1156. Catalog conflicts with later MRs were unioned (keep footer Homepage + Bridge). Leftover: live footer still shows Homepage and Bridge after Coolify frontend rebuild. Optional Playwright `e2e/footer-product-links-663.spec.ts` when LocalTerra is up.
PlasticDigits commented 2026-08-26 09:31:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #673

mentioned in issue #673
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#663
No description provided.