refactor(frontend): satisfy noUncheckedIndexedAccess (S3b)
Enable noUncheckedIndexedAccess and fix all 98 call sites it surfaced across 18 files. Every fix is behaviour-preserving — the flag flagged reads TypeScript couldn't prove in-bounds, all of which were already guarded by a length/index check, a findIndex result, or a non-empty invariant: - read-once so a ternary's narrowing sticks (Feed overrides, linkify mention); - non-null assertion AFTER an existing guard (PlayerModal/VideoEditor queue+cut-list indexing, modules step, useUndoable stacks) with a comment stating the invariant; - nullish fallback where undefined is a real possibility (GlassTuner slider ?? def, ConfigPanel active group ?? [], descriptionLinks id ?? null); - optional chaining where the entry genuinely can be absent (PlexBrowse IO entry); - a non-empty tuple type for the RELATIVE_UNITS constant (encodes "always has [0]"). The single highest-leverage fix: PlayerModal's `active` became `queue?.[index] ?? video` (always Video), clearing 34 of the 45 errors in that file at once. Verified: tsc/eslint/prettier clean, 56 vitest + 34 pytest + 17/17 e2e green (the e2e suite exercises the feed/player/channel components touched here).
This commit is contained in:
@@ -101,7 +101,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
// the true length (e.g. 213.44 vs 213). Without this the untouched last segment stays short, so
|
||||
// the no-op guard (isFullSingle: end >= duration) reads false and enables a "Create" that files a
|
||||
// clip of an unmodified video and drops its tail. Only touches the untouched segment (end===srcDur).
|
||||
setSegments((s) => (s.length === 1 && s[0].end === srcDur ? [{ ...s[0], end: d }] : s));
|
||||
setSegments((s) => (s.length === 1 && s[0]!.end === srcDur ? [{ ...s[0]!, end: d }] : s));
|
||||
};
|
||||
const onTimeUpdate = () => {
|
||||
const v = videoRef.current;
|
||||
@@ -124,7 +124,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
setSegments((segs) => {
|
||||
const i = segs.findIndex((s) => time > s.start + MIN_SEG && time < s.end - MIN_SEG);
|
||||
if (i < 0) return segs;
|
||||
const s = segs[i];
|
||||
const s = segs[i]!; // i >= 0 from findIndex, so segs[i] exists
|
||||
const b: Seg = { id: nid(), start: time, end: s.end, keep: s.keep };
|
||||
const next = [...segs.slice(0, i), { ...s, end: time }, b, ...segs.slice(i + 1)];
|
||||
return next;
|
||||
@@ -133,18 +133,19 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
const moveBoundary = (bi: number, time: number) =>
|
||||
setSegments((segs) => {
|
||||
if (bi < 0 || bi >= segs.length - 1) return segs;
|
||||
const lo = segs[bi].start + MIN_SEG;
|
||||
const hi = segs[bi + 1].end - MIN_SEG;
|
||||
// bi is in [0, length-2] here, so bi and bi+1 both index existing segments.
|
||||
const lo = segs[bi]!.start + MIN_SEG;
|
||||
const hi = segs[bi + 1]!.end - MIN_SEG;
|
||||
const tt = Math.max(lo, Math.min(hi, time));
|
||||
const copy = [...segs];
|
||||
copy[bi] = { ...copy[bi], end: tt };
|
||||
copy[bi + 1] = { ...copy[bi + 1], start: tt };
|
||||
copy[bi] = { ...copy[bi]!, end: tt };
|
||||
copy[bi + 1] = { ...copy[bi + 1]!, start: tt };
|
||||
return copy;
|
||||
});
|
||||
const deleteBoundary = (bi: number) =>
|
||||
setSegments((segs) => {
|
||||
if (segs.length < 2) return segs;
|
||||
const merged = { ...segs[bi], end: segs[bi + 1].end };
|
||||
const merged = { ...segs[bi]!, end: segs[bi + 1]!.end };
|
||||
return [...segs.slice(0, bi), merged, ...segs.slice(bi + 2)];
|
||||
});
|
||||
const toggleKeep = (id: number) =>
|
||||
@@ -257,8 +258,8 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
const isFullSingle =
|
||||
segments.length === 1 &&
|
||||
kept.length === 1 &&
|
||||
kept[0].start <= 0.01 &&
|
||||
kept[0].end >= duration - 0.01;
|
||||
kept[0]!.start <= 0.01 &&
|
||||
kept[0]!.end >= duration - 0.01;
|
||||
const valid = kept.length >= 1 && keptDur >= MIN_SEG && (cropOn || !isFullSingle);
|
||||
const willJoin = output === "join" && kept.length > 1;
|
||||
const reencode = cropOn || accurate;
|
||||
@@ -296,7 +297,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||
}
|
||||
const N = kept.length;
|
||||
for (let i = 0; i < N; i++) {
|
||||
const s = kept[i];
|
||||
const s = kept[i]!; // i < N === kept.length, so kept[i] exists
|
||||
const spec: EditSpec = { trim: { start_s: +s.start.toFixed(3), end_s: +s.end.toFixed(3) } };
|
||||
if (cp) spec.crop = cp;
|
||||
else spec.accurate = accurate;
|
||||
|
||||
Reference in New Issue
Block a user