diff --git a/README.md b/README.md
index 78b80fa..4758bca 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
+[๐งญ Clickable architecture and data-flow diagrams](docs/SYSTEM-MAPS.md)
+

# PakarController
diff --git a/docs/FILE-COMPARISON.md b/docs/FILE-COMPARISON.md
index a73d350..42b4f16 100644
--- a/docs/FILE-COMPARISON.md
+++ b/docs/FILE-COMPARISON.md
@@ -4,7 +4,7 @@ Whole-repository superiority is **not established**. This report exposes retaine
Routing: 1504 paths have no specialist capability match. They remain visible through `consult-reuse.ps1 -Concern donor-audit` and issue #3; lack of a keyword match is NOT an exclusion. Every CSV row includes capability routing and owning issue links.
-Inventory: 4781 files; 437 complete source-file comparisons; 4344 pending comparisons; 311 compared files with known parity gaps. Header/implementation pairs can describe the same gap. Missing sparse files remain pending.
+Inventory: 4785 files; 441 complete source-file comparisons; 4344 pending comparisons; 311 compared files with known parity gaps. Header/implementation pairs can describe the same gap. Missing sparse files remain pending.
Every inventoried path, source revision/hash, target/proof file and line, omitted code, disposition and concrete reason is in `file-comparison.csv`. Hash-bound manual decisions are in `file-comparison-evidence.json`. Donor SHA-256 values bind original bytes; target/proof SHA-256 values bind UTF-8 without BOM with LF newlines. Anchors are recomputed into exact line numbers. Test-code presence is not test execution; execution receipts are in `FILE-COMPARISON-RECEIPTS.md`.
@@ -18,7 +18,7 @@ Every inventoried path, source revision/hash, target/proof file and line, omitte
| esp32WS2811 | 24 | 16 | 8 | 9 |
| PCController | 1517 | 119 | 1398 | 107 |
| Pealayer | 682 | 66 | 616 | 47 |
-| portable-shell | 23 | 23 | 0 | 0 |
+| portable-shell | 27 | 27 | 0 | 0 |
| PU850-ESP | 109 | 109 | 0 | 96 |
| PuzzleBoard | 22 | 22 | 0 | 1 |
| RayanLamp | 2349 | 29 | 2320 | 19 |
@@ -224,10 +224,10 @@ Verdict: **parity-gap**. Revision: `filesystem-snapshot`. Original-byte SHA-256:
Verdict: **scoped-improvement**. Revision: `filesystem-snapshot`. Original-byte SHA-256: `1fd6ae8518b9de9ca62b4f94391c5615056409ace250755e5dc185ba090ceb15`.
-- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView]`.
+- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView]`.
- Retained: Read the entire loose snippet. VT100 clear/home, bounded row rendering, task inspection intent and interactive command editing are retained by the structured shell TUI rather than pasted into production AVR loop code.
- Omitted/replaced code: `top-level task('x')/cancel/add statements and private TaskItem memory dump`. Disposition: **replaced**. Reason: The loose text is not a compilable owner and reaches private scheduler internals. The shell exposes bounded public properties/actions without arbitrary callback addresses or unsafe pasted globals.
-- Test-code evidence: `tests/shell_tui_test.cpp:24 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=assert(output.size()<=PS_DEVICE_VIEW_FRAME_BYTES)]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:33 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=assert(output.size()<=PS_DEVICE_VIEW_FRAME_BYTES)]`.
- Scoped advantage: A bounded navigable TUI with secret redaction and draft ownership replaces non-compilable debug fragments and raw callback-address dumping.
- Remaining: Actual serial/Telnet multi-session terminal rendering and hardware-control acceptance remain separate from the host model test.
@@ -2889,13 +2889,13 @@ Verdict: **parity-gap**. Revision: `b0111ae0c80dc34149ea1044de7dd5488920301b`. O
Verdict: **parity-gap**. Revision: `b0111ae0c80dc34149ea1044de7dd5488920301b`. Original-byte SHA-256: `ecd399e2bf7718f3eb7b62e49f9dd884c0fb179e3d907bd15b238c01d31501e3`.
- Target: `desktop/src/terminal_ui.rs:478 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=fn window(total: usize, visible: usize, cursor: usize)]`.
-- Target: `firmware/esp/src/portable-shell/portable_device_table.hpp:233 [UTF8-LF sha256=784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2; anchor=// PCController's logical-row/section-line separation, without heap storage.]`.
+- Target: `firmware/esp/src/portable-shell/portable_device_table.hpp:390 [UTF8-LF sha256=37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73; anchor=// Separate logical rows from section headings without heap storage.]`.
- Retained: Actual tableWindow cursor-centred viewport arithmetic is adapted and exhaustively tested across total/visible/cursor cases. Reuses native logical selection/navigation and Unicode column clipping.
- Retained: 2026-10-29: also adapt logical control-row versus section-line mapping into the canonical shared event view. Contiguous categories render once as section headings, selection counts only settings, and viewport follows the selected logical row. This closes the grouping portion of the historical omission below; mouse mapping, complete tone semantics and richer specialized editors remain open.
- Omitted/replaced code: `Donor gocui DataTable groups/control-tone rendering, visibleLine mouse mapping and richer table columns`. Disposition: **pending**. Reason: Only the viewport math is currently integrated; gocui APIs require crossterm adaptation, while generic groups/tones/mouse and richer settings/mapping editors remain pending. No full donor table parity is claimed.
- Test-code evidence: `desktop/src/terminal_ui.rs:1190 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=fn actual_donor_table_window_keeps_cursor_in_resized_viewport()]`.
- Test-code evidence: `tests/terminal_ui_test.mjs:23 [UTF8-LF sha256=3916ae657e18de899b278509cd0f8fad53886579c9af53deebc6e47cb5ff9b26; anchor=TUI must reject redirected surfaces before contacting a device]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:97 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=s.output.find("Lighting")==s.output.rfind("Lighting")]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:97 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=s.output.find("LIGHTING")==s.output.rfind("LIGHTING")]`.
- Scoped advantage: Bounded checkpoint only; no whole-repository superiority or physical acceptance claim.
- Remaining: Complete useful omitted behavior and actual terminal/board acceptance; issue #2 remains open.
@@ -4285,7 +4285,7 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Inspected Git mode 120000 and target '.' at the pinned revision; accounted for without creating a recursive reference.
- Omitted/replaced code: `CodeQL root symlink to '.'`. Disposition: **not-applicable**. Reason: Environment-specific analysis marker is not shell code; recreating it would introduce self-referential source traversal.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `.gitignore`
@@ -4295,7 +4295,7 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Build/editor artifact exclusions retained in the exact reference snapshot; Pakar keeps its existing repository ignore policy.
- Omitted/replaced code: `Global wildcard ignores`. Disposition: **replaced**. Reason: Applying *.exe, *.a and IDE exclusions repo-wide would hide tracked recovery/test assets and existing project policies.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `CHANGELOG.md`
@@ -4305,7 +4305,7 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Read complete historical claims; retained verbatim as donor history, not imported as Pakar release certification.
- Omitted/replaced code: `Complete/security/performance claims`. Disposition: **replaced**. Reason: Source inspection shows placeholders and allocation in history/parser; an old claim of zero alerts cannot establish current safety.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `DOCUMENTATION.md`
@@ -4316,18 +4316,18 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Read all API/parameter/transport guidance; retained source documentation with corrected integration guidance.
- Omitted/replaced code: `Raw-pointer parameter binding and advertised editable TUI`. Disposition: **replaced**. Reason: Pakar validates durable settings through owner APIs and has a genuinely interactive staged TUI; no second unsafe parameter owner.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `examples/arduino_example.ino`
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `fe59231f346c7f5d8e44b206c5f1862e4fd71f308e7473fe1723c54bdcc3bcce`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
- Retained: Read complete sample callback/loop pattern. Existing loop-fed editor uses caller-owned output and bounded byte budgets.
- Omitted/replaced code: `Direct GPIO sample commands, while(!Serial), analogRead(0) loop`. Disposition: **replaced**. Reason: Arbitrary LED/pin access conflicts with board pin ownership; startup/loop work must not block UART/game scheduling.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `examples/linux_example.c`
@@ -4340,7 +4340,7 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Retained: Read complete termios raw-mode sample and parameter setup; existing host shell tool retained.
- Retained: Completed the host-side TCP/stdin path with hostname resolution, incremental Telnet negotiation, backpressure, bounded batch deadlines, hidden interactive authentication, terminal cleanup and no command replay. Actual loopback tests cover success/rejection/auth-required/timeout and interactive disconnect. Existing framed USB and board-side Telnet/TUI remain the real owners.
- Omitted/replaced code: `POSIX-only sample, stop-as-reboot and fake all-operational status`. Disposition: **replaced**. Reason: Use the existing device client/owned USB transport rather than a disconnected demo process or false status. Runtime behavior cannot be fabricated.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Test-code evidence: `tests/telnet_client_test.mjs:19 [UTF8-LF sha256=6d38cafbcf7c44ce351f1b70691dfda3c57132134c2aea52a5abe18f43d00f42; anchor=async function session]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4348,12 +4348,12 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `779a8e30dba97d85f24e2d3a76d7b3d1a3aeeb4b8066682d90bfdc5bcb0f4295`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Target: `tools/telnet-shell.mjs:49 [UTF8-LF sha256=85d7ea67800b3ea887d9515b37760a5bca81a4a1827afaffe415fb627869f812; anchor=export function connectShell]`.
- Retained: Read entire skeleton; preserved as reference, not wired into production.
- Retained: Completed the host-side TCP/stdin path with hostname resolution, incremental Telnet negotiation, backpressure, bounded batch deadlines, hidden interactive authentication, terminal cleanup and no command replay. Actual loopback tests cover success/rejection/auth-required/timeout and interactive disconnect. Existing framed USB and board-side Telnet/TUI remain the real owners.
- Omitted/replaced code: `Printed server banner and unimplemented bind/accept/session loop`. Disposition: **replaced**. Reason: Pakar already has actual bounded multi-session Telnet admission, authentication and network lifecycle.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Test-code evidence: `tests/telnet_client_test.mjs:19 [UTF8-LF sha256=6d38cafbcf7c44ce351f1b70691dfda3c57132134c2aea52a5abe18f43d00f42; anchor=async function session]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4361,56 +4361,65 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `d024ffc22f99a7c9f50fa68bbaf02b0e722d68b0edaf39512454643e6f1986e2`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Read each asserted requirement and reconciled against actual donor code rather than trusting completion checkmarks.
- Omitted/replaced code: `Thread-safe variable and full editor completeness claims`. Disposition: **replaced**. Reason: No synchronization exists for raw pointers; editor only prints and waits. Existing loop ownership and staged TUI retained.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `include/portable_device_table.hpp`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73`.
-- Target: `firmware/esp/src/portable-shell/portable_device_table.hpp:194 [UTF8-LF sha256=784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2; anchor=renderTable]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_device_table.hpp:1 [UTF8-LF sha256=37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73; anchor=tableCapacity]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/portable_device_view.hpp`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652`.
-- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:2 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/portable_editor.h`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `00bd8c83ae9d7ad8fdc3ae963e37acabf0b3f93096758f0294cdebed2f11cbd3`.
-- Target: `firmware/esp/src/portable-shell/portable_editor.h:302 [UTF8-LF sha256=1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea; anchor=static inline int ps_editor_feed]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_editor.h:1 [UTF8-LF sha256=00bd8c83ae9d7ad8fdc3ae963e37acabf0b3f93096758f0294cdebed2f11cbd3; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
+
+### portable-shell: `include/portable_meter.h`
+
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `48350a03fd496aaf50bf47149f8a099d7d07de9175024ef43a917e0a7cf53f1d`.
+
+- Target: `firmware/esp/src/portable-shell/portable_meter.h:1 [UTF8-LF sha256=48350a03fd496aaf50bf47149f8a099d7d07de9175024ef43a917e0a7cf53f1d; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
+- No production-code omission claimed for this reviewed file.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/portable_shell_editor.hpp`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `1289e6667828d637042de29b31941427d8a9f222c3a7c967fd751f3b0c7ba7c3`.
-- Target: `firmware/esp/src/portable-shell/portable_shell_editor.hpp:81 [UTF8-LF sha256=9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5; anchor=class LineEditor]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_shell_editor.hpp:1 [UTF8-LF sha256=1289e6667828d637042de29b31941427d8a9f222c3a7c967fd751f3b0c7ba7c3; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/portable_shell.h`
@@ -4418,47 +4427,74 @@ Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46
- Target: `firmware/esp/src/PortableShellCore.h:17 [UTF8-LF sha256=23ec970b7349e0eb29fbc420e5e8973461f46d0c4be70c141e0ee0bacbf6ace7; anchor=struct Command]`.
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Retained: Adapted PSCommand name/help model and portable callback output into immutable command catalogue/Terminal; independent session state remains in LineEditor.
- Omitted/replaced code: `Heap-linked arbitrary register/unregister, Arduino bool/HIGH/LOW macros, raw mutable parameter pointers`. Disposition: **replaced**. Reason: Build-time immutable descriptors prevent dangling name/callback ownership and duplicate registrations; C++ types remain intact; settings go through validation.
- Test-code evidence: `tests/portable_shell_test.cpp:8 [UTF8-LF sha256=1ce65230a875baf56a04184dc7b586cc7c15946b2fb066b12453759a84afb31f; anchor=int main()]`.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Scoped advantage: Actual command/VT100 integration preserves bounded authenticated owners and adds tested, non-executing command/property completion instead of a Tab bell placeholder. Device TUI adds Unicode frame bytes, bounded NAWS/TTYPE-aware layout, session terminal preferences and role-gated game actions/settings. Full Unicode entry, grapheme/RTL layout, CHARSET negotiation, advanced presentation override editing and live acceptance remain issue #29 work.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `include/portable_style.h`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `a29a3338b6ee4523fc394adeb496146f82840c931b4ac7035e52502bf9ce3f6e`.
+
+- Target: `firmware/esp/src/portable-shell/portable_style.h:1 [UTF8-LF sha256=a29a3338b6ee4523fc394adeb496146f82840c931b4ac7035e52502bf9ce3f6e; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
+- No production-code omission claimed for this reviewed file.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
+
+### portable-shell: `include/portable_table_policy.h`
+
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `eed0e170b4b80284b0cbbda87c7998480ec6d4040192d0402ca5af112ddfec04`.
-- Target: `firmware/esp/src/portable-shell/portable_style.h:15 [UTF8-LF sha256=d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11; anchor=ps_style_ink]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_table_policy.h:1 [UTF8-LF sha256=eed0e170b4b80284b0cbbda87c7998480ec6d4040192d0402ca5af112ddfec04; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/portable_terminal.h`
-Verdict: **equivalent-file**. Revision: `e90ffc004b39912e21a1799a75061ac61f22c74f`. Original-byte SHA-256: `356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1`.
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0`.
+
+- Target: `firmware/esp/src/portable-shell/portable_terminal.h:1 [UTF8-LF sha256=93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
+- No production-code omission claimed for this reviewed file.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=Consumer must use canonical components]`.
+- Test-code evidence: `tests/shell_workspace_test.cpp:122 [UTF8-LF sha256=2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd; anchor=Actual workspace binding]`.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
+
+### portable-shell: `include/portable_undo.h`
+
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `adf67d72d371b0e15fb1be3c64dfdea5155840b55a23a987be5807437fed9be0`.
+
+- Target: `firmware/esp/src/portable-shell/portable_undo.h:1 [UTF8-LF sha256=adf67d72d371b0e15fb1be3c64dfdea5155840b55a23a987be5807437fed9be0; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
+- No production-code omission claimed for this reviewed file.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
+
+### portable-shell: `include/portable_util.h`
+
+Verdict: **equivalent-file**. Revision: `3a7eeba179c238226deef1b64e3bd3b86c4e12e7`. Original-byte SHA-256: `568c68488cc8821e694b2ba576f138dc4d0d6f8f9970e83b99188b7727014256`.
-- Target: `firmware/esp/src/portable-shell/portable_terminal.h:371 [UTF8-LF sha256=356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1; anchor=static inline int ps_telnet_feed]`.
-- Retained: Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing.
+- Target: `firmware/esp/src/portable-shell/portable_util.h:1 [UTF8-LF sha256=568c68488cc8821e694b2ba576f138dc4d0d6f8f9970e83b99188b7727014256; anchor=#ifndef]`.
+- Retained: Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:33 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=Consumer must use canonical components]`.
-- Test-code evidence: `tests/shell_workspace_test.cpp:119 [UTF8-LF sha256=4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84; anchor=Actual workspace binding]`.
-- Remaining: Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration.
+- Remaining: Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration.
### portable-shell: `include/ps_io.h`
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `024a3ac2231b9b76b3a558a7f81406b5660ba7694c4f9e2f9b7caa585f05bcb1`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Target: `firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel]`.
- Target: `tools/telnet-shell.mjs:49 [UTF8-LF sha256=85d7ea67800b3ea887d9515b37760a5bca81a4a1827afaffe415fb627869f812; anchor=export function connectShell]`.
- Retained: Read callback/context contracts; preserved per-session transport-independent output and existing explicit USB ownership.
- Retained: Completed the host-side TCP/stdin path with hostname resolution, incremental Telnet negotiation, backpressure, bounded batch deadlines, hidden interactive authentication, terminal cleanup and no command replay. Actual loopback tests cover success/rejection/auth-required/timeout and interactive disconnect. Existing framed USB and board-side Telnet/TUI remain the real owners.
- Omitted/replaced code: `New PSSocketContext/PSUdpContext/PSSerialContext and undefined stdio initializer`. Disposition: **replaced**. Reason: Existing WiFiClient/SerialChannel owners already provide the real transports with authentication and replay fencing; no duplicate socket or serial owner.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Test-code evidence: `tests/telnet_client_test.mjs:19 [UTF8-LF sha256=6d38cafbcf7c44ce351f1b70691dfda3c57132134c2aea52a5abe18f43d00f42; anchor=async function session]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4470,18 +4506,18 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `firmware/esp/src/PortableShellCore.h:17 [UTF8-LF sha256=23ec970b7349e0eb29fbc420e5e8973461f46d0c4be70c141e0ee0bacbf6ace7; anchor=struct Command]`.
- Retained: MIT copyright and complete license retained with original source; derived header points to this notice.
- No production-code omission claimed for this reviewed file.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `Makefile`
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `e9d0814b7f08de2a7f53d004192a60cb329652e7b745bdee379144373cb26f4f`.
-- Target: `tools/test-shell.ps1:8 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=foreach($taskName]`.
+- Target: `tools/test-shell.ps1:8 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=foreach($taskName]`.
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Read full targets and retained upstream build recipe as reference. Tests integrated into the existing guarded native runner.
- Omitted/replaced code: `Separate donor make clean/install/uninstall and -g profile`. Disposition: **replaced**. Reason: No second build cache or uncontrolled system installation; existing cross-platform canonical low-write test/build wrappers remain authoritative.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `README.md`
@@ -4491,7 +4527,7 @@ Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b4
- Target: `third_party/portable-shell/README.md:1 [UTF8-LF sha256=dbdd9653cb23ba484160e050ed8c3c409d314b128ec0398620b391334c7bf24c; anchor=# Portable Shell source integration]`.
- Retained: Read entire requested branch README, including API examples and completed plan. Useful capabilities mapped to actual compiled owners.
- Omitted/replaced code: `Production-ready/comprehensive claims and example server instructions`. Disposition: **replaced**. Reason: Examples and tests do not implement promised transport/editor behaviors; integration guide records inspected limitations explicitly.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
### portable-shell: `src/portable_shell.c`
@@ -4500,14 +4536,14 @@ Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46
- Target: `firmware/esp/src/PortableShellCore.h:17 [UTF8-LF sha256=23ec970b7349e0eb29fbc420e5e8973461f46d0c4be70c141e0ee0bacbf6ace7; anchor=struct Command]`.
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
-- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView]`.
- Target: `firmware/esp/src/ShellLabels.h:5 [UTF8-LF sha256=3ea98be287d73a0188be5295a707d09bb66ad780f81d23554420245979b133cf; anchor=inline const char* propertyLabel]`.
- Target: `firmware/esp/src/ShellToneCommand.h:8 [UTF8-LF sha256=4bcb091529777d891c2f435724ef9531d4417f098ecb5ee2914d1e16d7e55a7a; anchor=inline bool toneCommand]`.
- Retained: Adapted strcmp command lookup and name/help metadata into the compiled catalogue; expanded Tab placeholder into safe completion plus descriptor-only muted suggestions accepted by Right/End, role-filtered grouped/wrapped Usage and a real validated finite beep route. TUI labels are presentation names, not changed API keys.
- Omitted/replaced code: `ps_create/destroy/run, heap history, strtok argv, synchronous escape reads, raw-pointer atoi/atof setters, blocking edit loop`. Disposition: **replaced**. Reason: Existing fixed/scrubbed history, incremental parser, output backpressure, property validation and staged TUI prevent allocation failures, secret retention, truncated execution and blocking LED/UART work.
- Test-code evidence: `tests/portable_shell_test.cpp:8 [UTF8-LF sha256=1ce65230a875baf56a04184dc7b586cc7c15946b2fb066b12453759a84afb31f; anchor=int main()]`.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Test-code evidence: `tests/shell_tone_dispatch_test.mjs:49 [UTF8-LF sha256=39e1e5a861b30040d05652b476789f6d3e6375c42d8ba535d6bf95be042612e2; anchor=Actual beep-to-buzzer-to-AVR branch]`.
- Scoped advantage: Actual command/VT100 integration preserves bounded authenticated owners and adds tested, non-executing command/property completion instead of a Tab bell placeholder.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4516,13 +4552,13 @@ Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46
Verdict: **equivalent-behavior**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `1eb80f98d09fb1042fee3f5facb9dfc3813962b0645c05307c701064aa6e6df6`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Target: `firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel]`.
- Target: `tools/telnet-shell.mjs:49 [UTF8-LF sha256=85d7ea67800b3ea887d9515b37760a5bca81a4a1827afaffe415fb627869f812; anchor=export function connectShell]`.
- Retained: Read all TCP/UDP/serial implementation; preserved original for review. Existing actual nonblocking Telnet and encapsulated USB remain single transport owners.
- Retained: Completed the host-side TCP/stdin path with hostname resolution, incremental Telnet negotiation, backpressure, bounded batch deadlines, hidden interactive authentication, terminal cleanup and no command replay. Actual loopback tests cover success/rejection/auth-required/timeout and interactive disconnect. Existing framed USB and board-side Telnet/TUI remain the real owners.
- Omitted/replaced code: `Blocking IPv4-only connect/recv, single partial send, unconnected UDP replies, serial init=-1 and no-op close`. Disposition: **replaced**. Reason: Avoid loop stalls, dropped output, invalid UDP destinations and fake serial support; hostname-first discovery and binary recovery are already shared project facilities.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Test-code evidence: `tests/telnet_client_test.mjs:19 [UTF8-LF sha256=6d38cafbcf7c44ce351f1b70691dfda3c57132134c2aea52a5abe18f43d00f42; anchor=async function session]`.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4532,11 +4568,11 @@ Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46
- Target: `firmware/esp/src/PortableShellCore.h:17 [UTF8-LF sha256=23ec970b7349e0eb29fbc420e5e8973461f46d0c4be70c141e0ee0bacbf6ace7; anchor=struct Command]`.
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
-- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void executeDeviceShell]`.
+- Target: `firmware/esp/src/ShellService.h:197 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void executeDeviceShell]`.
- Retained: Adapted actual ANSI clear/line/cursor/color/reset formatting into bounded callback emitters and used them by the live LineEditor and clear command.
- Omitted/replaced code: `Unbounded signed cursor/color formatting and unused standalone cursor-home/right wrappers`. Disposition: **replaced**. Reason: Validate coordinate/SGR ranges; existing editor retains echo-free password handling, plain-terminal behavior and movement semantics without duplicated control owners.
- Test-code evidence: `tests/portable_shell_test.cpp:8 [UTF8-LF sha256=1ce65230a875baf56a04184dc7b586cc7c15946b2fb066b12453759a84afb31f; anchor=int main()]`.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Scoped advantage: Actual command/VT100 integration preserves bounded authenticated owners and adds tested, non-executing command/property completion instead of a Tab bell placeholder.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4545,11 +4581,11 @@ Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46
Verdict: **scoped-improvement**. Revision: `67c5dcb8a63f4055cbdaf02e9ff271c53b46410d`. Original-byte SHA-256: `f26c767cd4ab7471a586b3b1500f9a613cc0ce00ac3f0ad23733e0c04af526eb`.
- Target: `tests/portable_shell_test.cpp:8 [UTF8-LF sha256=1ce65230a875baf56a04184dc7b586cc7c15946b2fb066b12453759a84afb31f; anchor=int main()]`.
-- Target: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Target: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Retained: Read all eight donor test scenarios; retained original tests and adapted independent-context, built-in, command-lookup, line-edit and VT100 output assertions into production-header fixtures.
- Omitted/replaced code: `Mock no-crash checks as completeness evidence`. Disposition: **replaced**. Reason: New tests assert exact bytes, safe aliases, bounds, password isolation, ambiguous/property completion and fragmented escape handling, alongside existing history/TUI/USB/lease tests.
- Test-code evidence: `tests/portable_shell_test.cpp:8 [UTF8-LF sha256=1ce65230a875baf56a04184dc7b586cc7c15946b2fb066b12453759a84afb31f; anchor=int main()]`.
-- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773; anchor=const source=]`.
+- Test-code evidence: `tests/portable_shell_source_test.mjs:5 [UTF8-LF sha256=52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea; anchor=const source=]`.
- Scoped advantage: Actual command/VT100 integration preserves bounded authenticated owners and adds tested, non-executing command/property completion instead of a Tab bell placeholder.
- Remaining: Issue #29: source/fixture acceptance only; no production flash or live USB/Telnet session acceptance in this integration task.
@@ -4957,8 +4993,8 @@ Verdict: **scoped-improvement**. Revision: `431104072952f694a35b3a83c19f13fbb098
Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. Original-byte SHA-256: `5181d7cc83efe2416a3a198ff88aad2aeffc8871ee2ef7a2a1ffdad51d31b88e`.
-- Target: `firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=bool shellSet(]`.
-- Target: `firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=shellGet(property)]`.
+- Target: `firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=bool shellSet(]`.
+- Target: `firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=shellGet(property)]`.
- Target: `firmware/esp/src/ShellSettings.h:20 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=bool configure(]`.
- Target: `docs/PU850-PARITY.md:98 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Fresh runtime and shell section review, 2026-10-19]`.
- Target: `firmware/esp/src/ShellPixelCommand.h:14 [UTF8-LF sha256=a5a9f5a43035578d904c547848429e71acd68867418c36f7e5fb1d6375ad32e6; anchor=template
]`.
@@ -4974,7 +5010,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Omitted/replaced code: `Commented AP_IP/AP_Gateway/AP_Subnet parameter entries`. Disposition: **pending**. Reason: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.
- Test-code evidence: `tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf]`.
- Test-code evidence: `tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:41 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Readonly never emits Set.]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:50 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Readonly never emits Set.]`.
- Test-code evidence: `tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)]`.
- Test-code evidence: `tests/shell_pixel_test.cpp:6 [UTF8-LF sha256=f914cdb8df67ea915d286187b89208be931cfde70cac255a10b30e0d63b05690; anchor=int main()]`.
- Test-code evidence: `tests/shell_metadata_test.mjs:8 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const tuiKeys=]`.
@@ -4987,8 +5023,8 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. Original-byte SHA-256: `48c0deb76d6105090fe1d58a2449427681a88775b1e63ddbb1b28ecff6341961`.
-- Target: `firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=bool shellSet(]`.
-- Target: `firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=shellGet(property)]`.
+- Target: `firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=bool shellSet(]`.
+- Target: `firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=shellGet(property)]`.
- Target: `firmware/esp/src/ShellSettings.h:20 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=bool configure(]`.
- Target: `docs/PU850-PARITY.md:98 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Fresh runtime and shell section review, 2026-10-19]`.
- Retained: DeviceParameter name/type/read-only/get/set design is adapted to ShellProperty descriptors for hostname/label, network mode/DHCP/static addressing, station SSID/write-only password, MQTT/WebSocket endpoints, telemetry interval, shell settings and LED mode/brightness/effect.
@@ -5002,7 +5038,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Omitted/replaced code: `Commented AP_IP/AP_Gateway/AP_Subnet parameter entries`. Disposition: **pending**. Reason: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.
- Test-code evidence: `tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf]`.
- Test-code evidence: `tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:41 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Readonly never emits Set.]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:50 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Readonly never emits Set.]`.
- Test-code evidence: `tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
@@ -5186,7 +5222,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Target: `docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]`.
- Target: `firmware/esp/src/ShellRemoteQuery.h:11 [UTF8-LF sha256=d5c21db264e3bc5519ca63c0ff7568183dc5b321ce6a6842468052068449c0e4; anchor=class RemoteQuery]`.
-- Target: `firmware/esp/src/ShellService.h:20 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceShellRemoteQuery()]`.
+- Target: `firmware/esp/src/ShellService.h:20 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceShellRemoteQuery()]`.
- Target: `tools/udp-device-query.mjs:10 [UTF8-LF sha256=1d0c45a2f2f05a2564ed9a80d116fc52c4564e0e79b07cebb09ee8bd872e7e25; anchor=const nonce=]`.
- Retained: Every section/function read through EOF: all UDP state/ports; initUdpDebug/UdpService/udpSend/handleUdpReceive/executeCommand; all ServiceEvent cases
- Retained: Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.
@@ -5228,7 +5264,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
- Target: `firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]]`.
- Target: `firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel]`.
-- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot]`.
+- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot]`.
- Target: `tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false]`.
- Target: `firmware/esp/src/SshShellBinding.h:37 [UTF8-LF sha256=a0333d0b81ed2196be82d6ce9e8e643789366f578b65d71a6e2490892382a560; anchor=void serviceSshShell()]`.
- Target: `firmware/esp32s3/src/SshTransport.h:31 [UTF8-LF sha256=988641dc923aafab5ec72ce909067455c44faf468bb241d5b15b3bac1bba33d8; anchor=class SshTransport {]`.
@@ -5257,8 +5293,8 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Test-code evidence: `tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()]`.
- Test-code evidence: `tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(]`.
- Test-code evidence: `tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:32 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Unchanged Enter is not a write.]`.
-- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:41 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Unchanged Enter is not a write.]`.
+- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]`.
- Test-code evidence: `tests/ssh_settings_test.cpp:17 [UTF8-LF sha256=44c6fda6dce749d7302d284c9f8ba1b9919b36fefd4e3e61fd00b665dfdf6ad2; anchor=int main()]`.
- Test-code evidence: `tests/ssh_exec_transport_test.cpp:91 [UTF8-LF sha256=615435469fa43f155f61ee84ce5f9fa847503a1f1d36360e963a24949b11a680; anchor=Actual SSH exec transport:]`.
- Test-code evidence: `tests/ssh_exec_binding_test.cpp:123 [UTF8-LF sha256=8d5b45f3825a81c91f8af77a263d87c37a4a53ec4f1399fca73f2490df9bbba0; anchor=Actual SSH shell exec binding/dispatcher:]`.
@@ -5284,7 +5320,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Target: `firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor]`.
- Target: `firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]]`.
- Target: `firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel]`.
-- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot]`.
+- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot]`.
- Target: `tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false]`.
- Retained: PU ShellSession transport/editor separation, insertion/shift/backspace/delete, CSI/SS3 arrows/home/end, Ctrl-C/Ctrl-L and split CR-LF/CR-NUL behavior are adapted in instance-owned TelnetParser/LineEditor.
- Retained: Generic parameter lookup/read-only enforcement and dispatch now map to ShellProperty descriptors, typed shellSet and the existing Pakar command/configuration owners; the same descriptors feed the device TUI.
@@ -5300,8 +5336,8 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Test-code evidence: `tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()]`.
- Test-code evidence: `tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(]`.
- Test-code evidence: `tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:32 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Unchanged Enter is not a write.]`.
-- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:41 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Unchanged Enter is not a write.]`.
+- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
- Remaining: Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.
@@ -5362,7 +5398,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. Original-byte SHA-256: `a78d88c149d19d8989c6db3dce0731189c2bf46c45fd64b73e9871a80f6c9bdd`.
- Target: `firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser]`.
-- Target: `firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceDeviceShells()]`.
+- Target: `firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceDeviceShells()]`.
- Target: `firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings]`.
- Target: `firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]`.
- Retained: Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops.
@@ -5377,7 +5413,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Test-code evidence: `tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.]`.
- Test-code evidence: `tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true]`.
- Test-code evidence: `tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()]`.
-- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]`.
+- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
- Remaining: Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.
@@ -5387,7 +5423,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. Original-byte SHA-256: `e85f5f1a4f2767323d9886313ddb2aabfd3acad7d781c2770bc8d2b2d2c6cb15`.
- Target: `firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser]`.
-- Target: `firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceDeviceShells()]`.
+- Target: `firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceDeviceShells()]`.
- Target: `firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings]`.
- Target: `firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]`.
- Retained: Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops.
@@ -5402,7 +5438,7 @@ Verdict: **parity-gap**. Revision: `431104072952f694a35b3a83c19f13fbb098ca5b`. O
- Test-code evidence: `tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.]`.
- Test-code evidence: `tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true]`.
- Test-code evidence: `tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()]`.
-- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]`.
+- Test-code evidence: `tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
- Remaining: Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.
@@ -6563,16 +6599,16 @@ Verdict: **parity-gap**. Revision: `filesystem-snapshot-no-git`. Original-byte S
Verdict: **parity-gap**. Revision: `filesystem-snapshot-no-git`. Original-byte SHA-256: `3a3b4f13d0c839664edc2b15bb4418254463ff4d36016cb798a799016ef2890a`.
-- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView]`.
-- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot]`.
+- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView]`.
+- Target: `firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot]`.
- Retained: formIsFiltering focused-field key ownership is adapted to a pure ShellTui editing state: printable input/Tab cannot invoke workspace shortcuts while a draft is active; Esc/Ctrl-C cancel without applying.
- Retained: Readonly fields cannot emit Set, unchanged Enter is not a write, CR-LF submits once, and a draft is cancelled if its key/editable/secret authority changes. Observed refreshes do not repaint or replace an active draft.
- Retained: One shared ShellProperty snapshot binds TUI fields to the same shell/configuration validators, rather than introducing a separate TUI business or persistence layer.
- Omitted/replaced code: `Huh form types, Go interface assertions and Bubble Tea runtime`. Disposition: **replaced**. Reason: The microcontroller uses an allocation-free C++ editing state and explicit Action handoff. No Go/Huh/Bubble Tea runtime is ported or required, and no full Huh select-filter UI is claimed.
- Omitted/replaced code: `Unicode/Persian input and desktop form widgets`. Disposition: **pending**. Reason: Current Arduino form accepts bounded printable ASCII only; terminal character shaping, Persian editing and richer host widgets require separate implementation and acceptance.
-- Test-code evidence: `tests/shell_tui_test.cpp:43 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=private\trefresh]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:37 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Async observation cannot repaint draft.]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:72 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=rows[0].editable=false]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:52 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=private\trefresh]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:46 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Async observation cannot repaint draft.]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:81 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=rows[0].editable=false]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
- Remaining: Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.
@@ -6630,9 +6666,9 @@ Verdict: **parity-gap**. Revision: `filesystem-snapshot-no-git`. Original-byte S
Verdict: **parity-gap**. Revision: `filesystem-snapshot-no-git`. Original-byte SHA-256: `3b56b9ee2bfa2e40b02e464c7cc2cd471d6dbb2148f06091bd56dfdf2926e1b3`.
- Target: `firmware/esp/src/ShellWire.h:31 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class OutputWindow]`.
-- Target: `firmware/esp/src/portable-shell/portable_terminal.h:210 [UTF8-LF sha256=356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1; anchor=static inline size_t ps_terminal_text]`.
-- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView]`.
-- Target: `firmware/esp/src/ShellService.h:401 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void closeDeviceShells()]`.
+- Target: `firmware/esp/src/portable-shell/portable_terminal.h:236 [UTF8-LF sha256=93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0; anchor=static inline size_t ps_terminal_text]`.
+- Target: `firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView]`.
+- Target: `firmware/esp/src/ShellService.h:401 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void closeDeviceShells()]`.
- Target: `tools/device-shell.ps1:25 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=Exactly once; uncertain input is NEVER replayed.]`.
- Retained: Session-fenced immutable input/output ownership is adapted into SerialChannel/OutputWindow and the host terminal tool: explicit Open/token, no uncertain input replay, nonwrapping sequences, output ACK before consumption and explicit close.
- Retained: Terminal safety is adapted into bounded terminalText and TUI rendering: external CSI/OSC/DCS/control payloads cannot inject terminal actions, and only the project renderer emits ANSI; output/drafts/transcripts are bounded.
@@ -6642,7 +6678,7 @@ Verdict: **parity-gap**. Revision: `filesystem-snapshot-no-git`. Original-byte S
- Omitted/replaced code: `Transparent raw serial and automatic uncertain write retry`. Disposition: **replaced**. Reason: An additive boot/token-fenced COBS channel preserves ordinary USB recovery. Host terminal sends input once and does not reopen/replay actions automatically after uncertainty.
- Test-code evidence: `tests/shell_wire_test.cpp:21 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=Lost read reply replays same bytes]`.
- Test-code evidence: `tests/shell_wire_test.cpp:26 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=output.sequence()==UINT16_MAX]`.
-- Test-code evidence: `tests/shell_tui_test.cpp:19 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=never-render-this]`.
+- Test-code evidence: `tests/shell_tui_test.cpp:28 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=never-render-this]`.
- Test-code evidence: `tests/telnet_shell_test.cpp:115 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=Inspect the caller-owned backing bytes]`.
- Scoped advantage: Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.
- Remaining: Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands.
diff --git a/docs/REUSE-PREFLIGHT-RECEIPT.md b/docs/REUSE-PREFLIGHT-RECEIPT.md
index 55db81d..019ceae 100644
--- a/docs/REUSE-PREFLIGHT-RECEIPT.md
+++ b/docs/REUSE-PREFLIGHT-RECEIPT.md
@@ -12131,3 +12131,47 @@ only those two target hashes in PU850-ROUTE-PARITY.json, keeping donor hashes,
route classifications and remaining acceptance unchanged. The actual50-route
checker, Web identity test and no-write application-artwork check pass. This is
not execution of native tests, firmware compilation or OTA/physical acceptance.
+
+## 2026-10-31: PortableShell consumer refresh
+
+Consulted donor-audit/portable-shell, terminal-ui, device-shell and build-storage;
+read the canonical component changes and shared policy/utility/undo/meter owners.
+The initial `shell` consultation name was invalid; corrected it to the documented
+concerns before editing. Exact exports use PortableShell 3a7eeba. Existing
+historical reference snapshots and consumer hardware/authentication owners stay.
+
+Updated target/proof bindings only for inspected changes. The table spacing,
+uppercase category and styled footer expectations changed with the upstream
+renderer; navigation/draft/owner-dispatch assertions remain. The old theme array
+had four entries but the refreshed renderer defaults to index four: add default,
+rename cyan, and derive choice count. Release mouse reporting before alternate
+screen exit, with a binding regression test.
+
+A failed workspace assertion left Windows holding the test executable. A named
+build output option in the existing gated test wrapper avoids output collisions;
+it does not bypass reuse gates or alter tested sources. An initial script-edit
+syntax error was corrected before rerunning. No production process was stopped,
+no board flashed and no unrelated donor verdict was upgraded.
+
+Validation: the complete gated shell suite passed, including nine-page binding,
+Telnet/TCP loopback, source hashes, wire admission, settings persistence, pixel
+topology, tone dispatch and SSH policy. The final mouse-release regression and
+page captures passed in the gated workspace-only run. Tests use hardware doubles;
+SSH policy tests are not a live SSH connection or physical acceptance.
+
+The full-session F7/history-expansion/hex APIs are not exported by the standalone
+header integration. This refresh does not claim those features are wired into
+firmware. PU850 remains at fetched origin/main 4311040: ShellSession write/close
+callbacks, CommandEntry handlers and DeviceParameter get/set callbacks provide
+an adapter boundary. A future migration must preserve device handlers, network
+lifecycle, persistence and serial/Telnet behavior, then compile and test hardware.
+No PU850 replacement or board rollout was performed in this pass.
+
+PU850 command mapping must retain `save`/`restore` persistence, `busy`/`ready`
+controller signals, `status` network observations and device-specific `beep`.
+Its `hex` converts a number: do not replace it silently with the desktop hex
+editor demo. Its upper/noupper behavior can migrate to a typed dotted setting
+under an explicit consumer decision. Keep its current command argument and
+read-only parameter semantics, write/close callbacks, per-session echo/line
+ending behavior, hostname prompt, and Wi-Fi/mDNS reconnect lifecycle. The library
+provides reusable byte/parser/rendering mechanisms, not these device operations.
diff --git a/docs/SYSTEM-MAPS.md b/docs/SYSTEM-MAPS.md
new file mode 100644
index 0000000..f66692f
--- /dev/null
+++ b/docs/SYSTEM-MAPS.md
@@ -0,0 +1,142 @@
+# PakarController: clickable system maps
+
+This is a navigable companion to [the detailed architecture and gaps](ARCHITECTURE.md). The ESP owns board configuration and command admission; AVR firmware owns its physical inputs and peripherals. Clients keep drafts and display observed state. PortableShell supplies reusable terminal mechanisms.
+
+Click a component to inspect its implementation. Links are pinned to source commit `0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b`, so this map remains inspectable as the code evolves. Arrows describe source-level relationships, not proof of deployment or physical behavior.
+
+## Whole-system ownership
+
+Arrows show application and transport responsibilities. Optional capabilities require matching build flags, runtime configuration and observed device support.
+
+```mermaid
+flowchart TD
+ desktop["Desktop / CLI / TUI client"]
+ mobile["Mobile client"]
+ esp["Integrated ESP application"]
+ s3["ESP32-S3 entry point"]
+ avr["AVR firmware and physical peripherals"]
+ wire["Shared wire framing"]
+ led["LED scene and rendering owner"]
+ auth["Access configuration and persistence"]
+ policy["Supported-peer policy"]
+ web["Browser client"]
+ web -->|HTTP and WebSocket| esp
+ click web "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/web/index.html" "Inspect web/index.html" _blank
+ class web component
+ cloud["Outbound MQTT and WebSocket"]
+ esp --> cloud
+ click cloud "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/CloudServices.cpp" "Inspect firmware/esp/src/CloudServices.cpp" _blank
+ class cloud component
+ desktop -->|selected transport| esp
+ mobile -->|selected transport| esp
+ s3 --> esp
+ esp --> auth
+ esp --> wire
+ wire --> avr
+ esp --> led
+ policy -. admission contract .-> esp
+ click desktop "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/desktop/src/main.rs" "Inspect desktop/src/main.rs" _blank
+ click mobile "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/mobile/lib/main.dart" "Inspect mobile/lib/main.dart" _blank
+ click esp "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/application.ino" "Inspect firmware/esp/application.ino" _blank
+ click s3 "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp32s3/esp32s3.ino" "Inspect firmware/esp32s3/esp32s3.ino" _blank
+ click avr "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/avr/avr.ino" "Inspect firmware/avr/avr.ino" _blank
+ click wire "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/libraries/BoardCore/src/WireCodec.h" "Inspect libraries/BoardCore/src/WireCodec.h" _blank
+ click led "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/LedGrid.h" "Inspect firmware/esp/src/LedGrid.h" _blank
+ click auth "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/AccessConfig.cpp" "Inspect firmware/esp/src/AccessConfig.cpp" _blank
+ click policy "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/interfaces/peer-policy.json" "Inspect interfaces/peer-policy.json" _blank
+ classDef component fill:#202936,stroke:#71d5e8,color:#f0f3f7,stroke-width:2px
+ class desktop,mobile,esp,s3,avr,wire,led,auth,policy component
+```
+
+## Terminal command and table lifecycle
+
+ShellService owns connection/authentication, bounded output and dispatch. The TUI renders snapshots and returns actions; the application validates and applies them. On exit, mouse reporting is disabled before the alternate screen is restored.
+
+```mermaid
+flowchart TD
+ service["Serial / Telnet / optional SSH sessions"]
+ editor["Shared editor facade"]
+ core["Portable command support"]
+ props["Typed property metadata"]
+ binding["Snapshots and action routing"]
+ view["Shared DeviceView"]
+ table["Shared table renderer"]
+ service --> editor
+ service --> core
+ service --> props
+ service --> binding
+ binding -->|observed snapshot| view
+ view --> table
+ view -->|requested action| binding
+ binding -->|application validation and dispatch| service
+ service -->|bounded output to peer| editor
+ click service "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellService.h" "Inspect firmware/esp/src/ShellService.h" _blank
+ click editor "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/TelnetShell.h" "Inspect firmware/esp/src/TelnetShell.h" _blank
+ click core "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/PortableShellCore.h" "Inspect firmware/esp/src/PortableShellCore.h" _blank
+ click props "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellProperties.h" "Inspect firmware/esp/src/ShellProperties.h" _blank
+ click binding "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellTuiBinding.h" "Inspect firmware/esp/src/ShellTuiBinding.h" _blank
+ click view "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/portable-shell/portable_device_view.hpp" "Inspect firmware/esp/src/portable-shell/portable_device_view.hpp" _blank
+ click table "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/portable-shell/portable_device_table.hpp" "Inspect firmware/esp/src/portable-shell/portable_device_table.hpp" _blank
+ classDef component fill:#202936,stroke:#71d5e8,color:#f0f3f7,stroke-width:2px
+ class service,editor,core,props,binding,view,table component
+```
+
+## Refresh, validation and release boundaries
+
+The component pin records current shared headers separately from historical reference snapshots. Passing host fixtures does not prove SSH wire interoperability, installed firmware or physical light/sound.
+
+```mermaid
+flowchart TD
+ pin["Canonical component revision and hashes"]
+ evidence["File comparison evidence"]
+ sourceTest["Verify pinned files and wiring"]
+ suite["Gated shell / loopback test runner"]
+ workspace["Actual nine-page binding fixture"]
+ firmware["Guarded firmware builds"]
+ receipt["Refresh and migration acceptance notes"]
+ pin --> sourceTest
+ evidence --> suite
+ sourceTest --> suite
+ workspace --> suite
+ suite -. source evidence .-> receipt
+ firmware -. build evidence .-> receipt
+ click pin "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/third_party/portable-shell/source.json" "Inspect third_party/portable-shell/source.json" _blank
+ click evidence "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/docs/file-comparison-evidence.json" "Inspect docs/file-comparison-evidence.json" _blank
+ click sourceTest "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tests/portable_shell_source_test.mjs" "Inspect tests/portable_shell_source_test.mjs" _blank
+ click suite "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tools/test-shell.ps1" "Inspect tools/test-shell.ps1" _blank
+ click workspace "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tests/shell_workspace_test.cpp" "Inspect tests/shell_workspace_test.cpp" _blank
+ click firmware "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tools/build-firmware.ps1" "Inspect tools/build-firmware.ps1" _blank
+ click receipt "https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/docs/REUSE-PREFLIGHT-RECEIPT.md" "Inspect docs/REUSE-PREFLIGHT-RECEIPT.md" _blank
+ classDef component fill:#202936,stroke:#71d5e8,color:#f0f3f7,stroke-width:2px
+ class pin,evidence,sourceTest,suite,workspace,firmware,receipt component
+```
+
+## Source index
+
+These links also work in readers that disable clickable diagram nodes.
+
+- [desktop/src/main.rs](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/desktop/src/main.rs)
+- [mobile/lib/main.dart](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/mobile/lib/main.dart)
+- [firmware/esp/application.ino](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/application.ino)
+- [firmware/esp32s3/esp32s3.ino](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp32s3/esp32s3.ino)
+- [firmware/avr/avr.ino](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/avr/avr.ino)
+- [libraries/BoardCore/src/WireCodec.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/libraries/BoardCore/src/WireCodec.h)
+- [firmware/esp/src/LedGrid.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/LedGrid.h)
+- [firmware/esp/src/AccessConfig.cpp](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/AccessConfig.cpp)
+- [interfaces/peer-policy.json](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/interfaces/peer-policy.json)
+- [firmware/esp/src/ShellService.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellService.h)
+- [firmware/esp/src/TelnetShell.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/TelnetShell.h)
+- [firmware/esp/src/PortableShellCore.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/PortableShellCore.h)
+- [firmware/esp/src/ShellProperties.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellProperties.h)
+- [firmware/esp/src/ShellTuiBinding.h](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/ShellTuiBinding.h)
+- [firmware/esp/src/portable-shell/portable_device_view.hpp](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/portable-shell/portable_device_view.hpp)
+- [firmware/esp/src/portable-shell/portable_device_table.hpp](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/portable-shell/portable_device_table.hpp)
+- [third_party/portable-shell/source.json](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/third_party/portable-shell/source.json)
+- [docs/file-comparison-evidence.json](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/docs/file-comparison-evidence.json)
+- [tests/portable_shell_source_test.mjs](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tests/portable_shell_source_test.mjs)
+- [tools/test-shell.ps1](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tools/test-shell.ps1)
+- [tests/shell_workspace_test.cpp](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tests/shell_workspace_test.cpp)
+- [tools/build-firmware.ps1](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/tools/build-firmware.ps1)
+- [docs/REUSE-PREFLIGHT-RECEIPT.md](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/docs/REUSE-PREFLIGHT-RECEIPT.md)
+- [web/index.html](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/web/index.html)
+- [firmware/esp/src/CloudServices.cpp](https://github.com/atomicdeploy/PakarController/blob/0a80e8cc3aad5c0f8adcc554863a2f2805b38b1b/firmware/esp/src/CloudServices.cpp)
diff --git a/docs/file-comparison-evidence.json b/docs/file-comparison-evidence.json
index 3a7033e..578fddb 100644
--- a/docs/file-comparison-evidence.json
+++ b/docs/file-comparison-evidence.json
@@ -63,7 +63,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -98,7 +98,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -138,7 +138,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -166,7 +166,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "third_party/portable-shell/README.md",
@@ -178,7 +178,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -218,7 +218,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [],
@@ -240,7 +240,7 @@
{
"path": "tools/test-shell.ps1",
"anchor": "foreach($taskName",
- "sha256": "51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967"
+ "sha256": "fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d"
},
{
"path": "third_party/portable-shell/README.md",
@@ -252,7 +252,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -287,7 +287,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -322,7 +322,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -357,7 +357,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "firmware/esp/src/TelnetShell.h",
@@ -369,7 +369,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -415,7 +415,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
},
{
"path": "tests/telnet_client_test.mjs",
@@ -449,7 +449,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "tools/telnet-shell.mjs",
@@ -461,7 +461,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
},
{
"path": "tests/telnet_client_test.mjs",
@@ -504,7 +504,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
}
],
"proof": [
@@ -516,7 +516,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -546,7 +546,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "firmware/esp/src/ShellWire.h",
@@ -563,7 +563,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
},
{
"path": "tests/telnet_client_test.mjs",
@@ -606,12 +606,12 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "firmware/esp/src/portable-shell/portable_device_view.hpp",
"anchor": "class DeviceView",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1"
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652"
},
{
"path": "firmware/esp/src/ShellLabels.h",
@@ -633,7 +633,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
},
{
"path": "tests/shell_tone_dispatch_test.mjs",
@@ -668,7 +668,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
},
{
"path": "firmware/esp/src/ShellWire.h",
@@ -685,7 +685,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
},
{
"path": "tests/telnet_client_test.mjs",
@@ -728,7 +728,7 @@
{
"path": "firmware/esp/src/ShellService.h",
"anchor": "void executeDeviceShell",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df"
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb"
}
],
"proof": [
@@ -740,7 +740,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -774,7 +774,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"proof": [
@@ -786,7 +786,7 @@
{
"path": "tests/portable_shell_source_test.mjs",
"anchor": "const source=",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773"
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea"
}
],
"omissions": [
@@ -2977,14 +2977,14 @@
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_device_view.hpp",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1",
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652",
"anchor": "class DeviceView"
}
],
"proof": [
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "assert(output.size()<=PS_DEVICE_VIEW_FRAME_BYTES)"
}
],
@@ -6450,7 +6450,7 @@
},
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "void serviceShellRemoteQuery()"
},
{
@@ -16369,8 +16369,8 @@
},
{
"path": "firmware/esp/src/portable-shell/portable_device_table.hpp",
- "sha256": "784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2",
- "anchor": "// PCController's logical-row/section-line separation, without heap storage."
+ "sha256": "37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73",
+ "anchor": "// Separate logical rows from section headings without heap storage."
}
],
"preserved": [
@@ -16400,8 +16400,8 @@
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
- "anchor": "s.output.find(\"Lighting\")==s.output.rfind(\"Lighting\")"
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
+ "anchor": "s.output.find(\"LIGHTING\")==s.output.rfind(\"LIGHTING\")"
}
],
"improvement": "Bounded checkpoint only; no whole-repository superiority or physical acceptance claim."
@@ -21120,7 +21120,7 @@
},
{
"path": "firmware/esp/src/ShellTuiBinding.h",
- "sha256": "fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9",
+ "sha256": "71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26",
"anchor": "struct ShellTuiSnapshot"
},
{
@@ -21217,12 +21217,12 @@
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "Unchanged Enter is not a write."
},
{
"path": "tools/test-shell.ps1",
- "sha256": "51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967",
+ "sha256": "fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d",
"anchor": "-Concern device-shell -Gate"
},
{
@@ -21363,7 +21363,7 @@
},
{
"path": "firmware/esp/src/ShellTuiBinding.h",
- "sha256": "fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9",
+ "sha256": "71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26",
"anchor": "struct ShellTuiSnapshot"
},
{
@@ -21400,12 +21400,12 @@
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "Unchanged Enter is not a write."
},
{
"path": "tools/test-shell.ps1",
- "sha256": "51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967",
+ "sha256": "fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d",
"anchor": "-Concern device-shell -Gate"
}
],
@@ -21472,7 +21472,7 @@
},
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "void serviceDeviceShells()"
},
{
@@ -21514,7 +21514,7 @@
},
{
"path": "tools/test-shell.ps1",
- "sha256": "51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967",
+ "sha256": "fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d",
"anchor": "-Concern device-shell -Gate"
}
],
@@ -21569,7 +21569,7 @@
},
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "void serviceDeviceShells()"
},
{
@@ -21611,7 +21611,7 @@
},
{
"path": "tools/test-shell.ps1",
- "sha256": "51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967",
+ "sha256": "fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d",
"anchor": "-Concern device-shell -Gate"
}
],
@@ -21662,12 +21662,12 @@
"targets": [
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "bool shellSet("
},
{
"path": "firmware/esp/src/ShellTuiBinding.h",
- "sha256": "fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9",
+ "sha256": "71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26",
"anchor": "shellGet(property)"
},
{
@@ -21704,7 +21704,7 @@
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "Readonly never emits Set."
},
{
@@ -21780,12 +21780,12 @@
"targets": [
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "bool shellSet("
},
{
"path": "firmware/esp/src/ShellTuiBinding.h",
- "sha256": "fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9",
+ "sha256": "71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26",
"anchor": "shellGet(property)"
},
{
@@ -21812,7 +21812,7 @@
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "Readonly never emits Set."
},
{
@@ -21869,29 +21869,29 @@
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_device_view.hpp",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1",
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652",
"anchor": "class DeviceView"
},
{
"path": "firmware/esp/src/ShellTuiBinding.h",
- "sha256": "fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9",
+ "sha256": "71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26",
"anchor": "struct ShellTuiSnapshot"
}
],
"proof": [
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "private\\trefresh"
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "Async observation cannot repaint draft."
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "rows[0].editable=false"
}
],
@@ -21932,17 +21932,17 @@
},
{
"path": "firmware/esp/src/portable-shell/portable_terminal.h",
- "sha256": "356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1",
+ "sha256": "93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0",
"anchor": "static inline size_t ps_terminal_text"
},
{
"path": "firmware/esp/src/portable-shell/portable_device_view.hpp",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1",
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652",
"anchor": "class DeviceView"
},
{
"path": "firmware/esp/src/ShellService.h",
- "sha256": "988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df",
+ "sha256": "206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb",
"anchor": "void closeDeviceShells()"
},
{
@@ -21964,7 +21964,7 @@
},
{
"path": "tests/shell_tui_test.cpp",
- "sha256": "650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d",
+ "sha256": "1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7",
"anchor": "never-render-this"
},
{
@@ -26291,205 +26291,205 @@
{
"origin": "portable-shell",
"path": "include/portable_terminal.h",
- "sha256": "356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_terminal.h",
- "sha256": "356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1",
- "anchor": "static inline int ps_telnet_feed"
+ "sha256": "93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0",
+ "anchor": "#ifndef"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
"origin": "portable-shell",
"path": "include/portable_editor.h",
- "sha256": "1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "00bd8c83ae9d7ad8fdc3ae963e37acabf0b3f93096758f0294cdebed2f11cbd3",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_editor.h",
- "sha256": "1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea",
- "anchor": "static inline int ps_editor_feed"
+ "sha256": "00bd8c83ae9d7ad8fdc3ae963e37acabf0b3f93096758f0294cdebed2f11cbd3",
+ "anchor": "#ifndef"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
"origin": "portable-shell",
"path": "include/portable_shell_editor.hpp",
- "sha256": "9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "1289e6667828d637042de29b31941427d8a9f222c3a7c967fd751f3b0c7ba7c3",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_shell_editor.hpp",
- "sha256": "9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5",
- "anchor": "class LineEditor"
+ "sha256": "1289e6667828d637042de29b31941427d8a9f222c3a7c967fd751f3b0c7ba7c3",
+ "anchor": "#ifndef"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
"origin": "portable-shell",
"path": "include/portable_device_view.hpp",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_device_view.hpp",
- "sha256": "38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1",
- "anchor": "class DeviceView"
+ "sha256": "fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652",
+ "anchor": "#ifndef"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
"origin": "portable-shell",
"path": "include/portable_style.h",
- "sha256": "d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "a29a3338b6ee4523fc394adeb496146f82840c931b4ac7035e52502bf9ce3f6e",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_style.h",
- "sha256": "d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11",
- "anchor": "ps_style_ink"
+ "sha256": "a29a3338b6ee4523fc394adeb496146f82840c931b4ac7035e52502bf9ce3f6e",
+ "anchor": "#ifndef"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
"origin": "portable-shell",
"path": "include/portable_device_table.hpp",
- "sha256": "784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2",
- "source_revision": "e90ffc004b39912e21a1799a75061ac61f22c74f",
+ "sha256": "37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
"source_read": "complete",
"verdict": "equivalent-file",
"preserved": [
- "Complete canonical component at e90ffc004b39912e21a1799a75061ac61f22c74f. Shared full palettes, rich tables, inline values and optional typed controls/dialogs. Explicit feature flags retain smaller layouts. Render inspection corrected Unicode hints, diagnostic surfaces and modal compositing."
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
],
"targets": [
{
"path": "firmware/esp/src/portable-shell/portable_device_table.hpp",
- "sha256": "784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2",
- "anchor": "renderTable"
+ "sha256": "37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73",
+ "anchor": "tableCapacity"
}
],
"proof": [
{
"path": "tests/portable_shell_source_test.mjs",
- "sha256": "f9b52abd7672d2053463adad77078e55c4fdd0bb3102463ce8c20c3895193773",
+ "sha256": "52b6834c58de5b07c7fd52e7be34f109aaa1c961ddd2f6362532a71fd4559aea",
"anchor": "Consumer must use canonical components"
},
{
"path": "tests/shell_workspace_test.cpp",
- "sha256": "4dc5ca08b38543de404e96a6a94cfcbac3483c9be89a6f1a0e877dac4cafeb84",
+ "sha256": "2be702d0a1bed5ec74488866d3d7d47416ec5740c200e6d3d1972845c0311fcd",
"anchor": "Actual workspace binding"
}
],
"omissions": [],
"remaining": [
- "Complete board firmware CI and separately authorized live device acceptance; no production deployment in this source integration."
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
]
},
{
@@ -26561,6 +26561,98 @@
"remaining": [
"Complete carrier/service parity and real deployed interoperability, timing, stack/RAM and physical acceptance separately."
]
+ },
+ {
+ "origin": "portable-shell",
+ "path": "include/portable_meter.h",
+ "targets": [
+ {
+ "path": "firmware/esp/src/portable-shell/portable_meter.h",
+ "sha256": "48350a03fd496aaf50bf47149f8a099d7d07de9175024ef43a917e0a7cf53f1d",
+ "anchor": "#ifndef"
+ }
+ ],
+ "proof": [],
+ "omissions": [],
+ "sha256": "48350a03fd496aaf50bf47149f8a099d7d07de9175024ef43a917e0a7cf53f1d",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
+ "source_read": "complete",
+ "verdict": "equivalent-file",
+ "preserved": [
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
+ ],
+ "remaining": [
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
+ ]
+ },
+ {
+ "origin": "portable-shell",
+ "path": "include/portable_undo.h",
+ "targets": [
+ {
+ "path": "firmware/esp/src/portable-shell/portable_undo.h",
+ "sha256": "adf67d72d371b0e15fb1be3c64dfdea5155840b55a23a987be5807437fed9be0",
+ "anchor": "#ifndef"
+ }
+ ],
+ "proof": [],
+ "omissions": [],
+ "sha256": "adf67d72d371b0e15fb1be3c64dfdea5155840b55a23a987be5807437fed9be0",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
+ "source_read": "complete",
+ "verdict": "equivalent-file",
+ "preserved": [
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
+ ],
+ "remaining": [
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
+ ]
+ },
+ {
+ "origin": "portable-shell",
+ "path": "include/portable_util.h",
+ "targets": [
+ {
+ "path": "firmware/esp/src/portable-shell/portable_util.h",
+ "sha256": "568c68488cc8821e694b2ba576f138dc4d0d6f8f9970e83b99188b7727014256",
+ "anchor": "#ifndef"
+ }
+ ],
+ "proof": [],
+ "omissions": [],
+ "sha256": "568c68488cc8821e694b2ba576f138dc4d0d6f8f9970e83b99188b7727014256",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
+ "source_read": "complete",
+ "verdict": "equivalent-file",
+ "preserved": [
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
+ ],
+ "remaining": [
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
+ ]
+ },
+ {
+ "origin": "portable-shell",
+ "path": "include/portable_table_policy.h",
+ "targets": [
+ {
+ "path": "firmware/esp/src/portable-shell/portable_table_policy.h",
+ "sha256": "eed0e170b4b80284b0cbbda87c7998480ec6d4040192d0402ca5af112ddfec04",
+ "anchor": "#ifndef"
+ }
+ ],
+ "proof": [],
+ "omissions": [],
+ "sha256": "eed0e170b4b80284b0cbbda87c7998480ec6d4040192d0402ca5af112ddfec04",
+ "source_revision": "3a7eeba179c238226deef1b64e3bd3b86c4e12e7",
+ "source_read": "complete",
+ "verdict": "equivalent-file",
+ "preserved": [
+ "Exact canonical component refresh at 3a7eeba179c238226deef1b64e3bd3b86c4e12e7. Reviewed upstream changes and required utility/undo/meter/selection-policy dependencies. Preserve consumer authentication, command dispatch and hardware callbacks; one canonical renderer replaces the old reduced fork."
+ ],
+ "remaining": [
+ "Issue #29: firmware compilation and live USB/Telnet/board acceptance remain pending. Full-session F7/history expansion/hex editor are not part of this standalone header component integration."
+ ]
}
]
}
diff --git a/docs/file-comparison.csv b/docs/file-comparison.csv
index 9d94b7c..f165e30 100644
--- a/docs/file-comparison.csv
+++ b/docs/file-comparison.csv
@@ -15,7 +15,7 @@
"ControllerBoardMini","Timer/LocalLib/TonePlayer.h","filesystem-snapshot","9f2aea4eadd0a4112ba62d4bdac906a9749026aa4e69404c2ff3169667f204df","firmware-hardware","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/6","scoped-improvement","complete","firmware/avr/TonePlayer.h:19 [UTF8-LF sha256=c102c4d0a789f9d19e8c94db92001caf489beca04e65dead01186d91998c2cde; anchor=bool enqueue] | firmware/avr/TonePlayer.cpp:87 [UTF8-LF sha256=6c578fb07b1c5059d1d03690beed73806a77b9d28cd05359d9f225b945275ed1; anchor=startsAt = stepEndsAt_]","Read the complete byte-identical Timer/Puzzles header. Ten queued tones/pauses, PB1/D9 buzzer ownership and pitch constants remain available.","waitForTonesEnd blocking loop [replaced] | global queue/functions and millis()+duration deadline [replaced]","waitForTonesEnd blocking loop: Nonblocking update/isBusy preserves completion while UART, input and front-panel services continue. | global queue/functions and millis()+duration deadline: Object ownership plus rollover-safe anchored deadlines prevents namespace collision, drift and loop starvation.","tests/tone_player_test.cpp:40 [UTF8-LF sha256=d1d890f6ead81d22702eb685ae710935bc7c007af5f97236454b851a3f5adc42; anchor=player.update(start+270)]","Hardware compare toggling and nonblocking anchored deadlines retain cues while keeping the real-time control loop responsive.","Register tests are not audible clock, melody-synchronization or transducer proof.","Not established by source/hash/test-code comparison; see issue #2"
"ControllerBoardMini","Timer/LocalLib/WS2811.cpp","filesystem-snapshot","0f7fec7879abd26dbf2d56df28441727ea76fcb9e093f009d39f20cae77832bb","firmware-hardware","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/6","parity-gap","complete","firmware/esp/src/LedGrid.cpp:34 [UTF8-LF sha256=9e08798277b72ee4420add3d3dc530dce83bf507e824a088439c7dc123211793; anchor=FastLED.addLeds] | firmware/esp/src/ShellNames.h:12 [UTF8-LF sha256=d5900e6687a9930fc4fb173c5be73d7f976040eb99a7a39d54e0015f421859e7; anchor=inline bool sameName]","DeviceParameter name/type/read-only/get/set design is adapted to ShellProperty descriptors for hostname/label, network mode/DHCP/static addressing, station SSID/write-only password, MQTT/WebSocket endpoints, telemetry interval, shell settings and LED mode/brightness/effect. | Read-only observations expose version, boot identity, uptime, heap, AVR presence, display ACK state, firmware phase and actual LED backend/RMT/DMA capability; no shell-specific LED engine or second configuration store is introduced. | Useful echo/status/connect behavior maps to echo/status and queued network reconnect; typed JSON builds route device/cell/bulk/pixel/audio/display/firmware actions through existing validators and maintenance owners instead of PU callback addresses. | Durable configuration owners apply validated updates rather than copying the donor manual Settings_Write/Settings_Read lifecycle or direct ESP8266 SDK network structures.","Station_Password/AP_Password getters returning plaintext credentials [replaced] | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks [replaced] | Direct wifi_station/softap config mutation, AssignResult into the global command buffer [replaced] | AP_SSID/AP_Password descriptors and legacy save/restore aliases [pending] | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries [pending]","Station_Password/AP_Password getters returning plaintext credentials: Secret descriptors are write-only and display /[hidden]; classified secret commands and URL credentials are excluded from history. Copying plaintext getters would disclose credentials over Telnet and framed USB. | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks: Generic flow/busy/ready/audio intent is retained through existing AVR flow/maintenance ownership and finite typed cues, not excluded as business. Only legacy PU wire addresses and unowned byte-to-buzzer callbacks are replaced; physical timing/audio acceptance stays open. | Direct wifi_station/softap config mutation, AssignResult into the global command buffer: Shared typed JSON configuration owners validate and persist updates without truncating into the current command buffer or mutating ESP8266-only SDK structures. The generic descriptor/read-only mechanism itself is retained. | AP_SSID/AP_Password descriptors and legacy save/restore aliases: AP credential editing and reviewed restore semantics remain useful gaps. Actual Firmware_Hash/Build_Date descriptor/getter code now serves read-only firmware.hash/build.date with focused production-prefix fixtures; this is SDK sketch MD5/compiler time, not full-image integrity or installation time. | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.","tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf] | tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296] | tests/shell_tui_test.cpp:41 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Readonly never emits Set.] | tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)] | tests/shell_pixel_test.cpp:6 [UTF8-LF sha256=f914cdb8df67ea915d286187b89208be931cfde70cac255a10b30e0d63b05690; anchor=int main()] | tests/shell_metadata_test.mjs:8 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const tuiKeys=]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Review remaining AP/restore descriptor gaps separately; hash/build getters now imported, while generic get/set source coverage does not certify every prior PU property.","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/Commands.h","431104072952f694a35b3a83c19f13fbb098ca5b","48c0deb76d6105090fe1d58a2449427681a88775b1e63ddbb1b28ecff6341961","device-shell","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=bool shellSet(] | firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=shellGet(property)] | firmware/esp/src/ShellSettings.h:20 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=bool configure(] | docs/PU850-PARITY.md:98 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Fresh runtime and shell section review, 2026-10-19]","DeviceParameter name/type/read-only/get/set design is adapted to ShellProperty descriptors for hostname/label, network mode/DHCP/static addressing, station SSID/write-only password, MQTT/WebSocket endpoints, telemetry interval, shell settings and LED mode/brightness/effect. | Read-only observations expose version, boot identity, uptime, heap, AVR presence, display ACK state, firmware phase and actual LED backend/RMT/DMA capability; no shell-specific LED engine or second configuration store is introduced. | Useful echo/status/connect behavior maps to echo/status and queued network reconnect; typed JSON builds route device/cell/bulk/pixel/audio/display/firmware actions through existing validators and maintenance owners instead of PU callback addresses. | Durable configuration owners apply validated updates rather than copying the donor manual Settings_Write/Settings_Read lifecycle or direct ESP8266 SDK network structures.","Station_Password/AP_Password getters returning plaintext credentials [replaced] | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks [replaced] | Direct wifi_station/softap config mutation, AssignResult into the global command buffer [replaced] | AP_SSID/AP_Password descriptors and legacy save/restore aliases [pending] | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries [pending]","Station_Password/AP_Password getters returning plaintext credentials: Secret descriptors are write-only and display /[hidden]; classified secret commands and URL credentials are excluded from history. Copying plaintext getters would disclose credentials over Telnet and framed USB. | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks: Generic flow/busy/ready/audio intent is retained through existing AVR flow/maintenance ownership and finite typed cues, not excluded as business. Only legacy PU wire addresses and unowned byte-to-buzzer callbacks are replaced; physical timing/audio acceptance stays open. | Direct wifi_station/softap config mutation, AssignResult into the global command buffer: Shared typed JSON configuration owners validate and persist updates without truncating into the current command buffer or mutating ESP8266-only SDK structures. The generic descriptor/read-only mechanism itself is retained. | AP_SSID/AP_Password descriptors and legacy save/restore aliases: AP credential editing and reviewed restore semantics remain useful gaps. Actual Firmware_Hash/Build_Date descriptor/getter code now serves read-only firmware.hash/build.date with focused production-prefix fixtures; this is SDK sketch MD5/compiler time, not full-image integrity or installation time. | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.","tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf] | tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296] | tests/shell_tui_test.cpp:41 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Readonly never emits Set.] | tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Review remaining AP/restore descriptor gaps separately; hash/build getters now imported, while generic get/set source coverage does not certify every prior PU property.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Commands.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","5181d7cc83efe2416a3a198ff88aad2aeffc8871ee2ef7a2a1ffdad51d31b88e","device-shell | application-identity","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/10","parity-gap","complete","firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=bool shellSet(] | firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=shellGet(property)] | firmware/esp/src/ShellSettings.h:20 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=bool configure(] | docs/PU850-PARITY.md:98 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Fresh runtime and shell section review, 2026-10-19] | firmware/esp/src/ShellPixelCommand.h:14 [UTF8-LF sha256=a5a9f5a43035578d904c547848429e71acd68867418c36f7e5fb1d6375ad32e6; anchor=template] | firmware/esp/src/ShellNames.h:12 [UTF8-LF sha256=d5900e6687a9930fc4fb173c5be73d7f976040eb99a7a39d54e0015f421859e7; anchor=inline bool sameName]","DeviceParameter name/type/read-only/get/set design is adapted to ShellProperty descriptors for hostname/label, network mode/DHCP/static addressing, station SSID/write-only password, MQTT/WebSocket endpoints, telemetry interval, shell settings and LED mode/brightness/effect. | Read-only observations expose version, boot identity, uptime, heap, AVR presence, display ACK state, firmware phase and actual LED backend/RMT/DMA capability; no shell-specific LED engine or second configuration store is introduced. | Useful echo/status/connect behavior maps to echo/status and queued network reconnect; typed JSON builds route device/cell/bulk/pixel/audio/display/firmware actions through existing validators and maintenance owners instead of PU callback addresses. | Durable configuration owners apply validated updates rather than copying the donor manual Settings_Write/Settings_Read lifecycle or direct ESP8266 SDK network structures.","Station_Password/AP_Password getters returning plaintext credentials [replaced] | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks [replaced] | Direct wifi_station/softap config mutation, AssignResult into the global command buffer [replaced] | AP_SSID/AP_Password descriptors and legacy save/restore aliases [pending] | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries [pending]","Station_Password/AP_Password getters returning plaintext credentials: Secret descriptors are write-only and display /[hidden]; classified secret commands and URL credentials are excluded from history. Copying plaintext getters would disclose credentials over Telnet and framed USB. | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks: Generic flow/busy/ready/audio intent is retained through existing AVR flow/maintenance ownership and finite typed cues, not excluded as business. Only legacy PU wire addresses and unowned byte-to-buzzer callbacks are replaced; physical timing/audio acceptance stays open. | Direct wifi_station/softap config mutation, AssignResult into the global command buffer: Shared typed JSON configuration owners validate and persist updates without truncating into the current command buffer or mutating ESP8266-only SDK structures. The generic descriptor/read-only mechanism itself is retained. | AP_SSID/AP_Password descriptors and legacy save/restore aliases: AP credential editing and reviewed restore semantics remain useful gaps. Actual Firmware_Hash/Build_Date descriptor/getter code now serves read-only firmware.hash/build.date with focused production-prefix fixtures; this is SDK sketch MD5/compiler time, not full-image integrity or installation time. | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.","tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf] | tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296] | tests/shell_tui_test.cpp:50 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Readonly never emits Set.] | tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)] | tests/shell_pixel_test.cpp:6 [UTF8-LF sha256=f914cdb8df67ea915d286187b89208be931cfde70cac255a10b30e0d63b05690; anchor=int main()] | tests/shell_metadata_test.mjs:8 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const tuiKeys=]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Review remaining AP/restore descriptor gaps separately; hash/build getters now imported, while generic get/set source coverage does not certify every prior PU property.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Commands.h","431104072952f694a35b3a83c19f13fbb098ca5b","48c0deb76d6105090fe1d58a2449427681a88775b1e63ddbb1b28ecff6341961","device-shell","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/ShellService.h:119 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=bool shellSet(] | firmware/esp/src/ShellTuiBinding.h:66 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=shellGet(property)] | firmware/esp/src/ShellSettings.h:20 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=bool configure(] | docs/PU850-PARITY.md:98 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Fresh runtime and shell section review, 2026-10-19]","DeviceParameter name/type/read-only/get/set design is adapted to ShellProperty descriptors for hostname/label, network mode/DHCP/static addressing, station SSID/write-only password, MQTT/WebSocket endpoints, telemetry interval, shell settings and LED mode/brightness/effect. | Read-only observations expose version, boot identity, uptime, heap, AVR presence, display ACK state, firmware phase and actual LED backend/RMT/DMA capability; no shell-specific LED engine or second configuration store is introduced. | Useful echo/status/connect behavior maps to echo/status and queued network reconnect; typed JSON builds route device/cell/bulk/pixel/audio/display/firmware actions through existing validators and maintenance owners instead of PU callback addresses. | Durable configuration owners apply validated updates rather than copying the donor manual Settings_Write/Settings_Read lifecycle or direct ESP8266 SDK network structures.","Station_Password/AP_Password getters returning plaintext credentials [replaced] | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks [replaced] | Direct wifi_station/softap config mutation, AssignResult into the global command buffer [replaced] | AP_SSID/AP_Password descriptors and legacy save/restore aliases [pending] | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries [pending]","Station_Password/AP_Password getters returning plaintext credentials: Secret descriptors are write-only and display /[hidden]; classified secret commands and URL credentials are excluded from history. Copying plaintext getters would disclose credentials over Telnet and framed USB. | busy/ready SendESPStatus_ToPU and Request_Beep PU protocol callbacks: Generic flow/busy/ready/audio intent is retained through existing AVR flow/maintenance ownership and finite typed cues, not excluded as business. Only legacy PU wire addresses and unowned byte-to-buzzer callbacks are replaced; physical timing/audio acceptance stays open. | Direct wifi_station/softap config mutation, AssignResult into the global command buffer: Shared typed JSON configuration owners validate and persist updates without truncating into the current command buffer or mutating ESP8266-only SDK structures. The generic descriptor/read-only mechanism itself is retained. | AP_SSID/AP_Password descriptors and legacy save/restore aliases: AP credential editing and reviewed restore semantics remain useful gaps. Actual Firmware_Hash/Build_Date descriptor/getter code now serves read-only firmware.hash/build.date with focused production-prefix fixtures; this is SDK sketch MD5/compiler time, not full-image integrity or installation time. | Commented AP_IP/AP_Gateway/AP_Subnet parameter entries: The donor leaves these entries commented out. Pakar station static-address fields are implemented, but independent configurable AP address/gateway/subnet via the shell is not claimed complete.","tests/shell_metadata_test.mjs:11 [UTF8-LF sha256=a2d96c172d01d99f67fee1a7da1b341a2583cbf1c7133bc4308d3b8dfc07f01a; anchor=const start=source.indexOf] | tests/shell_wire_test.cpp:29 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=4294967296] | tests/shell_tui_test.cpp:50 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Readonly never emits Set.] | tests/telnet_shell_test.cpp:88 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=!editor.remember(true)]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Review remaining AP/restore descriptor gaps separately; hash/build getters now imported, while generic get/set source coverage does not certify every prior PU property.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/CrashHandler.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","be001f01f3467950ec27b578a16c391d4d680e1bb562e3d8c06fb422d28cc5c9","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: active crash_ets_uart_putc1; complete commented custom_crash_callback timestamp/reset/register offsets/counter/cursor/stack heuristic | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","commented crash persistence/stack-capture sketch [pending]","commented crash persistence/stack-capture sketch: No active donor persistence implementation exists. Design bounded secret-safe platform capture/rotation and fault proof; keep S3 SDK panic/reset evidence rather than importing ESP8266 suppression.","","","No active donor persistence implementation exists. Design bounded secret-safe platform capture/rotation and fault proof; keep S3 SDK panic/reset evidence rather than importing ESP8266 suppression.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/CrashHandler.h","431104072952f694a35b3a83c19f13fbb098ca5b","d958445204e65686693ab766a3b090095585ed538c374139b7baf5f61c91785a","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: pragma/include; NO_CUT_HERE; crash printer declaration; commented custom callback declaration | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","Generic durable crash capture contract [pending]","Generic durable crash capture contract: Do not transplant ESP8266 callback structs/cont registers into S3; safe storage/rotation and fault acceptance remain open.","","","Do not transplant ESP8266 callback structs/cont registers into S3; safe storage/rotation and fault acceptance remain open.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/FirmwareTransfer.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","1dada0f284ba4c81a6185f1aeb34dbd299752a7b48b0ba56336d1dfdc9e2ac3c","pu-routes | ethernet | remote-ota | updates-telemetry","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/7","parity-gap","complete","firmware/esp/src/FirmwareTransfer.cpp:173 [UTF8-LF sha256=98dff31984a81dcfb769fb036664e711c4312b257baefbdcbbac92168799d4de; anchor=void RegisterFirmwareTransferRoutes(] | firmware/esp/src/FirmwareReadWindow.h:11 [UTF8-LF sha256=d67a961994aefd8eb3c9df1b0e5cd7e04529097e707b41988f9e4e5c00e07a88; anchor=class ReadWindow] | firmware/esp/src/FirmwareReadResponse.h:13 [UTF8-LF sha256=1671071b0a3fa1d036abec6289ba4f924f9ab52fc968b9f6b9c29c6f7d72cbfd; anchor=class ReadResponse final] | firmware/esp/src/FirmwareRange.h:26 [UTF8-LF sha256=03bf18b46233e539a84563f1eb63330d2eaf8e57b9bb73b6cfd92026bda6bef8; anchor=inline bool parseRange(] | firmware/esp/src/FirmwarePlatform.h:143 [UTF8-LF sha256=ad3e975a14f0f7b7d78446bf94217fbb2fc2770e984ad4979d7bcce142b75e8d; anchor=inline bool readRunning(] | tools/check-ethernet-live.mjs:71 [UTF8-LF sha256=69c61ad7a70fc4a49487ad89abb21e855cbda69da46d0b872bc8161f09c6b87c; anchor=export async function checkEthernet(] | desktop/src/bridge_transfer.rs:54 [UTF8-LF sha256=fbb13202d86296f1766d0e20bcd9e4a520a8b13d4988890e5a5273a20a5469fe; anchor=pub(super) fn open]","Complete actual donor read through EOF and current adapted production target reviewed. Imported GET/HEAD firmware download, bounded single byte ranges/open/suffix ranges, 416 responses, ETag/304, Content-MD5/Digest/identity headers and chunked flash reads; extracted parser exercises the production implementation. | ESP8266 aligned flash implementation is retained. ESP32-S3 adaptation downloads only the running application partition using bounded esp_partition_read, uses mbedtls Base64 and platform model, and shares the same endpoint/HTTP engine instead of a second flasher firmware. | Reconsulted complete donor for W5500 integration. Existing native ETH/lwIP shares the very same firmware HEAD/Range route; the hostname-first live probe checks actual TCP interface identity and exported image framing instead of assuming carrier means correct serving. | Reconsulted full FirmwareTransfer implementation/header for native network repeater. Preserve opaque binary GET/HEAD/Range/ETag and actual multipart upload status instead of JSON reparsing. Bound export/body sizes, selected route/boot/trace ownership and no-replay outcomes; USB never silently opens Wi-Fi. | Export recovery adapts the actual installed ESPAsyncWebServer3.12.1 response boundary: transient guarded flash-read timeout returns RESPONSE_TRY_AGAIN at the same offset, bounded by five failures or two seconds; persistent/fatal errors invalidate the response source so the SDK closes a failed transport instead of treating zero as successful EOF. Loop-owned admission retains a cross-actor-safe scoped reader lease through response destruction and accepts an immutable boot-verified identity snapshot rather than rehashing the full image per GET. This concrete source defect is repaired; the earlier observed live truncation's precise cause remains unmeasured.","Optional build-time FW_OTA_BEARER_TOKEN/secureEquals and WWW-Authenticate bearer realm [replaced] | FW_ENABLE_FULL_FLASH_DOWNLOAD full raw flash including settings [replaced] | ProductConfig PU model/product identity and compile date/time header [replaced] | readFlashRange failure returning0 from an in-flight asynchronous fixed-length response [replaced]","Optional build-time FW_OTA_BEARER_TOKEN/secureEquals and WWW-Authenticate bearer realm: Shared Pakar WebAccess callback now owns authorization and defaults to deny without an authorization provider. Credentials/session policy must not diverge between firmware download and other device endpoints. X-Login-Path links the actual login flow without provoking unexplained browser Basic authentication. | FW_ENABLE_FULL_FLASH_DOWNLOAD full raw flash including settings: Network full-flash export remains disabled because it can expose stored Wi-Fi/cloud/access secrets. Application-partition download and separately authorized backup paths are retained. User-authorized physical full flash backup is separate, not this network route. | ProductConfig PU model/product identity and compile date/time header: Uses Pakar product/model and consistent BuildInfo version without dates/timestamp naming; no PU850 business identity is retained. | readFlashRange failure returning0 from an in-flight asynchronous fixed-length response: Zero is SDK end-of-file, not a transient flash error. A bounded retry/read-window plus source-invalid transport failure preserves advertised length/integrity semantics without advancing offsets, silently truncating, retaining dangling requests or retrying any mutation. Raw app-image authorization/range/ETag/HEAD behavior is unchanged; full-flash secrets remain disabled.","tests/firmware_read_response_test.cpp:41 [UTF8-LF sha256=4e2dfe3020a82e402654d607dfb9fb73927a10735f47c5cdb2d09a3f93bea1a0; anchor=real SDK sets RESPONSE_FAILED + closes, never EOF] | tests/firmware_range_test.cpp:7 [UTF8-LF sha256=96a79254872cd829c6e23a240225063503d0286e9fe1a477dd48aac5e05a6ed5; anchor=assert(parseRange(""bytes=0-9""] | tests/s3_platform_wiring_test.mjs:44 [UTF8-LF sha256=8b362da60381843154a12cc07b0c0401ec3885eb4cba2f0a2ed7815496dff6c3; anchor=assert.match(transfer, /esp_partition_read] | tests/ethernet_live_tool_test.mjs:27 [UTF8-LF sha256=1158993fe22255b9fdfd218b0c7ead67398741e0e6e7094fcf160f721fcb0efd; anchor=for(const scenario of [] | desktop/src/bridge_transfer.rs:275 [UTF8-LF sha256=fbb13202d86296f1766d0e20bcd9e4a520a8b13d4988890e5a5273a20a5469fe; anchor=fn bridge_transfer_real_binary_range_and_multipart_are_not_json_reparsed]","","Real HTTP GET/HEAD/Range/auth/ETag checks against the integrated S3 and ESP8266 firmware remain separate from source/host-parser proof; verify exported application bytes against the running partition and protect credentials. Physical OTA/AVR flashing/power-loss/recovery acceptance remains open.","Not established by source/hash/test-code comparison; see issue #2"
@@ -2339,17 +2343,17 @@
"PU850-ESP","LocalLib/NetDiscovery.h","431104072952f694a35b3a83c19f13fbb098ca5b","405ee3b6854ad478d58811b1bb27ab69ceae490e31673aa4daf2512017a29b66","lan-link-discovery | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/19","parity-gap","complete","firmware/esp32s3/src/NbnsWire.h:26 [UTF8-LF sha256=5ecc7462855057585a28b732216798637158e5b830863b546490859ab8110699; anchor=inline size_t answer(] | firmware/esp32s3/src/NbnsService.h:14 [UTF8-LF sha256=01d4a3c989bb3adaed24b13fb9e7923b99756bcb117e030428a8efab46c01d03; anchor=class NbnsService] | firmware/esp32s3/src/Discovery.h:42 [UTF8-LF sha256=d2cd172d2934642d6255106edb2a417a5bf128ff021dd353ad4de57262277cd0; anchor=bool netbiosReady() const] | firmware/esp32s3/src/Lldp.h:46 [UTF8-LF sha256=1c485dc2ee5fcaceefc2ac815a022a7fab4e5aa226d36256734ffb293c199060; anchor=inline bool parse(] | firmware/esp32s3/src/EthernetLldp.cpp:136 [UTF8-LF sha256=753dd01083136f14e44d862fa883c916bbf6c971a64b8762dc1cc14c207c7e2c; anchor=void serviceEthernetLldp(] | tools/check-router-upnp.ps1:60 [UTF8-LF sha256=91700b6cbeb7717b9d72b07247f2044f7a09cb8450753b3e49429046c61d5a39; anchor=function Invoke-DiscoveryProbe(]","Completely read the actual donor source/header through EOF. NetDiscovery.cpp contains experimental ND_ACK/ANNOUNCE handlers and an infinite one-second-delay loop with last-octet identity; the header declarations/configuration are commented out. These are not a reusable NetBIOS or LLDP implementation. Retain the useful LAN discovery intent using actual product identity and bounded per-interface services, not a claimed byte-for-byte port. | New S3 NBNS adapts the installed Arduino3.3.12/ESP8266 first-level name encoding plus RFC1002 bounded NB/NBSTAT messages into loop-owned nonblocking exact-netif UDP137 listeners; no task-owned AsyncUDP buffer, silent hostname truncation, SMB advertisement or wrong-subnet reply. LLDP is a separate pinned-IDF native Ethernet link diagnostic, not donor ND protocol parity.","Experimental ND_ACK/ANNOUNCE application protocol, last-octet ACK identity, infinite delay loop and commented header configuration [pending] | NBNS WINS registration/conflict arbitration, SMB sessions and LLMNR [pending] | Automatic IGD port forwarding and router login [pending]","Experimental ND_ACK/ANNOUNCE application protocol, last-octet ACK identity, infinite delay loop and commented header configuration: Do not stall the integrated LED/API/UART loop or substitute an unauthenticated last-octet peer identity. Review any real ND peer consumer before a bounded product-identified compatibility adapter; useful custom ND semantics are not silently classified as business logic. | NBNS WINS registration/conflict arbitration, SMB sessions and LLMNR: This slice provides local NB name/node-status replies only. It does not promise WINS registration or implement a file-sharing/session server; conflict arbitration and LLMNR need their own interface-bound runtime acceptance. | Automatic IGD port forwarding and router login: Observed router advertises no IGD service yet; private label-based router credentials and isolated temporary mapping approval are pending. Do not expose auth-disabled board HTTP or Telnet to WAN, reset a router or guess credentials.","tests/nbns_test.cpp:66 [UTF8-LF sha256=4e4a84d2f731e50ae717805b32cb3acd5be00bdfa918894e9680779c23e28d48; anchor=Actual NBNS codec/service] | tests/ethernet_lldp_test.cpp:17 [UTF8-LF sha256=6d4d5656df1d2defdcafa0527b8cdd058f6c2fb8df1fba3baa66461f1d27fff8; anchor=void pure()] | tests/router_upnp_test.ps1:18 [UTF8-LF sha256=3fadc7e454cb0bbac934eb0363073044eb7e61ee861833eb9d1fcf5c1b7b3a4f; anchor=foreach($action]","","Actual Windows NBNS name/node-status and all-netif overlap/rename tests require deployment. Actual W5500 wiring/link/LLDP-switch visibility, Ethernet-only API/WebSocket/OTA/cloud and unplug/replug acceptance remain open; host doubles and an SDK build are not physical proof. | Source provenance for the LLDP shim is the installed Arduino3.3.12/IDF5.5.5 native glue and W5500 driver: standard input callback preserved exactly, TCPIP-thread TX, explicit opt-in promiscuous RX because hardware MAC filtering rejects LLDP multicast. No arbitrary raw-frame API, layer2 bridge or router capability is advertised.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/Public.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","9cda4aa1a1d02d3a7f8042173e049d326788952b7a05438a57b91d6e7d381303","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: UnPack32 four MSB-first shifts; eExpandDateTime/eCompressDateTime; CompressDate/ShamsiU16toU8/GregorianU16toU8 | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","Generic compressed Gregorian/Shamsi date/time and year offsets [pending]","Generic compressed Gregorian/Shamsi date/time and year offsets: Preserve useful calendar capability with validated real consumers/ranges/months/tests, not shared mutable ePart/year globals or PU payload compatibility.","","","Preserve useful calendar capability with validated real consumers/ranges/months/tests, not shared mutable ePart/year globals or PU payload compatibility.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/Public.h","431104072952f694a35b3a83c19f13fbb098ca5b","a5e504e4b33c8b1e26d41a7e5a8196fc1f7e7ae7a41be6209dc5617e484a83af","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","equivalent-behavior","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: stdbool include and bool-to-boolean/true/false replacement macros | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","C++ boolean token replacement [replaced]","C++ boolean token replacement: The supported toolchains natively implement bool/true/false; global macro substitution can corrupt SDK/system headers.","","","","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/RemoteCommand.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","9ca2a3d6be55cc5d0271d0e036e28fbc6a25ed71c276f53128262a03d30e9e52","udp-diagnostic-query | pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/29","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19] | firmware/esp/src/ShellRemoteQuery.h:11 [UTF8-LF sha256=d5c21db264e3bc5519ca63c0ff7568183dc5b321ce6a6842468052068449c0e4; anchor=class RemoteQuery] | firmware/esp/src/ShellService.h:20 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceShellRemoteQuery()] | tools/udp-device-query.mjs:10 [UTF8-LF sha256=1d0c45a2f2f05a2564ed9a80d116fc52c4564e0e79b07cebb09ee8bd872e7e25; anchor=const nonce=]","Every section/function read through EOF: all UDP state/ports; initUdpDebug/UdpService/udpSend/handleUdpReceive/executeCommand; all ServiceEvent cases | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice. | initUdpDebug/UdpService receive/reply lifecycle adapted in ShellRemoteQuery: explicit opt-in public read-only UDP, fixed port 9876, network-address rebinding, bounded datagrams, correlated unicast responses and bind backoff.","Generic bounded UDP diagnostic/network-event lifecycle [replaced]","Generic bounded UDP diagnostic/network-event lifecycle: The disabled donor listener is now an opt-in public read-only query with correlation, bounded input/output, rate limiting, unicast response and lifecycle fixtures. Auth-enabled policy disables this UDP surface. Arbitrary command execution, UDP authentication/ownership and live LAN acceptance remain pending.","tests/shell_remote_query_test.cpp:17 [UTF8-LF sha256=5d3a8434ea40cc4d5458f739eb5c23524d5d586bda1af129859d0980accb16b3; anchor=int main()] | tests/udp_device_query_test.mjs:20 [UTF8-LF sha256=45cf835ed865cdcd02a6cb0458c07d17815b7b5b0299475e7a9e5d9d9c4d6f19; anchor=assert.equal(requests,1)]","","Read-only UDP candidate requires actual LAN/AP/Ethernet tests; host socket fixtures do not establish board reachability. | Authenticated mutating UDP commands are not implemented: use existing protected Telnet/USB/HTTP/WebSocket routing; never copy donor unauthenticated dispatch.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/RemoteCommand.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","9ca2a3d6be55cc5d0271d0e036e28fbc6a25ed71c276f53128262a03d30e9e52","udp-diagnostic-query | pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/29","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19] | firmware/esp/src/ShellRemoteQuery.h:11 [UTF8-LF sha256=d5c21db264e3bc5519ca63c0ff7568183dc5b321ce6a6842468052068449c0e4; anchor=class RemoteQuery] | firmware/esp/src/ShellService.h:20 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceShellRemoteQuery()] | tools/udp-device-query.mjs:10 [UTF8-LF sha256=1d0c45a2f2f05a2564ed9a80d116fc52c4564e0e79b07cebb09ee8bd872e7e25; anchor=const nonce=]","Every section/function read through EOF: all UDP state/ports; initUdpDebug/UdpService/udpSend/handleUdpReceive/executeCommand; all ServiceEvent cases | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice. | initUdpDebug/UdpService receive/reply lifecycle adapted in ShellRemoteQuery: explicit opt-in public read-only UDP, fixed port 9876, network-address rebinding, bounded datagrams, correlated unicast responses and bind backoff.","Generic bounded UDP diagnostic/network-event lifecycle [replaced]","Generic bounded UDP diagnostic/network-event lifecycle: The disabled donor listener is now an opt-in public read-only query with correlation, bounded input/output, rate limiting, unicast response and lifecycle fixtures. Auth-enabled policy disables this UDP surface. Arbitrary command execution, UDP authentication/ownership and live LAN acceptance remain pending.","tests/shell_remote_query_test.cpp:17 [UTF8-LF sha256=5d3a8434ea40cc4d5458f739eb5c23524d5d586bda1af129859d0980accb16b3; anchor=int main()] | tests/udp_device_query_test.mjs:20 [UTF8-LF sha256=45cf835ed865cdcd02a6cb0458c07d17815b7b5b0299475e7a9e5d9d9c4d6f19; anchor=assert.equal(requests,1)]","","Read-only UDP candidate requires actual LAN/AP/Ethernet tests; host socket fixtures do not establish board reachability. | Authenticated mutating UDP commands are not implemented: use existing protected Telnet/USB/HTTP/WebSocket routing; never copy donor unauthenticated dispatch.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/Sessions.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","6186f9861e8ee3dcb093f260e25311a1e3952b5c6c2a7fa764de05b8a45e7f55","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","libraries/BoardCore/src/AccessPolicy.h:39 [UTF8-LF sha256=3524ee6acd036d7ef30e266d55fd71045628abe2c57243383162dea8e90c55d0; anchor=class Sessions] | docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: initSessions; addSession free-slot and unused-oldest scan; destroySession; validateSession; empty SessionCommand | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","SessionCommand authenticated protocol-origin revoke semantics [pending]","SessionCommand authenticated protocol-origin revoke semantics: Existing bounded 8-slot AccessPolicy imports the pool with expiry, unsigned elapsed time, strict hex and constant-work compare. Donor SessionCommand is empty; review actual UART-origin authority before implementing a new command.","tests/access_policy_test.cpp:13 [UTF8-LF sha256=96b2ca0669e125a08354779919684c192e71e7a2a6e760202eda653f5824c737; anchor=Sessions sessions;]","","Existing bounded 8-slot AccessPolicy imports the pool with expiry, unsigned elapsed time, strict hex and constant-work compare. Donor SessionCommand is empty; review actual UART-origin authority before implementing a new command.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/Sessions.h","431104072952f694a35b3a83c19f13fbb098ca5b","1ab8ea8f32ce56d64b976eb1c6922fa5476a3fd0498526bbf453a4254726862c","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","libraries/BoardCore/src/AccessPolicy.h:39 [UTF8-LF sha256=3524ee6acd036d7ef30e266d55fd71045628abe2c57243383162dea8e90c55d0; anchor=class Sessions] | docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: MAX_SESSIONS/TOKEN_LENGTH; raw token/accessLevel/lastActive/lastIP/isLoggedIn struct/global array; SessionCommand declaration | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","SessionCommand lifecycle declaration [pending]","SessionCommand lifecycle declaration: Existing encapsulated AccessPolicy/WebAccess owns roles/address/expiry/mutex admission; exact authenticated UART session lifecycle is still pending.","tests/access_policy_test.cpp:13 [UTF8-LF sha256=96b2ca0669e125a08354779919684c192e71e7a2a6e760202eda653f5824c737; anchor=Sessions sessions;]","","Existing encapsulated AccessPolicy/WebAccess owns roles/address/expiry/mutex admission; exact authenticated UART session lifecycle is still pending.","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/Shell.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","0113acbff973285f5af186a4857d94cc2df3c99813e0db4ba876f861e7c1083b","ssh-transport | device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/33","parity-gap","complete","firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor] | firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]] | firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot] | tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false] | firmware/esp/src/SshShellBinding.h:37 [UTF8-LF sha256=a0333d0b81ed2196be82d6ce9e8e643789366f578b65d71a6e2490892382a560; anchor=void serviceSshShell()] | firmware/esp32s3/src/SshTransport.h:31 [UTF8-LF sha256=988641dc923aafab5ec72ce909067455c44faf468bb241d5b15b3bac1bba33d8; anchor=class SshTransport {] | firmware/esp32s3/src/SshPolicy.h:22 [UTF8-LF sha256=ba8a8f5807e07e52a88e4098d6a2ae7d915d9547f4a0e3b25e6700669c10aab2; anchor=inline size_t commandLength(] | firmware/esp32s3/src/ScpDownload.h:10 [UTF8-LF sha256=a5864de20e077f081c03ddce98e9a9dac3a78a46149b3618ae66f330a53aca8c; anchor=class ScpDownload {] | firmware/esp/src/SshScpBinding.h:11 [UTF8-LF sha256=9acf2193fac080639fc5460273b16a3ebc5a1022fb4c95f67df8c0dab22d8e4a; anchor=void serviceSshScp()] | firmware/esp32s3/src/ScpUpload.h:8 [UTF8-LF sha256=f1a8c0743f079973a4fdedee3ac4ad16b538e6a28253763280ecec6204a240b5; anchor=class ScpUpload] | firmware/esp32s3/src/SftpSession.h:14 [UTF8-LF sha256=ff44da0e5871e592909952eaa54b1f563dbb4ec88a867b280089c11709a081d6; anchor=class SftpSession] | firmware/esp32s3/src/FtpSession.h:10 [UTF8-LF sha256=0fe8968237417b2fb2a1fe40b0471bffb7c87d67c1114d5f16c04ba1c4f94dae; anchor=class FtpSession] | firmware/esp/src/TransferFiles.h:7 [UTF8-LF sha256=dea201ffe11e3516975ef7b2c7850aac74940514eb8c05ee7b521ff6698ba18d; anchor=class TransferFiles] | firmware/esp/src/FileTransferBinding.h:9 [UTF8-LF sha256=666f8822b937bcda527bbe9c121040e4ef2d32894e91057988450774f0d427b0; anchor=serviceFileTransferCleanup] | firmware/esp/src/FtpBinding.h:4 [UTF8-LF sha256=ee60fbc7b27399818c2d46c5c7401d1688c7e595d2a5fd9b52d458e65902adfc; anchor=namespace DeviceFtp] | firmware/esp/src/SshUploadBinding.h:12 [UTF8-LF sha256=563386de05c9b0190942e73c1eafdfa384d06438e69da5119834642cab357b77; anchor=void resetSshTransfer]","PU ShellSession transport/editor separation, insertion/shift/backspace/delete, CSI/SS3 arrows/home/end, Ctrl-C/Ctrl-L and split CR-LF/CR-NUL behavior are adapted in instance-owned TelnetParser/LineEditor. | Generic parameter lookup/read-only enforcement and dispatch now map to ShellProperty descriptors, typed shellSet and the existing Pakar command/configuration owners; the same descriptors feed the device TUI. | Session-owned prompt-safe watch summaries use redrawEvent without losing the draft/cursor. Utilities clear/cls, echo, hex and upper/noupper are present; bounded eight-entry history adds recall without retaining classified secrets. | Raw serial ownership is replaced by an additive boot-bound COBS shell channel: explicit token, contiguous nonwrapping input sequence, duplicate-byte ACK without redispatch, and immutable output windows consumed only by matching ACK. | Issue #33 SSH exec reuses the same authenticated loop-owned DeviceShellSession/executeDeviceShell dispatcher. Bounded single commands have prompt-free output, nonzero failures and drained exit status despite stdin EOF; PTY tui/edit launches and exits the shared TUI directly, retaining resize, maintenance and security-revision fences. No operating-system shell or second command catalogue is introduced.","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter [replaced] | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add [replaced] | ShellBroadcastLine unrestricted event broadcast [replaced] | upper/noupper applies to all donor input echo and prompt redraw [replaced] | Donor printable-ASCII editor and richer Unicode terminals [pending]","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter: A separately boot-fenced COBS shell channel preserves USB recovery/protocol framing rather than mixing raw ASCII into the existing decoder. Instance-owned dispatch removes the donor global command/output pointer; host script never automatically replays uncertain input. | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add: Strict unsignedNumber validates full decimal input and overflow before conversion; command-specific signed display handling is explicit. Fixtures reject signs, commas, trailing junk and UINT32 overflow rather than silently normalizing a dangerous argument. | ShellBroadcastLine unrestricted event broadcast: Opt-in per-session watch publishes changed bounded summaries with prompt erase/redraw and backpressure. This is not an ordered complete peripheral event stream; individual event delivery remains outside this shell slice. | upper/noupper applies to all donor input echo and prompt redraw: Input echo, insertion, cursor redraw and history recall now uppercase only displayed bytes, preserving raw commands and secret suppression; portable_shell_test covers it. | Donor printable-ASCII editor and richer Unicode terminals: Arduino line editor/TUI deliberately accepts printable ASCII and sanitizes external controls. Persian/UTF-8 editing, bidi layout and font support are not supplied by this shell adaptation.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()] | tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(] | tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate] | tests/shell_tui_test.cpp:32 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Unchanged Enter is not a write.] | tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate] | tests/ssh_settings_test.cpp:17 [UTF8-LF sha256=44c6fda6dce749d7302d284c9f8ba1b9919b36fefd4e3e61fd00b665dfdf6ad2; anchor=int main()] | tests/ssh_exec_transport_test.cpp:91 [UTF8-LF sha256=615435469fa43f155f61ee84ce5f9fa847503a1f1d36360e963a24949b11a680; anchor=Actual SSH exec transport:] | tests/ssh_exec_binding_test.cpp:123 [UTF8-LF sha256=8d5b45f3825a81c91f8af77a263d87c37a4a53ec4f1399fca73f2490df9bbba0; anchor=Actual SSH shell exec binding/dispatcher:] | tests/ssh_exec_binding_test.mjs:9 [UTF8-LF sha256=8ad3023d3de3232cb50377d3aa81cf1f58b0f7e80c05ad97145d73346483bbb2; anchor=SSH exec production markers changed] | tests/ssh_policy_test.cpp:6 [UTF8-LF sha256=4e8b5abdd817ca1ecc2f3aaba1dfa8186acc125529e997a340a940f8cb73a73c; anchor=int main()] | tests/ssh_scp_binding_test.cpp:21 [UTF8-LF sha256=b47ef508eb652a9099ab0f29e33da744e83d23f1b452cc1c195b4118a3081412; anchor=int main()] | tests/scp_download_test.cpp:10 [UTF8-LF sha256=49b22f7ce4a962b1d5fd677ac3bc37bbc6aac8f41a5f7ac4ebcf9790caa79dd8; anchor=int main(] | tests/scp_client_test.mjs:15 [UTF8-LF sha256=bec216abf19d74af291ecba65e496d689e8ccdeb4e4c12f0707fcea2389b6a1d; anchor=Real OpenSSH SCP client accepted] | tests/file_transfer_test.cpp:22 [UTF8-LF sha256=b59d00420cac2a5eec4c863cfc5d60e1a93d4b1b041b8f90e1b01250a157dd8e; anchor=int main(] | tests/file_transfer_client_test.mjs:18 [UTF8-LF sha256=254834d63c0db3ac14d83db5cdb16c1a6773a6a9df78f7d270c38bcdc0d67ede; anchor=Real OpenSSH clients:] | tests/ftp_binding_test.cpp:61 [UTF8-LF sha256=442d67f43bf96b8031b6966c89ac9f9edf98d66afda048179242c734de502b3b; anchor=Actual FTP socket binding:] | tests/ftp_stubs/sys/socket.h:3 [UTF8-LF sha256=a03b479d9c3b25a90b08c8580d4a54ced5415914bd3ce805ef43706efa7b7a45; anchor=MSG_DONTWAIT] | tools/test-ssh.ps1:59 [UTF8-LF sha256=fc5bdf619662059e9373a54c608e3661aa9906f3f0a4e8cf5b5e36eeed5599e4; anchor=Actual FTP socket binding fixture]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Issue #33: native SSH plus gated transactional SCP/SFTP and passive FTP share the public-file store and maintenance lease. Policy/NVS/socket-FS doubles and real OpenSSH clients over stdio are not an encrypted board login. Exact footprint/SDK evidence belongs in the receipt and SSH.md; actual login, identity persistence, memory/TLS/LED coexistence and separately authorized live transfers/deployment remain open.","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/Shell.h","431104072952f694a35b3a83c19f13fbb098ca5b","9f8e76509ad917d8e204a82d9495e376607a159547d4eee3de6760e6bd5d7da6","ssh-transport | device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/33","parity-gap","complete","firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor] | firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]] | firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot] | tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false]","PU ShellSession transport/editor separation, insertion/shift/backspace/delete, CSI/SS3 arrows/home/end, Ctrl-C/Ctrl-L and split CR-LF/CR-NUL behavior are adapted in instance-owned TelnetParser/LineEditor. | Generic parameter lookup/read-only enforcement and dispatch now map to ShellProperty descriptors, typed shellSet and the existing Pakar command/configuration owners; the same descriptors feed the device TUI. | Session-owned prompt-safe watch summaries use redrawEvent without losing the draft/cursor. Utilities clear/cls, echo, hex and upper/noupper are present; bounded eight-entry history adds recall without retaining classified secrets. | Raw serial ownership is replaced by an additive boot-bound COBS shell channel: explicit token, contiguous nonwrapping input sequence, duplicate-byte ACK without redispatch, and immutable output windows consumed only by matching ACK.","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter [replaced] | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add [replaced] | ShellBroadcastLine unrestricted event broadcast [replaced] | upper/noupper applies to all donor input echo and prompt redraw [replaced] | Donor printable-ASCII editor and richer Unicode terminals [pending]","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter: A separately boot-fenced COBS shell channel preserves USB recovery/protocol framing rather than mixing raw ASCII into the existing decoder. Instance-owned dispatch removes the donor global command/output pointer; host script never automatically replays uncertain input. | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add: Strict unsignedNumber validates full decimal input and overflow before conversion; command-specific signed display handling is explicit. Fixtures reject signs, commas, trailing junk and UINT32 overflow rather than silently normalizing a dangerous argument. | ShellBroadcastLine unrestricted event broadcast: Opt-in per-session watch publishes changed bounded summaries with prompt erase/redraw and backpressure. This is not an ordered complete peripheral event stream; individual event delivery remains outside this shell slice. | upper/noupper applies to all donor input echo and prompt redraw: Input echo, insertion, cursor redraw and history recall now uppercase only displayed bytes, preserving raw commands and secret suppression; portable_shell_test covers it. | Donor printable-ASCII editor and richer Unicode terminals: Arduino line editor/TUI deliberately accepts printable ASCII and sanitizes external controls. Persian/UTF-8 editing, bidi layout and font support are not supplied by this shell adaptation.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()] | tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(] | tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate] | tests/shell_tui_test.cpp:32 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Unchanged Enter is not a write.] | tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Shell.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","0113acbff973285f5af186a4857d94cc2df3c99813e0db4ba876f861e7c1083b","ssh-transport | device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/33","parity-gap","complete","firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor] | firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]] | firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot] | tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false] | firmware/esp/src/SshShellBinding.h:37 [UTF8-LF sha256=a0333d0b81ed2196be82d6ce9e8e643789366f578b65d71a6e2490892382a560; anchor=void serviceSshShell()] | firmware/esp32s3/src/SshTransport.h:31 [UTF8-LF sha256=988641dc923aafab5ec72ce909067455c44faf468bb241d5b15b3bac1bba33d8; anchor=class SshTransport {] | firmware/esp32s3/src/SshPolicy.h:22 [UTF8-LF sha256=ba8a8f5807e07e52a88e4098d6a2ae7d915d9547f4a0e3b25e6700669c10aab2; anchor=inline size_t commandLength(] | firmware/esp32s3/src/ScpDownload.h:10 [UTF8-LF sha256=a5864de20e077f081c03ddce98e9a9dac3a78a46149b3618ae66f330a53aca8c; anchor=class ScpDownload {] | firmware/esp/src/SshScpBinding.h:11 [UTF8-LF sha256=9acf2193fac080639fc5460273b16a3ebc5a1022fb4c95f67df8c0dab22d8e4a; anchor=void serviceSshScp()] | firmware/esp32s3/src/ScpUpload.h:8 [UTF8-LF sha256=f1a8c0743f079973a4fdedee3ac4ad16b538e6a28253763280ecec6204a240b5; anchor=class ScpUpload] | firmware/esp32s3/src/SftpSession.h:14 [UTF8-LF sha256=ff44da0e5871e592909952eaa54b1f563dbb4ec88a867b280089c11709a081d6; anchor=class SftpSession] | firmware/esp32s3/src/FtpSession.h:10 [UTF8-LF sha256=0fe8968237417b2fb2a1fe40b0471bffb7c87d67c1114d5f16c04ba1c4f94dae; anchor=class FtpSession] | firmware/esp/src/TransferFiles.h:7 [UTF8-LF sha256=dea201ffe11e3516975ef7b2c7850aac74940514eb8c05ee7b521ff6698ba18d; anchor=class TransferFiles] | firmware/esp/src/FileTransferBinding.h:9 [UTF8-LF sha256=666f8822b937bcda527bbe9c121040e4ef2d32894e91057988450774f0d427b0; anchor=serviceFileTransferCleanup] | firmware/esp/src/FtpBinding.h:4 [UTF8-LF sha256=ee60fbc7b27399818c2d46c5c7401d1688c7e595d2a5fd9b52d458e65902adfc; anchor=namespace DeviceFtp] | firmware/esp/src/SshUploadBinding.h:12 [UTF8-LF sha256=563386de05c9b0190942e73c1eafdfa384d06438e69da5119834642cab357b77; anchor=void resetSshTransfer]","PU ShellSession transport/editor separation, insertion/shift/backspace/delete, CSI/SS3 arrows/home/end, Ctrl-C/Ctrl-L and split CR-LF/CR-NUL behavior are adapted in instance-owned TelnetParser/LineEditor. | Generic parameter lookup/read-only enforcement and dispatch now map to ShellProperty descriptors, typed shellSet and the existing Pakar command/configuration owners; the same descriptors feed the device TUI. | Session-owned prompt-safe watch summaries use redrawEvent without losing the draft/cursor. Utilities clear/cls, echo, hex and upper/noupper are present; bounded eight-entry history adds recall without retaining classified secrets. | Raw serial ownership is replaced by an additive boot-bound COBS shell channel: explicit token, contiguous nonwrapping input sequence, duplicate-byte ACK without redispatch, and immutable output windows consumed only by matching ACK. | Issue #33 SSH exec reuses the same authenticated loop-owned DeviceShellSession/executeDeviceShell dispatcher. Bounded single commands have prompt-free output, nonzero failures and drained exit status despite stdin EOF; PTY tui/edit launches and exits the shared TUI directly, retaining resize, maintenance and security-revision fences. No operating-system shell or second command catalogue is introduced.","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter [replaced] | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add [replaced] | ShellBroadcastLine unrestricted event broadcast [replaced] | upper/noupper applies to all donor input echo and prompt redraw [replaced] | Donor printable-ASCII editor and richer Unicode terminals [pending]","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter: A separately boot-fenced COBS shell channel preserves USB recovery/protocol framing rather than mixing raw ASCII into the existing decoder. Instance-owned dispatch removes the donor global command/output pointer; host script never automatically replays uncertain input. | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add: Strict unsignedNumber validates full decimal input and overflow before conversion; command-specific signed display handling is explicit. Fixtures reject signs, commas, trailing junk and UINT32 overflow rather than silently normalizing a dangerous argument. | ShellBroadcastLine unrestricted event broadcast: Opt-in per-session watch publishes changed bounded summaries with prompt erase/redraw and backpressure. This is not an ordered complete peripheral event stream; individual event delivery remains outside this shell slice. | upper/noupper applies to all donor input echo and prompt redraw: Input echo, insertion, cursor redraw and history recall now uppercase only displayed bytes, preserving raw commands and secret suppression; portable_shell_test covers it. | Donor printable-ASCII editor and richer Unicode terminals: Arduino line editor/TUI deliberately accepts printable ASCII and sanitizes external controls. Persian/UTF-8 editing, bidi layout and font support are not supplied by this shell adaptation.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()] | tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(] | tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate] | tests/shell_tui_test.cpp:41 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Unchanged Enter is not a write.] | tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate] | tests/ssh_settings_test.cpp:17 [UTF8-LF sha256=44c6fda6dce749d7302d284c9f8ba1b9919b36fefd4e3e61fd00b665dfdf6ad2; anchor=int main()] | tests/ssh_exec_transport_test.cpp:91 [UTF8-LF sha256=615435469fa43f155f61ee84ce5f9fa847503a1f1d36360e963a24949b11a680; anchor=Actual SSH exec transport:] | tests/ssh_exec_binding_test.cpp:123 [UTF8-LF sha256=8d5b45f3825a81c91f8af77a263d87c37a4a53ec4f1399fca73f2490df9bbba0; anchor=Actual SSH shell exec binding/dispatcher:] | tests/ssh_exec_binding_test.mjs:9 [UTF8-LF sha256=8ad3023d3de3232cb50377d3aa81cf1f58b0f7e80c05ad97145d73346483bbb2; anchor=SSH exec production markers changed] | tests/ssh_policy_test.cpp:6 [UTF8-LF sha256=4e8b5abdd817ca1ecc2f3aaba1dfa8186acc125529e997a340a940f8cb73a73c; anchor=int main()] | tests/ssh_scp_binding_test.cpp:21 [UTF8-LF sha256=b47ef508eb652a9099ab0f29e33da744e83d23f1b452cc1c195b4118a3081412; anchor=int main()] | tests/scp_download_test.cpp:10 [UTF8-LF sha256=49b22f7ce4a962b1d5fd677ac3bc37bbc6aac8f41a5f7ac4ebcf9790caa79dd8; anchor=int main(] | tests/scp_client_test.mjs:15 [UTF8-LF sha256=bec216abf19d74af291ecba65e496d689e8ccdeb4e4c12f0707fcea2389b6a1d; anchor=Real OpenSSH SCP client accepted] | tests/file_transfer_test.cpp:22 [UTF8-LF sha256=b59d00420cac2a5eec4c863cfc5d60e1a93d4b1b041b8f90e1b01250a157dd8e; anchor=int main(] | tests/file_transfer_client_test.mjs:18 [UTF8-LF sha256=254834d63c0db3ac14d83db5cdb16c1a6773a6a9df78f7d270c38bcdc0d67ede; anchor=Real OpenSSH clients:] | tests/ftp_binding_test.cpp:61 [UTF8-LF sha256=442d67f43bf96b8031b6966c89ac9f9edf98d66afda048179242c734de502b3b; anchor=Actual FTP socket binding:] | tests/ftp_stubs/sys/socket.h:3 [UTF8-LF sha256=a03b479d9c3b25a90b08c8580d4a54ced5415914bd3ce805ef43706efa7b7a45; anchor=MSG_DONTWAIT] | tools/test-ssh.ps1:59 [UTF8-LF sha256=fc5bdf619662059e9373a54c608e3661aa9906f3f0a4e8cf5b5e36eeed5599e4; anchor=Actual FTP socket binding fixture]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success. | Issue #33: native SSH plus gated transactional SCP/SFTP and passive FTP share the public-file store and maintenance lease. Policy/NVS/socket-FS doubles and real OpenSSH clients over stdio are not an encrypted board login. Exact footprint/SDK evidence belongs in the receipt and SSH.md; actual login, identity persistence, memory/TLS/LED coexistence and separately authorized live transfers/deployment remain open.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Shell.h","431104072952f694a35b3a83c19f13fbb098ca5b","9f8e76509ad917d8e204a82d9495e376607a159547d4eee3de6760e6bd5d7da6","ssh-transport | device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/33","parity-gap","complete","firmware/esp/src/TelnetShell.h:8 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class LineEditor] | firmware/esp/src/ShellProperties.h:16 [UTF8-LF sha256=7a6cb87cf78a769be09ac82a5d3d3b3912f485b36d98a6676b5320d8b54ed0fa; anchor=static const ShellProperty shellProperties[]] | firmware/esp/src/ShellWire.h:9 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class SerialChannel] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot] | tools/device-shell.ps1:8 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=AutomaticCommandRetry=$false]","PU ShellSession transport/editor separation, insertion/shift/backspace/delete, CSI/SS3 arrows/home/end, Ctrl-C/Ctrl-L and split CR-LF/CR-NUL behavior are adapted in instance-owned TelnetParser/LineEditor. | Generic parameter lookup/read-only enforcement and dispatch now map to ShellProperty descriptors, typed shellSet and the existing Pakar command/configuration owners; the same descriptors feed the device TUI. | Session-owned prompt-safe watch summaries use redrawEvent without losing the draft/cursor. Utilities clear/cls, echo, hex and upper/noupper are present; bounded eight-entry history adds recall without retaining classified secrets. | Raw serial ownership is replaced by an additive boot-bound COBS shell channel: explicit token, contiguous nonwrapping input sequence, duplicate-byte ACK without redispatch, and immutable output windows consumed only by matching ACK.","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter [replaced] | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add [replaced] | ShellBroadcastLine unrestricted event broadcast [replaced] | upper/noupper applies to all donor input echo and prompt redraw [replaced] | Donor printable-ASCII editor and richer Unicode terminals [pending]","Raw ShellTransport_Serial/SerialShellSession and mutable global command/include Commands.cpp adapter: A separately boot-fenced COBS shell channel preserves USB recovery/protocol framing rather than mixing raw ASCII into the existing decoder. Instance-owned dispatch removes the donor global command/output pointer; host script never automatically replays uncertain input. | NumberFromString permissive comma/minus parsing and unchecked signed multiply/add: Strict unsignedNumber validates full decimal input and overflow before conversion; command-specific signed display handling is explicit. Fixtures reject signs, commas, trailing junk and UINT32 overflow rather than silently normalizing a dangerous argument. | ShellBroadcastLine unrestricted event broadcast: Opt-in per-session watch publishes changed bounded summaries with prompt erase/redraw and backpressure. This is not an ordered complete peripheral event stream; individual event delivery remains outside this shell slice. | upper/noupper applies to all donor input echo and prompt redraw: Input echo, insertion, cursor redraw and history recall now uppercase only displayed bytes, preserving raw commands and secret suppression; portable_shell_test covers it. | Donor printable-ASCII editor and richer Unicode terminals: Arduino line editor/TUI deliberately accepts printable ASCII and sanitizes external controls. Persian/UTF-8 editing, bidi layout and font support are not supplied by this shell adaptation.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/shell_properties_test.cpp:5 [UTF8-LF sha256=5e82c5c20b2b39fc1efe4def4c4362bded29a81e464ed23fb16b329e98ed037b; anchor=int main()] | tests/telnet_shell_test.cpp:105 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=editor.redrawEvent(] | tests/shell_wire_test.cpp:11 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=C::Duplicate] | tests/shell_tui_test.cpp:41 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Unchanged Enter is not a write.] | tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/SSDP.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","2f9c9f8f0b613d958e8bb31920174a7d8fa9f523b3477b88d9430a71d385bf48","pu-routes | application-identity | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/10","parity-gap","complete","firmware/esp32s3/src/Discovery.h:11 [UTF8-LF sha256=d2cd172d2934642d6255106edb2a417a5bf128ff021dd353ad4de57262277cd0; anchor=class Discovery {] | firmware/esp32s3/src/Discovery.h:45 [UTF8-LF sha256=d2cd172d2934642d6255106edb2a417a5bf128ff021dd353ad4de57262277cd0; anchor=String xmlDescription() const {]","Complete donor wrapper was reread by the evidence owner, in addition to network agent's full read. Actual initSSDP metadata/schema/periodic-service ownership maps to Discovery::begin/service/xmlDescription and the imported SsdpDevice::schema XML escaper. Hostname/display name/application/version are Pakar-owned; no PU850 EEPROM business identity is retained.","ASA0002E/E_MainVersion/E_SerialNumber globals, include-SSDPDevice.cpp pattern, server-global registration and duplicate hand-built description [replaced] | Optional UPnP serviceList/SOAP/GENA implementation [not-applicable]","ASA0002E/E_MainVersion/E_SerialNumber globals, include-SSDPDevice.cpp pattern, server-global registration and duplicate hand-built description: Canonical main registers the description route; instance-owned Discovery uses consistent Pakar metadata and the imported escaped schema. No duplicate compiled .cpp/global service or PU serial/version state is retained. | Optional UPnP serviceList/SOAP/GENA implementation: The donor intentionally does not advertise an unimplemented service. Pakar likewise leaves optional service discovery disabled; no invented control protocol is exposed. Add a real contract before enabling it.","","","Validate actual S3 AP/STA multicast membership, M-SEARCH responses, alive/update/byebye/rename lifecycle, interface-correct LOCATION, escaped description and router/phone interoperability. Source anchors and compilation do not prove on-LAN discovery.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/SSDP.h","431104072952f694a35b3a83c19f13fbb098ca5b","f9f7b078b6085e731f0ed42c5dd983d1ab0a3a48c2912797406fb3e0b0df3ea5","pu-routes | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp32s3/src/Discovery.h:59 [UTF8-LF sha256=d2cd172d2934642d6255106edb2a417a5bf128ff021dd353ad4de57262277cd0; anchor=SsdpDevice ssdp_;]","Complete tiny donor header was reread by the evidence owner. Its current role is only the historical SSDP-to-singleton alias; actual device declarations map separately to the imported SsdpDevice.h.","#define SSDP SSDPDevice singleton shorthand [replaced]","#define SSDP SSDPDevice singleton shorthand: Explicit Discovery-owned instance avoids macro alias collisions and makes restart/teardown ownership clear; no useful implementation is silently dropped with this alias.","","","Validate actual S3 AP/STA multicast membership, M-SEARCH responses, alive/update/byebye/rename lifecycle, interface-correct LOCATION, escaped description and router/phone interoperability. Source anchors and compilation do not prove on-LAN discovery.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/SSDPDevice.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","82f8c2a3c9779392202dc9b864328040cf47b9303674f142e5348df2b17e34b4","lan-link-discovery | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/19","parity-gap","complete","firmware/esp32s3/src/SsdpDevice.cpp:255 [UTF8-LF sha256=25ffe3de9548501ec275174edbff3d9295ec1733fbea6f0fd288e4b284dc357c; anchor=void SsdpDevice::post(] | firmware/esp32s3/src/SsdpDevice.cpp:546 [UTF8-LF sha256=25ffe3de9548501ec275174edbff3d9295ec1733fbea6f0fd288e4b284dc357c; anchor=void SsdpDevice::schema(Print &client) const] | firmware/esp/src/UpnpDescription.h:19 [UTF8-LF sha256=c037418dca2cc7631c04f06d9dc51b4267d86aabe25d61bde4422a5430234639; anchor=template void description(] | firmware/esp/src/BoardIconAsset.h:6 [UTF8-LF sha256=2f0fd62282276aef2bc8223e3a54698e99e77f1bc6559a94fb2527c8f2240973; anchor=static const uint8_t kBoardIconPng[] PROGMEM] | tools/generate-board-icon-asset.mjs:7 [UTF8-LF sha256=fa6093635fbbc333fe87fb1b6733453147950bc2bc2e23c10d9c7b39d9b9f078; anchor=const variants=[];] | firmware/esp32s3/src/SsdpUdp.h:70 [UTF8-LF sha256=e25782c43552d9a2fb570b2dd18446c99907a9b58d0d1ec6f5f190b9fee84f98; anchor=bool beginPacketMulticast(] | firmware/esp32s3/src/DiscoveryInterfaces.h:17 [UTF8-LF sha256=f6c87195a581b098486e533d44192b3fa728145977888caf46676f780c9a50b6; anchor=inline DiscoveryInterfaces activeDiscoveryInterfaces()] | firmware/esp32s3/src/EthernetPort.cpp:41 [UTF8-LF sha256=b2ef56e69ae00a5d74c6d1fb90b7cd1dbdcaf2757f440b05f52abc28938c8a0a; anchor=bool EthernetPort::begin(]","Network agent fully read the actual complete PU850 donor and copied/adapted its source, not merely an SDK call. The evidence owner reviewed the complete donor-to-target diff. Retains bounded/de-duplicated delayed M-SEARCH queue, root/UUID/device responses, alive/update/byebye, boot/config identity, topology change handling and XML escaping/schema. | Only platform boundary and ownership are changed: PakarS3 namespace/instance-owned class, ESP32 chip identity and interface-bound SsdpUdp replace global ESP8266 WiFiUDP/IGMP. Both active AP and station memberships are supported rather than carrying ESP8266's small-IGMP-pool one-interface restriction. | Extended the actual SDK socket adapter from STA/AP to ready Ethernet/STA/AP, with independent W5500 SPI3 ownership, actual subnet masks, bounded secondary membership retry and topology cleanup. Native mDNS uses SDK netifs; no second WIZnet-only API/socket stack. Defaults leave W5500 uninitialized until the explicit build feature is selected.","Global SSDPDeviceClass singleton, ESP.getChipId and manual igmp_leavegroup/ESP8266 one-interface IGMP-pool policy [replaced]","Global SSDPDeviceClass singleton, ESP.getChipId and manual igmp_leavegroup/ESP8266 one-interface IGMP-pool policy: Instance-owned S3 Discovery explicitly owns teardown and stable identity; socket-scoped membership/IF/TTL replaces incompatible ESP8266 UDP internals. The adapter is bounded/nonblocking and no arbitrary shared global SPI/network engine is substituted. Actual AP/STA behavior remains unverified.","tests/network_path_test.cpp:20 [UTF8-LF sha256=fb78d7364432aa95ae1e4fe547ee4599ec0e7f0e9a97a629c0445c6c7b842b07; anchor=int main()] | tests/ethernet_discovery_test.mjs:41 [UTF8-LF sha256=84cd26938282eb3adfecec5c9464f29c817c280caf2236e96b62fd52949569f2; anchor=Ethernet discovery/build source integration checks passed] | tests/upnp_description_test.mjs:39 [UTF8-LF sha256=733ee296c2c7f106a398d297581a80acc09481f99acd9f8dc4b1444793824729; anchor=Actual UPnP schema: role PNG bytes/dimensions/depth]","","Validate actual S3 AP/STA multicast membership, M-SEARCH responses, alive/update/byebye/rename lifecycle, interface-correct LOCATION, escaped description and router/phone interoperability. Source anchors and compilation do not prove on-LAN discovery. | Verify W5500 physical multicast/source routing and all-netif mDNS on actual hardware. Custom UDP lacks ingress-netif metadata: equal overlapping subnets use documented longest-prefix/tie policy, not proven exact ingress.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/SSDPDevice.h","431104072952f694a35b3a83c19f13fbb098ca5b","3d94b63400ab64c0e556b1e73c722b40f7adade996aaf40ce73f88d1d0ab7cf2","lan-link-discovery | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/19","parity-gap","complete","firmware/esp32s3/src/SsdpDevice.h:98 [UTF8-LF sha256=11297a36d29bade8c44c67ce9bb9f805429c633d0f1f6164cfb2c2f9667291da; anchor=class SsdpDevice {]","Network agent fully read the actual donor; evidence owner reviewed the complete header diff. Retains packet templates, bounded queue and metadata fields, public discovery/description setters, cache interval and protocol structures. Adds truthful running() state from the real socket/interface.","ESP8266WiFi/WiFiUDP/IGMP includes, ProductConfig and extern singleton declaration [replaced]","ESP8266WiFi/WiFiUDP/IGMP includes, ProductConfig and extern singleton declaration: ESP32 WiFi, explicit socket adapter, Pakar DiscoveryConfig and namespace-owned instance replace platform-specific includes/global ownership. SERVER announces ESP32-S3 rather than falsely identifying as ESP8266. Useful public protocol behavior is retained; physical discovery remains open.","","","Validate actual S3 AP/STA multicast membership, M-SEARCH responses, alive/update/byebye/rename lifecycle, interface-correct LOCATION, escaped description and router/phone interoperability. Source anchors and compilation do not prove on-LAN discovery.","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/Telnet.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","a78d88c149d19d8989c6db3dce0731189c2bf46c45fd64b73e9871a80f6c9bdd","device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser] | firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceDeviceShells()] | firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings] | firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]","Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops. | Native WiFiServer/WiFiClient sessions use independent parser/editor/TUI/auth state, configurable bounded session count, 64-byte input budget, bounded output and nonblocking S3 socket writes; slow or overfull sessions retire rather than blocking LED/UART work. | ShellSettings persists enable, TCP port and color with checksum, pending write/readback/rename and unchanged-write suppression. Configuration schedules listener retirement/rebind after its response; published discovery port comes from the actual bound listener. | Authentication readiness/security revision fences both Telnet and framed USB; asynchronous close requests are deferred to the main loop so callbacks do not delete a live loop-owned editor/socket.","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression [replaced] | Single global TelnetShellSession and legacy product greeting callbacks [replaced] | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss [pending]","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression: Native WiFiServer/WiFiClient avoids the conflicting synchronous web header while retaining the actual Telnet protocol/parser behavior, not merely newline input. | Single global TelnetShellSession and legacy product greeting callbacks: Bounded independent sessions use Pakar application/hostname identity and the existing access owner. Telnet is still plaintext and is not claimed encrypted; disabling it or compile-time gating remains available. | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss: Source now supports durable enable/port rebind and truthful actual-listener discovery. Exact donor network-loss transition parity and multi-interface socket behavior still need explicit review/live acceptance rather than being inferred from settings tests.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.] | tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true] | tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()] | tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
-"PU850-ESP","LocalLib/Telnet.h","431104072952f694a35b3a83c19f13fbb098ca5b","e85f5f1a4f2767323d9886313ddb2aabfd3acad7d781c2770bc8d2b2d2c6cb15","device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser] | firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void serviceDeviceShells()] | firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings] | firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]","Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops. | Native WiFiServer/WiFiClient sessions use independent parser/editor/TUI/auth state, configurable bounded session count, 64-byte input budget, bounded output and nonblocking S3 socket writes; slow or overfull sessions retire rather than blocking LED/UART work. | ShellSettings persists enable, TCP port and color with checksum, pending write/readback/rename and unchanged-write suppression. Configuration schedules listener retirement/rebind after its response; published discovery port comes from the actual bound listener. | Authentication readiness/security revision fences both Telnet and framed USB; asynchronous close requests are deferred to the main loop so callbacks do not delete a live loop-owned editor/socket.","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression [replaced] | Single global TelnetShellSession and legacy product greeting callbacks [replaced] | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss [pending]","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression: Native WiFiServer/WiFiClient avoids the conflicting synchronous web header while retaining the actual Telnet protocol/parser behavior, not merely newline input. | Single global TelnetShellSession and legacy product greeting callbacks: Bounded independent sessions use Pakar application/hostname identity and the existing access owner. Telnet is still plaintext and is not claimed encrypted; disabling it or compile-time gating remains available. | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss: Source now supports durable enable/port rebind and truthful actual-listener discovery. Exact donor network-loss transition parity and multi-interface socket behavior still need explicit review/live acceptance rather than being inferred from settings tests.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.] | tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true] | tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()] | tools/test-shell.ps1:5 [UTF8-LF sha256=51673e6f5ae86036a7a439214e3e456a0b8dab6f4dc17485e1244ca862174967; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Telnet.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","a78d88c149d19d8989c6db3dce0731189c2bf46c45fd64b73e9871a80f6c9bdd","device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser] | firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceDeviceShells()] | firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings] | firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]","Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops. | Native WiFiServer/WiFiClient sessions use independent parser/editor/TUI/auth state, configurable bounded session count, 64-byte input budget, bounded output and nonblocking S3 socket writes; slow or overfull sessions retire rather than blocking LED/UART work. | ShellSettings persists enable, TCP port and color with checksum, pending write/readback/rename and unchanged-write suppression. Configuration schedules listener retirement/rebind after its response; published discovery port comes from the actual bound listener. | Authentication readiness/security revision fences both Telnet and framed USB; asynchronous close requests are deferred to the main loop so callbacks do not delete a live loop-owned editor/socket.","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression [replaced] | Single global TelnetShellSession and legacy product greeting callbacks [replaced] | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss [pending]","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression: Native WiFiServer/WiFiClient avoids the conflicting synchronous web header while retaining the actual Telnet protocol/parser behavior, not merely newline input. | Single global TelnetShellSession and legacy product greeting callbacks: Bounded independent sessions use Pakar application/hostname identity and the existing access owner. Telnet is still plaintext and is not claimed encrypted; disabling it or compile-time gating remains available. | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss: Source now supports durable enable/port rebind and truthful actual-listener discovery. Exact donor network-loss transition parity and multi-interface socket behavior still need explicit review/live acceptance rather than being inferred from settings tests.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.] | tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true] | tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()] | tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
+"PU850-ESP","LocalLib/Telnet.h","431104072952f694a35b3a83c19f13fbb098ca5b","e85f5f1a4f2767323d9886313ddb2aabfd3acad7d781c2770bc8d2b2d2c6cb15","device-shell | network-cloud","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/TelnetShell.h:7 [UTF8-LF sha256=69b28602442267c2c524cf05660f789725cbdfed3db8ef7ecb088c387a9d9e97; anchor=class TelnetParser] | firmware/esp/src/ShellService.h:450 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void serviceDeviceShells()] | firmware/esp/src/ShellSettings.h:10 [UTF8-LF sha256=9e73a173b820cf54b70e4fc6506bccbb67fee5e4ab600fc9660503d05c491417; anchor=class ShellSettings] | firmware/esp/src/ShellBuild.h:5 [UTF8-LF sha256=64e2f53b7c399cd564fc41f942af57433a9baf3e0362190e138bd5a6f0ce4ce5; anchor=PAKAR_TELNET_SESSIONS]","Actual five-state IAC/option/subnegotiation parser, escaped IAC, server ECHO/SGA greeting and unsupported-option rejection are retained. Option state suppresses greeting-ACK negotiation loops. | Native WiFiServer/WiFiClient sessions use independent parser/editor/TUI/auth state, configurable bounded session count, 64-byte input budget, bounded output and nonblocking S3 socket writes; slow or overfull sessions retire rather than blocking LED/UART work. | ShellSettings persists enable, TCP port and color with checksum, pending write/readback/rename and unchanged-write suppression. Configuration schedules listener retirement/rebind after its response; published discovery port comes from the actual bound listener. | Authentication readiness/security revision fences both Telnet and framed USB; asynchronous close requests are deferred to the main loop so callbacks do not delete a live loop-owned editor/socket.","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression [replaced] | Single global TelnetShellSession and legacy product greeting callbacks [replaced] | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss [pending]","ESPTelnet dependency and ESP8266WEBSERVER_H synchronous-header suppression: Native WiFiServer/WiFiClient avoids the conflicting synchronous web header while retaining the actual Telnet protocol/parser behavior, not merely newline input. | Single global TelnetShellSession and legacy product greeting callbacks: Bounded independent sessions use Pakar application/hostname identity and the existing access owner. Telnet is still plaintext and is not claimed encrypted; disabling it or compile-time gating remains available. | TelnetNetworkAvailable stop/restart and mDNS remove on every AP/station loss: Source now supports durable enable/port rebind and truthful actual-listener discovery. Exact donor network-loss transition parity and multi-interface socket behavior still need explicit review/live acceptance rather than being inferred from settings tests.","tests/shell_service_test.mjs:18 [UTF8-LF sha256=287a22bebfcf0457e307416b1bb6524d71a28a8876646691b449df017b023be6; anchor=Actual shell fixture extraction markers changed] | tests/shell_service_test.cpp:137 [UTF8-LF sha256=076058d45758b87cf76c294e94827929dd74a7e7075cee533aa4ee7251f7e47b; anchor=Accept crosses millis rollover.] | tests/telnet_shell_test.cpp:18 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=No negotiation loops on greeting acknowledgements.] | tests/shell_wire_test.cpp:34 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=fs.failRename=true] | tests/shell_wire_test.cpp:36 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=!corrupt.enabled()] | tools/test-shell.ps1:5 [UTF8-LF sha256=fcd706a22bf195f86ca43954692430a199ddb4f9375dc68801160e240f65c87d; anchor=-Concern device-shell -Gate]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/UART.cpp","431104072952f694a35b3a83c19f13fbb098ca5b","8c8201cad39550cffb8567b279447a093061bba7b9a188dbe2f47dbd22ef9a45","pu-uart","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/6","parity-gap","complete","libraries/BoardCore/src/SerialLink.h:12 [UTF8-LF sha256=99373bc4c090e34324cfd2ae8c94d6666c227ad7e157a47fbd85125aac6c13a0; anchor=class SerialLink {] | libraries/BoardCore/src/UartFlow.h:26 [UTF8-LF sha256=44f8638f725c9cf83a6f53639144f107aace1556dd254d6dcebcaed4853c6469; anchor=inline uint16_t receiveBudget(] | libraries/BoardCore/src/Protocol.h:137 [UTF8-LF sha256=331f68dfe248de399304210d34bfc51d97a39d84610731d7abe6d44587634059; anchor=inline bool decode(] | docs/PU850-PARITY.md:141 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: C++ transfer and UART, 2026-10-19]","Every transport and endpoint function read through EOF; current bounded COBS/CRC/sequence/explicit ownership remains, with exact generic pending and business-only omissions. | Complete section/function decisions and concrete unsafe legacy defects recorded in the C++ transfer and UART section of docs/PU850-PARITY.md.","SOH/EOT framing/RevisionMatch/bulk ring/string endpoints/baud/drain/response match [replaced] | Generic panel/title/buttons/icons/language/clock/voltage/topology/file bulk/backpressure/cancel/session/diagnostic/logging [pending] | Weight/hidden-weight/tare/receipt/report payloads only [not-applicable]","SOH/EOT framing/RevisionMatch/bulk ring/string endpoints/baud/drain/response match: Bounded COBS/CRC/sequence/current capability owner replaces unframed legacy ABI. Exact modulo-zero, sizeof(pointer), U16 wrapping, ten-byte string overflow, AP wrong-interface and baud fallthrough defects are recorded in PU850-PARITY. | Generic panel/title/buttons/icons/language/clock/voltage/topology/file bulk/backpressure/cancel/session/diagnostic/logging: Not business exclusions. Current low-level transport and HTTP backup do not prove these high-level paired capabilities; unwired power is Unknown, raw credential traces prohibited, file receive and diagnostic placeholders not claimed implemented. | Weight/hidden-weight/tare/receipt/report payloads only: Exact PU weighing/report business operations have no PakarController consumer; this does not exclude generic settings, file, UI or diagnostic code.","","","Not business exclusions. Current low-level transport and HTTP backup do not prove these high-level paired capabilities; unwired power is Unknown, raw credential traces prohibited, file receive and diagnostic placeholders not claimed implemented.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","LocalLib/UART.h","431104072952f694a35b3a83c19f13fbb098ca5b","13d3ec4617363d32ba58595a9e24137c45501cbda4f923b5e08e561e066e50fc","pu-uart","https://github.com/atomicdeploy/PakarController/issues/3 | https://github.com/atomicdeploy/PakarController/issues/6","parity-gap","complete","libraries/BoardCore/src/SerialLink.h:12 [UTF8-LF sha256=99373bc4c090e34324cfd2ae8c94d6666c227ad7e157a47fbd85125aac6c13a0; anchor=class SerialLink {] | libraries/BoardCore/src/UartFlow.h:26 [UTF8-LF sha256=44f8638f725c9cf83a6f53639144f107aace1556dd254d6dcebcaed4853c6469; anchor=inline uint16_t receiveBudget(] | libraries/BoardCore/src/Protocol.h:137 [UTF8-LF sha256=331f68dfe248de399304210d34bfc51d97a39d84610731d7abe6d44587634059; anchor=inline bool decode(] | docs/PU850-PARITY.md:141 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: C++ transfer and UART, 2026-10-19]","Every transport and endpoint function read through EOF; current bounded COBS/CRC/sequence/explicit ownership remains, with exact generic pending and business-only omissions. | Complete section/function decisions and concrete unsafe legacy defects recorded in the C++ transfer and UART section of docs/PU850-PARITY.md.","SOH/EOT framing/RevisionMatch/bulk ring/string endpoints/baud/drain/response match [replaced] | Generic panel/title/buttons/icons/language/clock/voltage/topology/file bulk/backpressure/cancel/session/diagnostic/logging [pending] | Weight/hidden-weight/tare/receipt/report payloads only [not-applicable]","SOH/EOT framing/RevisionMatch/bulk ring/string endpoints/baud/drain/response match: Bounded COBS/CRC/sequence/current capability owner replaces unframed legacy ABI. Exact modulo-zero, sizeof(pointer), U16 wrapping, ten-byte string overflow, AP wrong-interface and baud fallthrough defects are recorded in PU850-PARITY. | Generic panel/title/buttons/icons/language/clock/voltage/topology/file bulk/backpressure/cancel/session/diagnostic/logging: Not business exclusions. Current low-level transport and HTTP backup do not prove these high-level paired capabilities; unwired power is Unknown, raw credential traces prohibited, file receive and diagnostic placeholders not claimed implemented. | Weight/hidden-weight/tare/receipt/report payloads only: Exact PU weighing/report business operations have no PakarController consumer; this does not exclude generic settings, file, UI or diagnostic code.","","","Not business exclusions. Current low-level transport and HTTP backup do not prove these high-level paired capabilities; unwired power is Unknown, raw credential traces prohibited, file receive and diagnostic placeholders not claimed implemented.","Not established by source/hash/test-code comparison; see issue #2"
"PU850-ESP","PRODUCT-PROFILES.md","431104072952f694a35b3a83c19f13fbb098ca5b","b396143fd17590b35455f91fc17fc15bfb52862d3419198df10da62b86340aaa","pu-foundation","https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","docs/PU850-PARITY.md:307 [UTF8-LF sha256=90fd0fcaa4c6bfab14639db6e6fa479a2f70729100ed5800edd73020f494d1a3; anchor=## Exhaustive continuation: foundation slice, 2026-10-19]","Every section/function read through EOF: identity/overrides; secret-safe flags; all unversioned transfer routes; range/ETag/hash-pair/downstream/redaction contracts | Function-by-function disposition and exact remaining behaviors are recorded in docs/PU850-PARITY.md foundation slice.","Full cross-language uploader/download/resume/gzip/manifest compatibility [pending]","Full cross-language uploader/download/resume/gzip/manifest compatibility: Read and adapt every actual client/test; strict current auth/identity/integrity and supported-release policy remain authoritative. PU weighing/report identity is specifically out of scope.","","","Read and adapt every actual client/test; strict current auth/identity/integrity and supported-release policy remain authoritative. PU weighing/report identity is specifically out of scope.","Not established by source/hash/test-code comparison; see issue #2"
@@ -2714,7 +2718,7 @@
"RayanLamp","internal/tui/device.go","filesystem-snapshot-no-git","2aa5d5a04dc4df0f9fb424bcd6531e91c46fd78bbd2ac8496b37e088a3d8982d","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/diagnostics_test.go","filesystem-snapshot-no-git","4050ce39a3715cc12f50f1c5e679a31cece9780d3329dccc35c4707e2617b04b","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/diagnostics.go","filesystem-snapshot-no-git","ca9b21e35985b69acf8dbc43f38b39900321485dd4ed11f6674be3ba67097ffa","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
-"RayanLamp","internal/tui/form.go","filesystem-snapshot-no-git","3a3b4f13d0c839664edc2b15bb4418254463ff4d36016cb798a799016ef2890a","device-shell | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=fb551fb47f1c32d0b57c1245b2cc701af6265bf927b159bb556983dacd48d1e9; anchor=struct ShellTuiSnapshot]","formIsFiltering focused-field key ownership is adapted to a pure ShellTui editing state: printable input/Tab cannot invoke workspace shortcuts while a draft is active; Esc/Ctrl-C cancel without applying. | Readonly fields cannot emit Set, unchanged Enter is not a write, CR-LF submits once, and a draft is cancelled if its key/editable/secret authority changes. Observed refreshes do not repaint or replace an active draft. | One shared ShellProperty snapshot binds TUI fields to the same shell/configuration validators, rather than introducing a separate TUI business or persistence layer.","Huh form types, Go interface assertions and Bubble Tea runtime [replaced] | Unicode/Persian input and desktop form widgets [pending]","Huh form types, Go interface assertions and Bubble Tea runtime: The microcontroller uses an allocation-free C++ editing state and explicit Action handoff. No Go/Huh/Bubble Tea runtime is ported or required, and no full Huh select-filter UI is claimed. | Unicode/Persian input and desktop form widgets: Current Arduino form accepts bounded printable ASCII only; terminal character shaping, Persian editing and richer host widgets require separate implementation and acceptance.","tests/shell_tui_test.cpp:43 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=private\trefresh] | tests/shell_tui_test.cpp:37 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=Async observation cannot repaint draft.] | tests/shell_tui_test.cpp:72 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=rows[0].editable=false]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
+"RayanLamp","internal/tui/form.go","filesystem-snapshot-no-git","3a3b4f13d0c839664edc2b15bb4418254463ff4d36016cb798a799016ef2890a","device-shell | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView] | firmware/esp/src/ShellTuiBinding.h:3 [UTF8-LF sha256=71a0879a531b26b6058bb25eb13b5f09c8ae3e739a485c4620e37e2495e30d26; anchor=struct ShellTuiSnapshot]","formIsFiltering focused-field key ownership is adapted to a pure ShellTui editing state: printable input/Tab cannot invoke workspace shortcuts while a draft is active; Esc/Ctrl-C cancel without applying. | Readonly fields cannot emit Set, unchanged Enter is not a write, CR-LF submits once, and a draft is cancelled if its key/editable/secret authority changes. Observed refreshes do not repaint or replace an active draft. | One shared ShellProperty snapshot binds TUI fields to the same shell/configuration validators, rather than introducing a separate TUI business or persistence layer.","Huh form types, Go interface assertions and Bubble Tea runtime [replaced] | Unicode/Persian input and desktop form widgets [pending]","Huh form types, Go interface assertions and Bubble Tea runtime: The microcontroller uses an allocation-free C++ editing state and explicit Action handoff. No Go/Huh/Bubble Tea runtime is ported or required, and no full Huh select-filter UI is claimed. | Unicode/Persian input and desktop form widgets: Current Arduino form accepts bounded printable ASCII only; terminal character shaping, Persian editing and richer host widgets require separate implementation and acceptance.","tests/shell_tui_test.cpp:52 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=private\trefresh] | tests/shell_tui_test.cpp:46 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=Async observation cannot repaint draft.] | tests/shell_tui_test.cpp:81 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=rows[0].editable=false]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/interaction_test.go","filesystem-snapshot-no-git","c12486da8abb6fead0ea9cd885c6d0825ce30ee111435f014684b818d08f8ff4","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/keys.go","filesystem-snapshot-no-git","9db93f624f1fcc5b850993de2c6cd2ca1304bf91034849f246dd54fb70a02720","terminal-ui | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","desktop/src/terminal_ui.rs:302 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=fn key(&mut self, key: KeyEvent)]","Contextual Tab/ShiftTab, arrows, Help/quit and Find draft Enter/Escape route into shared Pakar selection and explicit capability-gated actions; repeat presses cannot queue writes.","Theme/mouse/reconnect/help catalog, localized binding vocabulary and donor lamp actions [pending]","Theme/mouse/reconnect/help catalog, localized binding vocabulary and donor lamp actions: Lamp-specific actuation is replaced by ACKed table actions. Useful theme, mouse, reconnect and complete localized help remain pending rather than hidden; g refresh and r red are deliberate table bindings.","desktop/src/terminal_ui.rs:1067 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=fn shared_find_selection_and_arrow_navigation_never_write()] | tests/terminal_ui_test.mjs:23 [UTF8-LF sha256=3916ae657e18de899b278509cd0f8fad53886579c9af53deebc6e47cb5ff9b26; anchor=TUI must reject redirected surfaces before contacting a device]","Bounded checkpoint only; no whole-repository superiority or physical acceptance claim.","Complete useful omitted behavior and actual terminal/board acceptance; issue #2 remains open.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/locale_test.go","filesystem-snapshot-no-git","93fa11fcd3f102e7fc27d452e602a8d668ba70292ae4ffdafa0b3e524e50fd6e","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
@@ -2731,7 +2735,7 @@
"RayanLamp","internal/tui/settings.go","filesystem-snapshot-no-git","94d6ef0eb920d89dbaddde46ffdbe09209814d313b575e1364705c3e9975cc90","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/styles.go","filesystem-snapshot-no-git","bbd69a81bc66be2c5ef286ceb2dd68fcb1957de43c8417fd686bf0e1d916defc","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/terminal_test.go","filesystem-snapshot-no-git","86071faeeb5a76e5f64a04cfdff2843ca29c7455301fe04378f69ef1867e16e0","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
-"RayanLamp","internal/tui/terminal.go","filesystem-snapshot-no-git","3b56b9ee2bfa2e40b02e464c7cc2cd471d6dbb2148f06091bd56dfdf2926e1b3","device-shell | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/ShellWire.h:31 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class OutputWindow] | firmware/esp/src/portable-shell/portable_terminal.h:210 [UTF8-LF sha256=356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1; anchor=static inline size_t ps_terminal_text] | firmware/esp/src/portable-shell/portable_device_view.hpp:46 [UTF8-LF sha256=38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1; anchor=class DeviceView] | firmware/esp/src/ShellService.h:401 [UTF8-LF sha256=988751a727abecc9737d9f7b9b3e8d14c8031deb761a83133632d404da2215df; anchor=void closeDeviceShells()] | tools/device-shell.ps1:25 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=Exactly once; uncertain input is NEVER replayed.]","Session-fenced immutable input/output ownership is adapted into SerialChannel/OutputWindow and the host terminal tool: explicit Open/token, no uncertain input replay, nonwrapping sequences, output ACK before consumption and explicit close. | Terminal safety is adapted into bounded terminalText and TUI rendering: external CSI/OSC/DCS/control payloads cannot inject terminal actions, and only the project renderer emits ANSI; output/drafts/transcripts are bounded. | Independent session state, draft preservation, explicit backpressure and batched TUI draws retain the donor terminal's ownership/refresh principles without moving networking or actuator work into callbacks.","Go terminalOverlay, Bubble Tea viewport, 512KiB scrollback and host long-poll goroutines [replaced] | sanitizeDeviceTerminal printable UTF-8/SGR subset, bidi-aware text and scrollback viewport parity [pending] | Transparent raw serial and automatic uncertain write retry [replaced]","Go terminalOverlay, Bubble Tea viewport, 512KiB scrollback and host long-poll goroutines: An MCU-owned bounded ANSI TUI and PowerShell framed terminal replace this runtime, not a wholesale Go port. The device does not retain a 512KiB transcript or spawn host long-poll workers. | sanitizeDeviceTerminal printable UTF-8/SGR subset, bidi-aware text and scrollback viewport parity: Arduino sanitization intentionally substitutes non-ASCII and removes external SGR rather than reproducing the donor Unicode viewport. Persian editing, resize/mouse/scrollback and full terminal-emulation parity are not implemented by this slice. | Transparent raw serial and automatic uncertain write retry: An additive boot/token-fenced COBS channel preserves ordinary USB recovery. Host terminal sends input once and does not reopen/replay actions automatically after uncertainty.","tests/shell_wire_test.cpp:21 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=Lost read reply replays same bytes] | tests/shell_wire_test.cpp:26 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=output.sequence()==UINT16_MAX] | tests/shell_tui_test.cpp:19 [UTF8-LF sha256=650a377abfcef9bccf989b501d452b4d9a175f8970fa3fcc52f0e6d227bb1d2d; anchor=never-render-this] | tests/telnet_shell_test.cpp:115 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=Inspect the caller-owned backing bytes]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
+"RayanLamp","internal/tui/terminal.go","filesystem-snapshot-no-git","3b56b9ee2bfa2e40b02e464c7cc2cd471d6dbb2148f06091bd56dfdf2926e1b3","device-shell | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","firmware/esp/src/ShellWire.h:31 [UTF8-LF sha256=af3f23d908310dd349b0c90d1fc4385a212e6439b10c72f8dc2f37dace4b5216; anchor=class OutputWindow] | firmware/esp/src/portable-shell/portable_terminal.h:236 [UTF8-LF sha256=93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0; anchor=static inline size_t ps_terminal_text] | firmware/esp/src/portable-shell/portable_device_view.hpp:62 [UTF8-LF sha256=fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652; anchor=class DeviceView] | firmware/esp/src/ShellService.h:401 [UTF8-LF sha256=206e4ffa92af68705e11a0b905c136c75918892d7c0b24071e8704b89187dddb; anchor=void closeDeviceShells()] | tools/device-shell.ps1:25 [UTF8-LF sha256=4e197e3502eb6ef40d2a3df7bdde97ed3c05140c97e06e3d07426232732a9c55; anchor=Exactly once; uncertain input is NEVER replayed.]","Session-fenced immutable input/output ownership is adapted into SerialChannel/OutputWindow and the host terminal tool: explicit Open/token, no uncertain input replay, nonwrapping sequences, output ACK before consumption and explicit close. | Terminal safety is adapted into bounded terminalText and TUI rendering: external CSI/OSC/DCS/control payloads cannot inject terminal actions, and only the project renderer emits ANSI; output/drafts/transcripts are bounded. | Independent session state, draft preservation, explicit backpressure and batched TUI draws retain the donor terminal's ownership/refresh principles without moving networking or actuator work into callbacks.","Go terminalOverlay, Bubble Tea viewport, 512KiB scrollback and host long-poll goroutines [replaced] | sanitizeDeviceTerminal printable UTF-8/SGR subset, bidi-aware text and scrollback viewport parity [pending] | Transparent raw serial and automatic uncertain write retry [replaced]","Go terminalOverlay, Bubble Tea viewport, 512KiB scrollback and host long-poll goroutines: An MCU-owned bounded ANSI TUI and PowerShell framed terminal replace this runtime, not a wholesale Go port. The device does not retain a 512KiB transcript or spawn host long-poll workers. | sanitizeDeviceTerminal printable UTF-8/SGR subset, bidi-aware text and scrollback viewport parity: Arduino sanitization intentionally substitutes non-ASCII and removes external SGR rather than reproducing the donor Unicode viewport. Persian editing, resize/mouse/scrollback and full terminal-emulation parity are not implemented by this slice. | Transparent raw serial and automatic uncertain write retry: An additive boot/token-fenced COBS channel preserves ordinary USB recovery. Host terminal sends input once and does not reopen/replay actions automatically after uncertainty.","tests/shell_wire_test.cpp:21 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=Lost read reply replays same bytes] | tests/shell_wire_test.cpp:26 [UTF8-LF sha256=d3b4221251b6d150bdec3b9a9e78d3506e9d9c123b5699eef2919cc957da8562; anchor=output.sequence()==UINT16_MAX] | tests/shell_tui_test.cpp:28 [UTF8-LF sha256=1ab6c44565526636089091f51719d2a0abbc9512a493ae0e97194d4a9214f1a7; anchor=never-render-this] | tests/telnet_shell_test.cpp:115 [UTF8-LF sha256=9554e3744bd58435e52bbc0879ebcb6fe24a965eb9db111e05812d7db8739b62; anchor=Inspect the caller-owned backing bytes]","Source comparison and hash-bound fixture references only; deployment, terminal interoperability and hardware acceptance remain pending.","Compile and deploy the integrated shell, then prove simultaneous bounded Telnet sessions, AP/station/Ethernet listener and discovery changes, and framed USB recovery alongside normal protocol commands. | Exercise authentication changes, disconnect/reconnect, slow-reader backpressure and maintenance exclusion on the actual board; an immediate OK/queued command is not proof of AVR execution or physical LED/display/audio success.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/text.go","filesystem-snapshot-no-git","ac575e39f688d9c796f4b013fdb768b0b3ecb5bbda04908950f14fc7217e327e","terminal-ui | rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/2 | https://github.com/atomicdeploy/PakarController/issues/3","parity-gap","complete","desktop/src/terminal_ui.rs:26 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=pub fn safe_line(value: &str)]","Actual sanitizeTerminalLine unsafe-control/bidi filtering and cell-aware truncation adapt to Unicode Rust strings and unicode-width. Removes OSC/CSI/DCS sequences and bounds work while retaining Persian text.","Donor renderer-specific text entry points and full terminal shaping acceptance [pending]","Donor renderer-specific text entry points and full terminal shaping acceptance: Rust TUI renderer calls one sanitizer for every emitted line rather than copying Go view APIs. Actual Persian shaping/terminal-font behavior is not inferred from string-preservation tests.","desktop/src/terminal_ui.rs:1013 [UTF8-LF sha256=762c7ee8137a21fdbd83a40bb776238a6f75b943da35d01073bbff6b2dd64379; anchor=fn donor_text_safety_preserves_unicode_not_peer_terminal_commands()] | tests/terminal_ui_test.mjs:23 [UTF8-LF sha256=3916ae657e18de899b278509cd0f8fad53886579c9af53deebc6e47cb5ff9b26; anchor=TUI must reject redirected surfaces before contacting a device]","Bounded checkpoint only; no whole-repository superiority or physical acceptance claim.","Complete useful omitted behavior and actual terminal/board acceptance; issue #2 remains open.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/theme_fallback_other.go","filesystem-snapshot-no-git","284e86a2bd47127ba7645fa70108417b86a90dbfdc6feda9cbee9d7f3c3d4025","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
"RayanLamp","internal/tui/theme_fallback_windows.go","filesystem-snapshot-no-git","b626c4d778f4ba9637eaeb1e997464efdf395ac7f9e1b157e05fbed35aeb85c9","rayanlamp-reuse","https://github.com/atomicdeploy/PakarController/issues/3","pending-comparison","not-certified-by-this-comparison","","","Not yet determined; not an accepted exclusion","No approved reason to leave this file out. screened is not behavior-comparison proof.","","","Read and compare this exact donor; map every applicable behavior and justify each omission.","Not established by source/hash/test-code comparison; see issue #2"
diff --git a/docs/legacy-source-ledger.csv b/docs/legacy-source-ledger.csv
index ca2077d..a0c543f 100644
--- a/docs/legacy-source-ledger.csv
+++ b/docs/legacy-source-ledger.csv
@@ -2285,13 +2285,17 @@
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","examples/linux_example.c","543e25c4d9c5a99ed9b7d225f33d984f05f5efd61e2bd43e68be75eec22aebf1","2430","application-core","present","shell","reviewed-open","tools/device-shell.ps1 + third_party/portable-shell/README.md","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Read complete termios raw-mode sample and parameter setup; existing host shell tool retained. Use the existing device client/owned USB transport rather than a disconnected demo process or false status. Runtime behavior cannot be fabricated."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","examples/telnet_server.c","779a8e30dba97d85f24e2d3a76d7b3d1a3aeeb4b8066682d90bfdc5bcb0f4295","1179","application-core","present","shell","reviewed-open","firmware/esp/src/ShellService.h","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Read entire skeleton; preserved as reference, not wired into production. Pakar already has actual bounded multi-session Telnet admission, authentication and network lifecycle."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","IMPLEMENTATION.md","d024ffc22f99a7c9f50fa68bbaf02b0e722d68b0edaf39512454643e6f1986e2","6217","documentation","present","shell","reviewed-open","firmware/esp/src/ShellService.h + third_party/portable-shell/README.md","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Read each asserted requirement and reconciled against actual donor code rather than trusting completion checkmarks. No synchronization exists for raw pointers; editor only prints and waits. Existing loop ownership and staged TUI retained."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_device_table.hpp","784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2","23512","application-core","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_device_table.hpp","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_device_view.hpp","38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1","41937","ui-design","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_device_view.hpp","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_editor.h","1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea","17531","firmware-hardware","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_editor.h","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_shell_editor.hpp","9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5","5741","application-core","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_shell_editor.hpp","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_device_table.hpp","37b99ff1e9322f84ec0ccf80893034f545bbc78255ef067a6c0900f1cbed3b73","35986","application-core","present","shell","stale-review","","Source changed since manual review of 784b6315a407af641060db4f01e4caf92148bf149389b79d5b5e397946c525a2. Re-read required."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_device_view.hpp","fb02160a3c97be72039964175768bf9ad8c4efb5e3812c60c3cbcf18a179a652","42694","ui-design","present","shell","stale-review","","Source changed since manual review of 38ccd92c729abb086088e2a76e0a3e35130b8ed5e73c22968af56dd1e044fdf1. Re-read required."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_editor.h","00bd8c83ae9d7ad8fdc3ae963e37acabf0b3f93096758f0294cdebed2f11cbd3","21507","firmware-hardware","present","shell","stale-review","","Source changed since manual review of 1187b7714998afaa5a045f333c50ce3081de846c6c9b0d9b41b8186723bb48ea. Re-read required."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_meter.h","48350a03fd496aaf50bf47149f8a099d7d07de9175024ef43a917e0a7cf53f1d","9221","firmware-hardware","present","shell","screened","","Pinned branch read through EOF; immutable source snapshot (CodeQL marker recorded only). Manual dispositions in file-comparison-evidence.json."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_shell_editor.hpp","1289e6667828d637042de29b31941427d8a9f222c3a7c967fd751f3b0c7ba7c3","5904","application-core","present","shell","stale-review","","Source changed since manual review of 9bf1a3b590217b8b1cc6562fbd681f9a71af9d761f057ac10585f597d3e11ff5. Re-read required."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","include/portable_shell.h","d2f886686945f5df81dea9aa42f8f6c364e7f54c01c614c3d14d6e9d92815fa9","4055","firmware-hardware","present","shell","adapted","firmware/esp/src/PortableShellCore.h + firmware/esp/src/TelnetShell.h + firmware/esp/src/ShellService.h","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Adapted PSCommand name/help model and portable callback output into immutable command catalogue/Terminal; independent session state remains in LineEditor. Build-time immutable descriptors prevent dangling name/callback ownership and duplicate registrations; C++ types remain intact; settings go through validation."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_style.h","d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11","4054","firmware-hardware","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_style.h","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
-"portable-shell","e90ffc004b39912e21a1799a75061ac61f22c74f","include/portable_terminal.h","356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1","14369","firmware-hardware","present","shell","reviewed-open","firmware/esp/src/portable-shell/portable_terminal.h","Complete source read; shared rich view and default feature flags. Production hardware acceptance remains open."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_style.h","a29a3338b6ee4523fc394adeb496146f82840c931b4ac7035e52502bf9ce3f6e","9019","firmware-hardware","present","shell","stale-review","","Source changed since manual review of d86b758b8d8509667ee5e8a49b9ab8319b8961b32923f89b89ea3d23e3db0e11. Re-read required."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_table_policy.h","eed0e170b4b80284b0cbbda87c7998480ec6d4040192d0402ca5af112ddfec04","405","firmware-hardware","present","shell","screened","","Pinned branch read through EOF; immutable source snapshot (CodeQL marker recorded only). Manual dispositions in file-comparison-evidence.json."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_terminal.h","93bed5802f25a474daed05338817b37b141a400f553a3ba5c7ca3857e1faeaf0","15294","firmware-hardware","present","shell","stale-review","","Source changed since manual review of 356dea61f6096e06a6328691d8f919c2dc582086f2234714cf5efbb4ffef79e1. Re-read required."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_undo.h","adf67d72d371b0e15fb1be3c64dfdea5155840b55a23a987be5807437fed9be0","2232","firmware-hardware","present","shell","screened","","Pinned branch read through EOF; immutable source snapshot (CodeQL marker recorded only). Manual dispositions in file-comparison-evidence.json."
+"portable-shell","3a7eeba179c238226deef1b64e3bd3b86c4e12e7","include/portable_util.h","568c68488cc8821e694b2ba576f138dc4d0d6f8f9970e83b99188b7727014256","653","firmware-hardware","present","shell","screened","","Pinned branch read through EOF; immutable source snapshot (CodeQL marker recorded only). Manual dispositions in file-comparison-evidence.json."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","include/ps_io.h","024a3ac2231b9b76b3a558a7f81406b5660ba7694c4f9e2f9b7caa585f05bcb1","1028","firmware-hardware","present","shell","reviewed-open","firmware/esp/src/ShellService.h + firmware/esp/src/ShellWire.h","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Read callback/context contracts; preserved per-session transport-independent output and existing explicit USB ownership. Existing WiFiClient/SerialChannel owners already provide the real transports with authentication and replay fencing; no duplicate socket or serial owner."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","LICENSE","b00e51aca5e080d2fcb8734e9a4779dc917e8df2f38730cca2e82e4ad08184aa","1084","application-core","present","shell","reviewed-open","third_party/portable-shell/README.md + firmware/esp/src/PortableShellCore.h","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. MIT copyright and complete license retained with original source; derived header points to this notice. MIT notice preserved."
"portable-shell","67c5dcb8a63f4055cbdaf02e9ff271c53b46410d","Makefile","e9d0814b7f08de2a7f53d004192a60cb329652e7b745bdee379144373cb26f4f","1724","application-core","present","shell","reviewed-open","tools/test-shell.ps1 + third_party/portable-shell/README.md","Read all source through EOF at 67c5dcb8a63f4055cbdaf02e9ff271c53b46410d. Read full targets and retained upstream build recipe as reference. Tests integrated into the existing guarded native runner. No second build cache or uncontrolled system installation; existing cross-platform canonical low-write test/build wrappers remain authoritative."
diff --git a/firmware/esp/src/ShellService.h b/firmware/esp/src/ShellService.h
index 0501808..4b033b8 100644
--- a/firmware/esp/src/ShellService.h
+++ b/firmware/esp/src/ShellService.h
@@ -223,16 +223,16 @@ void executeDeviceShell(DeviceShellSession &s,String line){
else if(args=="truecolor on"||args=="truecolor off")s.tui.setTrueColor(args.endsWith("on"));
else if(args.startsWith("theme ")){
const String theme=args.substring(6);using T=PakarShell::ShellTui;
- if(theme!="violet"&&theme!="red"&&theme!="lumen"&&theme!="onedark"){
- shellResult(s,false,F("Use terminal theme violet|red|lumen|onedark"));shellPrompt(s);return;}
- s.tui.setTheme(theme=="red"?T::Red:theme=="lumen"?T::Lumen:theme=="onedark"?T::OneDark:T::Violet);
+ if(theme!="default"&&theme!="violet"&&theme!="red"&&theme!="cyan"&&theme!="onedark"){
+ shellResult(s,false,F("Use terminal theme default|violet|red|cyan|onedark"));shellPrompt(s);return;}
+ s.tui.setTheme(theme=="default"?T::Default:theme=="red"?T::Red:theme=="cyan"?T::Cyan:theme=="onedark"?T::OneDark:T::Violet);
}
else if(args.startsWith("size ")){
const int split=args.indexOf(' ',5);uint32_t w=0,h=0;
if(split<0||!PakarShell::unsignedNumber(args.substring(5,split).c_str(),240,w)||
!PakarShell::unsignedNumber(args.substring(split+1).c_str(),100,h)||!s.parser.size(w,h)){
shellResult(s,false,F("Use terminal size 40..240 16..100"));shellPrompt(s);return;}
- }else if(args.length()){shellResult(s,false,F("Use terminal unicode|color|truecolor on|off, terminal theme violet|red|lumen|onedark, terminal size COLS ROWS"));shellPrompt(s);return;}
+ }else if(args.length()){shellResult(s,false,F("Use terminal unicode|color|truecolor on|off, terminal theme default|violet|red|cyan|onedark, terminal size COLS ROWS"));shellPrompt(s);return;}
s.print(F("Terminal "));s.print(s.parser.terminal());s.print(F(" | "));s.print(s.parser.columns());s.print(s.unicode?F(" \xc3\x97 "):F(" x "));s.print(s.parser.rows());
s.print(F(" | Unicode "));s.print(s.unicode?F("on"):F("off"));s.print(F(" | ANSI "));s.println(s.color&&s.parser.ansi()?F("on"):F("off"));
}else if(verb=="game"){
diff --git a/firmware/esp/src/ShellTuiBinding.h b/firmware/esp/src/ShellTuiBinding.h
index e4098e3..d732e52 100644
--- a/firmware/esp/src/ShellTuiBinding.h
+++ b/firmware/esp/src/ShellTuiBinding.h
@@ -52,14 +52,14 @@ struct ShellTuiSnapshot {
else if(session.tui.page()==T::TerminalPage){
values[0]=session.parser.terminal();values[1]=String(session.parser.columns());values[2]=String(session.parser.rows());
values[3]=session.unicode?"true":"false";values[4]=session.color?"true":"false";
- static const char *const themes[]={"violet","red","lumen","onedark"};
+ static const char *const themes[]={"violet","red","cyan","onedark","default"};
values[5]=themes[session.tui.theme()];values[6]=session.tui.trueColor()?"true":"false";
const char *labels[]={"Terminal type","Columns","Rows","Unicode","ANSI color","Theme","True color"};
const char *keys[]={"terminal.type","terminal.columns","terminal.rows","terminal.unicode","terminal.color","terminal.theme","terminal.truecolor"};
view.rowCount=7;for(size_t i=0;i<7;++i){rows[i]={labels[i],keys[i],values[i].c_str(),i!=0,false};rows[i].category="Session";}
rows[1].kind=rows[2].kind=T::Level;rows[1].minimum=40;rows[1].maximum=240;rows[2].minimum=16;rows[2].maximum=100;
rows[3].kind=rows[4].kind=rows[6].kind=T::Toggle;
- rows[5].kind=T::Choice;rows[5].choices=themes;rows[5].choiceCount=4;return;
+ rows[5].kind=T::Choice;rows[5].choices=themes;rows[5].choiceCount=PS_COUNT_OF(themes);return;
}
view.rowCount=count;
for(size_t i=0;i9+shift+diagnostics-(PS_DEVICE_VIEW_PLAIN_ROWS?2:0)?s.lines-9-shift-diagnostics+(PS_DEVICE_VIEW_PLAIN_ROWS?2:0):1;
+ if(visible>PS_DEVICE_VIEW_MAX_ROWS)visible=PS_DEVICE_VIEW_MAX_ROWS;
+ const unsigned reserve=8192U+unsigned(s.lines)*110U;
+ const unsigned budget=(PS_DEVICE_VIEW_FRAME_BYTES>reserve?PS_DEVICE_VIEW_FRAME_BYTES-reserve:1)/(w+450U);
+ if(visible>budget)visible=budget?budget:1;
+ return visible;
+}
+unsigned tableGutter(const Snapshot &s,unsigned width,unsigned offset) const {
+ if(!ansi_||compactScrollbar_||width-offset<64)return 0;
+ if(reserveScrollbar_)return 2;
+ uint16_t items[PS_DEVICE_VIEW_MAX_ROWS*4];unsigned focus;
+ return sectionItems(s,items,focus,true)>tableCapacity(s,width-1-offset)?2:0;
+}
/* DeviceView members. Shared device presentation, independent of CPU/transport.
* Typed controls and palettes follow portable_controls.inc; applications retain
* validators, authorization, pending state and all physical side effects. */
@@ -29,7 +45,6 @@ static uint32_t rowHash(const Row &r) {
return h;
}
static bool validValue(const Row &r,const char *value) {
- if(!PS_DEVICE_VIEW_WIDGETS)return true;
if(r.kind==Toggle)return toggle(value)>=0;
if(r.kind==Level){uint32_t n;return decimal(value,n)&&n>=r.minimum&&n<=r.maximum;}
if(r.kind==Choice){for(unsigned i=0;r.choices&&ikey);a.type=Action::Set;dirty_=true;return true;
}
bool startDialog(const Snapshot &s,Action &a) {
- if(editing_||grid_)return false;
+ if(!PS_DEVICE_VIEW_DIALOGS||editing_||grid_)return false;
feed('\r',s,a);crPending_=false;
if(editing_){dialog_=true;full_=dirty_=true;}return false;
}
@@ -70,8 +85,78 @@ static bool pageAvailable(unsigned p,const Snapshot &s) {
(p!=Game||(PS_DEVICE_VIEW_DIAGNOSTICS&&s.canGame))&&(p!=TerminalPage||s.terminalSettings)&&
(p!=Cloud||s.cloudSettings)&&(p!=Notifications||s.notificationSettings)&&(p!=Clock||s.clockSettings);
}
+
+bool mouse(const ps_mouse_event &e,const Snapshot &s,Action &a){
+ const unsigned b=e.button,x=e.column,y=e.row,button=b&3,drag=b&32;
+ if(e.release){mouseDrag_=false;return false;}
+ if(!ansi_||s.columns<40||s.lines<16||x>s.columns||y>s.lines||(b&~127U)||(b&28U))return false;
+ if(b&64U){if(!editing_&&!grid_&&button<2)for(unsigned i=0;i<3;++i)arrow(button?'B':'A',s,a);return false;}
+ if(button!=0&&button!=2)return false;
+ if(drag&&!mouseDrag_)return false;
+ if(!drag)mouseDrag_=true;
+ const unsigned width=s.columns>240?240:s.columns;
+ const unsigned offset=PS_DEVICE_VIEW_VERTICAL_TABS&&tabs_==VerticalTabs&&width>=64?22:0;
+ const unsigned w=width-1-offset-tableGutter(s,width,offset),shift=tabsInsideTable_?0:1,body=6+shift-(PS_DEVICE_VIEW_PLAIN_ROWS?2:0);
+ if(editing_){
+ if(button==2&&!drag){clearDraft();dirty_=full_=true;return false;}
+ if(y==editRow_&&x>=editColumn_&&x=editWidth_?cursor_-editWidth_+1:0;
+ cursor_=start+x-editColumn_;if(cursor_>ps_text_length(draft_))cursor_=ps_text_length(draft_);
+ dirty_=true;
+ }
+ return false;
+ }
+ if(!drag){
+ unsigned hit=PageCount;
+ if(offset&&x<=20&&y>=3&&yw-4&&p!=page_))continue;
+ unsigned n=unsigned(ps_text_length(pageName(p)))+3;
+ if(x>=left&&x=body+mouseVisible_)return false;
+ uint16_t items[PS_DEVICE_VIEW_MAX_ROWS*4];unsigned focus;
+ const unsigned total=sectionItems(s,items,focus,true);
+ if(x>=offset+w&&xmouseVisible_){
+ line=(y-body)*(total-1)/(mouseVisible_>1?mouseVisible_-1:1);
+ while(line=total||(items[line]&0x6000))return false;
+ selected_=uint8_t(items[line]&0x1fff);sectionFocus_=!!(items[line]&0x8000);dirty_=true;
+ if(x>=offset+w)return false;
+ if(sectionFocus_){if(!drag)toggleSection(s,0);return false;}
+ const Row *r=currentRow(s);if(!r)return false;
+ if(button==2&&!drag)return startDialog(s,a);
+ const unsigned labelWidth=w<59?12:w<95?24:28;
+ const unsigned valueX=offset+labelWidth+6+(groups_==CategoryColumn&&w>=95?19:0);
+ if(xeditable||r->pending||r->secret||!keySafe(r->key))return false;
+ if(PS_DEVICE_VIEW_WIDGETS&&r->kind==Level&&r->maximum>r->minimum&&r->step){
+ uint32_t observed;if(!decimal(r->value,observed)||observedminimum||observed>r->maximum)return false;
+ const unsigned span=offset+w>valueX+2?offset+w-valueX-2:1;
+ const unsigned position=x-valueX>span?span:x-valueX;
+ const uint32_t range=r->maximum-r->minimum;
+ uint32_t n=r->minimum+(range/span)*position+(range%span)*position/span;
+ n=r->minimum+((n-r->minimum)/r->step)*r->step;
+ if(position==span)n=r->maximum;
+ if(n==observed)return false;
+ numberText(a.value,n);copy(a.key,sizeof a.key,r->key);a.type=Action::Set;return true;
+ }
+ if(!drag){if(PS_DEVICE_VIEW_WIDGETS&&(r->kind==Toggle||r->kind==Choice))return adjust(' ',s,a);return feed('\r',s,a);}
+ return false;
+}
template void base(Writer &out) const {if(ansi_)out.raw(ps_style_base(theme_,trueColor_));}
-template void ink(Writer &out,unsigned role) const {if(ansi_)out.raw(ps_style_ink(theme_,trueColor_,true,role));}
+template void ink(Writer &out,unsigned role) const {if(ansi_)out.raw(ps_style_ink(theme_,trueColor_,theme_!=Default,role));}
template void edge(Writer &out,const Snapshot &s,const char *unicode,const char *ascii) const {
base(out);ink(out,7);out.raw(s.unicode?unicode:ascii);base(out);
}
@@ -82,7 +167,7 @@ template void rule(Writer &out,const Snapshot &s,unsigned width,cons
}
template void gridRule(Writer &out,const Snapshot &s,unsigned w,unsigned group,unsigned label,const char *junction,const char *left,const char *right) const {
base(out);ink(out,7);out.raw(s.unicode?left:"+");
- for(unsigned i=1;i void value(Writer &out,const Snapshot &s,const Row &r,unsig
if(r.secret){ink(out,0);out.field("[hidden]",width);return;}
if(r.pending){ink(out,2);out.field("Pending readback",width);return;}
if(PS_DEVICE_VIEW_WIDGETS&&r.kind==Toggle){
- const int on=toggle(r.value);ink(out,on<0?0:on?5:3);
+ const int on=toggle(r.value);ink(out,on<0?0:on?5:theme_==Default?PS_INK_INACTIVE:3);
if(on<0){out.field("Unknown",width);return;}
out.raw(s.unicode?(on?"\xe2\x97\x8f ":"\xe2\x97\x8b "):(on?"+ ":"- "));
out.field(on?r.onLabel:r.offLabel,width-2);return;
}
+ if(PS_DEVICE_VIEW_WIDGETS&&r.kind==Level&&r.meter&&r.meterStyle==PS_METER_CHEVRONS&&width>=6){
+ char text[244];ps_meter_value_field(text,sizeof text,width,r.value,s.unicode);
+ ink(out,PS_INK_NUMBER);out.raw(text);return;
+ }
uint32_t n;
if(PS_DEVICE_VIEW_WIDGETS&&r.kind==Level&&r.meter&&width>=22&&decimal(r.value,n)&&r.maximum>r.minimum&&n>=r.minimum&&n<=r.maximum){
// At most ten cells, no multiplication overflow for uint32 ranges.
const unsigned bar=10;const uint32_t span=r.maximum-r.minimum,offset=n-r.minimum;
- const uint32_t quantum=span/bar+(span%bar!=0);
- const unsigned filled=n==r.maximum?bar:unsigned(offset/quantum);
- for(unsigned i=0;i void hint(Writer &out,const char *text) const {
+ bool first=true;
+ while(*text){
+ char token[96];unsigned n=0;
+ const bool space=(unsigned char)*text<=32;
+ while(text[n]&&((unsigned char)text[n]<=32)==space&&n+1 void footer(Writer &out,const Snapshot &s,unsigned w) const {
+ if(ansi_){out.indent(0);out.position(s.lines-1);out.raw("\x1b[J");}
base(out);ink(out,0);
+ char line[384]={0},aligned[512];
if(editing_&&dialog_&&ansi_)return; // The modal owns its only Apply/Cancel hint.
- if(editing_){out.raw(s.unicode?"\xe2\x86\x90/\xe2\x86\x92 cursor Enter apply Esc cancel":"Left/Right cursor Enter apply Esc cancel");out.raw("\r\n");return;}
+ if(editing_){ps_style_footer_append(line,sizeof line,s.unicode?"\xe2\x86\x90/\xe2\x86\x92 cursor Enter apply Esc cancel":w<60?"Left/Right move Enter apply Esc cancel":"Left/Right cursor Enter apply Esc cancel");ps_style_footer_append(line,sizeof line,"\r\n");ps_style_footer_format(aligned,sizeof aligned,line,w,footerGrid_);hint(out,aligned);return;}
const Row *r=currentRow(s);
if(grid_&&page_==Led){
- out.raw(s.unicode?"\xe2\x86\x91\xe2\x86\x93\xe2\x86\x90\xe2\x86\x92 move":"Up/Down Left/Right");
- out.raw(w>=60?" W idle R lost O off":" W/R/O state");
- }else if(grid_&&page_==Pixel){out.raw(s.unicode?"\xe2\x86\x90/\xe2\x86\x92 test G group W/R/O color":"Left/Right test G group W/R/O color");}
- else if(page_==Game&&grid_){out.raw(s.gameReady?"1-9 press R reset":"AVR unavailable");}
- else {out.raw(s.unicode?"\xe2\x86\x91/\xe2\x86\x93 select":"Up/Down select");
+ ps_style_footer_append(line,sizeof line,s.unicode?"\xe2\x86\x91\xe2\x86\x93\xe2\x86\x90\xe2\x86\x92 move":"Up/Down Left/Right");
+ ps_style_footer_append(line,sizeof line,w>=60?" W idle R lost O off":" W/R/O state");
+ }else if(grid_&&page_==Pixel){ps_style_footer_append(line,sizeof line,s.unicode?"\xe2\x86\x90/\xe2\x86\x92 test G group W/R/O color":"Left/Right test G group W/R/O color");}
+ else if(page_==Game&&grid_){ps_style_footer_append(line,sizeof line,s.gameReady?"1-9 press R reset":"AVR unavailable");}
+ else if(sectionFocus_&&!sectionsCollapsible_){ps_style_footer_append(line,sizeof line,s.unicode?"โ/โ select":"Up/Down select");}
+ else if(sectionFocus_){ps_style_footer_append(line,sizeof line,w<60?"Enter/Space toggle":s.unicode?"Enter/Space toggle \xe2\x86\x90 collapse \xe2\x86\x92 expand":"Enter/Space toggle Left close Right open");}
+ else {ps_style_footer_append(line,sizeof line,s.unicode?"\xe2\x86\x91/\xe2\x86\x93 select":"Up/Down select");
if(r&&r->editable&&!r->pending){
- out.raw(PS_DEVICE_VIEW_WIDGETS&&r->kind==Toggle?" Space toggle":PS_DEVICE_VIEW_WIDGETS&&r->kind==Choice?(s.unicode?" \xe2\x86\x90/\xe2\x86\x92 choose":" +/- choose"):PS_DEVICE_VIEW_WIDGETS&&r->kind==Level?(s.unicode?" \xe2\x86\x90/\xe2\x86\x92 adjust":" +/- adjust"):" Enter edit");
- if(w>=70&&r->kind==Level)out.raw(" Enter edit");
- if(w>=90&&PS_DEVICE_VIEW_DIALOGS)out.raw(" F2 dialog");
+ ps_style_footer_append(line,sizeof line,PS_DEVICE_VIEW_WIDGETS&&r->kind==Toggle?" Space toggle":PS_DEVICE_VIEW_WIDGETS&&r->kind==Choice?(s.unicode?" \xe2\x86\x90/\xe2\x86\x92 choose":" +/- choose"):PS_DEVICE_VIEW_WIDGETS&&r->kind==Level?(s.unicode?" \xe2\x86\x90/\xe2\x86\x92 adjust":" +/- adjust"):" Enter edit");
+ if(w>=70&&r->kind==Level)ps_style_footer_append(line,sizeof line," Enter edit");
+ if(w>=90&&PS_DEVICE_VIEW_DIALOGS)ps_style_footer_append(line,sizeof line," F2 dialog");
}
}
- out.raw("\r\n");
- if(page_==Led||page_==Pixel||page_==Game)out.raw(grid_?"F settings ":page_==Led?"F matrix ":page_==Game?"F game ":"F pixels ");
- out.raw("Tab pages Esc close\r\n");
+ ps_style_footer_append(line,sizeof line,"\r\n");
+ if(page_==Led||page_==Pixel||page_==Game)ps_style_footer_append(line,sizeof line,grid_?"F settings ":page_==Led?"F matrix ":page_==Game?"F game ":"F pixels ");
+ ps_style_footer_append(line,sizeof line,w>=70?"Tab pages Shift+Tab previous Esc close":"Tab pages Esc close");
+ ps_style_footer_format(aligned,sizeof aligned,line,w,footerGrid_);hint(out,aligned);
+ if(ansi_)out.raw("\x1b[K");else out.raw("\r\n");
+}
+
+// Blank viewport cells are painted with cursor movement, not a frame buffer.
+template void blankRows(Writer &out,const Snapshot &s,unsigned first,unsigned last,
+ unsigned w,unsigned group=0,unsigned label=0) const {
+ if(!ansi_)return;
+ base(out);ink(out,7);
+ for(unsigned row=first;row<=last;++row){
+ out.position(row);out.raw("\x1b[K");
+ out.raw(s.unicode?"\xe2\x94\x82":"|");
+ if(group&&!PS_DEVICE_VIEW_PLAIN_ROWS){out.position(row,group+4);out.raw(s.unicode?"\xe2\x94\x82":"|");}
+ if(label&&!PS_DEVICE_VIEW_PLAIN_ROWS){out.position(row,label+4+(group?group+3:0));out.raw(s.unicode?"\xe2\x94\x82":"|");}
+ out.position(row,w);out.raw(s.unicode?"\xe2\x94\x82":"|");
+ }
+ base(out);
+}
+
+template void sidebar(Writer &out,const Snapshot &s,unsigned height) const {
+ out.position(1);rule(out,s,20,"\xe2\x95\xad","\xe2\x95\xae",false);
+ unsigned page=0;
+ for(unsigned row=2;row2&&page==PageCount){
+ ink(out,7);out.position(row);out.raw(s.unicode?"\xe2\x94\x82":"|");
+ out.field("",18);
+ out.position(row,20);out.raw(s.unicode?"\xe2\x94\x82":"|");continue;
+ }
+ out.position(row);edge(out,s,"\xe2\x94\x82 ","| ");
+ if(row==2){ink(out,0);out.field("Pages",16);}
+ else {
+ while(page void matrix(Writer &out,const Snapshot &s,unsigned w){
+template void matrix(Writer &out,const Snapshot &s,unsigned w,unsigned shift){
const unsigned slots=s.cellStaggered?20:10,tile=w>=slots*4+6?4:3;
const unsigned visibleX=(w-6)/tile=visibleX?selectedX-visibleX+1:0;
- const unsigned visibleY=s.lines>=18?8:s.lines-10,startY=selectedY>=visibleY?selectedY-visibleY+1:0;
- rule(out,s,w,"\xe2\x94\x9c","\xe2\x94\xa4");
+ const unsigned visibleY=s.lines>=18+shift?8:s.lines-10-shift,startY=selectedY>=visibleY?selectedY-visibleY+1:0;
+ rule(out,s,w,shift?"โญ":"โ",shift?"โฎ":"โค");
edge(out,s,"\xe2\x94\x82 ","| ");ink(out,4);
out.raw(s.cellStaggered?(s.unicode?"20 \xc3\x97 4 staggered":"20 x 4 staggered"):(s.unicode?"10 \xc3\x97 8 cells":"10 x 8 cells"));
const unsigned heading=s.cellStaggered?16:12;
@@ -167,9 +316,9 @@ template void matrix(Writer &out,const Snapshot &s,unsigned w){
const unsigned bright=((rgb>>16)&255)*299+((rgb>>8)&255)*587+(rgb&255)*114;
if(ansi_){
if(state>=4){ink(out,0);}
- else if(trueColor_){char style[80];
+ else if(PS_STYLE_THEMES&&trueColor_){char style[80];
ps_terminal_format(style,sizeof(style),"\x1b[48;2;%u;%u;%um\x1b[38;2;%u;%u;%um",(rgb>>16)&255,(rgb>>8)&255,rgb&255,bright>128000?20:255,bright>128000?20:255,bright>128000?20:255);out.raw(style);}
- else {const unsigned index=16+36*(((rgb>>16)&255)*5/255)+6*(((rgb>>8)&255)*5/255)+(rgb&255)*5/255;
+ else if(PS_STYLE_THEMES){const unsigned index=16+36*(((rgb>>16)&255)*5/255)+6*(((rgb>>8)&255)*5/255)+(rgb&255)*5/255;
char style[40];ps_terminal_format(style,sizeof(style),"\x1b[48;5;%um\x1b[%um",index,bright>128000?30:97);out.raw(style);}
if(picked)out.raw("\x1b[1;4m");
}
@@ -181,99 +330,144 @@ template void matrix(Writer &out,const Snapshot &s,unsigned w){
}
out.field("",w-6-visibleX*tile);edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");
}
+ if(ansi_){blankRows(out,s,5+shift+visibleY,s.lines-4,w);out.position(s.lines-3);}
rule(out,s,w,"\xe2\x95\xb0","\xe2\x95\xaf");
+ if(ansi_){out.indent(0);out.position(s.lines-2);}
const unsigned state=s.cellStates?s.cellStates[cell_-1]:255;char status[128];
ps_terminal_format(status,sizeof(status),"Cell %02u %s",cell_,cellName(s,state));ink(out,4);out.safe(status,w>=60?24:w);
if(w>=60){const uint32_t rgb=cellRgb(s,cell_);char hex[8]={'#',0};const char *digits="0123456789ABCDEF";
for(unsigned i=0;i<6;++i)hex[i+1]=digits[(rgb>>(20-4*i))&15];
if(state<4){out.raw(" ");out.raw(hex);}ink(out,0);out.raw(" ");
out.safe(*notice_?notice_:"Configured state colors",w-35);}
- else if(*notice_){out.raw("\r\n");out.safe(notice_,w);}
- out.raw("\r\n");footer(out,s,w);if(ansi_)out.raw("\x1b[J");
+ else if(*notice_){const unsigned statusLength=unsigned(ps_text_length(status));if(statusLength+2 bool renderTable(const Snapshot &s,Write write) {
Writer out(write,ansi_);
const unsigned width=s.columns>240?240:s.columns;
- bool paintAll=full_||!ansi_;
+ bool paintAll=full_||!ansi_||!PS_DEVICE_VIEW_RETAIN_ROWS;
if(ansi_){base(out);out.raw(full_?"\x1b[H\x1b[2J":"\x1b[H");}
full_=false;
if(width<40||s.lines<16){out.raw(s.unicode?"Resize terminal to 40 \xc3\x97 16\r\n":"Resize terminal to 40 x 16\r\n");return true;}
// Leave the physical terminal's last column free to avoid delayed autowrap.
- const unsigned w=width-1;
- base(out);ink(out,7);out.raw(s.unicode?"\xe2\x95\xad\xe2\x94\x80 ":"+- ");
+ const unsigned offset=PS_DEVICE_VIEW_VERTICAL_TABS&&tabs_==VerticalTabs&&ansi_&&width>=64?22:0;
+ const unsigned gutter=tableGutter(s,width,offset);
+ if(gutter!=paintedGutter_){paintAll=true;paintedGutter_=gutter;if(ansi_)out.raw("\x1b[H\x1b[2J");}
+ const unsigned w=width-1-offset-gutter;
+ const unsigned shift=tabsInsideTable_?0:1,body=6+shift-(PS_DEVICE_VIEW_PLAIN_ROWS?2:0);
+ if(offset){sidebar(out,s,s.lines-3);out.indent(offset);out.position(1);}
+ base(out);ink(out,7);if(!shift)out.raw(s.unicode?"\xe2\x95\xad\xe2\x94\x80 ":"+- ");
ink(out,4);char title[241];const unsigned titleSize=unsigned(terminalText(title,w-8,s.title));out.raw(title);
- ink(out,7);out.raw(" ");for(unsigned i=titleSize+4;iw-4&&i!=page_))continue;
+ if(offset||!pageAvailable(i,s)||(tabTotal>w-4&&i!=page_))continue;
if(i==page_&&ansi_)out.raw(ps_style_surface(theme_,trueColor_,1));else ink(out,0);
- out.raw(i==page_?"[":" ");out.raw(pageName(i));out.raw(i==page_?"] ":" ");base(out);
+ out.raw(i==page_&&(tabBrackets_||!ansi_)?"[":" ");out.raw(pageName(i));out.raw(i==page_&&(tabBrackets_||!ansi_)?"]":" ");base(out);
+ out.raw(" "); // Inter-tab spacing is outside the selected surface.
used+=unsigned(ps_text_length(pageName(i)))+3;
}
- if(tabTotal>w-4){char hint[40];ps_terminal_format(hint,sizeof(hint)," %u/%u",unsigned(page_)+1,unsigned(PageCount));
+ if(!offset&&tabTotal>w-4){char hint[40];ps_terminal_format(hint,sizeof(hint)," %u/%u",unsigned(page_)+1,unsigned(PageCount));
const unsigned n=unsigned(ps_text_length(hint));if(used+n=95;
+ out.field("",w-4-used);if(shift)out.raw(" ");else edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");
+ if(PS_DEVICE_VIEW_MATRIX&&page_==Led&&grid_&&s.canLeds){matrix(out,s,w,shift);return true;}
+ const bool category=!PS_DEVICE_VIEW_PLAIN_ROWS&&groups_==CategoryColumn&&w>=95;
const unsigned groupWidth=category?16:0,labelWidth=w<59?12:w<95?24:28;
const unsigned valueWidth=w-7-labelWidth-(category?groupWidth+3:0);
- gridRule(out,s,w,groupWidth,labelWidth,"\xe2\x94\xac","\xe2\x94\x9c","\xe2\x94\xa4");
- auto cell=[&](const char *text,unsigned n){ink(out,0);out.field(text,n);edge(out,s," \xe2\x94\x82 "," | ");};
+ gridRule(out,s,w,groupWidth,labelWidth,"โฌ",shift?"โญ":"โ",shift?"โฎ":"โค");
+ auto cell=[&](const char *text,unsigned n){ink(out,theme_==Default?PS_INK_HEADING:0);out.field(text,n);edge(out,s," \xe2\x94\x82 "," | ");};
+ if(!PS_DEVICE_VIEW_PLAIN_ROWS){
edge(out,s,"\xe2\x94\x82 ","| ");if(category)cell("Category",groupWidth);cell("Setting",labelWidth);
- ink(out,0);out.field("Value",valueWidth);edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");
- gridRule(out,s,w,groupWidth,labelWidth,"\xe2\x94\xbc","\xe2\x94\x9c","\xe2\x94\xa4");
+ ink(out,theme_==Default?PS_INK_HEADING:0);out.field("Value",valueWidth);edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");
+ }
const unsigned count=rowCount(s);if(selected_>=count)selected_=count?uint8_t(count-1):0;
const unsigned diagnostics=page_==Led&&!PS_DEVICE_VIEW_MATRIX?(s.lines<24?5:9):page_==Game?3:page_==Pixel?2:0;
- unsigned visible=s.lines>11+diagnostics?s.lines-11-diagnostics:1;
- if(visible>PS_DEVICE_VIEW_MAX_ROWS)visible=PS_DEVICE_VIEW_MAX_ROWS;
- // Bound complete frames rather than silently dropping their final border.
- const unsigned budgetRows=(PS_DEVICE_VIEW_FRAME_BYTES > 8192 ? PS_DEVICE_VIEW_FRAME_BYTES-8192 : 1)/(w+1000U);
- if(visible>budgetRows)visible=budgetRows?budgetRows:1;
- // PCController's logical-row/section-line separation, without heap storage.
- uint8_t logical[PS_DEVICE_VIEW_MAX_ROWS*2];bool section[PS_DEVICE_VIEW_MAX_ROWS*2];
- unsigned total=0,selectedLine=0;
- for(unsigned i=0;i(total?total:1))visible=total?total:1;
if(paintedRows_!=visible){paintAll=true;paintedRows_=visible;}
const unsigned start=selectedLine>=visible?selectedLine-visible+1:0;
+ mouseStart_=start;mouseVisible_=visible;
+ if(!PS_DEVICE_VIEW_PLAIN_ROWS)gridRule(out,s,w,groupWidth,labelWidth,total&&(logical[start]&0xe000)?"\xe2\x94\xb4":"\xe2\x94\xbc","\xe2\x94\x9c","\xe2\x94\xa4");
for(unsigned i=0;i=total || !(logical[line+1]&0xe000)) && body+icategory);
+ for(unsigned j=0;label[j];++j)if(label[j]>='a'&&label[j]<='z')label[j]=char(label[j]-'a'+'A');
+ edge(out,s,"\xe2\x94\x82","|");
+ if(ansi_)out.raw(PS_STYLE_THEMES?ps_style_surface(theme_,trueColor_,selected?1:0):selected?"\x1b[7m":"\x1b[1m");
+ out.raw(" ");out.raw(!sectionsCollapsible_||sectionIndicators_==2?" ":s.unicode&§ionIndicators_==0?(sectionClosed(at)?"\xe2\x96\xb8 ":"\xe2\x96\xbe "):(sectionClosed(at)?"+ ":"- "));
+ out.field(label,w-6);out.raw(" ");edge(out,s,"\xe2\x94\x82","|");out.raw("\r\n");continue;
+ }
edge(out,s,"\xe2\x94\x82 ","| ");
- if(group){ink(out,6);out.raw(s.unicode?"\xe2\x94\x80 ":"- ");out.field(r->category,w-6);edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");continue;}
if(category)cell(r&&(i==0||!ps_text_equal(r->category,s.rows[at-1].category))?r->category:"",groupWidth);
if(selected&&ansi_)out.raw(ps_style_surface(theme_,trueColor_,2));
- ink(out,selected?1:4);out.raw(selected?(s.unicode?"\xe2\x96\xb8 ":"> "):" ");
- out.field(r?r->label:"",labelWidth-2);edge(out,s," \xe2\x94\x82 "," | ");
- if(selected&&editing_&&(!dialog_||!ansi_)){editValue(out,valueWidth);editRow_=6+i;editColumn_=labelWidth+6+(category?groupWidth+3:0);editWidth_=valueWidth;}
+ ink(out,selected&&theme_!=Default?1:4);
+ bool categories=false;
+ if(sections())for(unsigned j=0;jlabel:"",labelWidth-markerWidth);if(PS_DEVICE_VIEW_PLAIN_ROWS)out.raw(" ");else edge(out,s," \xe2\x94\x82 "," | ");
+ if(PS_DEVICE_VIEW_INLINE_EDIT&&selected&&editing_&&(!dialog_||!ansi_)){editValue(out,valueWidth);editRow_=body+i;editColumn_=offset+labelWidth+6+(category?groupWidth+3:0);editWidth_=valueWidth;}
else if(r)value(out,s,*r,valueWidth);else out.field("",valueWidth);
edge(out,s," \xe2\x94\x82"," |");out.raw("\r\n");
}
paintedSelection_=selected_;paintedGrid_=grid_;
- if(ansi_){char pos[24];ps_terminal_format(pos,sizeof(pos),"\x1b[%u;1H",6+visible);out.raw(pos);}
- gridRule(out,s,w,groupWidth,labelWidth,"\xe2\x94\xb4","\xe2\x95\xb0","\xe2\x95\xaf");
+ if(ansi_){
+ if(paintAll)blankRows(out,s,body+visible,s.lines-4-diagnostics,w,groupWidth,labelWidth);
+ if(gutter){base(out);for(unsigned i=0;ivisible){
+ const unsigned height=s.lines-9-shift-diagnostics+(PS_DEVICE_VIEW_PLAIN_ROWS?2:0);
+ const unsigned thumb=height*visible/total?height*visible/total:1;
+ const unsigned top=start*(height-thumb)/(total-visible);
+ base(out);int previous=-1;
+ for(unsigned i=0;i=top&&ipending?"Waiting for readback":r&&!r->editable?"Read only":"";
- out.safe(context,w-unsigned(ps_text_length(status))-2);out.raw("\r\n");footer(out,s,w);
- if(ansi_)out.raw("\x1b[J");
+ out.safe(context,width-1-unsigned(ps_text_length(status))-2);out.raw("\r\n");footer(out,s,width-1);
if(PS_DEVICE_VIEW_DIALOGS&&dialog_&&editing_&&ansi_){
- const unsigned dw=w<74?w-4:70,left=(w-dw)/2+1,top=s.lines/2-3;
+ const unsigned dw=w<74?w-4:70,left=offset+(w-dw)/2+1,top=s.lines/2-3;
auto position=[&](unsigned row){char b[24];ps_terminal_format(b,sizeof(b),"\x1b[%u;%uH",row,left);out.raw(b);};
position(top);rule(out,s,dw,"\xe2\x95\xad","\xe2\x95\xae",false);
position(top+1);edge(out,s,"\xe2\x94\x82 ","| ");ink(out,4);out.field(editLabel_,dw-4);edge(out,s," \xe2\x94\x82"," |");
@@ -282,6 +476,12 @@ template bool renderTable(const Snapshot &s,Write write) {
position(top+4);rule(out,s,dw,"\xe2\x95\xb0","\xe2\x95\xaf",false);
editRow_=top+2;editColumn_=left+2;editWidth_=dw-4;
}
+ if(editing_&&!PS_DEVICE_VIEW_INLINE_EDIT&&!dialog_){
+ char label[19];const unsigned n=unsigned(terminalText(label,sizeof label,editLabel_));
+ out.position(s.lines);base(out);out.raw("Edit ");out.raw(label);out.raw(": ");
+ editRow_=s.lines;editColumn_=8+n;editWidth_=width-editColumn_-1;
+ editValue(out,editWidth_);
+ }
if(editing_&&ansi_)editLine(write);
return true;
}
@@ -297,7 +497,7 @@ template void renderDiagnostics(Writer &out,const Snapshot &snapshot
if (ansi_) {
const uint8_t state =
snapshot.cellStates ? snapshot.cellStates[id - 1] : 255;
- out.raw(state == 0 ? "\x1b[37m" : state == 1 ? "\x1b[31m" : "\x1b[90m");
+ if(PS_STYLE_THEMES)out.raw(state == 0 ? "\x1b[37m" : state == 1 ? "\x1b[31m" : "\x1b[90m");
if (picked && grid_)
out.raw("\x1b[7m");
}
@@ -313,9 +513,6 @@ template void renderDiagnostics(Writer &out,const Snapshot &snapshot
if (ansi_) {
ink(out,0);
}
- if(!PS_DEVICE_VIEW_TABLE)out.raw(width<50?(snapshot.unicode?"\xe2\x86\x91/\xe2\x86\x93 \xe2\x86\x90/\xe2\x86\x92 w/r/o\r\n":"Up/Down Left/Right w/r/o\r\n"):snapshot.unicode ? "\xe2\x86\x91/\xe2\x86\x93 \xe2\x86\x90/\xe2\x86\x92 "
- "move w white r red o off\r\n"
- : "Up/Down Left/Right w white r red o off\r\n");
if (ansi_) {
base(out);
}
@@ -338,7 +535,6 @@ template void renderDiagnostics(Writer &out,const Snapshot &snapshot
ps_terminal_format(winner, sizeof(winner), "Winner %u %s\r\n", snapshot.winner,
snapshot.gameReady ? "Ready" : "AVR unavailable");
out.raw(winner);
- if(!PS_DEVICE_VIEW_TABLE)out.raw("1-9 press box | r reset to white\r\n");
} else if (page_ == Pixel) {
syncPixel(snapshot);
const uint8_t count = group_ ? snapshot.groupCounts[groupAt(snapshot, pixel_)] : 1;
@@ -354,9 +550,6 @@ template void renderDiagnostics(Writer &out,const Snapshot &snapshot
out.raw("\r\n");
if (ansi_)
ink(out,0);
- if(!PS_DEVICE_VIEW_TABLE)out.raw(width<50?(snapshot.unicode?"\xe2\x86\x90/\xe2\x86\x92 +/- g group w/r/o\r\n":"Left/Right +/- g group w/r/o\r\n"):snapshot.unicode
- ? "\xe2\x86\x90/\xe2\x86\x92 +/- step g group w/r/o color\r\n"
- : "Left/Right +/- step g group w/r/o color\r\n");
if (ansi_)
base(out);
}
diff --git a/firmware/esp/src/portable-shell/portable_device_view.hpp b/firmware/esp/src/portable-shell/portable_device_view.hpp
index bdb597e..078218e 100644
--- a/firmware/esp/src/portable-shell/portable_device_view.hpp
+++ b/firmware/esp/src/portable-shell/portable_device_view.hpp
@@ -1,7 +1,9 @@
+#include "portable_table_policy.h"
#ifndef PORTABLE_DEVICE_VIEW_HPP
#define PORTABLE_DEVICE_VIEW_HPP
#include "portable_shell_editor.hpp"
#include "portable_style.h"
+#include "portable_meter.h"
/* MIT Copyright (c) 2026 AtomicDeploy contributors. Canonical snapshot/action
* device view. Source lineage is in the donor inventory. Hardware calls and routing
* stay with the caller. Include only in products requiring this optional view. */
@@ -11,9 +13,17 @@
#ifndef PS_DEVICE_VIEW_INLINE_EDIT
#define PS_DEVICE_VIEW_INLINE_EDIT 1
#endif
+#ifndef PS_DEVICE_VIEW_PLAIN_ROWS
+#define PS_DEVICE_VIEW_PLAIN_ROWS 0
+#endif
#ifndef PS_DEVICE_VIEW_TABLE
#define PS_DEVICE_VIEW_TABLE 1
#endif
+/* Legacy TABLE=0 now selects uncached painting, never a second renderer.
+ * Set RETAIN_ROWS explicitly in new integrations. Other features are independent. */
+#ifndef PS_DEVICE_VIEW_RETAIN_ROWS
+#define PS_DEVICE_VIEW_RETAIN_ROWS PS_DEVICE_VIEW_TABLE
+#endif
#ifndef PS_DEVICE_VIEW_WIDGETS
#define PS_DEVICE_VIEW_WIDGETS 1
#endif
@@ -26,13 +36,19 @@
#ifndef PS_DEVICE_VIEW_CATEGORY_COLUMN
#define PS_DEVICE_VIEW_CATEGORY_COLUMN 0
#endif
+#ifndef PS_DEVICE_VIEW_SECTIONS
+#define PS_DEVICE_VIEW_SECTIONS 1
+#endif
+#ifndef PS_DEVICE_VIEW_VERTICAL_TABS
+#define PS_DEVICE_VIEW_VERTICAL_TABS 1
+#endif
#ifndef PS_DEVICE_VIEW_MAX_ROWS
#define PS_DEVICE_VIEW_MAX_ROWS 64
#endif
#ifndef PS_DEVICE_VIEW_FRAME_BYTES
-#define PS_DEVICE_VIEW_FRAME_BYTES 32768
+#define PS_DEVICE_VIEW_FRAME_BYTES 65535UL
#endif
-#if PS_DEVICE_VIEW_TABLE && PS_DEVICE_VIEW_FRAME_BYTES < 16384
+#if PS_DEVICE_VIEW_FRAME_BYTES < 16384
#error Rich tables require a frame budget of at least 16384 bytes
#endif
#if PS_DEVICE_VIEW_MAX_ROWS < 1 || PS_DEVICE_VIEW_MAX_ROWS > 255
@@ -45,7 +61,9 @@
namespace PortableShell {
class DeviceView {
public:
- enum Theme : uint8_t { Violet, Red, Lumen, OneDark };
+ enum Theme : uint8_t { Violet=PS_THEME_VIOLET, Red=PS_THEME_RED, Cyan=PS_THEME_CYAN, OneDark=PS_THEME_ONE_DARK, Default=PS_THEME_DEFAULT };
+ enum TabLayout : uint8_t { HorizontalTabs, VerticalTabs };
+ enum GroupLayout : uint8_t { SectionRows, CategoryColumn };
enum Kind : uint8_t { Text, Toggle, Level, Choice };
enum Page : uint8_t {
Device,
@@ -72,6 +90,7 @@ class DeviceView {
uint8_t choiceCount = 0;
bool pending = false;
bool meter = false; // Meaningful bounded levels/PWM, not ports or timestamps.
+ unsigned meterStyle = PS_METER_LINE;
const char *unit = "";
const char *const *choiceLabels = nullptr;
const char *onLabel = "On", *offLabel = "Off";
@@ -124,10 +143,12 @@ class DeviceView {
ansi_ = ansi;
page_ = Device;
selected_ = 0;
+ sectionFocus_ = false;
grid_ = false;
dirty_ = full_ = true;
lastHash_ = 0;
escape_ = escapeBytes_ = attribute_ = 0;
+ mouseDrag_=false;
crPending_ = false;
cell_ = 1;
pixel_ = 0;
@@ -138,6 +159,10 @@ class DeviceView {
bool active() const {
return active_;
}
+ template void end(Write write) {
+ if(ansi_&&PS_MOUSE){const char *p="\x1b[?1002l\x1b[?1006l";write((const uint8_t*)p,ps_text_length(p));}
+ active_=false;mouseDrag_=false;clearDraft();
+ }
bool editing() const {
return editing_;
}
@@ -169,8 +194,43 @@ class DeviceView {
dirty_ = full_ = true;
}
}
- void setTheme(Theme theme) { if (theme <= OneDark && theme_ != theme) {theme_=theme;dirty_=full_=true;} }
+ void setTheme(Theme theme) { if (theme <= Default && theme_ != theme) {theme_=theme;dirty_=full_=true;} }
Theme theme() const { return theme_; }
+ void setTabLayout(TabLayout layout) {
+ if (layout <= VerticalTabs && tabs_ != layout) { tabs_=layout;dirty_=full_=true; }
+ }
+ TabLayout tabLayout() const { return tabs_; }
+ void setGroupLayout(GroupLayout layout) {
+ if (!editing_ && layout <= CategoryColumn && groups_ != layout) {
+ groups_=layout;sectionFocus_=false;dirty_=full_=true;
+ }
+ }
+ GroupLayout groupLayout() const { return groups_; }
+ bool sectionSelected() const { return sectionFocus_; }
+ void setSectionBorders(bool enabled) {
+ if(!editing_ && sectionBorders_!=enabled){sectionBorders_=enabled;dirty_=full_=true;}
+ }
+ bool sectionBorders() const { return sectionBorders_; }
+ void setSectionBottomBorder(bool enabled) {
+ if(!editing_ && sectionBottomBorder_!=enabled){sectionBottomBorder_=enabled;dirty_=full_=true;}
+ }
+ bool sectionBottomBorder() const { return sectionBottomBorder_; }
+ void setTabsInsideTable(bool enabled) {
+ if(!editing_ && tabsInsideTable_!=enabled){tabsInsideTable_=enabled;dirty_=full_=true;}
+ }
+ bool tabsInsideTable() const { return tabsInsideTable_; }
+ void setFooterKeycaps(bool enabled){keycaps_=enabled;dirty_=full_=true;}
+ // Indicators: 0 chevrons (ASCII +/-), 1 +/-, 2 none.
+ void setSectionIndicators(unsigned style){if(!editing_&&style<3){sectionIndicators_=style;dirty_=full_=true;}}
+ void setSectionsCollapsible(bool enabled){if(!editing_){sectionsCollapsible_=enabled;dirty_=full_=true;}}
+ void setReserveScrollbar(bool enabled){reserveScrollbar_=enabled;dirty_=full_=true;}
+ void setCompactScrollbar(bool enabled){compactScrollbar_=enabled;dirty_=full_=true;}
+ // Caller retains the marker string for the lifetime of this setting.
+ void setSelectionMarker(const char *marker){selectionMarker_=marker;dirty_=full_=true;}
+ void setFooterGrid(bool enabled){footerGrid_=enabled;dirty_=full_=true;}
+ void setSynchronizedOutput(bool enabled){syncOutput_=enabled;}
+ void setFooterEmphasis(bool enabled){footerEmphasis_=enabled;dirty_=full_=true;}
+ void setTabBrackets(bool enabled){tabBrackets_=enabled;dirty_=full_=true;}
void setTrueColor(bool enabled) { if (trueColor_!=enabled) {trueColor_=enabled;dirty_=full_=true;} }
bool trueColor() const { return trueColor_; }
void feedback(const char *text) {
@@ -199,6 +259,7 @@ class DeviceView {
action = Action{};
if (!active_)
return false;
+ syncSections(snapshot);
if (crPending_ && (ch == '\n' || ch == 0)) {
crPending_ = false;
return false;
@@ -206,6 +267,16 @@ class DeviceView {
if (ch != '\n' && ch != '\r')
crPending_ = false;
if (escape_) {
+ if(escape_==4){
+ if(unsigned(escapeBytes_)+1>=sizeof(mousePacket_)){escape_=5;return false;}
+ mousePacket_[escapeBytes_++]=char(ch);mousePacket_[escapeBytes_]=0;
+ if(ch>=0x40&&ch<=0x7e){
+ ps_mouse_event event;escape_=0;
+ if(PS_MOUSE&&ps_mouse_parse(mousePacket_,&event))return mouse(event,snapshot,action);
+ }
+ return false;
+ }
+ if(escape_==5){if(ch>=0x40&&ch<=0x7e)escape_=0;return false;}
if (escape_ == 1) {
if (ch == '[' || ch == 'O') {
escape_ = 2;
@@ -216,6 +287,9 @@ class DeviceView {
return arrow(ch, snapshot, action);
return false;
}
+ if(PS_MOUSE&&escape_==2&&ch=='<'&&escapeBytes_==0){
+ escape_=4;escapeBytes_=1;mousePacket_[0]='<';return false;
+ }
if (++escapeBytes_ > 16) {
escape_ = escapeBytes_ = 0;
return false;
@@ -227,10 +301,10 @@ class DeviceView {
escape_ = escapeBytes_ = attribute_ = 0;
if (snapshot.lines < 16 || snapshot.columns < 40)
return false;
- if (PS_DEVICE_VIEW_DIALOGS && PS_DEVICE_VIEW_TABLE && ch == 'Q')
+ if (PS_DEVICE_VIEW_DIALOGS && ch == 'Q')
return startDialog(snapshot, action);
if (ch == '~') {
- if (PS_DEVICE_VIEW_DIALOGS && PS_DEVICE_VIEW_TABLE && attribute == 12)
+ if (PS_DEVICE_VIEW_DIALOGS && attribute == 12)
return startDialog(snapshot,action);
if (attribute == 1 || attribute == 7)
ch = 'H';
@@ -276,6 +350,9 @@ class DeviceView {
return false;
if (editing_)
return edit(ch, snapshot, action);
+ if (sectionFocus_ && sections() && !grid_ && (ch==' ' || ch=='\r' || ch=='\n')) {
+ toggleSection(snapshot,0);crPending_=ch=='\r';return false;
+ }
if (PS_DEVICE_VIEW_WIDGETS && !grid_ && (ch == ' ' || ch == '+' || ch == '-'))
return adjust(ch, snapshot, action);
if (ch == '\t') {
@@ -374,6 +451,7 @@ class DeviceView {
bool render(const Snapshot &snapshot, Write write, bool force = false) {
if (!active_)
return false;
+ syncSections(snapshot);
if (columns_ != snapshot.columns || lines_ != snapshot.lines) {
columns_ = snapshot.columns;
lines_ = snapshot.lines;
@@ -387,173 +465,31 @@ class DeviceView {
}
if (!dirty_ && !force)
return false;
+ /* Direct sink writes keep the closing marker outside the frame byte
+ budget, including early returns and partial inline-editor updates. */
+ struct Frame {
+ Write &sink;bool enabled;
+ Frame(Write &w,bool on):sink(w),enabled(on){if(enabled)sink((const uint8_t*)"\x1b[?2026h",8);}
+ ~Frame(){if(enabled)sink((const uint8_t*)"\x1b[?2026l",8);}
+ } frame(write,ansi_&&syncOutput_);
dirty_ = false;
+ if(PS_MOUSE&&ansi_&&full_){const char *p="\x1b[?1002h\x1b[?1006h";write((const uint8_t*)p,ps_text_length(p));}
if (force) full_=true;
if (editing_ && ansi_ && columns_ >= 40 && lines_ >= 16 && !full_ && !force) {
editLine(write);
return true;
}
- if (PS_DEVICE_VIEW_TABLE)
- return renderTable(snapshot, write);
- Writer out(write, ansi_);
- if (ansi_)
- out.raw(full_ ? "\x1b[H\x1b[2J\x1b[1;31m" : "\x1b[H\x1b[1;31m");
- full_ = false;
- const size_t width = snapshot.columns < 40 ? 40
- : snapshot.columns > 100 ? 100
- : snapshot.columns;
- // UTF-8 wire bytes are char-based under both C++17 and C++23.
- out.raw(snapshot.unicode ? "\xe2\x95\xad\xe2\x94\x80 " : "+- ");
- out.safe(snapshot.title, width - 10);
- char titleMeasure[101];
- const size_t titleSize = terminalText(titleMeasure, width - 9, snapshot.title);
- for (size_t k = titleSize + 4; k < width - 1; ++k)
- out.raw(snapshot.unicode ? "\xe2\x94\x80" : "-");
- out.raw(snapshot.unicode ? "\xe2\x95\xae" : "+");
- if (ansi_) {
- out.raw("\x1b[0m");
- }
- out.raw("\r\n");
- if (snapshot.lines < 16 || snapshot.columns < 40) {
- out.raw(snapshot.unicode?"Resize terminal to 40 \xc3\x97 16\r\n":"Resize terminal to 40 x 16\r\n");
- return true;
- }
- for (uint8_t i = 0; i < PageCount; ++i) {
- if ((i == Led && (!PS_DEVICE_VIEW_DIAGNOSTICS || !snapshot.canLeds)) ||
- (i == Pixel && !pixels(snapshot)))
- continue;
- if ((i == Game && (!PS_DEVICE_VIEW_DIAGNOSTICS || !snapshot.canGame)) ||
- (i == TerminalPage && !snapshot.terminalSettings))
- continue;
- if ((i == Cloud && !snapshot.cloudSettings) ||
- (i == Notifications && !snapshot.notificationSettings))
- continue;
- if (i == Clock && !snapshot.clockSettings)
- continue;
- if (width < 80 && i != page_)
- continue;
- if (ansi_)
- out.raw(i == page_ ? "\x1b[1;35m" : "\x1b[90m");
- out.raw(i == Device ? " Device "
- : i == Network ? " Network "
- : i == Led ? " LEDs "
- : i == Pixel ? " Pixels "
- : i == Game ? " Game "
- : i == TerminalPage ? " Term "
- : i == Cloud ? " Cloud "
- : i == Clock ? " Time "
- : " Notify ");
- if (ansi_)
- out.raw("\x1b[0m");
- }
- out.raw("\r\n");
- if (ansi_)
- out.raw("\x1b[31m");
- out.raw(snapshot.unicode ? "\xe2\x94\x9c" : "+");
- for (size_t i = 0; i < width - 3; ++i)
- out.raw(snapshot.unicode ? "\xe2\x94\x80" : "-");
- out.raw(snapshot.unicode ? "\xe2\x94\xa4" : "+");
- if (ansi_) {
- out.raw("\x1b[0m");
- }
- out.raw("\r\n");
- const bool allowed = page_ != Led && page_ != Pixel ? true
- : page_ == Led ? snapshot.canLeds
- : pixels(snapshot);
- if (!allowed) {
- out.raw("Unavailable\r\n");
- } else {
- const uint8_t count = rowCount(snapshot);
- if (selected_ >= count)
- selected_ = count ? count - 1 : 0;
- const uint8_t visible = page_ == Led ? (snapshot.lines < 24 ? 1 : 4)
- : snapshot.lines < 24 ? 4
- : 8;
- const uint8_t start = selected_ >= visible ? selected_ - visible + 1 : 0;
- for (uint8_t i = 0; i < visible; ++i) {
- const uint8_t at = start + i;
- if (ansi_)
- out.raw("\x1b[31m");
- out.raw(snapshot.unicode ? "\xe2\x94\x82 " : "| ");
- if (at < count) {
- const Row &row = snapshot.rows[at];
- const bool selected = at == selected_ && !grid_;
- if (ansi_) {
- out.raw(selected ? "\x1b[1;35m" : "\x1b[0m");
- }
- out.raw(selected ? "> " : " ");
- const size_t labelWidth = width < 60 ? 12 : 22;
- out.field(row.label, labelWidth);
- if (ansi_)
- out.raw("\x1b[90m");
- out.raw(row.editable ? " " : "- ");
- if (ansi_)
- out.raw(selected ? "\x1b[1;35m" : "\x1b[0m");
- if (PS_DEVICE_VIEW_INLINE_EDIT && editing_ && selected)
- editValue(out, width - labelWidth - 9);
- else
- out.field(row.secret ? "[hidden]" : row.value, width - labelWidth - 9);
- if (ansi_)
- out.raw("\x1b[0m");
- } else
- out.field("", width - 5);
- if (ansi_)
- out.raw("\x1b[31m");
- out.raw(snapshot.unicode ? " \xe2\x94\x82" : " |");
- if (ansi_)
- out.raw("\x1b[0m");
- out.raw("\r\n");
- }
- if (ansi_)
- out.raw("\x1b[31m");
- out.raw(snapshot.unicode ? "\xe2\x95\xb0" : "+");
- for (size_t i = 0; i < width - 3; ++i)
- out.raw(snapshot.unicode ? "\xe2\x94\x80" : "-");
- out.raw(snapshot.unicode ? "\xe2\x95\xaf" : "+");
- if (ansi_)
- out.raw("\x1b[0m");
- out.raw("\r\n");
- renderDiagnostics(out,snapshot,unsigned(width));
- }
- if (ansi_)
- out.raw("\x1b[90m");
- if (editing_)
- out.raw("Enter save Esc cancel\r\n");
- else if (width < 60)
- out.raw(snapshot.unicode ? "\xe2\x86\x91/\xe2\x86\x93 Tab Enter Esc\r\n"
- : "Up/Down Tab Enter Esc\r\n");
- else
- out.raw(snapshot.unicode ? "\xe2\x86\x91/\xe2\x86\x93 select Tab pages Enter edit Esc back\r\n"
- : "Up/Down select Tab pages Enter edit Esc back\r\n");
- if (ansi_)
- out.raw("\x1b[0m");
- out.safe(notice_, width - 1);
- out.raw("\r\n");
- if (PS_DEVICE_VIEW_INLINE_EDIT) {
- if (ansi_) {
- out.raw("\x1b[J");
- if (editing_)
- editLine(write);
- }
- return true;
- }
- if (ansi_) {
- out.raw("\x1b[J");
- char position[24];
- ps_terminal_format(position, sizeof(position), "\x1b[%u;1H\x1b[2K", editorRow());
- out.raw(position);
- }
- if (editing_)
- editText(out);
- else
- out.raw(" ");
- return true;
+ return renderTable(snapshot, write);
}
private:
template class Writer {
public:
explicit Writer(Write write, bool ansi = false) : write_(write), ansi_(ansi) {}
+ void indent(unsigned column) { indent_=column; }
+ void position(unsigned row, unsigned column=1) {
+ if(ansi_){char b[32];ps_terminal_format(b,sizeof(b),"\x1b[%u;%uH",row,column+indent_);raw(b);}
+ }
void raw(const char *text) {
while (*text) {
const char *end = ansi_ ? ps_text_find(text, "\r\n") : nullptr;
@@ -566,16 +502,17 @@ class DeviceView {
return;
write_(reinterpret_cast("\x1b[K\r\n"), 5);
remaining_ -= 5;
+ if(indent_){char b[24];ps_terminal_format(b,sizeof(b),"\x1b[%uG",indent_+1);raw(b);}
text = end + 2;
}
}
void safe(const char *text, size_t width) {
- char clean[241];
+ char clean[241];clean[0]=0;
terminalText(clean, width + 1 < sizeof(clean) ? width + 1 : sizeof(clean), text);
raw(clean);
}
void field(const char *text, size_t width) {
- char clean[241];
+ char clean[241];clean[0]=0;
const size_t n =
terminalText(clean, width + 1 < sizeof(clean) ? width + 1 : sizeof(clean), text);
raw(clean);
@@ -585,8 +522,10 @@ class DeviceView {
private:
Write write_;
- size_t remaining_ = PS_DEVICE_VIEW_TABLE ? PS_DEVICE_VIEW_FRAME_BYTES : 4096;
+ // This limits emitted bytes, not storage. Both layouts can fill a tall viewport.
+ size_t remaining_ = PS_DEVICE_VIEW_FRAME_BYTES;
bool ansi_;
+ unsigned indent_ = 0;
};
static void scrub(char *p, size_t n) {
volatile char *out = p;
@@ -630,7 +569,67 @@ class DeviceView {
return s.rows ? (s.rowCount > PS_DEVICE_VIEW_MAX_ROWS ? PS_DEVICE_VIEW_MAX_ROWS : s.rowCount) : 0;
}
const Row *currentRow(const Snapshot &s) const {
- return selected_ < rowCount(s) ? s.rows + selected_ : nullptr;
+ return !sectionFocus_ && selected_ < rowCount(s) ? s.rows + selected_ : nullptr;
+ }
+ bool sections() const { return PS_DEVICE_VIEW_SECTIONS && groups_==SectionRows; }
+ static bool sectionStart(const Snapshot &s,unsigned i) {
+ return s.rows[i].category && *s.rows[i].category &&
+ (!i || !ps_text_equal(s.rows[i].category,s.rows[i-1].category));
+ }
+ bool sectionClosed(unsigned i) const {
+ return sectionsCollapsible_ && sections() && (closed_[page_][i/8] & (1U<<(i%8)));
+ }
+ void syncSections(const Snapshot &s) {
+ if (!sections()) return;
+ uint32_t signature=2166136261U;
+ const unsigned count=rowCount(s);
+ for(unsigned i=0;i=count){selected_=count?uint8_t(count-1):0;sectionFocus_=false;}
+ if(count && sectionFocus_ && !sectionStart(s,selected_))sectionFocus_=false;
+ unsigned start=0;bool closed=false;
+ for(unsigned i=0;i=rowCount(s)||!sectionStart(s,selected_))return;
+ uint8_t &bits=closed_[page_][selected_/8];const unsigned mask=1U<<(selected_%8);
+ if(direction<0)bits|=uint8_t(mask);else if(direction>0)bits&=uint8_t(~mask);else bits^=uint8_t(mask);
+ notice_[0]=0;dirty_=full_=true;
}
static bool pixels(const Snapshot &s) {
if (!PS_DEVICE_VIEW_DIAGNOSTICS || !s.canPixels || !s.pixelCount || s.pixelCount > 480 ||
@@ -689,6 +688,7 @@ class DeviceView {
}
selected_ = 0;
grid_ = page_ == Led || page_ == Pixel || page_ == Game;
+ sectionFocus_ = false;
dirty_ = full_ = true;
notice_[0] = 0;
}
@@ -740,7 +740,7 @@ class DeviceView {
return false;
}
if (grid_ && page_ == Led && s.canLeds) {
- if(PS_DEVICE_VIEW_TABLE&&PS_DEVICE_VIEW_MATRIX&&s.cellStaggered&&(key=='A'||key=='B'||key=='C'||key=='D')){
+ if(PS_DEVICE_VIEW_MATRIX&&s.cellStaggered&&(key=='A'||key=='B'||key=='C'||key=='D')){
const unsigned n=cell_-1,x=2*(n%10)+(n/10)%2,y=n/20;
const int nx=int(x)+(key=='C'?1:key=='D'?-1:0),ny=int(y)+(key=='B'?1:key=='A'?-1:0);
if(nx>=0&&nx<20&&ny>=0&&ny<4)cell_=uint8_t(ny*20+(nx%2)*10+nx/2+1);
@@ -790,6 +790,23 @@ class DeviceView {
return pixel_ != old && pixelAction(s, a);
}
const uint8_t count = rowCount(s);
+ if(sections() && !grid_){
+ if(sectionFocus_ && (key=='C'||key=='D')){toggleSection(s,key=='C'?1:-1);return false;}
+ if(key=='A'||key=='B'||key=='H'||key=='F'||key=='P'||key=='N'){
+ uint16_t items[PS_DEVICE_VIEW_MAX_ROWS*2];unsigned focus;
+ const unsigned total=sectionItems(s,items,focus);
+ if(total){
+ if(key=='A')focus=focus?focus-1:0;
+ else if(key=='B')focus=focus+14?focus-4:0;
+ else focus=focus+4kind==Level||row->kind==Choice))
@@ -885,11 +902,7 @@ class DeviceView {
dirty_ = true;
return false;
}
- size_t editCapacity() const {
- if (PS_DEVICE_VIEW_TABLE) return editWidth_;
- const size_t width = columns_ < 40 ? 40 : columns_ > 100 ? 100 : columns_;
- return width - (width < 60 ? 12 : 22) - 9;
- }
+ size_t editCapacity() const { return editWidth_; }
size_t editStart(size_t capacity) const {
return cursor_ >= capacity ? cursor_ - capacity + 1 : 0;
}
@@ -899,70 +912,18 @@ class DeviceView {
for (size_t i = 0; i < capacity && start + i < length; ++i)
visible[i] = secret_ ? '*' : draft_[start + i];
if (ansi_)
- out.raw(PS_DEVICE_VIEW_TABLE ? ps_style_surface(theme_,trueColor_,1) : "\x1b[7m");
+ out.raw(ps_style_surface(theme_,trueColor_,1));
out.field(visible, capacity);
if (ansi_)
- out.raw(PS_DEVICE_VIEW_TABLE ? ps_style_base(theme_,trueColor_) : "\x1b[0m");
- }
- template void editText(Writer &out) const {
- char label[19];
- terminalText(label, sizeof(label), editLabel_);
- out.raw("Edit ");
- out.raw(label);
- out.raw(": ");
- const size_t width = columns_ < 40 ? 40 : columns_ > 100 ? 100 : columns_;
- const size_t capacity = width - ps_text_length(label) - 9;
- const size_t length = ps_text_length(draft_),
- start = cursor_ >= capacity ? cursor_ - capacity + 1 : 0;
- char visible[101]{};
- for (size_t i = 0; i < capacity && start + i < length; ++i)
- visible[i] = secret_ ? '*' : draft_[start + i];
- out.raw(visible);
- if (ansi_) {
- char cursor[24];
- ps_terminal_format(cursor, sizeof(cursor), "\x1b[%u;%uH", editorRow(),
- static_cast(8 + ps_text_length(label) + cursor_ - start));
- out.raw(cursor);
- }
- }
- unsigned editorRow() const {
- if (PS_DEVICE_VIEW_TABLE) return editRow_;
- if (PS_DEVICE_VIEW_INLINE_EDIT) {
- const unsigned visible = page_ == Led ? (lines_ < 24 ? 1 : 4)
- : lines_ < 24 ? 4 : 8;
- return 4 + (selected_ < visible ? selected_ : visible - 1);
- }
- return lines_ > 24 ? 23 : lines_ - 1;
+ out.raw(ps_style_base(theme_,trueColor_));
}
template void editLine(Write write) const {
- Writer out(write, ansi_);
- if (PS_DEVICE_VIEW_TABLE) {
- char position[24];
- ps_terminal_format(position,sizeof(position),"\x1b[%u;%uH",editRow_,editColumn_);
- out.raw(position);editValue(out,editWidth_);
- ps_terminal_format(position,sizeof(position),"\x1b[%u;%uH",editRow_,editColumn_+unsigned(cursor_-editStart(editWidth_)));
- out.raw(position);out.raw("\x1b[?25h");return;
- }
- if (PS_DEVICE_VIEW_INLINE_EDIT) {
- const unsigned column = (columns_ < 60 ? 12 : 22) + 7;
- char position[24];
- ps_terminal_format(position, sizeof(position), "\x1b[%u;%uH", editorRow(), column);
- out.raw(position);
- const size_t capacity = editCapacity();
- editValue(out, capacity);
- ps_terminal_format(position, sizeof(position), "\x1b[%u;%uH", editorRow(),
- column + static_cast(cursor_ - editStart(capacity)));
- out.raw(position);
- out.raw("\x1b[?25h");
- return;
- }
- if (ansi_) {
- char position[24];
- ps_terminal_format(position, sizeof(position), "\x1b[%u;1H\x1b[2K", editorRow());
- out.raw(position);
- } else
- out.raw("\r");
- editText(out);
+ Writer out(write,ansi_);
+ char position[24];
+ ps_terminal_format(position,sizeof(position),"\x1b[%u;%uH",editRow_,editColumn_);
+ out.raw(position);editValue(out,editWidth_);
+ ps_terminal_format(position,sizeof(position),"\x1b[%u;%uH",editRow_,editColumn_+unsigned(cursor_-editStart(editWidth_)));
+ out.raw(position);out.raw("\x1b[?25h");
}
static uint32_t hash(const Snapshot &s) {
uint32_t value = 2166136261U;
@@ -1010,6 +971,7 @@ class DeviceView {
value ^= (s.rows[i].editable ? 4 : 0) | (s.rows[i].secret ? 8 : 0);
value ^= (unsigned(s.rows[i].kind)<<8)|(s.rows[i].pending?16:0);
value ^= s.rows[i].meter?32:0;
+ value ^= s.rows[i].meterStyle;value *= 16777619U;
value *= 16777619U;
value ^= s.rows[i].minimum;value *= 16777619U;
value ^= s.rows[i].maximum;value *= 16777619U;
@@ -1025,11 +987,26 @@ class DeviceView {
}
Page page_ = Device;
#include "portable_device_table.hpp"
- Theme theme_ = Violet;
+ Theme theme_ = Default;
+ TabLayout tabs_ = HorizontalTabs;
+ GroupLayout groups_ = PS_DEVICE_VIEW_CATEGORY_COLUMN ? CategoryColumn : SectionRows;
+ bool sectionFocus_ = false;
+ bool sectionBorders_ = true;
+ bool sectionBottomBorder_ = false;
+ bool tabsInsideTable_ = false;
+ uint8_t closed_[PS_DEVICE_VIEW_SECTIONS?PageCount:1][PS_DEVICE_VIEW_SECTIONS?(PS_DEVICE_VIEW_MAX_ROWS+7)/8:1]{};
+ uint32_t sectionSchema_[PS_DEVICE_VIEW_SECTIONS?PageCount:1]{};
bool trueColor_ = true, dialog_ = false;
+ bool keycaps_=PS_FOOTER_KEYCAPS!=0,tabBrackets_=PS_TAB_BRACKETS!=0;
+ bool compactScrollbar_=false;
+ const char *selectionMarker_=nullptr;
+ bool sectionsCollapsible_=true,reserveScrollbar_=false;
+ unsigned sectionIndicators_=0,paintedGutter_=0;
+ bool footerGrid_=PS_FOOTER_GRID!=0,footerEmphasis_=false;
+ bool syncOutput_=PS_SYNC_OUTPUT!=0;
unsigned editRow_ = 6, editColumn_ = 30, editWidth_ = 40;
uint32_t observed_ = 0;
- uint32_t painted_[PS_DEVICE_VIEW_TABLE ? PS_DEVICE_VIEW_MAX_ROWS : 1]{};
+ uint32_t painted_[PS_DEVICE_VIEW_RETAIN_ROWS ? PS_DEVICE_VIEW_MAX_ROWS : 1]{};
unsigned paintedSelection_ = 255;
unsigned paintedRows_ = 0;
bool paintedGrid_ = false;
@@ -1039,6 +1016,9 @@ class DeviceView {
bool active_ = false, ansi_ = true, dirty_ = true, full_ = true, editing_ = false,
secret_ = false, draftChanged_ = false, grid_ = false, group_ = false, crPending_ = false;
uint8_t escape_ = 0, escapeBytes_ = 0, attribute_ = 0;
+ char mousePacket_[PS_MOUSE?24:1]{};
+ bool mouseDrag_=false;
+ unsigned mouseStart_=0,mouseVisible_=0;
uint32_t lastHash_ = 0;
char draft_[385]{}, editKey_[32]{}, editLabel_[49]{}, notice_[65]{};
};
diff --git a/firmware/esp/src/portable-shell/portable_editor.h b/firmware/esp/src/portable-shell/portable_editor.h
index c52ad06..f438bd6 100644
--- a/firmware/esp/src/portable-shell/portable_editor.h
+++ b/firmware/esp/src/portable-shell/portable_editor.h
@@ -1,6 +1,10 @@
#ifndef PORTABLE_EDITOR_H
#define PORTABLE_EDITOR_H
#include "portable_terminal.h"
+#include "portable_undo.h"
+#ifndef PS_EDITOR_UNDO_DEPTH
+#define PS_EDITOR_UNDO_DEPTH 2
+#endif
#include "portable_style.h"
/* Bounded event editor profile; source lineage is in the donor inventory.
* No command execution, allocation or application state. Hidden by default.
@@ -13,13 +17,13 @@
#define PS_EDITOR_HISTORY 8
#endif
#ifndef PS_EDITOR_PROMPT_BYTES
-#define PS_EDITOR_PROMPT_BYTES 49
+#define PS_EDITOR_PROMPT_BYTES 64
#endif
#ifndef PS_EDITOR_SUGGEST_BYTES
#define PS_EDITOR_SUGGEST_BYTES 80
#endif
#ifndef PS_EDITOR_PROMPT_STYLE
-#define PS_EDITOR_PROMPT_STYLE "\x1b[1;35m"
+#define PS_EDITOR_PROMPT_STYLE "\x1b[36m"
#endif
#if PS_EDITOR_PROMPT_BYTES < 6 || PS_EDITOR_SUGGEST_BYTES < 2
#error "Prompt needs >=6 bytes and suggestion storage >=2"
@@ -37,11 +41,17 @@ enum {
};
typedef const char *(*ps_editor_candidate)(void *, size_t);
typedef struct {
+#if PS_EDITOR_UNDO_DEPTH
+ ps_undo undo;
+ char undo_frames[PS_EDITOR_UNDO_DEPTH+1][PS_EDITOR_CAPACITY+1];
+ unsigned undo_positions[PS_EDITOR_UNDO_DEPTH+1];
+#endif
+ unsigned paste,paste_match,csi_number;
char command[PS_EDITOR_CAPACITY + 1], history[PS_EDITOR_HISTORY][PS_EDITOR_CAPACITY + 1];
char draft[PS_EDITOR_CAPACITY + 1], prompt[PS_EDITOR_PROMPT_BYTES], suggestion[PS_EDITOR_SUGGEST_BYTES];
size_t next, count, offset, draft_cursor, cursor;
unsigned char escape, attribute, escape_bytes, echo, cr_pending, overflow, ansi, upper;
- unsigned char theme, true_color;
+ unsigned char theme, true_color, suggestion_space;
} ps_editor;
static inline void ps_term_emit(ps_terminal_write write, void *ctx, const char *text) {
write(ctx, (const unsigned char *)text, ps_text_length(text));
@@ -65,12 +75,22 @@ static inline void ps_term_left(ps_terminal_write write, void *ctx, size_t n, in
}
static inline void ps_editor_init(ps_editor *s) {
ps_secure_clear(s, sizeof(*s));
+#if PS_EDITOR_UNDO_DEPTH
+ ps_undo_bind(&s->undo,&s->undo_frames[0][0],s->undo_positions,PS_EDITOR_CAPACITY+1,PS_EDITOR_UNDO_DEPTH+1);
+#endif
s->ansi = 1;
s->theme = 255;
s->true_color = 1;
ps_bytes_copy(s->prompt, "shell", 6);
}
static inline void ps_editor_clear(ps_editor *s) {
+#if PS_EDITOR_UNDO_DEPTH
+ s->undo.frames=&s->undo_frames[0][0];s->undo.positions=s->undo_positions;
+#endif
+#if PS_EDITOR_UNDO_DEPTH
+ ps_undo_clear(&s->undo);
+#endif
+ s->paste=s->paste_match=s->csi_number=0;
ps_secure_clear(s->command, sizeof(s->command));
ps_secure_clear(s->draft, sizeof(s->draft));
ps_secure_clear(s->suggestion, sizeof(s->suggestion));
@@ -80,6 +100,7 @@ static inline void ps_editor_clear(ps_editor *s) {
static inline void ps_editor_clear_history(ps_editor *s) {
ps_secure_clear(s->history, sizeof(s->history));
ps_secure_clear(s->draft, sizeof(s->draft));
+ ps_secure_clear(s->suggestion, sizeof(s->suggestion));
s->next = s->count = s->offset = s->draft_cursor = 0;
}
static inline void ps_editor_reset(ps_editor *s) {
@@ -91,7 +112,7 @@ static inline void ps_editor_prompt(ps_editor *s, ps_terminal_write write, void
if (!s->echo)
return;
if (s->ansi)
- ps_term_emit(write, ctx, s->theme <= 3 ? ps_style_ink(s->theme, s->true_color, 0, 1) : PS_EDITOR_PROMPT_STYLE);
+ ps_term_emit(write, ctx, s->theme <= PS_THEME_DEFAULT ? ps_style_prompt(s->theme, s->true_color) : PS_EDITOR_PROMPT_STYLE);
ps_term_emit(write, ctx, s->prompt);
ps_term_emit(write, ctx, "> ");
if (s->ansi)
@@ -139,6 +160,16 @@ static inline void ps_editor_erase_suggestion(ps_editor *s, ps_terminal_write wr
ps_term_emit(write, ctx, "\x1b[K");
ps_secure_clear(s->suggestion, sizeof(s->suggestion));
}
+/* Application-owned identity changed: redraw without losing an edited draft,
+ * cursor or history. Call only for a visible prompt, not over an active TUI.
+ * Network identity validation/persistence remains the application's job. */
+static inline void ps_editor_refresh_prompt(ps_editor *s, const char *label, int ansi,
+ ps_terminal_write write, void *ctx) {
+ ps_terminal_text(s->prompt, sizeof(s->prompt), label, 128);
+ s->ansi = ansi != 0;
+ if (s->echo && !s->ansi) ps_term_emit(write, ctx, "\r\n");
+ ps_editor_redraw(s, write, ctx);
+}
static inline int ps_editor_remember(ps_editor *s, int allowed) {
size_t latest = (s->next + PS_EDITOR_HISTORY - 1) % PS_EDITOR_HISTORY;
if (!allowed || !s->echo || s->overflow || !ps_history_safe(s->command, sizeof(s->command)))
@@ -217,10 +248,10 @@ static inline void ps_editor_move(ps_editor *s, unsigned direction, ps_terminal_
size = ps_text_length(s->suggestion);
ps_bytes_copy(s->command + s->cursor, s->suggestion, size);
s->cursor += size;
- s->command[s->cursor++] = ' ';
+ if (s->suggestion_space) s->command[s->cursor++] = ' ';
s->command[s->cursor] = 0;
ps_editor_emit_input(s, write, ctx, s->suggestion);
- ps_term_emit(write, ctx, " ");
+ if (s->suggestion_space) ps_term_emit(write, ctx, " ");
ps_secure_clear(s->suggestion, sizeof(s->suggestion));
s->offset = 0;
return;
@@ -272,6 +303,7 @@ static inline void ps_editor_escape(ps_editor *s, unsigned ch, ps_terminal_write
return;
}
if ((s->escape == 2 || s->escape == 3) && ((ch >= '0' && ch <= '9') || ch == ';')) {
+ if(ch>='0'&&ch<='9'&&s->csi_number<1000)s->csi_number=s->csi_number*10+ch-'0';
if (!s->attribute)
s->attribute = (unsigned char)ch;
s->escape = 3;
@@ -279,7 +311,8 @@ static inline void ps_editor_escape(ps_editor *s, unsigned ch, ps_terminal_write
}
if (s->escape == 3) {
if (ch == '~') {
- if (s->attribute == '3') {
+ if(s->csi_number==200){s->paste=1;s->paste_match=0;}
+ else if (s->attribute == '3') {
ps_editor_erase_suggestion(s, write, ctx);
if (s->cursor < ps_text_length(s->command)) {
ps_editor_shift_left(s);
@@ -299,7 +332,7 @@ static inline void ps_editor_escape(ps_editor *s, unsigned ch, ps_terminal_write
ps_editor_move(s, ch, write, ctx);
s->escape = s->attribute = s->escape_bytes = 0;
}
-static inline int ps_editor_feed(ps_editor *s, unsigned char ch, ps_terminal_write write,
+static inline int ps_editor_feed_inner(ps_editor *s, unsigned char ch, ps_terminal_write write,
void *ctx) {
size_t target, i;
if (!s->escape && ch != 27)
@@ -326,6 +359,7 @@ static inline int ps_editor_feed(ps_editor *s, unsigned char ch, ps_terminal_wri
return PS_EDIT_NONE;
}
if (ch == 27) {
+ s->csi_number=0;
s->escape = 1;
return PS_EDIT_NONE;
}
@@ -403,6 +437,48 @@ static inline int ps_editor_feed(ps_editor *s, unsigned char ch, ps_terminal_wri
}
return PS_EDIT_NONE;
}
+/* Bracketed paste never executes controls or submits embedded newlines. */
+static inline int ps_editor_feed(ps_editor *s,unsigned char ch,ps_terminal_write write,void *ctx){
+ int result;
+#if PS_EDITOR_UNDO_DEPTH
+ unsigned pasted=s->paste;
+#endif
+#if PS_EDITOR_UNDO_DEPTH
+ s->undo.frames=&s->undo_frames[0][0];s->undo.positions=s->undo_positions;
+#endif
+ if(s->paste){
+ static const char end[]="\033[201~";
+ if(ch==(unsigned char)end[s->paste_match]){
+ if(++s->paste_match==sizeof(end)-1){
+ s->paste=s->paste_match=0;
+#if PS_EDITOR_UNDO_DEPTH
+ ps_undo_record(&s->undo,s->command,(unsigned)s->cursor);
+#endif
+ }return PS_EDIT_NONE;
+ }
+ s->paste_match=0;
+ if(ch==27){s->paste_match=1;return PS_EDIT_NONE;}
+ if(ch=='\r'||ch=='\n'||ch=='\t')ch=' ';
+ else if(ch<32||ch==127)return PS_EDIT_NONE;
+ }
+#if PS_EDITOR_UNDO_DEPTH
+ if((!s->escape&&ch==31)||(s->escape==1&&ch=='_')){
+ unsigned cursor;
+ ps_undo_record(&s->undo,s->command,(unsigned)s->cursor);
+ int redo=s->escape!=0;s->escape=0;
+ if(ps_undo_step(&s->undo,redo,s->command,&cursor)){s->cursor=cursor;s->overflow=0;ps_editor_redraw(s,write,ctx);}
+ else ps_editor_bell(s,write,ctx);
+ return PS_EDIT_NONE;
+ }
+ if(!pasted)ps_undo_record(&s->undo,s->command,(unsigned)s->cursor);
+#endif
+ result=ps_editor_feed_inner(s,ch,write,ctx);
+#if PS_EDITOR_UNDO_DEPTH
+ if(result==PS_EDIT_SUBMIT||result==PS_EDIT_TOO_LONG||result==PS_EDIT_CANCEL)ps_undo_clear(&s->undo);
+ else if(!pasted)ps_undo_record(&s->undo,s->command,(unsigned)s->cursor);
+#endif
+ return result;
+}
static inline void ps_editor_complete(ps_editor *s, size_t start, size_t count,
ps_editor_candidate candidate, void *cctx,
ps_terminal_write write, void *ctx) {
@@ -459,30 +535,44 @@ static inline void ps_editor_complete(ps_editor *s, size_t start, size_t count,
ps_term_emit(write, ctx, "\r\n");
ps_editor_redraw(s, write, ctx);
}
+static inline int ps_editor_offer(ps_editor *s, const char *suffix, int space,
+ ps_terminal_write write, void *ctx) {
+ size_t size = ps_text_length(suffix);
+ if (!size || size >= sizeof(s->suggestion) ||
+ s->cursor + size + (space != 0) > PS_EDITOR_CAPACITY)
+ return 0;
+ ps_bytes_copy(s->suggestion, suffix, size + 1);
+ s->suggestion_space = space != 0;
+ ps_term_emit(write, ctx, "\x1b[90m");
+ ps_editor_emit_input(s, write, ctx, s->suggestion);
+ ps_term_emit(write, ctx, "\x1b[0m");
+ ps_editor_left(s, write, ctx, size);
+ return 1;
+}
static inline void ps_editor_suggest(ps_editor *s, size_t start, size_t count,
ps_editor_candidate candidate, void *cctx,
ps_terminal_write write, void *ctx) {
- size_t i, typed, size;
+ size_t i, j, typed;
const char *name;
if (!s->echo || !s->ansi || s->overflow || s->escape ||
s->cursor != ps_text_length(s->command) || start >= s->cursor)
return;
ps_editor_erase_suggestion(s, write, ctx);
+ /* Retained history has already passed application admission and secret
+ * filtering. Prefer the newest exact prefix; preserve its raw tail. */
+ for (i = 0; i < s->count; ++i) {
+ name = s->history[(s->next + PS_EDITOR_HISTORY - 1 - i) % PS_EDITOR_HISTORY];
+ for (j = 0; j < s->cursor && name[j] == s->command[j]; ++j) {}
+ if (j == s->cursor && name[j] && ps_editor_offer(s, name + j, 0, write, ctx))
+ return;
+ }
typed = s->cursor - start;
for (i = 0; i < count; ++i) {
name = candidate(cctx, i);
if (!name || !ps_text_prefix(name, s->command + start, typed) ||
ps_text_length(name) <= typed)
continue;
- size = ps_text_length(name) - typed;
- if (size >= sizeof(s->suggestion) || s->cursor + size + 1 > PS_EDITOR_CAPACITY)
- return;
- ps_bytes_copy(s->suggestion, name + typed, size + 1);
- ps_term_emit(write, ctx, "\x1b[90m");
- ps_editor_emit_input(s, write, ctx, s->suggestion);
- ps_term_emit(write, ctx, "\x1b[0m");
- ps_editor_left(s, write, ctx, size);
- return;
+ if (ps_editor_offer(s, name + typed, 1, write, ctx)) return;
}
}
#endif
diff --git a/firmware/esp/src/portable-shell/portable_meter.h b/firmware/esp/src/portable-shell/portable_meter.h
new file mode 100644
index 0000000..277ec68
--- /dev/null
+++ b/firmware/esp/src/portable-shell/portable_meter.h
@@ -0,0 +1,166 @@
+#ifndef PORTABLE_METER_H
+#define PORTABLE_METER_H
+/* MIT. Byte-only C99/C++11 meter: caller owns state, transport and readback.
+ * No allocation or platform dependencies. Text pointers are borrowed.
+ * Rendering starts at column one; each emitted row ends with CRLF.
+ * Hosts route keys/clicks to the focused control. Requests never change readback. */
+#include "portable_terminal.h"
+#include
+enum { PS_METER_LINE, PS_METER_BLOCKS, PS_METER_TICKS, PS_METER_CHEVRONS };
+/* OR into style: opt-in paired markers for adjustable, centered block labels. */
+enum { PS_METER_ADJUST_MARKERS = 256, PS_METER_STYLE_MASK = 255 };
+enum { PS_METER_TEXT_ABOVE, PS_METER_TEXT_INSIDE, PS_METER_TEXT_NONE };
+enum { PS_METER_DECREASE, PS_METER_INCREASE, PS_METER_MINIMUM, PS_METER_MAXIMUM };
+typedef struct {
+ uint32_t minimum, maximum, observed, requested, step;
+ unsigned adjustable, show_requested;
+} ps_meter;
+typedef struct {
+ unsigned width, style, text, panel, unicode, color;
+ const char *title, *subtitle;
+} ps_meter_style;
+/* Return nonzero to accept a request. NULL permits a local draft. Callback must
+ * not reenter this control. Application subsequently supplies observed readback. */
+typedef int (*ps_meter_request_fn)(void *, uint32_t);
+static inline uint32_t ps_meter_clamp(const ps_meter *m, uint32_t v) {
+ return vminimum?m->minimum:v>m->maximum?m->maximum:v;
+}
+/* Exact floor(value*cells/span), without wide multiply/divide or overflow. */
+static inline unsigned ps_meter_fraction(uint32_t value, uint32_t span, unsigned cells) {
+ uint32_t rem=0; unsigned result=0,i;
+ if (!span) return 0;
+ if (value>=span) return cells;
+ for(i=0;i=span-value){rem-=span-value;++result;}else rem+=value;
+ }
+ return result;
+}
+static inline int ps_meter_request(ps_meter *m, uint32_t value, ps_meter_request_fn fn, void *ctx) {
+ if(!m || !m->adjustable || m->maximum<=m->minimum) return 0;
+ value=ps_meter_clamp(m,value);
+ if(value==m->requested || (fn&&!fn(ctx,value))) return 0;
+ m->requested=value; return 1;
+}
+static inline int ps_meter_adjust(ps_meter *m, unsigned action, ps_meter_request_fn fn, void *ctx) {
+ uint32_t v,step;
+ if(!m || m->maximum<=m->minimum) return 0;
+ v=ps_meter_clamp(m,m->requested);step=m->step?m->step:1;
+ switch(action){
+ case PS_METER_DECREASE:v=step>v-m->minimum?m->minimum:v-step;break;
+ case PS_METER_INCREASE:v=step>m->maximum-v?m->maximum:v+step;break;
+ case PS_METER_MINIMUM:v=m->minimum;break;
+ case PS_METER_MAXIMUM:v=m->maximum;break;
+ default:return 0;
+ }
+ return ps_meter_request(m,v,fn,ctx);
+}
+/* Mouse/drag adapter: zero-based position in a track with 2..240 cells. */
+static inline int ps_meter_position(ps_meter *m,unsigned position,unsigned cells,ps_meter_request_fn fn,void *ctx){
+ uint32_t span,v;unsigned divisions;
+ if(!m||m->maximum<=m->minimum||cells<2||cells>240)return 0;
+ divisions=cells-1;if(position>divisions)position=divisions;
+ span=m->maximum-m->minimum;
+ v=(span/divisions)*position+(span%divisions)*position/divisions;
+ return ps_meter_request(m,m->minimum+v,fn,ctx);
+}
+static inline const char *ps_meter_cell(unsigned style,unsigned filled,unsigned unicode){
+ style &= PS_METER_STYLE_MASK;
+ if(style==PS_METER_TICKS)return unicode?(filled?"โ":"โ"):(filled?"|":":");
+ return unicode?(style==PS_METER_BLOCKS?(filled?"โ":"โ"):(filled?"โ":"โ")):(filled?(style==PS_METER_BLOCKS?"#":"="):".");
+}
+/* Spaced segments share their geometry between standalone and table renderers. */
+static inline const char *ps_meter_track_cell(unsigned style,unsigned filled,unsigned unicode,unsigned index){
+ return (style&PS_METER_STYLE_MASK)==PS_METER_TICKS&&(index&1)?" ":ps_meter_cell(style,filled,unicode);
+}
+/* A fixed-cell-width value field, including chevrons. Input is sanitized ASCII;
+ * generated delimiters use Unicode. Capacity includes UTF-8 expansion and NUL. */
+static inline int ps_meter_value_field(char *dst,unsigned capacity,unsigned width,const char *value,unsigned unicode){
+ char clean[241];unsigned n=0,i,k=0,left,edge=unicode?1:2,available;
+ const char *open=unicode?"ยซ":"<<",*close=unicode?"ยป":">>";
+ if(!dst||!value||width<6||width>240||capacityunicode?(bottom?"โฐ":"โญ"):"+");
+ for(i=0;iunicode?"โ":"-");
+ ps_meter_emit(write,ctx,s->unicode?(bottom?"โฏ\r\n":"โฎ\r\n"):"+\r\n");
+}
+/* Returns emitted row count; invalid range/width emits nothing. Labels are
+ * control-stripped ASCII (portable_terminal_text policy), clipped and padded.
+ * Generated borders/tracks use Unicode when enabled. Color never conveys state
+ * alone: observed and target values remain explicitly labeled. */
+static inline unsigned ps_meter_render(const ps_meter *m,const ps_meter_style *s,ps_terminal_write write,void *ctx){
+ char value[96],labels[3][241];unsigned width,filled,rows=0,i,j,count=0,n,start,kind,markers=0;
+ uint32_t observed;
+ if(!m||!s||!write||m->maximum<=m->minimum||s->width<8||s->width>240)return 0;
+ width=s->width-(s->panel?2:0);observed=ps_meter_clamp(m,m->observed);
+ kind=s->style&PS_METER_STYLE_MASK;
+ filled=ps_meter_fraction(observed-m->minimum,m->maximum-m->minimum,width);
+ if(m->show_requested)ps_terminal_format(value,sizeof value,"%u%% Value %u Target %u",ps_meter_fraction(observed-m->minimum,m->maximum-m->minimum,100),(unsigned)observed,(unsigned)ps_meter_clamp(m,m->requested));
+ else ps_terminal_format(value,sizeof value,"%u%% %u / %u",ps_meter_fraction(observed-m->minimum,m->maximum-m->minimum,100),(unsigned)observed,(unsigned)m->maximum);
+ if(s->title&&*s->title)ps_terminal_text(labels[count++],sizeof labels[0],s->title,1024);
+ if(s->subtitle&&*s->subtitle)ps_terminal_text(labels[count++],sizeof labels[0],s->subtitle,1024);
+ if(s->text==PS_METER_TEXT_ABOVE)ps_terminal_text(labels[count++],sizeof labels[0],value,sizeof value);
+ if(s->color)ps_meter_emit(write,ctx,"\033[0;37m");
+ if(s->panel){ps_meter_edge(write,ctx,s,width,0);++rows;}
+ for(i=0;ipanel)ps_meter_emit(write,ctx,s->unicode?"โ":"|");
+ n=0;while(labels[i][n]&&npanel)ps_meter_emit(write,ctx,s->unicode?"โ":"|");
+ ps_meter_emit(write,ctx,"\r\n");++rows;
+ }
+ n=0;if(s->text==PS_METER_TEXT_INSIDE)while(value[n]&&nstyle&PS_METER_ADJUST_MARKERS) &&
+ m->adjustable && n && n+4<=width){markers=1;n+=4;}
+ start=(width-n)/2;
+ if(s->panel)ps_meter_emit(write,ctx,s->unicode?"โ":"|");
+ if(kind==PS_METER_CHEVRONS){
+ char field[244];ps_meter_value_field(field,sizeof field,width,value,s->unicode);
+ if(s->color)ps_meter_emit(write,ctx,"\033[33m");
+ ps_meter_emit(write,ctx,field);
+ }else for(i=0;i=start&&iunicode?(offset==0?"โ":"โถ"):(offset==0?"<":">");
+ else if(!markers || (offset>=2&&offsetcolor&&kind==PS_METER_BLOCKS){
+ ps_meter_emit(write,ctx,icolor)ps_meter_emit(write,ctx,icolor)ps_meter_emit(write,ctx,istyle,iunicode,i));
+ }
+ }
+ if(s->color)ps_meter_emit(write,ctx,"\033[0;37m");
+ if(s->panel)ps_meter_emit(write,ctx,s->unicode?"โ":"|");
+ if(s->color)ps_meter_emit(write,ctx,"\033[0m");
+ ps_meter_emit(write,ctx,"\r\n");++rows;
+ if(s->panel){ps_meter_edge(write,ctx,s,width,1);++rows;}
+ return rows;
+}
+#endif
diff --git a/firmware/esp/src/portable-shell/portable_shell_editor.hpp b/firmware/esp/src/portable-shell/portable_shell_editor.hpp
index f5f87fd..5af15b1 100644
--- a/firmware/esp/src/portable-shell/portable_shell_editor.hpp
+++ b/firmware/esp/src/portable-shell/portable_shell_editor.hpp
@@ -131,6 +131,9 @@ class LineEditor {
template void redraw(W w) {
ps_editor_redraw(&state_, writer, &w);
}
+ template void refreshPrompt(const char *label, W w, bool ansi = true) {
+ ps_editor_refresh_prompt(&state_, label, ansi, writer, &w);
+ }
template void redrawEvent(const char *text, W w) {
char safe[257];
terminalText(safe, sizeof(safe), text);
diff --git a/firmware/esp/src/portable-shell/portable_style.h b/firmware/esp/src/portable-shell/portable_style.h
index efe68af..a17224f 100644
--- a/firmware/esp/src/portable-shell/portable_style.h
+++ b/firmware/esp/src/portable-shell/portable_style.h
@@ -1,11 +1,79 @@
#ifndef PORTABLE_STYLE_H
#define PORTABLE_STYLE_H
+#include "portable_util.h"
+#ifndef PS_SYNC_OUTPUT
+#define PS_SYNC_OUTPUT 1
+#endif
+#ifndef PS_FOOTER_KEYCAPS
+#define PS_FOOTER_KEYCAPS 1
+#endif
+#ifndef PS_TAB_BRACKETS
+#define PS_TAB_BRACKETS 0
+#endif
+#ifndef PS_KEYCAP_STYLE
+#define PS_KEYCAP_STYLE "\033[0;1;38;5;255;48;5;238m"
+#endif
+#ifndef PS_FOOTER_GRID
+#define PS_FOOTER_GRID 1
+#endif
+#ifndef PS_KEYCAP_PROMINENT_STYLE
+#define PS_KEYCAP_PROMINENT_STYLE "\033[0;1;30;43m"
+#endif
+/* Shared fixed-cell grid. Whole rows fall back to compact spacing when they do
+ not fit; no hint is dropped merely to align the columns. UTF-8 continuation
+ bytes do not consume cells (footer vocabulary uses single-cell glyphs). */
+static inline void ps_style_footer_format(char *dst,unsigned capacity,const char *src,unsigned width,int grid) {
+ unsigned at=0;
+ if(!capacity)return;
+ while(*src&&at+1max)max=cells;cells=0;++count;while(pmax)max=cells;
+ {int aligned=grid&&max<20&&(count-1)*20+cells<=width;
+ while(src=8)kind=1; /* Preserve explicitly selected named palettes. */
static const char *const original[] = {"\033[38;5;245m", "\033[38;5;80m", "\033[38;5;223m",
"\033[38;5;203m", "\033[1;38;5;231m", "\033[38;5;151m",
"\033[38;5;80m", "\033[38;5;245m"};
@@ -32,18 +116,22 @@ static inline const char *ps_style_ink(unsigned theme, int rgb, int red_border,
"\033[38;2;229;192;123m", "\033[38;2;224;108;117m",
"\033[38;2;220;223;228m", "\033[38;2;152;195;121m",
"\033[38;2;198;120;221m", "\033[38;2;92;99;112m"};
- if (kind == 7 && theme != 2 && red_border) {
+ if (kind == PS_INK_BORDER && theme != PS_THEME_CYAN && red_border) {
return rgb ? "\033[38;2;255;95;115m" : "\033[38;5;203m";
}
- return theme == 2 ? original[kind % 8]
- : rgb ? (theme == 3 ? one_dark[kind % 8]
- : theme == 1 ? red[kind % 8]
+ return theme == PS_THEME_CYAN ? original[kind % 8]
+ : rgb ? (theme == PS_THEME_ONE_DARK ? one_dark[kind % 8]
+ : theme == PS_THEME_RED ? red[kind % 8]
: violet[kind % 8])
: fallback[kind % 8];
}
static inline const char *ps_style_surface(unsigned theme, int rgb, unsigned active) {
- if (!PS_STYLE_THEMES) return "";
- if (theme == 2)
+ if (!PS_STYLE_THEMES) return active ? "\033[7m" : "\033[1m";
+ if(theme==PS_THEME_DEFAULT){
+ if(!rgb)return active==1?"\033[48;5;252;38;5;234m":active==2?"\033[48;5;237m":"\033[48;5;236;38;5;255m";
+ return active==1?"\033[48;2;211;218;230m\033[38;2;24;26;31m":active==2?"\033[48;2;48;55;67m":"\033[48;2;36;41;50m\033[38;2;230;233;239m";
+ }
+ if (theme == PS_THEME_CYAN)
return active == 1 ? "\033[48;5;23;38;5;159;1m"
: active == 2 ? "\033[48;5;236m"
: "\033[48;5;23;38;5;159m";
@@ -51,11 +139,11 @@ static inline const char *ps_style_surface(unsigned theme, int rgb, unsigned act
return active == 1 ? "\033[48;5;171;38;5;16m"
: active == 2 ? "\033[48;5;53m"
: "\033[48;5;53;38;5;225m";
- else if (theme == 3)
+ else if (theme == PS_THEME_ONE_DARK)
return active == 1 ? "\033[48;2;198;120;221m\033[38;2;33;37;43m"
: active == 2 ? "\033[48;2;62;68;81m"
: "\033[48;2;33;37;43m\033[38;2;171;178;191m";
- else if (theme == 1)
+ else if (theme == PS_THEME_RED)
return active == 1 ? "\033[48;2;251;113;133m\033[38;2;42;16;24m"
: active == 2 ? "\033[48;2;72;35;47m"
: "\033[48;2;55;29;39m\033[38;2;253;194;205m";
@@ -65,4 +153,13 @@ static inline const char *ps_style_surface(unsigned theme, int rgb, unsigned act
: "\033[48;2;45;31;59m\033[38;2;225;194;247m";
}
+/* Prompt-only consumers must not retain whole table palettes in static RAM. */
+static inline const char *ps_style_prompt(unsigned theme, int rgb) {
+ if (!PS_STYLE_THEMES) return "";
+ if (theme==PS_THEME_DEFAULT) return rgb?"\033[38;2;112;211;235m":"\033[38;5;80m";
+ if (theme==2) return "\033[38;5;80m";
+ if (!rgb) return "\033[38;5;213m";
+ return theme==3?"\033[38;2;198;120;221m":theme==1?"\033[38;2;255;107;138m":"\033[38;2;232;121;249m";
+}
+
#endif
diff --git a/firmware/esp/src/portable-shell/portable_table_policy.h b/firmware/esp/src/portable-shell/portable_table_policy.h
new file mode 100644
index 0000000..39ad016
--- /dev/null
+++ b/firmware/esp/src/portable-shell/portable_table_policy.h
@@ -0,0 +1,8 @@
+#ifndef PORTABLE_TABLE_POLICY_H
+#define PORTABLE_TABLE_POLICY_H
+/* Shared selection policy; NULL override means automatic, "" means hidden. */
+static inline const char *ps_table_marker(const char *override_text,int unicode,int categories,int collapsible,int chevrons) {
+ if(override_text)return override_text;
+ return !categories||!collapsible||!chevrons?(unicode?"\xe2\x96\xb8 ":"> "):"";
+}
+#endif
diff --git a/firmware/esp/src/portable-shell/portable_terminal.h b/firmware/esp/src/portable-shell/portable_terminal.h
index 77decf5..c82a039 100644
--- a/firmware/esp/src/portable-shell/portable_terminal.h
+++ b/firmware/esp/src/portable-shell/portable_terminal.h
@@ -1,5 +1,6 @@
#ifndef PORTABLE_TERMINAL_H
#define PORTABLE_TERMINAL_H
+#include "portable_util.h"
/* MIT, Copyright (c) 2026 AtomicDeploy and PortableShell contributors.
* Shared byte-only terminal primitives. Source lineage is recorded in the
* repository integration documents and donor inventory.
@@ -171,6 +172,31 @@ static inline size_t ps_decimal(char *out, unsigned value) {
out[n] = 0;
return n;
}
+#ifndef PS_MOUSE
+#define PS_MOUSE 1
+#endif
+/* SGR mouse payload following CSI: