Migrating an existing UI
Relocating an independent feature
Once a feature boundary holds in place, move the slice, its contract, demo sandbox, and tests into their own package.
The candidate feature now speaks through a local port instead of reaching directly into backend clients. In place, the boundary is holding; now the code needs a structure that matches it.
Unplug from the host
Once the port exists, the feature is ready to be unplugged. The view, its state management, and its contract move out of the host tree and into a dedicated package or workspace directory.
The host application doesn't disappear; its job simply shrinks. Instead of holding data fetching, permissions, and layout in one bloated container, the host becomes a thin composer. It plugs the production adapter into the feature's port, supplies any session context, and mounts the view. The host provides the runtime environment; the feature owns its UI and the contract behind it.
A new home for UI, API, and demo
In its new location, the feature organizes around three distinct responsibilities:
feature/
api/ The port interface and UI view models
ui/ Presentational views, producers, and layout
demo/ A standalone sandbox running against mock data
The port and its display models live in api, defining the feature's outward contract. The live adapter satisfies that contract from the outside, but the real unlock happens inside demo.
Now that the feature is unplugged from the host, we can stand up a self-contained demo harness. The demo feeds mock scenarios through the port, letting developers, designers, and automated tools exercise every state—loading states, empty lists, and error banners—all without needing an authenticated session, a VPN, or a live backend.
Don't leave tests behind
When code moves, its test suite should move with it. Component tests, interaction checks, and state transitions belong in the feature package right alongside ui and api. If tests stay buried in the host app, modifying the feature still means waiting on the full application test runner.
The boundaries map cleanly across test layers:
- Feature tests carry the bulk of the suite. They exercise view models, rendering, and interaction logic directly against mock data and the local port. They move into the feature package and run in fast isolation.
- Adapter tests stay with the live API client, verifying that server payloads transform accurately into the contract models.
- Application journeys (auth gates, navigation, cross-feature handoffs) remain in the composer app. We keep these to a handful of smoke checks: verify the host mounts the view and passes credentials, leaving exhaustive UI states to the feature's local tests.
The move is complete. The feature no longer lives as entangled code inside a host screen, but as a bounded module with its own contract, sandbox, and verification.
Those are the exact conditions that make a feature resilient for humans—and legible to an AI agent without handing it the entire application.