# Architecture ## Ownership boundary Vue Router owns matching, lazy components, committed routes, redirects, guards, URL serialization, and browser history. Native Vue Router owns a separate visual ledger containing mounted route entries, scroll/focus state, cache status, and the current interactive transaction. A forward drag calls `router.resolve()` and Vue Router's public `loadRouteLocation()`, then renders the location through `` without calling `push()`. The URL and application history remain unchanged until the gesture commits. Each preview subtree receives a scoped `routeLocationKey`, so `useRoute()` returns preview params even though the global route is not committed. A normal `router.push()` or `replace()` runs only after the gesture chooses to commit. A guard failure cancels the transaction and removes the preview. Preview routes can contain ordinary Vue `` boundaries. A route may therefore become a live navigation surface immediately, show its fallback while async child setup continues, and preserve the resolved child when the route later moves into the mounted cache. Route guards remain commit-time authority and can still reject that cached destination on a later entry attempt. ## Transaction lifecycle Transactions move through `interactive`, `committing`, `settling`, and cancellation states. They expose normalized progress and velocity plus `fromKey`, `toKey`, direction, presentation, and optional source geometry. The runtime deliberately keeps two ledgers. The navigation stack mirrors committed push/replace/pop semantics and is the only source of predictive-back targets. The view cache owns mounted component instances independently, so a replaced tab can be reused without becoming an accidental back destination. Pointer movement changes only a CSS progress variable. Built-in presentations restrict active-frame work to compositor-friendly properties. When a destination needs a new component tree, the runtime resolves its lazy route, mounts it, and gives the browser a preparation frame before motion starts; cached destinations skip that wait. Velocity is expressed as normalized route progress per second, so gesture behavior remains consistent across screen sizes. Release uses distance/velocity intent and a damped spring whose settling rate follows the user's flick speed. Reduced-motion mode settles immediately. Settling animations are interruptible. A new button navigation or recognized gesture waits only for any in-flight Vue Router guard/history commit, immediately finalizes the old visual transaction, and begins from the newly authoritative route. It never waits for the previous spring to finish. Leading-edge back recognition runs in the navigator capture phase so partially visible component layers cannot steal the physical back edge. Component gesture owners stop propagation before a containing navigator can claim the same pointer. Navigator gestures wait for horizontal intent, use pointer capture, respect form controls and `data-native-gesture="ignore"`, and preserve normal vertical scrolling through `touch-action: pan-y`. ## Route metadata ```ts interface NativeRouteOptions { navigator?: string presentation?: 'push' | 'reveal' | 'slide' | 'fade' | 'modal' | 'sheet' | string parent?: RouteLocationRaw | ((route) => RouteLocationRaw) siblingGroup?: string siblingOrder?: number siblingHistory?: 'push' | 'replace' cache?: boolean | 'pin' gesture?: boolean | 'edge' | 'full' } ``` `parent` supplies a predictive back target when a deep link starts without an in-memory predecessor. Sibling routes replace history by default and use the built-in `slide` presentation, which moves both pages one-to-one as adjacent surfaces. Direction comes from `siblingOrder`. Set `siblingHistory: 'push'` when browser back should visit prior sibling selections. ## Custom presentations ```ts nativeRouter.registerPresentation(definePresentation({ name: 'scale-fade', axis: 'x', layerStyle({ role, progress }) { return role === 'to' ? { opacity: progress, transform: `scale(${0.92 + progress * 0.08})` } : { opacity: 1 - progress * 0.4 } }, })) ``` Applications can call `beginInteractive()`, `updateInteractive()`, and `finishInteractive()` to drive the same transaction engine from a bespoke recognizer. ## Cache semantics The cache is lazy: application startup mounts the current route, not every sibling. A replace-style sibling is created when it is first visited or previewed and can then remain mounted without becoming a browser-back entry. Recent history targets can also stay warm so predictive Back restores component-local state such as a scrolled list immediately. The default limit is four inactive views per runtime. `cache: false` always unmounts an inactive route, while `cache: 'pin'` exempts it from ordinary LRU and default bulk trimming. A pushed detail route that is popped or dismissed is unmounted after its exit animation unless it is explicitly pinned. If a guard rejects a cached destination, that component tree is evicted because it is no longer a valid navigation target. Older entries keep lightweight route descriptors and are lazily reconstructed if history reaches them again. Applications can explicitly release an inactive location with `nativeRouter.unload('/stories')`. It returns the number of component instances unmounted, retains history descriptors, and refuses to unload the active route or either side of an in-progress transition. `trimCache()` remains the bulk operation. This is deliberately not implemented with a single Vue ``. An interactive transition must render the current and destination route instances concurrently, while one `` outlet normally activates one selected child. Separate temporary wrappers would themselves be removed and lose their caches. The runtime therefore owns the small multi-view cache and exposes equivalent route-aware lifecycle signals: ```ts import { onNativeViewActivate, onNativeViewDeactivate, onNativeViewEvict, useNativeViewActiveEffect, useNativeViewLifecycle, } from '@native-vue-router/core' const view = useNativeViewLifecycle() useNativeViewActiveEffect(() => { const timer = startPolling() return () => stopPolling(timer) }) onNativeViewEvict((reason) => saveDraft(view.route.value, reason)) ``` `isActive` means the route is authoritative. `isVisible` also includes either side of an in-progress transition. Use `useNativeViewActiveEffect` for polling and other work that should pause in a cached tab, or `useNativeViewVisibleEffect` for work needed during the animation. Application data that must survive eviction belongs in an application store.