commit 6d6b16ce5b72e64e44aa13ce8caa905e93eae7c1
parent 3d17e85a9d5de84eed58928485187c6065263de9
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 17 Sep 2026 15:22:48 -0400
storage: the refusals the operator should read before a job exists
Five review nits, each the same shape — say it earlier, or say it right.
- A typo\x27d re-point root is an inline error, not a job record and a log. The
job\x27s preflight still refuses it; this is the form\x27s own check, one click
earlier, as relocateChannelMediaAction already does.
- buildStorage had a `refresh` option nothing passed. The page wants the memo;
the Refresh ACTION bypasses it itself. An option with no caller is a claim
about a path that does not exist.
- The label comment named a collision that never existed. The real ones are
"location root" against the form field of that exact name, and "location id"
inside "location identity" — which is what the first e2e run actually hit.
- The IA doc still said eleven in two places; it says twelve, pointing at the
recorded exception, in both.
- /api/test/invalidate-cache clears the storage probe memo with the other
process-wide caches. It is keyed by id and holds ten seconds, and "cold" is
the obvious name for a location in every spec that has one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
5 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/editor/app/api/test/invalidate-cache/route.ts b/editor/app/api/test/invalidate-cache/route.ts
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import { resetSnapshotScheduler } from "yt-dlp-transcript-common/jobs/snapshotScheduler";
import { resetChannelSnapshotMemo } from "yt-dlp-transcript-common/controller/channels";
+import { resetStorageProbeMemo } from "yt-dlp-transcript-common/controller/storageLocations";
export const dynamic = "force-dynamic";
@@ -88,6 +89,14 @@ function invalidate() {
// first's parsed object. Cleared here with the other process-wide caches
// rather than left to a coincidence.
resetChannelSnapshotMemo();
+ // And the storage probe memo, for the snapshot memo's reason in a different
+ // costume. It is keyed by location id and holds for ten seconds, so two specs
+ // using the same id — "cold" is the obvious name and both storage specs
+ // reach for it — could have the second answered with the first's reading of a
+ // disk that no longer exists. Keyed by id and root, which a spec using
+ // testInfo.outputPath varies by accident rather than on purpose; cleared here
+ // so it is on purpose.
+ resetStorageProbeMemo();
revalidatePath("/", "layout");
return NextResponse.json({ ok: true });
}
diff --git a/editor/app/storage/actions.ts b/editor/app/storage/actions.ts
@@ -261,7 +261,18 @@ export async function repointStorageLocationAction(
id: string,
newRoot: string,
): Promise<StreamActionResult> {
- const root = newRoot.trim();
+ const root = normalizeRoot(newRoot);
if (!root) return { ok: false, error: "No new root given." };
+ // THE SAME SHAPE CHECK THE FORM MAKES, one click earlier. The preflight
+ // inside the job refuses a relative root too — it is the guard — but a typo
+ // should be a sentence under the button, not a job record and a log the
+ // operator has to open to read the reason. Same argument as
+ // relocateChannelMediaAction's root check.
+ if (!root.startsWith("/")) {
+ return {
+ ok: false,
+ error: `The new root must be an absolute path (got "${newRoot}").`,
+ };
+ }
return enqueueRepointJob({ locationId: id, newRoot: root });
}
diff --git a/editor/app/storage/buildStorage.ts b/editor/app/storage/buildStorage.ts
@@ -19,14 +19,16 @@ import {
// SERVER ONLY. It reaches `storageVolumes.ts` and the controller, both of which
// import execa. Nothing `"use client"` may import this file; `next build` is
// what proves it.
-export async function buildStorage(opts: { refresh?: boolean } = {}): Promise<
- StorageRowsPayload
-> {
+// NO `refresh` PARAMETER. The page never wants one: a render answers from the
+// 10 s memo on purpose, and the Refresh button is a server ACTION that calls
+// `probeLocationMemo(..., { refresh: true })` itself and then revalidates. An
+// option nothing passes is a claim about a path that does not exist.
+export async function buildStorage(): Promise<StorageRowsPayload> {
const paths = getPaths();
const settings = getSettings();
const locations = settings.storage.locations;
const [probes, rollups, udisksctl] = await Promise.all([
- probeAllLocations(locations, paths, { refresh: opts.refresh }),
+ probeAllLocations(locations, paths),
channelsOnLocation({ paths, locations }),
// Memoised per binary path inside storageVolumes, so this is one
// `--version` for the life of the process.
diff --git a/editor/app/storage/components/StorageLocationsTable.tsx b/editor/app/storage/components/StorageLocationsTable.tsx
@@ -166,13 +166,15 @@ function LocationCard({
</div>
<dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 text-sm">
- {/* NOT "location root" and NOT "location identity". Playwright's
- getByLabel is a SUBSTRING match, so those names would each swallow a
- form field on this very page — "location id" is inside "location
- identity", "location root" inside "location root path" — and a spec
- filling the form would silently target a read-only <dd>. Two
- accessible names where one contains the other are a trap for screen
- reader users for the same reason. */}
+ {/* "location path" and "volume identity", NOT "location root" and
+ "location identity". The form on this same page labels its fields
+ "location root" and "location id", and Playwright's getByLabel is a
+ SUBSTRING match resolved against every match: "location root" would
+ have been ambiguous with the read-out of the same name, and
+ "location id" is contained in "location identity". Filling the form
+ targeted this read-only <dd> instead — the first e2e run failed
+ exactly there. Two accessible names where one contains the other are
+ a trap for a screen reader user for the same reason. */}
<dt className="text-muted-foreground">Root</dt>
<dd className="font-mono text-xs break-all" aria-label="location path">
{row.root}
diff --git a/plans/editor-operations-ia.md b/plans/editor-operations-ia.md
@@ -65,7 +65,8 @@ and concludes the model was wrong.
## The nav, end state
-Four groups, eleven top-level entries, down from three groups and nineteen:
+Four groups, twelve top-level entries, down from three groups and nineteen — eleven after
+slice 5, plus the one recorded exception below:
- **Corpus** — Dashboard, Channels, Review
- **Operations** — Operations *(Schedule folded in, 2026-08-29: it is `/operations/sync`)*
@@ -701,6 +702,8 @@ the site's tab; the family page cuts the release and batches the builds) → `a7
runs from the family page) → `df9b839` (the hub is the family's, and the Sites group is one
entry) → this docs commit. This is the LAST UI-only slice and the one that reached the nav's
end state: **eleven top-level entries, four groups**, with `nav.test.ts` holding it there.
+(Twelve since 2026-09-17: `/storage`, the one recorded exception — see "The nav, end state"
+above. `nav.test.ts` still holds the number, which is the point of asserting it.)
**Three operator decisions, taken before the plan was written:**