diff --git a/.gitignore b/.gitignore
index b9de9c3..8ea34eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,7 @@ devenv.local.nix
# html-router/assets/style.css
html-router/node_modules
.fastembed_cache/
+
+# insta: pending (unreviewed) snapshots; accepted *.snap files are committed
+*.snap.new
+.insta.bak
diff --git a/Cargo.lock b/Cargo.lock
index bb68cf7..0f6ca8c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1468,6 +1468,17 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "console"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "const-random"
version = "0.1.18"
@@ -2967,6 +2978,7 @@ dependencies = [
"common",
"futures",
"include_dir",
+ "insta",
"json-stream-parser",
"minijinja",
"minijinja-autoreload",
@@ -3342,7 +3354,7 @@ version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
dependencies = [
- "console",
+ "console 0.15.11",
"number_prefix",
"portable-atomic",
"unicode-width 0.2.2",
@@ -3412,6 +3424,19 @@ dependencies = [
"generic-array 0.14.7",
]
+[[package]]
+name = "insta"
+version = "1.47.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
+dependencies = [
+ "console 0.16.3",
+ "once_cell",
+ "regex",
+ "similar",
+ "tempfile",
+]
+
[[package]]
name = "instant"
version = "0.1.13"
@@ -6164,6 +6189,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+[[package]]
+name = "similar"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
+
[[package]]
name = "simple_asn1"
version = "0.6.4"
diff --git a/html-router/Cargo.toml b/html-router/Cargo.toml
index 6eddc96..1d231e6 100644
--- a/html-router/Cargo.toml
+++ b/html-router/Cargo.toml
@@ -43,6 +43,7 @@ json-stream-parser = { path = "../json-stream-parser" }
[dev-dependencies]
common = { path = "../common", features = ["test-utils"] }
+insta = { version = "1.47.2", features = ["filters"] }
tower = "0.5"
[build-dependencies]
diff --git a/html-router/assets/admin-prompt-reset.js b/html-router/assets/admin-prompt-reset.js
new file mode 100644
index 0000000..af6ffde
--- /dev/null
+++ b/html-router/assets/admin-prompt-reset.js
@@ -0,0 +1,19 @@
+/**
+ * Shared "Reset to Default" handler for the admin prompt-edit modals
+ * (templates/admin/edit_*_prompt_modal.html).
+ *
+ * Each reset button carries data-reset-target with a selector for the prompt
+ * textarea to repopulate from the modal's hidden #default_prompt_content.
+ */
+(function () {
+ 'use strict';
+
+ document.body.addEventListener('click', function (e) {
+ const btn = e.target.closest('[data-reset-target]');
+ if (!btn) return;
+ const scope = btn.closest('dialog') || document;
+ const source = scope.querySelector('#default_prompt_content');
+ const target = scope.querySelector(btn.dataset.resetTarget);
+ if (source && target) target.value = source.value;
+ });
+})();
diff --git a/html-router/assets/modal.js b/html-router/assets/modal.js
new file mode 100644
index 0000000..6db1e80
--- /dev/null
+++ b/html-router/assets/modal.js
@@ -0,0 +1,33 @@
+/**
+ * Modal lifecycle for markup injected into #modal (see templates/modal_base.html).
+ *
+ * Uses delegated listeners so we do not rely on inline