Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix(qbittorrent): guard against stale pagination responses#82

Open
jeromelefeuvre wants to merge 283 commits into
Shoshuo:mainShoshuo/Prismarr:mainfrom
jeromelefeuvre:fix/qbittorrent-pagination-racejeromelefeuvre/Prismarr:fix/qbittorrent-pagination-raceCopy head branch name to clipboard
Open

fix(qbittorrent): guard against stale pagination responses#82
jeromelefeuvre wants to merge 283 commits into
Shoshuo:mainShoshuo/Prismarr:mainfrom
jeromelefeuvre:fix/qbittorrent-pagination-racejeromelefeuvre/Prismarr:fix/qbittorrent-pagination-raceCopy head branch name to clipboard

Conversation

@jeromelefeuvre

Copy link
Copy Markdown

Summary

  • refreshData() aborts the previous in-flight fetch before firing a new one, but abort() is a no-op once a response has already arrived — common on localhost, where the round-trip can beat the click-to-JS-execution delay.
  • A periodic poll response for the old page could land after a user's page-change click and silently snap the view back to the previous page.
  • Adds a sequence counter so a response is only applied if no newer request has been issued since, regardless of whether abort() actually worked.

Test plan

  • Open the qBittorrent torrents page with several pages of results
  • Click to page 2/3 repeatedly right as the periodic auto-refresh would be ticking, confirm the page no longer snaps back to page 1
  • make lint-twig passes on the changed template

🤖 Generated with Claude Code

