Three real bugs behind the endless 'Download failed', found by putting a
diagnostic on the device instead of guessing:
1. HTTP read quit early. The loop treated a gap in the stream as
end-of-data, so a 49 KB file came back as 16 KB (n=16492 measured) and,
worse, that truncated buffer was returned as success. It now reads to
Content-Length, gives stalls a 4 s progress grace, and FAILS on a short
read instead of handing back half a file.
2. SD writes from PSRAM died after the first 4 KB (wr=4096, measured twice).
Writes now go through a small internal-RAM bounce buffer, which is what
every proven-good SD writer in this firmware already does. Full 49344
bytes written and installed, confirmed on the bench.
3. A truncated write still stamped the new '# ver:' (it is the file's FIRST
line) while the appended rows were lost — so the tab claimed v4, offered
no Update, and showed English. Downloads are atomic now: temp file,
verify on-card size, then rename over the target. A failure leaves the
old file and its old version untouched.
Plus: if a /lang write fails, an identical control write to /apps proves
whether the card is healthy, and if so /lang is rebuilt and the install
retried — self-healing the FAT damage that mid-write resets can leave.
Language changes now show 'Restarting to apply the language...' and reboot
on a timer, so the notice actually paints. showAlert() followed immediately
by rebootDevice() never rendered a frame — the device just went dark under
your finger. Applies to Update, Use, and the built-in picker alike.
The boot self-heal no longer toasts its failures (that was the 'download
failed' that greeted you when merely opening the store). Files at ver 5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>