Repository navigation
v3.0.0
What's New
Stricter macro diagnostics
@ViewModel now errors when @Observable is missing, instead of warning. Previously, omitting @Observable would generate observation code that compiled but silently failed at runtime — withObservationTracking requires @Observable to function. The macro now emits an error and skips code generation entirely.
@LazyViewModel and @LazyViewModels now return early when the required content property is missing, instead of generating a body that references a nonexistent content member and producing cascade compiler errors.
Multi-component @Bound key path validation
@Bound now rejects multi-component key paths like @Bound(\MyVM.service.nested) with a clear diagnostic. Previously, only the first component was extracted — the macro would silently generate self.service.<propertyName> instead of the intended self.service.nested.<propertyName>, producing wrong observation code at runtime.
Generated test doubles are final
@Stubbable and @Spyable now emit final class for generated Stub and Spy types, preventing unintended subclassing of test doubles.
Tighter public API surface
Internal plumbing methods on Router and ViewModelFactory that were previously public are now scoped to package, reducing the discoverable API to only what consumers need. Members referenced by macro-expanded code (ViewModelFactory.makeViewModel(router:), EnvironmentValues.router) remain public since they must be accessible from consumer targets.
Breaking Changes
Router.activate()is nowpackage(waspublic)Router.deactivate()is nowpackage(waspublic)Router.deepLinkOpen(to:)is nowpackage(waspublic)Router.childRouter()(modal variant) is nowpackage(waspublic)Router.deepLinkHandlersetter is nowpackage(set)(waspublic); useconfigureDeepLinks(scheme:parsers:)insteadViewModelFactory.init(routed:)is nowpackage(waspublic); use the typed.routed { }static method instead@ViewModelwithout@Observableis now an error (was a warning) and skips all code generation- Generated
StubandSpyclasses are nowfinal class(wasclass)
Migration guide
| Before | After |
|---|---|
router.activate() / router.deactivate() |
Managed automatically by NavigationContainer |
router.deepLinkHandler = { ... } |
router.configureDeepLinks(scheme:parsers:) |
ViewModelFactory(routed: { ... }) |
.routed { (router: Router<Scene>) in ... } |
factory.makeViewModel(router: r) |
Still public, but prefer @LazyViewModel / @LazyViewModels generated code |
Improvements
@LazyViewModelsno longer emits a misleadingmissingArgumentsdiagnostic when all arguments are individually malformed — per-argument diagnostics are sufficientVISORDiagnostic.diagnosticIDreplaced fragileString(describing:)reflection with an explicitswitch, matching the pattern used byTestDoubleDiagnostic- Removed
Foundationimport from the macro plugin (CodeGenHelpers.swift), reducing the plugin's dependency surface - Added
reserveCapacityforallObserveMethodNamesinViewModelMacroto avoid reallocations with many@Bound/@Reactionproperties buildMethodSignaturenow uses single-pass string interpolation instead of repeated+=concatenation- Added doc comments to all
Loadablecomputed accessors (value,isLoading,isEmpty,isError,error) URL.deepLinkComponentsremainspublicfor use in customDeepLinkParserclosures