Justin Maier 0340f692bf docs(tests): correct the seam guard's prose, and the false absolutes each fix introduced (#4940)
* docs(tests): correct the seam guard's header and cut what argues rather than informs

The header claimed "you cannot call the function without importing the module it
lives in". A consumer reached through a re-exporting barrel matches neither half
of the detector; what saves the ledger is that the BARREL matches the `from`
clause and joins it, one file away from the consumer that gates. Stated, with
the limit, because a reader trusting the absolute would stop looking.

Cuts the dated "85/85 green" count, the change-log narration of what the ledger
used to be, the summary of the assertions below it, and a clause arguing the
guard is correct. The mutants are the proof now; the header does not need to
make the case.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(tests): restore the truncated clause on the corpus-loop control

The comment above `expectedCorpus` ended mid-sentence, dropping the half that
explains why scoping the corpus loop is green: no detector fixture can observe
that loop at all, because `verdictFor` seeds `SOURCE` directly and runs past it;
and every corpus member already carries the token such a filter would scope by,
so the filter excludes nothing. Green there means inert, not caught, and the
re-derivation below is what would actually disagree.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(tests): correct the detector limit and move it onto the code it constrains

The header said a consumer reached through a re-exporting barrel matches neither
half of the detector. It matches the symbol half: a name-preserving re-export
still writes `classifyGatedImageForViewer(`, so the consumer joins the ledger at
its own path. Only a barrel hop that also renames escapes both. That mattered
more than wording, because the barrel case is the symbol half's ONLY unique
contribution - an alias, a namespace import and a re-export from the logic module
all carry the `from` clause - so the header handed a future tidier an argument
for deleting it.

The corrected statement lives on `isCallSite` rather than in the header, where it
sits beside the expression it describes instead of drifting from it. The same
docblock justified the symbol half as catching a namespace import or a re-export,
both of which the import half already catches.

The header keeps the rule and drops what restated it: the detection mechanism
(stated twice more, on `LOGIC_MODULE_IMPORT` and `isCallSite`), the paragraph
arguing a per-file suite could not catch this, and a summary of two sibling
suites' assertions - which also over-claimed, since the grid withholds an unrated
image's url from its author too when the image is flagged or scan-refused.

The corpus-loop comment now states the fact rather than the mutation-testing
note: every corpus member's path contains the token, so narrowing the loop by it
excludes nothing.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(tests): state the detector's escape shapes as a property, not a list

The previous round's docblock said only a renaming barrel hop escapes both
halves. It does not. A renaming dynamic import escapes both with no barrel
anywhere: `await import('…logic')` carries no `from` clause for the import half,
and a renamed destructure puts a `:` where the symbol half needs `(`. The file
still enters the corpus, is scanned, and comes back not-a-call-site. A helper
handed the function as a value escapes the same way.

That distinction is the safety-relevant part and it is now stated: a renaming
barrel hop reddens this suite at the barrel, while a renaming `import()` or a
helper reddens nothing at all. Written as a property of what escapes rather than
an enumeration of shapes, so finding a fourth shape does not make it false again.

Restores the clause saying each file type-checks and each file's own suite passes.
It was cut as self-justification, but it is the only statement of why the two
per-consumer suites cannot substitute for this one, it lives nowhere else, and
being about the nature of cross-file defects rather than about any code, it cannot
drift.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(tests): state both halves as spellings, and stop claiming the per-file suites are blind

Two false statements, both introduced by the previous two rounds of this branch.

The restored clause said each file type-checks and each file's own suite passes,
so this is the class of defect no per-file suite can see. Not true of the tree as
it stands: `block-post.service.test.ts` has an `it.each` whose first two rows are
`{ ingestion: 'Pending' }` and `{ nsfwLevel: 0 }`, both reaching the gate at
`block-post.service.ts:592`, so rewriting that gate as `=== 'hidden'` fails them.
It was true when the seam was created and stopped being true when those cases
were written. The narrower statement is the one that does not rot: neither file is
wrong on its own, so nothing fails until someone writes a per-consumer case for
the new state - which is exactly what a third consumer would not have.

The escape condition said a file escapes both halves by naming neither the module
nor the symbol. A renaming `import()` names the module in full and escapes anyway,
because the import half keys on a `from` clause rather than on the module's name -
so the condition excluded a case the same paragraph listed two clauses later.

Both halves are now stated as what they are, spellings, with the `from` forms left
to the regex's own docblock instead of restated fifty lines away. Every false
absolute on this file has been a claim about that regex written far from it.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(tests): cut the detector's consequence claims, and make its referent exact

