Every limit the fetcher was using was a constant. They are settings now, and
CollectOptions became the single place policy lives: AppSettings.ToCollectOptions
clamps them, and the HTTP layer's FetchOptions is projected from that. One
clamping site rather than two sets of ceilings drifting apart.
Clamping rather than validating, for the reason the proxy options already do it:
a hand-edited file must not stop the app from starting. A MaxItemBytes edited to
zero would otherwise refuse everything, and a zeroed concurrency would deadlock
the run outright - so both are pulled into range instead. An empty format filter
is read as "everything", because switching every format off is far more likely
to be a slip than an instruction to collect nothing.
The media root has an ordering problem - it is a setting that decides the paths
the container is built from - so the file is read once before the container
exists rather than making every path lazy for one value.
Purge is scoped to a source and lives on the Collect page, where the source is
already chosen. Content another source also holds survives, which is what the
index's reference count was for.
The showcase hint says out loud what a hard link means: editing the browsable
copy edits the original, and deleting it frees nothing until the last name goes.
That is surprising enough to belong in the UI rather than only in the code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pool was empty until someone pressed Refresh, which also meant the parser
would have had nothing to work with. A ProxyPoolLoader now fills it once at
launch; startup does not await it, because blocking on a public list being
reachable would be the wrong trade, and it never throws. The proxy page joins
the same operation rather than starting a second download, so it reports the
outcome whether it is opened during the load or long after.
That change surfaced a worse bug underneath. The first run still loaded zero
entries with no error logged at all, which turned out to be the feed source
never being asked: options said UseFeed=False and Protocols=None. Neither is
reachable from the UI — both are default(T).
The cause is that AppSettings kept its defaults on property initialisers, and
the source-generated deserialiser does not run them. Reflection-based
deserialisation of "{}" keeps them; the generated context does not. So a
settings.json written before a setting existed came back with default(T) for it:
the proxy feed switched off, the protocol filter empty, the probe timeout zero
and the probe URL blank — and the app looked like the network had failed.
Defaults now live on primary constructor parameters, which STJ applies for
absent JSON members on both paths, so an older file upgrades cleanly. The
regression test writes a settings file from before the proxy settings existed
and asserts each one comes back at its default. ToProxyOptions also treats an
empty protocol filter as "all", since a hand-edited file that matches nothing is
the least useful possible reading of it.
Also quietens IHttpClientFactory to Warning: four Information lines per request
buried everything the app said, and a proxy sweep makes thousands of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>