Skip to content

fix(broken-links): prefer parent-path over homepage fallbacks and block record-ID sibling suggestions#2576

Open
jsanchez07 wants to merge 5 commits into
mainfrom
SITES-45434-filtering-homepage-fallbacks
Open

fix(broken-links): prefer parent-path over homepage fallbacks and block record-ID sibling suggestions#2576
jsanchez07 wants to merge 5 commits into
mainfrom
SITES-45434-filtering-homepage-fallbacks

Conversation

@jsanchez07

Copy link
Copy Markdown
Contributor

Problem

When Mystique cannot access site content (e.g. no scraped pages cached for the domain),
it falls back to suggesting the site's homepage. That suggestion passes all existing
validation (same domain, returns 200) but gives users no useful redirect target — it just
dumps visitors at the root.

A second related problem: for broken URLs with numeric or prefixed-numeric slugs
(/event-schedule/917, /events/ticketevent-1226947352), Mystique sometimes suggests
a sibling record at the same path depth. Those records are equally deleted — or unrelated
— so the suggestion is wrong regardless of how good the path overlap looks.

Changes

src/utils/url-utils.js

  • looksLikeRecordId(slug) (new private helper): returns true for pure-numeric slugs
    ("917") and prefixed-numeric slugs ("ticketevent-1226947352").
  • isEntityReplacementSuggestion: extended to cover record-ID broken slugs.
    • Same-depth sibling → rejected (e.g. event-schedule/917event-schedule/2671).
    • One-level-up sibling → rejected unless it is the immediate parent directory itself
      (e.g. events/babymetal rejected, events/event-schedule/ kept).
    • Also relaxed the depth-equality guard so the record-ID path can be checked before
      the person-entity logic.

src/broken-links-guidance/guidance-handler.js

  • nonRootSuggestedUrls filter: after filterBrokenSuggestedUrls, drops any URL
    whose pathname is / or "". Mystique's homepage fallback passes the existing HTTP
    check (200) but is never a useful redirect target.
  • Parent-path fallback chain: when nonRootSuggestedUrls is empty:
    1. Prefer suggestions saved by a previous run (no change — existing logic).
    2. Try resolveParentPathFallback — walk up the broken URL one level at a time and
      return the first parent that responds 200. Never tries the root.
    3. If no live parent found, restore Mystique's original homepage suggestion
      (may be intentional — entire section removed).
    4. Last resort: effectiveBaseURL.
  • aiRationale clearing: when a homepage is replaced by a parent path, Mystique's
    rationale (which was written about the homepage) is dropped so it does not mislead
    users about why the parent path was chosen.

Safety properties

  • Mystique always wins — any non-homepage suggestion from Mystique is used unchanged.
  • Depth-1 broken URLs are immuneresolveParentPathFallback never tries root, so
    /some-page → returns null → homepage suggestion is preserved.
  • Live check required — parent path must return HTTP 200 or it is skipped.
  • Existing suggestions are preserved — if a previous run stored valid URLs they are
    reused and the fallback chain is never entered.

Test plan

  • resolveParentPathFallback returns first live parent, skips 404 parents, returns
    null when all parents 404 or broken URL has depth 1
  • isEntityReplacementSuggestion rejects same-depth and sibling-of-parent record-ID
    suggestions; keeps the immediate parent directory suggestion
  • Guidance handler uses parent-path when Mystique returns only homepage
  • Guidance handler restores homepage when no live parent found
  • aiRationale is cleared on homepage→parent-path replacement, preserved otherwise
  • Existing suggestions from a prior run short-circuit the fallback

@github-actions

Copy link
Copy Markdown
Contributor

This PR will trigger no release when merged.

@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment on lines +208 to +211
} else if (
nonRootSuggestedUrls.length === 0
&& filteredSuggestedUrls.length > 0
&& !filteredSuggestedUrls.includes(nextSuggestedUrls[0])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This also fires when we fell back to existing suggestions from a prior run which wipes existingData.aiRationale even though it's still valid. Consider moving aiRationale = undefined into the if (parentFallback) branch instead.

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.

2 participants