v0.147.1 — 4c follow-up: the bar must move on an INCREMENTAL run

Found by watching the v0.147.0 card during a real manual run on the demo box,
which is the only way this was going to surface: a 430MB immich push reported 0%
for 40+ seconds and then completed.

The parser was not broken. restic was genuinely reporting no transferred bytes —
on an incremental run where nothing changed, bytes_done is omitempty on restic's
side so it is not even in the JSON, and percent_done stays 0 for the whole run.
Confirmed against the real schema by capturing backup --dry-run --json from
restic 0.14.0 in the controller image rather than guessing; those captured lines
are now quoted verbatim in the type's doc comment.

Why it mattered: a byte-only bar is indistinguishable from a hang in the COMMON
case, which is precisely the silence 4c set out to remove. Shipping it would have
traded "no feedback" for "feedback that says 0% and looks stuck".

files_done/total_files are now parsed and published alongside the bytes; the card
prefers bytes when bytes move, otherwise drives the bar from files and says
"N / M fájl ellenőrizve". parseResticStatus returns a struct instead of four
positional values, and a new test pins the real incremental line shape (bytes
absent, files climbing) so a refactor cannot quietly restore the stuck bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
2026-07-19 09:44:21 +02:00
parent b5d78d1e0f
commit 77e8d5590b
4 changed files with 132 additions and 24 deletions
@@ -213,9 +213,20 @@
/* Only claim a percentage once restic has told us a total — before the scan finishes,
percent is 0 of 0, and a bar pinned at 0% reads as "stuck" rather than "measuring". */
var label = p.current_app ? ('Mentés: ' + p.current_app) : 'Mentés folyamatban';
if(p.total_bytes > 0){
if(p.bytes_done > 0){
/* Bytes are genuinely moving — the informative case. */
label += ' — ' + Math.round(p.percent) + '% (' + p.done_human + ' / ' + p.total_human + ')';
bar.style.width = Math.max(0, Math.min(100, p.percent)) + '%';
} else if(p.total_files > 0){
/* An INCREMENTAL run where nothing changed transfers no new bytes: restic reports
bytes_done 0 and percent 0 for the WHOLE run while still walking every file
(measured on the demo box: 430MB immich, 0% for 40+ seconds, then done). Driving the
bar off bytes here would be indistinguishable from a hang, so fall back to files —
which do move — and say what is actually happening. */
var fpct = Math.round((p.files_done / p.total_files) * 100);
label += ' — ' + p.files_done + ' / ' + p.total_files + ' fájl ellenőrizve'
+ (p.total_human ? ' (' + p.total_human + ')' : '');
bar.style.width = Math.max(0, Math.min(100, fpct)) + '%';
} else {
label += ' — a mentendő adatok felmérése…';
bar.style.width = '0%';