Skip to main content

dose.wiki: codebase overview

Next.js 16 + React 19 + Convex, written for someone new to the repository

dose.wiki is a harm-reduction encyclopedia: a public website of substance articles, subjective-effect documentation, and trip reports. The repository contains three systems. The public Next.js site renders content and accepts trip reports and feedback from readers. The login-protected /dev editor is where editors write, review, and cite that content. Command-line pipelines scrape, generate, and migrate it. All three read and write the same Convex database; what Convex holds is what the site serves.

01 The big picture in numbers

267
Published substance articles
233
Subjective effects
166
Trip reports
~1,400
TS/TSX files in src/
~700
Pipeline scripts in scripts/

The scripts/ directory holds around 700 script files, compared with around 1,400 TS/TSX files in src/, because pipelines produce most content and media. Scripts scrape source wikis, generate prose through OpenRouter, tie claims to real literature, and move replication media through Cloudflare R2. Editors review trip-report submissions before publication. Article text and citations have separate, ongoing review workflows; each article shows its review status. If you read only src/, you see the site but not the tooling that fills it.

02 System overview

Every system in the repository reads and writes Convex (a hosted realtime database with serverless functions). Content enters through scrapers, AI pipelines, and reader submissions. Editors change it in /dev. The public site reads it, and replication media is served from Cloudflare R2. The colors below keep the same meaning for the rest of this page.

