How dose.wiki is built
The public site, editor, data pipelines, and database.
dose.wiki is a harm-reduction encyclopedia of substances, subjective effects, and trip reports. Three systems share its PlanetScale Postgres database: a public website that displays content and accepts reader submissions, a protected /dev editor for writing, review, and citations, and command-line pipelines that scrape, generate, and migrate content.
01 The big picture in numbers
- 267
- Published substance articles
- 233
- Subjective effects
- 272
- Trip reports
- ~1,400
- TS/TSX files in
src/ - ~700
- Pipeline scripts in
scripts/
Pipelines produce most of the site's content and media. Reading only src/ shows the website, but not the tooling that fills it.
02 System overview
Scrapers and AI workflows supply content; readers submit reports and feedback. Shared server functions connect these inputs and the editor to the database. The public site serves published records, with replication files delivered separately from Cloudflare R2.
server/*.ts run inside the Next.js app and scripts through the Postgres adapter in lib/postgres/runtime/. Browser saves and submissions enter through Next.js route handlers in src/app/api/; access controls differ by route.03 Tech stack: what each tool is for
App Router server components fetch data and render mostly static HTML, prerendered at build time where possible. Vercel hosts the app.
PlanetScale Postgres hosts the production database. Authored queries, mutations, and schema definitions live in server/; lib/postgres/runtime/ executes them inside database transactions.
auth.ts configures a username-and-password credentials provider backed by memberships. Passwords are stored as scrypt hashes, not environment-variable passwords. Middleware keeps protected routes on the editor hosts; roles determine access.
An API gateway to multiple language models. Section synthesis, dosage and duration rewrites, quote extraction, and citation workflows each select a model in their configuration or entry module. Model choices change over time.
Tailwind provides utility-first styling from the tokens in src/theme/; Radix supplies accessible UI primitives. Framer Motion handles animation, and React Hook Form manages editor forms.
R2 stores replication files. Turnstile adds a CAPTCHA to site feedback; other intake controls and media handling are described below.
Bun manages packages; Vitest runs the tests (~500 test files in the recorded inventory). Zod schemas in src/schema/ and data/schemas/ reject invalid article structures before storage or rendering. Structural validation does not establish factual accuracy. Molecule diagrams use the vendored OpenChemLib fork in vendor/openchemlib, checked by scripts/chemistry/verify-openchemlib-fork.mjs.
04 Repository map: where things live
Four directories hold most code: src/ for the app, server/ for database functions, scripts/ for pipelines, and lib/ for shared server helpers. The rest holds documentation, assets, vendored dependencies, and configuration.
dose.wiki/ ├── src/ · Next.js application │ ├── app/ · public pages, /dev, API routes │ ├── features/ · domain modules │ ├── components/ · shared layouts, pages, UI primitives │ ├── schema/ · Zod article schemas │ ├── data/ · copy seeds, JSON artifacts, generated maps │ ├── theme/ · design tokens │ └── middleware.ts · /dev access and host policies ├── server/ · schema.ts and table functions, executed on Postgres ├── scripts/ · data pipelines (section 08) │ ├── parsers/ batch/ citations/ prepopulate/ │ ├── replications/ contributors/ chemistry/ legality/ │ ├── data-ops/ migrate/ seed/ build/ deploy/ reports/ │ ├── tools/ perf/ config/ eslint-plugins/ │ └── lib/ · CLI plumbing, OpenRouter client ├── lib/ · app and script server helpers │ ├── data/ · publicData.* database readers │ ├── next/ · route loaders, copy blocks, host policies │ ├── postgres/ · Drizzle schema and native callable runtime │ └── auth/ http/ og/ citations/ runtime/ ├── vendor/openchemlib/ · vendored molecule-rendering fork ├── public/ · molecule images, flags, favicons ├── docs/ · architecture docs, ADRs, operations and workflow guides ├── data/ content/ · datasets with licenses, and every authored prose file └── README.md ARCHITECTURE.md CONTRIBUTING.md AGENTS.md
Citation auditing and legality research run as editorial agent workflows maintained outside this repository; the runbooks they follow are docs/workflows/citations.md and docs/workflows/legality.md. Other workflow guidance lives beside its scripts or in docs/workflows/.
05 Frontend: routes and feature modules
src/app/ maps URLs to page routes and ~70 API handlers. Page routes fetch data, prepare metadata, and pass content to the rendering logic in src/features/, grouped into 15 domain modules.
/[slug]: the core substance article./substances,/effects,/effects/[effectSlug],/reports,/reports/[slug]: content catalogs and entries./replications, withaudio,tutorials,artist/[key], and[slug]routes: gallery browsing and playback./reports/submit: trip-report intake./about/feedback: site feedback; each article also has a feedback box./psychoactive,/chemical-classes,/mechanism,/category: classification indexes.- Other pages:
/interactions,/search,/about,/contributors,/articles,/blog,/mantras,/documentation-style-guide. /open-data/*.jsonand/api/v1: machine-readable data. Exports are linked from/about#data;/dataredirects there./docs/how,/docs/code,/docs/license: article methodology, this page, and licensing.
/dev: a catch-all page (src/app/dev/[[...segments]]) dispatches ~17 tabs: articles, writing, citations, trip-report submissions, article and site feedback, replications, molecules, copy, banners, contributors, tags, index layout, change log, and profiles./review: a standalone, full-width article-review workbench./api/dev/*: ~15 per-tool write endpoint families./api/save-article: whole-article saves./sign-in: next-auth sign-in. Protected routes require an authorised session.
NEXT_PUBLIC_SITE_FLAVOR selects dose.wiki or Effect Index at build time. src/config/siteFlavor.ts controls their navigation, wordmarks, and available routes; both publications share content records. Public DoseWiki, editor DoseWiki, and Effect Index are separate build artifacts and deployments. Only the editor carries general admin credentials; public DoseWiki has restricted create-only intake permissions, and Effect Index is read-only.
article/: dosage tables, duration charts, interaction grids, citations, and article feedback.effects/: effect rendering, including the custom VCode markup.reports/: report rendering and submission forms.replications/: gallery exploration, media viewing, and artist showcases.- Ten smaller modules:
blog/,articles/,chemical-classes/,coverage/,mantras/,site-feedback/,mailing-list/,effect-index/,psychoactive-summaries/,theme-lab/.
forms/: one React Hook Form section per article field group.tools/: ~14 tools, including substance, replication, copy, molecule, and banner editors; citation review; three intake queues; contributors; writing; and index layout.save-orchestrator/: coordinates multi-endpoint saves.context/,notices/,profile/,tags/,prompts/: editor state, notifications, profiles, tags, and prompts.
06 The data layer: what is in the database
server/schema.ts declares the data shapes (~45 in the recorded inventory); most tables have a matching server/<table>.ts query-and-mutation module. The groups below describe purpose, not visibility: editorial records can include public profiles, while content configuration can hold unpublished drafts. Related tables share a row. Stored-record estimates are historical snapshots, distinct from the live published counts above.
| Table | What it holds |
|---|---|
| Content and configuration | |
substanceIndex | One record per substance (~580 stored): summary, dosage, duration, pharmacology, harm potential, legality, citations, and references. |
subjectiveEffects | Effect articles such as geometry and euphoria, with VCode bodies, galleries, and audio replications (~230 stored). |
tripReports / tripReportSubstances | Trip reports (~165 stored) linked to substances and doses. tripReportSubstances indexes report/substance-name pairs so article pages can find related reports without scanning the table. |
replications / effectIndexArticles | Replication gallery metadata for videos, images, and audio, plus Effect Index methodology articles. R2 holds the media files. |
categoryLayout | The curated psychoactive layout read by /substances. Saving indexLayouts updates this table; the public page does not read indexLayouts directly. |
indexLayouts / siteConfig | Hand-curated classification trees (psychoactive / chemical / mechanism) and about-page configuration. |
moleculeOverrides | Canonical substance and chemical-class drawings from the Molecules editor: editable MOL data, rendered SVG, and seeded, hand-drawn, or template-aligned provenance. Articles display the SVG; without a record, they show no drawing. |
warningBannerPresets | Safety-banner text and its explicit substance-slug or sitewide scope. Editors can change the wording without redeploying. |
copyBlocks | Editable site text, one record per block, including this page. Copy Studio saves changes without a deploy; missing keys fall back to the checked-in JSON seed. |
reagentTests | Point-in-time ProtestKit reagent-test results, keyed by substance slug. |
substanceGalleries | Per-substance Replication Showcase curation: a pinned first item and suppressed replications slugs. |
replicationPlaylists | Named, ordered replication sets applied to gallery drafts. Saving a playlist does not publish it. |
| Editorial records | |
tripReportSubmissions | Private /reports/submit intake. Editors review submissions before promoting them into public tripReports records. |
memberships | Email-linked membership and role records, read by next-auth at sign-in. |
changelog | Editor-save history: author, time, and Markdown diff. Viewable at /dev/change-log. |
contributorProfiles | Public bios, aliases, links, and avatars for contributors. |
articleFeedback | Private issues and suggested edits submitted from article feedback boxes. Visible only to authorised editorial users, never published. |
siteFeedback | Private /about/feedback submissions. Visible only to authorised editorial users, never published. |
mailingListSubscribers | Private signups shared by the portal's four public sites, submitted through each site's own /api/subscribe route. |
moleculeClassTemplates | Editor-authored scaffold-orientation templates for the Molecules editor. These editor-only cosmetic settings do not change published molecules. |
| Pipeline data | |
articleSources | Raw per-substance text from the source sites, used for AI quote extraction. |
prompts / quotes | Editable section-generation prompts and verbatim quotes extracted from sources. |
citationEvidence | Citation-audit records linking claims to reference IDs, supporting quotes, and review status. |
replication* provenance + identity | About 17 replication* and contributor* tables retain source attribution, artist/contributor identity bindings, alias and taxonomy evidence, duplicate reconciliation, date research, and reversible merge/social operations. Separate display records keep gallery reads small. |
effectIndexArchive | Lossless Effect Index source records retained for reuse by Effect Index, outside dose.wiki's public read paths. |
generatedPublicationOperations | Successful reviewed section publications. Proposal IDs prevent the same publication from being applied twice. |
07 Four kinds of traffic, one database
Public site · read path
Anyone can read without signing in. Most pages are static, open to crawlers, and include search metadata.
Server components use route loaders in lib/next/ and cached lib/data/publicData.* readers. Cache tags identify related data for invalidation.
Browsers never connect to the database directly. Shared caches avoid a query on every visit, though generating or refreshing a page can read the database. Flow A follows that path.
Public writes · reader submissions
Trip reports, article feedback, site feedback, and mailing-list signups require no sign-in.
Each form POSTs to its own rate-limited API handler with a honeypot, payload cap, and hashed IP. Site feedback also requires Turnstile. Records enter private tables.
Editors can publish reviewed trip reports through Flow C. Feedback and mailing-list signups remain private.
/dev editor · write path
Authorised users sign in on the editor host. Membership roles are admin, editor, translator, contributor, and viewer; viewer has no editor access, and the other roles have distinct permissions. src/middleware.ts enforces the page and host gates.
Whole-article saves use save-orchestrator/ and /api/save-article; other tools use /api/dev/*. Routes enforce session roles and named rate limits. The browser never holds an admin key.
Source-backed generation runs in authenticated local batches. The browser editor supports manual review and editing without exposing scraped source text.
Machine reads · open data + API
Researchers, mirrors, and other applications can read the data without signing in.
/open-data/SubstanceIndex.json, /open-data/EffectIndex.json, and /open-data/TripReports.json provide daily snapshots with per-dataset licensing metadata. /api/v1 provides versioned JSON endpoints described by /api/v1/openapi.json.
Both interfaces are read-only and cache-friendly. Pages still draw their content from the database, not the export files.
Licensing varies by dataset. Base data is CC0, but exports include exceptions: TripSit's terms on substance interactions, third-party replication media in the effect index, and authors' rights on legacy trip reports. See License & reuse.
08 Scripts and pipelines: the third system
Node and Bun scripts parse scraped sources (scripts/parsers/), prepopulate high-confidence fields (scripts/prepopulate/), and draft prose through OpenRouter (scripts/batch/). Citation and legality workflows research and check supporting sources; claims can remain uncited. Editors review the results in /dev. Article text and citations have separate, ongoing review workflows, and each article displays its review status.
Local execution does not imply a local database. Check the target before any write. Production commands require DATA_BACKEND=postgres, an explicit --target or TARGET_POSTGRES_URL, an allowlisted operation, and every command-specific guard. Shared wrappers default to a dry run and require --write, --confirm-write=<operation>, and a matching --expected-deployment where supported. DATA_WRITES_FROZEN=1 blocks writes. Neither credentials nor a successful dry run authorise publication.
scripts/lib/data-ops-run-context.mjs resolves intent-scoped tokens (DATA_ADMIN_TOKEN_<INTENT>); scripts/lib/production-write-command.mjs supplies shared write gates. Migration-style scripts use pre-write backups and scripts/data/audit-logs/. Follow docs/operations/data-credentials.md for native environment diagnostics, the current allowlist, and the exact write procedure.
Workflow families have their own subdirectories and package commands:
scripts/citations/verdict-*: citation-support audits, run by the citation workflow indocs/workflows/citations.md.scripts/legality/: legal-source research, run by the legality workflow indocs/workflows/legality.md.scripts/replications/: media intake and R2 migration, the largest family.scripts/contributors/: contributor avatars.scripts/chemistry/: molecule and chemistry audits.scripts/migrate/,scripts/data-ops/: guarded Postgres migration and export tooling.scripts/build/: social-card and theme-CSS generators.
scripts/lib/workflow-command-surface.mjs and its sibling citation-command-surface.mjs index runnable commands; package.json defines the npm run entry points.
How substance articles are made explains scraping, verbatim quote extraction, section prompts, citation proposal and auditing, and human review. It publishes unedited extraction and section-generation prompts, but not citation or legality workflow prompts. Published prompts may differ from those used for a particular article.
09 The replications media platform
Replications are artist-made videos, images, and audio recreations of subjective effects. Postgres stores metadata; Cloudflare R2 stores files under content-addressed keys derived from their contents. server/lib/replicationUrls.ts constructs public URLs using REPLICATION_MEDIA_BASE_URL, required in every deployment.
scripts/replications/ uploads and verifies reviewed media before importing metadata, taxonomy, and attribution into Postgres. The Reddit archive follows docs/workflows/replication-intake.md. In Replication Studio, editors curate carousels, substance showcases, and playlists, and reconcile duplicates. Separate provenance and identity records preserve the source history.
substanceGalleries controls pinned and suppressed showcase items; replicationPlaylists supplies ordered sets to gallery drafts; siteConfig stores the Effect Index homepage carousel order. These database edits need no deploy. Public reads use lib/data/publicData.replications.ts, /api/replications/gallery, and /api/replications/showcase; both JSON endpoints are cacheable.
Known artists are credited; unidentified creators are shown as Unattributed. Rights default to unknown, independently of artist attribution. Source posts and posters are retained losslessly. The gallery's fair-use position appears in src/features/replications/replicationsFairUseCopy.ts and License & reuse.
10 Three flows, step by step
Flow A: a reader opens dose.wiki/lsd
Readers usually receive a cached page. When the page is generated or refreshed, this sequence runs:
Route
The server component in
src/app/[slug]/page.tsxruns.Fetch
A
lib/next/loader calls the tagged, cached, read-onlylib/data/publicData.*readers.Validate
lib/data/publicData.substanceContract.tsvalidates the record against the Zod schemas insrc/schema/.Render
src/features/article/renders sections, tables, and citations.Serve
The page includes JSON-LD search metadata and a substance-specific social card. Incremental static regeneration allows refresh after an hour; editor saves also invalidate affected paths and cache tags.
Flow B: an editor saves an article in /dev
Edit
React Hook Form holds edits in
src/features/dev/forms/.Collect changes
save-orchestrator/collects articles with unsaved changes.Check access and input
/api/save-articlechecks the session role, rate limit, and Zod contract.Save
substanceIndex.saveSubstanceswrites the changes and creates achangelogentry.Refresh
Publication invalidates local caches and requests revalidation on public deployments. A toast reports the save result.
Flow C: a reader submits a trip report
Write
/reports/submitcollects the report usingsrc/features/reports/submissions/.Check the submission
/api/trip-report-submissionsenforces a rate limit, honeypot, and 128 KB cap. The submitter's IP is stored only as a salted hash.Store privately
The report enters
tripReportSubmissions, not the public catalog.Review
The
/devTrip Report Portal shows the queued report with a needs-review badge.Approve for publication
An editor previews the result, assigns authorship, and confirms through
/api/trip-report-submissions/[id]/promote.Publish
A
tripReportsrecord makes the report available at/reports/[slug].
11 Recurring vocabulary
- Substance article
- A structured substance record with about 15 sections, defined by
src/schema/and stored insubstanceIndex. - VCode
- Custom markup for effect articles, stored with a parsed syntax tree (AST) and rendered by
src/features/effects/. - Database
- The shared Postgres store for content, editorial records, and pipeline data. A database record is not necessarily public.
- Site flavor
- The build-time choice of dose.wiki or Effect Index, distinct from the public/editor deployment boundary.
- Copy block
- A keyed piece of editable site text.
getCopy()reads it fromcopyBlocks, falling back to the checked-in JSON when absent.
12 Where to start reading
For a first pass through the code:
README.md,ARCHITECTURE.md: orientation, routes, package commands, and system layout.server/schema.ts: the data model; start withsubstanceIndex.src/app/[slug]/page.tsx→lib/next/routeLoaders.substances.tsx→lib/data/publicData.*→src/features/article/: follow the public read path in Flow A.src/middleware.ts,auth.ts: host routing, sign-in, and roles. Verify deployment routing with a production-shaped build, not justnext dev.src/lib/http/protectedRouteOperation.ts,lib/http/rateLimitPolicy.ts: protected API handling, session roles, named rate limits, and payload caps. Public intake uses separate controls.src/features/dev/save-orchestrator/,src/app/api/save-article/: follow Flow B's saves and audit trail.scripts/lib/workflow-command-surface.mjs: find batch, citation, replication, migration, and sync entry points.
Caveats about this page
Code and stored-record counts are approximate inventory snapshots; check the current tree and database. This overview simplifies rate limits and host routing. Consult AGENTS.md, docs/glossary.md, and docs/architecture/ before operational changes.