Skip to content

feat: section-by-section diffing (--sections)#1

Open
wpfyorg wants to merge 3 commits into
masterfrom
feat/section-by-section
Open

feat: section-by-section diffing (--sections)#1
wpfyorg wants to merge 3 commits into
masterfrom
feat/section-by-section

Conversation

@wpfyorg

@wpfyorg wpfyorg commented Jun 25, 2026

Copy link
Copy Markdown
Owner

What

Adds a --sections mode to the visual-diff skill that localizes where a page differs instead of only reporting a whole-page diff number. One tall section can drag a whole-page percentage; this drops to per-section so you can see exactly which region is off.

How it works

  • Auto-detects each <section> on the local page by its first semantic class (hero, pricing, faq, …), skipping the generic section utility token and de-duping by selector.
  • Clips the same selector on both local and live via a new captureScreenshot({ selector }) option (scrolls the element into view to fire reveal animations, then element-screenshots it so both sides are the same region/size).
  • Diffs each region independently. report.md gains a Section column; per-section PNGs are <section>-local.png / <section>-live.png / <section>-diff.png.
  • A missing class on the live side surfaces as ⚠️ error ("Selector not found") — a signal the ported section never got its semantic class.
  • A page may override auto-detection with a sections array (plain selectors, or {selector,label} to target a child).

Whole-page mode is unchanged.

Usage

node scripts/visual-diff.mjs --page <slug> --sections   # one page, all its sections
node scripts/visual-diff.mjs --sections                 # every page, section by section

Files

  • scripts/lib/capture.mjsselector clip option
  • scripts/visual-diff.mjs--sections, auto-detect, per-section report column, shared diffPair helper
  • SKILL.md / config.example.json — document --sections + the override

Testing

Ran both modes against a live local site (homepage, both bases pointed at localhost): --sections detected all 11 sections and reported each (all identical / pass); whole-page mode unchanged. Verified inside the repo's own skills/visual-diff/scripts/ layout.

Add a --sections mode to visual-diff.mjs that localizes where a page
differs instead of only reporting a whole-page number. It auto-detects
each <section> on the local page by its first semantic class, clips the
same selector on both sides via a new captureScreenshot({ selector })
option, and diffs each region independently. The report.md gains a
Section column; per-section PNGs are <section>-{local,live,diff}.png.

A page may override auto-detection with a sections array (selectors or
{selector,label}). Whole-page mode is unchanged.

- capture.mjs: selector clip (scrollIntoView + element screenshot)
- visual-diff.mjs: --sections, auto-detect, per-section report column
- SKILL.md / config.example.json: document --sections + override

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cb7715a38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +242 to +243
const cls = [...el.classList].filter((c) => !GENERIC.has(c));
if (cls.length) list.push({ selector: '.' + cls[0], label: cls[0] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a real section selector for detected sections

When --sections sees a local section whose first non-section class is a utility/layout token, e.g. <section class="container hero"> or Tailwind <section class="md:py-24 hero">, this emits .container/.md:py-24 as the selector. The former can capture an unrelated first .container elsewhere on the page, and the latter is not a usable unescaped CSS class selector, so the report can pass/fail the wrong region or error before reaching the actual section. Derive a selector for the actual <section> element and escape class names instead of trusting the first remaining token.

Useful? React with 👍 / 👎.

return page.sections.map((s) =>
typeof s === 'string' ? { selector: s, label: s.replace(/^[.#]/, '') } : s);
}
return detectSections(LOCAL_BASE + page.path, vp);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect live-only sections before passing

In --sections mode this enumerates only the local page, so a live/staging page with an extra banner or section inserted between otherwise matching sections can still report every row as pass: each local selector is captured as an isolated element, which ignores page position and never checks for live-only sections. Add a live-side section scan (or a whole-page guard row) so section mode cannot return exit code 0 when the candidate page contains extra unmatched content.

Useful? React with 👍 / 👎.

Comment on lines +241 to +243
document.querySelectorAll('section').forEach((el) => {
const cls = [...el.classList].filter((c) => !GENERIC.has(c));
if (cls.length) list.push({ selector: '.' + cls[0], label: cls[0] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip hidden sections during auto-detection

This collects every <section> in the DOM, including sections hidden at the current viewport by responsive CSS or feature toggles. In those pages --sections will later call locator(...).screenshot() for a hidden element, which waits for visibility and reports an error even though the hidden section is not part of the rendered page being compared. Filter detection to visible sections for the active viewport before adding them to the section list.

Useful? React with 👍 / 👎.

- Expand GENERIC skip-list to include layout utility tokens (container,
  wrapper, inner, content, row, col) and filter Tailwind-style responsive
  prefixes (md:py-24, lg:) via regex so they are never used as selectors
- Use CSS.escape() on the chosen class name so special chars (colons,
  slashes) produce a valid CSS selector; qualify with `section.CLASS`
  to target the <section> element itself rather than any unrelated .cls node
- Skip sections hidden by CSS or feature toggles (display:none /
  visibility:hidden) during auto-detection — Playwright would timeout
  waiting for visibility on a hidden element
- Add a whole-page guard row at the end of each section-mode pass so
  live-only sections (inserted banners, extra sections) are caught even
  when every matched local section passes individually

@wpfyorg wpfyorg left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three P2 comments addressed in commit c3b4b3b:

Use a real section selector (line 243): Selector now uses section.CLASS (targets the <section> element directly, not arbitrary DOM nodes) with CSS.escape() so class names containing colons or other special chars produce valid selectors. GENERIC skip-list expanded to include common layout utility tokens (container, wrapper, inner, content, row, col); an isUtility() predicate also rejects Tailwind-style responsive prefixes (md:py-24, lg:, etc.).

Skip hidden sections (line 243): detectSections now checks getComputedStyle for display:none / visibility:hidden before adding a section to the list, so Playwright never attempts to screenshot a hidden element.

Detect live-only sections (line 228): A whole-page guard row is appended after each page's per-section rows. Per-section rows only confirm matched sections; the guard catches banners or extra sections that exist on live but not on local — and would have caused a silent exit-code 0.

…en each diff image

Agents tend to report diff percentages without opening the diff images, so
they never localize what actually changed. Make the directive imperative and
unmissable: report.md now ends with a checklist of the exact diff PNGs to Read
(failing rows only), the header explains % shows whether not what, and the
console epilogue says the same. SKILL.md step 4 made mandatory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant