Skip to content

fix: on the Ângulo, GET /api/keypad sends the faces and points to /api/ui, not the original's geometry (F4) - #311

Merged
angeloINTJ merged 9 commits into
mainfrom
fix/keypad-route-angulo
Oct 31, 2026
Merged

angeloINTJ merged 9 commits into
mainfrom
fix/keypad-route-angulo

Conversation

@angeloINTJ

Copy link
Copy Markdown
Owner

Closes F4 of v2.13.0's Known list: on the Ângulo, GET /api/keypad no longer describes the original panel's keypad. It sends the faces, the state and the PIN policy, and names /api/ui as the route that lists where the Ângulo's keys are. The two Ângulo images get 208 and 216 B smaller, and the other nine are byte for byte what they were.

The defect

On an image built with the Ângulo, WebManager::handleApiKeypad( ) published the original panel's keypad geometry. A client that taps by those numbers misses the Ângulo's keys.

field depends on the panel?
faces, up, kb, policy no: they are core state, and the Ângulo's key k is the original's key k
grid[0..3]: keys, slots, cols, rows no
grid[4..9]: x0, y0, w, h, pitch yes. With 4 cards the original's keys are 152 × 44 at (6, 72), pitch 156/50. The Ângulo's are 146 × 44 at (12, 96), pitch 150/48
pop (groups keyboard only) yes. Its one key height cannot describe the Ângulo's popup: 48 px wide, 52 tall on one row and 36 on two

Nothing that ships reads the route: WebUI.h does not, and the Ângulo's bench script tools/tft_ui/roteiro_ui.py reads only faces and kb and taps by /api/ui. The two bench tools that drive the original panel did read it.

The change

The maintainer chose on 2026-10-31 among options (a) to (d) from the plan. His choice:

  • The route, option (c). On the Ângulo it sends faces, up, kb, policy and "targets":"/api/ui", with no grid and no pop. On the original panel the answer is unchanged.
  • The tools refuse an Ângulo answer, with a message that says where to tap instead.

How the route is switched:

  • A UI hook, not an #if. The hook is uiKeypadTargets( ):
    • the original answers nullptr, in src/ui/UiDriverMembers.h;
    • the Ângulo answers "/api/ui", defined inside the class in src/ui/angulo/Members.h.
  • The handler. It builds the Ângulo's answer when the hook answers. Otherwise today's statements run, textually unchanged.
  • Each side is a constant, so the branch an image does not take is not in the image. The Ângulo images carry neither the grid's format string nor the popup's. The original images with the route carry no "targets".
  • The sprawl count stays at 368. tools/ui_texts_allow.json names the route literal in Members.h as a route path, not text the panel draws.

The tools:

  • tools/keypad_answer.py is one reader for tools/panel_users_hw_test.py and tools/panel_fulltable_test.py. It refuses an answer that has targets, and its message names /api/ui and roteiro_ui.py. An answer with neither grid nor targets, as a device older than v25 sends, still gets the v24 layout.
  • Rig checks once at its start, right after the web login:
    • a run stops before it makes the test accounts or re-points the telemetry;
    • before it stops, it deletes the login's own account, smap.
  • The keypad read inside a run refuses the same way. It deletes smap too, because this tool's own cleanup is a step that a failure skips.
  • Documents: the CHANGELOG (English and Portuguese), the manual's REST chapter and the regenerated docs/MANUAL.pt-BR.html, AGENTS.md, docs/AUTHORIZATION.md, docs/MANUAL*.md, docs/analysis/UI_DRIVERS.md, tools/README.md and CLAUDE.md's script count.
    • The script count goes from 211 to 214: the two new files, plus one script main already had but miscounted. Its "Called by CI" heading said 73 over 74 rows.

Tests first

  • tools/test_keypad_answer.py, in CI's gates job. It has four cases: an original answer, an Ângulo answer (refused), an answer older than v25, and the groups keyboard with its popup. It also checks two things in the tools' source:
    • each tool reads the route only through the reader;
    • each of the tool's two refusals deletes the web login's account before it stops.
  • Red first: it was red before the reader existed, because the module was missing (eb192b74).
  • The account check was red before each of its two fixes (both in 5b4d8001) and is green after. Run with serial and requests stubbed:
    • on an Ângulo answer, the real refuse_angulo( ) and keypad_faces( ) send user del smap before they stop;
    • on an original answer they send nothing.
  • Mutation controls: with the refusal taken out, exactly the Ângulo case fails. Each of the other three lines the test holds fails exactly the cases it serves.
  • The images' bytes, by a script outside the repository. On 6981fd93 it fails 6 checks, all on the two Ângulo images: no ,"targets":", and the "pop":[%d and "grid":[ format strings still there. On this branch all 49 of its checks pass. With the two hooks' values swapped, it fails again.

Images

All eleven built from zero on both sides through the pio shim. A reviewer rebuilt all eleven independently and got the same sha256 and sizes.

Image 6981fd93 .bin this branch .bin Δ
pico2_w_release 1,038,352 1,038,144 −208
pico_wiretft_angulo 829,200 828,984 −216
the other nine — — 0, same sha256

Signed, as the CHANGELOG gives them (+241 B): 1,038,385 and 829,225. handleApiKeypad goes from 508 to 348 B on the Pico 2 W. The two budget entries in tools/flash_budget.json and their rows in docs/analysis/PLANO_STABLE.md §3.1 come down in this change, and check_flash_budget.py --table agrees.

Gates

  • ci_gates_local.py --raso at the tip: 76 of 77 steps passed at 5b4d8001. The fuzz step fails here only because this machine has no libclang, and the AddressSanitizer step runs only in CI.

  • Also passed:

    • check_authz.py, with a --list identical to main's;
    • check_readme_numbers.py;
    • check_feature_sprawl.py at 368;
    • check_ui_boundary.py with 0 findings, and test_ui_boundary.py 316 of 316;
    • check_ui_texts.py with 0 findings;
    • check_ui_link.py with 0 findings on both Ângulo ELFs.
  • The regenerated HTML. tools/build_manual.py is not run by CI, and this machine's pandoc drops the table row classes. A post-step restores them:

    • on main first: the generator's output plus the step equals the committed file byte for byte, sha256 ce4ef77f;
    • on this branch: the same holds for the committed file, sha256 0acd0772.

    The HTML's diff is 2 hunks, only the changed text.

Review

  • First pass, COM RESSALVAS. The reviewer rebuilt all eleven images and found the firmware change correct. It found one important defect, in a bench tool: Rig.__init__( ) makes the web login's full-admin account before the Ângulo check, and the refusal's SystemExit came before the cleanup, so a refused run left smap on the device.
  • Re-check of the fix: APROVADO, with one remnant: the keypad read inside the run had the same gap. This tool deletes smap only in a step that a failure skips, and its finally does not delete it. Both are fixed in 5b4d8001, test first: the check now reads every handler of the Ângulo's answer in the tool.
  • Not repeated: the reviewer did not re-run the firmware build after the fix, because that commit touches no firmware input.

Not run on a board

No device was contacted. The answers are proved from the images' bytes and by the host tests. The bench check needs the maintainer's word to flash the Pico 2 W:

  1. Before flashing, with the keypad on the glass, the current answer must still carry grid, and keypad_answer.py must accept it.
  2. After flashing this branch's pico2_w_release, open the keypad by /api/ui's targets:
    • the answer must carry targets = /api/ui and no grid or pop;
    • a PIN typed by /api/ui's targets must reach Settings;
    • the reader must refuse the new answer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu

Ângelo Moisés Alves and others added 9 commits October 31, 2026 18:03
…(F4)

F4 of v2.13.0's Known list: on the Ângulo, GET /api/keypad publishes the
original panel's grid and popup geometry, so a client that taps by it
misses the Ângulo's keys. The maintainer chose option (c) on 2026-10-31:
on the Ângulo the route sends faces, up, kb and policy plus
"targets":"/api/ui", and no grid and no pop; on the original it stays as
it is, byte for byte. The two bench tools that tap the original panel by
the grid are to refuse an Ângulo answer through one shared parser.

This commit is the test, written first. tools/test_keypad_answer.py reads
tools/keypad_answer.py, which does not exist yet, so on this commit the
test is red:

    $ python3 tools/test_keypad_answer.py
    test_keypad_answer: FAILED, tools/keypad_answer.py cannot be imported
    (No module named 'keypad_answer')            exit 1

It has four cases (an original answer, an Ângulo answer, an answer older
than v25, the groups keyboard with its pop), two checks that the tools
read the route only through the parser, and four controls: the parser run
again with one line taken out must fail exactly the cases that line
serves (the refusal out: the Ângulo case alone). It is added to the gates
job of the workflow next to test_ui_caps.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
…ls; it refuses the Ângulo's answer (F4)

panel_users_hw_test.py and panel_fulltable_test.py tap the original panel
by the grid that GET /api/keypad publishes. The Ângulo's answer will not
carry one (option (c): faces, up, kb, policy and "targets":"/api/ui"), so
both tools now read the route through tools/keypad_answer.py, which
refuses an answer with "targets" and says where to tap instead (the
targets of /api/ui, as tools/tft_ui/roteiro_ui.py does).

- keypad_answer.parse( ) holds what Rig.keypad_faces( ) did inline (faces,
  kb, grid, pop, policy, with the same shape checks) and the v24 layout
  (PIN_GRID_FALLBACK, moved here and imported back, because
  panel_fulltable_test.py takes it from panel_users_hw_test.py). An answer
  with no grid and no targets is a device older than v25, and keeps the v24
  layout: the refusal comes from "targets", not from the missing grid.
- The refusal is a SystemExit and not a KeypadGone: the callers reopen the
  keypad and read again on a KeypadGone, and the device would answer the
  same.
- Rig also checks once at its start (refuse_angulo( )), so that the run
  stops before it makes accounts and re-points the telemetry. A route that
  cannot be read is said and passed over, not taken for the Ângulo.
- panel_fulltable_test.py reads the policy it prints through the parser; it
  prints the same list as before.

Evidence (no device was contacted):
- T3, red on the commit before this one: the module did not exist
  ("cannot be imported", exit 1). Now:
    $ python3 tools/test_keypad_answer.py -v
      case original  ok / case angulo ok / case pre-v25 ok / case groups ok
      control "the refusal taken out": fails ['angulo'], the line serves ['angulo']
      control "the v24 fallback taken out": fails ['pre-v25'] (serves ['pre-v25'])
      control "pop taken out": fails ['groups'] (serves ['groups'])
      control "grid ignored": fails ['groups', 'original'] (serves the same)
    4 cases pass; 4 of 4 controls bite; 2 of 2 tools read the route through the parser.
- Negative control on the file: with the refusal's raise replaced by pass
  in a copy of tools/, exactly one case fails (angulo: "an answer with
  `targets` was accepted"), the other three pass; the real file passes.
- A/B of Rig.keypad_faces( ), the version of 6981fd9 against this one, on
  15 answers that are not the Ângulo's (4, 6 and 12 cards, the numeric pad
  with its two empty keys, the groups keyboard with pop, an answer older
  than v25, keypad not up, grids, pops and policies cut short, kb missing
  or empty, faces null or of the wrong count): the same return or
  exception, and the same kb, pop, grid and policy on all 15. On the
  Ângulo's two answers the old code fell back to the v24 grid and went on;
  the new one stops with the message.
- tools/README.md: two rows (test_keypad_answer.py, keypad_answer.py) and
  the counts, measured: the "Called by CI" heading already said 73 for 74
  rows at 6981fd9, so the total was 212 and not 211; it is 214 now (75, 17,
  64, 58), here and in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
…ts keys, not the original's geometry (F4)

F4 of v2.13.0's Known list: on an image built with the Ângulo, the route
published the original panel's grid and popup geometry, so a client that
taps by it misses the Ângulo's keys (for four cards the original's are
152x44 at 6,72, pitch 156/50, and the Ângulo's 146x44 at 12,96, pitch
150/48; its popup keys are 48 px wide, 52 tall on one row and 36 on two,
where the original's are 56x52).

The maintainer's option (c), 2026-10-31. On the Ângulo the answer is
{"faces":[...],"up":...,"kb":"...","policy":[...],"targets":"/api/ui"}:
the core's state, which means the same on both panels (the Ângulo's key k
is PinKb's key k), and the route that lists where the keys are; no grid,
no pop. On the original the statements are the ones that were, untouched.