ndandan and others added 30 commits June 13, 2026 20:04
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ror, a11y)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plays-over-time chart and the Radarr stats chart loaded Chart.js from cdn.jsdelivr.net, but the app's CSP script-src is 'self' 'unsafe-inline' data: so the CDN script was blocked and neither chart rendered. Vendor Chart.js v4.4.6 under public/static/chart and reference it via asset() so it loads same-origin under the strict CSP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spec for optimizations #1 (cache the Radarr/Sonarr library list) and
Shoshuo#2 (parallelize the per-page upstream calls via curl_multi), targeting
the 10-15s library page loads. Items Shoshuo#3-Shoshuo#5 deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Invalidate on the actions that mutate normalized-list fields (membership,
monitored, hasFile, quality): filmMonitor, filmDelete, filmAdd, filmFileDelete,
filmsBulkEdit, filmsBulkDelete, filmEdit. Skip async-command actions
(rename / bulk refresh / bulk search) — their effects land later via scan,
not at request time.
Invalidate on the series actions that mutate normalized-list fields:
seriesAdd, seriesImportBatch, seriesBulkEdit, seriesBulkDelete, serieEdit,
serieMonitor, serieDelete, seriesFileDelete, seasonMonitor. Skip async-command
actions (refresh / search / rename / rss-sync) and episode-level toggles that
don't change the series list.
filmFileUpdate edits quality/languages/releaseGroup, which normalizeMovie
embeds into the cached movie's quality + language fields. Without
invalidation the change stayed hidden until the 45s TTL. Found in code review.
A Windows checkout (core.autocrlf=true) converts the s6-overlay control
files to CRLF, so a local `docker build` produces an image where
s6-rc-compile rejects every service ("type must be oneshot, longrun, or
bundle") and the container never boots. The repo already stores LF, so CI
builds were unaffected and this went unnoticed. Pin docker/** and *.sh to
eol=lf so the image builds correctly regardless of the contributor's git
settings.
perf(media): cache library list + concurrent batch fetch for Radarr/Sonarr pages
GitHub forces Node 20 actions off on 2026-06-16. Bump every action flagged
in the GHCR run's deprecation annotation to its Node 24 major:
  actions/checkout v4 -> v5 (matches the v5 already used in ci/dockerhub)
  docker/setup-qemu-action v3 -> v4
  docker/setup-buildx-action v3 -> v4
  docker/metadata-action v5 -> v6
  docker/login-action v3 -> v4
  docker/build-push-action v6 -> v7
  softprops/action-gh-release v2 -> v3
  peter-evans/dockerhub-description v4 -> v5
All move the runtime to Node 24 (GitHub-hosted runners satisfy the
v2.327.1+ requirement); none change the inputs these workflows use.
ci: bump GitHub Actions to Node 24 runtimes
docs: summarize fork changes vs upstream
Locks the fix from 1b38675: filmFileUpdate() must invalidate the bound
instance's library cache on a successful movie-file metadata edit (quality /
languages feed normalizeMovie's cached fields), and must NOT invalidate when
the upstream write fails. Verified fail-first: with the invalidate() call
removed the success case fails ('invalidate() expected once, never invoked').
test(media): regression test for films cache invalidation on file edit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ndandan and others added 28 commits July 10, 2026 22:31
…fails

A widget fragment fetch that failed (non-OK response or network error)
was funneled into the same path as a deliberately-empty body and hidden
with display:none - a crashed endpoint silently removed its section from
the dashboard, the one surface whose job is answering "is everything OK?".

Empty 200 bodies keep hiding the section (the "service not configured"
contract is unchanged). Failures now render a muted "This section failed
to load" + Retry state inside the card body, so the section header stays
visible. Retry re-arms the data-dash-loaded guard, restores the spinner
and refetches. The delegated click handler is bound once per session
(window guard) so Turbo revisits don't stack listeners; the live-refresh
pollers keep their fail-open behavior.

Verified against the running app: forced failure renders in place,
Retry restores real content, health poller self-heals its own section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… retry button

The retry button rendered the literal key "common.retry" — the catalog
nests it as common.action.retry. Caught on the live :beta render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or, Prowlarr switch

Three fixes from the 2026-07-10 design critique (28/40):

- Zero-value stat numerals lose their semantic hue: "0 errors"/"0 paused"
  (Deluge + qBittorrent, initial render and live poll), "0 failing"
  (Prowlarr) and "0 pending"/"0 declined" (Seerr) render text-secondary
  until the count is nonzero, so red/orange stay meaningful.
- The 11px readability floor for inline font-size .5/.6rem text (mobile
  audit 2026-06-29) now applies at all breakpoints — the 8.8-9.6px
  cluster was equally unreadable on desktop.
- Prowlarr indexer actions: the ON/OFF button (which read as a status
  label) becomes a Tabler switch whose position is the state, the
  destructive delete gets separated by a gap, and the low-value ADDED
  column hides below xxl so the actions column stops clipping at 1440px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…~1550px)

The d-xxl-table-cell breakpoint (1400px) sits below the width where the
indexers table actually outgrows its card — at a 1440px viewport the
actions column still clipped by ~86px. Measured live: the table fits
from ~1526px up, so the ADDED column now hides under 1550px via a page
media query instead of the Bootstrap utility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion edges, quick-look close

Six fixes from the 2026-07-10 critique re-run (30/40):

- Calendar: the 8.8-9.9px month-view event text (the app's smallest) is
  floored at 11px on ALL breakpoints — the existing mobile floor list is
  unwrapped from its media query, matching the base.html.twig decision.
- Discover: countdown badges localize their prefix (EN "D-4", FR "J-4")
  instead of hardcoded French "J-"; poster type chips use dedicated short
  labels (EN "TV"/"Movie") so they stop colliding with the library badge
  ("TV Sho"), and card badges ride the 11px floor.
- Quick-look: the close button grows from Bootstrap's 14px/40%-opacity
  default to a 32px circular target on a permanent dark scrim, so it
  survives bright hero artwork.
- Radarr/Sonarr health banners drop the raw check-name prefix
  ("ImportMechanismCheck:") and show just the human message.
- SABnzbd: the speed-limit input gets a visible MB/s unit suffix (the
  unit only existed in the placeholder, invisible once a value is set).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ormat too

filmWarnings() hardcoded 'source : message' concatenation, so the
check-name prefix survived there after warning_format dropped it.
Caught on the live :beta — the banner still showed
"ImportMechanismCheck : ...". Now uses the same trans call as the
page-render path (line 127) and the Sonarr twin (line 887).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h batch

8 commits from the 2026-07-10 dual-agent design critique (28→30/40),
all live-verified on :beta: retryable dashboard widget error states,
earned semantic hues on zero-value stats, 11px text floor at all
breakpoints (app-wide + calendar), Prowlarr switch + un-clipped actions,
localized countdown badges, quick-look close button, clean health
warnings, SABnzbd unit suffix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… dl-pulse loop

Design-critique round-2, fork-only:

- Shoshuo#3 Calendar events open the app-global quick-look modal IN PLACE instead
  of ejecting to /medias?open=N (honors "everything in place"). Reuses the
  same library quick-look endpoint the dashboard/search use; the old
  /medias route is kept as a graceful fallback. Day-detail popup events are
  now clickable too. Factored the modal fetch+inject into a shared
  window.prismarrQl.openUrl(url, fallback) and routed the existing
  [data-ql-trigger] handler through it.
- Shoshuo#4 Episode blocks recolored into the blue/cyan family so they match the
  single blue "episode" legend dot; the old purple/pink/green/orange
  variants collided with cinema/digital/physical legend hues.
- Shoshuo#8 Removed the infinite decorative dl-pulse glow/scale loop on the sidebar
  download badge (the app's only non-state-driven motion, no reduced-motion
  guard). Static glow retained; a single reduced-motion-guarded .dl-bumped
  pulse now fires only when the count rises, via one shared
  window.prismarrDlBadge helper (deduped 4 poller badge-update sites).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design-critique round-2, priority-1 bug. 4 of 17 theme presets set
data-bs-theme=light, but several custom fork components hardcoded
near-white text / dark-only fills that turned invisible or washed out on
a white surface. Audited every inline <style>/style="" block and fixed
the genuine breaks (leaving white-on-colored-badge and already-gated
rules alone):

- calendrier: date numbers, day-of-week header, "+N more" and active
  filter pill switched to --tblr-secondary-color / --tblr-primary;
  pill hover uses primary-rgb tints; added a light-mode override that
  bumps the translucent event-block fills near-opaque so their pastel
  text keeps contrast over the white cell surface.
- jellyseerr: detail-modal hero gradient bottom stop was var(--tblr-card-bg)
  (white in light) under white title text -> pinned to opaque dark.
- prowlarr: inactive tag chips, capability labels, new-tag input and the
  #818cf8 protocol/tag badges tokenized.
- radarr/sonarr/prowlarr updates: changelog list text -> secondary-color.
- media/series_detail: header pinned to an opaque dark base (white hero
  text had no dark fallback); status badge tokenized.
- media/series: JS-built "followed" badge tokenized.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design-critique round-2. The media/explorer/watchlist/calendar "cards"
open a modal or quick-look on mouse click but were non-focusable DIVs
with no accessible name. Now they are focusable (tabindex=0), operable
(Enter/Space), and named (aria-label), with a visible focus ring:

- Shared global handler in _quicklook.html.twig: Enter/Space on a focused
  [data-kb-activate] element dispatches its click (bailing when focus is
  inside a real input/textarea/select/button/a/contenteditable).
- Stamped tabindex=0 role=button data-kb-activate + aria-label on:
  media/films + media/series .media-card, decouverte explorer .ex-card
  (JS), decouverte index static + JS-built .media-card, watchlist
  .wl-card, and all four calendar event blocks (month/week/day-row/
  day-popup) which already open the quick-look.
- base.html.twig: one [data-kb-activate]:focus-visible outline (theme
  primary), not clipped by the cards' overflow:hidden.

Existing img alts left as-is (decorative posters inside a now-labeled
card are correct).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner-requested additive Tabler primitives (fork-only):

- .avatar-list (stacked) in the shared Plex summary strip
  (_plex_summary, used by the dashboard widget + Tautulli now-playing):
  a glanceable "who's watching" row of initial-avatars built from the
  distinct session userDisplayNames — no backend change, degrades to
  nothing when idle. New key dashboard.plex.viewers (en/fr).
- .ribbon "New" corner flag on films/series library cards for items
  added within 7 days. Kept SPARSE and semantic (not a blanket
  quality ribbon, which the critique warned reads as uniform noise);
  on films it swaps the top-left quality-badge slot so corners never
  double up. New key common.labels.new_badge (en/fr) + a scoped
  .media-card .ribbon size rule on both pages.

Verified the Tabler .ribbon/.avatar-list classes exist in the shipped
CSS bundle before wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lp (Shoshuo#7)

Design-critique P3 grab-bag (fork-only), the high-value items:

- dashboard.quicklook.seasons was hardcoded "{count} seasons" (rendered
  "1 seasons"); now an ICU plural (en + fr).
- Sidebar logo linked to a hardcoded tmdb_index (Discover); now points at
  app_home, which already resolves the admin's display_home_page
  preference (dashboard/discovery/films/…/last-visited).
- Admin display settings: the Theme preset and Primary colour pickers had
  no explanation of how they relate. Added help text — Primary colour is
  the accent override ("Auto" keeps the preset's own accent); Theme is the
  full scheme that ships its own accent (en + fr).

Not changed: the 404/error page already extends base.html.twig with the
full sidebar/theme chrome + an .empty state and home CTA (the critique's
"degraded shell" flag predated a live check). Remaining P3s (Tautulli
casing, topbar-title gaps, dual save paradigms, Explorer empty state) left
as low-value polish for a later pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Shoshuo#2)

Design-critique deferred item. TMDb release dates, certifications, alt
titles and watch/providers all picked countries from hardcoded FR-first
lists, so an English user saw French release dates and providers first.

- New TmdbClient::regionPriority(locale, append) returns a country-code
  priority led by the locale's home region (en → US/GB/CA/AU, fr →
  FR/BE/LU/CA, + es/de/pt/it), followed by a broad common fallback chain
  and any extra countries present in the payload, de-duplicated and
  order-preserving.
- Wired at all six sites (via $this->translator->getLocale()):
  TmdbController alt-titles / pickProviders / pickMovieCertification /
  pickTvCertification, DashboardController tmdbMovieReleaseDates /
  quick-look providers.
- Unit test for regionPriority (6 cases: fr/en lead, locale suffix,
  append dedup, unknown + empty locale fallbacks) — green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…partial

Shared component vocabulary, slice 1 of 3. deluge/qbittorrent/usenet each
carried a near-identical view-switcher (list/table/compact) with its own
.qbt-view-*/.usenet-view-* markup, CSS and JS selectors.

- New _view_switcher.html.twig renders .view-switcher > .view-btn from a
  modes array (view/label/icon/title) + active + aria_label.
- Global .view-switcher/.view-btn CSS in base.html.twig, active accent =
  var(--tblr-primary) (was a hardcoded #2f67ba on the torrent pages; now
  matches the theme like usenet already did). Descendant-scoped so the
  media pages' bare .view-btn is untouched.
- All three pages migrated to the partial; local switcher CSS removed
  (layout rules .qbt-torrent-list[data-view]/.usenet-list[data-view]
  kept); JS click-delegation selectors → .view-btn. data-view values and
  default active unchanged. lint:twig OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared component vocabulary, slice 2 of 3.

- Global .stat-val/.stat-lbl in base.html.twig; removed the duplicate
  local copies from usenet/qbittorrent/deluge (kept their card-specific
  rules: .qbt-stat-card, .stat-speed/.stat-icon, VPN/total cards).
- New _stat_tiles.html.twig renders a tiles[] list in either a 'strip'
  (one card, inline value+label) or 'grid' (card per tile) layout; hue
  accepts a Tabler token (→ text-*) or a raw color (→ inline style);
  id/data_stat/unit passthrough for live-updated values.
- Migrated films + series (strip), prowlarr + usenet (grid) to the
  partial with their exact values/labels/hues. usenet's data-stat poll
  hooks preserved on .stat-val. Removed prowlarr's now-unused .pw-stat.
- deluge/qbittorrent keep their rich live-updating stat-cards (IDs, SVG
  watermarks, VPN) — only the shared-vocabulary CSS moved to global.

Harmonizations (intended): films/series value h3→stat-val, label
uppercased; prowlarr gutter g-3→g-2. lint:twig OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ulary

Shared component vocabulary, slice 3 of 3. films/series used Tabler
outline-buttons with solid-colored active state (.filter-btn); deluge/
qbittorrent used transparent tinted-active pills (.qbt-filter-btn) — two
looks for the same job.

- Global .filter-pill in base.html.twig: transparent, hue-tinted when
  active, per-pill semantic colour via --pill-hue (color-mix), default
  theme primary.
- New _filter_pills.html.twig renders the pill row from a pills[] list
  (filter/label/hue/count?/title?) + active + fill.
- Migrated films, series (fill, inside their GET form — hidden status
  input + submit-on-click preserved) and deluge, qbittorrent (client-side
  refresh preserved). Every data-filter value unchanged; JS selectors
  retargeted .filter-btn/.qbt-filter-btn → .filter-pill; per-filter CSS
  removed. Semantic colours preserved as --pill-hue.
- usenet/prowlarr keep their status dropdowns; downloads/watchlist tab
  filters (page-scoped, different pattern) untouched.

Visual change: films/series active pills go solid→hue-tinted; torrent
active bg now derives from the pill's text hue. lint:twig OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…k, a11y, Tabler r3, TMDb locale, shared component vocabulary

Fork-only UI batch (branch ui/design-critique-round-2, 12 commits, CI green,
live-verified on :beta):

- #1 light-preset readability: tokenized hardcoded dark literals so the 4
  light theme presets are legible (calendar, jellyseerr hero, prowlarr,
  updates changelogs, series header).
- Shoshuo#3/Shoshuo#4 calendar: events open the in-place quick-look (no eject); episode
  blocks recolored to match the blue legend dot.
- Shoshuo#5 a11y: media/explorer/watchlist/calendar cards are keyboard-focusable
  and operable (Enter/Space) with a focus ring.
- Shoshuo#7 P3s: season ICU plural, logo honors display_home_page, Theme vs
  Primary-colour help text.
- Shoshuo#8: retired the infinite dl-pulse loop (state-driven pulse instead).
- Tabler r3: .avatar-list of Plex viewers, sparse "New" .ribbon on cards.
- Shoshuo#2 TMDb: release/cert/provider region priority driven by locale, not
  hardcoded FR (TmdbClient::regionPriority + unit test).
- Shoshuo#6 shared component vocabulary: unified _view_switcher, _stat_tiles and
  _filter_pills partials + global CSS across the service pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dpoint

The dashboard's five live widgets (plex 10s, health/server/network 30s,
houndarr 60s) each ran their own setInterval and their own fragment
request, so at every 30s alignment up to four HTTP requests fired at
once (~13 requests/min steady state).

