This commit is contained in:
Mateusz Gruszczyński committed 2026-10-10 21:48:39 +02:00
1 parent 76b8bb7ee2
commit 24af7478b8
29 files changed
+321 -171

No files matched your search

+1 -53
View File
@@ -1,30 +1,12 @@
# Historical developer-level changelog archive
> Archived during the 0.25 documentation cleanup. This file preserves the pre-cleanup
> detailed release record, including compiler fixes, source refactors, regression notes and
> detailed release record, including source refactors, regression notes and
> packaging details. It is historical technical evidence, not the operator-facing changelog.
> Use [`../CHANGELOG.md`](../CHANGELOG.md) for user/operator-visible release changes.
# Changelog
## 0.31.0 Axum fallback handler E0277 source correction
- Correct the reported `Handler<_, _>` error at `src/proxy/types.rs:53` after the public cache-maintenance path was added. The asynchronous `cache_http_invalidation` helper previously accepted `&Request<Body>` while awaiting database audit and cache invalidation; Axum's body is not `Sync`, so the borrowed request can make the `forward` handler future non-`Send`.
- Pass ownership of `Request<Body>` to the maintenance helper, perform the same body/header/allowlist validation, copy the HTTP method for the audit entry, and release the request body before the first asynchronous operation. The `forward` router and public API semantics are unchanged.
- Add an offline source regression asserting that the async helper owns its request. Pre-Rust `./dev.sh test` checks and Go tests pass; full Rust parsing, type checking, tests and runtime maintenance calls cannot be validated without Cargo/Rustc.
## 0.30.1 same-version sysinfo compiler feature correction
- Fix four compiler `E0433` errors in the system information endpoint by enabling `sysinfo`'s `system` feature alongside `network` with `default-features = false` unchanged. No dependency version or API change.
- Remove unused Tokio `AsyncReadExt` imported by the ACME runtime, and add the explicit manifest feature check to the offline source regressions.
- The change addresses the supplied Rust compiler log. A full Rust build and multi-OS runtime check remain outstanding in the source packaging environment.
## 0.29.7 same-version `E0308` source correction
- Defer Reverse Proxy error interception from the upstream `Response<hyper::body::Incoming>` to the adapted `Response<axum::body::Body>` in `success_prepare.rs`. This preserves the H2 WebSocket tunnel's `Incoming` body type and ensures replacement still precedes response transformation and cache admission. Reapply response rules and enforce `no-store` for replaced application errors.
- Change health supervisor member collection to own `Arc<Member>` and spawn probes with `Arc::clone(member)`, meeting Tokio's owned `'static` task requirement.
- Extend the application-error UI/source regression check with the body-adaptation ordering, no-store and Arc ownership assertions. Offline checks passed; Rust compilation is not available in the packaging environment.
## 0.26.7
### Backend client lifecycle and cache diagnostics
@@ -39,29 +21,8 @@
- Build the OCI runtime image with a dedicated Certbot virtual environment containing the Cloudflare, OVH and RFC2136 DNS plugins supported by RustProxy, all pinned with the same Certbot release.
- Refresh container documentation so it no longer tells operators that those provider plugins must be added separately.
## 0.26.6
### Compiler follow-up
- Removed the redundant `country_resolved = true` assignment after route-level GeoIP resolution in `proxy::forward_entry`. The flag is only needed to prevent a second lookup before that branch; the removed write was never read and produced `unused_assignments` during a real Rust build. ACL/GeoIP behavior is unchanged.
- A fresh compiler-enabled lint/test run remains required on the Rust release host because this packaging workspace has no Rust toolchain.
## 0.26.0
### Compiler follow-up
- Corrected the external `cargo` build failure in `server::tls_runtime`: the HTTP/1 service closure now captures its own cloned `frontend_state`, leaving the original state available for connection-level protocol-error logging after `serve_connection` returns.
- Corrected `serde_json::Map::entry` type inference in the stored-log normalizer by passing the literal `"event"` directly instead of the ambiguous `"event".into()`.
- Rust-independent language, security, source-module, documentation, UI, source and release-smoke gates pass after both corrections. A fresh compiler-enabled `cargo check/test` remains required because this packaging workspace has no Rust toolchain.
## 0.25.1
### 0.25.1 source-split compiler follow-up
- Corrected Rust `include!` split regressions reported by a real `cargo` build: dangling method documentation in runtime fragments, an invalid inner module doc in `balance/model.rs`, and the missing sibling `htaccess` import in Web Server response headers.
- Removed the warning set reported by that build (stale imports, unnecessary `mut` bindings, an unnecessary returned timeout value, and unused Prometheus parameters).
- Strengthened `rust_module_contracts.py` so doc comments/attributes immediately before a closing brace and inner `//!` comments in included fragments fail preflight instead of producing a false PASS.
### Source modularization and release guards
- Enforce a 500-line ceiling for Rust implementation/test source files and bounded dashboard JavaScript, Docker Agent Go and operational deployment/migration modules.
@@ -79,7 +40,6 @@
- Enforce document-root confinement for Web Server paths, reject ambiguous/encoded traversal forms and prevent symlink escape by not following symlinks during Unix filesystem traversal.
- Reuse the existing vhost ACL/authentication/header/error/TLS/HTTP pipeline for local files without adding a separate router, error-page system or object cache.
- Add an authenticated **Docs Reader** for bundled project documentation alongside the existing API/OpenAPI view, and separate the current documentation index, release audit and open TODO material.
- Fix the 0.25 Docs Reader source embedding so compilation does not require a generated `OUT_DIR/docs-content.json`; Markdown sources are now direct compiler inputs and the JSON bundle is serialized from embedded strings.
- Split the 0.25 Web Server implementation from the former monolithic `src/web_server.rs` into the responsibility-based `src/web_server/` module (`config`, filesystem, security, response, routing, FastCGI, dispatcher, `.htaccess` and tests), keeping `mod.rs` as the stable public facade and updating source/package gates for the new layout.
- Extend the source-layout cleanup across the Rust tree: every checked-in `.rs` file is capped at 500 lines, large `config`/`docker`/`server`/`runtime`/`balance`/`prometheus`/`proxy`/cache/admin/etc. modules are split by responsibility, unit-test bodies live in separate test files, and compiler-independent contracts reject dangling attributes/doc comments, broken `include!` targets and unbalanced split fragments.
@@ -96,8 +56,6 @@
- Added composite Settings > Logging support for `error_log.enabled`, path and format while retaining legacy `/api/config/logging` payload compatibility.
- Removed the Error Log help sentence from the panel while keeping the existing controls and behavior unchanged.
- Fix Rust type inference for zeroizing ACME/Base64 and AEAD buffers so the new Secret Store and encrypted backup code compile with the pinned dependency set.
- Fix the secret-redaction KV regex literal so 0.24.9 compiles correctly; the redaction behavior itself is unchanged.
- Harden the built-in release checker for FreeBSD/OPNsense and other Unix platforms: supplement bundled Mozilla roots with common system CA bundles/`SSL_CERT_FILE`/`CURL_CA_BUNDLE`, follow bounded non-downgrading HTTP(S) redirects, and report failed checks as normal application `ERROR` events without changing dashboard behavior.
- Add a central AES-256-GCM encrypted Secret Store with private local master-key storage and migration of existing ACME DNS-provider credentials.
- Add encrypted full backup/restore with Argon2id, authenticated encryption, explicit manifests/checksums, staging validation and rollback-safe activation.
@@ -119,8 +77,6 @@
- Show transform-resource warnings when the selected budget cannot fit the worst-case buffers for one maximum compressed body or exceeds half of detected host/cgroup RAM.
- Add live dashboard resource accounting: current transform RAM reservation/budget and active slots, HOT/transient cache RAM, non-cache process RSS, plus COLD disk occupancy and object limits.
- Include global transform representation/eligibility settings in cache compatibility identity while excluding resource-only preset/budget/concurrency controls.
- Map bounded transform-body collection failures explicitly into `anyhow::Error`, avoiding the `anyhow::Context` trait-bound compile failure for Axum/HTTP body boxed errors.
- Clean compiler warnings in cache transient leases and CSS minification without suppressing lints: make RAII-only lease ownership explicit, keep COLD read reservations intentionally alive, and remove dead CSS state assignments.
- Extend SQLite/OpenAPI/offline memory/release regressions and update release identity to 0.24.7.
## 0.24.6
@@ -172,7 +128,6 @@
## 0.24.1
- Restore system network interface discovery and RX/TX counters after the shared-statistics refactor: use the cross-platform `sysinfo::Networks` API as the primary network inventory/counter source, merge `systemstat` as a library-level supplement/fallback for missing or partial inventory, preserve per-interface rate calculation, and stop silently truncating the interface list.
- Fix the compiler error in portable uptime collection by using the `systemstat` duration API correctly, and remove the now-unused `Future` import in the H2 server module.
- Remove RustProxy's artificial hostname-count ceilings from certificate SNI bindings, local certificate generation/import validation, ACME domain validation, vhost/redirect/404 hostnames, administration allowed hosts, host ACL policies and Docker aliases. Input validity and bounded control-plane/resource limits remain enforced.
- Replace per-BSD command/parsing statistics code with shared library collectors: `systemstat` for host metrics, `sysinfo::Networks` for network inventory/counters, and `metrics-process` for process metrics, using the documented dummy process fallback on targets without a native collector.
- Preserve Linux `/proc` and cgroup-specific details as additive compatibility metrics while using the common library API for portable host/process values.
@@ -223,7 +178,6 @@
- Bound inactive inbound HTTP/2 connections to five minutes so idle TLS/H2 sessions cannot accumulate indefinitely toward the process connection limit; export `rust_proxy_http2_active_connections` for direct diagnosis.
- Account hot-cache response-body backing allocation capacity rather than only visible body length, preventing `BytesMut` growth slack from bypassing the configured RAM budget and under-reporting resident cache memory.
- Fix the `--livelogs` stdin reader to send owned `String` values from the match body rather than a pattern guard, resolving Rust `E0507` during binary compilation.
- Add a scoped external Certificate API credential managed from a new **Settings → Certificate API** tab. RustProxy stores only the token hash plus a non-secret Key ID; the cleartext token is returned only on rotation.
- Allow the scoped Key ID + Bearer Token only on `POST /api/certificates/deploy` and `POST /api/certificates/reload`, require HTTPS for non-loopback token use, track last use, support immediate revoke, and retain normal administrator-session/CSRF access as an alternative.
- Keep production certificate operations available directly from the installed binary (`rust-proxy tls deploy` / `rust-proxy tls reload`) without requiring `dev.sh`.
@@ -243,7 +197,6 @@
## 0.22.2
- Fix the 0.22.2 backend diagnostics build failure by replacing the oversized recursive `serde_json::json!` member payload with explicit `serde_json::Map` construction; the administration JSON schema is unchanged.
- Add bounded replay-safe backend failover retry for body-free GET/HEAD/OPTIONS requests, including retry policy controls, passive-health integration, logs and per-backend retry counters.
- Correct ordinary explicit-HTTP/2 upstream requests and add a real Hyper h2c prior-knowledge regression path.
- Add per-backend request-concurrency limits with bounded capacity waiting, `503` rejection and capacity/queue-timeout metrics. This limits full proxy exchanges, not TCP socket count.
@@ -295,7 +248,6 @@
- Fix Docker-vhost Passive Backend Health checkbox sizing so enabling the expanded editor cannot make fields overlap.
- Remove the unused mutable `origin` binding from the background revalidation path.
- Replace the monolithic `src/cache.rs` with the responsibility-based `src/cache/` module (`model`, lifecycle, cold storage, dashboard, tickets, lookup, policy, fill and tests) and update source/package gates for the new layout.
- Fix the cache split so method groups are emitted from module-scope `include!` files wrapped in their own `impl Cache` blocks; this avoids the Rust compiler error caused by `include!` directly in impl-item position.
- Persist transformed-representation metadata through hot/Cold Cache, mark transformed objects in the Cache dashboard, and append `transformed` to standardized RFC 9211 `Cache-Status` diagnostics; origin `X-Cache-Status` lookalikes are stripped rather than emitted.
- Shard Cold Cache storage and add bounded asynchronous garbage collection plus selective manual cleanup.
@@ -326,7 +278,6 @@
## 0.19.3
- Fix the compiler error in the release checker body collector by converting Hyper/http-body-util boxed collection errors explicitly into `anyhow::Error` instead of calling `anyhow::Context` on an unsupported boxed error type.
- Simplify the dashboard footer to API latency and dropped-log count, and add a fail-soft built-in release checker using embedded `UPDATE_CHECK.toml` defaults. The checker reads the official Cargo manifest immediately and then hourly, exposes a newer-version link in the footer, and never performs automatic upgrades.
- Allow runtime release-check overrides through `--update-repository-url`, `--update-version-url` and `--update-check-interval-secs` (plus matching `RUST_PROXY_*` environment variables); interval `0` disables checks.
- Fix the remaining oversized Live Logs column checkboxes by excluding checkbox/radio inputs from both broad `.log-tools input` text-field sizing blocks that were overriding the shared 15px checkbox rules.
@@ -367,7 +318,6 @@
- Retain 4096 completed access-log entries while keeping the 256-entry live window; add one-shot on-demand history loading and document the API limit.
- Render backend traffic with KiB/s as the minimum unit, simplify the Live Logs column picker, align generic checkboxes and move Docker-vhost Advanced switches beside Backend protocol on desktop; remove the redundant alias help line.
- Remove stale simulated dashboard screenshots, retain only two explicitly labelled legacy references and add a documentation index for historical/version-labelled material.
- Restore the integration-test sources and independent Argon2id fixture required by release/source checks so compiler-enabled test builds no longer reference files missing from the supplied source archive.
- Update source, UI, OpenAPI, regressions and package identity to 0.19.1.
## 0.19.0
@@ -453,7 +403,6 @@
- Add a separate live **Docker vhosts** Prometheus collector group, enabled by default, with configured agent/assignment/domain gauges plus Docker vhost traffic/status/duration and full backend health/probe/runtime metrics.
- Extend Prometheus/OpenAPI/UI documentation and regression coverage for the Docker metric boundary.
- Extend the bundled Grafana operations dashboard with a dedicated Docker-vhost section and per-agent/service filters using only the separate Docker metric families.
- Fix Docker discovery compilation against the locked HTTP/body stack by mapping limited-body collection errors explicitly and logging `ApiError` via its `Debug` representation.
## 0.18.2
@@ -604,7 +553,6 @@
- Add per-vhost control over forwarding the effective client IP to the application. Disabling it removes `Forwarded`, `X-Forwarded-For` and `X-Real-IP` only on the backend request; ACLs, GeoIP, rate limiting and Tinyauth continue to use the real client identity.
- Add a compact Vhost search/filter for vhost IDs, hostnames/wildcards and backend IDs/URLs.
- Fix the invalid `quinn = 0.11.22` manifest requirement to the resolved `0.11.12` and stop `dev.sh` from rewriting a valid checked-in `Cargo.lock`.
- Fix JSONL access logging under the hardened systemd unit by provisioning `/var/log/rustproxy` as a writable service log directory while keeping `ProtectSystem=strict`.
- Add source/offline regression coverage and bump release/package/UI/OpenAPI/update metadata to 0.11.3. Rust compilation/runtime acceptance remains open in this packaging environment.
+64
View File
@@ -0,0 +1,64 @@
# RustProxy 0.31.1 — mobile dashboard initialization
## Scope
This patch changes only the authenticated administration panel bootstrap and its
release documentation. It does not modify reverse-proxy traffic processing,
authentication, stored configuration, SQLite migrations or preference values.
## Root-cause analysis
Previously, the authenticated dashboard HTML requested 17 independent deferred
JavaScript files. The first one (`/assets/app.js`) starts asynchronous session
and configuration requests from `web/app/runtime.js`. Deferred ordering only
orders *script evaluation*, not asynchronous continuation: a completed API call
can resume while later JavaScript resources are still downloading. In that
window, a render path may access `window.rustProxyIntegrations.bindings`
before `integrations.js` has finished evaluating. The browser then reports an
undefined-object exception. A failed/missing script has the same symptom.
The reported iPhone/reverse-proxy incident is consistent with this race. It was
not possible to capture the user's mobile network trace, so the precise
combination of mobile transport behavior, timing, and content encodings is not
proven.
## Changes
1. The dashboard HTML now requests only `/assets/app.js?v=0.31.1`. On the
backend, the same existing `/assets/app.js` route assembles all existing
UI source modules in their prior load order. `/assets/vhosts.js` and the
other private asset URLs remain available for compatibility.
2. Startup waits for `DOMContentLoaded` and verifies the required modules
before requesting a session or configuration. A missing module produces
a visible startup failure with the existing retry link instead of a
partially initialized workspace.
3. Authenticated HTML also requests `/assets/style.css?v=0.31.1`; versioned
URLs separate release assets in intermediary caches. No cache bypass
or potentially unsafe public caching is introduced on the server.
4. The initial `/api/config` load retries at most twice after a transient
failure (400 ms / 800 ms delays). If it cannot load, the existing startup
recovery UI is displayed. Session rejection (`401`) does not trigger retries.
5. Public login, CSP, compression negotiation, SSE, polling, Traffic and Cache
APIs, user preferences and the proxy request path are unchanged.
## Acceptance checks
- Node.js parses each source module and the complete emitted dashboard bundle.
- A JavaScript startup test verifies **zero API requests before DOM readiness**,
successful activation with complete modules, and recovery when a module is
missing. A second test verifies configuration retry success/failure.
- Chromium smoke test on the authenticated HTML with simulated session and
configuration endpoints, at desktop and mobile widths, when available.
- Run `./dev.sh test` and `./dev.sh check` in an environment with Rust/Cargo.
- On the production reverse-proxy path, test initial load, 20+ rapid reloads,
navigation between tabs, Safari on iPhone (Wi-Fi and cellular), and a
deliberately delayed/failed `/api/config` response. Confirm no uncaught JS
errors and no mixed asset versions.
- Compare actual HTTP `Content-Encoding`, `Content-Length`, `Vary`, `Status`
and response bodies for document, script and stylesheet behind the reverse.
Disable any reverse cache rule that ignores version/query keys.
## Environment limitations
Compiler/type-checking, live RustProxy API tests and Safari/iPhone tests require
the target runtime; they cannot be inferred from source or Chromium alone.
+3 -2
View File
@@ -31,7 +31,8 @@ This is the canonical index for the current project documentation. The authentic
- [0.26.3 implementation note](V0.26.3_IMPLEMENTATION.md) - global interface scaling, chart themes, square draft dock and Settings navigation polish.
- [0.26.2 implementation note](V0.26.2_IMPLEMENTATION.md) - unified Logs navigation, field-query search, dynamic JSON columns and RAW parsing.
- [0.26.1 implementation note](V0.26.1_IMPLEMENTATION.md) - historical vhost-aware protocol logging and Stored Logs UX milestone.
- [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) - current API/panel/BAN/PURGE attack-surface review, hardening, compile fix and mandatory runtime gates.
- [0.31.1 mobile panel bootstrap](IMPLEMENTATION_0.31.1.md) - panel script readiness, versioned assets, failure recovery and acceptance tests.
- [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) - current API/panel/BAN/PURGE attack-surface review, hardening and remaining runtime verification.
- [0.31.0 implementation report](IMPLEMENTATION_0.31.0.md) - consolidated changes, exact file inventory, validation results and outstanding Rust runtime acceptance.
- [TEST_REPORT](TEST_REPORT.md) - verification commands, results and environment limitations.
@@ -80,7 +81,7 @@ This is the canonical index for the current project documentation. The authentic
## Historical or version-labelled context
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md); [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md); [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md) is retained as the previous milestone; [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above.
+9 -1
View File
@@ -1,4 +1,12 @@
# Verification report - RustProxy 0.31.0
# Verification report - RustProxy 0.31.1
## 0.31.1 — mobile/reverse-proxy dashboard startup (2026-10-10)
- **Cause found in source:** dashboard HTML fetched 17 deferred JS resources, while the first bundle launched an async session/config bootstrap without waiting for all later scripts to finish. A fast API response could invoke the renderer before `window.rustProxyIntegrations.bindings` was registered. An interrupted script transfer could cause the same exception. The reported Safari error is consistent with this race, but the specific production network failure was not captured.
- **Implemented:** one ordered embedded JS payload at `/assets/app.js?v=0.31.1`; CSS URL also versioned; DOMContentLoaded/module gating and explicit startup recovery; up to two bounded retries of initial configuration (400/800 ms). Old private asset URLs remain available. No API, authentication, saved preferences, database or proxy data path changes.
- **PASS:** `./dev.sh test` completes all source checks, SQLite migration/backup and upgrade smoke tests, security and language checks, Node UI tests (including new startup-order and retry test), and documentation checks **before Cargo**. `python3 tools/checks/source_module_contracts.py` checks each JS module and the assembled 608 KB bundle with `node --check`. `cd docker-agent && go test ./...` passed. Shell syntax passed.
- **Not verified:** `./dev.sh test` terminates **127** at Cargo; `./dev.sh check` also **127** (`cargo`/`rustc` absent). Browser smoke against a local simulated API was attempted, but Chromium navigation was blocked by sandbox policy (`ERR_BLOCKED_BY_ADMINISTRATOR`); no successful browser smoke run is claimed. Playwright WebKit browser binaries are not installed here. No real iPhone Safari or production reverse-proxy test was possible.
- **Release-host checks:** `./dev.sh check && ./dev.sh test && ./dev.sh release`, followed by Safari/Chrome iPhone tests behind the reverse: 20+ full reloads from Overview/Cache/Settings, initial config API failures, SSE reconnect, browser console errors, and response encoding/version consistency for HTML, `/assets/app.js?v=0.31.1` and CSS. Review the detailed plan in [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md).
## 0.31.0 Axum handler `Send` correction (2026-10-10)