Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions DISCOVERIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ AI agents: when you contribute an improvement, add an entry here. See [CONTRIBUT

---

## 2026-05-28 — Squarespace 7.1 DOM fallback was capturing 1×1 placeholders instead of real images

**Found by:** Claude + Davi
**During:** Migrating https://www.walkaboutchronicles.com — a 1,500+ post Squarespace 7.1 photo blog with image-heavy posts (median 1–3 images, heaviest 33). On 7.1 Fluid Engine pages the `?format=json` body comes back empty, so the adapter falls through to the Playwright DOM extractor. Most posts ended up referencing the same placeholder image instead of the actual photos.
**Type:** platform quirk

### What I found
Squarespace 7.1 lazy-loads `<img>` elements via its own image loader. At the moment Playwright's `domcontentloaded` + 10s `networkidle` fires, most off-screen `<img>` tags still have a placeholder `src` (often `data:image/svg+xml;base64,...` or `https://images.squarespace-cdn.com/.../1x1`) — and the real CDN URL lives in `data-image=` waiting for the loader to swap it in. `extractDomContent` in `src/adapters/squarespace.ts` reads `(el as HTMLImageElement).src`, so for any image not yet hydrated by the loader it captures the placeholder, not the real URL.

The net effect: a Fluid Engine post with 8 images shows up in the WXR with 1 distinct media URL (the placeholder) referenced 8 times. The media downloader then fetches a tiny SVG, the post body shows broken images, and `verify` reports a confusing mix of "no extraction failures" + "all images failed at preview time."

### How it works
Read `data-image` first, then `data-src` (other lazy patterns sometimes use it), and only fall back to `.src` last. Validate with a `^https?://` regex so we don't accidentally pick up data URIs:

```ts
const dataImage = img.getAttribute('data-image') || img.getAttribute('data-src') || '';
const rawSrc = dataImage && /^https?:\/\//.test(dataImage) ? dataImage : img.src;
```

The fix sits inside the `IMG` branch of the existing element walker; no new browser round-trip and no waiting for the loader to fire.

### Why it's better than the previous approach
Before: 7.1 posts surfaced through DOM fallback emerged with placeholder URLs as their media references — silently, because the downloader saw a valid HTTP response (1×1 SVG) and the extraction log marked the URL as successfully fetched. The break only surfaced on visual review of the imported site.

After: the first-pass DOM extraction captures the real CDN URLs directly, eliminating a class of silent-failure migrations on what is the most common modern Squarespace site flavor.

---

## 2026-04-30 — `--resume` overwrites the existing WXR with only newly-extracted items

**Found by:** Claude + James
Expand Down
23 changes: 16 additions & 7 deletions src/adapters/squarespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,22 @@ async function extractDomContent(

for (const el of elements.slice(0, 250)) {
if (el.tagName === 'IMG') {
const src = (el as HTMLImageElement).src;
const alt = (el as HTMLImageElement).alt || '';
if (src && src.startsWith('http')) {
mediaUrls.push(src);
if (!seen.has(src)) {
seen.add(src);
blocks.push(`<img src="${src}" alt="${alt}" />`);
const img = el as HTMLImageElement;
// Squarespace 7.1 lazy-loads images: the visible `src` may be a
// 1x1 placeholder (or empty) until the JS loader runs, while the
// real CDN URL sits in `data-image=`. On a freshly-rendered DOM
// captured by Playwright the loader frequently hasn't fired for
// off-screen images, so reading `.src` yields placeholders.
// Prefer `data-image` when it looks like a real URL; fall back to
// `data-src` (other lazy patterns) and finally `.src`.
const dataImage = img.getAttribute('data-image') || img.getAttribute('data-src') || '';
const rawSrc = dataImage && /^https?:\/\//.test(dataImage) ? dataImage : img.src;
const alt = img.alt || '';
if (rawSrc && rawSrc.startsWith('http')) {
mediaUrls.push(rawSrc);
if (!seen.has(rawSrc)) {
seen.add(rawSrc);
blocks.push(`<img src="${rawSrc}" alt="${alt}" />`);
}
}
continue;
Expand Down