Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

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:
Meditor/app/api/test/invalidate-cache/route.ts | 9+++++++++
Meditor/app/storage/actions.ts | 13++++++++++++-
Meditor/app/storage/buildStorage.ts | 10++++++----
Meditor/app/storage/components/StorageLocationsTable.tsx | 16+++++++++-------
Mplans/editor-operations-ia.md | 5++++-
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:**