sort the walk, because readdir does not

`readdir` returns entries in the filesystem's own order — inode order on ext4 —
which is arbitrary and stable only until somebody moves a file.

Harmless for the sync, which does the same work whatever the order. Not harmless
for "Create Gallery": that list becomes the gallery's initial order, and nine
photographs arriving as 05, 08, 01, 07 reads as a bug even though nothing is
wrong.

`numeric` collation, so galeria2 sorts before galeria10 — which is what anybody
naming files that way means.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 21:32:32 +01:00
co-authored by Claude Opus 5
parent 9c62777f5b
commit 300edfb023
+13 -1
View File
@@ -401,7 +401,19 @@ async function buildVideo(
/** Every file under `originals/`, relative to it, depth first. Directories are structure, not work. */
export async function listOriginals(root: string, prefix = ''): Promise<string[]> {
const entries = await readdir(join(root, prefix), { withFileTypes: true }).catch(() => []);
/*
Sorted, because `readdir` is not.
It returns entries in the filesystem's own order — inode order on ext4 — which is arbitrary and
stable only until somebody moves a file. That is harmless for the sync, which does the same work in
any order, and it is not harmless for "Create Gallery": this list becomes the gallery's initial
order, and nine photographs arriving as 05, 08, 01, 07 reads as a bug even though nothing is wrong.
`numeric` so `galeria2` sorts before `galeria10`, which is what anybody naming files that way means.
*/
const entries = (await readdir(join(root, prefix), { withFileTypes: true }).catch(() => [])).sort((a, b) =>
a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
);
const found: string[] = [];
for (const entry of entries) {