@@ -100,7 +100,7 @@ That's it. All components subscribed to `CounterState` update automatically.
100100- [ Query System] ( #query-system ) | [ Async Helpers] ( #async-helpers ) | [ Optimistic Updates] ( #optimistic-updates )
101101
102102** Sync & Collaboration**
103- - [ Cross-Tab Sync] ( #cross-tab-sync ) | [ Server Sync] ( #server-sync-signalr ) | [ Persistence & DevTools] ( #state-persistence--redux-devtools-integration )
103+ - [ URL Sync ] ( #url-sync-experimental ) | [ Cross-Tab Sync] ( #cross-tab-sync ) | [ Server Sync] ( #server-sync-signalr ) | [ Persistence & DevTools] ( #state-persistence--redux-devtools-integration )
104104
105105** History & Advanced**
106106- [ Undo/Redo] ( #undoredo-history ) | [ Immer-Style Updates] ( #immer-style-updates ) | [ Redux-Style Actions] ( #redux-style-actions ) | [ Selectors] ( #selectors--performance-optimization )
@@ -414,6 +414,129 @@ await store.UpdateOptimistic<AppState, ServerItem>(
414414
415415---
416416
417+ ## URL Sync (Experimental)
418+
419+ ** Shareable URLs.** Sync component parameters with store state bidirectionally. Changes to URL update state, state changes update URL.
420+
421+ > ** ⚠️ Experimental Feature** - This API may change in future versions. ` [Experimental("EASB001")] ` attribute is applied. Phase 3 with attribute-based auto-sync is now available.
422+
423+ ### Attribute-Based Auto-Sync (Zero Boilerplate)
424+
425+ The simplest way to sync URLs - just add attributes:
426+
427+ ``` csharp
428+ @page " /products"
429+ @inherits UrlSyncStoreComponent < ProductsState >
430+
431+ [SupplyParameterFromQuery ]
432+ [AutoSyncWithQuery ]
433+ public int Page { get ; set ; } = 1 ; // Auto-maps to state.Page or state.CurrentPage
434+
435+ [SupplyParameterFromQuery ]
436+ [AutoSyncWithQuery (" q" )] // Custom query param name
437+ public string ? Search { get ; set ; }
438+
439+ [SupplyParameterFromQuery ]
440+ [AutoSyncWithQuery ]
441+ public bool OnSaleOnly { get ; set ; }
442+ ```
443+
444+ ** Convention-based matching:**
445+ - Exact match: ` Page ` → ` state.Page `
446+ - "Current" prefix: ` Page ` → ` state.CurrentPage `
447+ - "Value" suffix: ` Name ` → ` state.NameValue `
448+ - Case-insensitive: ` page ` → ` state.Page `
449+
450+ ### Manual Configuration (Advanced)
451+
452+ For more control, use manual configuration:
453+
454+ ``` csharp
455+ @page " /products"
456+ @inherits UrlSyncStoreComponent < ProductsState >
457+
458+ [SupplyParameterFromQuery ] public int Page { get ; set ; } = 1 ;
459+ [SupplyParameterFromQuery ] public string ? Search { get ; set ; }
460+ [SupplyParameterFromQuery ] public bool OnSaleOnly { get ; set ; }
461+
462+ protected override void ConfigureUrlSync (IUrlSyncBuilder < ProductsState > builder )
463+ {
464+ builder
465+ .SyncQueryParam (() => Page , s => s .CurrentPage )
466+ .SyncQueryParam (() => Search , s => s .SearchQuery )
467+ .SyncQueryParam (() => OnSaleOnly , s => s .FilterOnSale )
468+ .WithDebounce (TimeSpan .FromMilliseconds (500 ))
469+ .WithNavigationMode (UrlSyncNavigationMode .Replace );
470+ }
471+ ```
472+
473+ ### Hybrid Approach
474+
475+ Combine both for flexibility:
476+
477+ ``` csharp
478+ // Auto-sync simple properties
479+ [SupplyParameterFromQuery , AutoSyncWithQuery ]
480+ public int Page { get ; set ; }
481+
482+ // Manual config for advanced options
483+ protected override void ConfigureUrlSync (IUrlSyncBuilder < ProductsState > builder )
484+ {
485+ builder
486+ .WithDebounce (TimeSpan .FromMilliseconds (750 ))
487+ .ExcludeActions (" SERVER_SYNC" );
488+ }
489+ ```
490+
491+ ** What happens:**
492+ 1 . User visits ` /products?page=2&q=laptop&onSaleOnly=true `
493+ 2 . State updates: ` state with { CurrentPage = 2, SearchQuery = "laptop", FilterOnSale = true } `
494+ 3 . User clicks filter → state changes → URL updates
495+ 4 . User shares URL → recipient sees same filtered view
496+
497+ ### Supported Types
498+
499+ Only ** value types** and ** strings** are supported (prevents infinite loops):
500+ - Primitives: ` int ` , ` long ` , ` short ` , ` byte ` , ` float ` , ` double ` , ` decimal ` , ` bool `
501+ - Special: ` string ` , ` Guid ` , ` DateTime ` , ` DateTimeOffset ` , ` TimeSpan `
502+ - Enums: Any enum type
503+ - Nullable: All above types with ` ? `
504+
505+ ❌ ** Not supported** : Lists, arrays, complex objects (causes infinite update loops)
506+
507+ ### Options
508+
509+ ``` csharp
510+ builder
511+ .SyncQueryParam (() => Page , s => s .CurrentPage , " p" ) // Custom param name
512+ .WithDebounce (TimeSpan .FromMilliseconds (500 )) // Debounce rapid changes
513+ .WithNavigationMode (UrlSyncNavigationMode .Replace ) // Replace vs Push history
514+ .ExcludeActions (" SERVER_SYNC" , " CURSOR_UPDATE" ) // Don't sync these actions
515+ .OnConversionError ((param , ex ) => Logger .LogWarning (" Invalid {Param}: {Error}" , param , ex .Message ))
516+ .OnError (ex => Logger .LogError (ex , " URL sync error" ));
517+ ```
518+
519+ ### Navigation Modes
520+
521+ | Mode | Behavior | Use Case |
522+ | ------| ----------| ----------|
523+ | ** Replace** (default) | Replaces current history entry | High-frequency updates (sliders, filters) - prevents back button pollution |
524+ | ** Push** | Adds new history entry | Intentional navigation (wizard steps, tabs) |
525+
526+ ### Incompatibilities
527+
528+ ⚠️ ** Cannot use with:**
529+ - TabSync middleware (each tab needs independent URL)
530+ - Multiple ` UrlSyncStoreComponent ` per store (only one component can manage URL)
531+
532+ ### Security Notes
533+
534+ - URL parameters are ** user-controlled input** - always validate with ` IStateValidator<T> `
535+ - Sensitive data should ** never** be in URLs (use session storage or server state)
536+ - Maximum URL length: ~ 2000 chars (library warns at 1800)
537+
538+ ---
539+
417540## Undo/Redo History
418541
419542** Ctrl+Z for your app state.** Full history stack with memory limits and action grouping.
0 commit comments