Skip to content

Migration Guide: character-creator-studio@franksauvag/rpg-commons

This guide covers migrating code from character-creator-studio (CCS) to the shared @franksauvag/rpg-commons library.


2.0.0 (lib + backend — before CCS lot L4–L8)

ChangeAction
contracts.ts removedImport types from @franksauvag/rpg-commons/srd (same names: ClassDef, DndSpell, …)
RuntimeUse createSrdRuntime({ apiBaseUrl, defaultLocale? }) for bundles, loadTypeEntries, meta, translation-stats
CharacterImport DndCharacter from @franksauvag/rpg-commons/character (CCS: replace @/editor/types/character)
IndicesAbilityIndex, AlignmentIndex from @franksauvag/rpg-commons/srd
UI labels abilities/alignmentsBackend GET /api/locales/{locale}/ui/srd + i18next keys ability.str.name, etc.

Do not import from @franksauvag/rpg-commons/srd/transform/* — not part of the public API.


Overview of Changes

AreaBefore (CCS)After (lib)
UI component imports@/components/ui/button@franksauvag/rpg-commons
SRD data imports@/data/srd/...@franksauvag/rpg-commons/srd
API base URLRead from VITE_API_BASE_URL automaticallyInjected via createSrdClient({ apiBaseUrl })
Media resolutionresolveEntryMediaSrc(key) (reads env internally)resolveEntryMediaSrc(key, apiBaseUrl)
Image propmediaKey (SRD key, resolved internally)imageUrl (pre-resolved absolute URL)
Shared RPG components@/components/shared/...@franksauvag/rpg-commons
Type contracts@/data/srd/contracts (local)@franksauvag/rpg-commons/srd

1. UI Components

Before

ts
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";

After

ts
import { Button } from "@franksauvag/rpg-commons";
import { Card, CardHeader, CardTitle } from "@franksauvag/rpg-commons";
import { Badge } from "@franksauvag/rpg-commons";
import { Input } from "@franksauvag/rpg-commons";

// Or combined:
import { Button, Card, CardHeader, CardTitle, Badge, Input } from "@franksauvag/rpg-commons";

All 45 shadcn/ui components are exported from the main entry point.

Add stylesheet to app entry

ts
// In your main.tsx / main.ts
import "@franksauvag/rpg-commons/style";

2. SRD Data Client

The biggest change: the lib no longer reads VITE_API_BASE_URL from import.meta.env. The consuming app is responsible for providing apiBaseUrl when creating the client.

Before (CCS)

ts
// src/data/srd/http/srdApiClient.ts reads import.meta.env.VITE_API_BASE_URL implicitly
import { fetchSrdDataset, loadSrdDatasetMapped } from "@/data/srd/http/srdApiClient";
import { buildClassesBundle } from "@/data/srd/http/bundleBuilders";

const bundle = await buildClassesBundle();
const fighter = bundle.getClass("fighter");

After (lib)

ts
import { createSrdClient } from "@franksauvag/rpg-commons/srd";

// App provides apiBaseUrl explicitly (from env, config, or context)
const srd = createSrdClient({
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL ?? "",
});

const bundle = await srd.buildClassesBundle();
const fighter = bundle.getClass("fighter");
ts
// src/lib/srdClient.ts
import { createSrdClient } from "@franksauvag/rpg-commons/srd";

export const srdClient = createSrdClient({
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
});

With TanStack Query

ts
// src/hooks/useSrdBundles.ts
import { useQuery } from "@tanstack/react-query";
import { srdQueryKeys, SRD_QUERY_STALE_MS } from "@franksauvag/rpg-commons/srd";
import { srdClient } from "@/lib/srdClient";

export function useClassesBundle() {
  return useQuery({
    queryKey: [...srdQueryKeys.all, "classes-bundle"],
    queryFn: () => srdClient.buildClassesBundle(),
    staleTime: SRD_QUERY_STALE_MS,
  });
}

3. Media Resolution

Before (CCS)

ts
import { resolveSrdMediaUrl, resolveEntryMediaSrc } from "@/data/srd/http/resolveSrdMediaUrl";

// Env read internally
const url = resolveSrdMediaUrl("srd/classes/fighter.png");
const src = resolveEntryMediaSrc(entry.image);

After (lib)

ts
import { resolveSrdMediaUrl, resolveEntryMediaSrc } from "@franksauvag/rpg-commons/srd";

const API_BASE = import.meta.env.VITE_API_BASE_URL;

// apiBaseUrl is now explicit
const url = resolveSrdMediaUrl("srd/classes/fighter.png", API_BASE);
const src = resolveEntryMediaSrc(entry.image, API_BASE);

Behaviour change: when src is a non-legacy /images/... path (e.g. /images/placeholder.svg), the lib returns it unchanged. CCS called publicAssetPath() which prepended BASE_PATH. In the lib, /images/... paths are returned as-is — the consuming app handles its own public asset paths.


4. SrdImageOrInitials — API Change

The mediaKey prop has been replaced with imageUrl. The consuming app pre-resolves the URL using resolveSrdMediaUrl.

Before (CCS)

tsx
import { SrdImageOrInitials } from "@/components/shared/SrdImageOrInitials";

// Component resolved the key to URL internally
<SrdImageOrInitials mediaKey={classDef.image} label={classDef.name} />;

After (lib)

tsx
import { SrdImageOrInitials } from "@franksauvag/rpg-commons";
import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd";

const imageUrl = resolveSrdMediaUrl(classDef.image, import.meta.env.VITE_API_BASE_URL);

<SrdImageOrInitials imageUrl={imageUrl} label={classDef.name} />;

5. SelectionCard — API Change

Same principle: imageUrl replaces any internal media resolution.

Before (CCS)

tsx
<SelectionCard title="Fighter" mediaKey={classDef.image} />

After (lib)

tsx
import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd";

const imageUrl = resolveSrdMediaUrl(classDef.image, import.meta.env.VITE_API_BASE_URL);

<SelectionCard title="Fighter" imageUrl={imageUrl} />;

6. TypeScript Contracts

Before (CCS)

ts
import type { ClassDef, SpeciesDef, DndSpell } from "@/data/srd/contracts";
import type { ClassesBundle, SpellsBundle } from "@/data/srd/contracts";

After (lib)

ts
import type {
  ClassDef,
  SpeciesDef,
  DndSpell,
  ClassesBundle,
  SpellsBundle,
} from "@franksauvag/rpg-commons/srd";

All types (ClassDef, SpeciesDef, BackgroundDef, EquipmentItem, FeatDef, DndSpell, bundle interfaces, ClassSpellQuota, CasterPattern) are exported from @franksauvag/rpg-commons/srd.


7. srdDisplayInitials

ts
// Before (CCS — inline utility)
function getInitials(name: string) {
  /* custom */
}

// After (lib)
import { srdDisplayInitials } from "@franksauvag/rpg-commons";

srdDisplayInitials("Barbarian"); // → "BA"
srdDisplayInitials("Half-Orc"); // → "HA"
srdDisplayInitials("Dragon"); // → "DR"

8. Removed: Vite proxy dev same-origin behaviour

CCS's config.ts automatically switched to same-origin API URLs in dev mode to work with the Vite proxy. This logic is not present in the lib.

The consuming app should configure its own Vite proxy if needed:

ts
// vite.config.ts (in the app, NOT in the lib)
export default defineConfig({
  server: {
    proxy: {
      "/api": {
        target: process.env.VITE_API_BASE_URL,
        changeOrigin: true,
      },
    },
  },
});

Quick Reference

Old importNew import
import { Button } from "@/components/ui/button"import { Button } from "@franksauvag/rpg-commons"
import { ... } from "@/data/srd/contracts"import type { ... } from "@franksauvag/rpg-commons/srd"
import { buildClassesBundle } from "@/data/srd/http/bundleBuilders"import { createSrdClient } from "@franksauvag/rpg-commons/srd"client.buildClassesBundle()
import { resolveSrdMediaUrl } from "@/data/srd/http/resolveSrdMediaUrl"import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd" — add apiBaseUrl arg
import { SrdImageOrInitials } from "@/components/shared/SrdImageOrInitials"import { SrdImageOrInitials } from "@franksauvag/rpg-commons"mediaKeyimageUrl
import { srdQueryKeys } from "@/data/srd/queryKeys"import { srdQueryKeys } from "@franksauvag/rpg-commons/srd"