Inspiration

I started thinking about what it actually feels like to be a Reddit moderator in a large community. You open the mod queue and see 200 items — a flat, unsorted, ungrouped list. No indication of what's urgent. No memory of what you've seen before. No visibility into whether your team is overwhelmed or whether the same type of violation has been spiking all week.

Reddit has incredible communities, and the people who run them are volunteers doing a genuinely hard job with almost no tooling. The mod queue UI has barely changed in years. AutoModerator helps with filtering, but it has no memory, no learning, and no analytics. I wanted to build the tool I'd want if I were a moderator of a large subreddit — something that makes the queue feel intelligent instead of chaotic.

The hackathon brief specifically asked for tools that save mod time or make a measurable impact. That framing pushed me toward a question: what's the single biggest time sink for mods? The answer was clear — triage. Deciding what to look at first, and then having to re-decide the same thing over and over because there's no institutional memory.

That's where ModMind started.


What I Learned

Building ModMind taught me how different Devvit 0.12 is from the older Devvit public API. The new React web view architecture — with Hono on the server, Vite on the client, and Redis as the persistence layer — is a completely different mental model from the old Devvit.addTrigger pattern.

The biggest lesson was about how Devvit passes data between the different parts of an app. Trigger events wrap their payload in a nested body.post object rather than passing fields at the top level. Menu handlers have access to context.postId, but form submit handlers do not — the postId has to be embedded as a form field and passed through explicitly. None of this is obvious from the documentation, and I only figured it out by logging the raw request body at every step and reading the compiled template code in node_modules.

I also learned that Redis key design matters a lot in Devvit apps. Because Redis is the only persistence layer, the schema you choose upfront determines what queries are possible later. I designed ModMind's keys to be scoped by subredditId so the app is completely isolated between communities — clusters:{subredditId}, mod_stats:{subredditId}:{username}, precedent:{subredditId}:{contentHash} — which means any subreddit can install it without any risk of data leakage.

The content fingerprinting approach for precedent detection was the most interesting algorithmic problem. I needed a way to match "similar" posts without exact string matching, using only the tools available in a sandboxed Devvit environment (no external ML APIs). The solution was a simple but effective approach: strip stop words, sort the remaining tokens, join them, and hash the result with a djb2-style hash. Two posts with the same key content words in different orders produce the same hash — which is exactly the behavior needed for catching variations of the same spam or violation.


How I Built It

ModMind is built entirely on Devvit 0.12 using the React web view template.

The classifier lives in src/server/modmind/classifier.ts. It maintains a signal dictionary for six violation types — spam, harassment, ban evasion, misinformation, self-promotion, and rule violation — and scores incoming content by counting how many signals match. Each signal adds 25 points of confidence, capped at 95%. Severity is determined by confidence level, with harassment and ban evasion always elevated because the consequences of missing them are higher.

The storage layer in src/server/modmind/storage.ts manages five Redis key namespaces: clusters (active report groups, 24h TTL), daily stats (7-day rolling window, 90-day TTL), mod actions (decision history, 90-day TTL), precedents (content fingerprint index, 90-day TTL), and active mods (who has logged actions this week, 7-day TTL).

The triggers in src/server/routes/triggers.ts fire on three events: onPostSubmit for proactive detection before reports come in, onPostReport for classifying reported content and checking precedents, and onCommentReport for flagging reported comments.

The dashboard is a React custom post with three tabs — Queue (cluster triage view), Trends (7-day bar chart and violation type breakdown), and Mods (per-moderator workload tracker). It fetches data from the Hono server via /api/dashboard and refreshes on demand.

The Log Action flow uses a Devvit menu item on posts that opens a form with two dropdowns — action taken and violation type. On submit, the decision is stored in Redis, the post is removed from its cluster, and the mod's weekly stats are updated.

The entire stack: Devvit 0.12 · React 19 · Hono · Redis · TypeScript · Vite · Tailwind


Challenges I Faced

The postId problem. This was the hardest debugging session of the build. Devvit 0.12 passes context.postId in menu handlers but not in form submit handlers. The form receives its own separate context where postId is always undefined. The solution was to embed the postId as a visible string field in the form itself — pre-populated from the menu handler — so it gets submitted as part of the form values. It works, but it means the form shows a "Post ID" field that mods have to ignore. A cleaner hidden field mechanism would be a valuable Devvit platform addition.

The trigger payload structure. The Devvit 0.12 trigger system wraps event data in a nested object — body.post.id instead of body.postId. This isn't documented anywhere I could find, and I only discovered it by adding detailed JSON logging to every trigger handler and reading the raw output. Once I knew the structure, the fix was one line — but finding it took significant time.

The Redis expiration format. Devvit 0.12's Redis client expects a Date object for the expiration option, not a Unix timestamp number. Passing a number throws d.expiration.getTime is not a function at runtime. The error message is clear but the documentation doesn't mention the expected type, so it was a confusing failure on first encounter.

New account spam filtering. During testing, Reddit's own spam filter aggressively removed posts from the test account because it was newly created. This meant I couldn't test the report trigger with a second account the way I planned. The workaround was to post test content from the mod account itself, which isn't removed, and report it from the same account — which Reddit allows in a private test subreddit.

Despite these challenges, ModMind went from zero to a fully working, published, playtest-verified app in a single development session. Every feature described in the submission works end to end with real data on a real Reddit subreddit.

Built With

Share this project:

Updates