Skip to content

Release v1.8.2: hero selector fixes for the beta layout, canary hardening - #146

Merged
Seismix merged 2 commits into
mainfrom
dev
Aug 28, 2026
Merged

Release v1.8.2: hero selector fixes for the beta layout, canary hardening#146
Seismix merged 2 commits into
mainfrom
dev

Conversation

@Seismix

@Seismix Seismix commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Summary

Patch release. RoyalRoad renumbered the redesign's chapter hero headings, which broke every recap on the beta layout; the canary that should have caught it was only checking the legacy layout. Both are fixed.

User-visible changes

  • Recaps work again on the beta layout. chapterTitle was pinned to h3 and matched nothing after RoyalRoad's renumber, so every recap failed with "Could not find the previous chapter's title".
  • Blurbs point at the fiction again. fictionTitle silently landed on the author's heading, so findFictionOverviewUrl()'s closest("a") walk fetched /profile/<id> — no error, just the wrong text.
  • togglePlacement is honoured on the redesign. prepareMounts() never read the key, so an Advanced Settings override did nothing. Rendered placement is unchanged.

Both hero selectors now match by role rather than heading level, which survives the next renumber. The remaining selectors on both layouts were re-anchored on semantic hooks instead of position and colour in the same pass.

Maintenance

  • Canary covers every layout in ADAPTERS via the beta-ui-v2 cookie, asserts the requested layout was served, runs daily, and files one labelled issue on drift.
  • Identity selectors are checked for what they resolve to, not just that they resolve; selectors must match exactly one element unless listed in MAY_REPEAT.
  • Drift-issue automation keys off the canary step's own outcome, and won't auto-close an issue after an outage — an all-skipped Playwright run exits 0, so the close now requires stats.expected > 0.
  • Canary runs as Desktop Chrome rather than HeadlessChrome, and no longer leaks a context per describe per retry during an outage.

Testing

  • Live canary green: 25 passed, 1 skipped (redesign blurbLabels, intentionally unconfigured).
  • 90 unit tests, e2e, svelte-check and Prettier all green.
  • pnpm zip:firefox && pnpm verify:sources✔ Every file matches, 14 files compared.
  • Manually tested on Chrome against the redesign layout.

Merge with a merge commit, not a squash. Squashing devmain puts a commit on main whose tree matches dev but whose history doesn't, destroying the merge base — the next release PR then lists every commit since the last real ancestor and main needs a hand-repaired reconcile back-merge. The repo already carries two of those scars (b6ba92f, 313fbb9).

