diff --git a/.github/workflows/e2e-sites.yml b/.github/workflows/e2e-sites.yml index 7901622..e8e51bd 100644 --- a/.github/workflows/e2e-sites.yml +++ b/.github/workflows/e2e-sites.yml @@ -24,13 +24,6 @@ jobs: name: ${{ matrix.framework }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 25 - # Next.js leg is advisory until the fixture is converted to a pure - # static export. chromiumoxide 0.9.1 (PR #232) fixed the WebSocket - # 'Invalid message' bug, but Next.js's client-side hydration still - # produces non-deterministic computed-style timing across runs on - # Linux + Windows. macOS happens to be stable. Tracking the static - # fixture rewrite as aram-devdocs/plumb#233. - continue-on-error: ${{ matrix.framework == 'nextjs' }} strategy: fail-fast: false matrix: diff --git a/e2e-sites/nextjs/README.md b/e2e-sites/nextjs/README.md index 8f8cfc1..11362e0 100644 --- a/e2e-sites/nextjs/README.md +++ b/e2e-sites/nextjs/README.md @@ -41,8 +41,9 @@ matches the rest of the matrix. `expected.json` sets a `wait_for` block that the harness threads onto `plumb lint --wait-for --wait-ms `. The selector is -`html[data-plumb-ready="true"]`; the marker is set from a `useEffect` -in `app/plumb-ready.tsx`, so it appears only after React has hydrated -the tree on the client. Without it Chromium occasionally captures a -half-hydrated DOM under CI load — that flake was the reason the leg -ran with `continue-on-error: true` in `e2e-sites.yml`. +`html[data-plumb-ready="true"]`; the marker is server-rendered +directly onto the `` element in `app/layout.tsx`, so it appears +on the very first paint. The fixture is a pure static export with no +`'use client'` components — Chromium captures a stable, byte-identical +DOM on every run, which is what the harness's 3× determinism check +relies on. diff --git a/e2e-sites/nextjs/app/layout.tsx b/e2e-sites/nextjs/app/layout.tsx index e0dacb2..f99b71e 100644 --- a/e2e-sites/nextjs/app/layout.tsx +++ b/e2e-sites/nextjs/app/layout.tsx @@ -10,8 +10,13 @@ export default function RootLayout({ }: { children: React.ReactNode; }) { + // `data-plumb-ready="true"` is server-rendered directly onto the + // `` element so the harness's `wait_for` selector resolves + // against the very first paint. No `'use client'` component is + // needed; the resulting `out/index.html` is a deterministic static + // artifact, byte-stable across Linux/macOS/Windows builds. return ( - + {children} ); diff --git a/e2e-sites/nextjs/app/page.tsx b/e2e-sites/nextjs/app/page.tsx index 516c96f..5e1e5f2 100644 --- a/e2e-sites/nextjs/app/page.tsx +++ b/e2e-sites/nextjs/app/page.tsx @@ -3,12 +3,9 @@ // Same intentional violations as the rest of the matrix: see // ../README.md. -import PlumbReady from "./plumb-ready"; - export default function Page() { return (
-

Plumb e2e — nextjs

diff --git a/e2e-sites/nextjs/app/plumb-ready.tsx b/e2e-sites/nextjs/app/plumb-ready.tsx deleted file mode 100644 index 96492d5..0000000 --- a/e2e-sites/nextjs/app/plumb-ready.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -// Plumb e2e — readiness sentinel. -// -// The harness's `expected.json` carries a `wait_for.selector` of -// `html[data-plumb-ready="true"]`. Plumb's CDP driver polls for that -// selector after navigation and before snapshotting. Setting the -// attribute from a `useEffect` guarantees that React has hydrated the -// tree at least once on the client, which is the readiness signal we -// actually care about for layout-stable linting. -import { useEffect } from "react"; - -export default function PlumbReady() { - useEffect(() => { - document.documentElement.setAttribute("data-plumb-ready", "true"); - }, []); - return null; -} diff --git a/e2e-sites/nextjs/expected.json b/e2e-sites/nextjs/expected.json index 722a922..8f01738 100644 --- a/e2e-sites/nextjs/expected.json +++ b/e2e-sites/nextjs/expected.json @@ -1,5 +1,5 @@ { - "$comment": "Next.js 14 statically exported. The framework injects a hidden `` that uses the visually-hidden `margin: -1px` trick — that contributes 4 grid + 4 scale violations on top of the intentional `p-[13px]` hero. Expected counts are doubled accordingly. The non-target rules `sibling/height-consistency` and `sibling/padding-consistency` also fire on the announcer; they're not asserted on. The `wait_for` block points at a `data-plumb-ready` sentinel set from a `useEffect` in `app/plumb-ready.tsx`, which guarantees post-hydration capture and ends the CI flake that previously kept this leg advisory.", + "$comment": "Next.js 14 statically exported. The framework injects a hidden `` that uses the visually-hidden `margin: -1px` trick — that contributes 4 grid + 4 scale violations on top of the intentional `p-[13px]` hero. Expected counts are doubled accordingly. The non-target rules `sibling/height-consistency` and `sibling/padding-consistency` also fire on the announcer; they're not asserted on. The `wait_for` block points at a `data-plumb-ready` sentinel server-rendered onto `` in `app/layout.tsx`, which makes the captured DOM byte-stable across Linux/macOS/Windows runs.", "target_rules": [ "color/palette-conformance", "spacing/grid-conformance",