Files
broccolini-bot/settings-site/CLAUDE.md
2026-04-18 11:10:41 +00:00

5.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Scope

This is the settings-site subdirectory of the broccolini-bot repo. It is a separate Express process that provides an admin web UI for editing the bot's runtime config. It is not part of the bot's Node process.

The parent repo's rules in /opt/broccolini-bot/CLAUDE.md still apply here — especially CommonJS only, read before write, and no unsolicited refactors. Read that file alongside this one.

Commands

  • npm start — run the settings site (node server.js).
  • npm run dev — run with node --watch for auto-reload.
  • No lint, no test framework, no build step. Frontend is vanilla JS served from public/ — no bundler.
  • Deploy via its own compose file: docker compose up --build -d from this directory. Container name broccolini-settings, joins external broccoli-net.

Architecture

Two processes, one .env

The settings site is a thin HTTPS-oriented proxy in front of the bot's internal API:

browser ──► settings server.js (:SETTINGS_PORT, default 12752)
            │  session auth (SETTINGS_ADMIN_PASSWORD)
            ▼
            bot internalApp (127.0.0.1:INTERNAL_API_PORT, default 12753)
            │  header auth (x-internal-secret = INTERNAL_API_SECRET)
            ▼
            routes/internalApi.js in /opt/broccolini-bot

server.js loads ../.env (the bot's env file) — both processes share it. docker-compose.yml also mounts env_file: ../.env, not a local one. There is no settings-site-specific env beyond what's in .env.example.

Proxied endpoints

server.js exposes five authenticated endpoints that forward to the bot's /internal/* API via callBot():

Settings route Bot route
GET /api/config GET /internal/config
POST /api/config POST /internal/config
GET /api/discord/guild GET /internal/discord/guild
POST /api/restart POST /internal/restart
GET /api/restart/status GET /internal/restart/status

Every response-shape change in the bot's /internal/* handlers (routes/internalApi.js) is a breaking change here. The bot also gates POST /internal/config on an ALLOWED_CONFIG_KEYS allowlist — adding a new field to the UI requires adding the key to that Set in the bot first, otherwise the save returns 400 for that key.

server.js:20-26 sets cookie.secure: true. Browsers will refuse to persist the session cookie over plain HTTP, so login silently fails when not behind an HTTPS reverse proxy (SETTINGS_DOMAIN is the deployed domain). If you're reproducing a login bug, check this first before debugging auth logic. The session secret falls back to 'fallback-secret-change-me' when INTERNAL_API_SECRET is unset — don't rely on the fallback in any environment that matters.

Client-side routing

public/index.html is a single page with all sections rendered; public/js/app.js toggles .hidden on sections based on location.pathname. Routes live in the ROUTES map (app.js:425). The server has app.get('*', requireAuth, …) as a catch-all back to index.html (server.js:97), so any new client route works without server changes as long as it's added to ROUTES.

Config field binding (frontend)

Any form element with data-key="SOME_CONFIG_KEY" participates in the editor:

  • populateFields() (app.js:102) fills it from GET /api/config and wires change listeners.
  • Checkboxes serialize to the strings 'true' / 'false', and <input type="color"> serializes to 0xRRGGBB — this matches how the bot stores these values.
  • pendingChanges accumulates diffs; saveConfig() POSTs the whole diff at once.
  • data-smart="channel|category|role|member|multi-member" swaps the bare <input> for a searchable Discord picker backed by GET /api/discord/guild (see public/js/discord.js).

To add a new editable config field: (1) add the key to the bot's ALLOWED_CONFIG_KEYS, (2) add a <input data-key="NEW_KEY"> (optionally data-smart=…) inside the appropriate .section in public/index.html. No JS changes needed.

Notification thresholds editor

The Notifications section is not a simple data-key field — it's a custom editor (app.js:239-423) that serializes into a single hidden NOTIFICATION_THRESHOLDS_JSON field. Alert keys are hard-coded in NOTIFICATION_TAB_KEYS (surge / patterns / unclaimed / chat) and described in NOTIFICATION_ALERT_DESCRIPTIONS. Adding a new alert key requires editing both of those objects — otherwise it won't show up in any tab. Threshold values accept whole numbers or duration strings matching ^(\d+[mhd])+$ (e.g. 15m, 1h, 1d6h).

Gotchas

  • The frontend has no framework and no build — edit public/js/*.js directly; changes are live on reload.
  • getaddrinfo failures from callBot() surface to the UI as "Bot unreachable" (502). This is almost always the bot process being down or the internal port being wrong, not a bug in this codebase.
  • docker-compose.yml binds the port to the Tailscale IP 100.114.205.53:12752 — not 0.0.0.0. Changing that binding has security implications.