Replace the five bespoke refreshX functions + timers with one generic
coalescing scheduler: each widget node declares its cadence via
data-dash-poll, the scheduler ticks at the GCD of the present cadences,
and every widget due on a tick is batched into a single
/tableau-de-bord/widgets?w=... request that returns a { name: html } map.
Steady state drops to ~6 requests/min and the aligned burst collapses to
one request. Cadences are unchanged; initial hydration still fetches each
fragment in parallel for fast first paint; per-widget fail-open,
hidden-tab guard and Turbo-singleton behaviour are all preserved.

The combined endpoint reuses the existing widget actions verbatim
(widgetPlex/Health/Server/Houndarr/Network) via a private dispatch, so
their admin gates and empty-when-unconfigured contract are identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mbined /widgets endpoint

Live-verified on :beta — plex-only at 10s, four-widget burst coalesced to one
request at 30s, all five at 60s; every widget renders + refreshes, plex tab
persists across swap, console clean. ~13→~6 poll requests/min.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Catch the docs up to everything shipped since 2026-07-10:

- README: add the UniFi Network widget to the fork-only + Features lists
  (it shipped 2026-07-05 but was only in CHANGELOG/FORK-CHANGES).
- CHANGELOG: log the 2026-07-11 design-critique round 2 (light-preset
  legibility, calendar quick-look + legend-true colours, keyboard-
  activatable cards, .ribbon/.avatar-list, locale-aware TMDb regions,
  shared component partials, dl-pulse retirement).
