Three bugs from the first day of real use.
1. Quiet hours were evaluated in UTC. The router is handed a UTC `now` and
compared its hour against "22:00-07:00" directly, so notifications were
silenced from 18:00 local -- prime reading time -- and the whole day's budget
was released at 03:00 local. The reported avalanche landed at 03:39 and 03:55.
Quiet hours are now converted to the reader's wall clock first.
2. The timezone never resolved anyway. TZ was "America/Louisville", which exists
on this Arch host but NOT in a Debian container, which ships only the
canonical "America/Kentucky/Louisville". The container fell back to UTC and
`date` printed "America" as the zone abbreviation. The zone is now read from
config rather than the environment, so a bad TZ cannot silently change
behaviour, and the compose entry uses the canonical name. NOTE: ~20 other
services in compose.yml carry the same bad string; they were left alone since
it only affects their log timestamps.
3. Budgets are not a rate. Six notifications fired in a single minute because
the daily allowance could all be spent in one cycle. Each lane now has a
minimum gap (interest 75min, importance 30min) and sends at most one per
cycle, which turns the budget into a rate spread across the day.
Separately, the morning digest had silently fallen back to the templated list.
The real 28-story payload takes 70.3s to generate against a 60s timeout. Raised
to 180s -- and write_prose now LOGS why it failed instead of returning None in
silence, which is what made a one-line timeout cost a debugging session.
Connor pointed out he may use the site from a laptop away from home. The gated
/ui/feedback/ path was worse than just inconvenient: fetch() follows redirects,
so an expired Authelia session would go 302 -> login page -> HTTP 200, and the
UI would mark the card labelled having recorded nothing. Silent data loss on the
one signal the system learns from.
Both surfaces now post to the same HMAC-signed public endpoint, with
`redirect: 'error'` so a redirect fails loudly instead of looking like success.
The gated route is gone -- one mechanism, not two.
/config shows what every block actually matches right now, and a box to try a
filter before saving it: describe what you want caught, see what it catches and
what sits just below the line, move the threshold, then save it as a veto or a
demote. That boundary view is the point -- a crypto filter at 0.55 catches 131
clusters including "Security updates for Monday", and at 0.62 catches 4, which
you can only know by looking.
Saves use tomlkit rather than tomllib plus a dump, because profile.toml carries
the reasoning behind every constant and a comment-stripping round-trip would
quietly destroy the record of why each number is what it is. Verified: 97
top-level comment lines in, 97 out.
The config mount had to stop being :ro, and ~/config/news2 is now owned
10001:1000 group-writable so the container can save while hand-editing still
works. `:U` would have fixed the container and locked the user out.