diff --git a/.clang-format b/.clang-format index 0fdb014e..db4d66d8 100644 --- a/.clang-format +++ b/.clang-format @@ -1,14 +1,19 @@ -{ - "BasedOnStyle": "LLVM", - "UseTab": "Never", - "IndentWidth": 4, - "TabWidth": 4, - "ColumnLimit": 150, - "PointerAlignment": "Left", - "AlignAfterOpenBracket": "DontAlign", - "BreakBeforeBraces": "Attach", - "AllowShortIfStatementsOnASingleLine": true, - "AllowShortFunctionsOnASingleLine": "All", - "IndentCaseLabels": false, - "SpacesBeforeTrailingComments": 1 -} \ No newline at end of file +--- +BasedOnStyle: LLVM +UseTab: Never +IndentWidth: 4 +TabWidth: 4 +ColumnLimit: 150 +PointerAlignment: Right +ReferenceAlignment: Pointer +AlignAfterOpenBracket: DontAlign +BreakBeforeBraces: Attach +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortBlocksOnASingleLine: Always +IndentCaseLabels: false +SortIncludes: false +SpaceBeforeParens: Never +ReflowComments: false +SpacesBeforeTrailingComments: 1 diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..7e18dc65 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,16 @@ +# Third-party/vendor code +components/codecs/inc/** +components/esp-dsp/** +components/esp_http_server/** +components/spotify/cspot/** +components/squeezelite/** +components/telnet/libtelnet/** +components/tjpgd/** +managed_components/** + +# Generated web artifacts +components/wifi-manager/webapp/dist/** +components/wifi-manager/webapp/src/js/proto/** + +# Build outputs +build/** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..54eaf5ab --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +## Summary + +- What changed: +- Why it changed: + +## Test Strategy Check (Required) + +- [ ] I validated behavior at contract/module boundaries (not private implementation details). +- [ ] New tests are refactor-resilient (should survive internal reorganization with identical behavior). +- [ ] For bug fixes, I added at least one regression test at the highest stable boundary. +- [ ] I avoided brittle assertions on internals (private helper call order, private layout, incidental log text), unless behavior-critical. + +## Validation Performed + +- [ ] Host/unit tests +- [ ] Hardware/HIL tests (if applicable) +- [ ] Platform profile(s) validated: `i2s` / `muse` / `squeezeamp` / N/A + +Commands and results: +```bash +# paste exact commands used +``` + +## Risk and Rollback + +- Risk level: low / medium / high +- Potential regressions: +- Rollback plan: + +## Related Docs + +- [ ] `documentation/TESTING_CHARTER.md` reviewed +- [ ] `documentation/HARDWARE_TEST_MATRIX.md` reviewed (if platform impact) diff --git a/.github/workflows/style-lint.yml b/.github/workflows/style-lint.yml new file mode 100644 index 00000000..458d2532 --- /dev/null +++ b/.github/workflows/style-lint.yml @@ -0,0 +1,32 @@ +name: Style Lint + +on: + push: + branches: + - "refactoring" + - "main" + - "master-cmake" + pull_request: + branches: + - "refactoring" + - "main" + - "master-cmake" + +jobs: + clang-format: + name: clang-format + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends clang-format + + - name: Check style + run: | + build-scripts/lint_style.sh check diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..5db7b7d2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# AGENTS.md + +This file is the default startup brief for Codex sessions in this repository. +Keep it short, directive, and high-signal. + +## Mission + +- Preserve platform behavior and stability. +- Prefer small, reversible changes over broad rewrites. +- Keep behavior unchanged unless a request explicitly asks for behavior changes. + +## Working Scope + +- Primary source code: `components/`, `main/`, `test/`. +- Build and release scripts: `build-scripts/`, `docker/`, `.github/workflows/`. +- Documentation: `documentation/`. + +## Engineering Priorities + +1. Correctness and safety. +2. Backward-compatible behavior. +3. Maintainability and clarity. +4. Build/test reproducibility. +5. Performance and footprint optimization. + +Optimize in this order unless the task explicitly changes priorities. + +## Do Not Edit By Default + +- Vendor/external code: + - `components/esp-dsp/` + - `components/spotify/cspot/` + - `components/telnet/libtelnet/` + - `managed_components/` +- Build outputs and generated artifacts: + - `build/` + - `components/wifi-manager/webapp/dist/` + +Only modify these paths when the task explicitly requires it. + +## Behavioral Invariants + +- Treat OTA/recovery reliability as a product-level contract. +- Treat partition layout as a fixed contract unless explicitly requested otherwise. +- Do not introduce platform-specific compile-time branches when runtime/config solutions are sufficient. +- Avoid unrelated churn; keep patches tightly scoped to the request. + +## Standard Workflow + +1. Read the request and identify touched modules and contracts. +2. Inspect current code and existing patterns in those modules. +3. Implement the smallest coherent patch that preserves contracts. +4. Run targeted validation commands. +5. Report changes, validation evidence, and residual risks. + +## Validation Commands + +- Style check: `build-scripts/lint_style.sh check` +- Style normalize: `build-scripts/lint_style.sh format` +- Firmware build (example): `idf.py build` +- Recovery footprint (platform-specific): `build-scripts/build_recovery_size.sh [build-dir]` + +Use targeted checks first; avoid full-matrix builds unless requested. + +## Testing Rule (Grounding) + +- Tests should validate behavior contracts and invariants, not implementation details. +- Prefer module-boundary and regression tests over private-internal assertions. +- Every bug fix should include a regression test at the highest stable boundary possible. +- Use: + - `documentation/TESTING_CHARTER.md` + - `documentation/CONTRACT_TEST_TEMPLATE.md` + - `documentation/HARDWARE_TEST_MATRIX.md` + +## Style Baseline + +- Use repository formatting/lint settings as the source of truth. +- Do not introduce style-only churn outside task scope unless requested. +- During refactors, normalization is acceptable when intentionally planned. + +## Frontend Context (Required) + +- For any UI/API work involving `components/wifi-manager/webapp/` or `components/wifi-manager/http_server_handlers.c`, read: + - `documentation/agents/frontend_requirements_context.md` +- Treat that file as requirements-first context and keep it current. +- When frontend payload, routes, or protobuf contract changes, run: + - `build-scripts/ui_footprint_snapshot.sh` +- Then update `documentation/agents/frontend_requirements_context.md` in the same change. + +## Agent Docs Layout + +- Keep this file concise and policy-focused. +- Put detailed playbooks under `documentation/agents/`. +- Add module-specific guidance in local `AGENTS.md` files when needed (closest file to changed code takes precedence). diff --git a/Dockerfile b/Dockerfile index dbde118c..c7c7995e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ + clang-format \ curl \ git \ git-lfs \ diff --git a/build-scripts/lint_style.sh b/build-scripts/lint_style.sh new file mode 100644 index 00000000..e094ccdf --- /dev/null +++ b/build-scripts/lint_style.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: build-scripts/lint_style.sh [check|format] + +Commands: + check Verify C/C++ style with clang-format (default) + format Apply clang-format in place +EOF +} + +MODE="${1:-check}" +if [[ "$MODE" != "check" && "$MODE" != "format" ]]; then + usage + exit 2 +fi + +if ! command -v clang-format >/dev/null 2>&1; then + echo "clang-format is required but was not found in PATH." >&2 + exit 127 +fi + +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +declare -a PATHSPECS=( + "*.c" + "*.h" + "*.cc" + "*.cpp" + "*.hpp" + ":(exclude)build/**" + ":(exclude)managed_components/**" + ":(exclude)components/codecs/inc/**" + ":(exclude)components/esp-dsp/**" + ":(exclude)components/esp_http_server/**" + ":(exclude)components/spotify/cspot/**" + ":(exclude)components/squeezelite/**" + ":(exclude)components/telnet/libtelnet/**" + ":(exclude)components/tjpgd/**" + ":(exclude)components/wifi-manager/webapp/dist/**" + ":(exclude)components/wifi-manager/webapp/src/js/proto/**" +) + +mapfile -t FILES < <(git ls-files -- "${PATHSPECS[@]}" | grep -E '^(components|main|test)/.*\.(c|h|cc|cpp|hpp)$' || true) + +if [[ ${#FILES[@]} -eq 0 ]]; then + echo "No C/C++ files matched lint scope." + exit 0 +fi + +if [[ "$MODE" == "format" ]]; then + printf '%s\0' "${FILES[@]}" | xargs -0 -r clang-format -i + echo "Formatted ${#FILES[@]} file(s)." + exit 0 +fi + +declare -a FAILING=() +for file in "${FILES[@]}"; do + if ! clang-format --dry-run --Werror "$file" >/dev/null 2>&1; then + FAILING+=("$file") + fi +done + +if [[ ${#FAILING[@]} -gt 0 ]]; then + echo "Style check failed in ${#FAILING[@]} file(s)." + echo "First failing files:" + printf ' - %s\n' "${FAILING[@]:0:30}" + echo "Run: build-scripts/lint_style.sh format" + exit 1 +fi + +echo "Style check passed for ${#FILES[@]} file(s)." diff --git a/build-scripts/ui_footprint_snapshot.sh b/build-scripts/ui_footprint_snapshot.sh new file mode 100644 index 00000000..f03eec00 --- /dev/null +++ b/build-scripts/ui_footprint_snapshot.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +DIST_DIR="$ROOT/components/wifi-manager/webapp/dist" + +if [[ ! -d "$DIST_DIR" ]]; then + echo "dist directory not found: $DIST_DIR" >&2 + echo "Build webapp first (e.g. npm run build in components/wifi-manager/webapp)." >&2 + exit 1 +fi + +python - "$DIST_DIR" <<'PY' +import os +import sys +import glob +from datetime import datetime, timezone + +dist = sys.argv[1] + +def rel(p): + return os.path.relpath(p, start=os.getcwd()) + +all_files = [p for p in glob.glob(os.path.join(dist, "**", "*"), recursive=True) if os.path.isfile(p)] +ship_files = [p for p in all_files if p.endswith(".gz") or p.endswith(".png")] + +all_files_sorted = sorted([(os.path.getsize(p), p) for p in all_files], reverse=True) +ship_sorted = sorted([(os.path.getsize(p), p) for p in ship_files], reverse=True) + +print(f"Snapshot UTC: {datetime.now(timezone.utc).isoformat(timespec='seconds')}") +print(f"Dist dir: {rel(dist)}") +print("") +print(f"All dist files: {len(all_files)}") +print(f"All dist bytes: {sum(s for s, _ in all_files_sorted)}") +print(f"Shipped files (*.gz + *.png): {len(ship_sorted)}") +print(f"Shipped bytes: {sum(s for s, _ in ship_sorted)}") +print("") +print("Top 15 dist files:") +for size, path in all_files_sorted[:15]: + print(f"{size:9d} {rel(path)}") +print("") +print("Shipped files:") +for size, path in ship_sorted: + print(f"{size:9d} {rel(path)}") +PY diff --git a/components/wifi-manager/webapp/AGENTS.md b/components/wifi-manager/webapp/AGENTS.md new file mode 100644 index 00000000..45c2e7ec --- /dev/null +++ b/components/wifi-manager/webapp/AGENTS.md @@ -0,0 +1,21 @@ +# AGENTS.md (Web UI) + +Local guidance for work under `components/wifi-manager/webapp/`. + +## Required Context + +- Read `documentation/agents/frontend_requirements_context.md` before making UI/API changes. +- Treat that document as the active requirements context (not just historical notes). + +## Scope Rules + +- Prioritize behavior and contract clarity over framework churn. +- Do not edit `dist/` files manually. +- Keep generated protobuf artifacts and API usage aligned with the active request/response contract. + +## Maintenance Rules + +- If routes, payload contracts, or shipped web asset sizes change: + 1. Run `build-scripts/ui_footprint_snapshot.sh`. + 2. Update `documentation/agents/frontend_requirements_context.md`. + 3. Add/update the decision log entry in that file. diff --git a/documentation/CONTRACT_TEST_TEMPLATE.md b/documentation/CONTRACT_TEST_TEMPLATE.md new file mode 100644 index 00000000..f8eee02c --- /dev/null +++ b/documentation/CONTRACT_TEST_TEMPLATE.md @@ -0,0 +1,44 @@ +# Contract Test Template + +Use this template when adding/refactoring tests to ensure they are refactor-resilient. + +## Metadata + +- Test ID: +- Module/Boundary: +- Priority (`P0`/`P1`/`P2`): +- Platform scope (`host`, `all`, `i2s`, `muse`, `squeezeamp`, etc.): + +## Contract + +Describe what externally visible behavior must remain true. + +## Invariant(s) + +List invariant properties this test protects. + +## Inputs/Preconditions + +- Required setup: +- Input variants: +- Fault conditions (if any): + +## Expected Behavior + +- Success conditions: +- Error conditions: +- Bounds/time/resource expectations: + +## Non-goals + +State what this test intentionally does not verify (to avoid implementation coupling). + +## Refactor Resilience Check + +- Would this still pass if internals were reorganized but behavior unchanged? (`yes`/`no`) +- If `no`, explain why coupling is unavoidable. + +## Regression Linkage + +- Related issue/bug (if regression test): +- Why this boundary was chosen: diff --git a/documentation/HARDWARE_TEST_MATRIX.md b/documentation/HARDWARE_TEST_MATRIX.md new file mode 100644 index 00000000..a6bd33d5 --- /dev/null +++ b/documentation/HARDWARE_TEST_MATRIX.md @@ -0,0 +1,130 @@ +# Hardware Test Matrix + +Date: 2026-02-12 +Scope: Platform-specific stability and release validation for `squeezelite-esp32`. + +## Purpose + +Define a practical hardware-focused test strategy that complements host/unit tests and acknowledges embedded uncertainty under real-world load. + +This matrix is designed to: +- maximize platform stability +- prioritize high-impact failures first +- provide clear release gates per hardware profile + +Use together with: +- `documentation/TESTING_CHARTER.md` +- `documentation/CONTRACT_TEST_TEMPLATE.md` + +## Platforms + +Mapped to build profiles used in this repo: +- `i2s` -> `build-scripts/I2S-4MFlash-sdkconfig.defaults` +- `muse` -> `build-scripts/Muse-sdkconfig.defaults` +- `squeezeamp` -> `build-scripts/SqueezeAmp-sdkconfig.defaults` + +## Priority Levels + +- `P0`: release blocking, must pass +- `P1`: strong confidence tests, expected to pass before release +- `P2`: extended coverage/soak, required for nightly and milestone validation + +## Execution Tiers + +- PR smoke: fast subset (`P0` only, selected `P1`) +- Nightly: full `P0 + P1` and selected `P2` +- Pre-release: full matrix, all `P0/P1`, mandatory `P2` soak and fault-injection + +## Core Hardware Matrix + +| ID | Area | Test Path | Priority | Platforms | Pass Criteria | +|---|---|---|---|---|---| +| HW-BOOT-001 | Boot | Cold boot to operational state | P0 | all | boots successfully within target time budget; no panic/reset loop | +| HW-BOOT-002 | Boot | Warm reboot (`esp_restart`) loop x50 | P0 | all | no stuck boot, no boot partition confusion, stable counters | +| HW-BOOT-003 | Boot identity | Platform profile and key GPIO map sanity | P0 | all | expected `FW_PLATFORM_NAME`, expected peripheral init paths | +| HW-STOR-001 | Storage/NVS | NVS read/write/reset cycle | P0 | all | persisted values survive reboot, reset restores defaults | +| HW-STOR-002 | Storage/NVS | Corrupt/partial NVS handling | P1 | all | graceful recovery path, no crash, defaults restored or fallback used | +| HW-STOR-003 | SPIFFS | SPIFFS mount + defaults file read | P0 | all | mount succeeds, required files loaded, clear error if missing | +| HW-NET-001 | Wi-Fi | AP connect + DHCP + DNS | P0 | all | connected state reached, IP assigned, DNS query success | +| HW-NET-002 | Wi-Fi resilience | AP loss/recovery reconnect | P0 | all | reconnect within SLA, no task deadlock or watchdog | +| HW-NET-003 | Service discovery | mDNS announce/discover | P1 | all | service visible on LAN; no crash if mDNS unavailable | +| HW-NET-004 | Ethernet | Link up/down + DHCP + traffic | P1 | ethernet-capable | state transitions handled cleanly; no memory growth trend | +| HW-AUD-001 | Audio output | Start/stop playback, no signal path errors | P0 | all | successful init and playback lifecycle, no panic | +| HW-AUD-002 | Audio behavior | Format/rate transitions during playback | P1 | all | transitions without hard fault; bounded glitch behavior | +| HW-AUD-003 | Audio resilience | Underrun/rebuffer recovery | P1 | all | playback recovers, no runaway CPU or deadlock | +| HW-AUD-004 | Controls | Volume/mute/jack/speaker path checks | P1 | platform-specific | control actions reflected correctly in output state | +| HW-UI-001 | Inputs | Button/rotary/IR event mapping | P1 | platform-specific | expected commands dispatched; no ghost/repeat storm | +| HW-UI-002 | Display | Display init + update loop | P1 | display-capable | stable render/update path, no crash under repeated updates | +| HW-PWR-001 | Battery/charger | Battery telemetry and status logic | P1 | battery-capable | values in expected range; no invalid-state loop | +| HW-BT-001 | Bluetooth | Pair/connect/disconnect cycles | P1 | bt-enabled | stable lifecycle across repeated cycles | +| HW-BT-002 | Bluetooth resilience | BT stack restart/recovery | P2 | bt-enabled | stack recovers without reboot or hard fault | +| HW-OTA-001 | OTA | Happy-path OTA update | P0 | all | new image boots and reports expected version | +| HW-OTA-002 | OTA failure | Interrupted OTA (network/power cut) | P0 | all | safe fallback/rollback path works; device recoverable | +| HW-OTA-003 | Recovery partition | Recovery boot and exit path | P0 | all | recovery mode entry/exit consistent and deterministic | +| HW-PWRF-001 | Power fault | Brownout/power-cut during write/OTA | P0 | all | no bricking; deterministic recovery behavior | +| HW-SOAK-001 | Soak | 12h playback + periodic reconnect | P2 | all | no crash/reset; memory trend within threshold | +| HW-SOAK-002 | Soak | 24h mixed load (stream/control/network churn) | P2 | all | no regressions in responsiveness and stability | + +## Platform-Specific Focus + +## `i2s` (generic baseline) + +- Emphasize: + - audio path stability under varied sample rates + - generic GPIO/peripheral defaults + - OTA + recovery baseline behavior +- Required: + - all `P0` + - `HW-AUD-002`, `HW-AUD-003`, `HW-SOAK-001` + +## `muse` (portable/battery-centric) + +- Emphasize: + - battery telemetry, charger events, low-power edge conditions + - UI/display responsiveness under battery/network churn + - Wi-Fi reconnect after sleep-like or low-power transitions +- Required: + - all `P0` + - `HW-PWR-001`, `HW-UI-001`, `HW-UI-002`, `HW-SOAK-001` + +## `squeezeamp` (amp/control-centric) + +- Emphasize: + - amplifier-related controls, output routing, jack/speaker transitions + - thermal/long-play stability proxies (extended playback) + - network resilience during active playback +- Required: + - all `P0` + - `HW-AUD-004`, `HW-NET-002`, `HW-SOAK-001`, `HW-SOAK-002` + +## Release Gates + +A release is eligible only if: +- every target platform passes all `P0` tests +- no unresolved regression in previously passing `P1` tests +- at least one `P2` soak run per platform completed within release window +- OTA/recovery tests (`HW-OTA-001/002/003`) pass on all platforms + +## Observability Requirements During Tests + +Collect at minimum: +- reset reason, panic reason, boot count, recovery boot count +- heap free/minimum and largest block trend +- task stack high-water marks for critical tasks +- network reconnect counters and durations +- OTA stage/result events + +A test can be marked "pass with warning" only for non-`P0` tests and only with a filed follow-up issue and owner. + +## Failure Triage Rules + +- `P0` fail: immediate release block +- repeated `P1` fail on same platform: treat as release risk, escalate to block unless waived +- any soak fail with crash/reset: open defect with logs and reproduction steps before next release candidate + +## Suggested First Implementation Wave + +1. Automate all `P0` checks for each platform profile. +2. Add nightly `P1` network/audio/UI subset. +3. Add 12h soak (`HW-SOAK-001`) with telemetry capture and trend thresholds. +4. Expand to full pre-release matrix once stable signal quality is established. diff --git a/documentation/TESTING_CHARTER.md b/documentation/TESTING_CHARTER.md new file mode 100644 index 00000000..453e3389 --- /dev/null +++ b/documentation/TESTING_CHARTER.md @@ -0,0 +1,64 @@ +# Testing Charter + +Date: 2026-02-12 +Scope: `squeezelite-esp32` test design and review policy. + +## Objective + +Keep tests stable across refactors by validating behavior contracts and invariants, not implementation details. + +## Grounding Rule + +Tests must verify behavior that is expected to remain true after refactoring. + +Tests should be rejected in review if they mainly assert internals that can change without changing user-visible or system-visible behavior. + +## Contract-First Principles + +1. Test module boundaries +- public function behavior +- state machine transitions +- error semantics +- resource/timing bounds where relevant + +2. Prefer invariants over internals +- "must never crash on malformed input" +- "illegal state transition is rejected" +- "retry is bounded" +- "idempotent operation remains safe when repeated" + +3. Avoid implementation coupling +- do not assert private helper call order unless behavior-critical +- do not assert exact logs/strings unless contractually required +- do not assert private data layout + +4. Fixes require regression tests +- every production bug fix should add a regression test at the highest stable boundary that catches it + +5. Embedded + host split +- host-native tests for deterministic logic +- hardware/HIL tests for timing, drivers, memory layout, OTA/recovery, power fault behavior + +## Review Gate + +Reviewers should ask: + +1. What contract does this test protect? +2. Would this test still pass after a clean internal refactor with identical behavior? +3. Does this test provide failure signal that matters to users/platform stability? + +If answers are weak, request a contract-level test instead. + +## Minimum PR Expectations + +- New behavior: at least one contract-level test +- Bug fix: at least one regression test +- Refactor-only PR: existing contract tests still pass, no new implementation-coupled tests added + +## Mapping to Hardware Matrix + +Use this charter with: +- `documentation/HARDWARE_TEST_MATRIX.md` + +The charter governs *how* tests are written. +The matrix governs *where/what* is validated per hardware platform. diff --git a/documentation/agents/README.md b/documentation/agents/README.md new file mode 100644 index 00000000..4b6a41c3 --- /dev/null +++ b/documentation/agents/README.md @@ -0,0 +1,20 @@ +# Agent Documentation + +Store long-form guidance for coding agents here. + +## Suggested Files + +- `documentation/agents/architecture.md`: subsystem boundaries, data flow, invariants. +- `documentation/agents/build-and-test.md`: canonical commands, fast checks, CI mapping. +- `documentation/agents/style-and-lint.md`: formatting policy, lint severity, suppression rules. +- `documentation/agents/refactor-playbook.md`: safe refactor steps, rollout strategy, risk controls. +- `documentation/agents/frontend_requirements_context.md`: requirements-first UI context, size budgets, and migration constraints. +- `documentation/agents/module-notes/.md`: module-level constraints and edge cases. + +## Writing Rules + +- Keep files operational and concrete. +- Prefer checklists and explicit commands. +- Record known pitfalls and non-obvious invariants. +- Link back from `AGENTS.md` only to docs that are actively maintained. +- When frontend payload/routes/contracts change, refresh the snapshot with `build-scripts/ui_footprint_snapshot.sh` and update `frontend_requirements_context.md`. diff --git a/documentation/agents/frontend_requirements_context.md b/documentation/agents/frontend_requirements_context.md new file mode 100644 index 00000000..8311d881 --- /dev/null +++ b/documentation/agents/frontend_requirements_context.md @@ -0,0 +1,201 @@ +# Frontend Requirements Context (Living) + +Status: Active +Last reviewed: 2026-02-12 +Review scope: `components/wifi-manager/webapp`, `components/wifi-manager/http_server_handlers.c`, `components/wifi-manager/wifi_manager_http_server.c`, `spiffs_src/CMakeLists.txt` + +## Purpose + +This document captures reverse-engineered **product requirements** for the embedded web UI, with measurable constraints. +It is intentionally not an implementation walkthrough. + +Use this as session handoff context for UI refactoring and footprint control. + +## Snapshot Findings (Current State) + +### 1. What actually ships to firmware + +SPIFFS packaging currently copies only: + +- `components/wifi-manager/webapp/dist/*.gz` +- `components/wifi-manager/webapp/dist/*.png` + +Evidence: `spiffs_src/CMakeLists.txt`. + +Current shipped web payload: + +- Total shipped bytes: `219741` (`~214.6 KiB`, `~0.21 MiB`) +- Largest asset: `dist/js/node_vendors.bundle.js.gz` (`166729` bytes) + +### 2. What looks large but is not currently shipped + +- `dist/` total is about `4.5M`, dominated by source maps (`*.map`). +- Source maps are large in repo artifacts, but not copied to SPIFFS by current packaging. + +### 3. Major technical debt signal + +- UI source references many legacy endpoints (`/status.zzz`, `/messages.zzz`, `/commands.zzz`, etc.). +- Active server registration in `wifi_manager_http_server.c` is centered on `/data.bin` + static handlers; many legacy handlers are commented out. + +This indicates API/contract drift and unclear migration state. + +### 4. Modularity and proto usage signal + +- Main UI logic is largely monolithic in `src/js/custom.ts` (~2.5k LOC). +- `index.ts` imports generated protobuf bundle (`configuration_pb.js`) up front. +- `dist/js/index.bundle.js` includes many generated proto message definitions, not only request/response minimum set. + +Implication: upfront JS parse/execute and bundle weight are likely higher than needed. + +## Reverse-Engineered Product Requirements + +These are the requirements we should preserve while refactoring. + +### RQ-UI-001 Captive Portal Bootstrap + +The UI must reliably open and render from AP/captive-portal entry points with no manual URL knowledge. + +Acceptance: + +- Landing page reachable from captive portal probes. +- First meaningful controls visible without requiring cloud services. + +### RQ-UI-002 Network Onboarding and Recovery + +Users must be able to scan APs, join/disconnect Wi-Fi, and understand current link mode (Wi-Fi vs Ethernet) with clear status. + +Acceptance: + +- Scan, connect, disconnect workflows remain available and understandable. +- Connection state transitions are visible and unambiguous. + +### RQ-UI-003 Runtime Observability + +Users must see operational status and logs needed for diagnosis (network, playback, OTA progress, errors). + +Acceptance: + +- Status view remains available in normal and recovery mode. +- Error and warning paths are visible in UI without dev tools. + +### RQ-UI-004 Safe Configuration Editing + +Users must be able to view/edit/apply configuration with guardrails (validation feedback, clear commit/apply behavior). + +Acceptance: + +- Config edit path supports both selective and global update actions. +- Validation errors are explicit before/after submit. + +### RQ-UI-005 OTA Operations + +Users must be able to trigger firmware update by URL or upload, with progress and failure feedback. + +Acceptance: + +- OTA action always yields deterministic status progression or explicit terminal error. +- Recovery path remains accessible if normal OTA path fails. + +### RQ-UI-006 Advanced Control Access + +Power-user controls (commands/NVS/diagnostics) must exist but should not degrade common-task UX. + +Acceptance: + +- Advanced functions remain accessible. +- Main tasks are not blocked by advanced UI complexity. + +### RQ-UI-007 Backward Compatibility Envelope + +The UI must support currently deployed firmware API variants during migration (legacy and protobuf-era endpoints), with explicit deprecation strategy. + +Acceptance: + +- Contract compatibility matrix is documented and tested. +- Legacy paths have clear sunset criteria. + +### RQ-UI-008 Embedded Footprint Discipline + +Web assets must stay within explicit firmware budget and be measurable in CI. + +Acceptance: + +- Shipping budget exists and is enforced. +- Size regressions fail checks unless explicitly approved. + +## Non-Functional Budgets (Proposed) + +These are targets, not yet enforced: + +- `NB-001` Shipped web payload (`*.gz` + `*.png` in `dist/`) <= `180 KiB` target, `220 KiB` hard cap. +- `NB-002` Main JS gzip <= `120 KiB` target. +- `NB-003` Vendor JS gzip <= `120 KiB` target. +- `NB-004` No runtime dependency on external CDNs for critical UI function. +- `NB-005` One canonical API transport for new features (`/data.bin` protobuf), with compatibility shim for legacy endpoints during migration only. + +## UX + Footprint Improvement Directions + +Prioritized by impact/risk. + +### P1 Contract Unification (High impact, medium risk) + +- Define one frontend API client contract around protobuf request/response. +- Treat legacy `.zzz` paths as compatibility layer, not primary path. +- Add explicit compatibility table (firmware version -> supported endpoints). + +### P2 Modularization by Capability (High impact, medium risk) + +- Split monolithic UI logic into capability modules: + - onboarding + - status/logs + - config editor + - OTA + - advanced +- Lazy-load non-critical modules (advanced, release browser, heavy editors). + +Expected result: lower startup JS cost and clearer ownership boundaries. + +### P3 Proto Surface Minimization (High impact, medium risk) + +- Stop importing broad generated config surface on initial load. +- Create thin protobuf DTO layer for startup-critical flows. +- Load heavy schema modules only when entering related screens. + +### P4 UI Responsiveness and Clarity (Medium impact, low risk) + +- Replace mixed polling patterns with a single scheduler and explicit backoff policy. +- Standardize in-progress/error/success state rendering across all actions. +- Reduce ambiguous icon-only cues with text fallback in constrained/offline contexts. + +### P5 Build Output Hygiene (Medium impact, low risk) + +- Keep generating sourcemaps for dev/debug if needed, but separate from shipping artifact workflow. +- Enforce shipped-size budget on what SPIFFS actually receives, not raw `dist/`. + +## Living Maintenance Protocol + +This file must be updated when any of the following changes: + +- web routes/endpoints +- protobuf request/response schema used by UI +- shipped asset packaging rules +- significant UI feature flow + +Required update steps: + +1. Run `build-scripts/ui_footprint_snapshot.sh`. +2. Update Snapshot Findings and budget deltas in this file. +3. Update compatibility notes if API contract changed. +4. Add an entry to Decision Log. + +## Decision Log + +| Date | Decision | Why | Follow-up | +|------|----------|-----|-----------| +| 2026-02-12 | Treat this file as requirements-first context, not implementation notes | Reduce drift and speed up session handoffs | Add CI size gate and API contract checks | + +## Open Questions + +- Are legacy `.zzz` endpoints still required for deployed firmware versions, and for which versions exactly? +- Is the intended long-term model pure protobuf over `/data.bin` for all dynamic data? +- What startup latency target is acceptable on AP-mode captive portal connections?