fix(channels): address R14 deep-review findings
- Make the immediate-backfill guard SYMMETRIC: run_deep_backfill now also registers the channel it pages in _backfill_in_progress, so it and backfill_channel_now mutually exclude per-channel and never advance the same cursor concurrently (whichever started first). The interactive cap moves to a separate _immediate_running counter so the scheduler's channels don't false-trip it. - Attribute the immediate backfill's spend: wrap it in quota.attribute(user_id, VIDEOS_BACKFILL_FULL) (route passes user.id), so it shows under the user's full-backfill action, not the OTHER/system bucket. - Tests: the update_channel->background-task wiring + the demo guard (scheduled for a real user, NOT for demo); distinct N/S/L counts (2 short + 1 live) so a short/live swap fails; extracted channelDetailPollMs with a pure unit test of the poll-or-stop predicate. - a11y: role="img" on the fully-synced charm so screen readers announce it.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useLiveQuery } from "../lib/useLiveQuery";
|
||||
import { channelDetailPollMs } from "../lib/channelDetailPoll";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
import { useNavigationActions } from "./NavigationProvider";
|
||||
import { PriorityStepper } from "./PriorityStepper";
|
||||
@@ -85,7 +86,7 @@ export default function ChannelPage({
|
||||
// scheduler tops up any tail on its ~10-minute ticks — then stop polling once the back-catalogue is
|
||||
// in. Same queryKey as before, so mutation invalidations still hit.
|
||||
const detail = useLiveQuery(qk.channel(channelId), () => api.channelDetail(channelId), {
|
||||
intervalMs: (d) => (d?.deep_requested && !d.backfill_done ? 4000 : false),
|
||||
intervalMs: channelDetailPollMs,
|
||||
});
|
||||
const ch = detail.data;
|
||||
useEffect(() => setBannerAttempt(0), [ch?.banner_url]); // fresh channel/banner → try again
|
||||
|
||||
@@ -52,6 +52,7 @@ function StoredCell({ c }: { c: ManagedChannel }) {
|
||||
{isFullySynced(c) && (
|
||||
<Tooltip hint={t("channels.row.fullySyncedHint")}>
|
||||
<Check
|
||||
role="img"
|
||||
className="w-3.5 h-3.5 text-accent shrink-0 cursor-help"
|
||||
aria-label={t("channels.row.fullySynced")}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { channelDetailPollMs } from "./channelDetailPoll";
|
||||
import { type ChannelDetail } from "./api";
|
||||
|
||||
// Only deep_requested + backfill_done drive the decision; cast a partial for the pure predicate.
|
||||
const d = (over: Partial<ChannelDetail>): ChannelDetail => over as ChannelDetail;
|
||||
|
||||
describe("channelDetailPollMs", () => {
|
||||
it("polls at 4s while THIS user's full-history backfill is running", () => {
|
||||
expect(channelDetailPollMs(d({ deep_requested: true, backfill_done: false }))).toBe(4000);
|
||||
});
|
||||
|
||||
it("stops (false) once the back-catalogue is fully in", () => {
|
||||
expect(channelDetailPollMs(d({ deep_requested: true, backfill_done: true }))).toBe(false);
|
||||
});
|
||||
|
||||
it("stops (false) when the user hasn't requested full history", () => {
|
||||
expect(channelDetailPollMs(d({ deep_requested: false, backfill_done: false }))).toBe(false);
|
||||
});
|
||||
|
||||
it("stops (false) before the detail has loaded", () => {
|
||||
expect(channelDetailPollMs(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { type ChannelDetail } from "./api";
|
||||
|
||||
/** How often the channel page re-polls its detail (via useLiveQuery): every 4s while THIS user's
|
||||
* full-history backfill is running (`deep_requested && !backfill_done`) so the stored count + the
|
||||
* N/S/L breakdown climb on their own, and `false` (stop polling) once the back-catalogue is in or
|
||||
* the user never requested it. Extracted so the poll-or-stop decision is unit-testable. */
|
||||
export const channelDetailPollMs = (d: ChannelDetail | undefined): number | false =>
|
||||
d?.deep_requested && !d.backfill_done ? 4000 : false;
|
||||
Reference in New Issue
Block a user