Public site
/dev editor
Convex data
Scripts & pipelines
External / AI services
System overview: scraped sources and OpenRouter AI feed the data pipelines, which write metadata to the Convex database and media to Cloudflare R2; the Next.js app serves the public site and the /dev editor, and rate-limited public intake routes accept trip reports, feedback, and signups
There is no separate REST backend. Convex hosts the database and the server functions (convex/*.ts) that read and write it. The Next.js app, the /dev editor, and the scripts all connect to the same Convex deployment. Public pages read through the lib/convex/publicData.* helpers. Browser writes (editor saves, reader submissions) go through Next.js route handlers under src/app/api/. Editor saves require an authenticated session with the appropriate role. Public submissions do not require sign-in; they use the abuse controls described in section 07. Both paths have named rate limits. Scripts use an admin key. The one extra piece of infrastructure is Cloudflare R2, which stores replication media bytes (section 09).

03 Tech stack: what each tool is for

Framework
Next.js 16 + React 19

App Router with server components by default: pages fetch data on the server and serve mostly-static HTML, prerendered at build time wherever possible. The app deploys on Vercel.

Database + backend
Convex

Hosted document database with TypeScript server functions (queries, mutations). The convex/ directory is the backend. Every environment (local dev, previews, production) targets the single production deployment (glad-minnow-656); the dev deployment is retired (see AGENTS.md).

Auth
next-auth: username and password

auth.ts configures sign-in: a single username + password credentials provider backed by the Convex memberships table (scrypt hashes, never env passwords). middleware.ts protects /dev and keeps editor routes on the editor hosts. The membership role is admin, editor, contributor, or viewer. Viewer has no editor access; the other roles have different permissions.

AI generation
OpenRouter SDK

One API gateway to many LLMs. Each workflow (section synthesis, dosage and duration rewrites, quote extraction, formal citations) selects its own model in its config or entry module. Model choices change over time, so this page does not list them. Source-backed generation uses the gateway through authenticated local batch workflows. The browser editor is for manual review and editing.

Styling + UI
Tailwind CSS 4 + Radix UI

Utility-first styling with a token-based design system in src/theme/. Radix supplies accessible primitives. Framer Motion handles animation. React Hook Form drives the forms in the editor.

Media + abuse controls
Cloudflare R2 + Turnstile

Replication media bytes live in Cloudflare R2 under content-addressed keys; Convex stores only the metadata (section 09 covers URL resolution and rollback). Cloudflare Turnstile gates the site-feedback form; the other public forms rely on honeypots, payload caps, and per-route rate limits.

Tooling + safety
Bun, Vitest, Zod

Bun is the package manager. Zod schemas (in src/schema/ and schemas/) validate every article shape before the app stores or renders it, rejecting data that does not match the required structure. These checks do not establish factual accuracy. The repository has ~500 Vitest test files. Molecule diagrams render through a vendored OpenChemLib fork (vendor/openchemlib) with its own verification script.

04 Repository map: where things live

Most of the code lives in four directories: src/ (the app), convex/ (the backend), scripts/ (the pipelines), and lib/ (server-side helpers shared between them). Everything else is documentation, assets, vendored dependencies, or configuration.

dose.wiki/
├── src/                      · the Next.js application (~1,400 files)
│   ├── app/                  · App Router: public pages, /dev, ~70 /api route handlers
│   ├── features/             · 15 domain modules; dev has ~400 TS/TSX files, including tests
│   ├── components/           · shared UI: layout, pages, ui primitives
│   ├── schema/               · Zod schemas validating article shapes
│   ├── data/                 · copy-block seeds, JSON artifacts, generated maps
│   └── theme/                · design tokens
├── convex/                   · the backend: schema.ts + one file per table
├── scripts/                  · pipelines (~700 script files; see section 08)
│   ├── parsers/  batch/  citations/  prepopulate/
│   ├── replications/  contributors/  chemistry/  legality/
│   ├── convex/  migrate/  seed/  build/  deploy/  reports/
│   └── lib/                  · shared CLI plumbing, OpenRouter client
├── lib/                      · server-side helpers shared by app + scripts
│   ├── convex/               · publicData.* readers: how pages read Convex
│   ├── next/                 · route loaders, copy blocks, host policies
│   ├── auth/  http/  og/  citations/  runtime/
├── vendor/openchemlib/       · vendored molecule-rendering fork
├── auth.ts  middleware.ts   · sign-in config + /dev gatekeeper
├── public/                   · molecule images, flags, favicons
├── docs/  notes-and-plans/   · architecture docs, ADRs, agent runbooks
├── .agents/skills/           · citation-pi + legality-pi workflows
└── README.md  ARCHITECTURE.md  CLAUDE.md  CONTEXT.md

The repository keeps two local skills: .agents/skills/citation-pi/ (the citation entailment audit) and .agents/skills/legality-pi/ (legality-source research). Other automated workflows are documented beside their owning scripts under scripts/ and docs/agents/, so executable behavior and operating guidance stay together.

05 Frontend: routes and feature modules

The code splits two ways. src/app/ holds thin route folders (one per URL, mostly data-fetching and metadata) plus ~70 API route handlers. src/features/ holds the rendering logic, grouped into 15 domain modules. A route file fetches from Convex and passes the data to a feature module.

src/app/ · public routes
  • /[slug]: a substance article (the core page)
  • /substances · /effects, /effects/[effectSlug] · /reports, /reports/[slug]: the three content catalogs
  • /replications (+ audio · tutorials · artist/[key] · [slug]): the replication gallery (section 09)
  • /reports/submit: the public trip report form; /about/feedback: the site feedback form; every article ends in a feedback box
  • /psychoactive · /chemical-classes · /mechanism · /category: classification indexes
  • /interactions, /search, /about, /contributors, /articles, /blog, /mantras, /documentation-style-guide
  • /open-data/*.json: three daily dataset exports (linked from /about#data; /data redirects there); /api/v1: a read-only public API with an OpenAPI spec
  • /docs/how · /docs/code · /docs/license: methodology, this page, licensing
src/app/ · protected routes
  • /dev: one catch-all page (src/app/dev/[[...segments]]) dispatching ~17 editor tabs (articles, writing, citation review, trip-report submissions, article + site feedback, replication studio, molecule editor, copy studio, banners, contributors, tag editor, index layout, change log, profile…)
  • /review: a standalone full-bleed article-review workbench
  • /api/dev/*: ~15 per-tool write endpoint families; /api/save-to-convex: the whole-article save target
  • /sign-in: next-auth sign-in entry; middleware.ts redirects everyone else

One codebase builds two publications, dose.wiki and the Effect Index sister site, selected at build time by NEXT_PUBLIC_SITE_FLAVOR. The route tree therefore also holds Effect Index surfaces; which ones render depends on the flavor (src/config/siteFlavor.ts).

src/features/ · public-facing (14 modules)
  • article/: renders substance articles (dosage tables, duration charts, interaction grids, citation markers, the feedback box)
  • effects/: effect pages, including the custom "VCode" markup renderer
  • reports/: trip report pages + the public submission form
  • replications/: gallery explorer, media viewer, artist showcases
  • ten smaller modules: blog/, articles/, chemical-classes/, coverage/, mantras/, site-feedback/, mailing-list/, effect-index/, psychoactive-summaries/, theme-lab/
src/features/dev/ · the editor (~400 TS/TSX files, including tests)
  • forms/: one React Hook Form section per article field group
  • tools/: the ~14 editor tools (substance editor, replication studio, copy studio, molecule editor, banner studio, citation review, the three intake queues, contributors, writing, index layout)
  • save-orchestrator/: coordinates multi-endpoint saves
  • context/ · notices/ · profile/ · tags/ · prompts/: editor state, toasts, profile & tag editing

06 The Convex data layer: what is in the database

Most tables have a matching convex/<table>.ts module that exports their queries and mutations; convex/schema.ts declares all ~45 shapes. There are three groups: content, editorial, and workflow. Readers see the content. The editorial records control who can change it, hold what readers have submitted, and audit every change. The workflow tables feed the pipelines. Small sibling tables share a row below.

TableGroupWhat it holdsDocs
substanceIndex
content
The main table: one document per substance with every section (summary, dosage, duration, pharmacology, harm potential, legality, citations, references).~580
subjectiveEffects
content
Effect articles (for example "geometry" and "euphoria") with VCode markup bodies, galleries, and audio replications.~230
tripReports / tripReportSubstances
content
Trip reports linked to substances and doses. The sibling tripReportSubstances table denormalizes (report, substance name) pairs so an article page can find a substance's reports through an index instead of a table scan.~165
tripReportSubmissions
editorial
The private intake queue behind /reports/submit. Editors review, then promote a submission into tripReports before anything goes public (Flow C in section 10).
replications / effectIndexArticles
content
Effect gallery metadata for video/image/audio recreations of effects (the media bytes live in Cloudflare R2, section 09), plus Effect Index methodology articles.
categoryLayout
content
The table that the public /substances page reads: the hand-curated psychoactive index layout. Saves to indexLayouts also update this table, so the page never reads indexLayouts directly.
indexLayouts / siteConfig
content
Hand-curated classification trees (psychoactive / chemical / mechanism) and about-page configuration.
moleculeOverrides
content
The canonical structure drawing for each substance and chemical class, saved from the /dev Molecules editor: an editable MOL block, its rendered SVG, and whether it was seeded, hand-drawn, or template-aligned. The public article shows this SVG; an article with no row shows no drawing.
warningBannerPresets
content
Substance-article safety banner presets: every reader-visible string plus an explicit slug list or sitewide scope. Rewording a banner is an edit, not a deploy.
copyBlocks
content
Editable site copy, one document per block; the checked-in JSON seed is the fallback when a key has no row. The /dev Copy Studio edits it without a deploy, including every paragraph of this page.
reagentTests
content
Point-in-time ProtestKit reagent-test results, keyed by substance slug.
substanceGalleries
content
Per-substance curation of the Replication Showcase: a pinned head and a suppression list over replications slugs.
replicationPlaylists
content
Reusable named replication playlists editors apply into a gallery draft. Nothing here publishes on its own.
memberships
editorial
Membership records linking each email to an admin, editor, contributor, or viewer role, read by Auth.js at sign-in. Viewer has no editor access; the other roles have different permissions.
changelog
editorial
Audit trail: when an editor saves, the table records who, when, and a markdown diff. Browsable at /dev/change-log.
contributorProfiles
editorial
Public bios, aliases, links, and avatars for contributors.
articleFeedback
editorial
Private per-article "report an issue / suggest an edit" intake from the box at the bottom of every article. Editor-only; never rendered publicly.
siteFeedback
editorial
Private site-wide feedback intake from /about/feedback (the one Turnstile-gated form). Editor-only; never rendered publicly.
mailingListSubscribers
editorial
Private mailing-list signups shared by the portal's four public sites, written only through the public /subscribe Convex HTTP endpoint.
moleculeClassTemplates
editorial
Editor-authored plain-scaffold orientation templates for the Molecules editor. Editor-only cosmetics; saving one never changes a published molecule.
articleSources
workflow
Raw scraped source text per substance, from the sites listed on How dose.wiki substance articles are made; this is the input the AI generators quote from.
prompts / quotes
workflow
The editable AI prompts used for section generation, and quotes extracted from sources.
citationEvidence
workflow
The citation audit trail: each claim in an article mapped to reference IDs, supporting quotes, and a review status.
replication* provenance + identity
workflow
Replication workflow records (~17 replication* and contributor* tables): source attribution, artist and contributor identity bindings, alias evidence, taxonomy evidence, duplicate reconciliations, date-research dossiers, and reversible merge/social operations. All these records are kept separate from the documents read when displaying the gallery.
effectIndexArchive
workflow
Lossless Effect Index source records retained for a future Effect Index deployment. Not part of any dose.wiki read path.
generatedPublicationOperations
workflow
Successful reviewed section publications. The proposal ID prevents the same generated section publication from being applied more than once.

07 Four kinds of traffic, one database

Public site · read path

Who: anyone, no login. The launched site is open to crawlers, and most pages are static with tuned search metadata.

How: server components read Convex through the lib/convex/publicData.* helpers behind route loaders in lib/next/ (read-only URL, tagged caches). Flow A in section 10 traces this path step by step.

Key property: there is no client-side database access. Pages prerender, then revalidate by cache tag when an editor saves.

Public writes · reader submissions

Who: anyone, no login. Trip reports (/reports/submit), per-article feedback (the box at the bottom of every article), site feedback (/about/feedback), and mailing-list signups.

How: each form POSTs to its own rate-limited route handler with a honeypot, a payload cap, and a hashed IP; site feedback adds a Turnstile captcha. Rows land in private intake tables.

Key property: nothing a visitor submits renders publicly until an editor reviews it in /dev. Trip reports are promoted (Flow C in section 10); feedback never publishes at all.

/dev editor · write path

Who: authenticated editors only. middleware.ts redirects everyone else to /sign-in. The Convex memberships role decides what an editor can change.

How: whole-article saves go through the save-orchestrator and /api/save-to-convex (Flow B in section 10); every other editor tool has its own /api/dev/* endpoint. Each route checks the session role and a named rate limit; the browser never holds an admin key.

Source-backed generation runs only through authenticated local batch workflows. The /dev editor supports manual review and editing without exposing scraped source text in the browser.

Machine reads · open data + API

Who: anyone who wants the raw data (researchers, mirrors, other apps).

How: /open-data/SubstanceIndex.json, EffectIndex.json, and TripReports.json serve daily Convex snapshots with per-dataset license envelopes. /api/v1 is a versioned read-only JSON API with a self-describing openapi.json.

Key property: both surfaces are read-only and cache-friendly. Convex stays the live source for pages.

Licensing is per dataset. The base data is CC0, but each export is "mixed": the substance index keeps TripSit's terms on its interactions field, the effect index references third-party replication media, and legacy trip reports keep their authors' rights (see the license page).

08 Scripts and pipelines: the third system

Alongside the site and the editor, scripts/ is a set of Node and Bun command-line programs, one subdirectory per workflow family, each exposed as an npm run command. Parsers turn scraped wiki text into structured data (scripts/parsers/). Prepopulation loads the high-confidence fields into Convex (scripts/prepopulate/), and batch generation drafts the prose sections through OpenRouter (scripts/batch/). Editors review the result in /dev. The citation and legality workflows research and check the sources supporting individual claims (scripts/citations/, scripts/legality/); a claim can remain uncited.

Scripts write to Convex as an administrator, behind gates. Where an editor save goes through an authenticated route handler, a script holds an admin token scoped to the one kind of write it performs (CONVEX_ADMIN_TOKEN_<INTENT>, resolved by scripts/lib/data-ops-run-context.mjs). Production writes share one gate, in that run context and the scripts/lib/production-write-command.mjs wrapper. A run is a dry run by default. A real write is permitted only with --write, an explicit TARGET_CONVEX_URL, a --confirm-write=<operation> phrase, and an --expected-deployment that must match the target.

The same module offers a pre-write backup and an audit log under scripts/data/audit-logs/, which the migration-style scripts use. npm run convex:doctor checks the environment before any of this (see AGENTS.md).

These write conventions also apply to the following workflow families:

  • Citation entailment audit: scripts/citations/verdict-*, run by the citation-pi skill.
  • Legality-source research: scripts/legality/, run by legality-pi.
  • Replication intake and R2 media migration: scripts/replications/, the largest family (section 09).
  • Contributor avatar tooling: scripts/contributors/.
  • Molecule and chemistry audits: scripts/chemistry/.
  • Convex migrations and exports: scripts/migrate/ and scripts/convex/.
  • Build-time generators for social cards and theme CSS: scripts/build/.

The registry in scripts/lib/workflow-command-surface.mjs (with citation-command-surface.mjs beside it) is the index of runnable commands.

The content pipeline has its own page. This page stops at the architecture. How dose.wiki substance articles are made covers what the scrapers collect, how verbatim excerpts are pulled from them, the prompts each section is written from, how citations are proposed and then audited, and where people review. It publishes the extraction and section-generation prompt files unedited, but not the citation and legality workflow prompts. It also explains why the published section prompts may differ from the live versions used to generate an article.

09 The replications media platform

Replications (artist-made video, image, and audio recreations of subjective effects) are the one content type whose bytes do not live in Convex. Convex stores metadata; Cloudflare R2 stores media. Every media object sits under a content-addressed R2 key, a key derived from the file contents. convex/lib/replicationUrls.ts resolves public URLs from the REPLICATION_MEDIA_BASE_URL deployment variable; unset it and the site falls back to Convex Storage, which is the rollback switch.

Media arrives through scripts/replications/. Reviewed source media (for example the Reddit archive, following docs/agents/reddit-replication-intake.md) is uploaded to R2 under content-addressed keys and verified, then a production import writes the gallery metadata, taxonomy, and attribution rows to Convex. Provenance and artist identity are stored in the side tables from section 06, keeping the documents read when displaying the gallery small. Editors curate the result in the /dev Replication Studio: featured carousels, per-substance showcases, playlists, and duplicate reconciliation.

substanceGalleries pins and suppresses what each substance's Replication Showcase shows; replicationPlaylists are reusable ordered sets applied into a gallery draft; the Effect Index homepage carousel is an ordered list in siteConfig. All three are data, so curation changes ship without a deploy. The public gallery reads through lib/convex/publicData.replications.ts and two cacheable JSON endpoints (/api/replications/gallery and /showcase).

Rights metadata defaults to unknown, and every replication credits its artist; source posts and posters are retained losslessly in the attribution tables. The gallery's fair-use position is spelled out in its own copy (src/features/replications/replicationsFairUseCopy.ts) and on the license page.

10 Three flows, step by step

Flow A: a reader opens dose.wiki/lsd

1Route
src/app/[slug]/page.tsx server component runs
2Fetch
A route loader in lib/next/ calls the lib/convex/publicData.* readers: tagged, cached, read-only Convex queries
3Validate
The substance contract (lib/convex/publicData.substanceContract.ts) Zod-validates the document against the shapes in src/schema/
4Render
src/features/article/ sections, tables, citations
5Serve
Hourly incremental static regeneration (ISR) refreshes prerendered pages, with tag-based revalidation on editor saves; each page also emits JSON-LD and a per-substance social card

Flow B: an editor saves an article in /dev

1Edit
React Hook Form state in src/features/dev/forms/
2Orchestrate
save-orchestrator/ collects articles with unsaved changes
3Gate
/api/save-to-convex: auth role + rate limit + Zod contract validation
4Mutate
substanceIndex.saveSubstances + auto changelog entry
5Refresh
The app revalidates the public paths. A toast confirms the save

Flow C: a reader submits a trip report

1Write
The form at /reports/submit (src/features/reports/submissions/) collects the report
2Gate
/api/trip-report-submissions: rate limit + honeypot + a 128 KB cap; the submitter's IP is stored only as a salted hash
3Quarantine
The row lands in the private tripReportSubmissions table; nothing is public yet
4Review
The /dev Trip Report Portal queues it; editors see a needs-review badge
5Promote
/api/trip-report-submissions/[id]/promote: an editor previews the result, assigns authorship deliberately, and confirms
6Publish
The report becomes a tripReports row and renders at /reports/[slug]

11 Recurring vocabulary

Substance article
The central data object: one large structured document per drug, with ~15 sections (dosage, duration, pharmacology, legality, and more). Defined by Zod schemas in src/schema/, stored in substanceIndex.
VCode
A custom markup format used for subjective-effect article bodies, stored alongside a parsed AST and rendered by src/features/effects/.
Convex deployment
One production deployment (glad-minnow-656). All environments (local dev, previews, production) read and write it directly; the old dev deployment is retired. Treat every Convex write as a production write (see AGENTS.md).
Site flavor
One codebase, two publications: NEXT_PUBLIC_SITE_FLAVOR selects dose.wiki or the Effect Index at build time, and src/config/siteFlavor.ts owns every difference in nav, wordmark, and flavor-gated routes. Content and Convex data are shared.
Copy block
A keyed piece of editable site copy in the copyBlocks table, read through getCopy() with the checked-in JSON seed as fallback. Nearly all public prose, including this page, is copy blocks, edited in the /dev Copy Studio.

12 Where to start reading

A reasonable reading order for a first session:

  1. README.md and ARCHITECTURE.md: the orientation docs from the maintainers. They cover routes, npm scripts, and system layout.
  2. convex/schema.ts: the data model that everything else is built around. Skim the substanceIndex table definition first.
  3. src/app/[slug]/page.tsxlib/next/routeLoaders.substances.tsxlib/convex/publicData.*src/features/article/: Flow A from section 10. This chain covers the whole public read path.
  4. middleware.ts + auth.ts: short files that show how /dev is protected and how roles work.
  5. src/lib/http/protectedRouteOperation.ts + lib/http/rateLimitPolicy.ts: the shared handler for protected API operations (session role, named rate-limit bucket, payload cap). Public submission routes have separate abuse controls and do not require sign-in.
  6. src/features/dev/save-orchestrator/ and src/app/api/save-to-convex/: Flow B. These files show how edits reach the database with an audit trail.
  7. scripts/lib/workflow-command-surface.mjs: the registry of pipeline commands. From here you can find whichever pipeline (batch, citations, replications, migrate, sync) you need.
Caveats about this page

Counts (substances, files, tests, tables) are point-in-time snapshots and will drift; prefer the live tree over the printed number. This page also simplifies operational detail such as rate-limit policies and host routing. AGENTS.md, CLAUDE.md, CONTEXT.md, and docs/architecture/ in the repository are the authoritative versions.