fix(sse): send torrent page deltas instead of full snapshots - #2047
Conversation
The list stream re-sent a full ~300-row page-0 snapshot on every ~2s tick. On large instances that saturates the HTTP/2 connection flow-control window (auth/me stalls 20-50s -> black screen) and burns CPU and bandwidth on every front-end. Each subscription group now keeps a baseline (ordered keys + per-row fingerprint) and a tick emits only the added/changed rows plus the page key order (sent only when membership or order changed); aggregate metadata still travels whole so every existing stream consumer stays correct. init seeds the baseline so the first delta applies cleanly, and a full keyframe re-baselines every 30s to bound any drift without a reconnect. Closes #2046.
WalkthroughReplaces full-page SSE snapshots on every 2-second tick with incremental delta frames. The server computes per-row FNV-1a fingerprints, emits only changed rows plus an optional order list, and forces a full keyframe every 30 seconds. The client reconstructs the full page from the previous baseline plus the delta patch, gating downstream React state updates on actual row changes. Additionally, the connection stale-watchdog adopts a longer initial grace window (60s) to accommodate slow or large init downloads before the 15s steady-state heartbeat budget applies. ChangesSSE Delta/Keyframe Feature
Sequence Diagram(s)sequenceDiagram
participant qBit as qBittorrent Poller
participant mgr as SSE Manager
participant delta as buildUpdatePayload
participant sub as Subscriber Session
Note over mgr,delta: Init path (first connection)
mgr->>sub: writeInitToSession → streamEventInit (full snapshot)
mgr->>delta: seedBaselineIfEmpty(initData)
Note over mgr,delta: Tick path (~2s)
qBit->>mgr: HandleMainData(instanceID, meta)
mgr->>mgr: publishInstance → enqueueGroup (meta only)
mgr->>mgr: processGroup → buildGroupUpdatePayload
mgr->>delta: buildUpdatePayload(response, now)
alt baseline unseeded OR keyframe interval elapsed (30s)
delta-->>mgr: StreamPayload{type:"update", Data:fullSnapshot}
else rows changed
delta-->>mgr: StreamPayload{type:"delta", Data:{changedRowsOnly}, Delta:{order?}}
else aggregate-only change
delta-->>mgr: StreamPayload{type:"delta", Data:{}, Delta:{}}
end
mgr->>sub: broadcast payload
Note over sub: Client reconstruction
rect rgba(100, 149, 237, 0.5)
sub->>sub: SyncStreamContext receives "update" or "delta"
sub->>sub: useTorrentsList.handleStreamPayload
sub->>sub: applyStreamDelta(lastFullSnapshotRef, payload)
sub->>sub: applyOrderedDelta(prevRows, changedRows, order)
sub->>sub: rowsChanged → setAllTorrents (conditional)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…lta fingerprint Refutes the adversarial-review concern that an aggregate-only delta could hide a table-visible row change. The server fingerprints the whole row JSON, so any field change (including cross-instance instance_name) forces the row into the delta and it is never an aggregate-only skip. Documents the invariant where the skip happens.
…t storm On large instances the init is a single ~600KB SSE event. SSE is ordered, so the 5s heartbeats queue behind it and cannot arrive until it drains. The flat 15s stale watchdog therefore fired mid-init, tore down the in-flight download, and reconnected into a fresh full init -- a self-sustaining reconnect storm that saturated the shared HTTP/2 connection and starved every other request (auth/me, REST), producing the black screen / "Connecting to stream..." / perpetual polling. Use a longer pre-first-event budget (STREAM_INITIAL_TIMEOUT_MS) so the init can land; the normal 15s budget resumes once any event has arrived. REST polling still serves data during the grace, so nothing is blocked.
The delta optimization was barely shrinking the stream: a 50s capture of a 4816-torrent instance showed each "delta" was ~350KB every 2s (vs a 593KB full page). The cause was fingerprinting the whole row for change detection -- reannounce counts down, time_active/seeding_time increment, and peer counts jitter every tick, so nearly every row was flagged "changed" and re-sent each tick. On an HTTP/2 transport (e.g. tailscale serve) that ~175KB/s saturates the shared connection's flow-control window and head-of-line-blocks auth/me and every other request, which is the black screen / "ages to load". Change-detect on a row with those volatile fields zeroed, so a mostly-idle page streams only the rows that meaningfully changed (~5 active of 4816 here) instead of the whole page. The excluded fields still ride along with their current value whenever a row is sent for a real change. Also drop the periodic 593KB keyframe: a recurring full re-send re-jams the h2 connection every 30s. Correctness rests on init-seeds-baseline plus a fresh init on reconnect.
…#160) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.20.0` → `v1.21.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/7) for more information. --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.21.0`](https://github.com/autobrr/qui/releases/tag/v1.21.0) [Compare Source](autobrr/qui@v1.20.0...v1.21.0) ##### Changelog ##### New Features - [`51b29a1`](autobrr/qui@51b29a1): feat(api): add GET /api/version reporting the running version ([#​2060](autobrr/qui#2060)) ([@​s0up4200](https://github.com/s0up4200)) - [`98348f1`](autobrr/qui@98348f1): feat(automations): add RLS\_YEAR release-year condition ([#​2003](autobrr/qui#2003)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`58cc7cb`](autobrr/qui@58cc7cb): feat(automations): add tracker status and message conditions ([#​2006](autobrr/qui#2006)) ([@​luckylittle](https://github.com/luckylittle)) - [`2176a92`](autobrr/qui@2176a92): feat(automations): list configured indexer trackers in workflow selector ([#​2005](autobrr/qui#2005)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2de926e`](autobrr/qui@2de926e): feat(i18n): add Italian (it) translation ([#​2011](autobrr/qui#2011)) ([@​s0up4200](https://github.com/s0up4200)) - [`7b66655`](autobrr/qui@7b66655): feat(i18n): add Korean (ko) translation ([#​2042](autobrr/qui#2042)) ([@​jussaw](https://github.com/jussaw)) - [`2c06a3d`](autobrr/qui@2c06a3d): feat(i18n): add Ukrainian (uk) translation ([#​2032](autobrr/qui#2032)) ([@​rodion981](https://github.com/rodion981)) - [`ef8da0b`](autobrr/qui@ef8da0b): feat(sse): keep cached torrents with a staleness badge when qBittorrent is slow ([#​2057](autobrr/qui#2057)) ([@​s0up4200](https://github.com/s0up4200)) - [`38ebb6e`](autobrr/qui@38ebb6e): feat(web): add file priority controls to torrent file views ([#​2062](autobrr/qui#2062)) ([@​jussaw](https://github.com/jussaw)) ##### Bug Fixes - [`12ac536`](autobrr/qui@12ac536): fix(ci): skip release binary build on main pushes ([#​2008](autobrr/qui#2008)) ([@​s0up4200](https://github.com/s0up4200)) - [`57210a3`](autobrr/qui@57210a3): fix(crossseed): drop resolution token from TV search queries ([#​2017](autobrr/qui#2017)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`791bc7d`](autobrr/qui@791bc7d): fix(crossseed): inject rootless single-file adds into matched folder ([#​2029](autobrr/qui#2029)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3da437f`](autobrr/qui@3da437f): fix(crossseed): re-query indexers with no usable hit in the alternate connector pass ([#​2002](autobrr/qui#2002)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8e8b6d9`](autobrr/qui@8e8b6d9): fix(database): prevent int4 sequence exhaustion in Postgres string interning ([#​2015](autobrr/qui#2015)) ([@​s0up4200](https://github.com/s0up4200)) - [`c7e08b9`](autobrr/qui@c7e08b9): fix(db): index string\_pool FK columns so GC stops timing out ([#​2051](autobrr/qui#2051)) ([@​s0up4200](https://github.com/s0up4200)) - [`2553f75`](autobrr/qui@2553f75): fix(docs): use relative file paths for markdown links ([#​2013](autobrr/qui#2013)) ([@​nasenov](https://github.com/nasenov)) - [`8a9c2fc`](autobrr/qui@8a9c2fc): fix(i18n): improve French translation, fix hardcoded UI statuses, and update agent rules ([#​2014](autobrr/qui#2014)) ([@​OlziYT](https://github.com/OlziYT)) - [`190d936`](autobrr/qui@190d936): fix(jackett): decouple cache persistence from SkipHistory ([#​2056](autobrr/qui#2056)) ([@​jussaw](https://github.com/jussaw)) - [`91ad7e4`](autobrr/qui@91ad7e4): fix(pprof): bind to configurable loopback address instead of :6060 ([#​2044](autobrr/qui#2044)) ([@​s0up4200](https://github.com/s0up4200)) - [`10e2d02`](autobrr/qui@10e2d02): fix(qbittorrent): serve stale app info/preferences when refresh fails ([#​2054](autobrr/qui#2054)) ([@​s0up4200](https://github.com/s0up4200)) - [`6d3b04c`](autobrr/qui@6d3b04c): fix(sse): resolve sync-error staleness stamp off the sync-loop callback ([#​2065](autobrr/qui#2065)) ([@​s0up4200](https://github.com/s0up4200)) - [`677e8f4`](autobrr/qui@677e8f4): fix(sse): send torrent page deltas instead of full snapshots ([#​2047](autobrr/qui#2047)) ([@​s0up4200](https://github.com/s0up4200)) - [`a4d252b`](autobrr/qui@a4d252b): fix(sse): size full-sync timeout to prevent permanent disconnect on large instances ([#​2037](autobrr/qui#2037)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4f375c6`](autobrr/qui@4f375c6): fix(torrents): route unified view exports to the owning instance ([#​2018](autobrr/qui#2018)) ([@​s0up4200](https://github.com/s0up4200)) - [`d49e168`](autobrr/qui@d49e168): fix(web): move instance status bar back to bottom of screen ([#​2024](autobrr/qui#2024)) ([@​OlziYT](https://github.com/OlziYT)) - [`a69ae69`](autobrr/qui@a69ae69): fix(web): prevent framer-motion tab indicator from triggering layout shifts ([#​2025](autobrr/qui#2025)) ([@​OlziYT](https://github.com/OlziYT)) ##### Other Changes - [`860b5bb`](autobrr/qui@860b5bb): chore(deps): bump anthropics/claude-code-action from 1.0.140 to 1.0.148 in the github group ([#​2045](autobrr/qui#2045)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`0b23a7d`](autobrr/qui@0b23a7d): chore(deps): bump the github group with 2 updates ([#​2031](autobrr/qui#2031)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`1fea36b`](autobrr/qui@1fea36b): docs: document frontend testing conventions ([#​1984](autobrr/qui#1984)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f3443ec`](autobrr/qui@f3443ec): docs: promote multi-language support ([#​2009](autobrr/qui#2009)) ([@​s0up4200](https://github.com/s0up4200)) - [`df88e63`](autobrr/qui@df88e63): test(web): add coverage for hooks and lib/api.ts contract layer ([#​1931](autobrr/qui#1931)) ([#​1998](autobrr/qui#1998)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.20.0...v1.21.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.21.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Warsaw) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDMuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbInJlbm92YXRlL2NvbnRhaW5lciIsInR5cGUvbWlub3IiXX0=--> Reviewed-on: https://git.ajgon.casa/deedee/mark13/pulls/160
… (#200) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.20.0` → `v1.22.0` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.22.0`](https://github.com/autobrr/qui/releases/tag/v1.22.0) [Compare Source](autobrr/qui@v1.21.0...v1.22.0) ##### Changelog ##### New Features - [`5b9326e`](autobrr/qui@5b9326e): feat(web): add copy path option to content tab context menu ([#​933](autobrr/qui#933)) ([@​lenisko](https://github.com/lenisko)) - [`3a4383d`](autobrr/qui@3a4383d): feat(web): add kanagawa themes ([#​2069](autobrr/qui#2069)) ([@​notmarek](https://github.com/notmarek)) - [`db77a17`](autobrr/qui@db77a17): feat(web): shift+click range select in torrent content tab ([#​2081](autobrr/qui#2081)) ([@​jussaw](https://github.com/jussaw)) ##### Bug Fixes - [`b563d4b`](autobrr/qui@b563d4b): fix(sync): keep SSE aggregates fresh without tracker hydration ([#​2072](autobrr/qui#2072)) ([@​Audionut](https://github.com/Audionut)) ##### Other Changes - [`d428313`](autobrr/qui@d428313): chore(deps): bump the github group with 5 updates ([#​2073](autobrr/qui#2073)) ([@​dependabot](https://github.com/dependabot)\[bot]) **Full Changelog**: <autobrr/qui@v1.21.0...v1.22.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.22.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! ### [`v1.21.0`](https://github.com/autobrr/qui/releases/tag/v1.21.0) [Compare Source](autobrr/qui@v1.20.0...v1.21.0) ##### Changelog ##### New Features - [`51b29a1`](autobrr/qui@51b29a1): feat(api): add GET /api/version reporting the running version ([#​2060](autobrr/qui#2060)) ([@​s0up4200](https://github.com/s0up4200)) - [`98348f1`](autobrr/qui@98348f1): feat(automations): add RLS\_YEAR release-year condition ([#​2003](autobrr/qui#2003)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`58cc7cb`](autobrr/qui@58cc7cb): feat(automations): add tracker status and message conditions ([#​2006](autobrr/qui#2006)) ([@​luckylittle](https://github.com/luckylittle)) - [`2176a92`](autobrr/qui@2176a92): feat(automations): list configured indexer trackers in workflow selector ([#​2005](autobrr/qui#2005)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2de926e`](autobrr/qui@2de926e): feat(i18n): add Italian (it) translation ([#​2011](autobrr/qui#2011)) ([@​s0up4200](https://github.com/s0up4200)) - [`7b66655`](autobrr/qui@7b66655): feat(i18n): add Korean (ko) translation ([#​2042](autobrr/qui#2042)) ([@​jussaw](https://github.com/jussaw)) - [`2c06a3d`](autobrr/qui@2c06a3d): feat(i18n): add Ukrainian (uk) translation ([#​2032](autobrr/qui#2032)) ([@​rodion981](https://github.com/rodion981)) - [`ef8da0b`](autobrr/qui@ef8da0b): feat(sse): keep cached torrents with a staleness badge when qBittorrent is slow ([#​2057](autobrr/qui#2057)) ([@​s0up4200](https://github.com/s0up4200)) - [`38ebb6e`](autobrr/qui@38ebb6e): feat(web): add file priority controls to torrent file views ([#​2062](autobrr/qui#2062)) ([@​jussaw](https://github.com/jussaw)) ##### Bug Fixes - [`12ac536`](autobrr/qui@12ac536): fix(ci): skip release binary build on main pushes ([#​2008](autobrr/qui#2008)) ([@​s0up4200](https://github.com/s0up4200)) - [`57210a3`](autobrr/qui@57210a3): fix(crossseed): drop resolution token from TV search queries ([#​2017](autobrr/qui#2017)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`791bc7d`](autobrr/qui@791bc7d): fix(crossseed): inject rootless single-file adds into matched folder ([#​2029](autobrr/qui#2029)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3da437f`](autobrr/qui@3da437f): fix(crossseed): re-query indexers with no usable hit in the alternate connector pass ([#​2002](autobrr/qui#2002)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8e8b6d9`](autobrr/qui@8e8b6d9): fix(database): prevent int4 sequence exhaustion in Postgres string interning ([#​2015](autobrr/qui#2015)) ([@​s0up4200](https://github.com/s0up4200)) - [`c7e08b9`](autobrr/qui@c7e08b9): fix(db): index string\_pool FK columns so GC stops timing out ([#​2051](autobrr/qui#2051)) ([@​s0up4200](https://github.com/s0up4200)) - [`2553f75`](autobrr/qui@2553f75): fix(docs): use relative file paths for markdown links ([#​2013](autobrr/qui#2013)) ([@​nasenov](https://github.com/nasenov)) - [`8a9c2fc`](autobrr/qui@8a9c2fc): fix(i18n): improve French translation, fix hardcoded UI statuses, and update agent rules ([#​2014](autobrr/qui#2014)) ([@​OlziYT](https://github.com/OlziYT)) - [`190d936`](autobrr/qui@190d936): fix(jackett): decouple cache persistence from SkipHistory ([#​2056](autobrr/qui#2056)) ([@​jussaw](https://github.com/jussaw)) - [`91ad7e4`](autobrr/qui@91ad7e4): fix(pprof): bind to configurable loopback address instead of :6060 ([#​2044](autobrr/qui#2044)) ([@​s0up4200](https://github.com/s0up4200)) - [`10e2d02`](autobrr/qui@10e2d02): fix(qbittorrent): serve stale app info/preferences when refresh fails ([#​2054](autobrr/qui#2054)) ([@​s0up4200](https://github.com/s0up4200)) - [`6d3b04c`](autobrr/qui@6d3b04c): fix(sse): resolve sync-error staleness stamp off the sync-loop callback ([#​2065](autobrr/qui#2065)) ([@​s0up4200](https://github.com/s0up4200)) - [`677e8f4`](autobrr/qui@677e8f4): fix(sse): send torrent page deltas instead of full snapshots ([#​2047](autobrr/qui#2047)) ([@​s0up4200](https://github.com/s0up4200)) - [`a4d252b`](autobrr/qui@a4d252b): fix(sse): size full-sync timeout to prevent permanent disconnect on large instances ([#​2037](autobrr/qui#2037)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4f375c6`](autobrr/qui@4f375c6): fix(torrents): route unified view exports to the owning instance ([#​2018](autobrr/qui#2018)) ([@​s0up4200](https://github.com/s0up4200)) - [`d49e168`](autobrr/qui@d49e168): fix(web): move instance status bar back to bottom of screen ([#​2024](autobrr/qui#2024)) ([@​OlziYT](https://github.com/OlziYT)) - [`a69ae69`](autobrr/qui@a69ae69): fix(web): prevent framer-motion tab indicator from triggering layout shifts ([#​2025](autobrr/qui#2025)) ([@​OlziYT](https://github.com/OlziYT)) ##### Other Changes - [`860b5bb`](autobrr/qui@860b5bb): chore(deps): bump anthropics/claude-code-action from 1.0.140 to 1.0.148 in the github group ([#​2045](autobrr/qui#2045)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`0b23a7d`](autobrr/qui@0b23a7d): chore(deps): bump the github group with 2 updates ([#​2031](autobrr/qui#2031)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`1fea36b`](autobrr/qui@1fea36b): docs: document frontend testing conventions ([#​1984](autobrr/qui#1984)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f3443ec`](autobrr/qui@f3443ec): docs: promote multi-language support ([#​2009](autobrr/qui#2009)) ([@​s0up4200](https://github.com/s0up4200)) - [`df88e63`](autobrr/qui@df88e63): test(web): add coverage for hooks and lib/api.ts contract layer ([#​1931](autobrr/qui#1931)) ([#​1998](autobrr/qui#1998)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.20.0...v1.21.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.21.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/London) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/200
…#160) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.20.0` → `v1.21.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/7) for more information. --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.21.0`](https://github.com/autobrr/qui/releases/tag/v1.21.0) [Compare Source](autobrr/qui@v1.20.0...v1.21.0) ##### Changelog ##### New Features - [`51b29a1`](autobrr/qui@51b29a1): feat(api): add GET /api/version reporting the running version ([#​2060](autobrr/qui#2060)) ([@​s0up4200](https://github.com/s0up4200)) - [`98348f1`](autobrr/qui@98348f1): feat(automations): add RLS\_YEAR release-year condition ([#​2003](autobrr/qui#2003)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`58cc7cb`](autobrr/qui@58cc7cb): feat(automations): add tracker status and message conditions ([#​2006](autobrr/qui#2006)) ([@​luckylittle](https://github.com/luckylittle)) - [`2176a92`](autobrr/qui@2176a92): feat(automations): list configured indexer trackers in workflow selector ([#​2005](autobrr/qui#2005)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2de926e`](autobrr/qui@2de926e): feat(i18n): add Italian (it) translation ([#​2011](autobrr/qui#2011)) ([@​s0up4200](https://github.com/s0up4200)) - [`7b66655`](autobrr/qui@7b66655): feat(i18n): add Korean (ko) translation ([#​2042](autobrr/qui#2042)) ([@​jussaw](https://github.com/jussaw)) - [`2c06a3d`](autobrr/qui@2c06a3d): feat(i18n): add Ukrainian (uk) translation ([#​2032](autobrr/qui#2032)) ([@​rodion981](https://github.com/rodion981)) - [`ef8da0b`](autobrr/qui@ef8da0b): feat(sse): keep cached torrents with a staleness badge when qBittorrent is slow ([#​2057](autobrr/qui#2057)) ([@​s0up4200](https://github.com/s0up4200)) - [`38ebb6e`](autobrr/qui@38ebb6e): feat(web): add file priority controls to torrent file views ([#​2062](autobrr/qui#2062)) ([@​jussaw](https://github.com/jussaw)) ##### Bug Fixes - [`12ac536`](autobrr/qui@12ac536): fix(ci): skip release binary build on main pushes ([#​2008](autobrr/qui#2008)) ([@​s0up4200](https://github.com/s0up4200)) - [`57210a3`](autobrr/qui@57210a3): fix(crossseed): drop resolution token from TV search queries ([#​2017](autobrr/qui#2017)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`791bc7d`](autobrr/qui@791bc7d): fix(crossseed): inject rootless single-file adds into matched folder ([#​2029](autobrr/qui#2029)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3da437f`](autobrr/qui@3da437f): fix(crossseed): re-query indexers with no usable hit in the alternate connector pass ([#​2002](autobrr/qui#2002)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8e8b6d9`](autobrr/qui@8e8b6d9): fix(database): prevent int4 sequence exhaustion in Postgres string interning ([#​2015](autobrr/qui#2015)) ([@​s0up4200](https://github.com/s0up4200)) - [`c7e08b9`](autobrr/qui@c7e08b9): fix(db): index string\_pool FK columns so GC stops timing out ([#​2051](autobrr/qui#2051)) ([@​s0up4200](https://github.com/s0up4200)) - [`2553f75`](autobrr/qui@2553f75): fix(docs): use relative file paths for markdown links ([#​2013](autobrr/qui#2013)) ([@​nasenov](https://github.com/nasenov)) - [`8a9c2fc`](autobrr/qui@8a9c2fc): fix(i18n): improve French translation, fix hardcoded UI statuses, and update agent rules ([#​2014](autobrr/qui#2014)) ([@​OlziYT](https://github.com/OlziYT)) - [`190d936`](autobrr/qui@190d936): fix(jackett): decouple cache persistence from SkipHistory ([#​2056](autobrr/qui#2056)) ([@​jussaw](https://github.com/jussaw)) - [`91ad7e4`](autobrr/qui@91ad7e4): fix(pprof): bind to configurable loopback address instead of :6060 ([#​2044](autobrr/qui#2044)) ([@​s0up4200](https://github.com/s0up4200)) - [`10e2d02`](autobrr/qui@10e2d02): fix(qbittorrent): serve stale app info/preferences when refresh fails ([#​2054](autobrr/qui#2054)) ([@​s0up4200](https://github.com/s0up4200)) - [`6d3b04c`](autobrr/qui@6d3b04c): fix(sse): resolve sync-error staleness stamp off the sync-loop callback ([#​2065](autobrr/qui#2065)) ([@​s0up4200](https://github.com/s0up4200)) - [`677e8f4`](autobrr/qui@677e8f4): fix(sse): send torrent page deltas instead of full snapshots ([#​2047](autobrr/qui#2047)) ([@​s0up4200](https://github.com/s0up4200)) - [`a4d252b`](autobrr/qui@a4d252b): fix(sse): size full-sync timeout to prevent permanent disconnect on large instances ([#​2037](autobrr/qui#2037)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4f375c6`](autobrr/qui@4f375c6): fix(torrents): route unified view exports to the owning instance ([#​2018](autobrr/qui#2018)) ([@​s0up4200](https://github.com/s0up4200)) - [`d49e168`](autobrr/qui@d49e168): fix(web): move instance status bar back to bottom of screen ([#​2024](autobrr/qui#2024)) ([@​OlziYT](https://github.com/OlziYT)) - [`a69ae69`](autobrr/qui@a69ae69): fix(web): prevent framer-motion tab indicator from triggering layout shifts ([#​2025](autobrr/qui#2025)) ([@​OlziYT](https://github.com/OlziYT)) ##### Other Changes - [`860b5bb`](autobrr/qui@860b5bb): chore(deps): bump anthropics/claude-code-action from 1.0.140 to 1.0.148 in the github group ([#​2045](autobrr/qui#2045)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`0b23a7d`](autobrr/qui@0b23a7d): chore(deps): bump the github group with 2 updates ([#​2031](autobrr/qui#2031)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`1fea36b`](autobrr/qui@1fea36b): docs: document frontend testing conventions ([#​1984](autobrr/qui#1984)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f3443ec`](autobrr/qui@f3443ec): docs: promote multi-language support ([#​2009](autobrr/qui#2009)) ([@​s0up4200](https://github.com/s0up4200)) - [`df88e63`](autobrr/qui@df88e63): test(web): add coverage for hooks and lib/api.ts contract layer ([#​1931](autobrr/qui#1931)) ([#​1998](autobrr/qui#1998)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.20.0...v1.21.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.21.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Warsaw) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDMuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbInJlbm92YXRlL2NvbnRhaW5lciIsInR5cGUvbWlub3IiXX0=--> Reviewed-on: https://git.ajgon.casa/deedee/mark13/pulls/160
…#160) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.20.0` → `v1.21.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/7) for more information. --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.21.0`](https://github.com/autobrr/qui/releases/tag/v1.21.0) [Compare Source](autobrr/qui@v1.20.0...v1.21.0) ##### Changelog ##### New Features - [`51b29a1`](autobrr/qui@51b29a1): feat(api): add GET /api/version reporting the running version ([#​2060](autobrr/qui#2060)) ([@​s0up4200](https://github.com/s0up4200)) - [`98348f1`](autobrr/qui@98348f1): feat(automations): add RLS\_YEAR release-year condition ([#​2003](autobrr/qui#2003)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`58cc7cb`](autobrr/qui@58cc7cb): feat(automations): add tracker status and message conditions ([#​2006](autobrr/qui#2006)) ([@​luckylittle](https://github.com/luckylittle)) - [`2176a92`](autobrr/qui@2176a92): feat(automations): list configured indexer trackers in workflow selector ([#​2005](autobrr/qui#2005)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2de926e`](autobrr/qui@2de926e): feat(i18n): add Italian (it) translation ([#​2011](autobrr/qui#2011)) ([@​s0up4200](https://github.com/s0up4200)) - [`7b66655`](autobrr/qui@7b66655): feat(i18n): add Korean (ko) translation ([#​2042](autobrr/qui#2042)) ([@​jussaw](https://github.com/jussaw)) - [`2c06a3d`](autobrr/qui@2c06a3d): feat(i18n): add Ukrainian (uk) translation ([#​2032](autobrr/qui#2032)) ([@​rodion981](https://github.com/rodion981)) - [`ef8da0b`](autobrr/qui@ef8da0b): feat(sse): keep cached torrents with a staleness badge when qBittorrent is slow ([#​2057](autobrr/qui#2057)) ([@​s0up4200](https://github.com/s0up4200)) - [`38ebb6e`](autobrr/qui@38ebb6e): feat(web): add file priority controls to torrent file views ([#​2062](autobrr/qui#2062)) ([@​jussaw](https://github.com/jussaw)) ##### Bug Fixes - [`12ac536`](autobrr/qui@12ac536): fix(ci): skip release binary build on main pushes ([#​2008](autobrr/qui#2008)) ([@​s0up4200](https://github.com/s0up4200)) - [`57210a3`](autobrr/qui@57210a3): fix(crossseed): drop resolution token from TV search queries ([#​2017](autobrr/qui#2017)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`791bc7d`](autobrr/qui@791bc7d): fix(crossseed): inject rootless single-file adds into matched folder ([#​2029](autobrr/qui#2029)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3da437f`](autobrr/qui@3da437f): fix(crossseed): re-query indexers with no usable hit in the alternate connector pass ([#​2002](autobrr/qui#2002)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8e8b6d9`](autobrr/qui@8e8b6d9): fix(database): prevent int4 sequence exhaustion in Postgres string interning ([#​2015](autobrr/qui#2015)) ([@​s0up4200](https://github.com/s0up4200)) - [`c7e08b9`](autobrr/qui@c7e08b9): fix(db): index string\_pool FK columns so GC stops timing out ([#​2051](autobrr/qui#2051)) ([@​s0up4200](https://github.com/s0up4200)) - [`2553f75`](autobrr/qui@2553f75): fix(docs): use relative file paths for markdown links ([#​2013](autobrr/qui#2013)) ([@​nasenov](https://github.com/nasenov)) - [`8a9c2fc`](autobrr/qui@8a9c2fc): fix(i18n): improve French translation, fix hardcoded UI statuses, and update agent rules ([#​2014](autobrr/qui#2014)) ([@​OlziYT](https://github.com/OlziYT)) - [`190d936`](autobrr/qui@190d936): fix(jackett): decouple cache persistence from SkipHistory ([#​2056](autobrr/qui#2056)) ([@​jussaw](https://github.com/jussaw)) - [`91ad7e4`](autobrr/qui@91ad7e4): fix(pprof): bind to configurable loopback address instead of :6060 ([#​2044](autobrr/qui#2044)) ([@​s0up4200](https://github.com/s0up4200)) - [`10e2d02`](autobrr/qui@10e2d02): fix(qbittorrent): serve stale app info/preferences when refresh fails ([#​2054](autobrr/qui#2054)) ([@​s0up4200](https://github.com/s0up4200)) - [`6d3b04c`](autobrr/qui@6d3b04c): fix(sse): resolve sync-error staleness stamp off the sync-loop callback ([#​2065](autobrr/qui#2065)) ([@​s0up4200](https://github.com/s0up4200)) - [`677e8f4`](autobrr/qui@677e8f4): fix(sse): send torrent page deltas instead of full snapshots ([#​2047](autobrr/qui#2047)) ([@​s0up4200](https://github.com/s0up4200)) - [`a4d252b`](autobrr/qui@a4d252b): fix(sse): size full-sync timeout to prevent permanent disconnect on large instances ([#​2037](autobrr/qui#2037)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4f375c6`](autobrr/qui@4f375c6): fix(torrents): route unified view exports to the owning instance ([#​2018](autobrr/qui#2018)) ([@​s0up4200](https://github.com/s0up4200)) - [`d49e168`](autobrr/qui@d49e168): fix(web): move instance status bar back to bottom of screen ([#​2024](autobrr/qui#2024)) ([@​OlziYT](https://github.com/OlziYT)) - [`a69ae69`](autobrr/qui@a69ae69): fix(web): prevent framer-motion tab indicator from triggering layout shifts ([#​2025](autobrr/qui#2025)) ([@​OlziYT](https://github.com/OlziYT)) ##### Other Changes - [`860b5bb`](autobrr/qui@860b5bb): chore(deps): bump anthropics/claude-code-action from 1.0.140 to 1.0.148 in the github group ([#​2045](autobrr/qui#2045)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`0b23a7d`](autobrr/qui@0b23a7d): chore(deps): bump the github group with 2 updates ([#​2031](autobrr/qui#2031)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`1fea36b`](autobrr/qui@1fea36b): docs: document frontend testing conventions ([#​1984](autobrr/qui#1984)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f3443ec`](autobrr/qui@f3443ec): docs: promote multi-language support ([#​2009](autobrr/qui#2009)) ([@​s0up4200](https://github.com/s0up4200)) - [`df88e63`](autobrr/qui@df88e63): test(web): add coverage for hooks and lib/api.ts contract layer ([#​1931](autobrr/qui#1931)) ([#​1998](autobrr/qui#1998)) ([@​nitrobass24](https://github.com/nitrobass24)) **Full Changelog**: <autobrr/qui@v1.20.0...v1.21.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.21.0` - `docker pull ghcr.io/autobrr/qui:latest` ##### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Warsaw) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDMuMSIsInVwZGF0ZWRJblZlciI6IjQzLjI0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbInJlbm92YXRlL2NvbnRhaW5lciIsInR5cGUvbWlub3IiXX0=--> Reviewed-on: https://git.ajgon.casa/deedee/mark13/pulls/160
Fixes #2046. Since v1.20 the torrent-list SSE re-sent the full page on every tick: measured ~577 KB every ~2s (~290 KB/s sustained per open tab) on a 4.8k-torrent instance. That is wasted CPU and bandwidth for every user, and on a slow-drained connection (a constrained link, or a backgrounded tab that stops draining) it fills the HTTP/2 connection-level flow-control window and head-of-line-blocks other requests like
auth/me. This keeps a per-group baseline and sendsdeltaframes of only the rows whose meaningful fields changed, with per-second counters (reannounce,time_active, peer counts) excluded from change-detection so an otherwise-idle row no longer flags every tick. Measured steady delta is ~22 KB vs ~577 KB, roughly 26x less; aggregate metadata still travels whole so existing stream consumers stay correct unchanged.initseeds the baseline so the first delta applies cleanly, with no periodic keyframe (a recurring full re-send would re-jam the connection on a bad link); correctness rests on init-seeds-baseline plus a fresh init on reconnect. The frontend rebuilds the page from deltas with stable row references, and the stream watchdog gets a grace window before the first event so a large init is not killed mid-download into a reconnect storm. Hard cutover, no capability flag, since qui ships frontend and backend as one binary.