This page tracks the two most recent releases. For v5.2.0 (reactive permissions, revalidateCurrentRoute(), defineRoutes(), error-toast removal),
and the full history including v5.1.x and v5.0.x, see CHANGELOG.md.
v5.3.0
Generated navigation from a route tree: a permission-aware helpers/nav-tree module, subtree-aware use:active highlighting, and distinct styling for
product-level "coming soon" placeholders. One route tree can now drive your routes, your
sidebar, and your navbar — with permissions applied — from a single source of truth.
subtree: true on use:active — sidebar parent/child highlighting from one action
Registers both an exact-href match and a /href/* descendants
pattern from a single action call, derived from the link's href. Sidebar parents
stay highlighted on their own index page and every nested URL without writing a regex
or stacking two use:active calls by hand. Pair with the new subtreeClassName to style "parent of an active child" differently from "really
active".
helpers/nav-tree — permission-aware filtering for tree-shaped menus
New module exported at @keenmate/svelte-spa-router/helpers/nav-tree. Walks a route
tree, runs hasPermission() per node, and either drops inaccessible nodes
(mode: 'hide') or marks them with _forbidden: true for styled
disabled rendering (mode: 'disable'). Ancestor permissions enforced via sequential
checks (one per level), not by merging spec objects — mirrors the router's hierarchical-mode
pipeline. Plus a hidden: boolean | (node) => boolean getter that's
always-destructive in both modes — read a $state rune or a feature flag inside
the getter and the whole filter result becomes reactive.
disabled placeholders + distinct FilterOptions.disabledClassName
A disabled: true node is a product-level placeholder signal ("coming soon", "in private beta"), not a user-permission concern — so it renders as forbidden
in both filter modes rather than being dropped like a permission failure (ancestor
permission denial still hides disabled descendants — you can't see a placeholder in a section
you can't enter). The new disabledClassName option then lets you style those
placeholders distinctly from permission-denied items even though both render through the same
forbidden branch. When a node is both disabled AND permission-denied (rare), disabledClassName wins — the product-level signal is the more permanent one.
Cascade parents (forbidden only because every visible child is) keep forbiddenClassName. Default undefined → falls back to forbiddenClassName; fully backward compatible.
Stacked use:active actions cooperate cleanly
Two use:active calls on one element used to fight each other: the last invocation
unconditionally toggled its own result, so use:active use:active={'/foo/*'} silently broke on the bare path (the prefix
action stripped the class the default action just added). Replaced the internal toggleClasses with syncClassesForNode that aggregates across every
entry — an active class is present iff any entry's pattern matches. The subtree option above rides on this aggregation model.
/nav-tree-demo showcase + <NavLink> reference
The example app's tree-driven demo (/nav-tree-demo) walks a permission-filtered
tree to render its sidebar AND its navbar AND its routes — one source of truth drives all three.
Three live toggles in the sidebar — user, hide/disable mode, and a feature-flag rune — let you
see every filter behavior change in place with no page reload. The <NavLink> wrapper around use:link + use:active is a copy-paste reference for handling subtree and _forbidden. The
topbar items also opt into the Floating UI RichTooltip wrapper (via the meta.docsUrl convention) so one richTooltipContent snippet powers both
the horizontal navbar and the vertical sidebar. Covered end-to-end by Playwright tests.
AI-facing docs refreshed
The previous ai/link-actions.txt incorrectly claimed <a href="/docs/*" use:active> matched bare /docs. It doesn't: regexparam@2.0.2 compiles /docs/* to a pattern requiring the slash
after "docs", so bare /docs falls through. Rewritten as "descendants only" with
the quirk explained, plus new sections covering branch matching (subtree vs regex
vs stacked actions), sidebar-with-submenu, two-class parent/child pattern, generating nav from
a route tree, and filtering by permissions. Same expansion propagated to this site's Link actions feature page — including a Reactivity section
explaining the $derived(filterByPermissions(...)) chain.
v5.2.1
Bare-function routes unblocked on Svelte 5.5+ / Vite 7 / plugin-svelte 6
Single-issue release. Router.svelte's component validator threw Invalid component object for every bare-function route
({ '/': Foo }) on newer toolchains — the Svelte 5 compiler dropped a set of
parens in a nested boolean guard, flipping the check. wrap()-ed routes accidentally
dodged it. The fix hoists the type checks into named locals
(isComponentFn / isWrappedRoute) so the guard is single-nesting and
immune to the compiler's paren elision. If you were workarounded with wrap({ component: Foo }) you can now drop it. Either way: just upgrade.
Upgrading from older versions
v5.2.x → v5.3.0 requires no code changes. Stacked use:active cooperation is backward compatible for the single-action-per-node case (the overwhelmingly
common case). Pick up subtree: true and helpers/nav-tree when you're
ready. (If you happened to try a 5.3.0-rc01 pre-release, rename isHidden: → hidden: in your nav-tree definitions — see the Migration Guide.)
v5.0 / v5.1 → v5.2.x introduced a breaking removal in v5.2.0: the
built-in error toast (showToast) is gone from GlobalErrorHandler —
wire your own toast library inside the onError callback. See the Migration Guide for the details and the major v4 → v5 breakages.
Install
Then jump to the Getting Started guide.