ThreadReplay — Project Story

Inspiration

Online communities live and die by how well their moderation scales. But moderation is almost always reactive — a mod sees a report, takes action, and moves on. There's rarely a way to look back at a thread and understand how it unraveled: which comment ignited the toxicity, when a brigade arrived, and whether the cleanup actually helped sentiment recover.

We kept thinking about threads we'd seen spiral in real time — a civil discussion that hit one bad-faith reply and collapsed into a pile-on within minutes. The pattern felt almost mechanical, like watching a phase transition. That intuition is actually grounded in research on opinion dynamics. A simple model of how a hostile comment $h$ spreads through a thread of $n$ participants looks something like:

$$\frac{dh}{dt} = \beta \cdot h \cdot \left(1 - \frac{h}{n}\right) - \gamma \cdot h$$

where $\beta$ is the contagion rate of hostile framing and $\gamma$ is the natural dampening from civil replies or mod action. Small changes in $\frac{\beta}{\gamma}$ — the basic toxicity number — determine whether a thread self-corrects or cascades.

We wanted to make that invisible dynamic visible. That became ThreadReplay.


What it does

ThreadReplay is a forensic moderation tool built as a Reddit Devvit app. Given any thread, it:

  • Replays the comment timeline from $t = 0$ using an animated scrubber, so you can watch the conversation unfold chronologically
  • Detects toxic comments via keyword heuristics and score thresholds
  • Identifies brigade waves by finding clusters of $\geq 3$ low-score accounts posting within a 3-minute window — a pattern consistent with coordinated off-sub arrivals
  • Marks pivot moments — the first positive comments to appear within 10 minutes after a mod removal, signaling sentiment recovery
  • Shows per-root density maps so you can see at a glance which subtrees were noisy and when
  • Exports a Markdown mod report summarizing each root's statistics

The interface organizes the thread as an indexed accordion — each top-level comment (root) gets its own numbered row that expands to reveal its full comment subtree, with color-coded annotations for toxic 🔴, brigade 🟡, removed 🟢, and pivot 🟣 signals.


How we built it

The stack is deliberately lightweight: vanilla HTML, CSS, and JavaScript, packaged as a Devvit web view. No framework dependencies. The core pieces:

Detection pipeline runs entirely client-side. For each root subtree independently:

  1. Toxic detection — flag comments where $\text{score} < -10$ or body matches a regex of hostile terms
  2. Brigade detection — sliding window across sorted timestamps; if $|{authors}| \geq 3$ with $\text{score} < 0$ within $\Delta t \leq 180\text{s}$, mark as brigade
  3. Pivot detection — after each mod removal event $r$, find comments in $[t_r,\ t_r + 600\text{s}]$ where $\bar{s} > 40$; the first two become pivot markers

Timeline rendering uses a percentage cursor $p \in [0, 100]$ mapped to real timestamps:

$$t(p) = t_0 + \frac{p}{100} \cdot (t_{end} - t_0)$$

Comments are injected into the DOM as $p$ advances, with CSS animation for the slide-in effect. The playback loop ticks every 80ms and increments $p$ by $0.35 \times \text{speed}$.

Density canvas renders a <canvas> heatmap for both the global view and each root's minimap, drawing bars at x-positions proportional to comment timestamps. The minicursor tracks $p$ live.

Devvit bridge listens for postMessage events from the host app carrying {type: 'load', payload: {comments, removals}}, falling back to demo data after 1.5 seconds if nothing arrives.


Challenges we ran into

Subtree isolation. Early builds ran detection globally — a brigade in root C would incorrectly flag comments in root A. The fix was computing TOXIC, BRIGADE, and PIVOT maps per root ID, so each subtree is fully independent. This also meant the root-level stat badges needed their own scoped lookups.

Canvas sizing on dynamic layouts. When roots are inside an accordion, the minimap <canvas> has zero width before the panel is expanded. We had to defer drawRootMinimap calls to requestAnimationFrame after the DOM is painted, and re-trigger on expand.

Accordion + live replay interaction. If a panel is closed while playback is running, comments still render into the hidden <div> — which is correct, because toggling the panel later should show the accumulated state. But feed.scrollTop = feed.scrollHeight on a hidden element is a no-op. We kept it simple and let the scroll snap on open.

Brigade false positives. The sliding window algorithm is $O(n^2)$ over comments and was flagging legitimate bursts of discussion as brigades. Tightening the conditions to require both negative scores and $\geq 3$ distinct authors brought precision up without hurting recall on genuine coordination patterns.


Accomplishments that we're proud of

  • A zero-dependency forensics UI that loads instantly inside a Devvit web view
  • The indexed accordion layout — replacing a crowded multi-column grid with a scannable numbered list that scales to any number of roots cleanly
  • Per-subtree detection that is genuinely root-isolated, preventing cross-contamination of signals
  • A density heatmap that makes the temporal shape of toxicity immediately legible — you can see a brigade as a tight cluster of amber bars before you read a single comment
  • Clean Devvit message bridge that makes the demo data / real data switchover seamless

What we learned

Building this sharpened our thinking on a few things:

Moderation is a signal processing problem. The brigade detector is essentially a matched filter for a known waveform — coordinated posting has a distinctive temporal signature ($\Delta t$ tight, authors diverse, scores negative). Framing it that way made the algorithm cleaner.

Accordion beats grid at scale. A side-by-side column layout feels intuitive for 2–3 roots but breaks badly at 6+. An indexed list with expand-in-place panels is both more space-efficient and more scannable — users can read the summary row and decide whether to drill in.

Detection thresholds are social contracts, not math. The cutoffs ($\text{score} < -10$, window $\leq 180\text{s}$, $\bar{s} > 40$ for pivot) aren't derived from first principles — they're tuned to match moderator intuition on real data. Any deployment would need community-specific calibration.


What's next for ThreadReplay

  • Real Devvit data integration — wire the message bridge to Reddit's actual comment API instead of demo JSON
  • Configurable thresholds — let community mods tune the $\beta / \gamma$ detection parameters per-subreddit via a settings panel
  • Author graph overlay — visualize reply-to relationships as a force-directed graph to expose reply-chain coordination that the current linear view misses
  • Sentiment scoring — replace keyword matching with an embedding-based toxicity classifier for higher precision, ideally running via a lightweight WASM model client-side
  • Export to mod queue — one-click to surface detected comments directly into the subreddit mod queue with pre-filled removal reasons
  • Historical comparison — track $\frac{\beta}{\gamma}$ trends across threads over time to identify subreddits at elevated risk before a cascade starts

Built With

Share this project:

Updates