The docblock claimed that when an escape shape lands in an already-ledgered file,
the `status !== 'visible'` containment still holds the line. False for half the
ledger: `targets` is `EXPECTED_CALL_SITES` minus `MAY_BRANCH_ON_HIDDEN`, pinned by
name to `block-post.service.ts` alone, so the grid projection is ledgered with no
content assertion against it at all. The claim read as a backstop that does not
exist for the one consumer allowed to branch on `=== 'hidden'`.

Its companion - that a barrel hop still reddens at the barrel - was unconditional
in the same way: a barrel re-exporting via an extension the regex does not list
matches neither half itself, so that hop reddens nowhere either.

Both are deleted rather than qualified. This is the fifth false statement in this
docblock in four rounds, every one of them a consequence claim about a text
matcher; a deletion is the only edit here that cannot produce a sixth. What
remains is the part that has survived every round: each half pins a spelling, and
a file writing neither is not a call site.

That sentence defers to `LOGIC_MODULE_IMPORT`'s own docblock for the forms, which
makes it load-bearing, and it under-described them - it named the rooted, relative
and extensionless spellings while the regex also accepts `.ts`, `.tsx`, `.js` and
`.jsx`. A reader following the pointer to check a `.js` specifier was told by
implication it was not covered. Now stated exactly, with its closed end.

Comment-only: the diff contains no non-comment lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 11:41:10 -06:00
2026-07-30 13:19:05 -05:00

Contributors Forks Stargazers Issues Apache License 2.0 Discord


Table of Contents

About the Project

Our goal with this project is to create a platform where people can share their stable diffusion models (textual inversions, hypernetworks, aesthetic gradients, VAEs, and any other crazy stuff people do to customize their AI generations), collaborate with others to improve them, and learn from each other's work. The platform allows users to create an account, upload their models, and browse models that have been shared by others. Users can also leave comments and feedback on each other's models to facilitate collaboration and knowledge sharing.

Tech Stack

We've built this project using a combination of modern web technologies, including Next.js for the frontend, TRPC for the API, and Prisma + Postgres for the database. By leveraging these tools, we've been able to create a scalable and maintainable platform that is both user-friendly and powerful.

  • DB: Prisma + Postgres
  • API: tRPC
  • Front-end + Back-end: NextJS
  • UI Kit: Mantine
  • Storage: Cloudflare

Getting Started

To get a local copy up and running, follow these steps.

Prerequisites

  • Docker, with Compose v2 (docker compose, not the retired hyphenated docker-compose). The database, Redis, MinIO, Meilisearch, ClickHouse and the mail catcher all run as containers.
  • Node.js 24.19.0. Not "20 or later" — package.json declares engines.node: ">=24.0.0 <25". The exact version lives in .nvmrc; CI installs that file's version and the production image is built on the same one, so nvm use (or any tool that reads .nvmrc) is the right way to get it. Note that nothing stops you: pnpm install only prints WARN Unsupported engine and carries on, so the wrong major surfaces later as odd test failures rather than as a refusal at install time.
  • pnpm. This repo is pnpm-only, and this one is enforced — npm install exits 1 via the preinstall only-allow pnpm hook. corepack enable will pick up the packageManager field for you.
  • Make (optional).

Installation

Standard setup

git clone https://github.com/civitai/civitai.git
cd civitai
nvm use                                              # reads .nvmrc -> 24.19.0
corepack enable
git submodule update --init event-engine-common
cp .env-example .env.development
docker compose -f docker-compose.base.yml up -d
pnpm install
pnpm dev

Optional: Nix flake

Optional, and not the supported default. The standard setup above is what the project expects and what CI builds; nothing in the repo requires Nix, and you can ignore this section entirely. It exists because NixOS cannot use Prisma's published engines (there is no linux-nixos build), so a flake is the practical way to work on this repo there. If you are not on NixOS and not already a flakes user, skip it.

The flake owns the toolchain, so you do not install Node or pnpm yourself:

git clone https://github.com/civitai/civitai.git
cd civitai
nix run .#dev

That single command checks Docker is usable, checks out the event-engine-common submodule, creates .env.development from .env-example if you do not already have one, starts the container stack, waits for Postgres, runs pnpm install, and then starts the dev server on http://localhost:3000. Every step is idempotent — it is safe to re-run in a checkout that already works, and it will not overwrite your .env.development or touch your data.

Useful variants:

nix run .#dev -- --no-start   # bootstrap only, leave the services running
nix run .#dev -- --full       # also start the signals/buzz containers (see below)
nix run .#doctor              # check the flake's pins against the repo
nix flake check               # the same checks, plus their own self-test

For an interactive shell with the same toolchain, use nix develop, or copy .envrc.example to .envrc and run direnv allow to get it automatically on cd.

With devcontainers

