Inspiration
The spark wasn't an idea. It was a thread.
On May 9, 2026, a post appeared in r/ModSupport titled "Suggestion: Allow mods to mark posts as 'not reportable' after approval." It climbed to 90 upvotes within days — not because the feature request was clever, but because every moderator who read it recognized the exact 2 AM moment it described: you've already reviewed this post, you know it's fine, you made the call, and here it is again in the queue because three more people reported it overnight.
The OP's frustration was precise: native Reddit ignore-reports already exists. The problem is that it's memoryless. There's no team ledger. No shared record of who reviewed what, when, and why. No automatic flag when the content that was approved yesterday gets quietly edited into something different today. A moderator on a different shift doesn't know the prior decision happened. They open the same post, spend another 90 seconds re-reviewing it, and either make the same call again or — worse — a slightly different one, silently forking the team's moderation posture.
But the most useful comment in that thread wasn't about ignore-reports at all. One reply put a precise condition on the entire request:
"Support only while the content has not been edited since approval."
That single clause changed what we were building. Not a wrapper over ignore-reports. Not a modqueue dashboard. A closed loop that had never been built as a Devvit-native app:
- A moderator reviews content and locks the decision.
- Repeat reports on that exact, unchanged content are suppressed and counted.
- The moment the content changes, the lock breaks automatically.
- The item resurfaces for moderator attention — the new version, with full context.
The second thing we checked was whether this already existed. Flag App handles freeform report filtering. Native Reddit handles basic ignore. So ReviewLock had to stay in a specific, defensible lane: content-integrity-bound review state. Not another queue tool. Not a report filter. An edit-aware reviewed-content ledger that gives a team a shared memory for decisions they've already made — and that becomes meaningless the moment the reviewed content changes.
That constraint is not a limitation. It's what makes ReviewLock honest.
What It Does
ReviewLock runs a single four-beat loop. Judges and users need to see this loop before anything else:
┌──────────────────────────────────────────────────────────────────┐
│ THE REVIEWLOCK LOOP │
│ │
│ Moderator reviews post or comment │
│ │ │
│ ▼ │
│ [ Lock review ] ──────────────────────────────────────────────► │
│ │ Refetch target through Reddit API │
│ │ Compute SHA-256 content fingerprint │
│ │ Call approve() ──► if not already approved │
│ │ Call ignoreReports() │
│ │ Store lock record + content hash in Redis │
│ │ Write lock_created audit event │
│ │ │
│ ├─── Repeat report arrives ──────────────────────────┐ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Fingerprint still matches? │ │
│ │ │ │ │
│ │ YES ─┤──► ignoreReports() │ │
│ │ │ increment Reports suppressed │ │
│ │ │ write report_suppressed audit │ │
│ │ │ │ │
│ │ NO ─┤──► (content changed — fall through) │ │
│ │ │ │ │
│ │ UNCERTAIN ──► fail open (treat as changed) │ │
│ │ ▼ │
│ └─── Content changes ───────────────────────────────► │
│ │ │
│ ▼ │
│ Break lock │
│ Call unignoreReports() │
│ Enqueue reopen event with fingerprint delta │
│ Write lock_reopened audit event │
│ Surface as "Reopened after edit" in dashboard │
│ │
└──────────────────────────────────────────────────────────────────┘
Capabilities
| Capability | What it does |
|---|---|
Lock review (post/comment menu) |
Computes content fingerprint, calls approve() + ignoreReports(), stores lock record in Redis with reason and audit event. |
Unlock review (post/comment menu) |
Removes lock, calls unignoreReports(), writes audit. Locks that cannot unignore stay visible as failed rather than silently dropped. |
Open ReviewLock dashboard (subreddit menu) |
Launches the dashboard custom post — or reopens the existing one if it already exists for this subreddit. |
| Report triggers | onPostReport / onCommentReport — if lock is active and fingerprint unchanged, suppress and count. If changed, break lock. If uncertain, fail open. Idempotent: duplicate trigger delivery does not double-count. |
| Update triggers | onPostUpdate / onCommentUpdate / onPostNsfwUpdate / onPostSpoilerUpdate / onPostFlairUpdate — detects material content, flair, NSFW, and spoiler changes. Each detected change breaks the lock and enqueues a reopen event with the specific reason. |
| Dashboard | First viewport shows: active lock count, reports suppressed, reopened-after-edit count, latest edit-break event. Full panels: active lock list, reopen queue, report churn, audit timeline, runtime proof status. |
| Demo mode | Visibly labeled seeded scenario: 12 lock records, 47 reports suppressed, 5 edit-triggered reopens, 1 failure/warning example, post and comment examples. Demo data is namespace-isolated — it never pollutes live subreddit data. |
| Audit log | Every lock, unlock, suppress, reopen, dismiss, and runtime failure event is persisted with actor, timestamp, target, and structured data. |
| Runtime proof panel | Dashboard panel that shows which Devvit capabilities have been live-verified in controlled playtest vs. which are implemented but awaiting live proof. Moderators and judges see the exact claim boundary. |
The fingerprint — the product's backbone
ReviewLock doesn't ask Reddit "has this post been edited?" It answers its own question.
Post material fingerprint inputs: title + body/selftext + URL (for link posts) + flair text + flair template ID + NSFW flag + spoiler flag
Comment material fingerprint inputs: body only
Normalization before hashing:
| Rule | Example | Reason |
|---|---|---|
| Trim outer whitespace | " hello " → "hello" |
Prevents phantom unlocks on invisible whitespace |
| Normalize CRLF/CR → LF | Windows line endings | Cross-platform consistency |
| Collapse runs of spaces/tabs | "two spaces" → "two spaces" |
Non-semantic spacing should not trigger reopen |
| Preserve markdown line breaks | "line1\nline2" ≠ "line1 line2" |
Line break changes can alter meaning — this is material |
| Do not lowercase body/title | "CAP words" stays as-is |
Casing can be semantically meaningful |
If the fingerprint cannot be computed with confidence — a trigger payload arrives without enough content, and a re-fetch through the Reddit API fails — ReviewLock fails open. It reopens the lock rather than risk silently suppressing a report on content that may have changed.
This is not conservatism. This is the product thesis enforced at the code level: review state belongs to the exact content that was reviewed. Uncertainty means the contract is broken.
How We Built It
ReviewLock is built entirely on the Devvit platform. No external services, no external databases, no AI, no webhooks, no paid APIs.
Architecture
┌────────────────────────────────────────────────────────────────────┐
│ REVIEWLOCK ARCHITECTURE │
│ │
│ Reddit Platform (Moderator entry points) │
│ ┌─────────────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Post / Comment │ │ Subreddit Menu │ │ Report / Update │ │
│ │ Menu Actions │ │ Open dashboard │ │ Triggers │ │
│ └────────┬────────┘ └────────┬─────────┘ └────────┬──────────┘ │
│ │ │ │ │
│ ┌────────▼───────────────────▼──────────────────────▼──────────┐ │
│ │ Hono Server (@devvit/web) │ │
│ │ │ │
│ │ /internal/menu/* /internal/form/* │ │
│ │ /internal/triggers/* /api/locks/* │ │
│ │ /api/dashboard/* /api/context │ │
│ │ /api/runtime/* /api/demo/* │ │
│ └────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼──────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ │ │
│ │ fingerprint │ locks │ metrics │ │
│ │ reportTriggers │ reopenFlow │ audit │ │
│ │ updateTriggers │ dashboard │ demoData │ │
│ │ moderation │ targetResolver │ runtimeProof │ │
│ │ triggerDecisions│ reopenQueue │ (all pure TypeScript) │ │
│ └──────────────────┬──────────────────────┬──────────────────────┘ │
│ │ │ │
│ ┌──────────────────▼──────┐ ┌────────────▼─────────────────────┐ │
│ │ Reddit Adapter │ │ Devvit Redis │ │
│ │ getPostById() │ │ │ │
│ │ getCommentById() │ │ reviewlock:{sub}:lock:{id} │ │
│ │ approve() │ │ reviewlock:{sub}:locks:active │ │
│ │ ignoreReports() │ │ reviewlock:{sub}:reopen:{id} │ │
│ │ unignoreReports() │ │ reviewlock:{sub}:audit:{id} │ │
│ │ getCurrentSubreddit() │ │ reviewlock:{sub}:metrics:* │ │
│ └─────────────────────────┘ │ reviewlock:{sub}:runtime │ │
│ │ reviewlock:{sub}:config │ │
│ │ reviewlock:{sub}:demo │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Dashboard — Devvit Web Custom Post (WebView) │ │
│ │ Active locks · Reports suppressed · Reopened after edit │ │
│ │ Reopen queue · Audit timeline · Runtime proof · Demo mode │ │
│ └────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
Tech stack
| Layer | Technology |
|---|---|
| Devvit package family | devvit + @devvit/web + @devvit/start at 0.12.24 |
| Server framework | Hono (via @devvit/web/server) |
| Language | TypeScript (strict mode, no any escapes in service layer) |
| Storage | Devvit Redis (sorted sets, hashes, strings, NX guards, transactions) |
| Content hashing | Web Crypto SubtleCrypto.digest('SHA-256', ...) — no external hashing library |
| Dashboard | Devvit custom post (WebView), TypeScript client |
| Tests | Vitest — 444 tests across 43 test files |
| Build | Vite |
| CI gate | type-check → lint → test → build must all pass before devvit upload |
How the project was structured
ReviewLock was built in 30 sequential development waves, each with a defined ownership list and a mandatory commit + proof artifact. This wasn't ceremony — it was how a distributed build process maintained coherence. Every wave ran the full local gate before committing. Every wave logged what it changed, what commands it ran, and what it could not verify.
The project accumulated 143 documented engineering decisions (D001–D143 in decisions.md). Not design notes — actual decisions: the problem faced, the alternatives evaluated, the path chosen, and the effect on the product thesis. These include:
- D003 — Why fingerprinting must happen before suppression, not after. (Answer: fail-open on uncertainty.)
- D012 — Why Redis NX guards serialize trigger mutations per target. (Answer: report and update triggers fire in parallel; last-write-wins is wrong for lock state.)
- D020 — Why demo data is written to a completely separate namespace. (Answer: demo state must never mix with live subreddit moderation records.)
- D028 — Why
window.confirm()cannot be used for destructive actions in the Devvit WebView. (Answer: it's not reliably available inside embedded WebViews.) - D034 — Why client-supplied subreddit namespaces are rejected if they don't match Devvit runtime context. (Answer: a malicious or stale client must not be able to write into the wrong subreddit's data.)
The discipline of writing decisions down paid off repeatedly. When we hit a runtime surprise at wave 15 or wave 22, the decisions log told us exactly why an earlier choice was made — and whether the new problem was a reason to change it or a reason to leave it alone.
Challenges We Ran Into
1. Devvit WebView authorization is not what you expect
This was the most disorienting runtime discovery. You cannot smoke-test a Devvit Web server by calling it from curl or a terminal. Every request to a @devvit/web route requires Reddit-injected WebView headers — context that is only available when the request comes from inside a Reddit-embedded WebView.
This means: every runtime capability claim had to be proven from inside Reddit itself. We built a runtime proof panel directly into the ReviewLock dashboard for exactly this purpose. A moderator (or judge) clicks "Verify runtime" in the dashboard, the call goes through Reddit's WebView, the server resolves context.subredditName and redis, records the result in a structured proof record, and the panel shows exactly what was confirmed and when. The panel also shows which capabilities are still unverified — not hidden, front and center.
2. Trigger payload shapes are not stable
Devvit can deliver trigger events in multiple shapes. In controlled testing, we observed two: a direct shape ({ targetId, post, subreddit }) and a TriggerEvent-wrapped shape ({ postUpdate: { post: { ... } } }). Our initial trigger routes only handled the direct shape. Live trigger deliveries using the wrapped shape were reaching the route and returning nothing — silently.
The fix was a two-pass extraction: attempt the direct shape first, then unwrap from the TriggerEvent envelope. We also added sanitized payload-shape logging (reviewlock.trigger.payload_shape) that writes the structural shape of every trigger delivery to Devvit logs without writing any payload values. This let us confirm live delivery shape from devvit logs without exposing content.
3. Redis consistency across partial write sequences
A single lock creation involves writes to five or more Redis keys: the lock record, the active-lock sorted set, the active-by-target hash, the target-to-lock string binding, and the audit event. If any of these fail mid-sequence, the result is worse than no lock — the target index says a lock exists, but the lock record is missing or the audit wasn't written.
We implemented rollback behavior at every failure junction:
- If
approve()succeeds butignoreReports()fails → store lock asfailed, tell the moderator clearly. - If
ignoreReports()succeeds but the Redis lock write fails → attemptunignoreReports()rollback. - If that rollback also fails → write a
runtime_failureaudit event anyway so the divergence is visible. - If audit persistence fails after a successful reopen → write a compensating runtime failure event.
The decisions log includes more than 20 separate entries (D031, D064, D084, D085, D095–D107) specifically about rollback compensation. These weren't theoretical. They came from working through what a real partial failure looks like and making sure ReviewLock leaves an honest ledger even when Redis is having a bad day.
4. Race conditions on concurrent trigger delivery
Reddit can deliver the same trigger event more than once. More critically, a report trigger and an update trigger for the same target can arrive within milliseconds of each other. If a report trigger reads "active lock" and begins the suppress path at the same moment an update trigger reads the same state and begins the reopen path, one will silently overwrite the other — or both operations will commit, leaving the lock in an inconsistent state.
We used Redis NX (set-if-not-exists) guards per target to serialize lock mutations. Only one trigger per target can hold the modification lease at a time. If a second trigger arrives while a lease is held, it gets a structured "retry" result rather than corrupting state. These contention events are treated as retryable runtime uncertainty and surfaced in the dashboard — not silently swallowed.
5. Claim discipline at scale
With 30 waves, 444 tests, and extensive playtest output, the hardest non-technical challenge was keeping the submission honest. When you've built something thoroughly and tested it at every layer, the temptation to describe it as "production ready" or "verified across all paths" is real. But "it works in local tests" and "it was confirmed in a live Devvit playtest session" are different claims, and conflating them would be a failure of the product.
We maintained a live CLAIM_CHECK.md and RUNTIME_PROOF.md throughout the build. Every submission document was audited against the claim boundary before submission. The RUNTIME_PROOF.md file has a capability matrix with explicit verified, unverified, and blocked rows — and the Devpost submission cites only the verified rows.
Accomplishments That We're Proud Of
The core loop works in live Devvit playtest
This is the one. Not in unit tests, not in local integration harness — in actual Reddit, inside an actual r/reviewlock_dev subreddit, with actual Devvit runtime, with evidence captured in devvit logs and visible in the dashboard.
| Capability | Status | Live Evidence |
|---|---|---|
| Dashboard custom post renders inside Reddit | Verified | Playtest v0.0.10.2 on r/reviewlock_dev |
| Redis smoke from authorized WebView | Verified | Verify runtime → redis verified |
| Reddit context smoke from authorized WebView | Verified | Verify runtime → redditContext verified |
Post approve() live behavior |
Verified | Controlled post t3_1tm8nak |
Post ignoreReports() live behavior |
Verified | Controlled post t3_1tm8nak |
Post unignoreReports() via dashboard unlock |
Verified | Controlled post t3_1tm8nak, unignoreReports verified in runtime status |
| Repeat-report suppressed on unchanged locked post | Verified | Controlled report on t3_1tm8nak → Reports suppressed = 1, audit Report Suppressed 5/25/2026, 3:29 PM |
| Post body edit breaks the lock | Verified | Controlled edit on t3_1tnfgqf → fingerprint c322d267 → fc05f41b, Reopened after edit, audit Lock Reopened 5/25/2026, 10:53 PM |
| Comment body edit breaks the lock | Verified | Controlled edit on t1_ontlx1k → fingerprint 9da841c1 → 20abf990, Reopened after edit, audit Lock Reopened 5/25/2026, 11:05 PM |
| Dashboard reuse on subreddit menu re-launch | Verified | Public r/reviewlock_judges, menu reopened existing post 1tp3jxl |
| Comment report trigger delivery | Implemented, not yet live-verified | Live proof pending |
| Post NSFW / spoiler / flair update triggers | Implemented, not yet live-verified | Live proof pending |
The demo scenario numbers from the public judging dashboard (r/reviewlock_judges, uploaded version 0.0.12): 12 active locks, 47 reports suppressed, 5 reopened after edit, 13 audit events — all visibly labeled as seeded demo data, isolated in the reviewlock_demo namespace, never touching live subreddit state.
444 tests across 43 test files — including an end-to-end scenario
The full test suite covers:
- Fingerprint engine — 25 stress cases: whitespace normalization, markdown line break changes (material vs. not), body cleared, body rewritten, title changed, URL changed, flair change, NSFW/spoiler toggle, and fail-open on missing content.
- Lock state transitions — active → reopened → dismissed; active → unlocked; rollback paths.
- Redis key construction and namespace isolation — every key pattern, cross-subreddit isolation, demo namespace separation.
- Metrics aggregation — daily and target-level counters, partial failure compensation.
- Trigger decision logic — report trigger suppress/reopen/uncertain paths; update trigger change detection paths.
- Full integration scenario (
src/fullScenario.test.ts) — lock post, suppress × 2, edit post, reopen; lock comment, suppress × 1, edit comment, reopen. Final state:locksCreated: 2, reportsSuppressed: 3, locksReopened: 2, zero active locks, two items in reopen queue. - Client state and render helpers — render formatting, state transitions, forbidden-copy assertions.
- Runtime proof validation — strict ISO timestamp validation, capability record shape validation.
The integration scenario was also where we found (and fixed) a real bug: two distinct repeat-report events at the same timestamp were collapsing into a single audit entry. The event count was right but the audit trail was wrong. The fix was to include event IDs in audit record identifiers — and the scenario test now asserts that three suppressed reports produce three separate audit entries.
A safety posture that held across 30 waves
The safety audit (Wave 25) ran a comprehensive grep across every source file, test, documentation file, and config for prohibited patterns: reporter username storage, AI dependencies, external services, automatic removal, moderator surveillance, and forbidden product copy. It found zero violations in production code.
Every hit was in:
- A test asserting that the forbidden pattern does not appear in production output
- A guardrail doc or scan script
- A limitation note in documentation
The submission went through Wave 25 with the same product thesis it started with on day one: lock reviewed content until it changes. No feature creep. No scope drift. No safety compromise.
143 engineering decisions that held the line
The decisions log isn't just documentation. It's the architectural immune system. Every time a new wave encountered a tricky edge case, the decisions log either had a prior entry covering it — or the new decision was written and added. By the end, the project had 143 decisions resolving everything from Redis NX correctness (D012, D041, D091) to WebView subreddit context handling (D011, D026, D034) to audit ID uniqueness (D015) to the exact semantics of "fail open on fingerprint uncertainty" (D003, D051, D133).
What We Learned
Devvit's execution model inverts some web assumptions.
The first time we called a Devvit Web route from curl and got back nothing useful, we had to stop and re-learn how authorization works on this platform. There are no API keys you pass in a request header. Authorization flows through Reddit's WebView injection. Forms are server-bound with server-issued tokens, not client-submitted HTML. The UiResponse contract from Devvit's menu and form handlers is strict — returning { ok: true } (a totally reasonable web convention) fails at the Devvit layer because ok is not a recognized response key. These discoveries required real time investment to find, understand, and harden against.
Fail-open is not conservatism. It is the product thesis.
Every time ReviewLock had a choice between "suppress and hope" and "surface and let a human decide," we chose to surface. This is the product promise: review state is tied to content integrity. If that integrity cannot be verified — because a trigger payload was incomplete, because a Redis read returned nothing, because a re-fetch through the Reddit API threw — then the reviewed state is uncertain. Uncertain means the lock cannot be trusted. The lock cannot be trusted means a moderator needs to look again.
An honest moderation tool is more valuable than a confident one.
Idempotency is not a nice-to-have for event-driven systems. It is a correctness requirement.
Devvit trigger delivery is not guaranteed exactly-once. We designed every trigger handler — report suppression, lock creation, reopen, dismiss, unlock — so that receiving the same event twice always converges to the same final state. Redis NX guards prevent double-creation. Event-ID-scoped audit entries prevent double-counting. Reopen mutations check whether the target is still active before breaking the lock. Getting this right required thinking through every partial-failure scenario and asking: "if this exact sequence runs again, does the ledger stay consistent?"
Claim discipline is a product feature, not a documentation chore.
The runtime proof panel exists in the ReviewLock dashboard because we needed to give moderators (and ourselves) an honest account of what has been verified in a live Devvit environment and what hasn't. Writing RUNTIME_PROOF.md and CLAIM_CHECK.md and keeping them current throughout 30 waves of development was the discipline that made us confident enough to submit. We knew exactly what we had proven. We knew exactly what we hadn't. That clarity is its own kind of accomplishment.
What's Next for ReviewLock
The hackathon built the proof of concept. The product roadmap is real.
| Priority | Item | Why |
|---|---|---|
| High | Live-verify comment report trigger delivery | The only remaining gap in the core loop proof. |
| High | Live-verify post NSFW, spoiler, and flair update triggers | Implemented and locally tested — needs live payload capture. |
| Medium | First-run setup surface for lock expiry defaults and reason presets | Subreddit admins need control over team-level defaults. |
| Medium | Recruit 1–3 real moderation teams for post-hackathon playtesting | Real subreddit data changes the product in ways that controlled tests can't predict. |
| Lower | Reopen queue filters (by reason, by content kind, by date range) | Larger teams will want to triage by reason: content_changed vs. flair_changed vs. nsfw_changed. |
| Lower | CSV / JSON export of metrics and audit log | Mod teams track their own KPIs. Give them the data. |
| Lower | Lock expiry settings per lock, not just per subreddit | Some content should auto-expire after 7 days. Some should hold indefinitely. |
The communities we most want to get this into:
- A politics or local news subreddit where controversial but rule-compliant threads keep drawing repeat reports after a team decision — and where edit-after-approval abuse is a real risk.
- A high-volume advice community where later moderators keep re-reviewing content because they don't know a prior decision was already made.
- A marketplace or deals subreddit where approved posts draw reports after flair or link compliance checks — and where an edit to the title or URL genuinely should reopen review.
That is the version we're building toward. Not a hackathon demo. A tool that actually changes how a moderation team spends those 90 seconds, that third time they open the same post they already reviewed.
Technical Reference
Domain types (excerpt from src/shared/schema.ts)
| Type | Purpose |
|---|---|
ReviewLockRecord |
Full lock record: target, hash, reason, status, suppressed count, reopen metadata, runtime warnings |
ContentFingerprint |
Version-tagged hash with normalized input and computation timestamp |
ReopenEvent |
Fingerprint delta record: old hash, new hash, reason, dismissal state |
AuditEvent |
Timestamped moderation event: kind, actor, target, structured data |
DailyMetrics |
Aggregate daily counts: locks created, reports suppressed, locks reopened |
RuntimeProofCapability |
Per-capability proof record: name, status (verified / unverified / failed), evidence, notes |
Lock status lifecycle
lock_created
│
▼
[ active ]
│
├── author edits / trigger detects fingerprint change
│ └──► [ reopened ]
│ │
│ └── mod dismisses reopen
│ └──► [ unlocked ]
│
├── mod manually unlocks
│ └──► [ unlocked ]
│
├── lock expiry (future)
│ └──► [ expired ]
│
└── Reddit API or Redis failure during creation
└──► [ failed ]
Registered Devvit triggers and menu actions
{
"menu": [
"Lock review (post) → /internal/menu/lock-post",
"Lock review (comment) → /internal/menu/lock-comment",
"Unlock review (post) → /internal/menu/unlock-post",
"Unlock review (comment) → /internal/menu/unlock-comment",
"Open ReviewLock (post) → /internal/menu/open-post",
"Open ReviewLock (comment) → /internal/menu/open-comment",
"Open ReviewLock dashboard (subreddit) → /internal/menu/open-dashboard"
],
"triggers": [
"onPostReport → /internal/triggers/on-post-report",
"onCommentReport → /internal/triggers/on-comment-report",
"onPostUpdate → /internal/triggers/on-post-update",
"onCommentUpdate → /internal/triggers/on-comment-update",
"onPostNsfwUpdate → /internal/triggers/on-post-nsfw-update",
"onPostSpoilerUpdate → /internal/triggers/on-post-spoiler-update",
"onPostFlairUpdate → /internal/triggers/on-post-flair-update",
"onAppInstall → /internal/triggers/on-app-install",
"onAppUpgrade → /internal/triggers/on-app-upgrade"
]
}
App listing: https://developers.reddit.com/apps/reviewlock
Public judging dashboard: https://www.reddit.com/r/reviewlock_judges/comments/1tp3jxl/reviewlock_dashboard/
Developer: u/BrightyBrainiac
Built With
- devvit
- hono
- node.js
- redditapi
- redis
- typescript
- vite
- vitest
Log in or sign up for Devpost to join the conversation.