commit 203456bbdf419d85b6808015c8394576082c6dfc
parent 547493411ac3be12af6ff6c0776279ce72141dad
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 2 Oct 2026 01:15:58 -0400
editor: Clear hold's sentence outlives the strip it empties (review H2)
A clear that leaves nothing to list unmounts the Rate-limit cooldown
region, which held the sentence; it now lives on the lane view.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/editor/app/operations/components/RunnerOperationView.tsx b/editor/app/operations/components/RunnerOperationView.tsx
@@ -68,6 +68,9 @@ export function RunnerOperationView({
onRefresh: () => Promise<void>;
}) {
const [busy, setBusy] = useState(false);
+ // What the last Clear hold did. Held HERE, not in the strip: a clear that
+ // leaves nothing to list unmounts the strip, and the sentence must outlive it.
+ const [holdNote, setHoldNote] = useState<string | null>(null);
const now = useNow();
const control = useCallback(
@@ -129,9 +132,15 @@ export function RunnerOperationView({
subtitleDeferred={status.subtitleDeferred ?? []}
laneRunning={status.runner.running}
onRefresh={onRefresh}
+ onCleared={setHoldNote}
now={now}
/>
)}
+ {holdNote && (
+ <span aria-label="clear hold result" className="text-xs text-muted-foreground">
+ {holdNote}
+ </span>
+ )}
<SnoozeControl
kind={kind}
@@ -228,6 +237,7 @@ function CooldownStrip({
subtitleDeferred,
laneRunning,
onRefresh,
+ onCleared,
now,
}: {
cooldowns: PlatformCooldownView[];
@@ -236,15 +246,15 @@ function CooldownStrip({
subtitleDeferred: SubtitleDeferralView[];
laneRunning: boolean;
onRefresh: () => Promise<void>;
+ onCleared: (message: string) => void;
now: number | null;
}) {
const [clearing, setClearing] = useState<string | null>(null);
- const [cleared, setCleared] = useState<string | null>(null);
const clearHold = async (platform: string) => {
setClearing(platform);
try {
const res = await clearPlatformHoldAction(platform);
- setCleared(res.ok ? res.message : res.error);
+ onCleared(res.ok ? res.message : res.error);
await onRefresh();
} finally {
setClearing(null);
@@ -318,11 +328,6 @@ function CooldownStrip({
</ul>
</>
)}
- {cleared && (
- <span aria-label="clear hold result" className="text-xs text-muted-foreground">
- {cleared}
- </span>
- )}
{cooling.length > 0 && (
<>
<span className="font-medium text-warning">
diff --git a/plans/FACTS.md b/plans/FACTS.md
@@ -6410,7 +6410,11 @@ Line numbers are `plans/FACTS.md` lines at `e172749b`, before this record's in-p
returns it when a subtitle-429 line is present and every `ERROR:` line is a subtitle line (no
soft block, no bot check) — `isSubtitleRateLimitOnly` / `hasSubtitleRateLimit`. It is set on a
**SUCCESS** record (`status: ok…`, `failureClass: "subs_rate_limit"`), the one class that is.
-- **yt-dlp's `--ignore-errors` turns a subtitle failure into a WARNING and carries on**
+- **yt-dlp's `--ignore-errors` turns ANY subtitle failure into a WARNING and carries on** — a 429, a
+ 403/404/5xx, a failed `live_chat` replay, an OSError writing the file (review H1, verified offline for
+ a 403) — so the downloader parses the WARNING's class: only a rate limit takes the media path
+ (`hasNonRateLimitSubtitleFailure` keeps any other a failed attempt, not archived, classified from the
+ tail)
(`YoutubeDL._write_subtitles`: `ignoreerrors is True` → `report_warning`; `'only_download'`, i.e.
`--no-abort-on-error`, still raises). Without it, a `--load-info-json` run that fails re-extracts
from the URL once (`download_with_info_file` "The info failed to download … trying with URL") —
@@ -6437,9 +6441,14 @@ Line numbers are `plans/FACTS.md` lines at `e172749b`, before this record's in-p
`detectPlatform(url) ?? "unknown"`, the cooldown's key.
- **The hold**: `FAILS_TO_REACH_CAP` = 6 (60 s doubling reaches the 30-min cap at the sixth);
`failsAtCap(fails) = max(0, fails − 5)`. At `pacing.holdAfterFailsAtCap` the backoff's `until`
- becomes `now + holdProbeMinutes` (no jitter) and `platformHolds[pf].probeAt` mirrors it; a held
- platform's backoff entry is never pruned. Only a `transcribed` unit with no subtitle 429 clears
- it. A manual Sync / download / metadata scan / fetch-window on a held platform is refused with
+ becomes `now + holdProbeMinutes` (no jitter) and `platformHolds[pf].probeAt` mirrors it
+ (`rateLimited` false for a hold reached through network failures alone); a held platform's backoff
+ entry is never pruned. It is cleared by a clean lane unit (a `subs_rate_limit` unit included), by a
+ clean manual run once the probe is due (`runYtdlp`'s `onPlatformClean` → `recordPlatformClean`, and a
+ clean metadata scan), or by **Clear hold** (`clearPlatformHold`, a `clear-platform-hold` job on queue
+ `pacing:<pf>`). Manual actions are refused only while held AND before the probe time.
+- **The one time-based rule:** a raised pace eases one step per `PACE_TIME_DECAY_MS` (1 h) since its
+ `steppedAt` with no rate limit (`effectivePaceSeconds` for readers, `decayPaceByTime` on writes). A manual Sync / download / metadata scan / fetch-window on a held platform is refused with
`heldPlatformSentence` (`<pf> is held: … the next probe is in N min. <What> will run once a probe
comes back clean.`).
- **The download lane now waits between units**: `platformNextStartAt` (in memory) =