Hex only (0-9, a-f). Each extra character takes ~16× longer to find — long prefixes may take a very long time.
-
+
diff --git a/internal/core/templates/config_regions_edit.html b/internal/core/templates/config_regions_edit.html
index fd785d1..91c6c6f 100644
--- a/internal/core/templates/config_regions_edit.html
+++ b/internal/core/templates/config_regions_edit.html
@@ -129,11 +129,14 @@ function addBlock(kind) {
-
+
-
+
-
+
{{/* Settings/state: the region's own properties, shown as switches (matching
the root's flood switch) so each toggle reads clearly with its label,
diff --git a/internal/core/templates/repeater_docs.html b/internal/core/templates/repeater_docs.html
index ead6ed3..6c7684d 100644
--- a/internal/core/templates/repeater_docs.html
+++ b/internal/core/templates/repeater_docs.html
@@ -16,7 +16,8 @@
Shown on the public page when one is published.
Do not put gate codes, lock combinations, or other site-access secrets here — use the internal section below for those.
-
+
@@ -29,7 +30,8 @@
Sensitive site-access details — gate codes, lock combinations, landlord/contact info, climbing notes.
Only the owner, stewards, and people you've shared with can read this. Never shown on the public page.
-
+
diff --git a/internal/marketing/templates/docs.html b/internal/marketing/templates/docs.html
index e8f3363..dcf0f1a 100644
--- a/internal/marketing/templates/docs.html
+++ b/internal/marketing/templates/docs.html
@@ -25,7 +25,8 @@
Run this on your repeater — through the repeater console in the MeshCore companion app, directly over USB serial, or let MeshTender run it for you during USB setup:
{{if .SetPermCommand}}
-
+
The long value is MeshTender's public key — not your repeater's. If you see a guest-access warning after confirming a repeater, this is the fix: run the command above, then confirm again.
diff --git a/internal/web/security_test.go b/internal/web/security_test.go
index 1e0e631..ac8dbc9 100644
--- a/internal/web/security_test.go
+++ b/internal/web/security_test.go
@@ -1,6 +1,7 @@
package web
import (
+ "bytes"
"net/http"
"net/http/httptest"
"os"
@@ -192,6 +193,94 @@ func TestTemplatesHeadingConventions(t *testing.T) {
}
}
+// controlRe matches a form control's opening tag.
+var controlRe = regexp.MustCompile(`(?is)<(input|select|textarea)\b([^>]*)>`)
+
+// namedRe matches the attributes that give a control an accessible name directly.
+var namedRe = regexp.MustCompile(`(?i)aria-label\s*=|aria-labelledby\s*=|\btitle\s*=`)
+
+// skipTypes are input types with no user-visible value to name.
+var skipTypes = map[string]bool{
+ "hidden": true, "submit": true, "button": true, "image": true, "reset": true,
+}
+
+// TestTemplatesFormControlsAreLabeled enforces that every form control has an
+// accessible name. Without one, a screen reader announces "edit text, blank" and the
+// user has to guess from surrounding prose — assuming they find it at all.
+//
+// A control counts as named by any of:
+// - aria-label / aria-labelledby / title on the control
+// - a