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
- css
- devvit
- hono
- javascript
- node.js
- react
- redis
- tailwind
- typescript
- vite
Log in or sign up for Devpost to join the conversation.