add-ecosystem
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Port an existing form (a data-graph graph, or a bespoke RHF+zod form like model training) to the form-graph library. Use when asked to move a form's field logic, branching, persistence, or server validation onto form-graph. Encodes the method proven by the generation-form port —
$ npx -y skills add civitai/civitai --skill form-graph-port --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/form-graph-portContext preview
The summary Claude sees to decide when to auto-load this skill.
Port an existing form (a data-graph graph, or a bespoke RHF+zod form like model training) to the form-graph library. Use when asked to move a form's field logic, branching, persistence, or server validation onto form-graph. Encodes the method proven by the generation-form port —
name: form-graph-port description: Port an existing form (a data-graph graph, or a bespoke RHF+zod form like model training) to the form-graph library. Use when asked to move a form's field logic, branching, persistence, or server validation onto form-graph. Encodes the method proven by the generation-form port — oracle-first differential testing, scope mapping, staged cutover.
The method that took the generation form (~45 family graphs, 4 output types, 7 standalone workflows, ~12k differential cases) onto form-graph, distilled so the next port (e.g. model training) doesn't rediscover it. The worked example is `src/shared/form-graph/generation/` + `docs/form-graph-port-plan.md`; read the plan doc's phase structure before starting anything sizable.
`C:\work\form-graph\CLAUDE.md` carries the library's design invariants (one branch combinator, sync resolution, wire-named computedKeys, the prepack-after-every-edit rule for `link:` consumers). Don't design against an imagined API.
Nothing else starts until parity is measurable.
`graph.safeParse`. For a bespoke form (training: `src/components/Training/Wizard` + `src/server/schema/training.schema.ts` + the orchestrator validation) it's the submit payload builder — capture real input→payload fixtures if there's no parse function.
oracle parse over generated cases, plus the **parse-fixpoint pin** (re-parse the port's own state → identical data; this is what makes whatIf/cost preview trustworthy).
own (see CLAUDE.md's microtask-loop warning; a hang is unreportable in vitest).
have dead paths and quirks. Pin the deliberate deltas in a comment or the plan doc.
`.field('ecosystem', def)` then `.use(branch('ecosystem', [[keys, member], ...] as const))`. Group related keys into one pair — arm count should equal *family* count, not key count.
to the tagged `branch(key, pick, members, { emit: false })` form.
`modelIdOf`-style raw-or-parsed readers — store state holds RAW inputs, so anything reading ctx must accept both shapes).
Translate the legacy storage-adapter groups (see the v1 `createLocalStorageAdapter` config in `GenerationFormProvider.tsx` for the pattern) into graph/field `scope` declarations: graph-level `scope` for family buckets, `rootScope()` to opt a field out to global memory, `rootScope(workflow)` for per-workflow buckets, relative `[modelId]` appends for per-variant refinements. One persisted record per form (`persistedStorage('<key>')`).
`InferData` / `InferArm` / `InferLooseData` from the graph type the handlers (`EcosystemData<'X'>` pattern in `src/shared/form-graph/generation/types.ts`). Zero `as never`; a residual cast marks a provably-dead path and says so. After type-level work, measure compiler cost against main (`tsc --extendedDiagnostics`, delete `tsconfig.tsbuildinfo`, `NODE_OPTIONS=--max_old_space_size=12288` — default heap OOMs).
Consumer-side module (`migrate-v1-storage.ts` is the template): read the old records, pick ONLY the fields worth carrying, build one address→raw-value record with `scopedAddress`, write it once iff the new key is absent. Values go in raw — the input schemas validate on first resolve, so stale garbage degrades to defaults. Never delete the old records while anything still reads them.
One feature flag (`availability: ['mod']` first, widened via its Flipt key) gates the whole cutover per user: it swaps the form component on the client AND serves the port's parse on the server (read from the ctx the feature-flag system already threads — coerce it, a sparse record reads `undefined`). Every server parse runs BOTH engines regardless and records the comparison — outcomes counted (`registerCounterWithLabels`), divergence logged with **diff keys only — never field values** (user content must not reach logs; pin that with a test, one sentinel per emit path). Comparison noise is fine: it dies with the old engine.
Flag off must be byte-identical. The generation port briefly used three flags (separate shadow/serve Flipt switches) and collapsed them once the parity battery made independent server/client rollback unnecessary — start with one. Deleting the old engine is a separate change after the flag is fully widened.
Until the old graph is deleted, EVERY merge from main needs: `git diff HEAD...origin/main --stat -- src/shared/data-graph` — then mirror each change into the port AND add a differential shape covering the changed path. The suites only catch drift where shapes exercise it: krea2's community-checkpoint fix (2026-09-03) passed parity under BOTH the old and new fallback because no shape used an unknown model id. A mirrored change without a new shape is unverified.
RULE on the graph (`.effect({...})` — gesture-aware, fires before resolution, covers every writer), NOT in transcribed v1 UI handlers. v1 kept it in handl
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…