diff --git a/README.md b/README.md index 268e5637..b4294967 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![bundle size](https://img.shields.io/bundlephobia/minzip/@auraui/router)](https://bundlephobia.com/package/@auraui/router) [![license](https://img.shields.io/npm/l/@auraui/router.svg)](./LICENSE) -**Declarative router for Web Components** — routes, nested layouts, and lifecycle in HTML. HTML-first: real pages on first visit, client navigation after (MPA→SPA). +**HTML-first declarative router for Web Components with nested outlets and route lifecycle.** First visit renders a real HTML page; after `AuraRouter.install()`, in-app navigation updates content without a full reload (MPA→SPA). ```bash npm install @auraui/router diff --git a/ROADMAP.md b/ROADMAP.md index 57603732..9d48ef57 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,7 +5,7 @@ | | | | --- | --- | | **Version** | `0.0.1` | -| **Updated** | 2026-07-29 | +| **Updated** | 2026-08-04 | | **Audience** | Library users, contributors, and reviewers | | **Public docs** | [README](./README.md) · [Guide](./docs/guide.md) · [LIMITATIONS](./LIMITATIONS.md) · [SECURITY](./SECURITY.md) · [CHANGELOG](./CHANGELOG.md) | @@ -46,6 +46,8 @@ | :---: | --- | --- | | | **8.1 / 8.4** MPA adoption path | migration guide + pure static `.html` per URL (playground is Fastify, not static-only) | | | **7.x** Adoption artifacts | document recipes from [`playground/`](./playground/), Playwright E2E, hosted playground | +| · | **5.5 / 5.6** Basename + typed params | subfolder mount + `:id(int)`-style path types | +| · | **4.2** Navigation API transport | preferred `window.navigation` + History API fallback | | · | Pre-release **0.0.1** | merge → `main`, npm publish | | · | **6.x** Debugging & performance | Cache DevTools, nav timeline UI, per-nav metrics gate | @@ -87,8 +89,8 @@ Today’s sole package entry (`@auraui/router`) ships the **full** surface (load | **1** | [Routing engine](#shipped) | | Shipped — see summary below | | **2** | [Data & cache](#phase-2--data-loading--cache) | ~ | Cache ladder / invalidate / entry timings ; DataGraph parity + out-in prefetch open | | **3** | [View rendering](#phase-3--view-rendering) | ~ | Renderer API, incremental DOM | -| **4** | [Navigation UX](#phase-4--navigation-experience) | ~ | Loading chrome ; View Transitions API | -| **5** | [Developer API](#phase-5--developer-facing-api) | ~ | Route API + named hooks ; folders ~; optional `/min` later | +| **4** | [Navigation UX](#phase-4--navigation-experience) | ~ | Loading chrome ; View Transitions / Navigation API transport | +| **5** | [Developer API](#phase-5--developer-facing-api) | ~ | Route API + named hooks ; folders ~; basename / typed params / optional `/min` | | **6** | [DevTools](#phase-6--debugging--performance-tooling) | ~ | Event stream + smoke/size CI ; DevTools UI / per-nav gate open | | **7** | [Examples & docs](#phase-7--examples--docs) | ~ | [`playground/`](./playground/) covers nested / auth / cache / 404 ; dedicated recipes / E2E / hosted still open | | **8** | [MPA → SPA](#phase-8--mpa--spa) | ~ | Hydrate + shared layout inject ; migration guide / pure static example open | @@ -152,6 +154,7 @@ Orchestration: `NavigationCoordinator` → `NavigationTransaction` → `Navigati | ---: | --- | :---: | --- | | 4.0 | **Loading chrome** — skeleton / body class / events while prepare runs | | `loading-template` skipped when page transitions are defined; listed under [Shipped](#shipped) | | 4.1 | **View Transitions API** — optional cross-fade / slide via `document.startViewTransition` | | CSS / WAAPI transitions via attrs work in demo; browser VT API not wired in engine | +| 4.2 | **Navigation API transport** — prefer `window.navigation` (`navigate` + `intercept`); keep History API fallback | | Today: History API only. Dual provider; drop fallback only after wide Baseline (~2028+) + metrics. caniuse ~87% (June 2026) | --- @@ -166,6 +169,8 @@ Orchestration: `NavigationCoordinator` → `NavigationTransaction` → `Navigati | 5.2a | **Named hook registration** — `AuraRouter.use(name, fn)` / `defineRouteHook(name, fn)` | | Listed under [Shipped](#shipped) | | 5.3 | **Nested route folders** — file-system-style nested routes and layouts | ~ | Engine nested path + demo layouts ; colocated folder templates still open | | 5.4 | **Default vs `/min` entry** — one package, optional slim import | | **Default stays full:** `@auraui/router` always ships the full surface. **Later (optional):** `@auraui/router/min` — minimal working core, with plugins to opt into loaders / DataGraph / other pieces as needed | +| 5.5 | **Basename / subfolder mode** — mount app under `/app`, GitHub Pages, multi-SPA on one host | | Attr/config on ``; strip/join for match + links + `navigate` | +| 5.6 | **Typed path params** — e.g. `:userId(int)`, `:slug(slug)` in `path` | | Inline in `path` (not a separate `params` attr). Small builtin set; fail match on type mismatch. Avoid `:id<int>` in HTML attrs | --- diff --git a/docs/guide.md b/docs/guide.md index 3b0a718f..9f13928d 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -10,6 +10,7 @@ Detailed usage for [`@auraui/router`](https://www.npmjs.com/package/@auraui/rout - [Recipes](./recipes/README.md) — short copy-paste patterns (auth, nested, cache, 404, first paint) - [Navigation](#navigation) +- [Route match priority](#route-match-priority) - [Views](#views) - [Nested routes & layouts](#nested-routes--layouts) - [Lifecycle hooks](#lifecycle-hooks) @@ -60,6 +61,45 @@ Aura **joins** nested `path` values into one pattern (`/app` + `settings` → `/ ``` +### Route match priority + +When several patterns could match the same URL, Aura picks **one** leaf among `matchableNodes` (joined nested patterns). Matching uses the pathname with a trailing `/` stripped (`/users/` → `/users`); the address bar still keeps the link’s form. + +**Score (`matchScore`) — higher wins:** + +| Pattern kind | Example | Score rule | +| --- | --- | --- | +| Static or `:param` | `/users/about`, `/users/:id` | Number of path segments (`/a/b` → `2`) | +| Scoped catch-all | `/users/*` | Parent segment count − `0.5` (`/users/*` → `0.5`) | +| Global catch-all | `*` | `-1` (always last resort) | + +**How the winner is chosen:** + +1. Look up an **exact static** pattern (`pathname === pattern`, no `:param`, no `*`). That hit is the baseline. +2. Probe dynamic candidates (`:param`, scoped `/*`, global `*`). A dynamic route wins only if it matches **and** its `matchScore` is **strictly greater** than the current best. +3. On a **tie**, the current winner stays — so a static route beats a param at the same depth (`/users/about` wins over `/users/:id` for `/users/about`). +4. Among dynamics with the same score, the first matchable candidate that set the best score wins (tree build / declaration order). + +```html + + + + + + +``` + +| URL | Wins | Why | +| --- | --- | --- | +| `/users/about` | `/users/about` | Static baseline; param has the same score → tie keeps static | +| `/users/42` | `/users/:id` | Param matches; score `2` > scoped `/*` (`0.5`) | +| `/users/x/y` | `/users/*` | Param is one segment; scoped catch-all takes the rest (`splat`) | +| `/other` | `*` | Only global catch-all matches | + +`:param` routes need `URLPattern`. Catch-all params use `params.splat` (scoped `/users/*` needs a non-empty tail — `/users/` alone does not match `/users/*`). + +Duplicate **identical** static patterns: the later node in the tree index overwrites the earlier one in the exact map. + --- ## Views diff --git a/src/modules/aura-routing-engine/core/hooks/types.ts b/src/modules/aura-routing-engine/core/hooks/types.ts index f1e9a54c..a2c7033d 100644 --- a/src/modules/aura-routing-engine/core/hooks/types.ts +++ b/src/modules/aura-routing-engine/core/hooks/types.ts @@ -35,7 +35,7 @@ export type HookResultInput = HookResult | boolean | RedirectTarget; export type RouteHookFn> = ( ctx: RouteHookContext, -) => Promise; +) => HookResultInput | Promise; /** * Registered route hook — global by name, invoked when a route references it.