diff --git a/README.md b/README.md index 5dec5be..1d1dca6 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ Use `nativeRouter.sibling(to)` for tab or peer-route navigation. Direction is de Sibling views are lazy rather than pre-mounted: only the initial route exists on startup, and a sibling joins the bounded cache on its first visit or interactive preview. Route metadata accepts `cache: false` to opt out or `cache: 'pin'` for views that must survive ordinary trimming. `useNativeViewLifecycle()`, the `onNativeView*` hooks, and `useNativeViewActiveEffect()` let cached screens pause polling, media, or subscriptions while retaining their local UI state. +Call `nativeRouter.unload('/some-route')` to manually unmount inactive instances of one location while retaining their lightweight history descriptors. The active route and views participating in a transition are protected. + ## Packages - `@native-vue-router/core` — transactions, route ledger, concurrent views, gestures, caching, and public components/composables. diff --git a/apps/demo/e2e/navigation.spec.ts b/apps/demo/e2e/navigation.spec.ts index 8603964..e927187 100644 --- a/apps/demo/e2e/navigation.spec.ts +++ b/apps/demo/e2e/navigation.spec.ts @@ -197,6 +197,19 @@ test('renders a suspended pushed sibling and evicts it after backing out', async await expect(page.getByTestId('runtime-lab-view')).not.toHaveAttribute('data-mount-id', firstMountId!) }) +test('clicking the root tab from a pushed sibling collapses its back history', async ({ page }) => { + await page.goto('/profile') + await page.getByRole('link', { name: /Runtime stress lab/ }).click() + await expect(page).toHaveURL(/\/profile\/runtime-lab$/) + + await page.getByRole('link', { name: /You/ }).click() + await expect(page).toHaveURL(/\/profile$/) + await waitForTransition(page) + + await expect(page.locator('.nvr-navigator')).toHaveAttribute('data-native-can-go-back', 'false') + await expect(page.getByTestId('runtime-lab-view')).toHaveCount(0) +}) + test('lazily caches a visited sibling and pauses its active work while hidden', async ({ page }) => { await page.goto('/stories') const stories = page.getByTestId('stories-view') @@ -246,6 +259,22 @@ test('evicts a cached sibling when its dynamic entry guard rejects it', async ({ await expect(page.getByTestId('stories-view')).not.toHaveAttribute('data-mount-id', firstMountId!) }) +test('manually unloads an inactive route through the public API demo', async ({ page }) => { + await page.goto('/stories') + await page.getByRole('link', { name: /You/ }).click() + await expect(page).toHaveURL(/\/profile$/) + await waitForTransition(page) + await expect(page.getByTestId('stories-view')).toHaveCount(1) + + await page.getByRole('link', { name: /Navigation lab/ }).click() + await expect(page).toHaveURL(/\/settings$/) + await waitForTransition(page) + await page.getByTestId('unload-stories').click() + + await expect(page.getByTestId('unload-stories')).toHaveText('Unloaded 1 Stories view') + await expect(page.getByTestId('stories-view')).toHaveCount(0) +}) + test('opens and dismisses the compose sheet', async ({ page }) => { await page.goto('/inbox') await page.getByRole('button', { name: 'Compose' }).click() diff --git a/apps/demo/src/style.css b/apps/demo/src/style.css index 3650e96..a8548d9 100644 --- a/apps/demo/src/style.css +++ b/apps/demo/src/style.css @@ -52,6 +52,17 @@ html[data-pwa-edge-guard="active"] body { left: 0; } +/* Blurring content that is itself moving forces WebKit to repeatedly + rasterize the tab/header chrome. Use the same dark material without the + live blur for the short duration of a route transition. */ +.app-frame:has(.nvr-router-view--interactive) :is(.app-tabs, .app-header, .composer) { + backdrop-filter: none; +} + +.app-frame:has(.nvr-router-view--interactive) .app-tabs { + background: rgba(11, 13, 18, .98); +} + .screen { width: 100%; height: 100%; diff --git a/apps/demo/src/views/SettingsView.vue b/apps/demo/src/views/SettingsView.vue index a6adc28..1904416 100644 --- a/apps/demo/src/views/SettingsView.vue +++ b/apps/demo/src/views/SettingsView.vue @@ -1,4 +1,5 @@