Skip to content

v3.0.0

Choose a tag to compare

@avdn-dev avdn-dev released this 09 Mar 00:27
· 242 commits to main since this release

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 now package (was public)
  • Router.deactivate() is now package (was public)
  • Router.deepLinkOpen(to:) is now package (was public)
  • Router.childRouter() (modal variant) is now package (was public)
  • Router.deepLinkHandler setter is now package(set) (was public); use configureDeepLinks(scheme:parsers:) instead
  • ViewModelFactory.init(routed:) is now package (was public); use the typed .routed { } static method instead
  • @ViewModel without @Observable is now an error (was a warning) and skips all code generation
  • Generated Stub and Spy classes are now final class (was class)

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

  • @LazyViewModels no longer emits a misleading missingArguments diagnostic when all arguments are individually malformed — per-argument diagnostics are sufficient
  • VISORDiagnostic.diagnosticID replaced fragile String(describing:) reflection with an explicit switch, matching the pattern used by TestDoubleDiagnostic
  • Removed Foundation import from the macro plugin (CodeGenHelpers.swift), reducing the plugin's dependency surface
  • Added reserveCapacity for allObserveMethodNames in ViewModelMacro to avoid reallocations with many @Bound/@Reaction properties
  • buildMethodSignature now uses single-pass string interpolation instead of repeated += concatenation
  • Added doc comments to all Loadable computed accessors (value, isLoading, isEmpty, isError, error)
  • URL.deepLinkComponents remains public for use in custom DeepLinkParser closures