diff --git a/Plugin/unraid/Tools/js_check.sh b/Plugin/unraid/Tools/js_check.sh
new file mode 100755
index 0000000..1abb7a0
--- /dev/null
+++ b/Plugin/unraid/Tools/js_check.sh
@@ -0,0 +1,337 @@
+#!/bin/bash
+# ==============================================================================================
+# ============================= WebGUI JavaScript Checker ======================================
+# ==============================================================================================
+#
+# PURPOSE
+# ==============================================================================================
+# Finds the two JavaScript faults this plugin has actually shipped, neither of which any syntax
+# check can see, because both are runtime behaviour:
+#
+# 1. An identifier declared inside one function and referenced from another. Throws
+# ReferenceError on every call, kills the rest of the render, and passes php -l and
+# node --check without complaint.
+#
+# 2. A fetch chain ending in an empty catch. Not error handling — error deletion. The request
+# fails, nothing renders, nothing is logged, and the surface sits on "Loading…" forever.
+#
+# The two compound: on 2026-08-20 a cross-scope ReferenceError in the mesh chat was swallowed by
+# an empty catch on every render. It presented as "the chat takes a minute to load" — the minute
+# was the poller's backoff — and hours went into profiling PHP that was never slow. Once a catch
+# reported the error, the fault named itself in one line.
+#
+# ==============================================================================================
+# OPERATIONAL MODEL
+# ==============================================================================================
+#
+# 1. Extract the JS from each
+FIX
+ fails=0
+ echo "── self-test ────────────────────────────────────────────────"
+ if scan_scope "$WORK/fixture.php" | grep -q "paletteMap"; then
+ echo " cross-scope detector PASS"
+ else
+ echo " cross-scope detector FAIL — known fault not reported"; fails=1
+ fi
+ if scan_catches "$WORK/fixture.php" | grep -q "empty catch"; then
+ echo " empty-catch detector PASS"
+ else
+ echo " empty-catch detector FAIL — known fault not reported"; fails=1
+ fi
+ # A clean file must stay clean, or the detector is merely reporting everything.
+ cat > "$WORK/clean.php" <<'CLN'
+
+CLN
+ if [[ -z "$(scan_scope "$WORK/clean.php")$(scan_catches "$WORK/clean.php")" ]]; then
+ echo " clean file stays clean PASS"
+ else
+ echo " clean file stays clean FAIL — false positive on correct code"; fails=1
+ fi
+ echo "─────────────────────────────────────────────────────────────"
+ [[ "$fails" -eq 0 ]] && echo "self-test OK" || echo "SELF-TEST FAILED — do not trust a clean scan"
+ exit "$fails"
+fi
+
+# ── Scan ──────────────────────────────────────────────────────────────────────────────────────
+if [[ "${#FILES[@]}" -eq 0 ]]; then
+ while IFS= read -r p; do FILES+=("$p"); done < <(
+ find "$PLUGIN_DIR/pages" "$PLUGIN_DIR/include" -maxdepth 1 -name '*.php' 2>/dev/null
+ [[ -f "$PLUGIN_DIR/Varaverk.page" ]] && echo "$PLUGIN_DIR/Varaverk.page"
+ )
+fi
+
+found=0
+for f in "${FILES[@]}"; do
+ [[ -f "$f" ]] || continue
+ grep -q "