Justin Maier 233d0aa8be fix(nav): scroll the sub nav tab row, and keep the filters on its line (#4834)
The row is content-width and never collapses — `useResolvedNav` derives the
bar/More split from the user's saved config, not the viewport — so it measures a
fixed ~1334px signed in at every width from 1024 to 1400. `@md:overflow-visible`
overrode both overflow axes above the `md` container breakpoint (1024px),
removing the row's only escape: above 1024 it could not scroll, and an ancestor
`overflow-hidden` clipped whatever exceeded the viewport.

Measured at 1136px signed in, on `/`, `/models` and `/leaderboard/overall`: row
right edge 1334, scrollable overflow 0, and neither Shop nor More hit-testable.
`document.scrollWidth` equalled the viewport at every width, which is why a
page-level overflow check finds nothing here.

Dropping the override restores the scroll the row already relies on below 1024.
`overflow-y` cannot be `visible` beside `overflow-x: auto` — it computes to
`auto` — so the row clips on both axes, and an outline contributes no scrollable
overflow. The row pays the focus ring's 4px of ink on the axis that scrolls,
sized from the ink and carrying `var(--mantine-scale)` as Mantine's own rule
does, rather than from a rem scale that only matches at a 16px root.

The padding is horizontal ONLY, and that is a trade rather than an oversight.
Padding all four sides also unclipped the ring top and bottom, including below
1024 where it is clipped today, but it made the bar taller on every page at every
width. Justin saw the rendered result and declined it. Not "unchanged behaviour",
though: the 36px More button used to set the row's height and leave a 32px pill 2px
of slack, so half the ring showed. Shrinking More to pill height took that. The
mechanism is unchanged; the amount is not.

Second problem, same bar: on feed routes the filters and the settings gear
wrapped to a second line, doubling the bar's height from 44px to 88px. `SubNav2`
wraps, and a wrapping container places items at their flex BASIS before shrinking
any of them, so at `basis: auto` the row's content width does not fit and the
siblings wrap. `flex-1` gives it `basis: 0`: both share the line, and the row
absorbs the shortfall by scrolling. `shrink-0` on the More button is the other
half, because that shrink then lands on the children and More is the one that
collapses, to an empty 28px circle.

`min-w-0` is deliberately absent: `overflow-x: auto` already zeroes a flex item's
automatic minimum size, and removing it changed nothing at any of eight widths.
So is `lg:flex-nowrap`, measured the same way.

Measured on /images, sub nav height, against a control built by reverting only
these classes on the same dev server: 88px to 44px at 1440/1280/1184/1136/1024/900,
and byte-identical child geometry at 768/640/390. The band is route-dependent and
those figures are one route: 40px on /models where every control is `h-8`, 44px on
/images where one filter control is 36px, and 36px on /comics, where
`FilterButton`'s `compact-sm` takes the `h-9` branch.

The rest is one size for the whole row, all of it measured rather than eyeballed:
`SubNav2` top-aligns its children, because on platforms that draw classic
scrollbars the scroller is taller than its pills and centring put the filters half
a scrollbar low; the More button is 32px at 14px/600, matching the pills, where it
was 36px at 16px/500; and the settings gear is 32px, circular, with a 16px icon in
`--mantine-color-bright`. The gear's icon was never smaller than its neighbours —
every icon in the bar is 16x16 with a 2px stroke — it was rgb(222,226,230) against
their rgb(254,254,254), and at a matched box that reads as smaller rather than
dimmer. `bright` is the same #222/#fefefe pair the pills use, so the gear matches
the pills in both schemes; against the globe specifically it is exact in dark and
slightly darker in light, where the globe is `text-gray-8`.

The scrollbar is deliberately left visible: it is the only thing telling anyone the
row scrolls, and "it doesn't look like it scrolls" was the original report.

Guarded at two tiers, and every guard was mutated:

  Shell's provider deleted     RED  expected false to be true
  scale-95 planted (whitelist) RED  to deeply equal [...7 items]
  relative on the row          RED  expected false to be true
  flex-1 removed               RED  expected 32 to be +0
  shrink-0 removed from More   RED  expected 28 to be close to 81.796875
  padding to px-0              RED  expected -4 to be >= 0
  @md:overflow-visible back    RED  expected 'visible' to be 'auto'

The hit-test is the one that needed building twice. The trap it guards — a
`position: sticky` or `relative` ancestor becoming the unportalled dropdown's
containing block — produces a dropdown whose rect is IDENTICAL to a working one
while its items stop being hit-testable, so a rect assertion and a `textContent`
assertion both pass against the broken state. The first version of that test still
passed with `relative` planted, because the geometry harness mounts a bare
`MantineProvider` and never receives `ThemeProvider`'s `Popover.withinPortal:
false`: the menu portalled in the test while rendering inline in the app. It now
nests a provider carrying that default.

The source gate strips comments before matching, so deleting the live row and
leaving a commented-out copy fails loudly instead of passing silently, and it
rejects positioning tokens on the row so the prohibition above is checked rather
than merely written down.

`.moreButton`'s height moved from an explicit `32px` to `h-8`, beside the pills'
own. Measured either side on the same route and browser rather than argued from
the cascade, because that cascade misled two reviewers on this file today:
32px / 16px / 10px / 14px / 600 in both states, identical on every axis.

A second tidy-up — dropping `variant="subtle"`, which `LegacyActionIcon` and
`ThemeProvider` each already set — is deliberately NOT here. The gear does not
render for the probe session, so it could not be measured, and an unverifiable
change whose only benefit is tidiness is not worth carrying.

`.moreButton` no longer states a font either: measured with the declarations
removed, the button still renders 14px/600, because that is what a Mantine
`size="sm"` Button already produces. Two lines of framework default, deleted.

The More button's height is now pinned in the geometry tier against a PILL's
rather than a literal — deleting `h-8` reddens with `expected 36 to be 32`,
where before it passed both tiers green while growing the bar 4px site-wide.

The More button's height and typography are pinned in the geometry tier against
a PILL's and against the literal 32, because parity alone passes when both move:
`h-8` to `h-9` on both controls leaves them equal at Mantine's 36px and grows the
bar 4px on every page. Mutants: 36-vs-32 either side, and 20px-vs-14px on the font.

The pill's `text-base font-medium` does NOT render. `globals.css`'s unlayered
`.mantine-Button-label *` sets both to `inherit` and sits after
`@tailwind utilities`, so the span takes Mantine's `size="sm"` 14px/600. Three
reviewers read those classes as 16px/500 in one day, so it is written beside them.

The `geometry` project is `continue-on-error` on pull requests, so everything it
measures informs rather than gates. The height claim is therefore asserted in the
`unit` tier too — a 0.3s check that the More button's `clsx` still carries `h-8`.
Paired control: removing it gives `expected [ 'shrink-0' ] to include 'h-8'`,
reordering the class list stays green. The font and geometry claims stay advisory
and the PR body says so.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 18:08:18 -06:00
2024-12-16 16:24:45 -05:00
2024-05-16 16:46:10 -06:00
2023-04-12 20:34:56 +01: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%