…catch the next drift (#145)

* fix(redesign): match hero headings by role, not by heading level

RoyalRoad renumbered the redesign's chapter hero headings: the chapter
title went h3 -> h1 and the fiction title h4 -> h2. Both selectors were
pinned to the old levels, so on the beta layout:

  - chapterTitle matched nothing, and every recap failed with "Could not
    find the previous chapter's title";
  - fictionTitle silently landed on the AUTHOR's h4 instead. That one
    produced no error at all — the recap was titled with the author's
    name, and findFictionOverviewUrl()'s closest("a") walk pointed the
    blurb fetch at /profile/<id> rather than the fiction.

Match on role instead, which survives the next renumber: the chapter
title is the hero's only heading that is not inside a link, and the
fiction title is the heading inside the hero's link to the fiction.
Scoping the latter to that anchor also fixes the overview URL by
construction rather than by luck of document order.

* ci(canary): cover every layout, run daily, file an issue on drift

The drift above shipped unnoticed for a day because the canary could not
see it and would not have said so if it could. Two gaps, both closed:

Coverage. getChapterPageSelectors()/getFictionPageSelectors() default to
FALLBACK_ADAPTER, so the canary only ever checked the legacy layout — the
redesign's selectors were never tested against the live site at all. It
now loops over ADAPTERS and asks RoyalRoad for each layout in turn via
the beta-ui-v2 cookie, asserting first that the requested layout is what
was actually served so a withdrawn beta cannot masquerade as drift.

Cadence. It ran on pull_request only, and RoyalRoad changes its markup on
its own schedule rather than ours. Added a daily cron; a failing
scheduled run opens (or comments on) one labelled issue, which is closed
automatically when the canary goes green again — a red X on a cron run
notifies nobody.

Two further hardening changes fall out of the same bug. Presence checks
cannot see a selector that matches a real but WRONG element, so the two
identity selectors are now checked for what they resolve to: the chapter
title against the page's <title>, and the fiction title's closest("a")
against /fiction/. Under the old selectors that second test fails while
"fictionTitle selector exists" still passes. And selectors must now match
exactly one element unless listed in MAY_REPEAT, since matching several
is how the extension's querySelector starts reading the wrong one.

The canary no longer loads the extension, so the workflow's build step is
gone with it — one less way for this job to fail for reasons that have
nothing to do with selectors.

* refactor(selectors): anchor on semantic hooks instead of position and colour

Audit of every remaining selector on both layouts against the live site,
after the hero-heading drift showed what the fragile ones look like. Each
replacement below was verified across six different fictions.

Legacy:
  chapterTitle    h1.font-white -> .fic-header h1
  fictionTitle    h2.font-white -> .fic-header a[href*='/fiction/'] :is(h1..h6)
  closeButton     a four-deep nth-child chain -> #settings .modal-footer
                  button[data-dismiss='modal']
  reportPlacement div.col-lg-3:nth-child(3) -> div:has(> a[href^='/report/chapter/'])

Redesign:
  prevChapterBtn  now matches EITHER RoyalRoad's data-vt-direction='prev'
                  or the Font Awesome arrow, so losing one hook is survivable
  togglePlacement [class*='grid-cols-2']:has(#chapterSelect) — the same
                  element prepareMounts() resolves, so the override hint and
                  the behaviour finally agree, and it stops matching the
                  duplicate nav bar below the chapter
  closeButton     RoyalRoad's data-rr-dialog-close, not "the direct child
                  that happens to be a button"

font-white and the nth-child chains work today but say nothing about what
they select; the anchor-scoped fiction title additionally makes
findFictionOverviewUrl()'s closest("a") walk correct by construction
rather than by luck of nesting — the legacy layout had the same latent
author-heading trap the redesign fell into, one markup change away.

Deliberately unchanged: blurbLabels still leans on .font-red-sunglo, which
appears ~60 times on an overview page, because RoyalRoad gives the
content-warning box no id, data attribute or semantic class to aim at.

Fixtures now carry a .fic-header block with the author heading present,
mirroring the real page, so a title selector that drifts onto the author
fails in the suite. togglePlacement leaves the canary's MAY_REPEAT
allowlist: it matches exactly one element on both layouts now.

* ci(canary): gate the drift-issue automation on the canary's own result

Two ways the scheduled run's issue automation reported the wrong thing.

The close step ran on success(), but the canary calls test.skip() on
every test when RoyalRoad is unreachable, and Playwright exits 0 on an
all-skipped run (verified: stats.expected 0, exit 0). So an outage during
the 05:17 cron auto-closed a still-valid drift issue with "the canary is
green again", having checked nothing. It now runs the JSON reporter
alongside list and closes only when stats.expected is above zero.

The report step ran on failure() of ANY prior step, so a lockfile drift
or a Playwright download hiccup filed "RoyalRoad's layout drifted" and
sent someone into the adapters after a CI problem. Both steps now key off
steps.canary.outcome instead of the job's.

* test(canary): pin the browser it asks RoyalRoad with, and make the layout guard bite

browser.newContext() does not inherit the project's `use` options —
Playwright applies those in the context/page fixtures, which a shared
per-describe navigation can't use. The canary was therefore fetching
royalroad.com as HeadlessChrome rather than the Desktop Chrome the config
asks for, so anything the site varies on the user agent would have
surfaced as a selector drift on a required check.

The "RoyalRoad served this layout" test claimed to guard the tests below
it, but Playwright tests are independent and nothing skipped on its
failure: a renamed cookie would have failed it AND every selector test
with a "drifted" message, which is the exact masquerade the split exists
to prevent. The check now also runs in beforeAll and sets a flag the
selector tests skip on, while the test itself stays reportable.

Also close the context on the paths that never hand a page back — the
caller closes contexts through the page it was given, so an outage leaked
one context per describe per retry until the worker exited.

* fix(redesign): let togglePlacement actually move the toggle

prepareMounts() never read selectors.togglePlacement: it resolved the nav
bar from a hardcoded #chapterSelect walk with a #chapterHeroData
fallback, so resolveToggle() never returned null and the base mount was
dead code. A user whose toggle placement broke could edit the key in
Advanced Settings — which the settings UI offers precisely so a layout
change can be worked around without an extension update — and watch
nothing happen.

The configured selector is now asked first, with the #chapterSelect walk
as the fallback for when it stops matching. Both resolve to the same
inner grid on today's markup, so the rendered placement is unchanged.
@Seismix
Seismix merged commit 08b8631 into main Aug 28, 2026
5 checks passed
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