Eight verification rules, one design rule and two environment rules. None of them is theoretical: each one was born from a real mistake — the first nine while porting version 5 of the LEXA landing page, rules 10 and 11 on the corporate website — and each was adopted after that mistake caused damage or a false conclusion. The original mistake is written down because a rule without its story gets skipped the first time someone is in a hurry.
Whoever touches this landing page again inherits this before the code.
1 · A blank screenshot is not evidence of a blank page
will-change keeps elements in compositor layers that captureVisibleTab does not include. A perfectly painted section photographs as empty.
Before you believe an empty screenshot: check computed styles and force a repaint (el.style.willChange = 'auto' and a 1 px scroll).
Origin (5a): I reported a bug in the hero that did not exist. What settled it was opening the original mockup, which came out just as empty with opacity: 1 and filter: blur(0px).
2 · scroll-behavior: smooth cancels scrollIntoView()
If you follow it with a scrollBy, the smooth scroll is interrupted and the section never enters the viewport. It looks as if the observer is broken.
Use behavior: 'instant' for any verification scroll.
Origin (5b): I declared the “How it teaches” IntersectionObserver broken. It worked.
3 · Measure an image load after, not before
naturalWidth read before the request finishes is 0, and complete is false.
Origin (5b): I reported that the bento images were not loading. They loaded, with 200.
4 · A zero without a control proves nothing
grep does not interpret \xe2\x82\xac as bytes: it searches for that literal string, which never exists. It always returns zero, and that zero gets signed with all the confidence in the world.
Every non-ASCII pattern is built with printf — EURO=$(printf '\xe2\x82\xac') — and comes with a prior control that shows it matches something known. The control stays in the log, not just the result.
Origin (5d): I was about to certify “zero prices on the landing page” with a pattern incapable of finding anything.
5 · Forbidden strings are grepped case-insensitively, and anchored
grep -i always. And every pattern with enough context: partner appears inside Mediapartners-Google, /mes inside nextjs.org/docs/messages/, iOS inside the Spanish words usuarios and Ejercicios. A pattern made of a single number is useless: 30 returned 867 matches in minified code.
Always restrict the search to the files that contain the landing page, not to every chunk the page loads.
Origin (5e): “Seleccionado por Google for Startups” was still published and only showed up with -i. In plain lowercase it gave 0.
6 · Before calling something visual a bug, measure twice, apart in time
Three of my “bugs” were measurements taken while a transition was still running.
Origin (5e): the benchmark bars, “stuck at scaleX(0)”, were halfway through their animation.
7 · Every validation is done in both languages, with a live switch
Server-side rendering always happens in English. If the user ends up in English there is no re-render, no remount, and the bugs tied to switching language do not exist. Testing only in the default language proves nothing about half of the users.
Origin (production incident): half the page was invisible only in Spanish. My checks in English really did pass.
8 · No test may touch the class it is testing
Adding .in by hand to get around rule 1 manufactured exactly the false positive that hid the reveal bug throughout development.
If the capture artefact prevents measuring, measure with counts and computed styles:
document.querySelectorAll('.lx-rv').length // 56
document.querySelectorAll('.lx-rv.in').length // 56 <- they have to match
No scrolling, and after 8 s, which is what the hard fallback guarantees.
Origin (production incident): the most serious bug of the whole branch survived dozens of checks because of this.
9 · Do not add a defensive rule without first measuring whether the case exists
Before any @media, white-space, aspect-ratio or similar that is not in the mockup: measure at 400 px and at 980 px and write the figure down. If it does not overflow, it is not added.
Origin (5b and 5c): two invented deviations in two consecutive increments, both solving problems that did not exist. When measured, one gave the same dimensions as on desktop and the other took 110 px where there had been 345.
10 · A worktree that builds does not symlink node_modules
With output: 'standalone', a build inside a worktree whose node_modules is a link to the repository's leaves .next/standalone/node_modules pointing at the real directory. The next next build in that worktree cleans .next with recursive-delete, which follows links to directories, and empties the node_modules of the main repository.
A worktree that builds gets its own npm ci. If a link already exists, remove it with unlink before building or deleting anything: find <worktree> -type l -path '*node_modules*'.
Origin (corporate site, Clash Display worktree): at 03:23 a second build emptied the repository's node_modules, and the next one failed with Cannot find module …/jest-worker/processChild.js. It was restored with npm ci from the unchanged lockfile.
11 · When stopping to wait, no process of your own stays alive
A preview server or a headless browser only lives while you are measuring against it. Before stopping to wait for a decision, stop them all and check: ps without your own next-server, node server.js or Chromium, and ss -ltnp without their ports. pgrep -f also counts the shell running it, because its command line contains the pattern: filter by executable, not by the full command line.
Origin (corporate site, waiting for the GO): two preview servers (:9200 and :9301) stayed up for more than two hours while waiting for the GO, and two next-server processes from an earlier session had been alive for five hours. Ramón saw it as “two hung processes”. When checking the clean-up, pgrep -fc next-server returned 1: it was the shell itself.
What these rules found
Three findings of “blocking” severity, all three on their way to production:
- Without JavaScript the page was practically blank. Every
.lx-rvstarted atopacity: 0and only JavaScript revealed it. - The Docker build failed because of an optional
firebase-admindependency thatnpm cisilently skipped on Alpine, while the local build passed. - The reveal did not reach halfway down the page in Spanish, because of
keys derived from the translated text. This one reached production and lasted under an hour.
And two that a user would have noticed before anyone else: the CSS specificity that left the benchmark bars unanimated, and the <html lang> that did not follow the selected language.