Zachary Lowden bf43398486 feat(app-blocks): count the fifth bridge silence — validator_rejected (#4977)
* feat(app-blocks): count the fifth bridge silence — validator_rejected

The receiving half of the App Blocks bridge's fifth drop path. The other four
are already counted on civitai_app_block_bridge_messages_total (#4946); this one
no code here can observe, because the SDK's validator runs in the iframe AFTER
this host has replied — from here the exchange completed and the dispatcher
already counted it `handled`. The block is the only witness, so it reports over
the new fire-and-forget BLOCK_MESSAGE_REJECTED and the shared dispatcher
translates that into outcome="validator_rejected".

It is the one of the five with a confirmed production incident: on 2026-09-18
custom-generators served "Couldn't load your kept images just now." from relist
until a human found it by hand, while civitai_app_block_renders_total read
result=ok, error_class=none throughout.

  - bridgeLabels: a sixth BRIDGE_MESSAGE_OUTCOMES value. The beacon's
    z.enum(BRIDGE_MESSAGE_OUTCOMES) and the client emitter both derive from that
    array, so nothing else on the wire path needed respelling.
  - usePostMessage: one branch, in the SHARED dispatcher above the subscriber
    lookup — the message is telemetry, not a feature either host implements, so
    it reaches no onMessage subscriber by design and a per-host handler would be
    the same predicate written twice.
  - hostHandlerParity: the new type as an N/A-for-every-host entry, because the
    parity test greps hosts for a registration that must not exist here.
  - the label-product arithmetic in three docblocks: (A+1) x 47 x 2 x 5 becomes
    (A+1) x 48 x 2 x 6, ~29k series per pod at 50 approved apps.

The `type` on the new outcome is the block->host REQUEST left hanging
(GET_IMAGES_BY_IDS), not the rejected reply (IMAGES_RESULT). Deliberate and
measured: boundBridgeMessageType bounds the label against INVENTORY, which holds
no *_RESULT key, so a reply type would clamp to 'other' and collapse every
rejection in the protocol onto one label. Pinned by a test that asserts both
halves of that fact.

The clamp runs at the extraction site rather than in the sink. Found by the new
browser test rather than by reasoning: onOutcome is a documented seam, so a value
pulled from an untrusted payload and bounded only by the default sink is bounded
by nothing a reader of the branch can see — the test's own sink observed the raw
`NOT_A_REAL_MESSAGE`.

Watched to fail first. Disabling the dispatcher branch turns 6 of the 7 new
browser tests red; the one that stays green is the negative control (a healthy
exchange reports no validator_rejected). Removing the sixth outcome value fails
exactly one unit test; removing the INVENTORY entry fails exactly one other.

Verified: pnpm typecheck 0 errors (59s); vitest unit over src/components/AppBlocks
+ src/tests/api/track + src/server/metrics + src/server/schema 110 files / 1820
tests; test:lint-rules 46 files / 633 tests; component (chromium)
usePostMessageOutcomes.browser.test.tsx 15 tests.

Pairs with civitai/civitai-app-starters#317, which emits it.

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

* fix(app-blocks): round-0 audit fixes — six wrong or stale counts, three false rationales

Net -29 lines. No behaviour change except the one noted below; everything else is
prose that did not survive its own first extension. Each item's evidence was
re-verified before acting.

ARITHMETIC, re-derived rather than adjusted:
  - bridgeLabels.ts said this sixth outcome grew the label product "BY 20% —
    (approved apps + 1) x 47 x 2 x 6". Both halves wrong: 20% is the outcome axis
    alone and the type axis grew too, and the `x 47` omits the `'other'` slot the
    other two sites include. Re-derived at 50 approved apps: 51 x 48 x 2 x 6 =
    29,376 against 51 x 47 x 2 x 5 = 23,970, i.e. +22.6%. Also recorded that the
    product is a CEILING, not allocated heap — nothing pre-initialises the label
    space, so the sixth value costs zero series until a rejection occurs.
  - block-message.ts said "roughly 9x the existing renders_total product". The
    only renders_total figure in the repo is ~2,040, so it is ~14x. (The base said
    "7x" against 11.75x, so this one was already wrong before this branch — but it
    was rewritten rather than re-derived, which is the same defect.)

STALE COUNTS the change should have touched and did not:
  - bridgeLabels.ts "46-key INVENTORY" -> 47-key
  - bridgeLabels.ts "wrong for THREE of the five outcomes" -> FOUR of the six
  - bridgeMessageBeacon.ts "three of the five outcomes are reported above the
    bridge's inbound limiter" -> four of the six; validator_rejected is the fourth,
    which this branch's own comment already said
  - usePostMessageOutcomes.browser.test.tsx header "The four DISPATCHER outcomes …
    The fifth, no_token" -> five of the six are dispatcher-side now

FALSE RATIONALES — each would have led a reader to the wrong conclusion:
  - usePostMessageOutcomes.browser.test.tsx said "`report` -> `recordBridgeMessage`
    -> `boundBridgeMessageType` does the clamping; this pins that the branch routes
    through it". That is the PRE-correction design: these tests supply their own
    `onOutcome`, so `recordBridgeMessage` never runs and the clamp under test is the
    branch's own. A reader following the old comment would conclude the branch's
    clamp is dead code, delete it, redden four rows, and read the failure as "the
    test is wrong".
  - bridgeTelemetry.test.ts justified the new INVENTORY entry by the dispatcher's
    `no_handler` bookkeeping — a path this same change makes UNREACHABLE, since the
    new branch returns above it. The entry's real and only current purpose is
    hostHandlerParity's one-directional compile-time gate, i.e. it is what lets this
    repo bump @civitai/app-sdk past the version adding the message. Also recorded
    the cost of keeping it: boundBridgeMessageType now passes
    'BLOCK_MESSAGE_REJECTED' through for a forged POST instead of clamping it.
  - bridgeLabels.ts called this "the fifth silence". Retracted: the SDK's own
    handleMessage still drops silently and uncounted on an origin mismatch, on a
    malformed envelope, and on a well-formed reply whose requestId matches no
    pending request. This value covers the validator path only.

DELETED:
  - 10 of the 37 comment lines in usePostMessage.ts's new branch. Five of its six
    paragraphs restated bridgeLabels.ts, and the duplication had ALREADY drifted
    from the original inside this same branch — which is the defect the six counts
    above are. It now points at bridgeLabels.ts for the reading rules and keeps only
    what is specific to the branch.
  - one of the four clamp test.each rows. `{ type: 42 }` and `{}` reach the same
    `typeof !== 'string'` arm; each row costs a full renderWithProviders + iframe
    mount in chromium. `undefined` is kept — it is the only row exercising the `?.`.

Re-verified after the changes, since an audit fix resets the gate: pnpm typecheck
0 errors; vitest unit over src/components/AppBlocks + src/tests/api/track +
src/server/metrics + src/server/schema 1820 tests; component (chromium) 14 tests
(was 15 — one row removed). Mutation matrix re-run: disabling the dispatcher branch
reddens 5 of the 6 new tests, the survivor being the negative control; removing the
extraction-site clamp reddens exactly 1, printing the leaked value.

Pairs with civitai/civitai-app-starters#317.

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

* fix(app-blocks): the HELP text asserted an SDK emit budget the emitter deleted

Round-1 audit findings. No 🔴; every item is a false or stale claim on a surface
an operator reads.

🟡 THE SHIPPED HELP STRING WAS FALSE, on the axis this whole change exists to get
right. It told every scrape "the SDK budgets its reports at 30 per 10s per
transport so a sustained break UNDERCOUNTS — read it as which types and when it
started, never as a total." The emitting half deleted that budget in
civitai-app-starters#317 (its own docblock: "NO EMIT BUDGET, AND THE ONE AN
EARLIER REVISION CARRIED WAS JUSTIFIED BY A FALSEHOOD"), so the reading
instruction inverted the truth: an operator seeing >30 per 10s per transport
would conclude the count is structurally impossible and chase a forged beacon
instead of the rejection loop that produced it. Corrected in all three places it
was stated — the HELP string, bridgeLabels, and the flood test's comment.

🟡 AND THE CAVEAT THAT SHOULD HAVE BEEN THERE INSTEAD: a zero is not evidence of
health. The emitter ships inside each block's OWN bundle — every app pins
@civitai/blocks-react itself — so the series stays at zero until every app has
been rebuilt AND redeployed against a version carrying it, not merely until the
package publishes. A flat-zero diagnostic read as health is the exact failure
this outcome exists to end, and nothing said so.

🟡 THE BRIDGE_MESSAGE_COUNT_MAX DOCBLOCK'S HEADLINE OUTRAN ITS OWN ENUMERATION.
The previous commit changed "wrong for THREE of the five outcomes" to "FOUR of
the six" without touching the bullets below it, which still accounted for five,
or the prose after them, which still said "those three" and "the other three".
validator_rejected — the outcome this PR adds, reported above the limiter, and
whose emitter now has no cap either — appeared nowhere in the list a reader
consults to learn which outcomes can drive a key to the 100,000 clamp. At base
that docblock was internally consistent; this branch made it inconsistent, so it
is a regression in the artefact and not inherited rot.

🟡 The dispatched message type was a bare literal in the `if`, with no
compile-time link to the protocol — and the tests use the same literal, so an SDK
rename would silently disable the branch, return the series to zero, and falsify
hostHandlerParity's entry with nothing red anywhere. Now a constant bound with
`satisfies keyof typeof INVENTORY`; INVENTORY was already in this module's import
graph, so the binding costs nothing. It catches a rename that reaches the
inventory, not one that has only happened upstream — the upstream half is
hostHandlerParity's own gate, and the comment says so rather than overclaiming.

🟢 An in-code comment still said "deleting it reddens these four rows" above a
three-row test.each (the previous commit removed a row), and a byte figure I had
edited from ~6.7 to ~6.8 KB was never measured — replaced with "several KB"
rather than inventing precision.

Verified: pnpm typecheck 0 errors; vitest unit over src/components/AppBlocks +
src/tests/api/track + src/server/metrics + src/server/schema 1820 tests;
component (chromium) 14 tests.

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

* fix(app-blocks): a zero is per-APP, and the satisfies binding is narrower than it claimed

Round-2 delta findings. No 🔴; every one is an assertion on a surface an operator
or a maintainer reads.

🟡 THE ZERO CAVEAT STATED FLEET GRANULARITY WHERE THE MECHANISM IS PER-APP, and so
licensed the inference it was added to block. The counter is labelled
`app_block_id`; the series does NOT stay at zero until every app is rebuilt — it
goes non-zero the moment the FIRST rebuilt app hits a rejection, while every
un-rebuilt app's slice sits at a zero that means nothing. As written, an operator
seeing three apps report would conclude the fleet had picked it up and read the
other zeros as health — the flat-zero-read-as-health failure, one level up. Both
the shipped HELP string and bridgeLabels now say: read it WITH `app_block_id`;
for a given app a zero is "no rejections" OR "this app has not shipped a carrying
blocks-react", and another app reporting does not settle it.

🟡 THE `satisfies keyof typeof INVENTORY` BINDING IS NARROWER THAN "an SDK rename
is a type error", which is what its own comment and the PR body both claimed. It
fires only on a rename that reaches the inventory AND DROPS THE OLD KEY — and
hostHandlerParity's coverage gate is one-directional BY DESIGN, so the documented,
gate-satisfying way to track an upstream rename is to ADD the new key and leave
the old one (it carries three such keys today). In exactly that state the binding
stays green, the branch never fires, and validator_rejected returns to a permanent
zero the HELP now tells a reader to interpret as a rollout gap — the
silent-dead-branch failure the binding was added to end, surviving it.

No better guard was reached for, deliberately. The comment now states the real
boundary and says plainly that the add-and-keep window is OPEN rather than
supplying a fresh justification for a guard that does not cover it. The binding
still earns its place: it catches an outright key deletion and a typo either side.

🟡 "Two consequences" over three bullets — round 1's finding in the
BRIDGE_MESSAGE_COUNT_MAX docblock (headline outran its own list), re-made by the
commit that fixed it there, in the docblock next door. Now three, and the bullet
says so.

🟢 "The SDK shipped a 30-per-10s emit budget and then DELETED it" asserted a
release history that never happened: the budget existed only in an unmerged
revision of the sibling PR and reached no published package. Read as release
history it tells an operator that older bundles in the field DO undercount, which
is the inverted reading this whole fix removes.

🟢 An in-code comment's referent ("every row below" — the table is above).

Also corrected in the PR body: "~22 keys ahead" is the published union's SIZE, not
the gap — INVENTORY has 47 keys against 22 published members, so the gap is 25;
and the clamp mutation row said "exactly 1 red" where the ISOLATED mutant reddens
3. The earlier number was measured against a weaker mutant that removed the clamp
AND kept a hardcoded 'other' fallback — two changes, not the narrowest expression
that can be wrong. Re-measured with the clamp call alone deleted: 3.

Verified: pnpm typecheck 0 errors; unit over src/components/AppBlocks +
src/tests/api/track + src/server/metrics + src/server/schema 1820; component
(chromium) 14.

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

* fix(app-blocks): 25 of 47 keys run ahead, not three — and say what type='other' means here

Round-3 delta findings. All prose; zero executable change beyond one import
becoming type-only.

🟡 THE BOUNDARY STATEMENT'S OWN COUNT WAS WRONG BY 8x AND CONTRADICTED THE SAME
COMMIT'S PR-BODY EDIT. The docblock said the inventory "today carries three" keys
ahead of the published dist. Measured: 47 INVENTORY keys against the 22 members
the installed @civitai/app-sdk@0.14.0 block->host union declares, so 25 are ahead
— while that same commit edited the PR body to say 25. One commit asserted both
numbers for one quantity, and the wrong one was the one a maintainer meets first.

It is load-bearing, not decoration: the docblock's argument is that add-and-keep
is the documented way to track a rename, the window is open, and no better guard
was reached for. A reader told the window is a three-instance curiosity weighs
that differently from one told it is the state of 25 of 47 keys — i.e. the norm.

⚠️ And the "three" was inherited from hostHandlerParity.ts:56-58's parenthetical,
which is itself stale: it names CANCEL_WORKFLOW, REQUEST_SIGN_IN and
REQUEST_CONSENT as the ahead-of-published keys and ALL THREE are present in
0.14.0, so zero of them is ahead. The docblock now says to measure rather than
read that list. (Correcting the parenthetical is base rot, not this branch's.)

🟡 WHAT type='other' MEANS ON THIS OUTCOME WAS NOWHERE, and both surfaces implied
the opposite. The HELP said the type "is the block->host REQUEST left hanging" and
separately that an unknown type clamps to 'other'. Against the emitter's current
head, 'other' is also what it sends for a rejected host PUSH (nothing was awaiting
it, so nothing hangs) and for a reply it could not attribute. So an operator
applying those two sentences to a validator_rejected{type="other"} row concludes
an unrecognised request is hanging to a 30s timeout — both halves wrong, and the
two cases share the bucket. Stated on the HELP string and in bridgeLabels: 'other'
is the one value on this outcome you must NOT read as "a request is hanging".

🟡 The docblock also overstated the consequence in the SAFE direction, which is
still wrong: a renamed-and-kept key does not silently zero the signal, it files it
under no_handler with the NEW type (unclamped, since that would be an INVENTORY
key). Harder to notice than an absence, not easier.

🟢 The INVENTORY import is used only in `typeof INVENTORY`, so
@typescript-eslint/consistent-type-imports (error severity) reports it. It blocks
nothing — lint.yml gates on ADDED files and this one is modified, and the
in-cluster pr-check pipeline runs typecheck and tests, not eslint — but it is a
one-token fix and the annotation would have sat on the diff. Now `import type`.

Verified: pnpm typecheck 0 errors; unit over src/components/AppBlocks +
src/tests/api/track + src/server/metrics + src/server/schema 1820; component
(chromium) 14. The 25-vs-3 count was re-derived independently by enumerating both
sets, not taken from the audit.

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

* fix(app-blocks): type='other' is OVERLOADED four ways, and the clause said two

Round-4 delta finding, and the last one on this PR — see the closing note.

🟡 The `type='other'` clause added last round asserted an exclusivity the same PR's
code contradicts, and it contradicted the line two above it. It said 'other' "DOES
NOT MEAN an unrecognised type", directly below the sentence explaining the clamp
that produces exactly that case. FOUR things reach the bucket, not two:

  (a) the SDK rejected a host PUSH — nothing was awaiting it, so nothing hangs;
  (b) the SDK could not attribute the reply to one of its pending requests;
  (c) the block named a type this host's INVENTORY does not declare, or named
      nothing — usePostMessage's own boundBridgeMessageType clamp, which a browser
      test in this very PR exercises with NOT_A_REAL_MESSAGE;
  (d) the SDK clamped an undeclared requestType while a request genuinely DOES hang.

So 'other' cannot be read in EITHER direction: not as "a request is hanging" (a and
c may hang nothing) and not as "nothing is hanging" (b and d may). The previous
wording gave an operator a protocol diagnosis — "a rejected push, or an
unattributable reply" — for what may be a real exchange the emitter mislabelled.

⚠️ (c) is reachable today only for a malformed or undeclared value: the SDK's
47-entry type array and this repo's 47-key INVENTORY were measured EQUAL at both
current heads, so no legitimate protocol type clamps. Recorded as a measurement
across two moving repos, not as an invariant — the docblock 20 lines above is
precisely about drift in that relationship.

NOT FIXED, deliberately, and named so it is visible rather than absent:
hostHandlerParity.ts:685-686 carries a SECOND copy of the stale ahead-of-published
parenthetical (CANCEL_WORKFLOW, REQUEST_SIGN_IN, REQUEST_CONSENT "today" — all
three are in 0.14.0, so zero of them is ahead), and last round's warning points at
only the first copy. Both are pre-existing `main` text that no commit on this
branch touches. Correcting them is a one-line edit somebody should make; widening
this PR to do it would put an unrelated doc change in a bridge-telemetry diff.

🔴 CLOSING THIS PR'S AUDIT LADDER HERE, on a stated criterion rather than a feeling.
Rounds 3 and 4 both changed ZERO executable lines on this PR — round 3's only
non-comment edit was an import becoming type-only (erased at build), round 4's is
comment text and one HELP literal. The attribution gate's two-consecutive-
zero-payload condition cannot fire mechanically because comments in payload files
count as payload lines, but the condition it exists to detect is met: the rounds are
auditing the ladder's own prose, not the PR. The code has been unchanged and green
since 37a2ef5b34. A fifth round over an adjective is the cost the gate exists to
avoid, and the round-4 auditor recommended the same independently.

Verified: pnpm typecheck 0 errors; unit over src/components/AppBlocks +
src/tests/api/track + src/server/metrics + src/server/schema 1820; component
(chromium) 14.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 14:00:03 -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%