@ubean/vue 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Soybean
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,493 @@
1
+ # @ubean/vue
2
+
3
+ **English | [中文](./README.zh-CN.md)**
4
+
5
+ Lean Vue client kernel & page-routing owner for ubean.
6
+
7
+ `@ubean/vue` owns everything about **page routing** in the ubean ecosystem: file-based route scanning, virtual-module generation, physical route file generation, page cache (keep-alive), page transitions, reload signal, and the `definePage` macro. It ships as a Vue **plugin** (no app factory, no router factory) and depends on **`vue` + `vue-router` only** at runtime.
8
+
9
+ ```ts
10
+ import { createApp } from 'vue';
11
+ import { createRouter, createWebHistory } from 'vue-router';
12
+ import { ubeanVue } from '@ubean/vue';
13
+ import { routes } from 'virtual:ubean-vue-routes'; // generated by @ubean/vue/vite
14
+
15
+ const router = createRouter({ history: createWebHistory(), routes });
16
+ const app = createApp(App);
17
+ app.use(router);
18
+ app.use(ubeanVue, { routes }); // registers Link/PageView/SlotView + seeds page cache
19
+ app.mount('#app');
20
+ ```
21
+
22
+ ## Table of Contents
23
+
24
+ - [Package Positioning](#package-positioning)
25
+ - [Install](#install)
26
+ - [Quick Start](#quick-start)
27
+ - [File-Based Routing](#file-based-routing)
28
+ - [Conventions](#conventions)
29
+ - [`definePage` Macro](#definepage-macro)
30
+ - [Reuse Routes](#reuse-routes)
31
+ - [Special Pages](#special-pages)
32
+ - [Parallel & Intercepting Routes](#parallel--intercepting-routes)
33
+ - [Dynamic Param Matchers](#dynamic-param-matchers)
34
+ - [Markdown Pages (opt-in)](#markdown-pages-opt-in)
35
+ - [Route Output Modes](#route-output-modes)
36
+ - [Vite Plugin API](#vite-plugin-api)
37
+ - [Physical Route File Generator](#physical-route-file-generator)
38
+ - [Virtual Module Surface](#virtual-module-surface)
39
+ - [Vue Plugin](#vue-plugin)
40
+ - [Components](#components)
41
+ - [Composables](#composables)
42
+ - [Page Cache (keep-alive)](#page-cache-keep-alive)
43
+ - [Page Transitions & Reload](#page-transitions--reload)
44
+ - [View Transitions Helpers](#view-transitions-helpers)
45
+ - [Route Matchers API](#route-matchers-api)
46
+ - [Page-Level Head (opt-in)](#page-level-head-opt-in)
47
+ - [Route Pure Functions](#route-pure-functions)
48
+ - [Type Safety](#type-safety)
49
+ - [Architecture Notes](#architecture-notes)
50
+
51
+ ## Package Positioning
52
+
53
+ `@ubean/vue` is the **lean client kernel** and the **single owner of page-routing logic** in ubean:
54
+
55
+ | Layer | Responsibility |
56
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
57
+ | `@ubean/vue` (this package) | Page routing: scanning, virtual modules, physical route file generator, matchers, head guard, page cache, transitions, reload — pure client-side |
58
+ | `@ubean/scan` (aggregator) | Depends on `@ubean/vue`; layers server routing (rou3), API route scanning, SSR page handling on top |
59
+ | `@ubean/client` (framework runtime) | App factories (`createUbeanClientApp` / `createUbeanSSRApp`), unhead/SEO, i18n, islands, data layer |
60
+
61
+ **Included:**
62
+
63
+ - `ubeanVue` Vue plugin (the only wiring entry — plugin-first, no factory)
64
+ - `<PageView>` / `<Link>` / `<SlotView>` / `<LayoutChainRenderer>` / `<ErrorBoundary>`
65
+ - Page cache (keep-alive) store with imperative + declarative APIs
66
+ - Page transitions & reload signal (per-page remount with fresh cache)
67
+ - View Transitions helpers (feature detection + wrapper)
68
+ - `definePage` client macro (compile-time extracted, runtime no-op)
69
+ - Dynamic route matchers (`[param=matcher]` syntax + registry + guard)
70
+ - Page-level head (`setupPageHeadGuard` / lazy `createPageHead`)
71
+ - File-routing Vite plugin (`/vite` subpath): multi-dir scan, reuse routes, special pages, parallel/intercepting routes, markdown & head opt-in
72
+ - Physical route file generator (`/generator` subpath): `routes.ts` + `imports.ts` + `typed-router.d.ts` (file mode)
73
+
74
+ **NOT included** (framework runtime `@ubean/client` concerns):
75
+
76
+ - App factories (`createUbeanClientApp` / `createUbeanSSRApp`)
77
+ - unhead / `<Head>` / SEO (only the opt-in head _guard_ lives here)
78
+ - i18n (`Link` renders paths verbatim unless a localizer is provided via `LOCALIZE_PATH_KEY`)
79
+ - Islands (`v-client` directive), SSR state helpers, auto-import presets
80
+
81
+ **Runtime dependency whitelist (main entry): `vue` + `vue-router` only.** No `@ubean/*`, no `node:*`, no `@unhead/vue` static imports.
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ pnpm add @ubean/vue vue vue-router
87
+ # build-time (file routing):
88
+ pnpm add -D vite @ubean/vue # the /vite & /generator subpaths are part of this package
89
+ # optional peers (loaded lazily, only when enabled):
90
+ pnpm add @ubean/markdown # markdown pages
91
+ pnpm add @unhead/vue # page-level head (SPA)
92
+ ```
93
+
94
+ ## Quick Start
95
+
96
+ **1. Vite config** — the plugin scans your pages and generates `virtual:ubean-vue-routes`:
97
+
98
+ ```ts
99
+ // vite.config.ts
100
+ import vue from '@vitejs/plugin-vue';
101
+ import { ubeanVueVite } from '@ubean/vue/vite';
102
+
103
+ export default {
104
+ plugins: [vue(), ubeanVueVite()]
105
+ };
106
+ ```
107
+
108
+ **2. Bootstrap** — create the router yourself, then install the plugin:
109
+
110
+ ```ts
111
+ // src/main.ts
112
+ import { createApp } from 'vue';
113
+ import { createRouter, createWebHistory } from 'vue-router';
114
+ import { ubeanVue } from '@ubean/vue';
115
+ import { routes } from 'virtual:ubean-vue-routes';
116
+ import App from './App.vue';
117
+
118
+ const router = createRouter({ history: createWebHistory(), routes });
119
+ const app = createApp(App);
120
+ app.use(router);
121
+ app.use(ubeanVue, { routes });
122
+ app.mount('#app');
123
+ ```
124
+
125
+ **3. App shell** — `<PageView />` is the page outlet (keep-alive / transition / reload all take effect there):
126
+
127
+ ```vue
128
+ <!-- src/App.vue -->
129
+ <template>
130
+ <nav>
131
+ <Link to="/">Home</Link>
132
+ <Link to="/about">About</Link>
133
+ </nav>
134
+ <main><PageView /></main>
135
+ </template>
136
+ ```
137
+
138
+ A complete runnable example lives at [`examples/client-only-spa`](../../examples/client-only-spa).
139
+
140
+ ## File-Based Routing
141
+
142
+ ### Conventions
143
+
144
+ Default page extensions are `vue` / `tsx` / `jsx` (override with the `extensions` option). `.ts` is **not** a page extension — script pages must use `.tsx` / `.jsx` or `.vue`. Layouts use `vue` / `ts`.
145
+
146
+ ```
147
+ src/pages/
148
+ ├── index.vue → / Home
149
+ ├── about.vue → /about About
150
+ ├── users/index.vue → /users Users
151
+ ├── users/[id].vue → /users/:id UsersId
152
+ ├── users/[id=numeric].vue → /users/:id + matcher (see matchers)
153
+ ├── blog/[...slug].vue → /blog/**:slug BlogAllSlug (catch-all)
154
+ ├── docs/[[page]].vue → /docs/:page? DocsPageOptional
155
+ ├── (marketing)/about.vue → /about About (group stripped)
156
+ ├── settings.tsx → /settings Settings (JSX page)
157
+ ├── 404.vue → catch-all + NotFound (special page)
158
+ └── guide.md → /guide Guide (markdown, opt-in)
159
+ ```
160
+
161
+ Multiple `pagesDir` / `layoutsDir` are supported (first-seen-wins dedup, so folders can be layered).
162
+
163
+ - Route names are derived from paths in PascalCase (`users/[id]` → `UsersId`).
164
+ - `definePage({ name, path })` can override both.
165
+ - Route groups `(name)` never contribute to the URL or the route name.
166
+
167
+ ### `definePage` Macro
168
+
169
+ Declare page metadata at the top of `<script setup>` (or top-level in `.tsx`/`.jsx`/`.reuse.ts`):
170
+
171
+ ```vue
172
+ <script setup lang="ts">
173
+ import { definePage } from '@ubean/vue';
174
+
175
+ definePage({
176
+ name: 'UserProfile', // override the file-derived route name
177
+ path: '/u/:id', // override the file-derived path
178
+ cache: true, // keep-alive page cache
179
+ transition: 'fade', // page-level transition name
180
+ layout: 'admin', // layout marker: name, array (outer→inner), or false
181
+ requiresAuth: true, // auth marker → route.meta.requiresAuth
182
+ head: { title: 'Profile' }, // page head (requires plugin `head: true`)
183
+ meta: { custom: 'any' } // arbitrary RouteMeta extensions
184
+ });
185
+ </script>
186
+ ```
187
+
188
+ Dual semantics:
189
+
190
+ - **Build time (recommended):** the `/vite` plugin extracts the argument object, merges it into the generated route table, and strips the call from the output (zero runtime cost).
191
+ - **Runtime fallback:** without the plugin the function is a no-op — no errors, no side effects; pages register with file-derived paths/names. `cache` can be restored at runtime via `enablePageCache(name)`.
192
+
193
+ > Use `meta: { middleware: [...] }` and consume it in your own navigation guard.
194
+
195
+ ### Reuse Routes
196
+
197
+ `.reuse.ts` / `.reuse.js` are **pure metadata files** (they only contain `definePage({ reuse })`) that register a new route reusing another page's component:
198
+
199
+ ```ts
200
+ // src/pages/about2.reuse.ts
201
+ definePage({ reuse: 'About' });
202
+ // → route /about2 renders the About page component
203
+ ```
204
+
205
+ - `.reuse.vue` is **NOT** part of the convention — a `.vue` file is a real component and is treated as a regular page (route `/xxx.reuse`).
206
+ - Reuse routes that don't declare `cache` explicitly **inherit** the target's cache setting; declare `cache: false` to opt out.
207
+ - The target must be a regular (non-reuse) page.
208
+
209
+ ### Special Pages
210
+
211
+ Root-level files in a pages directory are wired to framework slots:
212
+
213
+ | File | Role |
214
+ | ----------------------------- | ---------------------------------------------------------------- |
215
+ | `404.vue` (or `.tsx` / `.md`) | Vue Router catch-all `/:pathMatch(.*)*` named `NotFound` |
216
+ | `loading.vue` | `<Suspense>` fallback while async pages load (via `LOADING_KEY`) |
217
+ | `error.vue` | `<ErrorBoundary>` error component (via `ERROR_KEY`) |
218
+
219
+ Only root-level files are special — `users/404.vue` remains a regular route at `/users/404`.
220
+
221
+ ### Parallel & Intercepting Routes
222
+
223
+ - **Parallel routes:** `@slotName/` directories register named views; render them with `<SlotView name="slotName" />`.
224
+ - **Intercepting routes:** `(..)target/`, `(.)target/`, `(...)target/` (one level up / same level / root), Next.js-style.
225
+
226
+ ```
227
+ src/pages/dashboard/@analytics/index.vue → parallel slot "analytics"
228
+ src/pages/photos/(..)photo/[id].vue → intercept /photo/:id from /photos/*
229
+ ```
230
+
231
+ ### Dynamic Param Matchers
232
+
233
+ `[param=matcherName]` file syntax attaches a validation matcher to a dynamic segment:
234
+
235
+ ```
236
+ src/pages/users/[id=numeric].vue → /users/:id + meta.matchers = { id: 'numeric' }
237
+ ```
238
+
239
+ Register matchers with `defineMatcher` (see [Route Matchers API](#route-matchers-api)) and enforce them with `createMatcherGuard`. Failed validation redirects to the `NotFound` route.
240
+
241
+ ### Markdown Pages (opt-in)
242
+
243
+ Enable with `markdown: true` (`md` + `mdx`), `'md'`, or `'mdx'`. Requires `@ubean/markdown` (optional peer, loaded lazily only when enabled):
244
+
245
+ ```ts
246
+ ubeanVueVite({ markdown: true });
247
+ ```
248
+
249
+ Frontmatter maps onto page metadata (`name` / `path` / `layout` / `cache` / `head`):
250
+
251
+ ```md
252
+ ---
253
+ name: Guide
254
+ layout: docs
255
+ cache: true
256
+ head:
257
+ title: Guide
258
+ ---
259
+
260
+ # Guide content
261
+ ```
262
+
263
+ ## Route Output Modes
264
+
265
+ Both modes are owned by `@ubean/vue`:
266
+
267
+ | Mode | Output | Consumers |
268
+ | --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------ |
269
+ | **Virtual** (default) | `virtual:ubean-vue-routes` in-memory module + `typed-router.d.ts` | Lean SPAs; framework's virtual mode |
270
+ | **Physical files** | `routes.ts` + `imports.ts` + `typed-router.d.ts` written to disk | File mode — editable `meta`, `import.meta.glob`-free imports |
271
+
272
+ The framework wires them via `routing.mode` in `ubean.config.ts` (`'virtual'` | `'file'` | `'both'`, see `@ubean/builder`); lean SPAs use the `/vite` plugin directly and may call the `/generator` subpath manually.
273
+
274
+ ## Vite Plugin API
275
+
276
+ ```ts
277
+ import { ubeanVueVite } from '@ubean/vue/vite';
278
+
279
+ ubeanVueVite({
280
+ pagesDir: 'src/pages', // string | string[] (relative to project root or absolute)
281
+ layoutsDir: 'src/layouts', // string | string[]
282
+ extensions: undefined, // default ['vue', 'tsx', 'jsx']; markdown exts appended
283
+ ignore: [], // extra glob ignore patterns
284
+ generateTypes: true, // write typed-router.d.ts
285
+ dtsDir: '.ubean', // dts output dir (relative to root or absolute)
286
+ markdown: false, // true | 'md' | 'mdx'
287
+ head: false // enable page-level head extraction
288
+ });
289
+ ```
290
+
291
+ The plugin also exposes standalone helpers: `scanPages`, `scanClientPages`, `extractSlotAndIntercept`, `generatePagesModuleSource`, `generateTypedRouter`, `stripDefinePageCalls`, `filePathToRoute`, `parseMatchers`, `stripRouteGroups`, `generateRouteName`, `generateLayoutName`, `extractDefinePage`, `extractDefinePageFromCode`, `extractCallObject`, `normalizePageHead`.
292
+
293
+ ## Physical Route File Generator
294
+
295
+ The `/generator` subpath generates editable route files on disk (`@ubean/vue/generator`):
296
+
297
+ ```ts
298
+ import { generateRouteFiles } from '@ubean/vue/generator';
299
+
300
+ const result = await generateRouteFiles(
301
+ { pages, layouts }, // GeneratorScanInput — subset of ScanPagesResult
302
+ {
303
+ cwd: process.cwd(),
304
+ srcDir: 'src', // base for the default @/ import paths
305
+ outDir: 'src/router/_generated',
306
+ // dtsPath: '…', // default: <outDir>/typed-router.d.ts
307
+ // generateRoutes / generateImports / generateDts: true,
308
+ routeLazy: true, // or (page) => boolean
309
+ layoutLazy: true,
310
+ getRouteMeta: page => ({ ... }), // extra meta (definePage meta wins)
311
+ getImportPath: page => `@/pages/${page.name}.vue`,
312
+ }
313
+ );
314
+ // → { routesPath, importsPath, dtsPath, routeCount, layoutCount }
315
+ ```
316
+
317
+ Produces 3 files under `outDir`:
318
+
319
+ - `routes.ts` — flat `RouteRecord[]` (`name` / `path` / `component`(key into `views`) / `layout` / `meta` / `cache` / `requiresAuth`; reuse routes point `component` at their target)
320
+ - `imports.ts` — `views` / `layouts` lazy-loader maps + categorized key types (`RouteKey` / `RouteFileKey` / `RouteReuseKey` / `LayoutKey`)
321
+ - `typed-router.d.ts` — `@ubean/scan` module augmentation (`RouteKey` / `RoutePathMap` / `RouteLayoutKey` / `ReuseRouteKey`) + `vue-router/auto-routes` `RouteNamedMap` (typed `useRoute<Name>(name)` params inference)
322
+
323
+ The `RouteFileGenerator` class is also exported for incremental use (`new RouteFileGenerator(options).generate(scan)`).
324
+
325
+ > The dts still augments the `@ubean/scan` module — that is the framework's established type contract (framework code imports `RouteKey` etc. from `@ubean/scan`); the generator's physical location does not change the artifact. Lean SPAs use the virtual-mode `typed-router.d.ts` from the `/vite` plugin instead.
326
+
327
+ ## Virtual Module Surface
328
+
329
+ `virtual:ubean-vue-routes` exports:
330
+
331
+ | Export | Description |
332
+ | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
333
+ | `routes` | Vue Router `RouteRecord[]` (with `meta.cache` / `transition` / `matchers` / `head` etc.) |
334
+ | `pages` / `layouts` | `{ [name]: () => Promise<Component> }` lazy loaders |
335
+ | `pageNames` / `layoutNames` | Name arrays (include `NotFound` when present) |
336
+ | `defaultLayout` | Default layout name or `null` |
337
+ | `resolvePageComponent(name)` / `resolveLayoutComponent(name)` | Component resolvers (reuse routes resolve their target) |
338
+ | `loadingComponent` / `errorComponent` | Special-page components or `null` |
339
+ | `resolveLoadingComponent()` / `resolveErrorComponent()` | Lazy resolvers |
340
+ | `hasNotFoundPage()` / `hasErrorPage()` | Presence checks |
341
+
342
+ ## Vue Plugin
343
+
344
+ ```ts
345
+ app.use(ubeanVue, { routes });
346
+ ```
347
+
348
+ Registers `Link` / `PageView` / `SlotView` as global components and seeds the keep-alive include list from `meta.cache: true` declarations (`initCachedViewsFromRoutes`). This is the **only** wiring entry of the package (main subpath — distinct from the same-named Vite plugin in the `/vite` subpath).
349
+
350
+ ## Components
351
+
352
+ ### `<PageView>`
353
+
354
+ The page outlet. Wraps the matched component in `<Transition>` + `<KeepAlive>` (+ `<Suspense>` / `<ErrorBoundary>` when loading/error components are provided via injection keys).
355
+
356
+ Props:
357
+
358
+ - `transition: string | boolean` — transition name, `false` disables. Priority: prop > `route.meta.transition` > global `usePageTransition()`.
359
+ - `reloadKey: string | number` — reactive key forcing remount; defaults to `route.fullPath` + per-page reload count.
360
+ - `mode: 'default' | 'out-in' | 'in-out'` — defaults to `'default'` (cross-fade). `'out-in'` is degraded to `'default'` with a dev warning due to a vue@3.5.x KeepAlive upstream bug.
361
+
362
+ On SSR (via `SSR_KEY`), KeepAlive/Transition/Suspense/ErrorBoundary are skipped.
363
+
364
+ ### `<Link>`
365
+
366
+ Internal links render via `RouterLink`; external links (`http*`, `//`, `#`) render as native `<a target="_blank" rel="noopener noreferrer">`.
367
+
368
+ Props: `to` (string or location object), `href`, `replace`, `activeClass`, `exactActiveClass`, `noActiveClass`. Path localization is opt-in via `LOCALIZE_PATH_KEY` (the framework runtime provides it; lean SPAs render paths verbatim).
369
+
370
+ ### `<SlotView>`
371
+
372
+ `<SlotView name="modal" />` renders the matched component for a parallel-route slot (nothing when no slot matches).
373
+
374
+ ### `<LayoutChainRenderer>`
375
+
376
+ Recursively renders a chain of nested layout components (framework factories build the chain; lean SPAs can use it directly via render functions).
377
+
378
+ ### `<ErrorBoundary>`
379
+
380
+ Catches render/async/setup errors from descendants and renders the configured error component; resets on route change.
381
+
382
+ ## Composables
383
+
384
+ - **`usePage<T>()`** — lean page-data access: returns the `PAGE_KEY` injected data (`props` / `component` / `errors`) as-is, or a shared empty object when nothing was provided. Route state (`url` / `params` / `query` / `meta`) is NOT included — use vue-router's `useRoute()` directly. The framework runtime (`@ubean/client`) layers its own route-aware `usePage` on top for the full PageObject protocol.
385
+ - **`useRouter()`** — not shipped: import vue-router's `useRouter()` directly so `push` / `replace` keep their `RouteNamedMap` typed overloads. (`@ubean/client` re-exports it purely for auto-import convenience.)
386
+ - **`useCacheViews()`** — see [Page Cache](#page-cache-keep-alive).
387
+ - **`usePageTransition()`** — `{ name, set, clear, enabled }` global transition name.
388
+ - **`useReloadSignal()`** — `{ counter, reloading, reload(routeName?, duration?) }`.
389
+ - **`useViewTransition()`** — `{ enabled, supports, options }` for native View Transitions.
390
+
391
+ ## Page Cache (keep-alive)
392
+
393
+ Declarative:
394
+
395
+ ```ts
396
+ definePage({ cache: true }); // or route meta: { cache: true }
397
+ ```
398
+
399
+ Imperative:
400
+
401
+ ```ts
402
+ import {
403
+ enablePageCache, // enablePageCache('DashboardIndex')
404
+ disablePageCache, // remove from include list (prunes the instance)
405
+ invalidatePageCache, // clear a page's (or all) cached instance(s)
406
+ isPageCached,
407
+ excludePageCache, // temporarily exclude (forces prune on next render)
408
+ includePageCache,
409
+ useCacheViews, // reactive store: cachedViews / excludedViews / enable / disable / ...
410
+ resetRouteCache
411
+ } from '@ubean/vue';
412
+ ```
413
+
414
+ Caching is keyed by the **route name** (`route.meta.pageName`), which the kernel injects as the component `name` at render time so `<keep-alive :include>` matches `<script setup>` SFCs.
415
+
416
+ ## Page Transitions & Reload
417
+
418
+ ```ts
419
+ import { setPageTransition, useReloadSignal } from '@ubean/vue';
420
+
421
+ setPageTransition('fade-slide'); // global default; '' / 'none' disables
422
+ // per-page: definePage({ transition: 'fade' }) or route meta
423
+
424
+ const { reload } = useReloadSignal();
425
+ await reload('DashboardIndex'); // blank → prune stale cache → fresh remount
426
+ ```
427
+
428
+ `reloadPage(name)` only bumps **that page's** key — other pages' keep-alive entries survive an unrelated reload.
429
+
430
+ ## View Transitions Helpers
431
+
432
+ ```ts
433
+ import { supportsViewTransitions, withViewTransition, useViewTransitionState, getNavigationType } from '@ubean/vue';
434
+
435
+ if (supportsViewTransitions()) {
436
+ await withViewTransition(
437
+ async () => {
438
+ /* mutate DOM/state */
439
+ },
440
+ { types: ['slide-left'] }
441
+ );
442
+ }
443
+ ```
444
+
445
+ ## Route Matchers API
446
+
447
+ ```ts
448
+ import { defineMatcher, createMatcherGuard } from '@ubean/vue';
449
+
450
+ // src/matchers/numeric.ts
451
+ export default defineMatcher('numeric', value => /^\d+$/.test(value));
452
+
453
+ // router setup — matcher failure redirects to NotFound
454
+ router.beforeEach(createMatcherGuard(/* { notFoundRouteName?, onReject? } */));
455
+ ```
456
+
457
+ Registry helpers: `getMatcher(name)`, `hasMatcher(name)`, `listMatcherNames()`, `clearMatchers()` (tests only), `validateParams(matchers, params)`.
458
+
459
+ ## Page-Level Head (opt-in)
460
+
461
+ Enable extraction with `head: true` on the plugin, then:
462
+
463
+ ```ts
464
+ import { createPageHead, setupPageHeadGuard } from '@ubean/vue';
465
+
466
+ const head = await createPageHead(); // lazily loads @unhead/vue (optional peer)
467
+ setupPageHeadGuard(router, head); // pushes route.meta.head after each navigation
468
+ ```
469
+
470
+ Already have a unhead instance? Skip `createPageHead()` and pass the instance directly — `PageHeadClient` is a structural type (`{ push }`), so no third-party dependency enters the core.
471
+
472
+ ## Route Pure Functions
473
+
474
+ ```ts
475
+ import { resolveRoute, isActiveRoute } from '@ubean/vue';
476
+
477
+ resolveRoute({ name: 'UsersId', params: { id: 7 }, query: { tab: 'info' } }, routeMap);
478
+ // → '/users/7?tab=info'
479
+ isActiveRoute('/users/7', '/users', false); // → true (prefix match)
480
+ ```
481
+
482
+ ## Type Safety
483
+
484
+ - The package augments `vue-router`'s `RouteMeta` with `cache` / `pageName` / `layout` / `requiresAuth` / `transition` / `head` / `matchers` — projects get full type-safety without their own `declare module` block.
485
+ - With `generateTypes: true` (default), the `/vite` plugin writes `ubean-vue-routes.d.ts` + `typed-router.d.ts` into `dtsDir` (default `.ubean`, same convention as the framework's auto-imports dts); the `/generator` subpath writes its own dts for file mode (see above).
486
+
487
+ ## Architecture Notes
488
+
489
+ - **Plugin-first:** `ubeanVue` (main subpath, Vue plugin for `app.use`) and `ubeanVueVite` (`/vite` subpath, Vite plugin factory) are the two wiring entries — distinctly named, no more overloading. The kernel ships no app factory and no router factory — you create both with stock `vue` / `vue-router` APIs.
490
+ - **Runtime boundary:** the main entry (`@ubean/vue`) must never statically import `node:*`, `@ubean/*`, `@unhead/vue`, `vite`, or `tinyglobby` (enforced by tests). Build-time-only code lives behind the `/vite` and `/generator` subpaths.
491
+ - **Lazy optional peers:** `@ubean/markdown` (markdown pages) and `@unhead/vue` (head) are optional peers, dynamically imported only when their features are enabled.
492
+ - **Global singletons** (cache store, transition name, reload counters) live on `globalThis` so duplicated module instances (dep re-optimization, HMR `?t=` variants) share one state — prevents SSR hydration mismatches and KeepAlive cache misses.
493
+ - **Ownership:** `@ubean/scan` (aggregator) depends on `@ubean/vue` — never the reverse. The aggregator re-exports page-routing types, functions, and the generator.