From dd2c44713ced3d12a88f807909d5b1faa3545350 Mon Sep 17 00:00:00 2001 From: ValenF Date: Mon, 17 Aug 2026 20:29:54 -0300 Subject: [PATCH 1/3] fix(navbar): prevent hero CTA flash on initial render --- .csdd/todo.md | 10 ++++++++++ app/page.tsx | 2 +- components/sections/Navbar.tsx | 21 +++++++++++++-------- e2e/smoke.spec.ts | 29 +++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/.csdd/todo.md b/.csdd/todo.md index e1bfc62..095c60f 100644 --- a/.csdd/todo.md +++ b/.csdd/todo.md @@ -9,6 +9,16 @@ ## Ready to Land +- [ ] T-043 — Corregir el flash de CTAs del Navbar al cargar el Hero (issue #25) + - Owner: Valen + - Agent: Codex + - Scope: `components/sections/Navbar.tsx`, `app/page.tsx`, `e2e/smoke.spec.ts`, `.csdd/todo.md`; no tocar `PrimaryCta` ni el glow del issue #26 + - Target: `development` + - Updated: 2026-08-17 + - Landing: draft PR hacia `master`; sin merge manual del issue + - Verification: lint OK (1 warning preexistente en `Footer.tsx`), typecheck OK, build OK, smoke E2E 9/9 OK; QA visual desktop/mobile OK + - Note: el estado inicial del home se hace determinista con `isHome`, evitando depender de `usePathname()` durante render/hidratación. Se preservan transición geométrica, `aria-hidden`, `inert`, foco y CTAs mobile. + ## Blocked ## Pending diff --git a/app/page.tsx b/app/page.tsx index 8087919..814ce8f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -23,7 +23,7 @@ export default function Home() { return (
- + diff --git a/components/sections/Navbar.tsx b/components/sections/Navbar.tsx index c1f26d4..ccf00e4 100644 --- a/components/sections/Navbar.tsx +++ b/components/sections/Navbar.tsx @@ -3,12 +3,12 @@ * components/sections/Navbar.tsx * * Barra sticky. En desktop, los CTAs se ocultan mientras #hero ocupa el viewport - * (T-028 / T-030) para no duplicar el par del Hero; al scrollear fuera reaparecen. + * (T-028 / T-030 / T-043) para no duplicar el par del Hero; al scrollear fuera + * reaparecen. */ import { useState, useEffect, useRef } from "react"; import Image from "next/image"; import Link from "next/link"; -import { usePathname } from "next/navigation"; import { Menu, X } from "lucide-react"; import { PrimaryCta, SecondaryCta } from "@/components/conversion"; import { useModal } from "@/lib/context/ModalContext"; @@ -34,21 +34,26 @@ function isHeroOccupyingViewport(): boolean { return hero.getBoundingClientRect().bottom > NAV_CLEARANCE_PX; } -export function Navbar() { +type NavbarProps = { + /** The home route needs deterministic SSR markup before client hydration. */ + isHome?: boolean; +}; + +export function Navbar({ isHome = false }: NavbarProps) { const { openModal } = useModal(); - const pathname = usePathname(); const [isOpen, setIsOpen] = useState(false); // Home: arrancar ocultos (Hero ya lleva el par). Otras rutas: siempre visibles. - const [showDesktopCtas, setShowDesktopCtas] = useState(pathname !== "/"); + // Keep this route decision server-deterministic to avoid a hydration flash. + const [showDesktopCtas, setShowDesktopCtas] = useState(!isHome); const menuButtonRef = useRef(null); const menuDialogRef = useRef(null); const closeMenuButtonRef = useRef(null); const desktopCtasRef = useRef(null); const logoLinkRef = useRef(null); - // T-028: CTAs desktop siguen la geometría del Hero (sync + scroll/resize). + // T-028/T-043: CTAs desktop siguen la geometría del Hero (sync + scroll/resize). useEffect(() => { - if (pathname !== "/") { + if (!isHome) { setShowDesktopCtas(true); return; } @@ -69,7 +74,7 @@ export function Navbar() { window.removeEventListener("scroll", syncCtaVisibility); window.removeEventListener("resize", syncCtaVisibility); }; - }, [pathname]); + }, [isHome]); // Si los CTAs se ocultan con foco dentro, devolverlo al logo. useEffect(() => { diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index e116ea8..231f255 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -106,3 +106,32 @@ test.describe("cotizador", () => { ).toBeVisible(); }); }); + +test.describe("navbar en el hero", () => { + test("mantiene los CTAs desktop ocultos desde el HTML inicial y al volver arriba", async ({ page }) => { + const response = await page.goto("/", { waitUntil: "domcontentloaded" }); + + expect(response).not.toBeNull(); + expect(await response?.text()).toContain('aria-hidden="true"'); + + const nav = page.getByRole("navigation", { name: "Navegación principal" }); + const navCtaGroup = nav.locator("div[aria-hidden]").first(); + const navCtaGrid = nav.locator("div.opacity-0").first(); + const navCta = nav.locator('button[aria-label^="Armar viaje"]').first(); + + await expect(navCtaGroup).toHaveAttribute("aria-hidden", "true"); + await expect(navCtaGroup).toHaveAttribute("inert", ""); + await expect(navCtaGrid).toHaveCSS("opacity", "0"); + + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await expect(navCta).toBeVisible(); + await expect(navCtaGroup).toHaveAttribute("aria-hidden", "false"); + + await page.evaluate(() => window.scrollTo(0, 0)); + await expect(navCtaGroup).toHaveAttribute("aria-hidden", "true"); + await expect(navCtaGrid).toHaveCSS("opacity", "0"); + + await page.reload({ waitUntil: "domcontentloaded" }); + await expect(nav.locator("div.opacity-0").first()).toHaveCSS("opacity", "0"); + }); +}); From 60471de7829a768b458d73f2d3cf161097eadfa5 Mon Sep 17 00:00:00 2001 From: ValenF Date: Mon, 17 Aug 2026 20:30:41 -0300 Subject: [PATCH 2/3] docs(csdd): link T-043 draft PR --- .csdd/todo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.csdd/todo.md b/.csdd/todo.md index 095c60f..8142934 100644 --- a/.csdd/todo.md +++ b/.csdd/todo.md @@ -15,7 +15,7 @@ - Scope: `components/sections/Navbar.tsx`, `app/page.tsx`, `e2e/smoke.spec.ts`, `.csdd/todo.md`; no tocar `PrimaryCta` ni el glow del issue #26 - Target: `development` - Updated: 2026-08-17 - - Landing: draft PR hacia `master`; sin merge manual del issue + - Landing: [draft PR #28](https://github.com/ValenFelizia/787-Rumbos/pull/28) hacia `master`; sin merge manual del issue - Verification: lint OK (1 warning preexistente en `Footer.tsx`), typecheck OK, build OK, smoke E2E 9/9 OK; QA visual desktop/mobile OK - Note: el estado inicial del home se hace determinista con `isHome`, evitando depender de `usePathname()` durante render/hidratación. Se preservan transición geométrica, `aria-hidden`, `inert`, foco y CTAs mobile. From c59453be70df3a8128f82cfa3b677c3d051ffdb1 Mon Sep 17 00:00:00 2001 From: ValenF Date: Mon, 17 Aug 2026 20:50:10 -0300 Subject: [PATCH 3/3] test(navbar): harden initial CTA regression --- .csdd/todo.md | 2 +- components/sections/Navbar.tsx | 1 + e2e/smoke.spec.ts | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.csdd/todo.md b/.csdd/todo.md index 8142934..54faa6e 100644 --- a/.csdd/todo.md +++ b/.csdd/todo.md @@ -13,7 +13,7 @@ - Owner: Valen - Agent: Codex - Scope: `components/sections/Navbar.tsx`, `app/page.tsx`, `e2e/smoke.spec.ts`, `.csdd/todo.md`; no tocar `PrimaryCta` ni el glow del issue #26 - - Target: `development` + - Target: `master` - Updated: 2026-08-17 - Landing: [draft PR #28](https://github.com/ValenFelizia/787-Rumbos/pull/28) hacia `master`; sin merge manual del issue - Verification: lint OK (1 warning preexistente en `Footer.tsx`), typecheck OK, build OK, smoke E2E 9/9 OK; QA visual desktop/mobile OK diff --git a/components/sections/Navbar.tsx b/components/sections/Navbar.tsx index ccf00e4..c4fa48b 100644 --- a/components/sections/Navbar.tsx +++ b/components/sections/Navbar.tsx @@ -195,6 +195,7 @@ export function Navbar({ isHome = false }: NavbarProps) {
{ const response = await page.goto("/", { waitUntil: "domcontentloaded" }); expect(response).not.toBeNull(); - expect(await response?.text()).toContain('aria-hidden="true"'); + const initialHtml = await response!.text(); + const initialDesktopCtaMarkup = initialHtml.match( + /]*data-testid="desktop-navbar-ctas"[^>]*>/ + )?.[0]; + + expect(initialDesktopCtaMarkup).toContain('aria-hidden="true"'); + expect(initialDesktopCtaMarkup).toContain('inert=""'); const nav = page.getByRole("navigation", { name: "Navegación principal" }); - const navCtaGroup = nav.locator("div[aria-hidden]").first(); + const navCtaGroup = nav.getByTestId("desktop-navbar-ctas"); const navCtaGrid = nav.locator("div.opacity-0").first(); const navCta = nav.locator('button[aria-label^="Armar viaje"]').first();