Skip to content

chore: v2.12.0 - #300

Merged
angeloINTJ merged 4 commits into
mainfrom
chore/v2.12.0
Oct 29, 2026
Merged

angeloINTJ merged 4 commits into
mainfrom
chore/v2.12.0

Conversation

@angeloINTJ

Copy link
Copy Markdown
Owner

chore: v2.12.0

The release chore for v2.12.0, cut from main 96a5af0 (the merge of #299). Host only: nothing ran on a board, and nothing here is signed, tagged or published. The gate on both boards follows this change, and "The bench" section of both CHANGELOGs is a placeholder that the gate's documentation change fills in (said so in the entry itself).

Decisions of the maintainer (2026-10-28)

Commits (one proof each)

  1. 3df4dce0 chore: SIMUT_VERSION 2.12.0, the only source line.
  2. f983f082 docs: the v2.12.0 changelog, in English and in Portuguese.
  3. 7a679374 docs: v2.12.0 in the READMEs, the manual and the site.
  4. 3025339d docs: the review's fixes (the changelog's gaps and a measured claim made honest); no file under src/.

The 35 pull requests since v2.11.1, and the section each one is in

The section of each PR was chosen by hand, and the script that builds this table checks that the PR's number is named in that section of both entries (the check scripts are kept in the release scratchpad, outside the repository); it fails if one of the 35 is missing from either entry.

PR Title Merged (UTC) Section of the English entry Section of the Portuguese entry
#299 Web: the Files page no longer says "Upload complete." for an upload the device refused (F5) 2026-10-29 A refused upload on the Files page Um envio recusado na página Arquivos
#298 Web: theme names in the device's language (the device sends English and a code; the page translates) 2026-10-28 Theme names on the web page Os nomes dos temas na página web
#297 pico_w_test: serve the licence page from LittleFS, for 3,699 B of room under the OTA ceiling 2026-10-28 Under the hood Por dentro
#295 fix(panel): the theme and the language ask Core 0 for PERM_SYS_CONFIG, and check_authz.py proves every panel event's check 2026-10-28 The panel's theme and language O tema e o idioma do painel
#294 fix(panel): after a boot in English the Idioma screen lists the pack in /lang again, so the language has a way back 2026-10-28 English has a way back on the panel O inglês tem volta no painel
#293 ci(savings): a failed nightly run opens an issue, or comments on the open one 2026-10-25 Under the hood Por dentro
#291 fix(tools): the bench web client logs back in on the 401 a dropped session gets 2026-10-25 Under the hood Por dentro
#290 fix(tools): the flash budget gate fails when it cannot see the firmware.bin 2026-10-24 Under the hood Por dentro
#289 docs: R-R17 no longer says the pack gate and CLAUDE.md promise "only that line" 2026-10-24 Under the hood Por dentro
#288 chore(https-test): the license page of pico_w_test_https is served from LittleFS, for 4,300 B of room 2026-10-22 Under the hood Por dentro
#287 docs: the bench of 2026-10-22, what TRL( ) gives on the device, and what an OTA does to the language packs 2026-10-22 Under the hood Por dentro
#286 fix(alpha): the console and syslog name the event again outside English 2026-10-22 The console and syslog of an alpha or a Wire O console e o syslog de um Alpha ou de um Wire
#285 fix(tools): the savings floor measures past the BTstack bank's fill 2026-10-22 Under the hood Por dentro
#284 fix(log): the language pack's records carry their own tag and codes (#283) 2026-10-21 The language pack's log records Os registros de log do pacote de idioma
#282 fix(net): the radio is asked for its return code before a blind join and in the RSSI watch 2026-10-21 The radio that stops answering O rádio que para de responder
#281 fix(log): a planned restart writes the log records it holds in RAM 2026-10-21 The log of a planned restart O log de um reinício planejado
#280 fix(touch): an injected touch ends on its own clock, not inside mapTouchPoint( ) 2026-10-16 An injected touch that never ended Um toque injetado que nunca terminava
#279 fix(tools): patch.sh 2b-ter greps text its patch adds; a gate holds every guard to its patch 2026-10-16 Under the hood Por dentro
#278 perf(tft): clock clk_peri from clk_sys; the panel's SPI at 66.5 MHz 2026-10-16 The panel's bus O barramento do painel
#277 fix(web): drop a non-TLS client on the TLS port before the handshake (#274) 2026-10-15 A plain client on the TLS port Um cliente simples na porta TLS
#276 perf(wire): the ENC28J60 sends a frame while the one before is on the wire 2026-10-15 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#275 deps(wire): PicoENC28J60 v1.3.1 2026-10-15 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#273 fix(web): send a response's small writes together (#189) 2026-10-15 A reply that lost its framing Uma resposta que perdia o enquadramento
#272 fix(web): a write cut short ends the response instead of leaving a hole (#189) 2026-10-14 A reply that lost its framing Uma resposta que perdia o enquadramento
#271 fix(net): the link's level is read inside the NTP wait; the Air's FLUSH waits for NTP 10 s at most 2026-10-14 The link, inside the wait for the time O link, dentro da espera pela hora
#270 fix(net): a scan the radio never answered is not escaped by a join 2026-10-14 The radio that stops answering O rádio que para de responder
#269 net: the radio watches its link inside the NTP wait 2026-10-14 The link, inside the wait for the time O link, dentro da espera pela hora
#268 net: the radio is a driver unit; NetworkManager.cpp is the host 2026-10-14 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#267 net: the network driver's boundary: identity apart from class, a generic wired link, a gate 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#265 configurator: the network is a choice of driver, not the lan switch 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#264 configurator: refuse what does not fit the PIO blocks or the pins 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#263 feat(lan): the SIMUT Wire loses what belongs to the radio; a ninth image with the TFT 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#262 feat(net): the wired driver for the SIMUT Wire 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#261 feat(net): a lan switch for the SIMUT Wire, its CI image, and a PIO and pin budget 2026-10-13 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver
#260 feat(configurator): the Pico 2 W in the build configurator, and an overflow measured on the .bin 2026-10-12 The SIMUT Wire and the network as a driver O SIMUT Wire e a rede como driver

