`@civitai/ui` gaining a vitest config took the packages/* suites to 13, and the primitives under components/ui/ are at 54 — while the prose still said nine and 24. The counts are removed rather than corrected: CI's ledger script already asserts every workspace suite ran, so a number in prose only rots, and both of these had rotted twice. The schema-drift README also said apps/* had no CI job and that the ledger script hardcoded `packages/`. Both stopped being true when the App unit tests + typecheck job landed; it takes the workspace as an argument. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
39 KiB
Creator Studio (creator.civitai.com) — Implementation Plan
Status: Draft for review. Synthesised from the Creator Release 2026 HackMD, the ClickUp task (868k6zt4m), and a code-map of the current main app. Decisions + open questions for Justin are in §9.
1. What this app is
Creator Studio is a new SvelteKit spoke app at creator.civitai.com — "the single home for managing everything a
creator earns from." It is the UI surface for the Creator Release 2026, which replaces the flat 25% generation
compensation with creator-controlled earning: licensing fees, sellable model access (no time cap), and a
Creator Shop.
Scope (from the ClickUp task), all creator-facing:
- Bulk licensing-fee edits across many models, with model-type default-fee suggestions.
- Shop management — cosmetic submission, listing order, models toggle, publish. (Deferred: built in the main app first — see below.)
- Earnings + analytics — license fees, tips, model-access sales, cosmetic sales; usage analytics.
- Model management — early/paid-access toggles.
v1 is Studio-only. It does not absorb general creator surfaces (model upload, posts, profile settings) from the main app; those stay put for now.
Access model (confirmed by Justin): any logged-in user can access Creator Studio. Specific items/actions are
restricted to members, gated on the user's subscription tier. The app is not CP-gated at the door — the
gate is "authenticated," and member-only capabilities (e.g. setting a licensing fee) are enforced per-action.
v1 build priority (from the designer meeting): model management and basic analytics come first; the Creator Shop is out of v1 — see the shop note below.
Licensing-fee rules (confirmed by the designer): a licensing fee is per model version; members can turn it on and adjust it; non-members cannot set one.
Creator Shop is being built in the main app by another dev (confirmed by Justin) and will be transferred into Creator Studio later. It is therefore out of scope for this app's v1 — all shop questions are deferred to that work. This resolves the earlier designer-vs-HackMD tension: shop ships (in the main app), just not here yet.
Cutover — a separate backend track (not this app's v1)
Retiring 25% compensation and activating licensing fees still land together on the backend, but per Justin this cutover is decoupled from Creator Studio v1: the app ships ~1 week before the cutover. Fees set during that pre-cutover week carry a special "not-yet-payable" flag so they don't pay out until the cutover flips. So comp-retirement is its own backend track, not part of this app's v1 (§8).
2. Architecture & tooling
Creator Studio is a spoke app in this monorepo, exactly like apps/moderator and apps/auth. Do not port the
main app's React/Mantine components — these apps are Svelte 5 / SvelteKit / Tailwind v4.
Read these first (they are the source of truth for the wiring):
docs/packages/new-app-integration.md— the app-bootstrap + data-layer companion. Covers the cherry-pick model, theprocess.envshim,ssr.noExternaltranspile requirement, and the env cheat-sheet.docs/auth/spoke-integration-guide.md+docs/auth/auth-hub-spoke-overview.md— the hub↔spoke auth contract (civ-token,@civitai/auth,createSpokeGuard).packages/civitai-ui/README.md—@civitai/ui, the shared shadcn-svelte component package (shadcn-svelte primitives + dark-only theme, Tailwind v4). Consume it; add new shared components into it, not into the app.
Scaffolding: use the scaffold-civitai-app skill to stand up apps/creator-studio. It cherry-picks only the
packages we import and wires each one's dependency, transpile entry, env vars, and server shim (SvelteKit by default).
SvelteKit tooling notes (consolidated so we don't re-learn them):
| Concern | Decision |
|---|---|
| Framework | SvelteKit + @sveltejs/adapter-node, Svelte 5, Vite |
| UI | @civitai/ui (shadcn-svelte / bits-ui) — reach for shadcn-svelte before hand-building; Civitai composites (EdgeMedia, ImageGuard, masonry) get built on top of the primitives, into @civitai/ui |
| Styling | Tailwind v4 (@tailwindcss/vite), dark-only (<html class="dark">), @import '@civitai/ui/theme.css', @source the package so classes aren't purged |
| Auth | createSpokeGuard in hooks.server.ts; gate is any authenticated user (require: (u) => !!u). Member-only actions are enforced per-action on the tier (see §5) |
| Data | @civitai/db/kysely (env-free, no Prisma engine) for domain reads/writes; monetization mutations run in-process via a creator-studio module (kysely + @civitai/buzz) — no main-app tRPC hop. See §5 |
| Buzz | @civitai/buzz — a server-side-only client for the buzz service (transactions + earnings reads). All buzz comms are server-side; never imported into browser code |
| Env | app-local .env (+ committed .env.example); process.env shim in vite.config.ts |
Package discipline (avoid sprawl). A thing becomes a shared @civitai/* package only when (1) ≥2 apps
actually import it and (2) it's a coherent, nameable capability — not "a slice of shared logic." Otherwise
it's a module inside the app, extracted to a package only when a second app needs it (rule-of-three). For v1 that
means exactly one new package — @civitai/buzz — with monetization writes and analytics reads living as
creator-studio modules until the full-cutover consolidation (§9).
3. Page list (v1)
Routes under apps/creator-studio/src/routes/. Grouped, minimal, one screen per job.
| Route | Page | Purpose |
|---|---|---|
/ |
Dashboard / overview | At-a-glance earnings across all sources + headline stats; entry points to each section. |
/earnings |
Earnings | Breakdown by source: license fees, tips, model-access sales, cosmetic sales. Time-series + totals. Links to CP cash/withdrawal. |
/earnings/analytics |
Basic analytics ⭐ | Model usage that drives fees (generations per resource, downloads, engagement over time). v1 priority — keep "basic" for v1; richer analytics is post-v1. |
/licensing |
Licensing fees (bulk editor) | Table of the creator's models/versions; multi-select; set/clear fee; apply model-type default suggestions (LoRA ~0.1, base ~1 buzz/image); fractional pricing. (Bulk editor may trail the per-version editor on /models — §8.) |
/models |
Model management ⭐ | Grouped by model (versions nested, drafts included). Per-version: full early/paid-access config, licensing-fee on/off + amount (members only), "sell access indefinitely" for CP members, and (2nd-priority) publish/schedule. v1 priority. See models.md. |
/settings |
Payout & settings | Payment config (Tipalti) status, membership/tier status, per-account default fee suggestions. |
/join |
Membership upsell | Shown to non-members (any logged-in user can reach the Studio): what the member tier unlocks + link to subscribe. (Tier vs CP gate to confirm — §5.2.) |
Public/allowlisted (pre-gate): /favicon.svg, health check.
Shop pages are not in this app's v1. Shop management (
/shop,/shop/items/[id]) is being built in the main app by another dev and transferred into Creator Studio later; routes reserved but not built here now.
Navigation. Desktop = sidebar, mobile = header nav — both come from @civitai/ui's shadcn sidebar +
sheet + is-mobile hook (the responsive collapse is largely built in). Both navs render from a single app-local
constant (apps/creator-studio/src/lib/nav.ts — { href, label, icon, memberOnly? }[]) that mirrors this table, so
the two can't drift and adding a page is a one-line change. It's app-local config (one app → a module, not a package).
Notes: match active state carefully for nested routes (/earnings vs /earnings/analytics); memberOnly lets
both navs conditionally show/disable member-gated items off the user's tier (ties into the §5.2 gate).
4. Feature → workstream map
| # | Workstream | Built today? | v1 work |
|---|---|---|---|
| 1 | Retire 25% comp | Live (to sunset) | Post-v1 cutover track (not this app's v1): stop minting Compensation rows; remove comp UI; keep tips + license-fee payout paths |
| 2 | Creator-controlled licensing fees | Mostly built | Fractional pricing, member-tier gating, bulk edit (new), default suggestions |
| 3 | Sell model access (no time cap) | Partially (score-capped EA) | Remove time/qty caps for members; expose toggle in Studio |
| 4 | Creator Shops | Being built in the main app (by another dev) | Not this app's v1 — transferred into Creator Studio later; cosmetics 70/30; Shopify merch is fast-follow |
| 5 | Creator Studio | Net-new | This whole app |
5. API / service plan
5.1 The core architectural decision — where does business logic run?
Starting point: the money ledger is already a separate service. createBuzzTransaction doesn't write a local table
— it POSTs to BUZZ_ENDPOINT (buzzApiFetch('/transaction'), buzz.service.ts:383). The buzz service is a standalone
.NET / ASP.NET Core Minimal API on PostgreSQL (C:\work\civitai-buzz) with ClickHouse only as a post-commit
tracking sink. So the ledger is not something to extract — it's already external, and every app is a client of it.
What lives in the main app is monetization orchestration, not the ledger: validate → call the buzz service →
co-write domain rows in the same flow (early-access purchase mints the buzz transaction and an EntityAccess
grant + version meta), plus fee rules, stacking, split recipients, tier gating — all over ModelVersion /
CustomerSubscription.
Decision (agreed with the team): server-side, in-process; Studio runs mutations itself — no main-app tRPC hop.
One new package (@civitai/buzz); monetization writes are a creator-studio module, not a package yet
(package discipline, §2).
@civitai/buzz (PACKAGE — built) server-side client for the buzz service. Earns package status: the main app
already uses it broadly AND creator-studio needs it. Lifted out of buzz.service.ts.
SERVER-ONLY — no browser entry; all buzz comms stay server-side.
monetization module (creator-studio MODULE — apps/creator-studio/src/lib/server/monetization/, NOT a package yet)
the creator ops: setLicensingFee, bulkSetLicensingFee, setUnlimitedAccess + member-tier gate.
Deps: @civitai/db (kysely) + @civitai/buzz. NO ClickHouse. NO stacking (backend handles it).
Why a module, not a package: in v1 only creator-studio writes fees (the main app keeps its own inline version), so the ops have a single caller — no cross-app sharing yet, so no package. They graduate to a package only at the consolidation (full cutover), when the main app adopts the same code (§9).
Imported only in server code — in creator-studio that's +page.server.ts, form actions, +server.ts,
hooks.server.ts. The browser calls creator-studio's own server, which runs the operation in-process:
BROWSER ─► creator-studio SERVER ─► monetization module ─► @civitai/buzz ─► buzz service (.NET)
(no buzz) (SvelteKit load/actions) (kysely domain writes) (package)
Why in-process (module/package), not a service or a tRPC hop:
- A standalone monetization service is the wrong shape: the orchestration co-writes main-app domain tables, so a service would need those tables (moving the domain, not payments) or add distributed-transaction failure modes on money code. Running in-process keeps the buzz-call ↔ domain-write co-transactionality inside each consumer.
@civitai/buzzearns package status (main app uses it broadly + creator-studio); the monetization ops do not yet (one caller in v1) — extract them to a package when the main app adopts them, not before.
Cost note: the eventual package extraction (at consolidation) means porting the moved functions Prisma→kysely and repointing the main app's live call sites — money-critical. But v1's creator-side writes are new, small, and single-app, so there's no extraction on the critical path (Q1 decided minimal — §9).
Earnings/analytics reads — ClickHouse, not the buzz service (Justin):
All earnings + analytics read from ClickHouse, not the buzz service — querying the buzz service for dashboards
is too slow. Buzz earnings already land in ClickHouse, and aggregate tables like resourceCompensations are
already daily aggregates — the right source. Design for scale from materialized views / daily records, never
individual rows, and audit which mat views exist vs. need adding (§7.6).
Creator Studio reads via @civitai/clickhouse (a required v1 dep). (The buzz client's report endpoints exist but
are not the analytics path.)
5.2 Reuse (existing main-app endpoints/services)
| Need | Existing surface |
|---|---|
Is user a paying member (by tier)? |
Active subscription tier: CustomerSubscription → Product.metadata.tier (bronze/silver/gold). ⚠️ creatorProgram.getCreatorRequirements also gates on creator score ≥40k (full CP membership) — a stricter bar; use it only if the gate is meant to be CP-membership, not tier alone. Confirm which (Justin said "tier," HackMD said "CP members" — see §9 "to confirm"). creator-program.service.ts:205 |
| Earnings + analytics (all) | ClickHouse via @civitai/clickhouse — buzz earnings + resourceCompensations aggregates live here; the buzz service is too slow for this. Work from materialized views / daily records (§7.6) |
| CP cash / banked / pool | creatorProgram.getCash / getBanked / getCompensationPool (creator-program.router.ts) |
| Set a licensing fee (single) | modelVersion upsert — licensingFee* fields (model-version.schema.ts:418), flag licensing-fee |
| Early/paid access config + purchase | modelVersion.earlyAccessPurchase, earlyAccessModelVersionsOnTimeframe (model-version.router.ts) |
| Cosmetic purchase + 70/30 split | cosmeticShop.purchaseShopItem; split via computeCreatorShopSplit (creator-shop.schema.ts) paying Cosmetic.createdById → TransactionType.Sell (cosmetic-shop.service.ts, purchaseCosmeticShopItem) |
5.3 New monetization operations (creator-studio module; extract to a package at consolidation)
These are module functions in creator-studio, not main-app tRPC procedures — creator-studio calls them from its own server. The main app keeps its own inline fee/access logic for v1; the two converge onto one shared implementation only at the consolidation (§9 "full cutover").
| Operation | Why |
|---|---|
setLicensingFee / bulkSetLicensingFee |
Per-version fee set (member-only) + bulk edit across many versions — no bulk path exists today. |
getDefaultFeeSuggestions |
Model-type default suggestions (LoRA ~0.1, base ~1 buzz/image). |
setUnlimitedAccess |
Remove EA time/quantity caps for members. |
| fee validation (member gate + fractional) | Enforce member tier + fractional bounds when setting a fee. Stacking/split is NOT here — the backend already handles it (§7.3). |
member-tier gate |
Authorization asserted inside the module on member-only operations (and reused when the main app adopts it at consolidation). |
Earnings reads are not package operations — all analytics come from ClickHouse via @civitai/clickhouse
(§5.2, §7.6).
Per-creator shop CRUD is out of scope (main-app shop work owns it, see §1).
6. Main-app code to REMOVE
Strategy (per decision): redirect-first. In v1 we do not delete the superseded main-app code. We (a) redirect
the relevant routes to creator.civitai.com, and (b) mark each block for deletion with
// TODO(creator-studio): remove after Creator Release cutover so a later phase can safely rip it out. The 25%-comp
payout change is the one behavioural change that lands at cutover.
6.1 Retire 25% generation compensation (behavioural — at cutover)
| File | What |
|---|---|
src/server/jobs/deliver-creator-compensation.ts:150 |
Stop minting Compensation-type BuzzTransaction rows at cutover; keep tip + licenseFee payout paths. This is the real sunset. |
src/store/tip.store.ts:4 |
creatorTip: 0.25 default → remove creator-tip default. |
src/components/generation_v2/FormFooter.tsx:150 |
Remove creatorComp && hasCreatorTip tip line from cost calc. |
src/components/ImageGeneration/GenerationForm/GenerationCostPopover.tsx:108 |
Remove creator-tip rate UI. |
src/components/Buzz/Rewards/DailyCreatorCompReward.tsx |
Remove the "Compensation" tab; License-Fees view moves to Studio /earnings. |
src/pages/user/buzz-dashboard.tsx:77 |
Remove creatorComp-gated comp section; redirect creator-earnings entry to creator.civitai.com/earnings. |
src/server/services/feature-flags.service.ts |
Retire the creatorComp flag once UI is gone. |
6.2 Redirect superseded management surfaces
| Main-app surface | Redirect target |
|---|---|
Buzz dashboard creator-earnings section (buzz-dashboard.tsx) |
creator.civitai.com/earnings |
Licensing-fee editing in ModelVersionUpsertForm.tsx:800 |
Keep (per Justin) — inline fee editing stays as an additional surface until model management is fully deprecated from the main site. Not redirected/removed in v1; optionally add a "manage all fees" link → creator.civitai.com/licensing. |
Any "creator score → early access caps" management copy (constants.ts:1708) |
Superseded by member "sell indefinitely"; keep score caps for non-members, add Studio link. |
Every redirect/removal block gets the
TODO(creator-studio)marker so the post-release cleanup is a grep away.
7. New packages, schema & cross-team work
What has to be built/changed outside Creator Studio's own UI for it to work.
7.1 Schema / data (main-app DB)
| Change | Detail |
|---|---|
| Fractional licensing fee | ModelVersion.licensingFee is Int today (schema.full.prisma:1042, MAX_LICENSING_FEE=100). Justin confirmed fees as small as 1 buzz per 100 images = 0.01 buzz/image, so we need decimal with 0.01 precision. Migrate to numeric/decimal; settle sub-buzz amounts at the daily payout boundary (not floored per transaction) in deliver-creator-compensation.ts. Manual migration (repo does not run prisma migrate deploy). |
| Access "unlimited" flag | Represent "available for sale indefinitely, no time/quantity cap" on the model version (extend earlyAccessConfig or add a field) so Creator Program members (per Justin) bypass scoreTimeFrameUnlock/scoreQuantityUnlock. Needs main-app support + studio integration (models.md). |
| Licensing-fee "active" flag (per Justin) | A member's fee must auto-pause when they have no active membership — the set value is never removed, only whether it applies. The mini endpoint (src/pages/api/v1/model-versions/mini/[id].ts, where Koen resolves the fee) checks active membership on hit to decide whether the fee applies, and the flag drives whether the fee shows on the model card. Add an active/enabled flag on the version's licensing fee. The pre-cutover "not-yet-payable" flag (§1) is the same kind of gate for the launch window. |
Per-creator shop schema (owner + submission/approval on CosmeticShopSection/CosmeticShopItem) is owned by the
main-app shop work, not this plan.
7.2 New package + creator-studio modules
@civitai/buzz(package, built) — server-side client for the buzz service, lifted out ofbuzz.service.ts; both apps import it. Server-only, no browser entry. (The one thing that earns package status now — §2.)- Monetization module (creator-studio, not a package yet) — the §5.3
ops, built on
@civitai/db(kysely) +@civitai/buzz, authorization inside, no ClickHouse. Lives inapps/creator-studio/src/lib/server/monetization/. - Analytics reads module (creator-studio, not a package) — ClickHouse reads via
@civitai/clickhouse(§7.6). - Consolidation (post-v1, not v1) — at the full cutover, extract the monetization module into a shared package and
repoint the main app's inline fee/access logic (
model-version.service.ts) onto it so it exists once. Money-critical (Prisma→kysely port + dep untangling); deferred off v1's path (§9).
7.3 Fee stacking — already handled (no new work)
Per Justin, stacking is already handled on the backend — a derivative fee stacking on the base model's fee is a
backend concern, not something creators do. There is nothing for creators to do here beyond setting their own
licensing fee. So the earlier "must implement additive stacking + split payout" is not Creator Studio work, and
it does not land in the monetization module. (Eng note: the lineage/base fee now comes from a derivative's
explicit parent root (licensingSourceVersionId → its LicensingRoot), stacked on top of the creator's own
per-version fee — the two are independent, so a creator can always set their own fee. The old
BaseModelLicensingFee fallback that this note used to worry about no longer exists.)
7.4 Cross-team dependencies & coordination
The design has cross-team dependencies — surface these early (likely with Koen / backend):
- ClickHouse / data — the earnings
sourcesplit (compensation / licenseFee) already exists inorchestration.resourceCompensations(tips + access/cosmetic sit in other tables — see §7.6 / A5); the real add is the owner-keyed earnings rollup (§7.6). (The earlier concern about tagging the buzz-service report endpoints is moot — analytics read from ClickHouse, not the buzz service.) - Main-app owner — sign-off on the §7.2 consolidation refactor: repointing live, money-critical fee/access call sites onto the extracted monetization package at the cutover.
- Buzz-service owner (
C:\work\civitai-buzz, .NET) — only if v1 shows access/cosmetic-sale earnings by source (those are buzz transactions — §7.6 gap #2).
7.5 Engineering to-dos (not Justin — eng verification)
- Earnings by source is partly available:
orchestration.resourceCompensations.source= comp / licenseFee only (CH audit 2026-07-14 — notipthere; tips live indefault.buzz_resource_compensation, access/cosmetic are buzz txns → A5). The remaining gaps are the owner-keyed rollup and unioning A1+A5 sources (§7.6). - The
earlyAccessPurchase()dependency-map is now optional — with Q1 minimal and the extraction deferred to the post-v1 consolidation, it's off any critical path. Do it when scoping the consolidation.
7.6 ClickHouse analytics — materialized views
Analytics + earnings read from ClickHouse via @civitai/clickhouse (required v1 dep), from daily
aggregates, never individual rows (the buzz service is too slow). Inventory from a live audit (2026-07-01):
Exists — reuse (all keyed by modelVersionId, date):
| Need | Table |
|---|---|
| Earnings by resource + source (comp / licenseFee — no tip here) | orchestration.resourceCompensations (SummingMergeTree; +accountType, source) |
| Earnings mirror (comp/tip split) | default.buzz_resource_compensation |
| Generations per resource | default.daily_resource_generation_counts |
| Downloads | default.daily_downloads, daily_downloads_unique, modelVersionUniqueDownloads |
| Model/version metrics | default.entityMetric* suite |
Needs adding — the gap is the creator/owner dimension. Every earnings aggregate is keyed by modelVersionId,
never the owner's userId (the userId columns that exist — daily_user_resource, userModelDownloads — are the
generator/downloader, not the creator). So "creator X's earnings" needs modelVersion → ownerUserId:
- Owner-keyed earnings rollup — an MV aggregating
(ownerUserId, date, source) → sum(amount)offresourceCompensations(+ amodelVersion → ownerUserIddictionary in CH). Scales better than app-sideWHERE modelVersionId IN (…), which balloons for prolific creators. Full spec + open decisions: owner-rollup-handoff.md. Two design decisions flagged there (CH audit 2026-07-14): (D1) the key likely needsaccountType/currency so buzz-color vs cash stay separable (B8); (D2)resourceCompensationsholds comp + license only — tips + access/cosmetic come from other MVs (A5), so the/earningssource filter must union A1+A5 on a shared source vocabulary. Also:amountis fractional — the MV must notFLOORit. - Per-creator access/cosmetic-sale earnings (only if v1 needs them) — those are buzz transactions, not in
resourceCompensations;buzz.transactions_daily_statsis platform-wide (no account dimension). Would need a per-toAccountIddaily buzz-earnings-by-type MV.
Shared read logic: the CH client (@civitai/clickhouse) is already shared by both apps. The creator-earnings
query logic is new in Creator Studio and legacy in the main app (getDailyCompensationRewardByUser, being
redirected). Consolidating both onto one server-side read module is a full-cutover item (§9)
— worth it so both domains show identical numbers during the transition.
8. Phasing
Ordering reflects the designer steer: model management + analytics lead. The shop is not this app's job (main-app dev owns it; transfers in later). The comp⇆fee cutover is a separate backend track — Creator Studio v1 ships ~1 week before it (Justin), so comp-retirement is not a v1 item.
v1 — MVP (designer-prioritised)
- App shell — scaffold
apps/creator-studio, auth spoke gate (any authenticated user) + member-tieraction gating,@civitai/ui, dashboard. - Buzz package + monetization module —
@civitai/buzz(client, built) + a creator-studio monetization module (creator ops:setLicensingFee/bulkSetLicensingFee/setUnlimitedAccess+ member-tiergate; no stacking — backend handles it) built on@civitai/db+@civitai/buzz(§7.2). No main-app repoint in v1 — that's the consolidation (post-v1). - Model management ⭐ —
/models: per-version early/paid-access toggles, per-version licensing-fee on/off + amount (members only; non-members cannot set), "sell access indefinitely" for CP members. - Analytics ⭐ —
/earnings/analytics+/earnings: read from ClickHouse (daily aggregates / materialized views, §7.6) — not the buzz service. - Licensing-fee mechanics — fractional pricing (schema migration + daily-boundary settlement) + the membership-gated "active" flag and pre-cutover "not-yet-payable" flag (§7.1). No stacking work (backend). Bulk editing / default suggestions can trail the per-version editor.
Not in v1 (separate backend track): retiring 25% comp + activating fees flips ~1 week after v1 ships (§1).
Post-v1 — fast-follow / later
- Comp⇆fee cutover (~1 week after v1) — flip fees live + stop minting
Compensationrows (§6.1); pre-cutover fees become payable. - Transfer the Creator Shop into Creator Studio — the main-app dev's shop (cosmetics, 70/30) moves here; Shopify merchandise trails that (blocked on Shopify token).
- Bulk licensing-fee editor + model-type default suggestions — if not landed in v1.
- Delete the redirect-marked main-app code (the
TODO(creator-studio)sweep). - Broaden the Studio beyond monetization (model upload, posts, profile settings migration) — only if the "creator hub" direction is later chosen.
- Richer analytics (cohorts, per-model funnels), cash-flow forecasting.
9. Decisions & open questions
Decided (Justin, 2026-07-01)
- Access model. Any logged-in user can access Creator Studio; member-only items/actions are gated on the user's
subscription
tier. → app gate is "authenticated," not CP-only (§1, §2). - Timeline. Launch by end of month (~2026-07-31); Justin is working the orchestrator side with Koen.
- Fractional pricing. Fees as small as 1 buzz per 100 images = 0.01 buzz/image. → migrate
licensingFeeto decimal (0.01 precision), settle at the daily payout boundary (§7.1). - Shop. Being built in the main app by another dev; transferred into Creator Studio later. → out of this app's v1; all shop questions deferred to that work.
- Infra.
creator.civitai.comas an auth spoke — confirmed. - Service topology (eng decision). Buzz ledger is already an external service. No standalone monetization service.
Server-side, in-process: one new package —
@civitai/buzz(buzz-service client, built) — plus creator-studio modules for monetization writes (kysely + buzz; auth inside) and analytics reads (ClickHouse); Studio runs mutations itself with no main-app tRPC hop. Modules graduate to packages only at the consolidation, per the package bar (§2). Analytics read from ClickHouse (Q6 below), not the buzz service (§5.1). What remains for Justin is Q1's timeline/scope tradeoff, not the topology. - Fee stacking (Q2). Already handled on the backend — creators only set their fee; no stacking/split work in Creator Studio (§7.3).
- Membership gating (Q3). Fees auto-pause when a member lapses; the set value is kept, only its application is
gated. The mini endpoint checks active membership on hit; add an
activeflag on the version's fee for card display (§7.1). - Inline fee editing (Q4). Stays on the main app as an additional surface until model management is fully deprecated there (§6.2).
- Cutover timing (Q5). The comp⇆fee cutover is a separate backend track; Creator Studio v1 ships ~1 week before it. Pre-cutover fees carry a "not-yet-payable" flag (§1, §8).
- Analytics source (Q6). All analytics/earnings read from ClickHouse (daily aggregates / materialized views), not the buzz service (too slow). ClickHouse is a required v1 dep (§7.6).
Decided — Q1: minimal monetization scope for v1
The creator-studio monetization module ships the minimal creator-side writes only — setLicensingFee /
bulkSetLicensingFee with a Creator Program membership gate (decided 2026-07-09) and the active flag
(§7.1), plus setUnlimitedAccess (same shape). These are plain ModelVersion writes — no buzz call, no domain co-write. The risky
buyer-side paths (earlyAccessPurchase, cosmetic purchase) stay in the main app; they don't move for v1. It's a
module, not a package (single caller in v1 — §2); with the cutover decoupled (Q5) the
extraction is off the critical path, so the dependency-map (§7.5) is no longer a blocker.
Member gate — DECIDED (2026-07-09)
- Full Creator Program membership is the single bar for ALL member-only actions (fee-set and
sell-indefinitely) — not a tier-only or feature-specific split. Rationale: tier-only would let a brand-new
bronze subscriber fee-gate other people's models; CP requires creator score. Resolved from the
onboardingCP flag. See questions-justin-product.md B1 / pre-implementation-decisions.md B1.
Questions for Justin — review pass (2026-07-02) · ANSWERED 2026-07-09
Full responses in questions-justin-product.md (B1–B11) and the reconciled pre-implementation-decisions.md. Notable changes from the assumptions below: single CP-membership gate (Q1), indefinite-sale = uncapped early access (Q2), all earnings sources in v1 (Q3), two-section analytics (Q4), notify on fee pause (Q5), bulk editing is v1 (Q9).
Consolidated product/business calls for Justin's doc review. (Eng/design-owned items — charting lib, /licensing
separate-vs-mode, owner-keyed rollup, etc. — are in creator-studio/README.md.)
- Feature-specific gates? Confirm the two bars differ: licensing fee gates on subscription
tier; indefinite-sale gates on CP membership (see the "To confirm" item above). - Indefinite-sale mechanics. How does "available for sale indefinitely" actually work — a one-time purchase at a creator-set price? How does it relate to early-access pricing (replaces / stacks / separate)? New and underspecified; needs main-app support (§7.1).
- v1 earnings sources. Show all sources (incl. model-access + cosmetic sales — need a new buzz rollup, §7.6 gap #2), or ship comp / license / tip only in v1 and add the rest later? (earnings.md)
- "Basic analytics" scope. Lock the v1 metric/chart list (proposed: generations-over-time, downloads-over-time, a top-models table, a few stat tiles) so it doesn't balloon (analytics.md).
- Fee auto-pause → notify? When a fee silently pauses because membership lapsed (§7.1), is the creator notified (in-app / email)? A silent pause = lost income = support tickets. Do we need notifications in v1 (fee paused, payout ready)?
- Cutover creator comms / grandfathering. When 25% comp retires (~1 week after v1), what's the creator-facing story, and is there any grandfathering / transition for creators mid-accrual?
- Max licensing fee. Floor is 0.01 buzz/image (confirmed). Is the cap still 100 buzz/image
(
MAX_LICENSING_FEE) with fractional pricing, or does it change? - Studio discoverability. How do creators find
creator.civitai.com— a nav link from the main app for everyone, only users with models, or a launch announcement? (Shapes the non-member //joinexperience.) - Publish/schedule + bulk fee editor — v1 or fast-follow? Both are flagged "2nd priority" / "may trail" (models.md, licensing.md) — confirm which land in v1.
- Currency display. Buzz-only in v1, or show USD equivalents for cash earnings (dashboard / earnings)?
- Default fee suggestions. Confirm the values (LoRA ~0.1, base ~1 buzz/image) and which model types get one.
For the full cutover (post-v1 notes)
- Extract the monetization module into a package and repoint the main app's own fee-set call sites onto it so
there's one implementation (today
ModelVersionUpsertForm→ main-app service writes the fee; that stays until this consolidation). This is when the module graduates to a package (second app now imports it — §2). - Consolidate earnings/analytics reads — the main app's legacy
getDailyCompensationRewardByUser(buzz.service.ts:1142) and Creator Studio's new ClickHouse reads should resolve to one server-side read module so civitai.com and creator.civitai.com show identical numbers during the transition (§7.6). - Add the owner-keyed earnings rollup in ClickHouse (§7.6).
- Flip comp-retirement + fee activation together (the cutover, ~1 week after v1); make pre-cutover fees payable.
- Delete the redirect-marked main-app code (the
TODO(creator-studio)sweep).
Appendix — key code references
- Comp payout:
src/server/jobs/deliver-creator-compensation.ts(daily 02:00 UTC;tip|compensation|licenseFeesources) - Licensing fee:
ModelVersion.licensingFee*,ModelVersion.licensingSourceVersionId(a derivative's explicit parent root),LicensingRoottable (root membership +isDefault; replaces the oldLicensingRootflag +BaseModelLicensingFeepointer),ModelVersionFlag.NotDerivative(non-derivative versions skip parent attribution),MAX_LICENSING_FEE=100(model-version.schema.ts), flaglicensing-fee. Fee resolution: root → own fee; explicit parent → its fee; no(baseModel, modelType)fallback. - Early access:
earlyAccessConfig/earlyAccessEndsAt(schema.full.prisma:1035), score caps (constants.ts:1708-1738),earlyAccessPurchase()(model-version.service.ts:1481) - CP membership:
getCreatorRequirements()(creator-program.service.ts:205),MIN_CREATOR_SCORE=40000,OnboardingSteps.CreatorProgram=16 - Cosmetic shop: platform-owned
/shop;purchaseCosmeticShopItem()split (cosmetic-shop.service.ts:619); CRUD is moderator-only (cosmetic-shop.router.ts) - Spoke wiring:
docs/packages/new-app-integration.md,docs/auth/spoke-integration-guide.md,packages/civitai-ui/README.md, skillscaffold-civitai-app