From 9a18d6b52f422a766671f9f198ad5a3e8ca75a0b Mon Sep 17 00:00:00 2001
From: clawbot
Date: Sun, 1 Mar 2026 11:42:19 -0800
Subject: [PATCH] feat: add About well to settings with build info and debug
easter egg
Add a new well at the bottom of the settings view that displays:
- License (GPL-3.0)
- Author (sneak)
- Version (from package.json)
- Build date (injected at build time)
- Git commit short hash (linked to Gitea commit URL)
Build-time injection: build.js now reads the git commit hash and version
from package.json, injecting them via esbuild define constants. The
Dockerfile and Makefile pass commit hashes as build args so the info is
available even when .git is excluded from the Docker context.
Easter egg: clicking the version number 10 times reveals a hidden debug
well below the About well, containing a toggle for debug mode. The debug
mode flag is persisted in state and enables verbose console logging via
the runtime debug flag in the logger.
closes #144
---
Dockerfile | 5 ++++
Makefile | 5 +++-
build.js | 50 +++++++++++++++++++++++++++++++
src/popup/index.html | 48 +++++++++++++++++++++++++++++
src/popup/views/settings.js | 60 ++++++++++++++++++++++++++++++++++++-
src/shared/buildInfo.js | 35 ++++++++++++++++++++++
src/shared/log.js | 21 +++++++++++--
src/shared/state.js | 4 +++
8 files changed, 223 insertions(+), 5 deletions(-)
create mode 100644 src/shared/buildInfo.js
diff --git a/Dockerfile b/Dockerfile
index d9e1ac2..b2cf0b6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,5 +11,10 @@ RUN yarn install --frozen-lockfile
COPY . .
+ARG GIT_COMMIT_SHORT=unknown
+ARG GIT_COMMIT_FULL=unknown
+ENV GIT_COMMIT_SHORT=${GIT_COMMIT_SHORT}
+ENV GIT_COMMIT_FULL=${GIT_COMMIT_FULL}
+
RUN make check
RUN make build
diff --git a/Makefile b/Makefile
index ff9ffe8..4d596a1 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,10 @@ dev:
@yarn run build --watch 2>&1
docker:
- @docker build -t autistmask .
+ @docker build \
+ --build-arg GIT_COMMIT_SHORT=$$(git rev-parse --short HEAD 2>/dev/null || echo unknown) \
+ --build-arg GIT_COMMIT_FULL=$$(git rev-parse HEAD 2>/dev/null || echo unknown) \
+ -t autistmask .
hooks:
@echo "Installing pre-commit hook..."
diff --git a/build.js b/build.js
index fdc9a24..4fd43d3 100644
--- a/build.js
+++ b/build.js
@@ -11,9 +11,55 @@ function ensureDir(dir) {
fs.mkdirSync(dir, { recursive: true });
}
+function getBuildInfo() {
+ const pkg = JSON.parse(
+ fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
+ );
+ let commitHash = process.env.GIT_COMMIT_SHORT || "unknown";
+ if (commitHash === "unknown") {
+ try {
+ commitHash = execSync("git rev-parse --short HEAD", {
+ encoding: "utf8",
+ }).trim();
+ } catch (_) {
+ // not a git repo or git not available
+ }
+ }
+ let commitHashFull = process.env.GIT_COMMIT_FULL || "unknown";
+ if (commitHashFull === "unknown") {
+ try {
+ commitHashFull = execSync("git rev-parse HEAD", {
+ encoding: "utf8",
+ }).trim();
+ } catch (_) {
+ // not a git repo or git not available
+ }
+ }
+ return {
+ version: pkg.version,
+ license: pkg.license,
+ author: pkg.author,
+ commitHash,
+ commitHashFull,
+ buildDate: new Date().toISOString().slice(0, 10),
+ };
+}
+
async function build() {
console.log("Building AutistMask extension...");
+ const buildInfo = getBuildInfo();
+ console.log("Build info:", buildInfo);
+
+ const define = {
+ __BUILD_VERSION__: JSON.stringify(buildInfo.version),
+ __BUILD_LICENSE__: JSON.stringify(buildInfo.license),
+ __BUILD_AUTHOR__: JSON.stringify(buildInfo.author),
+ __BUILD_COMMIT__: JSON.stringify(buildInfo.commitHash),
+ __BUILD_COMMIT_FULL__: JSON.stringify(buildInfo.commitHashFull),
+ __BUILD_DATE__: JSON.stringify(buildInfo.buildDate),
+ };
+
// compile tailwind CSS
console.log("Compiling Tailwind CSS...");
const tailwindInput = path.join(SRC, "popup", "styles", "main.css");
@@ -38,6 +84,7 @@ async function build() {
platform: "browser",
target: ["chrome110", "firefox110"],
minify: true,
+ define,
});
// bundle background script
@@ -49,6 +96,7 @@ async function build() {
platform: "browser",
target: ["chrome110", "firefox110"],
minify: true,
+ define,
});
// bundle content script
@@ -60,6 +108,7 @@ async function build() {
platform: "browser",
target: ["chrome110", "firefox110"],
minify: true,
+ define,
});
// bundle inpage script (injected into page context, separate file)
@@ -71,6 +120,7 @@ async function build() {
platform: "browser",
target: ["chrome110", "firefox110"],
minify: true,
+ define,
});
// copy popup HTML
diff --git a/src/popup/index.html b/src/popup/index.html
index 90fb615..53c9113 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -1002,6 +1002,54 @@
+
+
+
About
+
+
+ License:
+
+
+
+ Author:
+
+
+
+ Version:
+
+
+
+ Build date:
+
+
+
+
+
+
+
+
Debug
+
+
diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js
index 07b42ac..6d5c48e 100644
--- a/src/popup/views/settings.js
+++ b/src/popup/views/settings.js
@@ -3,12 +3,23 @@ const { applyTheme } = require("../theme");
const { state, saveState, currentNetwork } = require("../../shared/state");
const { NETWORKS, SUPPORTED_CHAIN_IDS } = require("../../shared/networks");
const { onChainSwitch } = require("../../shared/chainSwitch");
-const { log, debugFetch } = require("../../shared/log");
+const { log, debugFetch, setRuntimeDebug } = require("../../shared/log");
const deleteWallet = require("./deleteWallet");
+const {
+ BUILD_VERSION,
+ BUILD_LICENSE,
+ BUILD_AUTHOR,
+ BUILD_COMMIT,
+ BUILD_DATE,
+ GITEA_COMMIT_URL,
+} = require("../../shared/buildInfo");
const runtime =
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
+let versionClickCount = 0;
+let versionClickTimer = null;
+
function renderSiteList(containerId, siteMap, stateKey) {
const container = $(containerId);
const hostnames = [...new Set(Object.values(siteMap).flat())];
@@ -134,6 +145,28 @@ function show() {
renderSiteLists();
renderWalletListSettings();
+ // Populate About well
+ $("about-license").textContent = BUILD_LICENSE;
+ // Show only the name part of the author field (strip email)
+ const authorName = BUILD_AUTHOR.replace(/\s*<[^>]+>/, "");
+ $("about-author").textContent = authorName;
+ $("about-version").textContent = BUILD_VERSION;
+ $("about-build-date").textContent = BUILD_DATE;
+ $("about-commit-link").textContent = BUILD_COMMIT;
+ $("about-commit-link").href = GITEA_COMMIT_URL;
+
+ // Reset version click counter each time settings opens
+ versionClickCount = 0;
+
+ // Show debug well if debug mode is already enabled
+ const debugWell = $("settings-debug-well");
+ if (state.debugMode) {
+ debugWell.style.display = "";
+ } else {
+ debugWell.style.display = "none";
+ }
+ $("settings-debug-mode").checked = state.debugMode;
+
showView("settings");
}
@@ -281,6 +314,31 @@ function init(ctx) {
ctx.showSettingsAddTokenView,
);
+ // Easter egg: click version 10 times to reveal the debug well
+ $("about-version").addEventListener("click", () => {
+ versionClickCount++;
+ clearTimeout(versionClickTimer);
+ // Reset counter if user stops clicking for 3 seconds
+ versionClickTimer = setTimeout(() => {
+ versionClickCount = 0;
+ }, 3000);
+ if (versionClickCount >= 10) {
+ versionClickCount = 0;
+ clearTimeout(versionClickTimer);
+ $("settings-debug-well").style.display = "";
+ }
+ });
+
+ // Debug mode toggle
+ $("settings-debug-mode").addEventListener("change", async () => {
+ state.debugMode = $("settings-debug-mode").checked;
+ setRuntimeDebug(state.debugMode);
+ await saveState();
+ });
+
+ // Sync runtime debug flag on init
+ setRuntimeDebug(state.debugMode);
+
$("btn-settings-back").addEventListener("click", () => {
ctx.renderWalletList();
showView("main");
diff --git a/src/shared/buildInfo.js b/src/shared/buildInfo.js
new file mode 100644
index 0000000..63c4b1a
--- /dev/null
+++ b/src/shared/buildInfo.js
@@ -0,0 +1,35 @@
+// Build-time constants injected by esbuild define in build.js.
+// These globals are replaced at bundle time with string literals.
+
+/* global __BUILD_VERSION__, __BUILD_LICENSE__, __BUILD_AUTHOR__,
+ __BUILD_COMMIT__, __BUILD_COMMIT_FULL__, __BUILD_DATE__ */
+
+const BUILD_VERSION =
+ typeof __BUILD_VERSION__ !== "undefined" ? __BUILD_VERSION__ : "dev";
+const BUILD_LICENSE =
+ typeof __BUILD_LICENSE__ !== "undefined" ? __BUILD_LICENSE__ : "GPL-3.0";
+const BUILD_AUTHOR =
+ typeof __BUILD_AUTHOR__ !== "undefined"
+ ? __BUILD_AUTHOR__
+ : "sneak ";
+const BUILD_COMMIT =
+ typeof __BUILD_COMMIT__ !== "undefined" ? __BUILD_COMMIT__ : "unknown";
+const BUILD_COMMIT_FULL =
+ typeof __BUILD_COMMIT_FULL__ !== "undefined"
+ ? __BUILD_COMMIT_FULL__
+ : "unknown";
+const BUILD_DATE =
+ typeof __BUILD_DATE__ !== "undefined" ? __BUILD_DATE__ : "unknown";
+
+const GITEA_COMMIT_URL =
+ "https://git.eeqj.de/sneak/AutistMask/commit/" + BUILD_COMMIT_FULL;
+
+module.exports = {
+ BUILD_VERSION,
+ BUILD_LICENSE,
+ BUILD_AUTHOR,
+ BUILD_COMMIT,
+ BUILD_COMMIT_FULL,
+ BUILD_DATE,
+ GITEA_COMMIT_URL,
+};
diff --git a/src/shared/log.js b/src/shared/log.js
index b4a63f9..551fb20 100644
--- a/src/shared/log.js
+++ b/src/shared/log.js
@@ -1,12 +1,27 @@
// Leveled logger. Outputs to console with [AutistMask] prefix.
-// Level is DEBUG when the DEBUG constant is true, INFO otherwise.
+// Level is DEBUG when the compile-time DEBUG constant is true or the runtime
+// debugMode state flag is enabled. The runtime flag is checked lazily so it
+// responds immediately when toggled in settings.
const { DEBUG } = require("./constants");
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
-const threshold = DEBUG ? LEVELS.debug : LEVELS.info;
+
+// Runtime debug mode flag — set by settings.js when the user toggles debug
+// mode via the easter egg. Kept here as a simple mutable reference so it can
+// be updated without circular dependency issues with state.js.
+let _runtimeDebug = false;
+
+function setRuntimeDebug(enabled) {
+ _runtimeDebug = enabled;
+}
+
+function isDebug() {
+ return DEBUG || _runtimeDebug;
+}
function emit(level, method, args) {
+ const threshold = isDebug() ? LEVELS.debug : LEVELS.info;
if (LEVELS[level] >= threshold) {
console[method]("[AutistMask]", ...args);
}
@@ -37,4 +52,4 @@ async function debugFetch(url, opts) {
return resp;
}
-module.exports = { log, debugFetch };
+module.exports = { log, debugFetch, setRuntimeDebug, isDebug };
diff --git a/src/shared/state.js b/src/shared/state.js
index b39faed..1d97b8d 100644
--- a/src/shared/state.js
+++ b/src/shared/state.js
@@ -29,6 +29,7 @@ const DEFAULT_STATE = {
fraudContracts: [],
tokenHolderCache: {},
theme: "system",
+ debugMode: false,
};
const state = {
@@ -67,6 +68,7 @@ async function saveState() {
fraudContracts: state.fraudContracts,
tokenHolderCache: state.tokenHolderCache,
theme: state.theme,
+ debugMode: state.debugMode,
currentView: state.currentView,
selectedWallet: state.selectedWallet,
selectedAddress: state.selectedAddress,
@@ -126,6 +128,8 @@ async function loadState() {
state.fraudContracts = saved.fraudContracts || [];
state.tokenHolderCache = saved.tokenHolderCache || {};
state.theme = saved.theme || "system";
+ state.debugMode =
+ saved.debugMode !== undefined ? saved.debugMode : false;
state.currentView = saved.currentView || null;
state.selectedWallet =
saved.selectedWallet !== undefined ? saved.selectedWallet : null;