feat(my-videos): add pure video-health classifier (Phase 1, step 1)

First piece of the "My Videos" management tab: classifyVideoHealth decides the
maintenance status for one of the user's own videos from note-side data plus an
optional URL probe result. Pure/synchronous so it's deterministic and testable;
the controller will do the actual HEAD probe and pass the result in.

Statuses: no-source (no url AND no magnet) -> error; hosted-URL-unreachable ->
warning; deleted (tombstoned) -> info; otherwise ok. Key guardrail: only URLs
under the user's storage publicBaseUrl can be flagged dead — external/CORS-opaque
links are never cried wolf on. Deleted is checked before no-source since
soft-delete scrubs url/magnet.

Tests + mutation-verified: dropping the hosted guard (external false-alarm) and
removing deleted-precedence each fail a scenario.

Build + full lint green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-20 14:24:51 -04:00
1 parent 73a06069ad
commit 8cb03d23cc
2 files changed
+145

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
// Pure health classification for the "My Videos" management tab.
//
// Given a parsed video note (and optionally a URL probe result), decide the
// maintenance status shown to the user. Deliberately no network/IO here so it
// stays deterministic and testable; the controller performs the actual HEAD
// probe and passes the result in. Phase 2 (bucket reconciliation) layers
// storage-side statuses on top of this note-side classification.
export const VIDEO_HEALTH_STATUS = Object.freeze({
DELETED: "deleted",
NO_SOURCE: "no-source",
DEAD_URL: "dead-url",
OK: "ok",
});
function trimStr(value) {
return typeof value === "string" ? value.trim() : "";
}
/**
* Does `url` live under the user's storage public base URL? Only hosted URLs are
* ones we can meaningfully probe / reconcile against the bucket; third-party
* links are out of our control (and CORS-opaque), so they must never be flagged
* red just because we can't verify them.
*/
export function isUrlUnderBase(url, publicBaseUrl) {
const u = trimStr(url);
const base = trimStr(publicBaseUrl).replace(/\/+$/, "");
if (!u || !base) {
return false;
}
return u === base || u.startsWith(`${base}/`);
}
/**
* Classify the maintenance health of one of the user's own videos.
*
* @param {object} video parsed video note ({ url, magnet, deleted, ... })
* @param {object} [opts]
* @param {string} [opts.publicBaseUrl] the user's storage public base URL
* @param {{ ok: boolean }|null} [opts.urlProbe] result of a HEAD probe on
* `video.url`, if one was performed (null = not probed / unverifiable)
* @returns {{ status: string, severity: "error"|"warning"|"info"|"ok",
* label: string, hosted: boolean }}
*/
export function classifyVideoHealth(video, { publicBaseUrl = "", urlProbe = null } = {}) {
const v = video && typeof video === "object" ? video : {};
const url = trimStr(v.url);
const magnet = trimStr(v.magnet);
const hosted = isUrlUnderBase(url, publicBaseUrl);
// A deleted (tombstoned) note is intentional, not a problem to fix here — it's
// a candidate for storage cleanup (Phase 2). Checked first because soft-delete
// scrubs url/magnet, which would otherwise read as "no source".
if (v.deleted === true) {
return { status: VIDEO_HEALTH_STATUS.DELETED, severity: "info", label: "Deleted on Nostr", hosted };
}
if (!url && !magnet) {
return { status: VIDEO_HEALTH_STATUS.NO_SOURCE, severity: "error", label: "No playable source", hosted };
}
// A dead URL is only a hard signal when it's a hosted URL we could actually
// probe. External links that fail (or can't be probed) stay "ok" so we don't
// cry wolf on CORS-opaque third-party hosts.
if (hosted && url && urlProbe && urlProbe.ok === false) {
return { status: VIDEO_HEALTH_STATUS.DEAD_URL, severity: "warning", label: "Hosted file unreachable", hosted };
}
return { status: VIDEO_HEALTH_STATUS.OK, severity: "ok", label: "OK", hosted };
}
+74
View File
@@ -0,0 +1,74 @@
// The "My Videos" management tab flags maintenance issues per video so a user can
// keep a large library clean. classifyVideoHealth is the pure decision core:
// no-source -> error (red), hosted-and-unreachable -> warning, deleted -> info,
// otherwise ok. It must NOT flag external/unverifiable URLs as broken.
import assert from "node:assert/strict";
import test from "node:test";
import {
classifyVideoHealth,
isUrlUnderBase,
VIDEO_HEALTH_STATUS,
} from "../js/ui/profileModal/myVideosHealth.js";
const BASE = "https://cdn.example.com";
test("a video with neither URL nor magnet is a red no-source error", () => {
const r = classifyVideoHealth({ url: "", magnet: "" }, { publicBaseUrl: BASE });
assert.equal(r.status, VIDEO_HEALTH_STATUS.NO_SOURCE);
assert.equal(r.severity, "error");
});
test("a magnet-only video is OK (no hosted URL needed)", () => {
const r = classifyVideoHealth({ url: "", magnet: "magnet:?xt=urn:btih:abc" }, { publicBaseUrl: BASE });
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
assert.equal(r.severity, "ok");
});
test("a hosted URL with no probe is OK", () => {
const r = classifyVideoHealth({ url: `${BASE}/u/npub1/h/v.mp4` }, { publicBaseUrl: BASE });
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
assert.equal(r.hosted, true);
});
test("a deleted note is classified as deleted (info), not no-source", () => {
// Soft-delete scrubs url/magnet; deleted must win over no-source.
const r = classifyVideoHealth({ url: "", magnet: "", deleted: true }, { publicBaseUrl: BASE });
assert.equal(r.status, VIDEO_HEALTH_STATUS.DELETED);
assert.equal(r.severity, "info");
});
test("a hosted URL that fails its probe is a dead-url warning", () => {
const r = classifyVideoHealth(
{ url: `${BASE}/u/npub1/h/v.mp4` },
{ publicBaseUrl: BASE, urlProbe: { ok: false } },
);
assert.equal(r.status, VIDEO_HEALTH_STATUS.DEAD_URL);
assert.equal(r.severity, "warning");
});
test("an EXTERNAL URL that fails its probe is NOT flagged (unverifiable, no false alarm)", () => {
const r = classifyVideoHealth(
{ url: "https://third-party.example/v.mp4" },
{ publicBaseUrl: BASE, urlProbe: { ok: false } },
);
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
assert.equal(r.hosted, false);
});
test("a hosted URL whose probe succeeds is OK", () => {
const r = classifyVideoHealth(
{ url: `${BASE}/u/npub1/h/v.mp4` },
{ publicBaseUrl: BASE, urlProbe: { ok: true } },
);
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
});
test("isUrlUnderBase handles trailing slashes, external hosts, and empty input", () => {
assert.equal(isUrlUnderBase(`${BASE}/a/b.mp4`, BASE), true);
assert.equal(isUrlUnderBase(`${BASE}/a/b.mp4`, `${BASE}/`), true, "trailing slash on base");
assert.equal(isUrlUnderBase("https://evil.example/a.mp4", BASE), false);
assert.equal(isUrlUnderBase(`${BASE}-lookalike.com/a.mp4`, BASE), false, "prefix must be a path boundary");
assert.equal(isUrlUnderBase("", BASE), false);
assert.equal(isUrlUnderBase(`${BASE}/a.mp4`, ""), false);
});