fix(ext): immediate SPA nav handling + stop the detection canary over-firing - #11
Merged
Conversation
…anary crying wolf Two non-blocking minors from the final Phase 4 QA pass. 1. COLD FIRST HOP held the previous video's verdict (colour and all) for ~2s. Root cause was upstream of the settle logic: yt-navigate-finish went through the 250ms debounce, and the first refresh after a full load is the one that NOTICES the url changed and starts the settle machinery, so the post-nav mutation storm delayed even that. Warm hops looked fine only because the previous navigation's settle timers were still firing. Navigation is a discrete, known-important event and is now handled immediately, with the debounced pass still following for the DOM settling after it. The tradeoff QA asked me to weigh: forcing gray during SETTLING DOES mean an allowed -> allowed hop between two videos BY THE SAME CHANNEL sits in grayscale for a beat, because the byline never changes there so nothing can confirm freshness and only the backstop ends the wait. Taken deliberately, grayscale is cosmetic and self-corrects, whereas holding colour on a video that might be from a channel the user is avoiding is the product failing at its one job. To bound the annoyance without touching the punitive side, colour now has its own shorter backstop (SETTLE_COLOR_MS 2.5s) while the interstitial keeps the full 6s. The reverse-direction protection is unchanged: the gate stays suppressed throughout. 2. THE DEGRADED-DETECTION CANARY over-fired. It counted in-feed ads and Shorts lockups as unidentifiable, so it announced "YouTube changed its DOM" on every ordinary home feed (~15 cards). Those card types have no channel BY NATURE, so their absence proves nothing, and a warning that fires when nothing is wrong is a warning nobody reads, which would have cost us the real signal the canary exists to give. isChannellessCard excludes a card that IS one, CONTAINS one (YouTube wraps in-feed ads in an ordinary ytd-rich-item-renderer, so the marker is a DESCENDANT, the case that made my first attempt still count the ad), or sits INSIDE a Shorts shelf. Tests are outcome-phrased: a mixed feed with an ad, a Shorts shelf and exactly one genuinely unreadable organic card counts ONE, stays silent when that organic card is removed, and leaves every unidentifiable card visible. The colour/verdict asymmetry is asserted directly (colour confirms mid-window while the verdict is still waiting). The cold-hop timing itself remains live-only, fixtures have no mutation storm. Gate: eslint clean, tsc clean, 598 unit tests (21 files, +6), 37/37 e2e.
astraedus
marked this pull request as ready for review
July 26, 2026 08:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two non-blocking minors from the final Phase 4 QA pass. Small round on top of the merged
0ca3ad2.1. Cold first hop held the previous verdict for ~2s
The root cause turned out to be upstream of the settle logic:
yt-navigate-finishwas routed through the 250ms debounce, and the first refresh after a full page load is the one that notices the URL changed and starts the settle machinery. So the post-nav mutation storm delayed even that. Warm hops only looked fine because the previous navigation's settle timers were still firing.Navigation is a discrete, known-important event, it's now handled immediately, with the debounced pass still following for DOM settling.
The tradeoff you asked me to weigh
Forcing gray during
SETTLINGdoes mean an allowed → allowed hop between two videos by the same channel sits in grayscale for a beat: the byline never changes there, so nothing can confirm freshness and only the backstop ends the wait.I took that deliberately. Grayscale is cosmetic and self-correcting; holding colour on a video that might be from a channel the user is avoiding is the product failing at its one job. To bound the annoyance without touching the punitive side, colour now has its own shorter backstop (
SETTLE_COLOR_MS2.5s) while the interstitial keeps the full 6s.The reverse-direction protection is unchanged, the gate stays suppressed throughout, so no regression there.
2. The degraded-detection canary was crying wolf
It counted in-feed ads and Shorts lockups as unidentifiable, so it announced "YouTube changed its DOM" on every ordinary home feed (~15 cards). Those card types have no channel by nature, so their absence proves nothing, and a warning that fires when nothing is wrong is a warning nobody reads, which would have cost us the real signal the canary exists to give.
isChannellessCardnow excludes a card that is one, contains one, or sits inside a Shorts shelf. The "contains" case is the one that caught me out: YouTube wraps in-feed ads in an ordinaryytd-rich-item-renderer, so the ad marker is a descendant, not an ancestor, my first attempt still counted the ad.Verification
npm run lintnpm run typechecknpm testnpm run buildnpm run e2eTests are outcome-phrased. The canary fixture is a realistic mixed feed, ordinary videos, an in-feed ad, a Shorts shelf, and exactly one genuinely unreadable organic card: it counts one, stays silent when that organic card is removed, and leaves every unidentifiable card visible. The colour/verdict asymmetry is asserted directly (colour confirms mid-window while the verdict is still waiting).
Still live-only: the cold-hop timing itself. Fixtures have no mutation storm, so nothing here reproduces the starvation that caused it, over to QA's live re-verify.
Draft on purpose, the orchestrator QA-gates the merge.