Initial commit: notifications, monitoring, on-call and incidents

One binary and one SQLite file. Topics with ntfy-style publish and
subscribe, HTTP/TCP/DNS/TLS/heartbeat monitors with a public status page,
schedules and escalation policies, and incidents with a timeline and
post-mortem. The UI follows the Lightwell design language.

Builds against an unreleased Reef (secretbox) through a local replace line.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 23:21:56 -06:00
commit 0c5a57556a
22 files changed
+4953

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
// Style gate for the dashboard, following the Lightwell design language (see docs/design.md).
// Fails on em or en dashes, emoji, exclamation marks in interface copy, and raw colors outside the
// token block of web/index.html. Run with: node scripts/lint-design.mjs
import { readdirSync, readFileSync, statSync } from "node:fs";
import { extname, join, relative, sep } from "node:path";
const root = new URL("..", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
const targets = ["internal/sandbar/web", "internal/sandbar", "docs", "README.md", "scripts", "cmd", "."];
const skipDirs = new Set(["node_modules", ".git"]);
const exts = new Set([".html", ".md", ".go", ".sh", ".mjs", ".example"]);
const dash = /[–—−]/;
const emoji = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F900}-\u{1F9FF}\u{FE0F}\u{2B50}\u{2705}\u{274C}]/u;
const rawColor = /#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(/;
const seen = new Set();
function* walk(p, top) {
const st = statSync(p, { throwIfNoEntry: false });
if (!st || seen.has(p)) return;
if (st.isFile()) { seen.add(p); return yield p; }
for (const name of readdirSync(p)) {
if (skipDirs.has(name) || (top && name.startsWith("."))) continue;
const full = join(p, name);
// the "." target only contributes the files at the top level
if (p === root && statSync(full).isDirectory()) continue;
yield* walk(full, false);
}
}
const problems = [];
for (const t of targets) {
for (const file of walk(join(root, t), true)) {
if (!exts.has(extname(file)) && !file.endsWith(".env.example")) continue;
const rel = relative(root, file).split(sep).join("/");
if (rel.endsWith("go.sum") || rel === "scripts/lint-design.mjs") continue;
const isPage = rel.includes("/web/") && rel.endsWith(".html");
let inTokens = false;
readFileSync(file, "utf8").split("\n").forEach((line, i) => {
const at = (msg) => problems.push(`${rel}:${i + 1}: ${msg}`);
if (line.includes("tokens:begin")) inTokens = true;
if (dash.test(line)) at("em dash, en dash or minus sign. Use a comma, colon, period or the word to.");
if (emoji.test(line)) at("emoji or pictographic symbol. Use an approved outline icon.");
if (isPage && !inTokens && rawColor.test(line.replace(/\/\*.*?\*\//g, "")) && !/placeholder|href="#|&#/.test(line)) {
at("raw color outside the token block. Use a token.");
}
if (isPage && /(?:toast|textContent\s*=|class:'(?:empty|notice)')[^;\n]*'[^']*[A-Za-z]![^']*'/.test(line)) at("exclamation mark in interface copy.");
if (line.includes("tokens:end")) inTokens = false;
});
}
}
if (problems.length) {
console.error(problems.join("\n"));
console.error(`\n${problems.length} style problem(s).`);
process.exit(1);
}
console.log("Style checks passed.");