## 2026-10-13 (night) — estimator v3.1 tests - `test_build_sizing_web.py` (12 tests): the bar is never below the smallest named box, its parts still sum to it within 1 MB (nothing ticked, one tiny app, household, all 62), the base system never below the 8 GB split (4,096); 9 apps → 9 named parts, 10 apps → 8 named + a group of 2. Red-proofed: the floor removed from the JS and from the mirror, and grouping a single app in the JS and in the mirror, each convicted. ## 2026-10-13 (evening) — estimator v3: segments and the English article under test - `build_sizing_web.py`: `bar_app_colours` (8) in the data file, so the script holds no number of its own. - `test_build_sizing_web.py` (10 tests): the bar's segments (base system = box − guest, Felhom, each app's peak, spare = guest − peak) add up to the bar within 1 MB for the four cases and for nothing selected; base and spare are never negative; the household preset by hand (spare = 5,515 × 0.5, base = 4,096 + 612); every English recommendation gets „an 8" / „a 12" … Red-proofed: the „an 8" rule removed, the JS computing spare from the typical figure, and segments off by 50 MB each convicted. ## 2026-10-13 (afternoon) — estimator v2: `build_sizing_web.py` box sizes, tests for the inverse - `build_sizing_web.py`: `boxes_gb` = 8, 12, 16, 24, 32, 48, 64; new `bar_marks_gb` and `bar_max_gb`. - `test_build_sizing_web.py` (8 tests): the four cases × seven sizes, with the recommended („fits well”) and the smallest „tight” size each; the inverse agrees with the forward rule (the guest at the answer holds the need, one step smaller does not); SIZING.md's table is read by its header and must carry the 24 GB column; `sizing.js` may hold only 0, 1, 2, 100 and 1024. Red-proofed: a wrong 24 GB cell, a missing 24 GB column, a hard-coded 64 in the JS, and an inverse 1 GB too big each convicted. ## 2026-10-13 — `build_sizing_web.py` + `site_gates.py` UNLISTED pairs and gate 25 - **`build_sizing_web.py`** builds `website/assets/sizing-data.json` (the machine estimator's data) from `documentation/audits/sizing-2026-10-13/sizing.json` and the two apps pages. Refuses (exit 1, writes nothing) when a listed app is missing from `sizing.json` (FileBrowser excepted by name: built in), when a peak is below its typical figure, or when the two apps pages disagree. `--check` compares the committed file with a fresh build. - **`test_build_sizing_web.py`** (7 tests): fresh build = committed file; the four cases of SIZING.md §5 give the expected verdicts; SIZING.md's verdict table agrees with the rule; `sizing.js` carries the rule's shape and none of its numbers. Red-proofed: a hard-coded 470 in the JS, a changed headroom in the data, and a wrong table cell each convicted. No JS runtime exists on DooPlex or in CI, so the rule is mirrored in Python on the same data. - **`site_gates.py`**: `UNLISTED` twin pairs get every twin check (hreflang, globe, canonical, lang, no Hungarian on the English page) and are excluded from the sitemap requirement; **gate 25** adds what unlisted means — `noindex`, a `Disallow:` line per URL in `robots.txt`, absent from `sitemap.xml`, linked from no other page. Red-proofed: each of the four, and a wrong twin alternate, convicted on its own; the first run caught the missing `robots.txt` lines for real. ## 2026-10-12 (night) — make_hero_clip.py: scenes keep their own length - **`--seconds` is no longer a fixed window.** Each scene now plays its full recorded length, read per file with `ffprobe` (operator ruling 2026-10-12: *„use the full length for all of them, they had been cut as I liked them”* — the raw file IS the edit). `--seconds N` stays as a cap, for trying a shorter loop; `0` (the default) means full length. - The xfade offsets are computed from the running sum of the scenes so far **minus one fade per transition already made** — xfade measures its offset on the chain it is handed, which is shorter than the raw sum. With equal-length scenes the old `i * (seconds - FADE)` was the same number; with unequal ones it is not. - **Output is `hero-clip.mp4` / `.webm` / `hero-poster.webp`** — no language suffix, because the same recordings serve `/` and `/en/`. `--lang hu` puts `-hu` back if the two ever differ. - Six real scenes, 36.6s after the fades: **mp4 1 134 KB, webm 1 295 KB, poster 53 KB.** Three times the 11.4s trial, as expected — the hero is the only page that loads it, and only once it is on screen. ## 2026-10-12 (evening) — make_hero_clip.py: the home page's laptop clip - `scripts/make_hero_clip.py` assembles the hero clip from the operator's raw app recordings: `NN-appname.ext` in, scaled and centre-cropped to the laptop screen's 1.626:1, crossfaded, out as **mp4 (h264) + webm (VP9) + a webp poster**. Runs on DooPlex; Windows has no ffmpeg. It is not part of any build, gate or deploy — the site has no build step, so its output is committed like any other picture. - **Why mp4+webm and not a GIF**, measured on DooPlex 2026-10-12 with six crossfaded scenes: | | 11.4s @ 904×556 | 11.4s @ 1130×696 | |---|---:|---:| | mp4 (h264 crf30) | **331 KB** | 775 KB | | webm (VP9 crf44) | 411 KB | 1 196 KB | | animated webp | — | 2 551 KB | | GIF | **2 676 KB for 6s** | — | A GIF is ~11× the mp4 for the same seconds, and dithers to 256 colours on a dark UI. - Proven end to end against six placeholder scenes built from the repo's own app screenshots: 21s loop → mp4 480 KB, webm 609 KB, poster 83 KB. ## 2026-10-12 (afternoon) — gate 22 also refuses an unscoped bare element rule - `site_gates.py` gate 22 gains a second half: a rule in `site.css` whose selector starts with a bare `nav`, `header`, `main` or `aside` and is not scoped `body:not(.shell)` / `.shell` is a failure, because those elements ARE the shell (`