CommentHide is a small, self-hosted Cloudflare Worker. It stays small on purpose. Bug fixes, new rule kinds and documentation are all welcome.
npm install
cp .dev.vars.example .dev.vars # then fill in the three secrets
npm run db:create # prints your database_id
# paste that id into wrangler.toml (cp wrangler.toml.example wrangler.toml)
npm run db:apply:local
npm run devnpm run setup walks through the same steps and generates the two random
keys for you. It never writes a secret to disk.
Two things to know about local development:
- Cron triggers do not fire under
wrangler dev. Use Run now in the dashboard to exercise the poll loop. __Host-cookies require HTTPS. On plain-HTTP localhost the app falls back to unprefixed cookie names automatically — that is expected, not a bug.
npm run typecheck # tsc --noEmit, strict
npm test # vitest run
npm run test:watch
npm run test:coverageBoth must pass before a pull request is reviewed. CI runs exactly these two
commands plus a wrangler deploy --dry-run to prove the Worker still builds.
The rule engine (src/lib/rules.ts) is pure and has no I/O, so it is the
easiest place to add tests — do it there rather than through the HTTP layer
whenever the behaviour allows.
src/
index.ts Hono app, route mounting, scheduled() cron entry point
types.ts every shared type — import from here, never redeclare
lib/
auth.ts login, session cookie, CSRF
crypto.ts AES-256-GCM, redact(), constant-time compare
graph.ts Meta Graph API client
poller.ts the poll loop
retention.ts history pruning
rules.ts rule engine (pure)
security.ts security headers
storage.ts every D1 query
ui/ the dashboard, served as one self-contained document
migrations/ D1 schema — the migration owns the schema, not the code
tests/ vitest
docs/CONTRACT.md binding signatures and semantics for every module
docs/CONTRACT.md is authoritative. If you need a signature that is not in
it, add to the contract in the same pull request rather than diverging from
it quietly.
These are enforced by review, and most of them by tsconfig.json:
- Strict TypeScript, with
noUncheckedIndexedAccesson. Indexing an array or record yieldsT | undefined; handle theundefinedbranch. - No file over 400 lines. Split it instead.
- No mutation of inputs. Build and return a new object.
- Handle every error path explicitly. Never catch and discard.
- Redact anything that could contain a token. Any string derived from a
Graph API response or an error message goes through
redact(text, token)before it is logged, stored or returned. The Page Access Token must never reach the browser, a log line or a URL. - Comments in English, sparse, explaining why rather than what.
- No web fonts and no third-party runtime requests. The dashboard is one self-contained document.
A rule kind is a value of RuleKind in src/types.ts plus a matcher in
src/lib/rules.ts. To add one:
- Open an issue first describing what the kind matches and why the existing kinds cannot express it.
- Add the literal to
RuleKindinsrc/types.ts. - Add the value to the
CHECK (kind IN (...))constraint. Schema changes go in a new migration file (migrations/0002_*.sql); never edit0001_init.sql, which is already applied on live deployments. - Implement the matcher in
compileRuleand give it a line indescribeRuleso the dashboard can explain itself. - Add tests covering a match, a non-match and a malformed pattern.
compileRulereturnsnullfor an invalid pattern — it must not throw. - Document the kind in the rule-kind table in
docs/CONTRACT.mdand in the README.
Matchers must be pure, must not use catastrophic backtracking, and must be
accent-insensitive where text comparison is involved (normalise with
normalize("NFD") and strip combining marks).
New kinds that suppress ordinary criticism rather than spam or abuse will be
declined. DEFAULT_RULES in particular has to stay defensible.
Conventional commits, lowercase, imperative:
feat: add author_allow rule kind
fix: stop re-resolving the post target on every poll
refactor: split storage helpers by table
docs: document the baseline flag
test: cover emoji_spam threshold parsing
chore: bump wrangler
perf: batch seen-comment lookups into one query
ci: run wrangler deploy --dry-run on pull requests
One logical change per commit. Keep the subject under 72 characters and put the reasoning in the body.
- Branch from
main. - Fill in the pull request template.
- Say what you tested and how. "Ran the poll loop against a real post in dry-run mode" is a useful sentence; "works" is not.
- Never paste an access token, a Page id or a real comment author's name into an issue, a pull request or a test fixture.