Mod Triage Board — Project Story

About the project

Mod Triage Board is a Reddit Devvit mod tool that turns the modqueue into a shared moderation workspace. Moderators see items in Unclaimed, In Progress, and Resolved columns, can claim ownership, invite collaborator mods into active investigations, leave internal notes, and skim a lightweight activity history—so two mods don’t burn time on the same report, and handoffs stay visible.

It is deliberately not a full ticketing system or a second Reddit. The goal is narrower: less duplicate moderator effort on the reports you were already going to open anyway.


What inspired it

Moderation queues are full of coordination problems disguised as content problems.

“Is anyone on this?”, “I’m halfway through—don’t nuke it”, “Who resolved this and why?” Native mod tools are strong at actions (remove, approve, modmail) but thin at shared state across a team.

The spark was treating the modqueue as a triage lane: one visible owner, optional partner, clear state, and a place for mod-only context without turning the sub into Jira.

Ship something moderators could demo in one minute and use for real the next.

Hackathon scope made that bar credible—and collaborators made it human: a second mod can join an active item without hijacking ownership, then resolve alongside the owner when they’re ready.


How we built it

Platform and shape

  • Runtime: Devvit Web — server and custom post webview are first-class, no ad-hoc Node servers.
  • Server: Hono mounted the way the Devvit Web template expects (createServer / getServerPort from @devvit/web/server), with routes under /api (webview) and /internal (menu, triggers).
  • Client: React 19 + TypeScript + Tailwind, built with Vite; the board is a single tall custom post surface tuned for touch and small viewports (safe areas, scroll behavior).
  • Persistence: Devvit Redis stores per-item JSON (workflow state, owner, collaborator, notes, mod activity history). The queue source of truth remains Reddit’s modqueue—we don’t fork reports into a private database for the MVP.

Workflow and rules

State machine is intentionally small: unclaimed → in_progress → resolved, plus release and reopen. Resolve on an in-progress item is restricted to the owner or an assigned collaborator so random mods can’t close someone else’s active investigation by accident.

SLA as a visual nudge

Card urgency increases visually as items age past a configurable SLA threshold. The goal is to help moderators notice neglected items—not to enforce strict operational guarantees. Stepped styling (warning → overdue-style) is a hint, not a contract: mods still decide what “critical” means in context.

Dev vs production

Menu entries for seeding a test modqueue live only in a generated playtest manifest (devvit.playtest.json merged from devvit.seed-menu.partial.json), while devvit.json stays clean for devvit upload. Seed/clear handlers stay server-guarded (*_dev subreddits or an explicit env flag) so production subs can’t be spammed even if someone probed endpoints.


What we learned

  1. Devvit rewards boring architecture. Fighting the template (extra HTTP servers, import-time side effects) costs more than it pays. Keeping Redis writes in handlers and the install trigger minimal made playtests reliable.

  2. The webview is a real browser—and also not one. Clipboard + host toasts behave differently than window.open or in-frame navigation to reddit.com. On embedded Android, opening Reddit inside Reddit is fragile; copy permalink plus a short preview in the drawer beat “open here” for MVP trust.

  3. Coordination UX is mostly information design. Badges for owner and collaborator, a drawer for permalink + preview + notes, and a resolved column that doesn’t pretend every old item is equally important (partitionResolvedItems / date grouping) did more for clarity than new features would have.

  4. TypeScript at the boundary. Shared types for ModBoardItem and queue payloads kept the webview and server aligned; small pure helpers (modboardDomain) stayed easy to test with Vitest.


Challenges we faced

Challenge What we tried Outcome
Embedded client navigation Opening Reddit URLs from the webview Dropped for MVP; copy link + user opens in their browser
Duplicate “Close” on mobile Header + sticky footer both visible on small screens Hide header close below sm; keep sticky close for thumb reach
Resolve permissions Anyone with mod perms could resolve Owner or collaborator only for in-progress owned items; API returns 400 on violation
Manifest dev menu in prod Seed items in devvit.json Split manifest: prod devvit.json, merged playtest config for npm run dev
Install / trigger flakiness Heavy OnAppInstall Kept trigger minimal (JSON success only), per Devvit debugging guidance

Where it could go next

Deeper in-board context (full body, report reasons from API) was prototyped and rolled back when it cluttered the hackathon story—the collaboration layer stayed the hero. A future pass could bring that back as a single focused panel, not two surfaces.


Closing

Mod Triage Board is a bet that visibility beats sophistication for small teams working the same queue: who owns it, who’s collaborating, how long it’s been open, and what the last mod did. If that saves one duplicate pass per shift, it earned its place on the menu.

Built With

Share this project:

Updates