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:
2026-08-15 07:11:50 +02:00
parent 2cc77757d9
commit 5720356191
7 changed files with 159 additions and 59 deletions
+2 -1
View File
@@ -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")}
/>