@keenmate/svelte-spa-router 5.0.0-rc10 → 5.0.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/CHANGELOG.md CHANGED
@@ -1,654 +1,767 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [5.0.0-rc10] - 2025-02-01
11
-
12
- ### Added
13
-
14
- #### Debug Logging System
15
- - **Category-based debug logging** - Built-in debug logging to troubleshoot routing issues during development
16
- - **`setDebugLoggingEnabled()` function** - Enable/disable debug logging with a single call
17
- - Simple on/off control - no complex configuration needed
18
- - Disabled by default to keep production consoles clean
19
- - Example: `setDebugLoggingEnabled(import.meta.env.DEV)`
20
- - **`getDebugLoggingEnabled()` function** - Check current debug logging state
21
- - **Color-coded console output** - Easy visual distinction between log categories
22
- - `[Router]` logs in orange (#ff3e00) - Route matching, component loading, guard execution, metadata updates
23
- - `[Router:Utils]` logs in green (#10b981) - Navigation functions (push, pop, replace, goBack), scroll restoration
24
- - **Multiple log levels** - debug, info, warn, error for different severity
25
- - **Zero overhead when disabled** - If checks can be eliminated by bundlers in production
26
- - **Internal architecture** - Generic logging utility (`src/lib/internal/logging.js`) not exposed to users
27
- - `createLogger(category, prefix, color)` factory function
28
- - `enableLoggingCategory()` / `disableLoggingCategory()` for granular control
29
- - Logger instances: `routerLogger`, `utilsLogger`
30
- - **Configuration warnings unaffected** - Critical configuration errors always show regardless of debug setting
31
- - **Example output**:
32
- ```
33
- [Router] Running pipeline for: /document/123
34
- [Router] Route loaded successfully: /document/:id
35
- [Router:Utils] Called - navigationContext: { source: 'menu' }
36
- [Router] Scroll effect triggered - restoreScrollState: true
37
- ```
38
- - **TypeScript support** - Full type definitions with comprehensive JSDoc
39
- - Package export: `@keenmate/svelte-spa-router/utils` (setDebugLoggingEnabled, getDebugLoggingEnabled)
40
-
41
- ## [5.0.0-rc09] - 2025-01-30
42
-
43
- ### Added
44
-
45
- #### Tree/Nested Route Structure (Alternative API)
46
- - **`createHierarchy()` helper** - Define routes in a tree structure as an alternative to flat definitions
47
- - Child paths automatically concatenated to parent paths (relative paths)
48
- - Routes automatically inherit breadcrumbs, permissions, conditions, and authorization from parents
49
- - Optional route names - only add when needed for programmatic navigation
50
- - Coexists seamlessly with flat route definitions
51
- - Example:
52
- ```javascript
53
- import { createHierarchy } from '@keenmate/svelte-spa-router/helpers/hierarchy'
54
-
55
- const routes = createHierarchy({
56
- '/admin': {
57
- name: 'admin',
58
- component: AdminLayout,
59
- breadcrumbs: [{ label: 'Admin' }],
60
- permissions: { any: ['admin'] },
61
- children: {
62
- 'users': {
63
- name: 'adminUsers',
64
- component: AdminUsers,
65
- breadcrumbs: [{ label: 'Users' }],
66
- children: {
67
- ':id': {
68
- name: 'adminUserDetail',
69
- component: AdminUserDetail
70
- }
71
- }
72
- }
73
- }
74
- }
75
- })
76
- // Results in: /admin, /admin/users, /admin/users/:id
77
- ```
78
- - Requires hierarchical mode enabled: `setHierarchicalRoutesEnabled(true)`
79
- - Package export: `@keenmate/svelte-spa-router/helpers/hierarchy`
80
-
81
- #### Named Routes Enhancement
82
- - **`getRouteByName()` function** - Get route pattern by name
83
- - Returns the path pattern for a registered route name
84
- - Useful for testing and debugging
85
- - Example: `getRouteByName('documentDetail')` returns `'/documents/:documentId'`
86
-
87
- #### Automatic Referrer Tracking System
88
- - **Configurable referrer tracking** - Router automatically tracks previous route information and injects it into navigationContext
89
- - **`setIncludeReferrer()` configuration** - Control when referrer info is injected
90
- - `'never'` - Disable referrer tracking (default)
91
- - `'notfound'` - Only inject referrer for 404/NotFound routes
92
- - `'always'` - Inject referrer for all route navigations
93
- - **Referrer object structure** - Complete information about the previous route:
94
- - `location` - Previous route path (e.g., '/documents/123')
95
- - `querystring` - Previous route query string (e.g., 'tab=info&view=grid')
96
- - `params` - Previous route parameters object (e.g., { id: '123' })
97
- - `routeName` - Previous route name if it was registered (e.g., 'documentDetail'), or URL path as fallback
98
- - **NotFound integration** - When catch-all route (`'*'`) matches, router automatically injects:
99
- - `attemptedRoute` - The URL that was not found
100
- - `attemptedQuerystring` - Query string from the attempted route
101
- - `referrer` - Complete previous route information (see structure above)
102
- - **Zero programmer effort** - Works automatically once configured
103
- - **Safe "Go Back" implementation** - Using `push()` with explicit URL instead of `history.back()`
104
- - Safer than `history.back()` which breaks with `replace()` navigation
105
- - Allows conditional logic (e.g., don't go back to unauthorized routes)
106
- - Supports custom fallback destinations
107
- - **Works correctly with both `push()` and `replace()` navigation**
108
- - **Timing behavior** - On first navigation after referrer is updated, component $effects may run twice:
109
- - First run: location changes but referrer not yet injected (sees undefined)
110
- - Second run: navigationContext updates with referrer (sees correct value)
111
- - This is expected Svelte 5 behavior and doesn't affect functionality
112
- - Subsequent navigations are smooth with single $effect run
113
- - UI always renders correctly on the second run
114
- - **Example usage in NotFound component**:
115
- ```javascript
116
- const navContext = $derived(navigationContext())
117
- const referrer = $derived(navContext?.referrer)
118
- const canGoBack = $derived(referrer?.location && referrer.location !== '/')
119
-
120
- function goBack() {
121
- const returnPath = referrer?.location || '/'
122
- const returnQuery = referrer?.querystring
123
- const returnUrl = returnQuery ? `${returnPath}?${returnQuery}` : returnPath
124
- push(returnUrl)
125
- }
126
- ```
127
- - **Example usage in protected routes** (avoid going back to authorization failures):
128
- ```javascript
129
- const navContext = $derived(navigationContext())
130
- const referrer = $derived(navContext?.referrer)
131
- const returnPath = $derived(referrer?.location || navContext?.returnTo || '/')
132
- const returnQuery = $derived(referrer?.querystring || navContext?.returnQuery)
133
-
134
- // Referrer tracks last SUCCESSFULLY loaded route, not attempted routes that failed auth
135
- ```
136
- - Example implementations:
137
- - `example/src/routes/NotFound.svelte` - 404 page with "Go Back"
138
- - `example/src/routes/Unauthorized.svelte` - Authorization failure with safe "Go Back"
139
- - `example/src/routes/User.svelte` - Regular page with referrer-based navigation
140
- - `example/src/routes/ReferrerDemo.svelte` - Comprehensive demo with console logging
141
-
142
- - **`setNavigationContext()` function** - Exported for advanced use cases
143
- - Allows manual setting of navigation context
144
- - Used internally by router for referrer auto-injection
145
- - Example: `setNavigationContext({ customData: 123 })`
146
-
147
- ### Fixed
148
-
149
- #### Named Routes Navigation
150
- - **Fixed `push()` and `replace()` with single-argument named routes** - Resolved "Invalid parameter location" error
151
- - Issue: Calling `push('routeName')` with a single named route argument threw error
152
- - Root cause: Legacy signature detection treated non-slash strings as href paths instead of named routes
153
- - Solution: Added check in legacy signature branch - strings without leading `/` or `#/` are treated as named routes
154
- - Now works: `push('about')`, `push('userProfile')`, `replace('home')`
155
- - Multi-param signature still required for routes with params: `push('userProfile', {id: 123})`
156
-
157
- #### Hierarchical Routes & Breadcrumb System
158
- - **Fixed infinite loop in route metadata updates** - Resolved state comparison issues causing Router to re-render continuously
159
- - Issue: Router's `$effect()` was triggering on every update due to `routeContext` object reference changes
160
- - Root cause: `updateRouteMetadata()` created new objects every call, even when values didn't change
161
- - Solution 1: Added `lastAssignedContext` reference tracking to only update when context reference actually changes
162
- - Solution 2: Implemented route key tracking (`${location}|${querystring}|${JSON.stringify(params)}`) to detect real route changes vs metadata updates
163
- - Fixed `$state` proxy comparison warnings by using `$state.snapshot()` for value comparisons
164
-
165
- - **Breadcrumb cache system for preserving manual updates** - Child route navigation no longer resets dynamic breadcrumbs to "Loading..."
166
- - Issue: Navigating from `/documents/1` to `/documents/1/logs` reset "Document 1" breadcrumb back to "Loading..."
167
- - Root cause: Router composed fresh breadcrumbs on every route change, losing manual `updateBreadcrumb()` calls
168
- - Solution: Implemented breadcrumb cache (`updatedBreadcrumbsCache` Map) to preserve manual updates across child route navigation
169
- - Cache intelligently clears only when navigating to different base routes (not child routes)
170
- - `updateBreadcrumb(id, updates)` stores updates in cache, Router applies them during breadcrumb composition
171
-
172
- - **Child component breadcrumb initialization pattern** - Direct child route reloads now show correct parent breadcrumbs
173
- - Issue: Reloading `/documents/1/logs` directly showed "Loading..." for parent breadcrumb because parent component never mounted
174
- - Pattern: Child components call `updateBreadcrumb()` in `onMount()` to update their parent's dynamic breadcrumbs
175
- - Example: DocumentLogs component updates 'documentDetail' breadcrumb with actual document name on mount
176
- - Works with breadcrumb cache to ensure updates persist across subsequent child navigation
177
- - Applied to all child components in test apps: DocumentLogs, DocumentPermissions, DocumentHistory, AdminUserPermissions, AdminUserActivity
178
-
179
- ### Documentation
180
- - **Added comprehensive documentation showcase** - New SvelteKit-based documentation site
181
- - Built with @keenmate/svelte-docs for consistent styling and components
182
- - Complete API reference with all functions, parameters, and return types
183
- - Feature guides: Routing Modes, Route Configuration, Parameters, Guards, Named Routes, Programmatic Navigation, Querystring, Filters, Permissions, Multi-Zone Routing, Hierarchical Routes, Nested Routes
184
- - Interactive code examples with syntax highlighting
185
- - Live demo apps for both hash and history modes
186
- - Deployed at https://svelte-spa-router.keenmate.dev
187
- - Demo apps: https://history.svelte-spa-router.keenmate.dev and https://hash.svelte-spa-router.keenmate.dev
188
- - **Added Multi-Zone Routing documentation** - Comprehensive guide for multi-zone layouts
189
- - New showcase page at `/features/multi-zone`
190
- - Visual diagram showing zone layout structure
191
- - Complete examples: route configuration, layout setup, zone components
192
- - Covers async loading, route parameters, guards, permissions, and metadata in zones
193
- - Use cases: admin dashboards, email clients, music players, document editors
194
- - Best practices and responsive layout patterns
195
- - **Updated CLAUDE.md** - Added comprehensive section on tree/nested route structure
196
- - **Added examples** - Tree structure examples in main example app
197
-
198
- ## [5.0.0-rc08] - 2025-10-26
199
-
200
- ### Breaking Changes
201
- - **BREAKING: Renamed `params` to `routeParams`** for clarity
202
- - Component prop: `let { params } = $props()` `let { routeParams } = $props()`
203
- - Function call: `params()` `routeParams()`
204
- - This makes it immediately clear these are route parameters from URL patterns
205
- - Update all route components to use `routeParams` instead of `params`
206
- - TypeScript: `params<T>()` `routeParams<T>()`
207
-
208
- ## [5.0.0-rc07] - 2025-10-26
209
-
210
- ### Fixed
211
- - **Critical: Tree-shaking issue in production builds** - Added `sideEffects` field to package.json to prevent Vite from incorrectly tree-shaking `.svelte` and `.svelte.js` files during production builds
212
- - Fixes "link is not defined" and similar errors in production builds with npm package
213
- - `.svelte.js` files contain Svelte 5 runes (`$state`, `$derived`, `$effect`) at module level which have side effects and must not be tree-shaken
214
- - Issue only affected production builds, not development mode
215
-
216
- ## [5.0.0-rc06] - 2025-10-26
217
-
218
- ### Added
219
-
220
- #### Multi-Parameter Navigation & Strict Parameter Replacement
221
- - **Multi-parameter signature for `push()` and `replace()`** - Natural function call style
222
- - `push(route, routeParams, queryString, navigationContext)`
223
- - `replace(route, routeParams, queryString, navigationContext)`
224
- - Route resolution: Starts with `/` = exact path, otherwise = named route lookup
225
- - Examples: `push('userProfile', { userId: 123 }, { tab: 'settings' })`
226
- - Backward compatible with all existing formats (string, array, object)
227
-
228
- - **4-element array support** - Navigation context in arrays
229
- - `push(['route', params, query, navigationContext])`
230
- - `link={['route', params, query, navigationContext]}`
231
- - Example: `<a use:link={['bookDetail', {bookId: 123}, {tab: 'reviews'}, {source: 'list'}]}>`
232
-
233
- - **Strict parameter replacement with placeholder**
234
- - `setParamReplacementPlaceholder(value)` - Configure placeholder for missing params (default: 'N-A')
235
- - Missing params replaced with placeholder instead of being removed
236
- - Predictable URLs: `/users/:userId/:section` with missing section → `/users/123/N-A`
237
- - Triggers `onNotFound` callback for error tracking
238
-
239
- #### Resource-Based Authorization
240
- - **`authorizationCallback` parameter for `createProtectedRoute()`** - Combine role + resource checks
241
- - Supports both role-based (permissions) and resource-based (authorizationCallback) authorization
242
- - Conditions execute in order: permissions first (fast), then authorizationCallback (API call)
243
- - Example: Check if user has 'editor' role, then check if they can access specific document
244
- - Perfect for document access, resource ownership, dynamic permissions
245
- - Callback receives full route detail: `{ route, location, params, query, routeContext, navigationContext }`
246
-
247
- #### Global Error Handler System
248
- - **Production-ready global error handler** for catching and recovering from unhandled errors
249
- - `configureGlobalErrorHandler()` - Configure error handling behavior in `main.js`
250
- - `GlobalErrorHandler` component - Wraps your app and catches all errors
251
- - `ErrorDisplay` component - Beautiful default error UI with recovery options
252
- - **Recovery Strategies**: `navigateSafe`, `restart`, `showError`, `custom`
253
- - **Loop Prevention**: SessionStorage-based restart tracking prevents infinite reload loops
254
- - **Custom Callbacks**: `onError` for logging/monitoring, `onRecover` for custom recovery logic
255
- - **Helper Functions**: `restart()`, `navigate()`, `showError()`, `canRestart()`, `getRestartCount()`
256
- - **Error Filtering**: Ignore known non-critical errors (ResizeObserver, etc.)
257
- - **UI Options**: Toast notifications, full-page error component, or custom error component
258
- - **TypeScript Support**: Full type definitions for all APIs
259
- - **Example Integration**: Working demo with Sentry integration example
260
-
261
- #### 404 Not Found Tracking
262
- - **`onNotFound` callback on Router component** - Track 404s for analytics/monitoring
263
- - Fires when catch-all route (`'*'`) matches (user sees 404 page)
264
- - Fires when no route matches at all (no 404 page defined)
265
- - Perfect for logging to Sentry, Google Analytics, or other monitoring services
266
- - Event detail includes `{ location, querystring }`
267
- - Example: `<Router {routes} onNotFound={(e) => Sentry.captureMessage('404', { extra: e.detail })} />`
268
-
269
- #### Convenient Route Creation API
270
- - **New `createRoute()` and `createRouteDefinition()` functions** for easier route configuration
271
- - `createRoute()` - Returns already wrapped component (most convenient, no `wrap()` needed)
272
- - `createRouteDefinition()` - Returns route definition for use with `wrap()` (advanced use)
273
- - Consistent API pattern matching `createProtectedRoute()` / `createProtectedRouteDefinition()`
274
- - Support for `title` and `breadcrumbs` metadata directly in route options
275
- - Automatic detection of sync vs async components
276
-
277
- - **Enhanced Permission System**
278
- - `createProtectedRoute()` - Now returns already wrapped component (breaking change from definition-only)
279
- - `createProtectedRouteDefinition()` - New function that returns definition (replaces old `createProtectedRoute()` behavior)
280
- - Maintains backward compatibility for existing `wrap(createProtectedRoute(...))` usage
281
-
282
- - **Route Metadata Support**
283
- - `title` - Set page title for routes
284
- - `breadcrumbs` - Define breadcrumb trail with `{ label, path? }` structure
285
- - Metadata stored in `routeContext` object, accessible in route events and components
286
-
287
- #### Dynamic Metadata & Loading Control
288
- - **Reactive Metadata Helpers**: `helpers/route-metadata.svelte.js`
289
- - `routeTitle()` - Get current route title reactively
290
- - `routeBreadcrumbs()` - Get current breadcrumb trail reactively
291
- - `routeContext()` - Get full route context reactively
292
- - `updateRouteMetadata(routeContext)` - Update metadata after data loads (e.g., change title from "Document" to "Invoice.pdf")
293
- - **`updateTitle(title)` - Update just the title** (simpler than updateRouteMetadata)
294
- - **`updateBreadcrumb(id, updates)` - Partial breadcrumb updates** (update specific segments by ID)
295
- - Automatically updated by Router on route changes
296
-
297
- - **Partial Breadcrumb Updates** (NEW!)
298
- - Add `id` property to breadcrumb items in route config: `{ id: 'docDetail', label: 'Loading...', path: '/doc/:id' }`
299
- - Update specific breadcrumbs after data loads: `updateBreadcrumb('docDetail', { label: 'Invoice.pdf', path: '/doc/123' })`
300
- - **No need to replace the entire breadcrumbs array** - only update what changes!
301
- - Perfect for nested paths: `/documents/:id/logs/:logId` where each segment needs dynamic data
302
- - Static segments (Home, Documents, etc.) stay unchanged
303
-
304
- - **Flexible Loading Control** with `shouldDisplayLoadingOnRouteLoad` flag
305
- - **Pattern 1 (Router-managed with loadingComponent)**: Set `shouldDisplayLoadingOnRouteLoad: true` - Router keeps loading component visible until component calls `hideLoading()`
306
- - **Pattern 2 (Component-managed)**: Default behavior - component handles its own loading state
307
- - **Pattern 3 (Global overlay)**: User-defined global loading UI in App.svelte that reacts to `routeIsLoading()`
308
- - `showLoading()` - Manually show loading state (triggers global overlay if defined)
309
- - `hideLoading()` - Component signals data is loaded (hides loading component/overlay)
310
- - `routeIsLoading()` - Check if route is currently loading (reactive state)
311
- - Perfect for routes that fetch data and need dynamic titles/breadcrumbs
312
- - Supports multi-zone layouts (toolpanel + content) with different loading UIs per zone
313
-
314
- **Use cases**:
315
- - Document detail page: Show "Document" while loading, then "Invoice template.pdf" after data loads
316
- - User profile: Show "User Profile" while loading, then "John Doe" after data loads
317
- - Product page: Show "Product" while loading, then "iPhone 15 Pro" after data loads
318
- - Nested paths: `/documents/:id/logs` where both document name and "Logs" need to be in breadcrumbs
319
-
320
- #### Named Routes Enhancement
321
- - **Array/Object Syntax for Navigation**: `push()` and `replace()` functions now support the same convenient array/object syntax as the `link` action
322
- - Array format: `push(['userProfile', { userId: 123 }, { tab: 'settings' }])`
323
- - Object format: `push({ route: 'userProfile', params: { userId: 123 }, query: { tab: 'settings' } })`
324
- - String format (legacy): `push('/about')` - still fully supported for backward compatibility
325
- - Eliminates the need to manually call `buildUrl()` for programmatic navigation
326
- - See `example-history/src/routes/LinksDemo.svelte` for interactive demos
327
-
328
- ### Added - Querystring & Filter System
329
-
330
- #### Querystring Helpers
331
- - **Shared Reactive State**: `helpers/querystring.svelte.js`
332
- - `configureQuerystring(options)` - Configure querystring parsing for entire app
333
- - `query<T>()` - Get reactive parsed querystring with TypeScript generics
334
- - Auto-detection of array formats (repeat vs comma-separated)
335
- - Configure once, use everywhere pattern
336
-
337
- - **Querystring Utilities**: `helpers/querystring-helpers.svelte.js`
338
- - `parseQuerystring(qs, options)` - Parse querystring with array format support
339
- - `stringifyQuerystring(obj, options)` - Convert object to querystring
340
- - `getParsedQuerystring(options)` - Get parsed querystring (non-reactive)
341
- - `updateQuerystring(updates, options)` - Update URL querystring (partial or full)
342
- - `createQuerystringHelpers(parser, stringifier)` - Custom parser support
343
-
344
- - **Array Format Support**:
345
- - `'auto'` (default) - Auto-detects both repeat and comma formats
346
- - `'repeat'` - `?tags=foo&tags=bar` (multiple parameters with same key)
347
- - `'comma'` - `?tags=foo,bar,baz` (comma-separated values)
348
-
349
- #### Filter System
350
- - **Flexible Filters**: `helpers/filters.svelte.js`
351
- - `configureFilters(options)` - Configure filter mode and parsing
352
- - `filters<T>()` - Get reactive parsed filters with TypeScript generics
353
- - `updateFilters<T>(updates, options)` - Update filters with type safety
354
- - `getFiltersConfig()` - Get current filter configuration
355
-
356
- - **Dual Mode Support**:
357
- - **Flat Mode** (default): Each filter as separate query parameter
358
- - Example: `?search=java&category=books&status=active`
359
- - **Structured Mode**: Single parameter with custom syntax
360
- - Example: `?$filter=search eq 'java' AND category eq 'books'`
361
- - Supports custom parse/stringify functions for OData, Microsoft Graph API, etc.
362
-
363
- #### TypeScript Support
364
- - Full generic support for type-safe parameter access:
365
- - `params<T>()` - Route parameters with intellisense
366
- - `query<T>()` - Query parameters with intellisense
367
- - `filters<T>()` - Filter parameters with intellisense
368
- - `updateFilters<T>(updates, options)` - Type-safe filter updates
369
-
370
- #### Value Handling
371
- - **Filters**:
372
- - `undefined` - Always removes the parameter
373
- - `null` - Keeps parameter with empty value
374
- - **Querystring**:
375
- - `undefined` - Always removes the parameter
376
- - `null` - Controlled by `dropNull` option (default: true removes it)
377
- - Empty string - Controlled by `dropEmpty` option (default: false keeps it)
378
-
379
- #### Example Applications
380
- - `example-history/src/routes/QuerystringDemo.svelte` - Interactive querystring demo
381
- - `example-history/src/routes/FiltersDemo.svelte` - Filter system with products demo
382
- - `example-history/src/routes/RouteDataDemo.svelte` - Route data extraction examples
383
-
384
- ### Fixed
385
-
386
- #### Router Core Fixes
387
- - **Router Initialization**: Fixed location state initialization to be reactive to config changes
388
- - Issue: Direct URL access (e.g., `http://localhost:5050/querystring-demo`) showed homepage
389
- - Solution: Changed from `$state(getLocation())` to `$derived.by()` to react to config changes
390
- - Now correctly reads `setHashRoutingEnabled()` before initializing location state
391
-
392
- #### Loading State Fixes
393
- - **Component Double Mounting**: Fixed race condition causing components to mount twice
394
- - Issue: Router template had component in two separate `{:else if}` blocks - one hidden, one visible
395
- - When `isWaitingForData` changed, Svelte unmounted from first block and remounted in second
396
- - Resulted in `onMount()` running twice, causing duplicate data fetches and delays
397
- - Solution: Refactored template to keep component in single block with `style:display` toggle
398
- - Component now mounts once and visibility is controlled via CSS
399
-
400
- - **Component Params Not Available on Mount**: Fixed params being empty when component loads
401
- - Issue: Router set `isWaitingForData = true` and waited for `hideLoading()` BEFORE setting `componentParams`
402
- - Component mounted without params, causing "params not ready" errors
403
- - Solution: Moved `componentParams`, `componentProps`, `componentrouteContext` assignment BEFORE waiting logic
404
- - Component now has access to params immediately on mount
405
-
406
- - **Global vs Route Loading Conflict**: Fixed overlapping loading indicators
407
- - Issue: Both global loader and route-specific loader showed simultaneously
408
- - Solution: Added `shouldShowGlobalLoading()` helper that only returns true when route doesn't have custom loading
409
- - Added `hasCustomLoadingComponent` flag set by `startRouteLoading(hasCustomComponent)`
410
- - Global loader now only shows for routes without custom loading components
411
-
412
- - **Manual Loading Not Showing**: Fixed `showLoading()` not displaying global loader
413
- - Issue: `showLoading()` set `isRouteLoading = true` but didn't reset `hasCustomLoadingComponent` flag
414
- - If current route had custom loading, `shouldShowGlobalLoading()` returned false
415
- - Solution: Made `showLoading()` also set `hasCustomLoadingComponent = false`
416
- - Manual loading triggers now correctly show global loader
417
-
418
- #### Authorization & Navigation Context Fixes
419
- - **"Go Back" After Unauthorized**: Fixed return navigation after authorization failures
420
- - Issue: Unauthorized page's "Return to Home" button always went to home instead of previous location
421
- - Root cause: Using `push('/unauthorized', { data })` was ambiguous - interpreted as routeParams instead of navigationContext
422
- - Solution: Changed all unauthorized redirects to use explicit 4-parameter signature: `push(route, {}, {}, navContext)`
423
- - Added `returnTo` and `returnQuery` to navigation context for proper "Go Back" functionality
424
- - Unauthorized page now displays "Go Back" button when `returnTo` is available
425
-
426
- - **Document Authorization Navigation**: Fixed document access redirects
427
- - Updated `authorizationCallback` to pass `returnTo` and `returnQuery` via navigationContext
428
- - Fixed "View Document" buttons to pass navigation context for proper back navigation
429
- - All authorization demo flows now preserve return path for better UX
430
-
431
- #### Protected Route Fixes
432
- - **Async Component Params Empty**: Fixed params not being passed to protected routes
433
- - Issue: `createProtectedRouteDefinition` was using `asyncComponent: component` which caused wrap() to double-wrap async imports
434
- - Solution: Kept `asyncComponent: component` which properly passes async imports to wrap()
435
- - Protected routes now receive params correctly when using `() => import('./Component.svelte')`
436
-
437
- #### Example Application Fixes
438
- - **Missing Routes**: Added missing example routes
439
- - Added `/about` route (About component)
440
- - Added `/user/:first/:last?` route (User component with optional last name)
441
- - Links in LinksDemo now work correctly
442
-
443
- - **Navigation Context Demo**: Fixed incorrect route paths and push signatures
444
- - Changed `/context-demo` references to correct `/navigation-context-demo` route
445
- - Fixed `backToList()` function to navigate to correct route
446
- - Updated code examples to show proper 4-parameter signature
447
- - Fixed "View" and "Edit" buttons to use correct navigation context syntax
448
-
449
- - **HTML Entity Escaping**: Fixed Svelte parse errors
450
- - Changed unescaped `{}` in code examples to HTML entities `&#123;&#125;`
451
- - Prevents Svelte from treating them as reactive expressions
452
-
453
- ### Documentation
454
- - Updated `README.md` with comprehensive querystring and filter system documentation
455
- - Added TypeScript generic examples throughout
456
- - Added Quick Reference section with all available imports
457
- - Updated main features list to highlight TypeScript and URL helpers
458
-
459
- ## [1.0.0] - 2024
460
-
461
- ### Package Information
462
-
463
- - **Package Name:** @keenmate/svelte-spa-router
464
- - **Publisher:** KeenMate (https://keenmate.com)
465
- - **Repository:** https://github.com/keenmate/svelte-spa-router
466
-
467
- ### Added - Svelte 5 Conversion
468
-
469
- #### Core Router
470
- - Complete rewrite using Svelte 5 runes (`$state`, `$props`, `$effect`)
471
- - Reactive location tracking with rune-based state management
472
- - Event handlers converted from `on:` directives to callback props
473
- - Maintained full backward compatibility in route definition syntax
474
-
475
- #### Dual-Mode Routing System
476
- - **Hash Mode (Default)**: Traditional hash-based routing (`#/path`)
477
- - No configuration required
478
- - Works everywhere, including `file://` protocol
479
- - Perfect for static hosting
480
-
481
- - **History Mode (New)**: Clean URL routing (`/path`)
482
- - Uses History API for clean URLs without hash
483
- - Supports modifier keys (Ctrl+Click to open in new tab)
484
- - Respects `target` attribute on links
485
- - Requires server configuration to serve index.html for all routes
486
-
487
- #### Configuration Functions
488
- - `setHashRoutingEnabled(boolean)` - Toggle between hash and history mode
489
- - `setBasePath(string)` - Set base path for history mode
490
- - `getHashRoutingEnabled()` - Get current routing mode
491
- - `getBasePath()` - Get current base path
492
-
493
- #### Permission System (New)
494
- - `helpers/permissions.svelte.js` - Comprehensive RBAC system
495
- - `configurePermissions(config)` - Setup permission checking logic
496
- - `createPermissionCondition(requirements)` - Create route guards
497
- - `createProtectedRoute(options)` - Helper for protected routes
498
- - `hasPermission(requirements)` - UI-level permission checking
499
- - Support for `any` (OR) and `all` (AND) permission logic
500
- - Flexible integration with existing route guards
501
-
502
- #### URL Helpers
503
- - `helpers/url-helpers.svelte.js` - Path manipulation utilities
504
- - `joinPaths(...paths)` - Intelligent path joining with slash handling
505
-
506
- #### Enhanced Active Link Detection
507
- - Updated to work with both hash and history modes
508
- - Automatic CSS class application on matching routes
509
- - Pattern matching support
510
-
511
- ### Changed
512
-
513
- #### Naming Changes (Non-Breaking in Usage, Breaking for Type Imports)
514
- - **`userData` `routeContext`**: Renamed for clarity
515
- - Refers to static route-level configuration data
516
- - `wrap({ routeContext: { ... } })`
517
- - `routeContext()` helper function
518
- - All TypeScript interfaces updated
519
-
520
- - **`context` `navigationContext`**: Renamed for clarity
521
- - Refers to dynamic data passed during navigation (doesn't appear in URL)
522
- - `push('/path', { orderId: 123 })` - second parameter is navigationContext
523
- - `navigationContext()` accessor function
524
- - `wrap({ navigationContext: { ... } })`
525
-
526
- These changes clarify the distinction between:
527
- - **routeContext**: Static metadata defined in route configuration
528
- - **navigationContext**: Dynamic data passed at navigation time (WinForms-like experience)
529
-
530
- #### API Changes (Breaking)
531
- - **Stores Functions**:
532
- - `$location` `location()`
533
- - `$querystring` `querystring()`
534
- - `$params` `params()`
535
- - `$loc` `loc()`
536
-
537
- - **Events Props**:
538
- - `on:routeLoading` → `onrouteLoading`
539
- - `on:routeLoaded` `onrouteLoaded`
540
- - `on:conditionsFailed` `onconditionsFailed`
541
- - `on:routeEvent` `onrouteEvent`
542
-
543
- - **Component Props**:
544
- - `export let params` → `let { params } = $props()`
545
-
546
- - **Reactive Subscriptions**:
547
- - `.subscribe()` `$effect(() => { ... })`
548
-
549
- ### Unchanged (Backward Compatible)
550
-
551
- - Route definition syntax (same object/Map structure)
552
- - `push()`, `pop()`, `replace()` navigation functions
553
- - `link` action for anchor tags
554
- - `active` action for link highlighting
555
- - ✅ `wrap()` utility for async components and conditions
556
- - Route guards/pre-conditions
557
- - Dynamic imports and code-splitting
558
- - Nested routers with prefix
559
- - Scroll restoration
560
- - Loading components
561
-
562
- ### Documentation
563
-
564
- #### New Files
565
- - `CONTEXT.md` - Comprehensive project overview
566
- - `CHANGELOG.md` - This file
567
- - `DEVELOPMENT.md` - Development workflow guide
568
- - `MIGRATION.md` - Detailed v4 → v5 migration guide
569
- - `HASHLESS_MERGE_PLAN.md` - Technical implementation notes
570
-
571
- #### Updated Files
572
- - `README.md` - Added routing modes and permission system documentation
573
- - `package.json` - Updated exports for new modules
574
-
575
- ### Examples
576
-
577
- - `example/` - Hash mode example (updated for Svelte 5)
578
- - `example-history/` - History mode example (new)
579
-
580
- ### Infrastructure
581
-
582
- - ESLint configuration for Svelte 5
583
- - Makefile for cross-platform development commands
584
- - Package exports for all modules
585
-
586
- ## Migration Guide
587
-
588
- See [MIGRATION.md](./MIGRATION.md) for detailed instructions on upgrading from v4 to v5.
589
-
590
- ### Quick Migration Checklist
591
-
592
- 1. Update imports: `svelte-spa-router` → `@keenmate/svelte-spa-router`
593
- 2. ✅ Change stores to functions: `$location` → `location()`
594
- 3. Update event handlers: `on:routeLoaded` `onrouteLoaded`
595
- 4. Convert component props: `export let params` → `let { params } = $props()`
596
- 5. Replace subscriptions with `$effect`
597
- 6. Test all routes and navigation
598
-
599
- ### Optional: Enable History Mode
600
-
601
- ```javascript
602
- // main.js
603
- import { setHashRoutingEnabled, setBasePath } from '@keenmate/svelte-spa-router/utils'
604
-
605
- setHashRoutingEnabled(false)
606
- setBasePath('/')
607
-
608
- mount(App, { target: document.body })
609
- ```
610
-
611
- ### Optional: Add Permission System
612
-
613
- ```javascript
614
- // main.js
615
- import { configurePermissions } from '@keenmate/svelte-spa-router/helpers/permissions'
616
-
617
- configurePermissions({
618
- checkPermissions: (user, requirements) => {
619
- // Your permission logic
620
- },
621
- getCurrentUser: () => getCurrentUser(),
622
- onUnauthorized: (detail) => push('/unauthorized')
623
- })
624
- ```
625
-
626
- ## Browser Compatibility
627
-
628
- - **Svelte 5**: All modern browsers (Chrome, Firefox, Safari, Edge)
629
- - **Hash Mode**: All browsers including IE10+
630
- - **History Mode**: All browsers with History API support (IE10+)
631
- - **Permissions**: All modern browsers
632
-
633
- ## Dependencies
634
-
635
- ### Runtime
636
- - `regexparam@2.0.2` - Route pattern matching
637
-
638
- ### Peer Dependencies
639
- - `svelte@^5.0.0` - Required
640
-
641
- ### Dev Dependencies
642
- - `eslint@^9.0.0` - Code linting
643
-
644
- ## Credits
645
-
646
- - **Publisher:** KeenMate (https://keenmate.com)
647
- - **Original svelte-spa-router:** Alessandro Segala (@ItalyPaleAle)
648
- - **Svelte 5 Conversion:** KeenMate team
649
- - **History Mode:** Adapted from svelte-spa-router-hashless
650
- - **Permission System:** KeenMate team
651
-
652
- ## License
653
-
654
- MIT License - See [LICENSE.md](./LICENSE.md)
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [5.0.0] - 2025-01-17
11
+
12
+ ### Changed
13
+ - **Code Quality:** Major ESLint cleanup - reduced linting issues from 161 to 11 (93% reduction)
14
+ - Removed unused imports across multiple modules (hierarchyLogger, location, untrack, hasRoute, etc.)
15
+ - Removed unused function parameters in test files
16
+ - Replaced unused catch error variables with bare catch blocks
17
+ - Added `src/lib/vendor/**` to eslint ignore list (third-party loglevel library)
18
+ - Remaining 11 warnings are false positives from ESLint not understanding Svelte 5 runes
19
+ - **Note:** If issues arise, this cleanup touched error-handler, hierarchy, navigation-guard, permissions, querystring-helpers, route-metadata, utils, and all test files
20
+ - **Logger API:** Renamed `enableCategory()` to `setCategoryLevel()` for better clarity
21
+ - Old name was confusing: `enableCategory('ROUTER:SCROLL', 'silent')` reads as "enable to disable"
22
+ - New name is explicit: `setCategoryLevel('ROUTER:SCROLL', 'silent')` clearly sets the level
23
+ - Added `'silent'` to TypeScript type definitions for level parameter
24
+ - No backward compatibility - clean break for clearer API
25
+
26
+ ### Fixed
27
+ - **Breaking:** Standardized Router callback prop naming to camelCase (JavaScript convention)
28
+ - `onrouteLoading` `onRouteLoading`
29
+ - `onrouteLoaded` `onRouteLoaded`
30
+ - `onconditionsFailed` `onConditionsFailed`
31
+ - `onNotFound` remains unchanged (already correct)
32
+ - Updated all documentation, examples, tests, and showcase site
33
+ - **Migration:** Update your Router component props to use camelCase naming
34
+ - **Permissions:** Completely redesigned unauthorized handling system
35
+ - Previously required manual `/unauthorized` route definition and `onUnauthorized` callback with hash-based navigation
36
+ - New system treats unauthorized state as special router state (like 404), not a regular route
37
+ - `/unauthorized` route no longer needs to be defined in routes object
38
+ - Respects configured routing mode (hash/history) instead of forcing hash navigation
39
+ - Two behavior modes available:
40
+ - `unauthorizedBehavior: 'component'` - Shows unauthorized component without changing URL (default)
41
+ - `unauthorizedBehavior: 'navigate'` - Navigates to configured unauthorized route
42
+ - Configure via `configurePermissions()` with new options: `unauthorizedBehavior`, `unauthorizedRoute`, `unauthorizedComponent`
43
+ - Router automatically detects permission failures by checking `routeContext.permissions`
44
+ - `createPermissionCondition()` only calls `onUnauthorized` handler if explicitly configured (backward compatibility)
45
+ - Added internal `hasExplicitHandler()` tracking to distinguish explicit callbacks from defaults
46
+ - **Migration:** Old `onUnauthorized` callback approach still works, new declarative config recommended
47
+ - **Referrer Tracking:** Fixed referrer not being preserved on browser back/forward navigation
48
+ - Previously, pressing back button would show chronological previous route as referrer instead of original referrer
49
+ - Example: `/` `/links` (referrer: `/`) `/query` (referrer: `/links`) → [BACK] → `/links` showed referrer `/query` (wrong!) instead of `/` (correct)
50
+ - Root cause: navigationContext with referrer was calculated by router but never saved to history.state
51
+ - Solution: Router now saves calculated navigationContext (with referrer) back to history.state after route loads
52
+ - Added serialization handling for Proxy objects in params (uses JSON serialization fallback when structuredClone fails)
53
+ - Applied to both hash mode and history mode navigation
54
+ - Navigation sequence tracking now correctly increments on forward and decrements on back
55
+ - `goBack()` function simplified to use native browser back (`window.history.back()`) instead of manual push
56
+ - Referrer and scroll position now automatically restored from history.state on back/forward navigation
57
+ - **Permissions:** Fixed `createProtectedRoute()` failing with synchronous component imports
58
+ - Error: "Cannot read properties of undefined (reading 'before')" when using sync imports like `component: AdminPanel`
59
+ - Root cause: `createProtectedRouteDefinition()` always treated components as async, causing Router to call component constructor as function returning `undefined`
60
+ - Solution: Detect sync vs async components - use `component` key for sync (let wrap() handle Promise wrapping) and `asyncComponent` key for async
61
+ - Now supports both patterns: `component: AdminPanel` (sync) and `component: () => import('./Admin.svelte')` (async)
62
+ - Async detection: `typeof component === 'function' && component.length === 0`
63
+
64
+ ## [5.0.0-rc12] - 2025-02-12 ✅ Published
65
+
66
+ ### Fixed
67
+ - **Critical:** Fixed missing TypeScript source files in published npm package
68
+ - Added `src/lib/**/*.ts` to `package.json` files field
69
+ - Fixes build error: `Rollup failed to resolve import "@keenmate/svelte-spa-router/logger"`
70
+ - The `logger.ts` file was missing from rc11 npm package, breaking production builds
71
+ - Removed deprecated `setDebugLoggingEnabled()` and `getDebugLoggingEnabled()` type declarations from `utils.d.ts`
72
+ - These functions were removed in rc11 but TypeScript definitions remained, causing confusion
73
+ - Use new API: `import { enableLogging, disableLogging } from '@keenmate/svelte-spa-router/logger'`
74
+ - Added missing TypeScript definitions for `helpers/hierarchy` module
75
+ - Created `src/lib/helpers/hierarchy.d.ts` with full type support for `createHierarchy()`
76
+ - Includes `HierarchyNode`, `HierarchyTree`, and `CreateHierarchyOptions` interfaces
77
+ - Fixed logger type resolution in package.json exports
78
+ - Changed `"types": "./src/lib/logger.d.ts"` to `"types": "./src/lib/logger.ts"`
79
+ - TypeScript now correctly resolves types from the source file
80
+
81
+ ## [5.0.0-rc11] - 2025-02-01
82
+
83
+ ### Changed
84
+
85
+ #### BREAKING: Logging System Migration to loglevel
86
+ - **Migrated from custom logging to loglevel library** (~1KB) with hierarchical categories
87
+ - Consistent with `@keenmate/web-multiselect` logging implementation
88
+ - Uses vendored `loglevel` and `loglevel-plugin-prefix` libraries (ESM versions)
89
+ - **Breaking API change**: `setDebugLoggingEnabled()` replaced with new API
90
+ - Old: `import { setDebugLoggingEnabled } from '@keenmate/svelte-spa-router/utils'`
91
+ - New: `import { enableLogging, disableLogging, setLogLevel, setCategoryLevel } from '@keenmate/svelte-spa-router/logger'`
92
+ - **12 hierarchical categories** for granular control:
93
+ - `ROUTER` - Core routing pipeline, route matching
94
+ - `ROUTER:NAVIGATION` - push, pop, replace, goBack
95
+ - `ROUTER:SCROLL` - Scroll restoration
96
+ - `ROUTER:GUARDS` - Navigation guards
97
+ - `ROUTER:CONDITIONS` - Route condition checks
98
+ - `ROUTER:HIERARCHY` - Hierarchical route inheritance
99
+ - `ROUTER:PERMISSIONS` - Permission checking
100
+ - `ROUTER:ROUTES` - Named routes and URL building
101
+ - `ROUTER:ZONES` - Multi-zone routing
102
+ - `ROUTER:METADATA` - Breadcrumbs and route metadata
103
+ - `ROUTER:ERROR_HANDLER` - Global error handling
104
+ - `ROUTER:FILTERS` - Filter parsing
105
+ - **Enhanced output format**: \`[HH:MM:SS.mmm] [LEVEL] [CATEGORY] message\`
106
+ - **Color-coded by log level**: Blue (debug), Green (info), Orange (warn), Red (error)
107
+ - **Per-category control**: Enable specific categories at different log levels
108
+ \`\`\`javascript
109
+ disableLogging() // Disable all
110
+ setCategoryLevel('ROUTER:SCROLL', 'debug') // Enable only scroll logs
111
+ setCategoryLevel('ROUTER:NAVIGATION', 'info') // Navigation at info level
112
+ \`\`\`
113
+ - **Global level control**: \`setLogLevel('warn')\` to set all categories at once
114
+ - Removed \`src/lib/internal/logging.js\` (custom implementation)
115
+ - Added \`src/lib/logger.ts\` (loglevel-based implementation)
116
+ - Added \`src/lib/vendor/loglevel/\` with ESM library files
117
+
118
+ ### Fixed
119
+ - Fixed ESM import error with loglevel library (\`Cannot set properties of undefined\`)
120
+ - Switched from UMD to ESM versions of vendored libraries
121
+ - Added wrapper files (\`index.js\`, \`prefix.js\`) for proper ES module imports
122
+
123
+ ## [5.0.0-rc10] - 2025-02-01
124
+
125
+ ### Added
126
+
127
+ #### Debug Logging System
128
+ - **Category-based debug logging** - Built-in debug logging to troubleshoot routing issues during development
129
+ - **`setDebugLoggingEnabled()` function** - Enable/disable debug logging with a single call
130
+ - Simple on/off control - no complex configuration needed
131
+ - Disabled by default to keep production consoles clean
132
+ - Example: `setDebugLoggingEnabled(import.meta.env.DEV)`
133
+ - **`getDebugLoggingEnabled()` function** - Check current debug logging state
134
+ - **Color-coded console output** - Easy visual distinction between log categories
135
+ - `[Router]` logs in orange (#ff3e00) - Route matching, component loading, guard execution, metadata updates
136
+ - `[Router:Utils]` logs in green (#10b981) - Navigation functions (push, pop, replace, goBack), scroll restoration
137
+ - **Multiple log levels** - debug, info, warn, error for different severity
138
+ - **Zero overhead when disabled** - If checks can be eliminated by bundlers in production
139
+ - **Internal architecture** - Generic logging utility (`src/lib/internal/logging.js`) not exposed to users
140
+ - `createLogger(category, prefix, color)` factory function
141
+ - `enableLoggingCategory()` / `disableLoggingCategory()` for granular control
142
+ - Logger instances: `routerLogger`, `utilsLogger`
143
+ - **Configuration warnings unaffected** - Critical configuration errors always show regardless of debug setting
144
+ - **Example output**:
145
+ ```
146
+ [Router] Running pipeline for: /document/123
147
+ [Router] Route loaded successfully: /document/:id
148
+ [Router:Utils] Called - navigationContext: { source: 'menu' }
149
+ [Router] Scroll effect triggered - restoreScrollState: true
150
+ ```
151
+ - **TypeScript support** - Full type definitions with comprehensive JSDoc
152
+ - Package export: `@keenmate/svelte-spa-router/utils` (setDebugLoggingEnabled, getDebugLoggingEnabled)
153
+
154
+ ## [5.0.0-rc09] - 2025-01-30
155
+
156
+ ### Added
157
+
158
+ #### Tree/Nested Route Structure (Alternative API)
159
+ - **`createHierarchy()` helper** - Define routes in a tree structure as an alternative to flat definitions
160
+ - Child paths automatically concatenated to parent paths (relative paths)
161
+ - Routes automatically inherit breadcrumbs, permissions, conditions, and authorization from parents
162
+ - Optional route names - only add when needed for programmatic navigation
163
+ - Coexists seamlessly with flat route definitions
164
+ - Example:
165
+ ```javascript
166
+ import { createHierarchy } from '@keenmate/svelte-spa-router/helpers/hierarchy'
167
+
168
+ const routes = createHierarchy({
169
+ '/admin': {
170
+ name: 'admin',
171
+ component: AdminLayout,
172
+ breadcrumbs: [{ label: 'Admin' }],
173
+ permissions: { any: ['admin'] },
174
+ children: {
175
+ 'users': {
176
+ name: 'adminUsers',
177
+ component: AdminUsers,
178
+ breadcrumbs: [{ label: 'Users' }],
179
+ children: {
180
+ ':id': {
181
+ name: 'adminUserDetail',
182
+ component: AdminUserDetail
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ })
189
+ // Results in: /admin, /admin/users, /admin/users/:id
190
+ ```
191
+ - Requires hierarchical mode enabled: `setHierarchicalRoutesEnabled(true)`
192
+ - Package export: `@keenmate/svelte-spa-router/helpers/hierarchy`
193
+
194
+ #### Named Routes Enhancement
195
+ - **`getRouteByName()` function** - Get route pattern by name
196
+ - Returns the path pattern for a registered route name
197
+ - Useful for testing and debugging
198
+ - Example: `getRouteByName('documentDetail')` returns `'/documents/:documentId'`
199
+
200
+ #### Automatic Referrer Tracking System
201
+ - **Configurable referrer tracking** - Router automatically tracks previous route information and injects it into navigationContext
202
+ - **`setIncludeReferrer()` configuration** - Control when referrer info is injected
203
+ - `'never'` - Disable referrer tracking (default)
204
+ - `'notfound'` - Only inject referrer for 404/NotFound routes
205
+ - `'always'` - Inject referrer for all route navigations
206
+ - **Referrer object structure** - Complete information about the previous route:
207
+ - `location` - Previous route path (e.g., '/documents/123')
208
+ - `querystring` - Previous route query string (e.g., 'tab=info&view=grid')
209
+ - `params` - Previous route parameters object (e.g., { id: '123' })
210
+ - `routeName` - Previous route name if it was registered (e.g., 'documentDetail'), or URL path as fallback
211
+ - **NotFound integration** - When catch-all route (`'*'`) matches, router automatically injects:
212
+ - `attemptedRoute` - The URL that was not found
213
+ - `attemptedQuerystring` - Query string from the attempted route
214
+ - `referrer` - Complete previous route information (see structure above)
215
+ - **Zero programmer effort** - Works automatically once configured
216
+ - **Safe "Go Back" implementation** - Using `push()` with explicit URL instead of `history.back()`
217
+ - Safer than `history.back()` which breaks with `replace()` navigation
218
+ - Allows conditional logic (e.g., don't go back to unauthorized routes)
219
+ - Supports custom fallback destinations
220
+ - **Works correctly with both `push()` and `replace()` navigation**
221
+ - **Timing behavior** - On first navigation after referrer is updated, component $effects may run twice:
222
+ - First run: location changes but referrer not yet injected (sees undefined)
223
+ - Second run: navigationContext updates with referrer (sees correct value)
224
+ - This is expected Svelte 5 behavior and doesn't affect functionality
225
+ - Subsequent navigations are smooth with single $effect run
226
+ - UI always renders correctly on the second run
227
+ - **Example usage in NotFound component**:
228
+ ```javascript
229
+ const navContext = $derived(navigationContext())
230
+ const referrer = $derived(navContext?.referrer)
231
+ const canGoBack = $derived(referrer?.location && referrer.location !== '/')
232
+
233
+ function goBack() {
234
+ const returnPath = referrer?.location || '/'
235
+ const returnQuery = referrer?.querystring
236
+ const returnUrl = returnQuery ? `${returnPath}?${returnQuery}` : returnPath
237
+ push(returnUrl)
238
+ }
239
+ ```
240
+ - **Example usage in protected routes** (avoid going back to authorization failures):
241
+ ```javascript
242
+ const navContext = $derived(navigationContext())
243
+ const referrer = $derived(navContext?.referrer)
244
+ const returnPath = $derived(referrer?.location || navContext?.returnTo || '/')
245
+ const returnQuery = $derived(referrer?.querystring || navContext?.returnQuery)
246
+
247
+ // Referrer tracks last SUCCESSFULLY loaded route, not attempted routes that failed auth
248
+ ```
249
+ - Example implementations:
250
+ - `example/src/routes/NotFound.svelte` - 404 page with "Go Back"
251
+ - `example/src/routes/Unauthorized.svelte` - Authorization failure with safe "Go Back"
252
+ - `example/src/routes/User.svelte` - Regular page with referrer-based navigation
253
+ - `example/src/routes/ReferrerDemo.svelte` - Comprehensive demo with console logging
254
+
255
+ - **`setNavigationContext()` function** - Exported for advanced use cases
256
+ - Allows manual setting of navigation context
257
+ - Used internally by router for referrer auto-injection
258
+ - Example: `setNavigationContext({ customData: 123 })`
259
+
260
+ ### Fixed
261
+
262
+ #### Named Routes Navigation
263
+ - **Fixed `push()` and `replace()` with single-argument named routes** - Resolved "Invalid parameter location" error
264
+ - Issue: Calling `push('routeName')` with a single named route argument threw error
265
+ - Root cause: Legacy signature detection treated non-slash strings as href paths instead of named routes
266
+ - Solution: Added check in legacy signature branch - strings without leading `/` or `#/` are treated as named routes
267
+ - Now works: `push('about')`, `push('userProfile')`, `replace('home')`
268
+ - Multi-param signature still required for routes with params: `push('userProfile', {id: 123})`
269
+
270
+ #### Hierarchical Routes & Breadcrumb System
271
+ - **Fixed infinite loop in route metadata updates** - Resolved state comparison issues causing Router to re-render continuously
272
+ - Issue: Router's `$effect()` was triggering on every update due to `routeContext` object reference changes
273
+ - Root cause: `updateRouteMetadata()` created new objects every call, even when values didn't change
274
+ - Solution 1: Added `lastAssignedContext` reference tracking to only update when context reference actually changes
275
+ - Solution 2: Implemented route key tracking (`${location}|${querystring}|${JSON.stringify(params)}`) to detect real route changes vs metadata updates
276
+ - Fixed `$state` proxy comparison warnings by using `$state.snapshot()` for value comparisons
277
+
278
+ - **Breadcrumb cache system for preserving manual updates** - Child route navigation no longer resets dynamic breadcrumbs to "Loading..."
279
+ - Issue: Navigating from `/documents/1` to `/documents/1/logs` reset "Document 1" breadcrumb back to "Loading..."
280
+ - Root cause: Router composed fresh breadcrumbs on every route change, losing manual `updateBreadcrumb()` calls
281
+ - Solution: Implemented breadcrumb cache (`updatedBreadcrumbsCache` Map) to preserve manual updates across child route navigation
282
+ - Cache intelligently clears only when navigating to different base routes (not child routes)
283
+ - `updateBreadcrumb(id, updates)` stores updates in cache, Router applies them during breadcrumb composition
284
+
285
+ - **Child component breadcrumb initialization pattern** - Direct child route reloads now show correct parent breadcrumbs
286
+ - Issue: Reloading `/documents/1/logs` directly showed "Loading..." for parent breadcrumb because parent component never mounted
287
+ - Pattern: Child components call `updateBreadcrumb()` in `onMount()` to update their parent's dynamic breadcrumbs
288
+ - Example: DocumentLogs component updates 'documentDetail' breadcrumb with actual document name on mount
289
+ - Works with breadcrumb cache to ensure updates persist across subsequent child navigation
290
+ - Applied to all child components in test apps: DocumentLogs, DocumentPermissions, DocumentHistory, AdminUserPermissions, AdminUserActivity
291
+
292
+ ### Documentation
293
+ - **Added comprehensive documentation showcase** - New SvelteKit-based documentation site
294
+ - Built with @keenmate/svelte-docs for consistent styling and components
295
+ - Complete API reference with all functions, parameters, and return types
296
+ - Feature guides: Routing Modes, Route Configuration, Parameters, Guards, Named Routes, Programmatic Navigation, Querystring, Filters, Permissions, Multi-Zone Routing, Hierarchical Routes, Nested Routes
297
+ - Interactive code examples with syntax highlighting
298
+ - Live demo apps for both hash and history modes
299
+ - Deployed at https://svelte-spa-router.keenmate.dev
300
+ - Demo apps: https://history.svelte-spa-router.keenmate.dev and https://hash.svelte-spa-router.keenmate.dev
301
+ - **Added Multi-Zone Routing documentation** - Comprehensive guide for multi-zone layouts
302
+ - New showcase page at `/features/multi-zone`
303
+ - Visual diagram showing zone layout structure
304
+ - Complete examples: route configuration, layout setup, zone components
305
+ - Covers async loading, route parameters, guards, permissions, and metadata in zones
306
+ - Use cases: admin dashboards, email clients, music players, document editors
307
+ - Best practices and responsive layout patterns
308
+ - **Updated CLAUDE.md** - Added comprehensive section on tree/nested route structure
309
+ - **Added examples** - Tree structure examples in main example app
310
+
311
+ ## [5.0.0-rc08] - 2025-10-26
312
+
313
+ ### Breaking Changes
314
+ - **BREAKING: Renamed `params` to `routeParams`** for clarity
315
+ - Component prop: `let { params } = $props()` `let { routeParams } = $props()`
316
+ - Function call: `params()` `routeParams()`
317
+ - This makes it immediately clear these are route parameters from URL patterns
318
+ - Update all route components to use `routeParams` instead of `params`
319
+ - TypeScript: `params<T>()` → `routeParams<T>()`
320
+
321
+ ## [5.0.0-rc07] - 2025-10-26
322
+
323
+ ### Fixed
324
+ - **Critical: Tree-shaking issue in production builds** - Added `sideEffects` field to package.json to prevent Vite from incorrectly tree-shaking `.svelte` and `.svelte.js` files during production builds
325
+ - Fixes "link is not defined" and similar errors in production builds with npm package
326
+ - `.svelte.js` files contain Svelte 5 runes (`$state`, `$derived`, `$effect`) at module level which have side effects and must not be tree-shaken
327
+ - Issue only affected production builds, not development mode
328
+
329
+ ## [5.0.0-rc06] - 2025-10-26
330
+
331
+ ### Added
332
+
333
+ #### Multi-Parameter Navigation & Strict Parameter Replacement
334
+ - **Multi-parameter signature for `push()` and `replace()`** - Natural function call style
335
+ - `push(route, routeParams, queryString, navigationContext)`
336
+ - `replace(route, routeParams, queryString, navigationContext)`
337
+ - Route resolution: Starts with `/` = exact path, otherwise = named route lookup
338
+ - Examples: `push('userProfile', { userId: 123 }, { tab: 'settings' })`
339
+ - Backward compatible with all existing formats (string, array, object)
340
+
341
+ - **4-element array support** - Navigation context in arrays
342
+ - `push(['route', params, query, navigationContext])`
343
+ - `link={['route', params, query, navigationContext]}`
344
+ - Example: `<a use:link={['bookDetail', {bookId: 123}, {tab: 'reviews'}, {source: 'list'}]}>`
345
+
346
+ - **Strict parameter replacement with placeholder**
347
+ - `setParamReplacementPlaceholder(value)` - Configure placeholder for missing params (default: 'N-A')
348
+ - Missing params replaced with placeholder instead of being removed
349
+ - Predictable URLs: `/users/:userId/:section` with missing section → `/users/123/N-A`
350
+ - Triggers `onNotFound` callback for error tracking
351
+
352
+ #### Resource-Based Authorization
353
+ - **`authorizationCallback` parameter for `createProtectedRoute()`** - Combine role + resource checks
354
+ - Supports both role-based (permissions) and resource-based (authorizationCallback) authorization
355
+ - Conditions execute in order: permissions first (fast), then authorizationCallback (API call)
356
+ - Example: Check if user has 'editor' role, then check if they can access specific document
357
+ - Perfect for document access, resource ownership, dynamic permissions
358
+ - Callback receives full route detail: `{ route, location, params, query, routeContext, navigationContext }`
359
+
360
+ #### Global Error Handler System
361
+ - **Production-ready global error handler** for catching and recovering from unhandled errors
362
+ - `configureGlobalErrorHandler()` - Configure error handling behavior in `main.js`
363
+ - `GlobalErrorHandler` component - Wraps your app and catches all errors
364
+ - `ErrorDisplay` component - Beautiful default error UI with recovery options
365
+ - **Recovery Strategies**: `navigateSafe`, `restart`, `showError`, `custom`
366
+ - **Loop Prevention**: SessionStorage-based restart tracking prevents infinite reload loops
367
+ - **Custom Callbacks**: `onError` for logging/monitoring, `onRecover` for custom recovery logic
368
+ - **Helper Functions**: `restart()`, `navigate()`, `showError()`, `canRestart()`, `getRestartCount()`
369
+ - **Error Filtering**: Ignore known non-critical errors (ResizeObserver, etc.)
370
+ - **UI Options**: Toast notifications, full-page error component, or custom error component
371
+ - **TypeScript Support**: Full type definitions for all APIs
372
+ - **Example Integration**: Working demo with Sentry integration example
373
+
374
+ #### 404 Not Found Tracking
375
+ - **`onNotFound` callback on Router component** - Track 404s for analytics/monitoring
376
+ - Fires when catch-all route (`'*'`) matches (user sees 404 page)
377
+ - Fires when no route matches at all (no 404 page defined)
378
+ - Perfect for logging to Sentry, Google Analytics, or other monitoring services
379
+ - Event detail includes `{ location, querystring }`
380
+ - Example: `<Router {routes} onNotFound={(e) => Sentry.captureMessage('404', { extra: e.detail })} />`
381
+
382
+ #### Convenient Route Creation API
383
+ - **New `createRoute()` and `createRouteDefinition()` functions** for easier route configuration
384
+ - `createRoute()` - Returns already wrapped component (most convenient, no `wrap()` needed)
385
+ - `createRouteDefinition()` - Returns route definition for use with `wrap()` (advanced use)
386
+ - Consistent API pattern matching `createProtectedRoute()` / `createProtectedRouteDefinition()`
387
+ - Support for `title` and `breadcrumbs` metadata directly in route options
388
+ - Automatic detection of sync vs async components
389
+
390
+ - **Enhanced Permission System**
391
+ - `createProtectedRoute()` - Now returns already wrapped component (breaking change from definition-only)
392
+ - `createProtectedRouteDefinition()` - New function that returns definition (replaces old `createProtectedRoute()` behavior)
393
+ - Maintains backward compatibility for existing `wrap(createProtectedRoute(...))` usage
394
+
395
+ - **Route Metadata Support**
396
+ - `title` - Set page title for routes
397
+ - `breadcrumbs` - Define breadcrumb trail with `{ label, path? }` structure
398
+ - Metadata stored in `routeContext` object, accessible in route events and components
399
+
400
+ #### Dynamic Metadata & Loading Control
401
+ - **Reactive Metadata Helpers**: `helpers/route-metadata.svelte.js`
402
+ - `routeTitle()` - Get current route title reactively
403
+ - `routeBreadcrumbs()` - Get current breadcrumb trail reactively
404
+ - `routeContext()` - Get full route context reactively
405
+ - `updateRouteMetadata(routeContext)` - Update metadata after data loads (e.g., change title from "Document" to "Invoice.pdf")
406
+ - **`updateTitle(title)` - Update just the title** (simpler than updateRouteMetadata)
407
+ - **`updateBreadcrumb(id, updates)` - Partial breadcrumb updates** (update specific segments by ID)
408
+ - Automatically updated by Router on route changes
409
+
410
+ - **Partial Breadcrumb Updates** (NEW!)
411
+ - Add `id` property to breadcrumb items in route config: `{ id: 'docDetail', label: 'Loading...', path: '/doc/:id' }`
412
+ - Update specific breadcrumbs after data loads: `updateBreadcrumb('docDetail', { label: 'Invoice.pdf', path: '/doc/123' })`
413
+ - **No need to replace the entire breadcrumbs array** - only update what changes!
414
+ - Perfect for nested paths: `/documents/:id/logs/:logId` where each segment needs dynamic data
415
+ - Static segments (Home, Documents, etc.) stay unchanged
416
+
417
+ - **Flexible Loading Control** with `shouldDisplayLoadingOnRouteLoad` flag
418
+ - **Pattern 1 (Router-managed with loadingComponent)**: Set `shouldDisplayLoadingOnRouteLoad: true` - Router keeps loading component visible until component calls `hideLoading()`
419
+ - **Pattern 2 (Component-managed)**: Default behavior - component handles its own loading state
420
+ - **Pattern 3 (Global overlay)**: User-defined global loading UI in App.svelte that reacts to `routeIsLoading()`
421
+ - `showLoading()` - Manually show loading state (triggers global overlay if defined)
422
+ - `hideLoading()` - Component signals data is loaded (hides loading component/overlay)
423
+ - `routeIsLoading()` - Check if route is currently loading (reactive state)
424
+ - Perfect for routes that fetch data and need dynamic titles/breadcrumbs
425
+ - Supports multi-zone layouts (toolpanel + content) with different loading UIs per zone
426
+
427
+ **Use cases**:
428
+ - Document detail page: Show "Document" while loading, then "Invoice template.pdf" after data loads
429
+ - User profile: Show "User Profile" while loading, then "John Doe" after data loads
430
+ - Product page: Show "Product" while loading, then "iPhone 15 Pro" after data loads
431
+ - Nested paths: `/documents/:id/logs` where both document name and "Logs" need to be in breadcrumbs
432
+
433
+ #### Named Routes Enhancement
434
+ - **Array/Object Syntax for Navigation**: `push()` and `replace()` functions now support the same convenient array/object syntax as the `link` action
435
+ - Array format: `push(['userProfile', { userId: 123 }, { tab: 'settings' }])`
436
+ - Object format: `push({ route: 'userProfile', params: { userId: 123 }, query: { tab: 'settings' } })`
437
+ - String format (legacy): `push('/about')` - still fully supported for backward compatibility
438
+ - Eliminates the need to manually call `buildUrl()` for programmatic navigation
439
+ - See `example-history/src/routes/LinksDemo.svelte` for interactive demos
440
+
441
+ ### Added - Querystring & Filter System
442
+
443
+ #### Querystring Helpers
444
+ - **Shared Reactive State**: `helpers/querystring.svelte.js`
445
+ - `configureQuerystring(options)` - Configure querystring parsing for entire app
446
+ - `query<T>()` - Get reactive parsed querystring with TypeScript generics
447
+ - Auto-detection of array formats (repeat vs comma-separated)
448
+ - Configure once, use everywhere pattern
449
+
450
+ - **Querystring Utilities**: `helpers/querystring-helpers.svelte.js`
451
+ - `parseQuerystring(qs, options)` - Parse querystring with array format support
452
+ - `stringifyQuerystring(obj, options)` - Convert object to querystring
453
+ - `getParsedQuerystring(options)` - Get parsed querystring (non-reactive)
454
+ - `updateQuerystring(updates, options)` - Update URL querystring (partial or full)
455
+ - `createQuerystringHelpers(parser, stringifier)` - Custom parser support
456
+
457
+ - **Array Format Support**:
458
+ - `'auto'` (default) - Auto-detects both repeat and comma formats
459
+ - `'repeat'` - `?tags=foo&tags=bar` (multiple parameters with same key)
460
+ - `'comma'` - `?tags=foo,bar,baz` (comma-separated values)
461
+
462
+ #### Filter System
463
+ - **Flexible Filters**: `helpers/filters.svelte.js`
464
+ - `configureFilters(options)` - Configure filter mode and parsing
465
+ - `filters<T>()` - Get reactive parsed filters with TypeScript generics
466
+ - `updateFilters<T>(updates, options)` - Update filters with type safety
467
+ - `getFiltersConfig()` - Get current filter configuration
468
+
469
+ - **Dual Mode Support**:
470
+ - **Flat Mode** (default): Each filter as separate query parameter
471
+ - Example: `?search=java&category=books&status=active`
472
+ - **Structured Mode**: Single parameter with custom syntax
473
+ - Example: `?$filter=search eq 'java' AND category eq 'books'`
474
+ - Supports custom parse/stringify functions for OData, Microsoft Graph API, etc.
475
+
476
+ #### TypeScript Support
477
+ - Full generic support for type-safe parameter access:
478
+ - `params<T>()` - Route parameters with intellisense
479
+ - `query<T>()` - Query parameters with intellisense
480
+ - `filters<T>()` - Filter parameters with intellisense
481
+ - `updateFilters<T>(updates, options)` - Type-safe filter updates
482
+
483
+ #### Value Handling
484
+ - **Filters**:
485
+ - `undefined` - Always removes the parameter
486
+ - `null` - Keeps parameter with empty value
487
+ - **Querystring**:
488
+ - `undefined` - Always removes the parameter
489
+ - `null` - Controlled by `dropNull` option (default: true removes it)
490
+ - Empty string - Controlled by `dropEmpty` option (default: false keeps it)
491
+
492
+ #### Example Applications
493
+ - `example-history/src/routes/QuerystringDemo.svelte` - Interactive querystring demo
494
+ - `example-history/src/routes/FiltersDemo.svelte` - Filter system with products demo
495
+ - `example-history/src/routes/RouteDataDemo.svelte` - Route data extraction examples
496
+
497
+ ### Fixed
498
+
499
+ #### Router Core Fixes
500
+ - **Router Initialization**: Fixed location state initialization to be reactive to config changes
501
+ - Issue: Direct URL access (e.g., `http://localhost:5050/querystring-demo`) showed homepage
502
+ - Solution: Changed from `$state(getLocation())` to `$derived.by()` to react to config changes
503
+ - Now correctly reads `setHashRoutingEnabled()` before initializing location state
504
+
505
+ #### Loading State Fixes
506
+ - **Component Double Mounting**: Fixed race condition causing components to mount twice
507
+ - Issue: Router template had component in two separate `{:else if}` blocks - one hidden, one visible
508
+ - When `isWaitingForData` changed, Svelte unmounted from first block and remounted in second
509
+ - Resulted in `onMount()` running twice, causing duplicate data fetches and delays
510
+ - Solution: Refactored template to keep component in single block with `style:display` toggle
511
+ - Component now mounts once and visibility is controlled via CSS
512
+
513
+ - **Component Params Not Available on Mount**: Fixed params being empty when component loads
514
+ - Issue: Router set `isWaitingForData = true` and waited for `hideLoading()` BEFORE setting `componentParams`
515
+ - Component mounted without params, causing "params not ready" errors
516
+ - Solution: Moved `componentParams`, `componentProps`, `componentrouteContext` assignment BEFORE waiting logic
517
+ - Component now has access to params immediately on mount
518
+
519
+ - **Global vs Route Loading Conflict**: Fixed overlapping loading indicators
520
+ - Issue: Both global loader and route-specific loader showed simultaneously
521
+ - Solution: Added `shouldShowGlobalLoading()` helper that only returns true when route doesn't have custom loading
522
+ - Added `hasCustomLoadingComponent` flag set by `startRouteLoading(hasCustomComponent)`
523
+ - Global loader now only shows for routes without custom loading components
524
+
525
+ - **Manual Loading Not Showing**: Fixed `showLoading()` not displaying global loader
526
+ - Issue: `showLoading()` set `isRouteLoading = true` but didn't reset `hasCustomLoadingComponent` flag
527
+ - If current route had custom loading, `shouldShowGlobalLoading()` returned false
528
+ - Solution: Made `showLoading()` also set `hasCustomLoadingComponent = false`
529
+ - Manual loading triggers now correctly show global loader
530
+
531
+ #### Authorization & Navigation Context Fixes
532
+ - **"Go Back" After Unauthorized**: Fixed return navigation after authorization failures
533
+ - Issue: Unauthorized page's "Return to Home" button always went to home instead of previous location
534
+ - Root cause: Using `push('/unauthorized', { data })` was ambiguous - interpreted as routeParams instead of navigationContext
535
+ - Solution: Changed all unauthorized redirects to use explicit 4-parameter signature: `push(route, {}, {}, navContext)`
536
+ - Added `returnTo` and `returnQuery` to navigation context for proper "Go Back" functionality
537
+ - Unauthorized page now displays "Go Back" button when `returnTo` is available
538
+
539
+ - **Document Authorization Navigation**: Fixed document access redirects
540
+ - Updated `authorizationCallback` to pass `returnTo` and `returnQuery` via navigationContext
541
+ - Fixed "View Document" buttons to pass navigation context for proper back navigation
542
+ - All authorization demo flows now preserve return path for better UX
543
+
544
+ #### Protected Route Fixes
545
+ - **Async Component Params Empty**: Fixed params not being passed to protected routes
546
+ - Issue: `createProtectedRouteDefinition` was using `asyncComponent: component` which caused wrap() to double-wrap async imports
547
+ - Solution: Kept `asyncComponent: component` which properly passes async imports to wrap()
548
+ - Protected routes now receive params correctly when using `() => import('./Component.svelte')`
549
+
550
+ #### Example Application Fixes
551
+ - **Missing Routes**: Added missing example routes
552
+ - Added `/about` route (About component)
553
+ - Added `/user/:first/:last?` route (User component with optional last name)
554
+ - Links in LinksDemo now work correctly
555
+
556
+ - **Navigation Context Demo**: Fixed incorrect route paths and push signatures
557
+ - Changed `/context-demo` references to correct `/navigation-context-demo` route
558
+ - Fixed `backToList()` function to navigate to correct route
559
+ - Updated code examples to show proper 4-parameter signature
560
+ - Fixed "View" and "Edit" buttons to use correct navigation context syntax
561
+
562
+ - **HTML Entity Escaping**: Fixed Svelte parse errors
563
+ - Changed unescaped `{}` in code examples to HTML entities `&#123;&#125;`
564
+ - Prevents Svelte from treating them as reactive expressions
565
+
566
+ ### Documentation
567
+ - Updated `README.md` with comprehensive querystring and filter system documentation
568
+ - Added TypeScript generic examples throughout
569
+ - Added Quick Reference section with all available imports
570
+ - Updated main features list to highlight TypeScript and URL helpers
571
+
572
+ ## [1.0.0] - 2024
573
+
574
+ ### Package Information
575
+
576
+ - **Package Name:** @keenmate/svelte-spa-router
577
+ - **Publisher:** KeenMate (https://keenmate.com)
578
+ - **Repository:** https://github.com/keenmate/svelte-spa-router
579
+
580
+ ### Added - Svelte 5 Conversion
581
+
582
+ #### Core Router
583
+ - Complete rewrite using Svelte 5 runes (`$state`, `$props`, `$effect`)
584
+ - Reactive location tracking with rune-based state management
585
+ - Event handlers converted from `on:` directives to callback props
586
+ - Maintained full backward compatibility in route definition syntax
587
+
588
+ #### Dual-Mode Routing System
589
+ - **Hash Mode (Default)**: Traditional hash-based routing (`#/path`)
590
+ - No configuration required
591
+ - Works everywhere, including `file://` protocol
592
+ - Perfect for static hosting
593
+
594
+ - **History Mode (New)**: Clean URL routing (`/path`)
595
+ - Uses History API for clean URLs without hash
596
+ - Supports modifier keys (Ctrl+Click to open in new tab)
597
+ - Respects `target` attribute on links
598
+ - Requires server configuration to serve index.html for all routes
599
+
600
+ #### Configuration Functions
601
+ - `setHashRoutingEnabled(boolean)` - Toggle between hash and history mode
602
+ - `setBasePath(string)` - Set base path for history mode
603
+ - `getHashRoutingEnabled()` - Get current routing mode
604
+ - `getBasePath()` - Get current base path
605
+
606
+ #### Permission System (New)
607
+ - `helpers/permissions.svelte.js` - Comprehensive RBAC system
608
+ - `configurePermissions(config)` - Setup permission checking logic
609
+ - `createPermissionCondition(requirements)` - Create route guards
610
+ - `createProtectedRoute(options)` - Helper for protected routes
611
+ - `hasPermission(requirements)` - UI-level permission checking
612
+ - Support for `any` (OR) and `all` (AND) permission logic
613
+ - Flexible integration with existing route guards
614
+
615
+ #### URL Helpers
616
+ - `helpers/url-helpers.svelte.js` - Path manipulation utilities
617
+ - `joinPaths(...paths)` - Intelligent path joining with slash handling
618
+
619
+ #### Enhanced Active Link Detection
620
+ - Updated to work with both hash and history modes
621
+ - Automatic CSS class application on matching routes
622
+ - Pattern matching support
623
+
624
+ ### Changed
625
+
626
+ #### Naming Changes (Non-Breaking in Usage, Breaking for Type Imports)
627
+ - **`userData` → `routeContext`**: Renamed for clarity
628
+ - Refers to static route-level configuration data
629
+ - `wrap({ routeContext: { ... } })`
630
+ - `routeContext()` helper function
631
+ - All TypeScript interfaces updated
632
+
633
+ - **`context` → `navigationContext`**: Renamed for clarity
634
+ - Refers to dynamic data passed during navigation (doesn't appear in URL)
635
+ - `push('/path', { orderId: 123 })` - second parameter is navigationContext
636
+ - `navigationContext()` accessor function
637
+ - `wrap({ navigationContext: { ... } })`
638
+
639
+ These changes clarify the distinction between:
640
+ - **routeContext**: Static metadata defined in route configuration
641
+ - **navigationContext**: Dynamic data passed at navigation time (WinForms-like experience)
642
+
643
+ #### API Changes (Breaking)
644
+ - **Stores → Functions**:
645
+ - `$location` → `location()`
646
+ - `$querystring` `querystring()`
647
+ - `$params` `params()`
648
+ - `$loc` `loc()`
649
+
650
+ - **Events Props**:
651
+ - `on:routeLoading` → `onrouteLoading`
652
+ - `on:routeLoaded` → `onrouteLoaded`
653
+ - `on:conditionsFailed` → `onconditionsFailed`
654
+ - `on:routeEvent` `onrouteEvent`
655
+
656
+ - **Component Props**:
657
+ - `export let params` → `let { params } = $props()`
658
+
659
+ - **Reactive Subscriptions**:
660
+ - `.subscribe()` → `$effect(() => { ... })`
661
+
662
+ ### Unchanged (Backward Compatible)
663
+
664
+ - ✅ Route definition syntax (same object/Map structure)
665
+ - ✅ `push()`, `pop()`, `replace()` navigation functions
666
+ - ✅ `link` action for anchor tags
667
+ - ✅ `active` action for link highlighting
668
+ - ✅ `wrap()` utility for async components and conditions
669
+ - ✅ Route guards/pre-conditions
670
+ - ✅ Dynamic imports and code-splitting
671
+ - ✅ Nested routers with prefix
672
+ - ✅ Scroll restoration
673
+ - ✅ Loading components
674
+
675
+ ### Documentation
676
+
677
+ #### New Files
678
+ - `CONTEXT.md` - Comprehensive project overview
679
+ - `CHANGELOG.md` - This file
680
+ - `DEVELOPMENT.md` - Development workflow guide
681
+ - `MIGRATION.md` - Detailed v4 → v5 migration guide
682
+ - `HASHLESS_MERGE_PLAN.md` - Technical implementation notes
683
+
684
+ #### Updated Files
685
+ - `README.md` - Added routing modes and permission system documentation
686
+ - `package.json` - Updated exports for new modules
687
+
688
+ ### Examples
689
+
690
+ - `example/` - Hash mode example (updated for Svelte 5)
691
+ - `example-history/` - History mode example (new)
692
+
693
+ ### Infrastructure
694
+
695
+ - ESLint configuration for Svelte 5
696
+ - Makefile for cross-platform development commands
697
+ - Package exports for all modules
698
+
699
+ ## Migration Guide
700
+
701
+ See [MIGRATION.md](./MIGRATION.md) for detailed instructions on upgrading from v4 to v5.
702
+
703
+ ### Quick Migration Checklist
704
+
705
+ 1. ✅ Update imports: `svelte-spa-router` → `@keenmate/svelte-spa-router`
706
+ 2. ✅ Change stores to functions: `$location` → `location()`
707
+ 3. ✅ Update event handlers: `on:routeLoaded` → `onrouteLoaded`
708
+ 4. ✅ Convert component props: `export let params` → `let { params } = $props()`
709
+ 5. ✅ Replace subscriptions with `$effect`
710
+ 6. ✅ Test all routes and navigation
711
+
712
+ ### Optional: Enable History Mode
713
+
714
+ ```javascript
715
+ // main.js
716
+ import { setHashRoutingEnabled, setBasePath } from '@keenmate/svelte-spa-router/utils'
717
+
718
+ setHashRoutingEnabled(false)
719
+ setBasePath('/')
720
+
721
+ mount(App, { target: document.body })
722
+ ```
723
+
724
+ ### Optional: Add Permission System
725
+
726
+ ```javascript
727
+ // main.js
728
+ import { configurePermissions } from '@keenmate/svelte-spa-router/helpers/permissions'
729
+
730
+ configurePermissions({
731
+ checkPermissions: (user, requirements) => {
732
+ // Your permission logic
733
+ },
734
+ getCurrentUser: () => getCurrentUser(),
735
+ onUnauthorized: (detail) => push('/unauthorized')
736
+ })
737
+ ```
738
+
739
+ ## Browser Compatibility
740
+
741
+ - **Svelte 5**: All modern browsers (Chrome, Firefox, Safari, Edge)
742
+ - **Hash Mode**: All browsers including IE10+
743
+ - **History Mode**: All browsers with History API support (IE10+)
744
+ - **Permissions**: All modern browsers
745
+
746
+ ## Dependencies
747
+
748
+ ### Runtime
749
+ - `regexparam@2.0.2` - Route pattern matching
750
+
751
+ ### Peer Dependencies
752
+ - `svelte@^5.0.0` - Required
753
+
754
+ ### Dev Dependencies
755
+ - `eslint@^9.0.0` - Code linting
756
+
757
+ ## Credits
758
+
759
+ - **Publisher:** KeenMate (https://keenmate.com)
760
+ - **Original svelte-spa-router:** Alessandro Segala (@ItalyPaleAle)
761
+ - **Svelte 5 Conversion:** KeenMate team
762
+ - **History Mode:** Adapted from svelte-spa-router-hashless
763
+ - **Permission System:** KeenMate team
764
+
765
+ ## License
766
+
767
+ MIT License - See [LICENSE.md](./LICENSE.md)