add-ecosystem
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Port a moderator page from the main Next.js app (src/pages/moderator/**) into apps/moderator. Use when asked to migrate, move or cut over a /moderator/* page to the spoke, or to port its tRPC procedures and Prisma services to SvelteKit loads/actions and Kysely.
$ npx -y skills add civitai/civitai --skill moderator-page-migration --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/moderator-page-migrationContext preview
The summary Claude sees to decide when to auto-load this skill.
Port a moderator page from the main Next.js app (src/pages/moderator/**) into apps/moderator. Use when asked to migrate, move or cut over a /moderator/* page to the spoke, or to port its tRPC procedures and Prisma services to SvelteKit loads/actions and Kysely.
name: moderator-page-migration description: Port a moderator page from the main Next.js app (src/pages/moderator/**) into apps/moderator. Use when asked to migrate, move or cut over a /moderator/* page to the spoke, or to port its tRPC procedures and Prisma services to SvelteKit loads/actions and Kysely.
Ports a page under `src/pages/moderator/**` into `apps/moderator`, then **switches the main-app page off**. The React component is not the work — the page's whole backend slice is. tRPC and Prisma do not exist in the spoke.
Sibling skill: [`retool-migration`](../retool-migration/SKILL.md), for the other inbound path. The two converge — same app, same conventions, same reviews — and differ only in what the source is and how you read it. Read that skill's §4–§6 if you have not built a page here before; they are not repeated.
A migration is one unit of work and it is not finished until every part of it is. Partial delivery is the failure mode this whole skill is arranged around — a page that is 90% ported and still live in both apps is worse than one that was never started, because a moderator now has two tools that disagree.
1. **Full functional parity.** Every query, every mutation, every side effect, and every action reachable from the page's child components. Not "the useful half" — §1 and §2 are how you enumerate it, §6.1 is the gate. 2. **The legacy page removed, and everything it orphans with it.** Delete the page file, redirect its URL, then trim the procedures, controllers, schemas and service functions nothing else uses. Leaving the old page live means moderators keep using it. Leaving dead procedures behind means the next reader cannot tell what is still load-bearing. §5. 3. **Reviewed** — `svelte-correctness-review`, `svelte-idiom-review` and `svelte-abstraction-review` over the segment, findings fixed. §6.
Do not tick the tracker until all three have happened.
[`docs/moderator-app/page-migration-checklist.md`](../../../docs/moderator-app/page-migration-checklist.md) is the tracker. Every page is a checkbox with its procedures, services, schemas and infra already enumerated, plus what earlier ports deferred. **Read your page's entry before writing code** — several carry a decision you would otherwise re-litigate (`/moderator/tags` may be superseded by `/images/tags`; Paddle pages are excluded by decision).
Update it as part of the work, not afterwards: tick the page, and write what you deliberately did not port. "Not ported" and "not needed" look identical six months later.
The second tracker is [`side-effect-parity-checklist.md`](../../../docs/moderator-app/side-effect-parity-checklist.md) — see §3.
A page is not its procedures. Walk outward until nothing new appears:
sed -n '1,80p' src/pages/moderator/<page>.tsx # the flag guard and what it renders grep -n "trpc\." src/pages/moderator/<page>.tsx # procedures the PAGE calls grep -rn "<procedure>" src/server/routers/ # → controller → service
Then, for every service function a mutation reaches, read it to the end. That is where the migration actually lives.
🔴 **The child components issue mutations the page never mentions.** A context menu, a row action, a confirm dialog — each imports `trpc` itself, so grepping the page file finds none of them. The `/moderator/articles` entry in the tracker says exactly this ("`ArticleContextMenu` may issue extra mutations — verify"), and it is the main-app analogue of the Retool export's missing event handlers. Grep the component tree, not the page:
grep -rn "trpc\.[a-z]" $(grep -o "from '~/components/[^']*'" src/pages/moderator/<page>.tsx \ | sed "s|from '~/|src/|;s|'||") 2>/dev/null
**A shared service is ported once, for the whole cluster.** `image.service.ts` (~8K lines) and `report.service.ts` back most of the image/CSAM pages. Port the functions your page needs into a spoke service, and check `apps/moderator/src/lib/server/` first — 80+ services already exist there and the one you want is often among them.
The queries port mechanically. **The mutations are the risk**, because a main-app moderation write is rarely one UPDATE: it fans out to notifications, email, Buzz, ClickHouse, Redis busts, search-index enqueue and session invalidation, and none of that is visible from the procedure name.
For each mutation, list every effect its service performs and put each into one bucket:
| Bucket | Meaning | | --- | --- | | **port** | The spoke does it. Most Postgres/Redis/ClickHouse/S3 work, via the `@civitai/*` packages. | | **delegate** | Stays in the main app behind an internal endpoint the spoke calls. The pattern exists: `syncKonoFinalize` in `apps/moderator/src/lib/server/kono.ts` → `src/pages/api/internal/kono-finalize.ts`. | | **defer** | Deliberately dropped for now. **Goes in the side-effect checklist with a severity**, not in a code comment. | | **equivalent** | Covered differently here — say how. |
An effect not in a bucket is dropped by accident, and a dropped side effect is invisible: the page works, the moderator sees success, and the notification/index/cache silently never happens. That is the whole reason `side-effect-parity-checklist.md` exists — it was written after an audit found eight of them across three already-migrated domains, two rated BLOCKER.
**Delegate rather than defer for anything a moderator would notice.** Deferring is for effects whose absence is recoverable (analytics, a self-healing metric); delegating is for the ones that are not.
| Main app | Spoke | | --- | --- | | tRPC query procedure | `+page.server.ts` `load`, or an `+server.ts` endpoint if it is slow enough to keep off the load | | tRPC mutation | form `action` in `+page.server.ts`, calling a service | | `
Repo: civitai/civitai
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Wire an existing ecosystem into the generation system. Adds generation support to…
Author a prompt-enhancement system prompt for a new ecosystem and register/update it on the…
Add a new trainable base model to BOTH trainers end-to-end — the in-app trainer (main Next.js…
Wire an existing ecosystem into the LoRA training system so it appears as a trainable base…
Deterministically drive a first-party Civitai App Block in the operator's browser and produce…