- The switch is a UI hook, not an #if: uiKeypadTargets( ) is nullptr on
  the original (UI_HOOK, always_inline, src/ui/UiDriverMembers.h, beside
  uiLangLoaded( )) and "/api/ui" on the Ângulo (defined in the class,
  src/ui/angulo/Members.h, like uaSujo( ), so the handler sees a constant).
  The comment of src/ui/UiDriver.h that said the Ângulo's hooks are
  ordinary functions gains the clause for this one.
- handleApiKeypad( ) builds the Ângulo's answer when the hook answers,
  and returns; otherwise the grid and pop statements run, textually
  unchanged. No #if was added: tools/feature_sprawl.json is unchanged and
  check_feature_sprawl.py passes at the same count.
- tools/ui_texts_allow.json: "/api/ui" in Members.h is a route path and
  not text the panel draws (group 6, the bench endpoint's route). Without
  the entry check_ui_texts.py reports exactly "src/ui/angulo/Members.h:17:
  "/api/ui"" (run on a copy), with it 0 findings.
- Comments that described the route are corrected: the handler's own, and
  those of PinKeypad.h, ui/original/PinKeypadLayout.h (two places) and
  DisplayManager.h (pinGrid( )). The comment at DisplayManager.h:1185
  (the CLI and /api/keypad read the faces through pinKeyFace( )) is still
  true on both panels and stays.

Proof, all eleven images built from zero on this tree and on 6981fd9
through the pio shim, one at a time under the build lock, -Werror holding,
each held to tools/check_flash_budget.py before the budget edit that follows:
- T1, the bytes of each firmware.bin (stage script, out/t1_bin_grep.py).
  Red on the base: six failures, all in the Ângulo section (no
  ',"targets":"', and the "pop":[%d and "grid":[ format strings still
  there, on both Ângulo images). Green on this tree: 49 of 49 checks. Its
  control (an Ângulo image read under an original's name) fails exactly the
  six checks of the two swapped images.
  The briefing said the nine others must contain the pop string. Three of
  them cannot: pico_w_alpha, pico_w_air and pico_wire have no TFT, so
  handleApiKeypad( ) is not compiled in them (0 occurrences of the route
  on the base). T1 asks of those three that neither answer is there, and of
  the six with the route (pico_w_release, _test, _test_https, _asserts,
  pico_wiretft, pico2_w_original) that the pop and grid format strings are
  there once and "targets" is not.
- The nine images that are not the Ângulo's: sha256, size and name equal to
  the base's, `diff` of the two lists empty (a39bf356, 2a01445f, dbc57bfe,
  a490b838, bf5381ff, 0fe6a215, 480268b8, d6cdd1f0, 3f3ef7aa). No __LINE__
  is used in src/, and the line count of WebManager_History.cpp changed.
- The Ângulo's two images (the sha256 changes, the .bin shrinks):
    pico2_w_release      1,038,352 -> 1,038,144 B  (-208; used 1,038,328 -> 1,038,120)
    pico_wiretft_angulo    829,200 ->   828,984 B  (-216; used   817,164 ->   816,948)
  handleApiKeypad( ) goes from 508 to 348 B (24 + a 324 B .part.0) on the
  Pico 2 W and from 488 to 326 B on the Wire; the strings, -52 B (the grid
  and policy format 82, the pop format 27 and the closing "}" 2 are out,
  the new format 59 is in; "/api/ui" is the literal the route registration
  already holds, merged by the linker, 0 B). The rest is alignment.
- check_authz.py exit 0 and its --list identical to the base's;
  check_readme_numbers.py exit 0; check_ui_boundary.py 0 findings;
  test_ui_boundary.py 316 of 316; check_ui_texts.py 0 findings;
  check_ui_link.py 0 findings and 12 of 12 allowances on both new ELFs;
  test_keypad_answer.py green.
Not run on a board and no device was contacted: the answer was proved by
the images' bytes and by the host tests. The budget, the documents and the
generated manual follow in their own commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
pico2_w_release and pico_wiretft_angulo shrink with the F4 change (the
original panel's grid and popup format strings and the code that wrote
them are out of the Ângulo's images). The budget is a high-water mark that
comes down in the change that lowers it:

  pico2_w_release       used 1,038,328 -> 1,038,120 (-208)   .bin 1,038,352 -> 1,038,144
  pico_wiretft_angulo   used   817,164 ->   816,948 (-216)   .bin   829,200 ->   828,984

The nine other records stay: those images have the sha256 of 6981fd9's.
The OTA headroom table of PLANO_STABLE.md section 3.1 follows the .bin
records (signed, + 241 B): pico2_w_release 1.038.385 B with 530.383 B of
slot left (530.175 before), pico_wiretft_angulo 829.225 B with 211.159 B
under the OTA ceiling (210.943 before).

All eleven built from zero on the tip of the F4 change (the baseline 6981fd9
first, equal to every record), each held to the gate on its own build log
with its .bin present:

    $ python3 tools/check_flash_budget.py <env> out/tip/<env>.log     (eleven times)
    rc=0 for all eleven (OK for eight, WARN for pico_w_release, pico_w_test and
    pico_w_asserts as before: each under one flash sector of the OTA ceiling,
    3,731, 2,947 and 1,427 B; SKIP for the exempt pico_w_test_https)
    $ python3 tools/check_flash_budget.py --table
    OK the OTA headroom table matches the record (11 images, 1 exempt).

Not run on a board.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
Each hit of `grep -n "api/keypad"` was read against the code (the handler
in WebManager_History.cpp, uiKeypadTargets( ) in the two driver headers).
Changed, because they described the grid and the popup as the route's
answer on every image:

- AGENTS.md (the bench manual, Portuguese): the grid and policy are read
  from the route "never from constants", and that holds for the original
  panel; on the Ângulo images the answer has no grid and no pop and names
  /api/ui, and the two original-panel tools refuse it (keypad_answer.py).
  The pop line says it is the original's.
- docs/AUTHORIZATION.md: same gate (PERM_SYS_CONFIG) on both panels; what
  the answer carries differs.
- docs/MANUAL.md and docs/MANUAL.pt-BR.md, the route table row: grid and
  pop on the original, "targets":"/api/ui" in their place on the Ângulo.
- docs/_manual/cap26-api-rest.md, the source of the generated manual: the
  route table row, and the example. The example was not an answer the
  firmware writes (kb "num" with four faces and a grid that is no layout of
  PinKb); it is now the factory four-card answer, followed by the Ângulo's,
  with a paragraph on each field and on which to read. The generated HTML
  follows in its own commit.
- docs/analysis/UI_DRIVERS.md (Living): the three places that called F4
  open (the S9 list, S10a's F4 and S10b-1's F4) say it is closed, with the
  maintainer's option (c), the hook and the result; the two places that said
  the route publishes the geometry (the PinKeypad.h row of section 4.2 and
  the S9 audit bullet) say "on the original panel's images only".

Read and left as they are: cap26 line 537 (the route's one-line summary,
"the faces of the PIN keypad", true on both panels), A3's record in
UI_DRIVERS.md (what was tried on 2026-10-30, with the route as it was),
MODELO_DE_RECURSOS.md (the route is a bench switch that only the TFT images
carry, 368 B, still true), tools/route_sweep.py and tools/build_webui_gz.py
(they say the route is left out of the sweep and registered by the TFT
images only), the historical entries of both CHANGELOGs and the strings of
tools/flash_budget.json. The v2.13.0 Known list keeps F4: it is the record
of what that release shipped; the Unreleased section closes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
…13.0's Known list, with its flash table

A section on GET /api/keypad on the Ângulo after the Flash section of F3
and the alignment, and a sentence in the Unreleased summary. It says what
was wrong (the original's grid and popup on the Ângulo's images), what the
route sends now (the maintainer's option (c)), how it is switched (the
uiKeypadTargets( ) hook, a constant on each side, no #if), the bench
tools, the tests, and that nothing was run on a board.

The flash table follows the table above it: the signed firmware.bin (the
image plus its 241-byte signature), pico_w_test_https unsigned, against
main at 6981fd9, all eleven from zero. The same numbers as the build's
.bin, without the signature, are in the text:

  pico2_w_release       1,038,593 -> 1,038,385 (-208)   .bin 1,038,352 -> 1,038,144
  pico_wiretft_angulo     829,441 ->   829,225 (-216)   .bin   829,200 ->   828,984
  the other nine         unchanged, sha256 equal

The slack moves only on the Ângulo's images: 530,383 B left in the Pico 2 W's
slot (530,175 before) and 211,159 B under the OTA ceiling on the Wire's
Ângulo (210,943). Every figure was read from the build outputs of the tip
(out/tip) and from check_flash_budget.py's own lines, not computed twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
tools/build_manual.py renders docs/_manual/ into the page. This machine's
pandoc (3.7.0.2) no longer writes the table row classes the committed page
has (header, odd, even), so the page was made in three steps:

1. main (6981fd9) regenerated in a clean export of the tree and compared
   with its committed page: they differ by 3,240 lines and by nothing else.
   Every differing line is a <tr class="header|odd|even"> against a bare
   <tr> (the committed page, with those classes taken out, is the
   regenerated page, byte for byte).
2. A post-step puts the classes back by pandoc's old rule (header on the
   rows of the <thead>, odd and even alternating from odd on each <tbody>,
   on the <tr> that stand alone on a line; build_manual.py's own two
   tables are one line of compact HTML and never had them). Proof on main:
   regenerated + post-step is the committed page, cmp equal, sha256
   ce4ef77f861af0b5e24164d13bcd3706b4c5d1ded5c0396731612931339be051.
3. This tree (the commit before this one) regenerated the same way. The
   page differs from main's in 16 diff lines: the route's row in the
   chapter 26 table, and the example of GET /api/keypad with its
   paragraph and the Ângulo's example and paragraph. Nothing else moved.

python3 tools/build_manual.py --check passes (it is what CI runs). The
post-step is a stage script and is not in the repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
The contract of section 2.2 lists the driver's hooks. The new one is a row:
not a body that moved but a constant on each side (nullptr on the original,
"/api/ui" on the Ângulo), public because its caller is the web handler, and
the reason it is defined in the Ângulo's class. The sentence above the table
said all the hooks are private members; it now names the exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy7K2mzEmmjBnpxpdYNDFu
…top (review of F4)

Rig.__init__( ) runs _login( ), which makes this run's full-admin account
smap over the console, and only then refuse_angulo( ). On an Ângulo
answer the SystemExit came before main( )'s try, so a refused run left
the account on the device: the failure that _login( )'s docstring tells
of. The review found it by running the real __init__ with serial,
requests and the console stubbed.

The keypad read inside the run had the same gap, which the re-review
found: this tool deletes smap only in step_cleanup( ), a step that is
skipped once an earlier one raises, and main( )'s finally only writes
the results and closes the port. (panel_fulltable_test.py's finally does
delete it.) Both handlers of AnguloAnswer now send `user del smap`
before they raise.

Test first: tools/test_keypad_answer.py reads every handler of
AnguloAnswer in panel_users_hw_test.py from the source (the tool imports
serial and requests, so it is read the way the wiring is). It fails
unless there are both and each calls `self.cfg('user del smap')` before
its raise. It was red on refuse_angulo( ) before the first fix and on
the keypad read (line 500) before the second, and is green after.

Run with stubs, the real methods send ['user del smap'] and stop on an
Ângulo answer. On an original answer they send nothing; refuse_angulo( )
returns, and keypad_faces( ) returns the faces.

The CHANGELOG, in both languages, now says the check comes right after
the web login and deletes that login's account, and its tests bullet
names the new check.

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 display Display, TFT, touchscreen, and themes ci Continuous integration and automation tools Build tools, scripts, and developer tooling code Firmware source changes (src/, WebUI.h) labels Oct 31, 2026
@angeloINTJ
angeloINTJ merged commit 2bead92 into main Oct 31, 2026
15 checks passed
@angeloINTJ
angeloINTJ deleted the fix/keypad-route-angulo branch October 31, 2026 22:04
angeloINTJ added a commit that referenced this pull request Nov 4, 2026
…-11-04

configurator: the costs measured again on main, after #307, #308 and #311 changed the images
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and automation code Firmware source changes (src/, WebUI.h) display Display, TFT, touchscreen, and themes documentation Improvements or additions to documentation tools Build tools, scripts, and developer tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant