Skip to content

Adopting Incrementally

If you already have a frontend and you don’t want to rewrite it, Kitsune can be adopted gradually. Here’s a practical sequence.

The lowest-risk first move. One file changes.

// React
import { KitShellProvider } from '@atheory-ai/kitsune-react'
<KitShellProvider modules={[]}>
<ExistingApp />
</KitShellProvider>

Or for a non-React app:

<kit-shell name="my-app">
<!-- existing markup -->
</kit-shell>
import '@atheory-ai/kitsune-app'
const shell = document.querySelector('kit-shell')!
shell.modules = []

You now have a runtime. It does nothing yet. That’s fine.

Step 2 — Pick one feature; wrap it in a boundary

Section titled “Step 2 — Pick one feature; wrap it in a boundary”

Don’t try to boundary the whole app at once. Pick the feature most in need of cross-cutting capability — analytics, audit, observability — and wrap that.

<KitBoundary surface="checkout" feature="commerce">
<ExistingCheckoutPage />
</KitBoundary>

Existing components don’t have to know they’re inside a boundary. The boundary is a context provider; descendants opt in.

Find a button, a form, a card — anything that fires a meaningful interaction. Add data-meta-event (and data-meta-prop-* for context):

<button
onClick={existingHandler}
data-meta-event="checkout.started"
data-meta-prop-cart-id={cart.id}
>
Pay
</button>

The existing handler still runs. Kitsune just also observes the click and emits an event.

Pick the cross-cutting concern your team most wants to standardize. Often this is analytics. Write a tiny module:

import { defineKitModule } from '@atheory-ai/kitsune-core'
import { posthog } from './existing-posthog-client'
export const analytics = defineKitModule({
name: 'analytics',
events: {
'*': (event) => {
posthog.capture(event.type, {
...event.payload,
surface: event.context?.surface,
feature: event.context?.feature,
})
},
},
})

Install it on the shell. The button you tagged in step 3 is now tracked. No other change.

Step 5 — Migrate one capability out of components

Section titled “Step 5 — Migrate one capability out of components”

Find code that’s repeated across components — the analytics calls, the audit calls, the observability breadcrumbs. Pick one and move it to a module.

This isn’t a rewrite. It’s a migration:

  1. The module subscribes to the same event types your existing components were tracking.
  2. You delete the duplicated tracking call from each component.
  3. Run the app. The module observes; the analytics platform sees the same events.
  4. If something’s wrong, restore the inline call temporarily.

Iterate per feature. The module replaces N inline calls with one observer.

If the tracking-only migration goes well, try replacing one component with its native equivalent.

A typical first candidate: your <Modal> component. Replace it with <kit-dialog>. The trigger button’s onClick becomes meta-command="dialog.open" meta-prop-target="...". The Cancel button becomes meta-command="dialog.close". Your custom focus-trap library deletes itself.

Start small. The bigger the framework component, the riskier the swap. Modals, toasts, tooltips, popovers — these are the easy wins because the platform now does them better.

Once one feature is comfortable, repeat. Add boundaries around routes. Add metadata to interactions. Migrate cross-cutting code into modules. Each step is small. Each step is reversible.

You don’t have to finish.

Incremental adoption works best when:

  • Your codebase has clear cross-cutting concerns (analytics, audit, observability) that are duplicated across components.
  • You have long-lived screens that won’t be rewritten in the next quarter.
  • The team values long-term maintainability over short-term shipping speed.

It works less well when:

  • The app is months from a full rewrite anyway. (Use Kitsune in the rewrite, don’t migrate.)
  • The cross-cutting concerns are already well-isolated (e.g., a single analytics provider that sits at the route level). The architectural payoff is smaller.

When to write new code in Kitsune-style from day one

Section titled “When to write new code in Kitsune-style from day one”

For new features in an existing app:

  • Wrap the feature in a boundary.
  • Use data-meta-* for declarative event/command emission.
  • Put cross-cutting logic in modules from the start.

The new feature is a clean Kitsune surface. The rest of the app continues unchanged. Over time, more features look like the new feature.

  • Don’t rewrite the whole router first. Routing is the hardest piece and the lowest-leverage to migrate. Leave it.
  • Don’t replace your form library wholesale. Wrap forms in boundaries; emit on submit; keep the rest.
  • Don’t try to remove React. You can ship a Kitsune-shaped React app forever. The architectural payoff doesn’t require dropping React.