v0.311.0: after a power cut, an app repairs the files the cut left empty; the household sees lost photos (R-1007)
gates / gates (push) Successful in 52s

New template block after_unclean_boot (decision 236): once per controller start that followed an
UNCLEAN host boot (crashboot's own verdict, after the 15-minute crash grace; a normal or unknown boot
never runs it), each app with the block rebuilds ONLY its zero-byte files (scan: regular files, no
symlinks, its own filesystem; ids appended to the app's own command in batches; one retry, then
reported) and its own lost-file check is read. The household sees lost files (name, date taken) on
the app page until dismissed; the operator gets app_repaired_after_crash with counts only. Skips,
with the reason, an unproven image, an unknown key, an app not running. MinAgent 0.155.0 unchanged.
Tests red-proofed (14 guards). One new parity fixture (the new card only).

Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-14 16:47:13 +02:00
parent acbbf4b305
commit 8d862e4e69
24 changed files with 1865 additions and 3 deletions
@@ -28,6 +28,21 @@
</div>
</div>
{{- if .LostFiles}}
<div class="app-info-card alert-warning" style="margin-top:1rem" id="lost-files-card">
<h3>{{T "app_info.lost_files_title"}}</h3>
<p>{{T "app_info.lost_files_text"}}{{if .LostBootAt}} <strong data-lost-boot-at>{{.LostBootAt}}</strong>{{end}}</p>
<ul data-lost-files>
{{- range .LostFiles}}
<li><span class="mono">{{.Name}}</span>{{if .Taken}} — {{T "app_info.lost_files_taken"}} {{.Taken}}{{end}}</li>
{{- end}}
</ul>
<p>{{T "app_info.lost_files_hint"}}</p>
<button type="button" class="btn btn-sm btn-outline" onclick="appPress(this, '/apps/{{.Meta.Slug}}/lost-files/dismiss')">{{T "app_info.lost_files_dismiss_btn"}}</button>
<span class="form-hint app-press-err" style="display:none;color:var(--red)"></span>
</div>
{{- end}}
{{if .Stack.Updating}}
<div class="alert alert-info" style="margin-top:1rem" data-update-phase="{{.Stack.UpdatePhase}}">{{updatePhaseText .Stack}}</div>
{{else if and .Stack.HoldReason (eq .Stack.HoldKind "unhealthy_stop")}}
@@ -350,7 +365,7 @@ function icCopyPw(btn) {
{{end}}
{{template "layout_end" .}}
{{- if or .SetupGateClosed .SignupClosed .CloseSignupOffered (and .Stack.Deployed .KnownLoginLine)}}
{{- if or .SetupGateClosed .SignupClosed .CloseSignupOffered (and .Stack.Deployed .KnownLoginLine) .LostFiles}}
<script>
// v0.280.0: the household's presses on this page — "Done, I set it up" (decision 46), "I changed it" (R-710).
function appPress(btn, url) {