web: a reading walks past the recycle bin, not into it

The walk already turned its back on the folders a camera and an editor leave
behind: a name beginning with `.` or `@` is skipped, which is `.thumbnails`,
`.git`, `.Trash`, and the `@eaDir` a Synology writes beside every frame.

An external volume drags along a second set of names, and none of them begins
with a dot, so every one of them was walked. `$RECYCLE.BIN` and `RECYCLER` are
where Windows parks what the reader deleted — frames among them, at full size,
and every one of them decoded into a thumbnail on the way in. `System Volume
Information` is Windows' own bookkeeping. `#recycle` is the Synology share, and
`lost+found` is the directory a Linux volume keeps for repairs. A frame that
comes back out of a bin is a frame the reader threw away, and the pictures in a
share's recycle folder are pictures someone else deleted; neither belongs in
the catalogue, and both cost the reading the same seconds a real frame does.
The folder also stood in the column as a row of its own.

The rule is now one `SYSTEM_DIR` expression over both sets, matched
case-insensitively — the same volume spells the bin `$RECYCLE.BIN` on one drive
and `$Recycle.Bin` on the next — and the walk asks it before it looks at what
the entry is, so a refused folder is neither read nor named.

Verified:
  roll-walk-check.mjs — all passed, with two new assertions: the stand-in roll
    now carries `$RECYCLE.BIN`, `$Recycle.Bin`, `RECYCLER`, `System Volume
    Information`, `#recycle` and `lost+found`, and every one of them holds a
    file named like a frame, so nothing about the files can be what keeps them
    out. No frame is read from them, and none of them reaches the column.
  library-check.mjs — 43 steps, all passed. scan-nav-check.mjs — all passed.
    frontend tsc --noEmit clean.

ponytail: the list is the names the volumes being read actually use, not a
guess at every spelling there is — a box that files its junk under something
else gets a line in that expression, which is the whole of the change. A folder
of the reader's own that happens to be called `#recycle` is skipped too; that
name is worth the trade.

Co-authored-by: PenguinHarness <noreply@penguin.local>
This commit is contained in:
2026-09-29 07:10:41 +07:00
parent fbe9a1bb5b
commit d7241531f9
2 changed files with 25 additions and 5 deletions
+10 -3
View File
@@ -37,6 +37,15 @@ export interface Walk {
// pass rather than a hundred.
export const DIRS_PER_PASS = 200;
// Folders a roll drags along that are never the reader's photographs. A leading
// `.` or `@` is the `.thumbnails` a camera writes, `.git`, `.Trash`, and the
// `@eaDir` a Synology puts beside every frame; the spelled-out names are the
// recycle bins and system folders of Windows, Synology and Linux volumes, which
// hold deleted or machine-written files rather than frames. Matching is
// case-insensitive because the same folder is `$RECYCLE.BIN` on one volume and
// `$Recycle.Bin` on the next.
export const SYSTEM_DIR = /^[.@]|^\$RECYCLE\.BIN$|^RECYCLER$|^System Volume Information$|^#recycle$|^lost\+found$/i;
// The folder the reader clicked goes first. When it is not in the queue the walk
// has not reached it yet — its row is on screen because the parent pass named it
// — so it is opened from the picked folder and read next. A folder already read,
@@ -82,9 +91,7 @@ export async function walkPass(
walk.walked.add(here.rel);
try {
for await (const entry of here.dir.values()) {
// Names beginning with `.` or `@` are skipped — that is `.thumbnails`,
// `.git`, and the `@eaDir` a Synology writes beside every frame.
if (/^[.@]/.test(entry.name)) continue;
if (SYSTEM_DIR.test(entry.name)) continue;
if (entry.kind === 'directory') {
const rel = `${here.rel}${entry.name}/`;
walk.pending.push({ dir: entry as FileSystemDirectoryHandle, rel });