⚠️ Known out of step: .devcontainer/public/docker-compose.yml pins mcr.microsoft.com/devcontainers/typescript-node:1-22, i.e. Node 22, which is outside this repo's engines.node range. pnpm install will warn rather than stop, so the container comes up and then misbehaves in ways that look like your branch. There is no 1-24 tag (the template major moved on); 3-24 is the closest equivalent. Not changed here because it could not be exercised.

⚠️ Important Warning for Windows Users: Either clone this repo onto a WSL volume, or use the "clone repository in named container volume" command. Otherwise, you will see performance issues.

  • Open the directory up in your IDE of choice
    • VS Code should prompt you to "Open in container"
      • If not, you may need to manually run Dev Containers: Open Folder in Container
    • For other IDEs, you may need to open the .devcontainer/devcontainer.json file, and click "Create devcontainer and mount sources"
    • Note: this may take some time to run initially
  • Run make run

The signals and buzz services

docker-compose.base.yml holds everything a contributor needs (and is also what nix run .#dev starts). The extra services in docker-compose.yml (signals, buzz) come from private ghcr.io images, so they only work for internal members:

  • create a GitHub personal access token with read:packages
  • set it as CR_PAT
  • echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
  • then docker compose up -d (or, with the flake, nix run .#dev -- --full)

After the first start

  1. Edit .env.development. Most defaults work out of the box; these do not:
    • S3 upload credentials. Open the MinIO console at http://localhost:9001 (username and password both minioadmin) — note it is port 9001, port 9000 is the S3 API itself — go to "Access Keys", click "Create Access Key", and copy the key and secret into S3_UPLOAD_KEY / S3_UPLOAD_SECRET and S3_IMAGE_UPLOAD_KEY / S3_IMAGE_UPLOAD_SECRET.
    • WEBHOOK_TOKEN — any random string; it authenticates requests to the webhook endpoint.
    • EMAIL_USER, EMAIL_PASS, and EMAIL_FROM (a valid email format) — any values, but they must be set for user registration to work.
  2. On an empty database, populate it. These are slow and destructive, which is why no bootstrap runs them for you:
    make run-migrations
    make reseed
    
  3. Visit http://localhost:3000.

Please report any issues with these commands to us on discord.

* Note that account creation will run emails through maildev, which can be accessed at http://localhost:1080.

Altering your user

  • First, create an account for yourself as you normally would through the UI.
  • You may wish to set yourself up as a moderator. To do so:
    • Use a database editor (like DataGrip) or connect directly to the DB (PGPASSWORD=postgres psql -h localhost -p 15432 -U postgres civitai)
    • Find your user (by email or username), and change isModerator to true

Known limitations

Services that require external input will currently not work locally. These include:

  • Orchestration (Generation, Training)
  • Signals (Chat, Notifications, other real-time updates)
  • Buzz

Contributing

Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the repository to your own GitHub account.
  2. Create a new branch for your changes.
  3. Make your changes to the code.
  4. Commit your changes and push the branch to your forked repository.
  5. Open a pull request on our repository.

If you would like to be more involved, consider joining the Community Development Team! For more information on the team as well as how to join, see Calling All Developers: Join Civitai's Community Development Team.

Data Migrations

Over the course of development, you may need to change the structure of the database. To do this:

  1. Make your changes to the packages/civitai-db-schema/prisma/schema.full.prisma file. Not schema.prisma — that one is gitignored and regenerated from schema.full.prisma by scripts/generate-slim-schema.js on every pnpm run db:generate, so edits to it are silently overwritten.
  2. Run pnpm run db:migrate:empty "brief description here". This creates packages/civitai-db-schema/prisma/migrations/YYYYMMDDHHmmss_brief_description_here/migration.sql for you, in the one directory Prisma reads. To create it by hand instead, use that same path — not the prisma/migrations directory at the repo root, which predates the monorepo layout and is no longer read.
  3. Put your sql changes in the generated migration.sql
    • These are usually simple sql commands like ALTER TABLE ...
  4. Run make run-migrations and make gen-prisma
  5. If you are adding/changing a column or table, please try to keep the gen_seed.ts file up to date with these changes.

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website.

License

Apache License 2.0 - Please have a look at the LICENSE for more details.

S
Description
clickup: Interact with ClickUp tasks and documents - get task details, view comments, create and manage tasks, create and edit docs. Use when working with ClickUp…; quick-mockups: Create multiple UI design mockups in parallel. Use when asked to create mockups, wireframes, or design variations for a feature. Creates HTML files using…
Readme 362 MiB
Languages
TypeScript 93.3%
JavaScript 2.6%
Svelte 2.5%
PLpgSQL 0.5%
SCSS 0.4%
Other 0.6%