Add Typed Display Settings Administration

Implements admin-only Display settings with generation-checked
Apply, Save, Load, Defaults, and Reset operations across the web UI,
CLI, SSH dispatcher, and local UI owner. Adds bounded HTTP handling,
session-isolated operation results, browser lifecycle support, and
comprehensive host tests and documentation.
This commit is contained in:
2026-09-09 10:15:23 +02:00
parent 60d9c54bb4
commit d9ec3c08de
26 changed files with 1164 additions and 81 deletions
+8
View File
@@ -229,6 +229,14 @@ Persistent namespaces/blobs include:
Configuration modules generally choose RAM defaults without erasing incompatible storage. Security-material modules fail closed on malformed existing material and require explicit reset. OTA slots, coredump space, an NVS-key partition, and storage are reserved in `partitions.csv`; OTA, NVS encryption, coredump handling, and filesystem mounting are not implemented.
## Typed Display settings (8D.14)
`web_display_settings` adds admin-only bodyless GET snapshot and GET/POST operation routes. HTTPD validates bounded typed JSON and queues only an ID on the existing administration dispatcher. One login-bound slot and non-reused operation IDs follow Serial's30-second dequeue admission deadline, without a new timer; already-admitted operations may finish after logout. Results are replaceable, not durable or idempotent. The UI preserves both terminals/lease, shows Serial-style labels/values, validates timeout limits, explicitly distinguishes RAM/drafts/NVS and uses at most10 one-second result GETs/15seconds overall, then manual recovery without mutation replay.
`local_status_ui` provides a zero-wait RAM/generation projection and conditional public update API. A short critical-section reservation serializes every config writer, including legacy Apply and CLI, across NVS IO performed outside the critical section. Stale nonzero generation rejects before mutation; successful RAM publication advances a nonwrapping generation and activity. Save stabilizes selected working bytes, not browser drafts. Load keeps canonical missing/incompatible-storage defaults without rewriting NVS. Reset now commits defaults before publishing RAM (CLI too); failure leaves RAM unchanged without rollback. Buttons/diagnostic holds affect independent activity, not configuration generation; renderer reads its previous consistent RAM copy during storage work. Absent panel does not prevent config when UI task is available; no panel/I2C IO is called by settings.
Bounds:256-byte/four-receive request,128-byte snapshot,96-byte result, one slot;30 URI handlers/six sockets, no new task/timer/stack-size/queue/schema expansion. Optional staged allocation failures retain other routes. Host tests/build pass; target and memory/stack margins pending. Full contract, resource evidence and checklist: `docs/phase8d14_implementation.md`.
## Local UI and hardware boundaries
`board_pins.h` centralizes project-assigned RS-232, diagnostic, RGB LED, and local-UI hardware resources; UART0 GPIOs remain local to `main.c`, and native USB uses platform wiring. `local_display` solely owns I2C0, the SSD1315-compatible OLED, its static framebuffer, and display mutex. Display frames belong to the initiating task. Dirty-page commits and I2C transactions are bounded.
+4 -2
View File
@@ -59,6 +59,8 @@ This is a semantic map, not a complete file inventory. Start here, then read the
## Web and WebSocket serial
- **8D.14 Display (2026-09-09):** `web_display_settings.{c,h}` owns optional admin-only GET `/api/settings/display`, GET/POST `/api/settings/display-operation`; `web_ui.c` supplies Serial-style dim/off settings and bounded completion checks. `local_status_ui` owns generation-safe config/storage reservation shared with CLI; buttons do not edit timeouts. No I2C changes. 256-byte/four-receive request,128-byte snapshot,96-byte result, one slot/no timer;30 handlers/six sockets, unchanged tasks/stacks/queue/schema. Tests: cookie `--display` (7+shared), UI111, lifecycle23, dispatcher and broad regressions. Actual baseline100,100/1,748,513 B → final100,196 RAM/1,765,233 flash at160MHz. Target pending; exact API, reset ordering, deadlines, resource/validation limits: `docs/phase8d14_implementation.md`.
- **Current 8D.12/8D.13 — user functional sign-off 2026-09-08, including Settings presentation:** `web_network_settings.{c,h}` owns optional admin-only GET `/api/settings/network` and GET/POST `/api/settings/network-operation`; `web_ui.c` supplies Network, UTF-8/hex SSID editing and explicit transient-secret/connection controls. `wifi_manager` owns generation-checked secret-free snapshots/patch/save/stored-only load and radio transitions; `mdns_service` owns independent conditional hostname persistence, with manager reannouncement. Existing dispatcher receives IDs only. 768-byte request/2,048-byte snapshot/128-byte result, one slot/one-second timer with 30-second queued expiry plus scheduling latency; no hard cancellation. 27 handlers/six sockets, no task/stack/queue/schema growth. Parent integrated tests/build PASS; latest styling UI100 + renderer/CSP/Chromium checks, 99,548 B RAM / 1,744,325 B flash. User full-mix evidence accepted; loaded internal/DMA minima2,276/156 B remain resource follow-ups, not reserve approval. Full contract/exclusions/checklist: `docs/phase8d12_13_implementation.md`. Both phases user-authorized together; no 8D.14/M3 claim. Older next-phase statements below are historical.
- **8D.11:** `web_account_settings.{c,h}` extends Accounts with fingerprint-only POST `/api/settings/accounts/keys` and key-add/key-delete/key-clear on the existing operation endpoint/dispatcher. `user_database.{c,h}` owns zero-wait target-checked snapshots and canonical conditional key mutations. `web_ui.c` handles confirmations, sparse stable indices and self-revocation uncertainty. 24 handlers, six sockets; no new task/stack/queue depth. Host-tested/build-verified, target pending. Contracts/tests/checklist: `docs/phase8d11_implementation.md`.
@@ -160,12 +162,12 @@ This is a semantic map, not a complete file inventory. Start here, then read the
**Responsibility:** own OLED I2C/framebuffer operations and present status plus constrained button actions.
- Files: `src/local_display.{h,c}`, `src/local_status_ui.{h,c}`, `src/local_boot_animation.{h,c}`, `src/local_ui_config.{h,c}`, `src/local_ui_console.{h,c}`
- Interfaces: display init/frame/draw/commit/snapshot; UI start/activity/config; versioned NVS settings
- Interfaces: display init/frame/draw/commit/snapshot; UI start/activity/config; generation-checked settings projection/update and explicit persistence reservation; versioned NVS settings
- Called by: startup, local UI task, diagnostics, display console
- Dependencies: copied snapshots/public APIs from serial, broker, USB, Wi-Fi, web, SSH
- Ownership: `local_display` solely owns I2C0 and framebuffer mutex; a frame belongs to its initiating task.
- Lifecycle: the low-priority task is firmware-lifetime only if button GPIO initialization succeeds; it still runs with an absent panel so a press can reprobe after successful I2C bus setup. Failed bus creation is not recoverable by that reprobe, and `display` configuration commands depend on the UI task.
- Constraint: collect service snapshots before I2C; local UI never joins broker or handles secrets.
- Constraint: collect service snapshots before I2C; local UI never joins broker or handles secrets. All configuration writers honor the UI owner's zero-wait reservation; NVS runs outside timing critical sections. Reset commits defaults before RAM publication, including CLI; buttons/diagnostic holds update activity, not configuration generation.
## Hardware and diagnostics
+2
View File
@@ -4,6 +4,8 @@ This file is working memory. Update it during active work and before handoff; do
## Development state
- **8D.14 Display implementation complete (2026-09-09), user-authorized; host-tested/build-verified, target pending:** Admin Settings → Display supplies typed dim/off086400s, Apply/Save/Load/Defaults/Reset, Serial-style label/value presentation and bounded result polling. `web_display_settings` owns one secret-free login-bound slot, 256-byte/four-receive requests,128-byte snapshot/96-byte results; IDs use the existing dispatcher. `local_status_ui` owns nonwrapping config generation and zero-wait reservation across NVS outside critical sections; CLI shares the gate, buttons retain independent activity/reprobe behavior. Reset now commits defaults before RAM publication (CLI too), eliminating rollback overwrite. Absent panel does not gate config if UI task is available; no I2C/renderer ownership change.30 handlers/six sockets, no new task/timer/stack size/queue/schema/assets changes.30-second dequeue admission deadline, not timed cancellation; admitted work may finish after logout. PASS Display7+shared auth, UI111+C/HTML/CSP, lifecycle23, dispatcher/accounts/policy, cookie all variants, canonical Network, parser294, transports/tickets/store/idle/diagnostics/throughput/broker/login regressions and diff check. Separate self-review fixed strict result status/action/replacement handling. Independent final review found no actionable findings and independently reran Display7+shared auth, dispatcher boundary, lifecycle23, UI111+C/HTML/CSP and diff checks PASS. Parent final pio confirmation PASS9.08s, unchanged100,196/1,765,233 B; diff check PASS. Actual pre-edit pio12.10s100,100 RAM/1,748,513 flash; final pio26.09s100,196/1,765,233 (+96/+16,720 B). CPU160 confirmed in defaults/active/generated configuration; throughput fix and user sign-off preserved. Initial worktree reported clean. Optional Chromium geometry attempt blocked by sandbox socket/crash-report restrictions; no geometry pass. Target display/save-reboot/absent-panel/buttons-concurrency/full-mix/heap/HTTPD-dispatcher margins and sign-off pending, no reserve approval or full M3 claim. Exact contracts/tests/resources/checklist: `docs/phase8d14_implementation.md`. No upload/erase/commit or later phase; older no-8D.14 authorization statements below are historical.
- **Throughput fix signed off at160MHz (2026-09-08):** User explicitly validates drop-free230400-baud operation with the full client mix, including browser admin, at160MHz. Current defaults, active sdkconfig and generated sdkconfig.h independently confirm160MHz selection. This supersedes earlier frequency uncertainty and pending functional throughput validation below. Retain combined binary header/payload TLS send and bounded failure isolation; no further tuning needed for this issue. Latest full-mix160MHz result is user-reported without additional raw counters or duration; no invented long-soak, peer-byte comparison or HTTPD-stack reserve validation. Documentation-only acceptance update; no source/config/build/upload/erase/commit action.
- **Combined binary WS send functionally validated by user (2026-09-08):** User confirms no drops with browser admin also connected and explicitly signs off. Prior detailed two-browser/SSH/USB capture (browser admin absent) had UART71,292 B, all four clients read71,292/drop0, global285,168, web430 frames/142,584 B, HWM2,499/2,563; send averages4,215/4,522us and callback5,691/6,253us, no transport errors/disconnects. Full-mix follow-up is user-reported, no additional counters/duration supplied. Supersedes target-pending functional status below; no sustained-soak/HTTPD-stack reserve approval inferred. User also reports commenting out CPU240 default and assumes160MHz; inspection shows tracked default commented but active sdkconfig and generated sdkconfig.h STILL select240MHz. Thus full-mix functional sign-off stands, but160MHz operation is not verified. Preserve user config edit; no automatic rollback/build/upload. Next optional step is explicit160MHz selection in defaults and active config, rebuild/generated-setting verification, then target retest. Documentation-only sign-off update.
+6
View File
@@ -2,6 +2,12 @@
Only constraints supported by implementation or current project documentation belong here. When original rationale is unknown, the entry describes the observable constraint without inventing intent.
## Display configuration has an owner reservation separate from button activity
**Decision (8D.14):** `local_status_ui` owns a nonwrapping RAM config generation and a zero-wait reservation shared by typed Display operations and canonical CLI/legacy Apply. Compare/reserve and publish occur in short timing critical sections; NVS occurs outside them. Save stabilizes selected bytes, Load retains canonical fallback, and Reset commits defaults before RAM publication, including CLI. This replaces Reset's apply/rollback race with failure-before-publication semantics.
**Consequence:** Browser mutations must carry the selected nonzero generation, including Save; never silently replace intervening CLI edits. Buttons and diagnostic holds retain independent activity state, not config generation or persistence ownership. Do not hold timing locks across storage or I2C, add display-presence prerequisites to configuration, or mistake a successful config API for physical panel success. The existing dispatcher carries IDs only; the secret-free slot uses a30-second dequeue admission deadline, not a new timer or hard cancellation. Preserve explicit result uncertainty and no automatic mutation replay. `docs/phase8d14_implementation.md` records the exact contracts and pending target gates.
## Typed Network edits preserve manager ownership and current secret bytes
**Decision (8D.12/8D.13):** `web_network_settings` admits bounded typed operations into one login-bound slot; the existing dispatcher carries IDs only and calls canonical generation-checked Wi-Fi/mDNS APIs. HTTPD reads only zero-wait secret-free projections. Wi-Fi mutex-local compare/merge/validation preserves omitted PSKs and prevents stale edits undoing CLI/local changes; queue admission precedes RAM publication. Save stabilizes selected bytes under the mutex; browser Wi-Fi Load reads stored configuration only, never generates fallback AP secrets. mDNS uses its own generation and reports RAM-applied/reannouncement-not-queued separately.
+3 -1
View File
@@ -44,10 +44,12 @@ Missing `user_db/database` storage is committed empty. On UART0 run `user add <u
| `display set dim-seconds <0..86400>` | Set the RAM inactivity delay before contrast drops to `1`; `0` disables dimming. |
| `display set off-seconds <0..86400>` | Set the RAM inactivity delay before the OLED switches off; `0` disables automatic off. |
| `display save` / `display load` | Save the working aging settings to NVS or load them. |
| `display defaults` / `display reset` | Apply 300/600-second defaults in RAM, or apply and persist them. |
| `display defaults` / `display reset` | Apply 300/600-second defaults in RAM, or save defaults first and then apply them. |
When both transitions are enabled, `off-seconds` must be greater than `dim-seconds`. Applying settings counts as local UI activity. At normal boot, an initialized OLED shows a bounded five-second identity animation before the status UI begins; it scrolls the device name in yellow and draws the compact upright-terminal logo in blue. A missing OLED remains nonfatal; after reconnecting it safely, one new button press requests a bounded reprobe and is consumed without navigating.
Display settings require an available local UI task, not an attached panel. CLI and browser **Settings → Display** share the public configuration owner; concurrent mutations can report busy, and browser operations reject an intervening configuration edit rather than overwrite it. Save persists working RAM, not browser drafts. Load selects defaults when saved storage is absent/incompatible without rewriting NVS. Reset storage failure leaves RAM unchanged (commit-before-publication, no RAM rollback). Browser timeout/navigation does not cancel already-admitted work; Check Result and Refresh before retrying. [Display settings contract and target checklist](phase8d14_implementation.md).
## Serial service
| Command | Description |
+138
View File
@@ -0,0 +1,138 @@
# Phase 8D.14 — Typed Display settings
## Status and scope (2026-09-09)
User-authorized implementation complete; host-tested and production-build-verified. **Target validation and phase sign-off remain pending.** No 8D.15/later phase or full M3 claim.
Adds an admin-only **Settings → Display** view with Serial-style label/value presentation, typed dim/off inactivity edits, and explicit Apply/Save/Load/Defaults/Reset. The production starting worktree reported clean, despite the request's warning about uncommitted work; the existing combined binary WebSocket throughput fix and CPU160MHz configuration were preserved without source/configuration edits. Prior user full-mix230400 throughput acceptance stands and is not a validation claim for these Display changes.
No I2C/framebuffer/renderer ownership changes, electrical-diagnostic UI, secret fields/logging, generic command runner/job framework, new task/timer/mutex allocation, stack-size increase, queue-depth/item expansion, socket increase, NVS schema migration, generated-asset change/regeneration, upload, erase, branch or commit. Browser-shell authorization restrictions are unchanged.
## Production files and ownership
- `src/web_display_settings.{c,h}`: bounded HTTP authorization/admission, one login-bound operation/result slot, dispatcher execution adapter and RAM snapshot encoding.
- `src/local_status_ui.{c,h}`: authoritative configuration generation and zero-wait settings projection; conditional public configuration/persistence operations.
- `src/local_ui_console.c`: canonical CLI Set uses compare-generation Apply; Save/Load/Reset use the same reserved owner API. Defaults and existing public Apply also pass through the owner gate.
- `src/admin_ssh_console.{c,h}`: Display ID in the existing typed request union and existing sole dispatcher, without a command string.
- `src/web_server.c`, `src/CMakeLists.txt`: optional route registration and source composition.
- `src/web_ui.c`: authored Display HTML/JS; no vendored/generated asset edits or loader/CSP changes.
- `local_ui_config.c` remains the unchanged canonical version-1, 12-byte NVS validator/loader/saver.
### Configuration/currentness contract
`local_status_ui_get_settings(config, generation)` copies RAM plus a nonzero generation in one short timing critical section. It returns unavailable if UI startup did not publish configuration, or busy while a reserved configuration/storage operation is running. It never accesses the panel or NVS. The preexisting renderer/get-config path can still read the last committed RAM configuration during storage work.
`local_status_ui_update_settings(action, expected_generation, config, loaded_defaults)` compares generation and reserves configuration mutation under that same critical section. Storage IO runs **outside** the critical section. Every configuration writer, including legacy Apply and CLI, honors the reservation; competing mutations return busy rather than block. Browser requests require a nonzero expected generation; zero is reserved for unconditional canonical CLI operations. Successful RAM publication advances the generation even when bytes are identical, and signals external activity. Save does not advance generation or signal activity. Generation exhaustion fails closed rather than wrapping.
Buttons currently do **not** edit these timeouts: their activity, wake, navigation and diagnostic-hold sequences remain distinct from the configuration generation. They can proceed during NVS work. No timing lock is retained across I2C. The UI task is still optional and firmware-lifetime: configuration can work with an absent panel if the task starts, while unavailable task/button initialization yields an unavailable settings snapshot. No response claims actual brightness, successful I2C, physical presence or successful reprobe. Failed bus creation remains unrecoverable through the existing button reprobe.
### Persistence semantics
| Action | Required fields besides `action` | Effect |
|---|---|---|
| `apply` | `generation`, `dim_seconds`, `off_seconds` | Replace both RAM timeout values, not NVS; signal activity. |
| `save` | `generation` | Persist exactly the selected working generation; browser drafts are ignored. Reservation prevents changes during Save. |
| `load` | `generation` | Canonical load into RAM. Missing/incompatible/invalid storage selects defaults and returns `loaded_defaults`; NVS is not overwritten. IO errors leave RAM unchanged. |
| `defaults` | `generation` | Apply 300/600-second defaults to RAM only; signal activity. |
| `reset` | `generation` | Save defaults first, then publish RAM and signal activity. |
**Intentional CLI behavior refinement:** Reset previously applied RAM and attempted rollback on storage failure. Both typed and CLI Reset now commit defaults before RAM publication. Failure leaves RAM unchanged, so no rollback can overwrite concurrent activity/configuration and no `rollback_failed` result is needed. Successful resulting RAM/NVS values are unchanged. A storage error is not a power-loss guarantee about physical flash; inspect/reload after uncertain delivery or storage failure.
Both timeout values are unsigned integers in **086,400 seconds**. Zero disables that transition. If both are nonzero, Off must be strictly later than Dim. The fixed NVS version is supplied internally, not browser-editable. Set rejects stale read/modify/write configuration rather than overwriting a concurrent edit. No persisted generation, schema expansion or migration is needed.
## HTTP and operation contract
All three routes reuse current cookie/principal authentication, admin role enforcement, strict header/framing validation, no-store/nosniff/no-referrer response policy and request scratch cleanup. Normal users receive 403; invalid/expired sessions receive 401. GET is bodyless/queryless with the existing GET Origin policy. POST additionally requires canonical same-origin Origin and CSRF, a supported JSON Content-Type, no query or transfer encoding, and a nonempty body.
| Route | Method | Payload / bound |
|---|---|---|
| `/api/settings/display` | GET | Exactly `generation`, `dim_seconds`, `off_seconds`; 128-byte response buffer. 503 if UI unavailable/busy. No storage/panel reads. |
| `/api/settings/display-operation` | POST | Maximum 256 bytes and four receive calls. Exactly the fields in the action table. 202 returns admission, not completion. |
| `/api/settings/display-operation` | GET | Latest result for this originating login only, exactly `id`, `action`, `state`; 96-byte response buffer. |
Strict flat parser rejects unknown/duplicate/missing fields, escapes, strings in numeric fields, negative/fractional/exponent/overflow/leading-zero numbers, embedded NUL, trailing garbage, nested values and unsupported actions. Partial/error/excessively fragmented bodies are rejected, with unread bodies closing rather than unbounded drain/retry.
One static operation slot is shared by all administrators, but result retrieval is isolated by non-reused originating web-session ID. Other logins see `{id:0,action:"none",state:"idle"}`. Pending/full dispatcher admission returns 503 with Retry-After 1. Slot IDs never wrap; completed results are replaceable, not durable history or idempotent retry records.
Only an ID enters the existing four-entry dispatcher queue. On dequeue, the adapter validates the original session/principal/admin and **30-second admission deadline**, then calls the owner conditional API. This is the Serial-style dequeue deadline: **no new timer**, no hard cancellation, and a blocked dispatcher can retain this small secret-free pending record beyond 30 seconds until dequeue. Restart/revocation does not authorize stale queued work: session IDs are not reused. Already-admitted storage work can complete after logout, absolute expiry or server stop; acknowledgement/result delivery is not guaranteed.
States:
- `idle`: no result belonging to this login; not proof earlier work was cancelled.
- `pending`: queued or executing, not completed.
- `ok`: owner operation completed; only Save/Reset imply successful explicit persistence.
- `loaded_defaults`: Load selected defaults without overwriting missing/incompatible storage.
- `conflict`: selected generation stale, owner unavailable or generation exhausted; this operation made no change.
- `failed`: owner busy, validation/storage failure; this operation did not publish RAM. Inspect storage after uncertainty.
- `cancelled`: original session/currentness/dequeue deadline rejected before owner admission.
Optional registrations are staged through the existing adapter: snapshot first, then operation GET, then POST. A POST allocation failure unregisters the operation GET; failed unregister can leave a read-only result route, never a mutation-only domain. Failure preserves other settings/auth/transports. HTTPD handler budget **27 → 30**, six sockets/no LRU unchanged.
## Browser lifecycle
Display navigation preserves both sockets, hidden output draining, broker identity and writer lease; only terminal keyboard routing/view selection changes. It reads fresh working state on entry and allows explicit Refresh. Invalid/unavailable snapshots disable mutation until a valid refresh. Refresh discards drafts; there is no saved-value read or automatic mutation on selection.
All mutation bodies carry the last loaded generation, so a CLI edit between refresh and dequeue becomes a conflict, including Save. Routine Apply/Save/Load/Defaults use inline consequences; only Reset asks for confirmation of saved-NVS overwrite. Pending values remain visible but marked stale; repeated submission is disabled.
After a validated 202 acknowledgement, automatic completion checks run at one-second intervals, **at most ten GETs and fifteen seconds overall**, including session checks. Known terminal results refresh the RAM snapshot once while retaining the outcome message. No automatic POST retry. Result replacement stops automatic following; same-ID action mismatch, invalid HTTP status/schema or impossible loaded-defaults action fails validation. Lost acknowledgement, idle/replaced results and failures retain an explicit uncertainty warning with manual Check Result/Refresh recovery.
Domain/view navigation, pagehide, logout, expiry, identity replacement and late request/body completion are fenced by existing work generation plus Display request ownership/abort state. Drafts clear on teardown. Returning does not resume polling or replay a mutation. Backend work is not cancelled by browser navigation or timeout. Session replacement still requires a clean document rather than exposing retained terminal state to a new identity.
## Validation executed
All commands below ran successfully unless explicitly marked unavailable. Host storage/RTOS/HTTPD/renderer doubles are not target timing, power-loss or real-device evidence.
| Command | Result / scope |
|---|---|
| `python3 tests/web_cookie_auth/run.py --display` | PASS: **7 Display groups**, plus shared cookie/store suite. Compiles production web handler, public owner function bodies, unchanged full NVS config implementation, canonical CLI handlers, cookie/store/parser and installed-SDK boundary doubles. Schema/bounds, admin/auth, absent UI/busy projection, generation conflicts, CLI parity, real loader reboot projection, activity/diagnostic-hold interleaving, init/open/read/set/commit fault injection, replay isolation, session expiry/revocation/restart and lost ACK. |
| `python3 tests/admin_console_boundary/run.py` | PASS, including added Display zero-ID/not-ready/full-queue/FIFO routing with Serial/Accounts/Network in the unchanged four-entry queue. Existing certificate and SSH owner adapter suites pass. |
| `python3 tests/admin_console_boundary/lifecycle.py` | PASS canonical lifecycle/force/deferred owner isolation. |
| `python3 tests/admin_console_boundary/accounts.py` | PASS canonical accounts/keys/storage/currentness regressions. |
| `python3 tests/admin_ssh_policy/run.py` | PASS parsed policy, startup gates and completion regressions. |
| `python3 tests/web_admin_transport/server_lifecycle.py` | PASS **23 groups**, including all six Display staged descriptor/name failure positions, failed unregister, stop failure/restart, and other-domain failure isolation. |
| `python3 tests/web_ui_session/run.py` | PASS **111 browser groups**: prior 100 plus 11 Display groups; production C renderer, all resource/header failures, HTML structure/style and exact loader/CSP checks. All four views covered by structural fixtures. |
| `python3 tests/web_cookie_auth/run.py --settings` | PASS Settings/status/optional adapter and shared auth. |
| `python3 tests/web_cookie_auth/run.py --serial-settings` | PASS Serial mutation/CLI/failure/currentness regressions and shared auth. |
| `python3 tests/web_cookie_auth/run.py --accounts` | PASS account/key/credential policies and shared auth. |
| `python3 tests/web_cookie_auth/run.py --network` | PASS Network bounds/secret handling/owner admission and shared auth. |
| `python3 tests/web_cookie_auth/run.py --admin` | PASS real cookie/ticket/admin admission integration and shared auth. |
| `python3 tests/web_network_settings/run.py` | PASS 5 canonical Wi-Fi/mDNS owner groups. |
| `python3 tests/web_auth_parse/run.py` | PASS **294 cases, 0 failures**. |
| `python3 tests/web_admin_transport/run.py --tickets` | PASS transport and ticket suites. |
| `python3 tests/web_httpd_idle/run.py` | PASS idle lifecycle and SDK contract guards. |
| `python3 tests/web_diagnostics/run.py` | PASS diagnostics and SDK guards. |
| `python3 tests/web_session_store/run.py --serial` | PASS store and serial integration. |
| `python3 tests/web_serial_performance/run.py` | PASS preserved combined-send/failure isolation, installed-SDK wire contract and performance/epoch regressions. |
| `python3 tests/session_broker_diagnostics/run.py` | PASS **7 groups**, including bounded fanout/counter/ownership regressions. |
| `python3 tests/web_login_ui/run.py` | PASS renderer/CSP and **8 browser groups**. |
| `git --no-pager diff --check` | PASS. |
Optional real layout attempt: `WEB_UI_CHROMIUM=/usr/bin/chromium-browser python3 tests/web_ui_session/run.py` could not execute Chromium in the terminal sandbox: read-only crash-report storage and forbidden process-singleton `socket()`/ptrace. **No Chromium geometry/visual pass claimed.** Structural four-view fixtures pass; optional geometry fixtures were updated to all four views at 320/600/1200px (12 cases), but await an environment that permits Chromium. No dependency installation or broad sandbox escape was used. No sanitizer validation claimed.
Separate self-review checked owner reservation/public call sites, generation/publication/failure ordering, ID/session/deadline fences, HTTP route wiring, browser lifecycle/result validation and fixture coverage. Fixed result HTTP-status/action/replacement checking and expanded storage failures during that pass. This initial self-review was followed by an independent review of the final implementation: no actionable findings. Reviewer independently reran Display7+shared auth, dispatcher boundary, lifecycle23, UI111+C/HTML/CSP and diff checks, all PASS. Parent final `pio run` confirmation PASS9.08s at100,196 B RAM/1,765,233 B flash; diff check PASS. No target validation inferred.
## Build and resources
Actual pre-edit `pio run`: **PASS 12.10 s**, **100,100 B RAM / 1,748,513 B flash**. This matches the recorded combined-send size but was measured afresh in this worktree at CPU160MHz.
Final production `pio run`: **PASS 26.09 s**, **100,196 B RAM / 1,765,233 B flash**. Delta **+96 B RAM / +16,720 B flash** versus actual pre-edit baseline. Intermediate full configuration build passed in 71.95 s and emitted the existing nonfatal SDK `FATFS_PRINT_FLOAT` bool-default warning; final incremental build passed without that warning. No configuration was changed to suppress it.
Tracked defaults, active sdkconfig and generated `sdkconfig.h` explicitly select **160MHz**. PlatformIO's generic board banner prints 240MHz; it is not the compiled CPU selection.
Target toolchain object inspection (not host `sizeof`): Display slot **88 B**, operation ID **4 B**, portMUX **8 B**; UI config busy flag **1 B**, generation **4 B**. These are individual symbols before placement and do not by themselves explain the whole-image RAM delta. Three URI descriptors are **24 B each**. Successful startup requests **170 B additional route/table/name heap before allocator overhead**: 72 descriptor + 12 table-pointer + 86 NUL-terminated URI-name bytes. No new timer/task/mutex allocation.
Local Xtensa entry frames, excluding callees: operation HTTP handler **656 B**, snapshot HTTP handler **320 B**, dispatcher execute **128 B**, owner update **48 B**, owner snapshot **32 B**. These are not whole-call-chain requirements or runtime high-water margins. Rendered authored HTML/app/loader sizes: **26,973 / 97,829 / 1,173 bytes**. Browser allocations, runtime route heap overhead, HTTPD/dispatcher stack floors and loaded internal/DMA/PSRAM reserves remain unmeasured. Previously low lifetime memory minima remain follow-ups, not approved reserves or reopened prior functional acceptance.
## Pending target checklist — no execution/sign-off claimed
- [ ] Compare browser values with UART0/admin SSH `display status`, including 0/0, disabled Dim, disabled Off, 86,400 boundary and enabled ordering; reject malformed/direct API values without state changes.
- [ ] Verify actual dim/off/wake behavior and fresh activity after Apply/Load/Defaults/Reset; Save must not count as a wake/activity change.
- [ ] Change RAM without Save, reboot through an authorized independent control and confirm old saved values; explicitly Save, reboot and confirm new values. Confirm Defaults RAM-only and Reset persistence. No factory erase required.
- [ ] Missing/incompatible storage selects defaults on Load without silently replacing NVS; storage faults leave RAM unchanged. Test faults only through an approved safe test setup.
- [ ] With panel absent at boot but working buttons/UI task, read/apply/save/load configuration and observe continued UART0/native USB recovery. Reconnect panel safely and verify existing consumed-press reprobe/wake. Distinguish absent panel from failed I2C bus or unavailable UI task.
- [ ] While browser snapshot/operation is pending, edit through UART0/admin SSH and exercise buttons. Verify stale generation conflict, explicit Refresh/review, no overwritten CLI edit, no stuck reservation, no accidental button action or I2C ownership change.
- [ ] Two admin tabs/logins: global pending capacity, same-login result replacement warning, other-login result isolation, expired/revoked cookie denial, lost acknowledgement recovery, blocked dispatcher/dequeue expiry and HTTPS stop/restart fencing. Never interpret timeout as cancellation.
- [ ] Desktop/mobile narrow layout, keyboard focus, Refresh draft replacement, Reset confirmation, automatic completion bounds, manual uncertain-result recovery, logout/expiry/pagehide and same-session restoration.
- [ ] Repeat settled full mix at CPU160MHz/230400 baud with two browser serial clients, SSH/USB and both admins while using Display. Capture broker drops/HWM, service errors, free/minimum/largest internal/DMA/PSRAM and HTTPD/dispatcher stack high-water; distinguish acceptance from numeric reserve approval.
- [x] Independent diff/test review complete; no actionable findings.
- [ ] Explicit user target acceptance before claiming 8D.14 sign-off. No later-phase work is implied.
+3 -1
View File
@@ -1,5 +1,7 @@
# Phase 8D — Incremental web administration plan
**8D.14 current implementation (2026-09-09):** Separately user-authorized Display settings implemented and host/build verified; target sign-off pending. Typed dim/off edits and explicit Apply/Save/Load/Defaults/Reset use generation-checked public UI ownership shared with CLI, with NVS outside critical sections and Reset commit-before-RAM publication. Serial-style Display view, preserved terminal/lease lifecycle, bounded login-bound operation results/no automatic replay.30 handlers/six sockets; no task/timer/stack-size/queue/schema/I2C/assets changes. Display7+shared HTTP, UI111+C/HTML/CSP, lifecycle23 and broad dispatcher/auth/owner/transport/throughput regressions PASS. Actual pre-edit build100,100 RAM/1,748,513 flash → final100,196/1,765,233 (+96/+16,720 B), pio26.09s PASS, CPU160 confirmed. Chromium geometry blocked by sandbox, target save/reboot/absent-panel/buttons-concurrency and heap/stack margins pending. [Exact implementation and target checklist](phase8d14_implementation.md). No target/full-M3 sign-off, reserve approval or 8D.15/later work. Supersedes historical no-8D.14-authorization statements below; prior scoped functional acceptance stands.
**Latest functional acceptance (2026-09-08):** User signs off implemented **8D.12/8D.13 and Settings presentation refinements** after boot/full-client-mix telemetry. Supersedes target-pending/no-signoff statements for this scope below, not historical build/test evidence or unreported checklist limits. [Acceptance record](phase8d12_13_implementation.md) retains all samples and counters: web sole writer + web/SSH/USB observers, both admins; no web send/queue/protocol failures or SSH handshake/auth/IO failures, but rejected input and one logout/disconnect retained without diagnosis. Loaded internal/DMA free31,512/23,756 B, minima2,276/156 B, largest20,480 B. Low conservative lifetime minima remain a resource follow-up, not approved reserves or proof of allocation failure. No full M3 claim or next-phase implementation authorization; 8D.14 awaits a separate request.
**Current implementation (2026-09-08):** User authorized **8D.12 and 8D.13 together**, backend and Network UI delivered. 8D.12 covers nonsecret STA/AP/profile/mDNS edits and persistence; 8D.13 adds explicit secret replacement/disabled-STA clear and connection controls. Profile selection means selecting a configuration to edit; connection control is canonical **Next profile**, not explicit-index selection. 27 handlers/six sockets, one bounded slot/timer, no task/stack/queue/schema growth. Backend/cookie Network PASS, UI agent97+renderer/CSP/review PASS, lifecycle agent21 PASS; backend P3 queue-drop-counter finding fixed. **Parent integrated suites and build PASS:** 24.99 s, 99,548 B RAM / 1,742,437 B flash (+288/+36,656 vs legacy-cleanup baseline). Parent UI97/CSP, lifecycle21, Network/HTTP policy, canonical console/accounts, transport/tickets, idle/store/diagnostics checks passed; exact attribution below. **Target behavior, timer heap/memory floors and HTTPD/dispatcher stack margins remain pending.** [8D.12/8D.13 implementation](phase8d12_13_implementation.md) is the exact API/SSID/secret/uncertainty contract and checklist. No Wi-Fi reset/default-secret/export, browser-shell policy widening, 8D.14 work, M3 completion or target sign-off. Supersedes historical next-request restrictions below; previous scoped acceptance stands.
@@ -184,7 +186,7 @@ Typed operations must preserve subsystem owner/lock/persistence contracts and co
| **8D.11 — SSH authorized keys** | List fingerprints and add/delete/clear supported public keys through bounded user APIs. | Ed25519/P-256 import, maximum supported length, malformed input, duplicates, targeted revocation, and unchanged SSH authentication behavior. No private-key upload/export or host-identity management. |
| **8D.12 — Network settings without secret mutation** | Secret-free STA/AP/profile and mDNS settings, non-secret edits, and explicit persistence through `wifi_manager`, `wifi_config`, `mdns_service`/`mdns_config`. | Responses never serialize saved PSKs; validate working/persisted semantics, live hostname changes, and behavior after connection loss. No new manager/task or Wi-Fi blob migration. Split mDNS into a follow-up if needed. |
| **8D.13 — Wi-Fi secrets and connection controls** | Explicit password replacement/clear semantics, bounded transient input, profile selection/reconnect and AP policy actions using manager-owned operations. | Preserve existing secrets when fields are omitted; never prefill saved secrets; document apply/save and likely connection loss; reconnect via STA/AP and verify UART0/USB recovery. No background secret fetch or general credential export. |
| **8D.14 — Display settings** | Typed local display configuration and explicit persistence via `local_ui_config`/public UI APIs. | Validate limits, save/reboot, absent-display behavior, and concurrent buttons/CLI edits. No I2C ownership changes or electrical diagnostics UI. |
| **8D.14 — Display settings** | **Implemented, host/build verified; target pending.** Typed local display configuration and explicit persistence via `local_ui_config`/generation-checked public UI APIs; [contract/evidence](phase8d14_implementation.md). | Host limits/storage/CLI-generation/activity/lifecycle regressions pass; actual save/reboot, absent-display and concurrent buttons/CLI target checklist remains pending. No I2C ownership changes or electrical diagnostics UI. |
| **8D.15 — Bounded network diagnostics** | Secret-free network status and a narrowly bounded diagnostic workflow through existing network facilities. | Diagnostic start/result/cancel/expiry behavior and concurrent CLI use are bounded; callbacks do not format/send HTTP directly. No unbounded result/history buffer or new generic jobs framework. Split asynchronous ping from read-only status if necessary. |
| **8D.16 — Broker client visibility and writer transfer** | Admin-only detailed client snapshot plus explicit confirmed writer assignment using existing broker APIs; smallest broker change only if authoritative generation-safe validation is missing. | Stale/disconnected/reused target fails without changing the current lease; exactly one writer; normal users cannot obtain management details or transfer. Test concurrent USB/SSH/browser requests. No transfer on page open or selection alone. |
| **8D.17 — Serial/Wi-Fi quick popovers** | UI-only reuse of completed typed endpoints, with full-page links and shared validation; start in `web_ui`. | Hover, focus, click/tap parity, Escape/outside-click dismissal, no mutation on opening, explicit apply/save, no secret exposure. No duplicate backend or new settings scope. |