Skip to content

For agents: an index of these docs is at /docs/llms.txt, and this page as markdown is at /docs/migration.md.

Migrating an app

Moving an existing app onto Signet.

Assessed 2026-09-22. Do app-b first — it is small, low-risk, and proves the packages work in a real product before touching the donor.

app-b — small, do this first

Local code that Signet now provides: lib/api.ts, auth.ts. No local UI components at all, so nothing to delete or visually diff.

Already on current versions: better-auth@^1.7.5, biome@latest, typescript@latest. Nothing to bump.

Steps:

  1. Replace apps/web/lib/api.ts with createApiClient from @dapplelight/signet-lib/api.
  2. Adopt the error envelope: errorHandler + parseBody from @dapplelight/signet-server/http in apps/api.
  3. Adopt socialProviders from @dapplelight/signet-server/auth/better-auth; its provider-skip logic came from app-b. betterAuthDefaults is optional here: it now defaults to Better Auth's own behaviour, but app-b's config (BETTER_AUTH_URL, a comma list of origins) may already be all it needs. An earlier version of this step said behaviour was unchanged. It was not — the defaults were app-a's, including a disabled OAuth state check.
  4. Optionally adopt @dapplelight/signet-server/middleware for cors and rate-limit.

Risk: low. No UI, no version jumps, and it already shares the conventions.

app-a — large, do this second

29 local UI components — exactly the set Signet was extracted from, so this is mostly deletion. Plus 16 modules with Signet equivalents: lib/api.ts, format.ts, query-client.ts, utils.ts, env.ts, runtime-env.ts, sentry.ts, email.ts, notify.ts, validate.ts, auth.ts, and the five middlewares.

Behaviour changes to expect — this is not a pure refactor

Signet's copies were deliberately changed during extraction. Migrating app-a adopts those changes:

  • Every <Badge> changes colour. The default variant went from bg-accent-green to bg-primary. That was a finance convention leaking into a generic component, but app-a is the product where it looked intentional. Expect a visible diff; decide whether app-a wants variant="default" or an explicit green variant at each call site.
  • trend-chip moved out of the barrel to @dapplelight/signet-ui/domain/trend-chip. Imports must be updated; it is no longer exported from the index.
  • Badges become pills. rounded-4xl referenced an undefined --radius-4xl and silently produced no radius. Now rounded-full.
  • --color-cat-* moved to @dapplelight/signet-tokens/finance.css. Import that instead of editorial.css or the category colours vanish.
  • getRuntimeEnv() behaviour changed. app-a's short-circuited to process.env whenever Bun was present, discarding injected Workers values. Signet's merges both, injected winning. Verify nothing depended on the old behaviour.
  • Validation errors move. app-a stringified flatten() into the message field; Signet puts it in details. Any client code doing JSON.parse on an error message breaks and should read ApiError.details.fieldErrors.

Version jumps required

app-a is on typescript@^5.8.3, vite@^6.3.2, @tanstack/react-table@^8.21.3, react-day-picker@^9.14.0, better-auth@1.6.9, date-fns@^4.1.0.

Signet is on TS 7, Vite 8, react-table 9, day-picker 10. Three are breaking:

  • TypeScript 7 removed baseUrl — every tsconfig using it needs paths.
  • react-table 9 is opt-in features and a renamed API. app-a has its own table call sites beyond data-table.tsx; each needs the same treatment.
  • react-day-picker 10 renamed the table classname slot to month_grid.

date-fns can be dropped — @dapplelight/signet-lib/format uses Intl.

Suggested order

  1. Bump app-a to the Signet versions first, on its own branch, and get it green. Do not mix a version migration with a package migration.
  2. Swap packages/ui for @dapplelight/signet-ui, import finance.css, review the Badge diff visually.
  3. Swap the server modules one at a time, env and middleware first (mechanical), auth last (highest risk). betterAuthDefaults no longer defaults to app-a's settings. To keep app-a's current behaviour exactly, pass them explicitly — and reconsider the first two:
    betterAuthDefaults(env, {
    sessionSeconds: 30 * 24 * 60 * 60,
    cookieCacheSeconds: 300, // a deleted account stays signed in up to 5 min
    skipStateCookieCheck: true, // disables the OAuth state CSRF check
    rateLimit: { window: 60, max: 10 },
    })
  4. Delete the local copies only once each swap is green.

Why not a big-bang migration

app-a has no test suite I have verified. Every change above is individually recoverable; combined, a regression has too many candidate causes.