- FORK-CHANGES: restamp to 2026-07-11 and add the missing waves —
  worker mode + OPcache + dashboard poller consolidation, mobile/settings
  polish, and the design-critique/accessibility pass.

Docs only; no code or image change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes per-page inline init scripts that re-bound a document-level
turbo:load listener on every Turbo body swap, accumulating stale
slug-baked closures that corrupted other instances' saved keys. The
module now loads once in <head>, is guarded against re-init, and reads
the page type/slug from a data-libfilter-* marker on the filter form.
Also hardens the EXCLUDE lookup with hasOwnProperty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each Radarr/Sonarr library tab reopens with the user's last-used
filter + sort, remembered per instance in localStorage. Filters are
saved off the URL on load and restored via location.replace only on a
truly empty URL (so ?open= deep links are untouched); sidebar/subnav
library links are rewritten on load so the common path lands filtered
with no reload flash; Reset/Clear wipes the saved state.

Implemented via a single head-loaded module driven by a data-libfilter-*
marker on the filter form — no per-page scripts, no accumulating Turbo
listeners. Search text is intentionally not remembered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
)

The full-library endpoints (/api/v3/series, /api/v3/movie) are fetched
via the shared get(), which capped CURLOPT_TIMEOUT at 4s (Sonarr) / 8s
(Radarr). On a large or busy instance the response body doesn't finish
in that window — libcurl reports "Operation timed out after 4002 ms with
0 bytes received" (TCP connected, no body yet), which trips the circuit
breaker and surfaces the service as "unreachable/disconnected".

Give only the two heavy full-library calls a 30s budget; connect timeout
stays 4s so a genuinely-down service still trips the breaker fast. Radarr
get() gains an optional $timeout param (default 8s) so all other call
sites are unchanged. The library route already allows set_time_limit(120)
and the MediaLibraryCache means only the cold fetch pays this cost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refreshData() aborts the previous in-flight fetch before firing a new
one, but abort() is a no-op once a response has already arrived —
common on localhost, where the round-trip can beat the
click-to-JS-execution delay. A periodic poll response for the old
page could then land after a user's page-change click and silently
snap the view back to the previous page.

Add a sequence counter so a response is only applied if no newer
request has been issued since, regardless of whether abort() worked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.