commit 92f4f51e85de625d7bd9911dc7353d3d5e62e52f
parent 9a0daa6ce88dd3f26bc29ea48740b50ff768c19c
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 17 Sep 2026 20:45:23 -0400
storage: the badge names the drive, not the path
A relocated channel's badge said "Media relocated to /run/media/user/09b598d2-b765-40bd-9466-06d8e16cb119/archilyzer-media" — a true sentence nobody reads. Now that the roots have names, it says "on Platter", and in a table cell that is the whole badge: "on Platter", or "on Platter — unreachable".
The name is a PURE PREFIX MATCH (locationLabelOfDataDir, new beside locationOfDataDir) against the configured roots, resolved by the two server pages that build channel rows. Never a probe: a table draws one badge per row and a probe is up to three subprocesses. Whether the drive is reachable right now is media.status, which inspect() already measured per channel.
The accessible name only EXTENDS: "media location: Media relocated to … · on Platter", so everything that addressed the badge by its old words still finds it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 108 insertions(+), 14 deletions(-)
diff --git a/common/lib/storageLocations.ts b/common/lib/storageLocations.ts
@@ -96,6 +96,26 @@ export function locationOfDataDir(
return best;
}
+// The NAME of the location a channel's media is on, or undefined for none.
+//
+// The badge's projection, and the reason it lives here rather than beside the
+// badge: `MediaLocationBadge.tsx` is imported by `"use client"` files, so it may
+// take TYPES from this module but must never call into it — this file imports
+// `node:path`, which has no business in a browser bundle. The two server pages
+// that build channel rows call this and ship the resulting string.
+//
+// `label || id` is the same fallback the sanitizer applies on write and
+// `views/storage.ts` applies on render: a location whose label was blanked by a
+// hand edit is still named by something.
+export function locationLabelOfDataDir(
+ dataDir: string | undefined,
+ locations: StorageLocation[],
+): string | undefined {
+ if (!dataDir) return undefined;
+ const found = locationOfDataDir(dataDir, locations);
+ return found ? found.label || found.id : undefined;
+}
+
// The root of the default location, or "" when there is none. This is the
// one-line replacement for every `settings.storage.mediaRoot` read: the Storage
// panel's prefill, the bulk move's fallback, the selection deck's box.
diff --git a/editor/app/channels/page.tsx b/editor/app/channels/page.tsx
@@ -30,7 +30,10 @@ import {
getSettings,
type SiteSettings,
} from "yt-dlp-transcript-common/lib/settings";
-import { defaultLocationRoot } from "yt-dlp-transcript-common/lib/storageLocations";
+import {
+ defaultLocationRoot,
+ locationLabelOfDataDir,
+} from "yt-dlp-transcript-common/lib/storageLocations";
import { buildChannelBands } from "yt-dlp-transcript-common/views/pipeline/buildBands";
import { EXTERNAL_BAND_IDS } from "yt-dlp-transcript-common/views/pipeline/buildBands";
import {
@@ -53,9 +56,10 @@ export const metadata: Metadata = { title: "Channels" };
// makes the page load in milliseconds instead of seconds. That trade is only
// honest if the page says how old the numbers are — so report the OLDEST
// snapshot on screen, and name the channels that have never had one.
-function summariseFreshness(
- briefs: ReadonlyArray<ChannelBrief>,
-): { oldest: string | null; missing: string[] } {
+function summariseFreshness(briefs: ReadonlyArray<ChannelBrief>): {
+ oldest: string | null;
+ missing: string[];
+} {
let oldest: number | null = null;
let oldestIso: string | null = null;
const missing: string[] = [];
@@ -221,6 +225,15 @@ export default async function ChannelsPage({
status: media.status,
target: media.target,
detail: media.detail,
+ // WHICH NAMED LOCATION — a pure prefix match of the recorded
+ // target against the configured roots, done here because the
+ // settings are here and the table is a client component. NEVER a
+ // probe: this table draws one badge per row. Undefined for a root
+ // nobody named, which renders exactly what it rendered before.
+ locationLabel: locationLabelOfDataDir(
+ media.target,
+ settings.storage.locations,
+ ),
}
: null,
priority: {
@@ -256,9 +269,7 @@ export default async function ChannelsPage({
? buildChannelGroupSections(activeSite, channels, briefs, settings)
: null;
const shown = new Set(channels.map((c) => c.slug));
- const freshness = summariseFreshness(
- briefs.filter((b) => shown.has(b.slug)),
- );
+ const freshness = summariseFreshness(briefs.filter((b) => shown.has(b.slug)));
return (
// ON md+ THE DOCUMENT STOPS SCROLLING. The page is a flex column exactly
// the height of the viewport (main carries py-6, hence -3rem) so that the
diff --git a/editor/app/components/MediaLocationBadge.tsx b/editor/app/components/MediaLocationBadge.tsx
@@ -35,7 +35,12 @@ import type {
export type MediaBadgeInput = Pick<
ChannelMediaLocation,
"status" | "target" | "detail"
->;
+> & {
+ // The name of the storage location this channel's media is on, projected by
+ // the server that built the row (see `mediaBadgeOf`). A fourth string is
+ // still cheaper than shipping the location list to every table.
+ locationLabel?: string;
+};
export type MediaBadgeTone = "neutral" | "danger";
@@ -43,6 +48,10 @@ export type MediaBadge = {
label: string;
title: string;
tone: MediaBadgeTone;
+ // What a table cell draws. Named here rather than recomputed at the call site
+ // so the accessible name (always the full `label`) and the visible text
+ // cannot drift apart.
+ short: string;
};
const LABELS: Record<ChannelMediaStatus, string | null> = {
@@ -53,17 +62,54 @@ const LABELS: Record<ChannelMediaStatus, string | null> = {
inconsistent: "Media inconsistent",
};
+// The one-word state, for the compact rendering of a NAMED location: "on
+// Platter — unreachable" says more in less room than "Media unreachable" plus a
+// hover, because the name is the half the operator already recognises.
+const SHORT_STATUS: Record<ChannelMediaStatus, string | null> = {
+ "in-place": null,
+ ok: null,
+ unreachable: "unreachable",
+ "in-transition": "moving",
+ inconsistent: "inconsistent",
+};
+
// Null means "draw nothing" — an in-place channel, or no location at all (a
// caller that could not inspect). Both are the same instruction to a renderer.
+//
+// `locationLabel` IS THE NAME OF THE STORAGE LOCATION the channel's media sits
+// on — `locationOfDataDir(config.dataDir, settings.storage.locations)`, resolved
+// on the SERVER where the settings are, and passed down as a string. Undefined
+// when it sits on none: a root the operator typed by hand, or a corpus with no
+// locations configured.
+//
+// IT ONLY EVER ADDS. The label keeps its existing words and gains " · on
+// Platter", so everything that addresses this badge by `media location: Media
+// relocated…` still finds it, while the row now names the drive in the
+// operator's own vocabulary instead of an absolute path.
+//
+// NEVER A PROBE. A table draws dozens of these and a probe is up to three
+// subprocesses; the name comes from a pure prefix match against the configured
+// roots. Whether that drive is reachable *right now* is `media.status`, which
+// inspect() already measured per channel — /storage is where a location's own
+// availability is reported.
export function mediaBadgeOf(
media: MediaBadgeInput | null | undefined,
+ locationLabel?: string,
): MediaBadge | null {
if (!media) return null;
const label = LABELS[media.status] ?? null;
if (!label) return null;
const where = media.target ? ` to ${media.target}` : "";
+ const named = (locationLabel ?? "").trim();
+ const on = named ? ` · on ${named}` : "";
+ const shortStatus = SHORT_STATUS[media.status];
return {
- label: media.status === "ok" ? `${label}${where}` : label,
+ label: (media.status === "ok" ? `${label}${where}` : label) + on,
+ short: named
+ ? `on ${named}${shortStatus ? ` — ${shortStatus}` : ""}`
+ : media.status === "ok"
+ ? (LABELS.ok as string)
+ : label,
// The detail carries the reason; the target alone is the fallback so a
// location written by an older inspect() still says where it points.
title: media.detail ?? `${label}${where}`,
@@ -77,18 +123,24 @@ const TONE_CLASS: Record<MediaBadgeTone, string> = {
};
// `compact` drops the target from the label: a table cell wants the four-word
-// state, and the full path is one hover away on the title.
+// state, and the full path is one hover away on the title. With a named
+// location that becomes "on Platter" / "on Platter — unreachable", which is
+// shorter AND says more.
export function MediaLocationBadge({
media,
compact = false,
+ locationLabel,
}: {
media: MediaBadgeInput | null | undefined;
compact?: boolean;
+ locationLabel?: string;
}) {
- const badge = mediaBadgeOf(media);
+ // The explicit prop wins, for the Storage panel — it holds a whole
+ // ChannelMediaLocation from inspect(), which has no room for a name. A table
+ // row carries the name inside its projected `media` instead.
+ const badge = mediaBadgeOf(media, locationLabel ?? media?.locationLabel);
if (!badge) return null;
- const label =
- compact && media?.status === "ok" ? (LABELS.ok as string) : badge.label;
+ const label = compact ? badge.short : badge.label;
return (
<span
title={badge.title}
diff --git a/editor/app/page.tsx b/editor/app/page.tsx
@@ -3,6 +3,7 @@ import type { Metadata } from "next";
import { getPaths } from "yt-dlp-transcript-common/lib/paths";
import { inspectChannelMedia } from "yt-dlp-transcript-common/lib/channelMedia";
import { getSettings } from "yt-dlp-transcript-common/lib/settings";
+import { locationLabelOfDataDir } from "yt-dlp-transcript-common/lib/storageLocations";
import {
getSite,
listSiteIds,
@@ -84,6 +85,8 @@ export default async function Dashboard({
),
),
);
+ // Read once for the whole table, not once per row.
+ const locations = getSettings().storage.locations;
const channels: DashboardChannel[] = rows.map((r) => ({
slug: r.channel.slug,
handling: r.channel.config.handling,
@@ -98,7 +101,15 @@ export default async function Dashboard({
media: (() => {
const m = mediaBySlug.get(r.channel.slug);
return m && m.status !== "in-place"
- ? { status: m.status, target: m.target, detail: m.detail }
+ ? {
+ status: m.status,
+ target: m.target,
+ detail: m.detail,
+ // The named location, matched against the configured roots here
+ // because the settings are here and the table is a client
+ // component. A pure prefix match, never a probe.
+ locationLabel: locationLabelOfDataDir(m.target, locations),
+ }
: null;
})(),
}));