diff --git a/CHANGELOG.md b/CHANGELOG.md index c04b14e..c1e1162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## Changelog +### v0.126.0 — UI uniformity bundle: shared app-list rows, infra-app metadata, restore-form polish, mojibake gate (2026-07-13) + +- **Gate housekeeping (first commit):** `scripts/backups_split_move_check.py` retired — the + one-shot v0.124.0 migration gate served its purpose (it pinned the split to verbatim moves vs + df7ad37); this release legitimately rewrites those blocks onto the shared row partial. All + other template gates stay mandatory. + ### v0.125.0 — .fab volume export/import: containerized path-strand data loss FIXED (IA finding 1, HIGH) (2026-07-13) — MinAgent: 0.81.0 Both .fab volume legs streamed via `docker run -v ` host mounts — correct diff --git a/REUSE.md b/REUSE.md index 2d8ac6e..7d79024 100644 --- a/REUSE.md +++ b/REUSE.md @@ -210,7 +210,7 @@ Cross-repo edges: - **New page/nav item**: `baseData` + sidebar in controller/internal/web/templates/ (nested sub-links pattern `.nav-links-nested`); must pass `controller/scripts/template_id_gate.py` + `controller/scripts/emoji_gate.py` + `controller/scripts/native_confirm_gate.py` + `controller/scripts/offbox_rename_gate.py`. - **Docker volume tar streaming (v0.125.0)**: `appexport.dockerExec` (seam, package var) + `withVolumeHelper`/`exportVolumeTar`/`importVolumeTar` — stream volume content via `docker cp` through a stopped helper container. NEVER `docker run -v ` — the daemon resolves `-v` host-side and strands the data when the controller is containerized (the v0.124.0 HIGH finding); `controller/scripts/docker_run_volume_path_gate.py` enforces (every `"-v"` allowlisted with its WHY). - **Guarded file download (v0.124.0)**: `handler_export_download.go` — the canonical shape for streaming a server-side file to the browser: accept a BASENAME only (shape regexp + no separators/`..`), `filepath.Join` then assert `filepath.Dir(path) == dir`, `io.Copy` (never ReadAll), `Content-Disposition: attachment`, remove after a successful stream, TTL sweep (`sweepFabDownloads(dir, now, maxAge, logger)` — now injected for tests). Red-proof the guard by loosening to prefix-matching (the `..` case must fail). -- **Backups sub-page data**: `backupsCommonData(page, title, r)` + `backupsOffboxData(data)` (handlers.go) — the ONLY builders for the four `/backups*` pages; a new backups section extends these, never re-derives in a page handler. Section moves are gated by `scripts/backups_split_move_check.py` (one-shot vs df7ad37). +- **Backups sub-page data**: `backupsCommonData(page, title, r)` + `backupsOffboxData(data)` (handlers.go) — the ONLY builders for the four `/backups*` pages; a new backups section extends these, never re-derives in a page handler. (The one-shot v0.124.0 move gate `backups_split_move_check.py` was retired in v0.126.0.) - **Consequential-action confirm (LIGHT)**: `felhomConfirm(el, question, onYes)` in layout.html (v0.123.0) — the trigger swaps in place to "kérdés + Igen/Mégse"; form buttons opt in with `data-confirm="…"` (delegated listener, `requestSubmit` keeps formaction/name-value). NEVER native `confirm()`/`prompt()` (OS-modals freeze browser automation — drill F-11; `native_confirm_gate.py` enforces). Heavy destructive flows keep the `.confirm-overlay` `openDialog` pattern. - **New hub event**: typed `Notify*` wrapper on Notifier + hub allowlist entry (cross-repo). - **New app integration**: `integrations.Manager.RegisterHandler` with `IntegrationKey(provider, target)`. diff --git a/controller/README.md b/controller/README.md index 58117a9..1489e2a 100644 --- a/controller/README.md +++ b/controller/README.md @@ -367,7 +367,8 @@ self-sufficient backup** — any single tier can fully restore an app. Shared data builders: `backupsCommonData` (chrome + full-status + flash) + `backupsOffboxData` (offbox target/toggles) in `handlers.go`; shared partials in `templates/backups_shared.html`. -The split is MOVE-only vs v0.123.0 — `scripts/backups_split_move_check.py` gates it. +(The v0.124.0 split was MOVE-only, gated one-shot by `backups_split_move_check.py`; the gate was +retired in v0.126.0 when the moved blocks were legitimately rewritten onto the shared row partial.) | Tier | Contents | Location | Can fully restore? | |------|----------|----------|--------------------| diff --git a/controller/scripts/backups_split_move_check.py b/controller/scripts/backups_split_move_check.py deleted file mode 100644 index 299ef8e..0000000 --- a/controller/scripts/backups_split_move_check.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -"""Backups IA-split move check (v0.124.0, one-shot) — the split MOVED the v0.123.0 sections, -it did not rewrite them: every moved block from the pre-split backups.html (commit df7ad37) -must appear, whitespace-normalized, on EXACTLY its mapped page. Allowed divergences are encoded -explicitly (the tier-3 anchor retarget; the restore-to-verify form's relocation). - -Run from controller/: python scripts/backups_split_move_check.py -""" -import io, os, re, subprocess, sys - -BASELINE = "df7ad37" -OLD_PATH = "controller/internal/web/templates/backups.html" -TPL = os.path.join("internal", "web", "templates") - -# (name, 1-indexed start, end inclusive, target template file, normalizer applied to the NEW page) -BLOCKS = [ - ("storage-overview", 32, 77, "backups.html", None), - ("whole-guest", 79, 124, "backups.html", None), - ("stat-cards", 227, 271, "backups.html", None), - ("offbox-pre", 127, 186, "backups_remote.html", None), - ("offbox-post", 193, 221, "backups_remote.html", None), - ("apps-divider", 224, 225, "backups_apps.html", None), - ("schedule", 273, 302, "backups_apps.html", None), - ("databases", 304, 376, "backups_apps.html", None), - # the tier-3 action anchors were retargeted cross-page — normalize them back before comparing - ("per-app-rows", 378, 541, "backups_apps.html", - lambda s: s.replace('href="/backups/remote#offbox-section"', 'href="#offbox-section"')), - ("restore-panel", 543, 586, "backups_restore.html", None), - ("offbox-verify-form", 188, 191, "backups_restore.html", None), - ("banner-js", 591, 627, "backups_shared.html", None), - ("apps-js", 628, 674, "backups_apps.html", None), - ("guest-js", 676, 712, "backups.html", None), - ("restore-js", 714, 841, "backups_restore.html", None), -] - -PAGES = ["backups.html", "backups_remote.html", "backups_apps.html", "backups_restore.html", - "backups_shared.html"] - - -def norm(s): - return re.sub(r"\s+", " ", s).strip() - - -def main(): - old = subprocess.run(["git", "show", BASELINE + ":" + OLD_PATH], - capture_output=True, text=True, encoding="utf-8") - if old.returncode != 0: - print("cannot read baseline %s:%s — %s" % (BASELINE, OLD_PATH, old.stderr.strip())) - sys.exit(2) - old_lines = old.stdout.split("\n") - - pages = {} - for fn in PAGES: - pages[fn] = io.open(os.path.join(TPL, fn), encoding="utf-8").read() - - failed = 0 - for name, a, b, target, fix in BLOCKS: - block = norm("\n".join(old_lines[a - 1:b])) - hits = [] - for fn in PAGES: - content = pages[fn] - if fix: - content = fix(content) - if block in norm(content): - hits.append(fn) - if hits != [target]: - failed += 1 - print("MOVED-BLOCK MISMATCH %-18s (old L%d-%d): expected [%s], found %s" - % (name, a, b, target, hits)) - if failed: - print("MOVE CHECK FAILED: %d block(s) rewritten, duplicated or lost" % failed) - sys.exit(1) - print("move check OK — all %d v0.123.0 blocks moved verbatim to their mapped page" % len(BLOCKS)) - - -if __name__ == "__main__": - main()