Coverage: 35 of 35 in both languages. Both entries have 19 ### sections in the same order, and the digit runs of the two entries are identical once the locale's separators are removed.

Known and Upgrading, item by item

The six items the maintainer listed are in (a refused file followed by an accepted one in the same upload; the four panel events without Core 0's check; data/themes/*.thm against tools/gen_theme_collection.py; the es-ES proposals; the emergency console without language; the theme select that ignores the answer of /api/save_sys).

v2.11.1 item Now Evidence
The build configurator does not build for the Pico 2 W out #260 (merged 2026-10-12): build_custom.py hands out the trial-flagged .bin and the factory .uf2; the README already says "for the Pico 2 W too"
A narrowed PIN alphabet or length ceiling strands an old PIN kept no keypad, PIN, account or user file changed since v2.11.1 (git diff --stat v2.11.1 origin/main -- src)
A Core 1 hard fault leaves no program counter kept 0 changed lines in src/ and WebUI.h match heartbeat
A panel save during an upload restarts Core 1 kept 0 changed lines match the Core 1 restart calls
A chunked reply loses its framing (#189) out #272, #273: on the same Pico W and network (the A/B of #273), 3 of 1,000 replies cut with #272 alone and 0 of 1,000 with #273, then 0 of 5,000; the pre-fix baseline (24 broken replies in 2,000 at night, 1 in 2,000 in the morning) moved with the hour, so it is not set against the later runs; measured on a Pico W only
The alpha's event names are empty out #286: both lookups return a null pointer; checked in the images, not on a rig
Accented boot lines print ? kept the boot box is untouched; the 7 changed lines that match Latin or UTF-8 are #294's Idioma screen
No retry of an install refused with 503 kept the only 503 in the WebUI.h diff is a comment of #299
configure terminal needs no enable kept 0 changed lines
Two pressure sensors, one key kept no telemetry file changed
The Telemetry page's token list stops at v22 kept 0 changed lines
Adopt and sensor_wipe do not restart the history kept git grep provisionEpoch origin/main -- src: writers only, no reader
First reading after a boot can be a wrong probe's kept 0 changed lines match % 5

"Kept" rests on a changed-lines search of src/ and WebUI.h, which says the code the v2.11.1 entry read has not moved; none was exercised again. The v2.11.1 bullet "simut-rx does not know releasetwo" stays: its pull request #14 is still open and its latest release is 1.9.1 (gh, read only, 2026-10-28).

Measured on this tree (2026-10-28, host only)

  • Only one line reaches a binary: git diff --stat origin/main -- src WebUI.h data keys platformio.ini 'tools/*.py' shows src/SystemDefs_Limits.h | 2 +-.
  • The nine images, each after rm -rf .pio/build/<env>, with the pio shim under the shared lock, check_flash_budget.py <env> <log> right after each build; every one exits 0; the flash used of all nine equals its record in tools/flash_budget.json ("0 B under budget") and the eight .bin that have a record equal it (pico_w_test_https has none: it is exempt from the OTA ceiling). The version string has the same length and the login page's gzip did not move, so flash_budget.json and PLANO_STABLE section 3.1 are untouched. --table and test_flash_budget.py pass. "2.12.0" is in the four published images and "2.11.1" is not (grep -c -a).
Image firmware.bin Signed (+241) Under the OTA ceiling check_flash_budget.py
pico_w_release 1,035,892 1,036,133 4251 B exit 0, flash used equals its record; bin equals its record
pico_w_alpha 990,380 990,621 49763 B exit 0, flash used equals its record; bin equals its record
pico_w_air 1,035,340 1,035,581 4803 B exit 0, flash used equals its record; bin equals its record
pico2_w_release 1,000,000 1,000,241 568527 B exit 0, flash used equals its record; bin equals its record
pico_w_test 1,036,716 1,036,957 3427 B exit 0, flash used equals its record; bin equals its record
pico_w_test_https 1,041,044 1,041,285 exempt (ota_exempt) exit 0, flash used equals its record (no bin record)
pico_w_asserts 1,038,188 1,038,429 1955 B exit 0, flash used equals its record; bin equals its record
pico_wire 604,464 604,705 435679 B exit 0, flash used equals its record; bin equals its record
pico_wiretft 787,608 787,849 252535 B exit 0, flash used equals its record; bin equals its record
  • Against the published v2.11.1 (bancada_20261028_rel2120/old/, signed .bin): release 1,033,357 to 1,036,133 B (+2,776); alpha 990,621 to 990,621; Air 1,031,485 to 1,035,581 (+4,096); Pico 2 W 997,417 to 1,000,241 (+2,824).
  • Host tests: the eleven suites, 0 failures: 328 + 17 + 54 + 34 + 62 + 14 + 59 + 51 + 83 + 13 + 19 = 734 cases (the READMEs quote 734; check_readme_numbers.py passes). Under AddressSanitizer and UBSan (the gates job's last step): all eleven suites exit 0 (finished 21:33:23 on 2026-10-28).
  • Routes: tools/check_authz.py reports 62 routes, 52 gated and 10 public by design, with the four panel-event warnings the Known list names.
  • The gates job's commands (read from .github/workflows/build.yml, pip install and pio test steps left out): 39 steps, 0 failed, among them scan_secrets.sh, check_angulo.py, check_readme_numbers.py, check_flash_budget.py --table, gen_logcodes.py --check, check_authz.py. python3 tools/build_manual.py --check passes.
  • The CHANGELOG check (check_changelog.py, in the release scratchpad) with three negative controls, each giving exactly one finding (a PR number removed, a ### removed, an Unreleased heading put back).

Deviations and things to decide

  • The manual's generated HTML (docs/MANUAL.pt-BR.html) was not regenerated whole: with pandoc 3.7.0.2 the generator no longer writes class="header|odd|even" on table rows, and a plain regeneration rewrites ~3,081 lines. As in Web: theme names in the device's language (the device sends English and a code; the page translates) #298 and Web: the Files page no longer says "Upload complete." for an upload the device refused (F5) #299, the diff is the substance only (56 insertions, 30 deletions). Checked: the committed file with its row classes stripped equals the generator's output for the old sources; the committed file equals that output with the classes put back by position (thead rows header, tbody rows odd/even), so the new file is the new output with the same rule; and the committed file after the change equals the regeneration from the final sources (cmp).
  • The entry's heading is ## v2.12.0 (2026-10-28), the format of the previous entries (## v2.11.1 (2026-10-12)). The date is the chore's; the READMEs, the manual and the site carry the same date. If publication is on another day, the gate's documentation change sets it.
  • The panel at 66.5 MHz is stated as a setting ("is set to" in the CHANGELOG header, "ajustado a" in the manual and the site) ("ajustado a 66,5 MHz"), with 24 MHz as the measured "before" (bench, 2026-10-15). The after-change clock was measured only on a study build of the Angulo UI branch (perf(tft): clock clk_peri from clk_sys; the panel's SPI at 66.5 MHz #278), not on the release image; the gate checks it (/api/status clk_peri, tft_hz).
  • pico_w_test has 3,427 B of OTA room today (check_flash_budget.py and PLANO_STABLE section 3.1 agree). The fix(panel): after a boot in English the Idioma screen lists the pack in /lang again, so the language has a way back #294 bullet that said "139 B of OTA headroom left of 763" now says that was that day's room and that pico_w_test: serve the licence page from LittleFS, for 3,699 B of room under the OTA ceiling #297 gave it back.
  • The release image has 4,251 B of OTA room, 155 B above the one sector under which check_flash_budget.py warns (the Air has 4,803 B). The next addition to either is a decision, not a surprise.
  • Not in Known: ctx=205 (a reboot under dead telemetry, open without a reproduction) and B1 (ctx 209/455, known and instrumented). Say if they belong there.
  • The es-ES text in docs/index.html (the new release article) is a proposal awaiting a native review, like the theme names and fil_up_refused.
  • Builds were quick, and they were real: 10 to 15 s each on 16 cores, 2 minutes for the eleven suites and the nine images. The pico_w_release log shows 318 compile steps; there is no ccache here and platformio.ini sets no build cache. The sizes equal the records that CI's builds produced.

Review

A revisor reviewed the three first commits (COM RESSALVAS, no blocker): it rebuilt pico_w_release from scratch byte for byte (sha256 dd41318a…, signed 1,036,133 B, 4,251 B under the OTA ceiling, strings shows 2.12.0 and no 2.11.1), ran the eleven native suites (734), checked 35 of 35 merges in both entries and every Known item against the code. Its findings (two changelog gaps: #262's top-panel fix and #272's metr.cgw/short_write series; "set to" for the 66.5 MHz the release image never measured; the Upgrading note on English until the packs return; an hour-skewed pair of numbers on the site; Portuguese agreement) are fixed in 3025339d.

Not done here

The jobs of CI (this PR runs them), the signed candidate, both gates on hardware, and the gate's documentation PR, which fills "The bench" and moves the entry's date to the publication day (the gate takes hours: the publication will not be on 2026-10-28).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu

Ângelo Moisés Alves and others added 4 commits October 28, 2026 21:40
The only source line of the v2.12.0 release chore: SIMUT_VERSION "2.11.1" -> "2.12.0" (src/SystemDefs_Limits.h). CONFIG_VERSION stays at 26 and
the security version at 1 (keys/ota_policy.json, series 1); git diff v2.11.1 HEAD -- keys src/ota/ota_layout.h is empty (2026-10-28).

Proof, 2026-10-28, on this tree (origin/main 96a5af0 plus this line), host only:
- git diff --stat origin/main -- src WebUI.h data keys platformio.ini 'tools/*.py': one file, one line.
- The nine images built after rm -rf .pio/build/<env> (pio shim, under the shared lock), tools/check_flash_budget.py <env> <log> right after
  each: nine exit 0. The version has as many characters as 2.11.1 and nothing moved: the flash used of all nine equals its record, and the eight .bin that
  have a record equal it (pico_w_test_https has none: it is exempt from the OTA ceiling), so tools/flash_budget.json and
  docs/analysis/PLANO_STABLE.md section 3.1 are untouched; --table and tools/test_flash_budget.py pass. Signed .bin (bin + 241 B): release
  1,036,133 (4,251 B under the OTA ceiling), alpha 990,621 (49,763), Air 1,035,581 (4,803), Pico 2 W 1,000,241. Against the published
  v2.11.1: +2,776, 0, +4,096 and +2,824 B. "2.12.0" is in the four published images and "2.11.1" is not (grep -c -a).
- The eleven native suites, 0 failures: 328 + 17 + 54 + 34 + 62 + 14 + 59 + 51 + 83 + 13 + 19 = 734 cases; the same eleven under
  AddressSanitizer and UBSan (the gates job's last step): exit 0.
Not verified on hardware: nothing here ran on a board.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
"Unreleased" becomes "v2.12.0 (2026-10-28)" in CHANGELOG.md and CHANGELOG.pt-BR.md, each with 19 sections in the same order.

- The header says what the release is, and that CONFIG_VERSION stays at 26 and the security version at 1.
- Each of the 35 pull requests merged since v2.11.1 (#260 to #299) is named in a section, and the headings of the existing sections carry
  their numbers now. New sections: the link inside the wait for the time (#269, #271); the reply that lost its framing (#189, by #272 and
  #273); the plain client on the TLS port (#274, by #277); the injected touch (#280); the SIMUT Wire and the network as a driver
  (#260-#265, #267, #268, #275, #276); Under the hood; Flash; The bench, a placeholder the gate's docs change fills in; Upgrading; and
  Known, item by item against v2.11.1's: out with the configurator for the Pico 2 W (#260), #189 and the alpha's empty event names (#286);
  in with the six the maintainer listed.
- The #294 bullet that said "139 B of OTA headroom left of 763" says it was that day's room and that #297 gave it back:
  check_flash_budget.py says 3,427 B for pico_w_test today.
- Measured here, 2026-10-28: Flash against the published v2.11.1 (release +2,776 B, alpha 0, Air +4,096, Pico 2 W +2,824), 734 host test
  cases in 11 suites, 62 routes (52 gated, 10 public by design).

Proof: chore/check_changelog.py (scratchpad) finds 35 of 35 in both entries, the same PR numbers in the headings of both, the same last five
sections, no Unreleased heading and no placeholder; its three negative controls (a PR number removed, a heading removed, an Unreleased
heading put back) give exactly one finding each. The digit runs of the two entries are identical once the locale's separators are removed.
No pictograph in either entry. Not verified on hardware; the entry says which claims are by the code and which were measured, and where.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
The places the number lives, as the v2.11.1 chore found them (git show f1bfd13 --stat): the current-release row and the manual row of the
three READMEs, docs/MANUAL.md and docs/MANUAL.pt-BR.md, docs/README.md, the manual's chapters 1 (the versions, with the v2.12.0 list), 3, 4,
9, 13, 14 and 28 and Appendix C (a "Linha 2.12" with its row), and docs/index.html with its pt and es dictionaries (a new release article,
the oldest dropped, the keys shifted). The panel's 66.5 MHz is written as a setting ("ajustado a"), 24 MHz as the measured before
(bench, 2026-10-15): the clock after the change was measured only on a study build (#278), and the gate checks it.

docs/MANUAL.pt-BR.html is the one deviation from "a generated file is never edited by hand", recorded the way 501b7ac (PR #298) and
6dbea49 (PR #299) recorded it. With pandoc 3.7.0.2, build_manual.py no longer writes class="header|odd|even" on table rows, so a plain
regeneration rewrites 3,081 lines this change has no business with. The diff is the substance only: 56 insertions, 30 deletions.
Checked, not assumed: (i) the committed file with its row classes stripped equals the generator's output for the old sources; (ii) the
committed file equals that output with the classes put back by position (thead rows "header", tbody rows "odd"/"even"), so the new file is
the new output with the same rule; (iii) the file after this change equals the regeneration from the final sources (cmp, a fresh copy of
the tree). python3 tools/build_manual.py --check passes.

Proof, 2026-10-28: git grep -n "2\.11\.1" -- . ':!CHANGELOG*' ':!*.html' leaves 19 files, all history phrasing (the VERIFICATION rows,
"until v2.11.1" and "since v2.11.1" sentences, measurements labelled "release 2.11.1", the Appendix C row, the previous release's list in
chapter 1, a marker fixture of test_ota_sig); in the HTML, the v2.11.1 article of the site and the same history in the manual. A planted
"v2.11.1" in a copy of docs/MANUAL.md is found by the same grep. The 39 steps of the CI gates job (read from build.yml, pip and pio steps
left out) exit 0, among them scan_secrets.sh, check_angulo.py, check_readme_numbers.py (the READMEs quote 734 cases in 11 suites) and
check_flash_budget.py --table. The es-ES text of the new site article is a proposal until a native speaker reviews it.
Not verified on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
…log, one claim made honest)

No file under src/ changes (git diff --stat HEAD~1 -- src is empty).

- CHANGELOG.md and CHANGELOG.pt-BR.md, the same 19 sections in both:
  - #262's display fix, which every image with the TFT panel carries: a top panel pinned to a slot with no running sensor drew its name
    from heap bytes (104077d). It falls back to "Empty / Inactive" now, as the bottom panel does, and the DisplayManager constructor
    writes the fields it skipped.
  - #272's additions to the API and the console: metr.cgw in /api/status, simut_web_aborts_total{cause="short_write"} in /metrics and the
    short write among the send aborts of show net status; on the bench, 4 cuts in 5,000 replies were counted one for one.
  - #263: the Wire's emergency console has ip and dns (the Wire is not published).
  - The header says the panel's SPI "is set to" 66.5 MHz: only the 24 MHz before was measured on the release image; the after came from
    study builds (#278). The manual's cap02 and cap30 say "ajustado a 66,5 MHz" too.
  - Upgrading says what a Pico W shows after the apply: it keeps only /config/system.bin, /lang is gone, and the device is in English until the
    packs are back and it has rebooted (the comment #287 put in release-ota.yml). A Pico 2 W keeps its packs, so the labels added in this
    release read in English until the two new packs replace them.
  - pt-BR: "O console", not "A console", and "o Air" throughout.
- docs/MANUAL.pt-BR.html by the hunk method of #298 and #299: 2 insertions, 2 deletions. The committed file with its row classes stripped
  equals the generator's output for the old sources; the committed file equals that output with the classes put back by position; the new
  file is the new output with the same rule.
- docs/index.html (en, pt, es): the new release article set "24 of 2,000" against "0 of 6,000", a night baseline against runs at other hours,
  and the changelog itself says the rate moved with the hour. It cites the A/B of #273 on the same Pico W and network instead: 3 of 1,000
  replies cut with #272 alone, 0 of 1,000 with #273.

Proof, 2026-10-28: tools/check_*.py of the CI gates job (39 steps) exit 0, among them check_angulo.py and check_readme_numbers.py (734 cases in
11 suites); scan_secrets.sh is clean; build_manual.py --check passes. The release scratchpad's check_changelog.py finds 35 of 35 in both
entries, the same sections, identical digit runs, and its three negative controls give exactly one finding each.
Not verified on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
@github-actions github-actions Bot added documentation Improvements or additions to documentation code Firmware source changes (src/, WebUI.h) labels Oct 29, 2026
@angeloINTJ
angeloINTJ merged commit 229d5d5 into main Oct 29, 2026
13 checks passed
angeloINTJ pushed a commit that referenced this pull request Oct 30, 2026
First parent: 22d83de. What lands from main (c902894..cf97b95): #299 (96a5af0), the Files page says a refused upload was refused: doUpload( ) in WebUI.h, fil_up_refused in both packs' @webdict, tools/test_webui_upload_result.py;
#300 (229d5d5), chore v2.12.0: the version in src/SystemDefs_Limits.h, both CHANGELOGs, the READMEs, the manual and the site; #301 (e776822), the release gate's docs; #302 (cf97b95), the savings floor without the linker's alignment fill:
tools/measure_savings.py (flash_fill( ), FLASH_TOL = 8), tools/test_measure_savings.py (45 cases), tools/feature_savings.json re-recorded (16 floors), AGENTS.md, tools/README.md, docs/analysis/ECONOMIA_DE_RECURSOS.md, a step's comment and name in build.yml.

Conflicts (7), as resolved
- data/lang/language_pt-BR.lng, data/lang/language_es-ES.lng (one line each, the @webdict): the union by key against the base (c902894's): 557 keys = the branch's 556 (538 + the 18 thm_) + main's fil_up_refused, put after fil_up_err where main put it;
  every other line the branch's (main changed that line only). check_lang_packs.py OK, pt-BR 52,706 B and es-ES 54,727 B of the 65,536 B file ceiling.
- docs/analysis/PLANO_STABLE.md 3.1: the branch's table (ten rows, the two Angulo images included); every row is what tools_s10c/budget_set.py writes from this merge's builds.
- tools/flash_budget.json (five hunks): the branch's file; measured_on = the branch's string + the 633 characters main appended to the base's (measured_at_commit: main appended none) + this merge's sentence; every number from the builds
  (budget_set.py for ten images; pico_w_test_https is unchanged, 1,029,188 B used = its budget).
- tools/features.toml (one comment): the pico_w_test headroom with both figures, main's 3.427 B on cf97b95 (3.539 B before the refused-upload message) and this tree's 3.267 B, which PLANO_STABLE's row now says.
- tools/README.md: the branch's rows; measure_savings.py = the branch's row with main's "Desde 30/10" sentence in the place of its "Desde 22/10" one, and the date; test_measure_savings.py = main's row (the branch left it as the base).
- tools/test_measure_savings.py: main's rewrite with the branch's cases kept inside it: the SIMUT_SAVINGS_PIO block after main's tagged [g3] line in pio_run_cases( ), flips_cases( ) beside table_cases( ), and main( ) runs it last.
  21 cases at the base, 42 on the branch, 45 on main, 66 merged = 45 + (42 - 21): none dropped, none duplicated.
Merged clean: WebUI.h, tools/measure_savings.py (below), src/SystemDefs_Limits.h, AGENTS.md, build.yml, both CHANGELOGs, the READMEs, the manual, ECONOMIA_DE_RECURSOS.md, tools/feature_savings.json.

tools/measure_savings.py, how the two halves meet (the merged file read whole)
- Main's half (201 changed lines) is the floor: flash_fill( ), FLASH_TOL, pio_build( ) returning 'fill', gate_flash( ) = flash - fill, measure( ) keeping fill_base/fill_off, print_table( ), cmd_check( ) letting a drop of FLASH_TOL pass.
  The branch's half (98 changed lines) is the matrix: the ui=<id> flips (CHOICES, flip_key, cost_env, flips_of, plan_flip), env_text( ) naming the Angulo's static-RAM budget, the 13th check, SIMUT_SAVINGS_PIO.
  The matrix builds through pio_build_full( ), which keeps the whole `used` (the slot holds the fill), so the halves share no function body: the changed lines of the merged file against main's are exactly the branch's 98, and against
  the branch's exactly main's 201 (compared as sets of added and removed lines: identical both ways). They meet in pio_run( ): pio_build( ) asks for the map through it and the branch picks the pio binary in it; the suite holds both in pio_run_cases( ).
  --check/--update never take the ui= branch, --matrix/--checks never read 'fill'; no code that uses bank_pad, a 'pad' key or BANK_SECTION is left in tools/ (the label of main's case [e5] still says "it was 0 for bank_pad", as history).
- test_measure_savings.py 66/66. Main's 15 mutations (sav_20261029/mutate.py) on the merged file: each caught by exactly the cases its table names (m1..m15 "== expected"). Its harness copies only tools/, and the branch's flips_cases( ) read src/ and the manifest,
  so the unmutated copy crashed (rc=1) after main's cases; stages/sync8/scripts/mutate_merged.py links the rest of the tree beside the copy and the unmutated copy is rc=0, none failed.

The eleven images (from scratch, the shim first in PATH, no PLATFORMIO_*, under flock; the baseline is the same eleven at 22d83de built first the same way: sha256-equal to sync 7's in all eleven, the instrument's control)
  image                 22d83de -> merge        main's pair (c902894 -> cf97b95)
  pico_w_release        1,035,956 -> 1,036,060  +104   +112
  pico_w_test           1,036,772 -> 1,036,876  +104   +112
  pico_w_test_https     1,041,220 -> 1,041,220     0      0 (other sha256)
  pico_w_asserts        1,038,260 -> 1,038,364  +104   +112
  pico_w_alpha            990,380 ->   990,380     0      0 (other sha256)
  pico_w_air            1,035,340 -> 1,035,340     0      0 (other sha256)
  pico2_w_release         999,936 -> 1,000,048  +112   +112
  pico_wire               604,312 ->   604,424  +112   +112
  pico_wiretft            787,664 ->   787,776  +112   +112
  pico_wiretft_angulo     827,504 ->   827,616  +112   (not a main image: like pico_wiretft)
  pico2_w_angulo        1,036,752 -> 1,036,864  +112   (not a main image: like pico2_w_release)
Decoded (nm -S, objdump -h, stages/sync8/decode): in every image that carries the Files page exactly two symbols move, WebUI_GZ::FILE_PAGE_GZ (+109..+112 B, #299) and WebUI_GZ::LOGIN_PAGE_GZ (+1 B, the version string of #300), and only .rodata changes.
The three Pico W images that grow 104 B where main's grow 112 B (pico_w_release, pico_w_test, pico_w_asserts) are the same two symbols: .rodata = the symbols (+110 B) + the bytes no symbol covers (-6 B here, +2 B on main's tree), counted per ELF
(pico_w_release 414,288 -> 414,392 = +110 - 6; main's 414,296 -> 414,408 = +110 + 2; pico2_w_release +113 - 1 here, +111 + 1 on main's): the alignment of the entries behind the page blobs, not a symbol. pico_w_alpha and pico_w_air carry the page too (the same two symbols grow, +109 and +1 B, and no section changes size: the bytes no symbol covers give back what they take) and pico_w_test_https has no Files page (LOGIN_PAGE_GZ +1 B, no section
changes size): the three keep their size with another sha256, as main's do.

The savings floors (tools/measure_savings.py --check, SIMUT_SAVINGS_PIO = the shim's pio): 15 of 16 hold. Measured - floor in flash: 0 for twelve, web_history -6 (inside FLASH_TOL), tel_mqtt +2, syslog +3; RAM +0 for all.
tft_graph saves 20,365 B against its floor of 20,433 B: 68 B below, 60 past the tolerance. It is not this merge: the same tool on main's tree (cf97b95) gives 20,433 B (main's floor, to the byte), and on 22d83de, before the merge, 20,365 B (stages/sync8/tg_*.txt).
It is the branch's UI-as-driver code in the release images: what the switch takes out of DisplayManager::handleTouch( ) is 1,804 B here against 1,832 B on main and out of DisplayManager::loopCore1( ) 436 B against 484 B (-28, -48), and a few
compiler switch tables differ (CSWTCH.*, 116 B on main's side). feature_savings.json is NOT updated here: the floor is main's number and the nightly runs on main; the --update, with this explanation, belongs to the merge that takes the branch into main.
A first run of the floors let PlatformIO's library installer start a git clone of one of the project's own libraries for a temporary environment (network, which this step does not allow): it was interrupted (SIGINT; TempEnvs put profiles.ini back, tools/generated
unchanged) and the run was repeated with GIT_ALLOW_PROTOCOL=file and the proxies on a closed port. The figures above are from the repeat.

Proofs (host only; records in stages/sync8)
- check_flash_budget.py <env> <log> right after each build: eight over their budgets by 104..112 B before budget_set.py (pico_w_alpha, pico_w_air and pico_w_test_https at 0), exit 0 for all eleven after it (run on the saved images: the floors had
  rewritten .pio/build by then); --table OK (11 images, 1 exempt); test_flash_budget.py 24 ok; gen_features.py --check; check_readme_numbers.py (772 cases in 11 suites, unchanged: main adds no native case); git diff --check; no conflict marker anywhere.
  pico_w_test is 3,267 B and pico_w_asserts 1,779 B under the OTA ceiling (the gate's WARN: under one sector, as before).
- tools/test_webui_upload_result.py PASS and tools/test_webui_theme_names.py PASS with the union in both packs. V-PIX: 951 of 951 frames equal to the records, six contact sheets and base en/es equal, its control bites. tsv_check.py: 394 rows, 0 findings.
  tools/tft_ui tests: 8 files, 0 fail. check_lang_packs.py, check_ui_texts.py (0 findings): clean.
- ci_gates_local.py --raso (the shim first in PATH, a depth-1 clone with the working tree laid over it): 68 of 69 steps passed; 1 skipped (the AddressSanitizer/UBSan host suites: they need PLATFORMIO_BUILD_FLAGS, CI only);
  the one failure is the fuzz step (clang++ cannot load libclang-cpp.so.18.1 on this host, as in the sync 6 and 7 records). The eleven native suites: 366 + 62 + 34 + 14 + 59 + 54 + 17 + 13 + 51 + 19 + 83 = 772 cases, all succeeded.

The host proofs above (V-PIX, tsv_check, the tft_ui tests, the packs, check_ui_texts, the authorization matrix) ran on the tree
before budget_set.py rewrote PLANO_STABLE section 3.1, flash_budget.json and two comment lines of features.toml, files none of them
reads. The review re-ran tsv_check (394 rows, 0 findings), check_ui_texts, check_lang_packs, the two web tests,
check_readme_numbers (772 cases), test_flash_budget (24 ok), check_flash_budget.py --table and gen_features.py --check on the
committed tree: all pass. Floor run: tft_graph saves 20,365 B here against main's floor of 20,433 B; the tree before the merge
(22d83de) gives the same 20,365 B and main's tree 20,433 B. That is the branch's UI-as-driver code in six DisplayManager symbols,
not the merge. It is left for the branch's merge into main; feature_savings.json is main's, unchanged.

Not verified on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code Firmware source changes (src/, WebUI.h) documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant