Skip to content

3.0.0#147

Closed
0xLeif wants to merge 17 commits into
0xLeif:mainfrom
corvid-agent:main
Closed

3.0.0#147
0xLeif wants to merge 17 commits into
0xLeif:mainfrom
corvid-agent:main

Conversation

@0xLeif

@0xLeif 0xLeif commented Jun 9, 2026

Copy link
Copy Markdown
Owner

No description provided.

claude and others added 17 commits June 9, 2026 16:12
Introduce a SwiftData integration that follows AppState's existing
persistence-type conventions, fully gated behind `canImport(SwiftData)`
and `@available` (iOS 17 / macOS 14 / tvOS 17 / watchOS 10 / visionOS 1).

- ModelContainer as an AppState dependency, with `Application.modelContext(_:)`
  to access the shared main-actor `ModelContext` from non-view code, and a
  `modelContainer(_:)` registration convenience.
- `Application.ModelState<Model>` (conforms to `MutableApplicationState`):
  reads via a `FetchDescriptor`, with `insert`/`delete`/`save`/`reset` and a
  `value` get (fetch) / set (insert new + save) accessor.
- `@ModelState` property wrapper exposing `[Model]`, with the projected value
  surfacing `insert`/`delete`/`save`.
- Public factory/accessor helpers: `modelState(...)`, `Application.modelState(_:)`,
  and `Application.reset(modelState:)`.
- Unit tests (in-memory container) covering the dependency, CRUD through the
  application and property wrapper, reset, and fetch-descriptor ordering.
- Runnable example SwiftPM package (Examples/SwiftDataExample) wired into macOS
  CI via `swift build`/`swift run` as a smoke test.
- Documentation: usage-modelstate guide plus README and usage-overview updates.
Begin the v3 modernization. Raise the minimum deployment targets to
iOS 17 / watchOS 10 / macOS 14 / tvOS 17 / visionOS 1 and explicitly pin
`swiftLanguageModes: [.v6]`.

Because the new floors exactly match SwiftData's availability, the
`@available` annotations on the SwiftData integration are now redundant and
have been removed; the `#if canImport(SwiftData)` guards remain so Linux and
Windows (which have no SwiftData) still compile to nothing.
Add -Xswiftc -warnings-as-errors to the build and test steps on macOS,
Ubuntu, and Windows so any Swift 6 (or future-Swift) warning fails CI. This
establishes the zero-warning baseline for the v3 modernization.
…ature

Future-proof against upcoming Swift by enabling the ExistentialAny upcoming
feature on the AppState target and annotating every existential type with
'any' (the *Managing dependency protocols, Loggable, and Error). This keeps
the library warning-clean as ExistentialAny moves toward becoming the default.
Adopt @observable for `Application` while keeping `NSObject` (so the @objc
iCloud `didChangeExternally` hook is unchanged). State and dependency values
still live in the untracked `Cache`, so a private `changeAnchor` bridges cache
changes to Observation:

- `Application` is now `@Observable`; `lock`/`cache`/`bag` are `@ObservationIgnored`.
- `registerObservation()` (read by every property wrapper's getter) registers the
  current tracking scope; `notifyChange()` (public) bumps the anchor to update
  observers. The cache observer and `DependencySlice` setters call `notifyChange()`.
- Property wrappers (`AppState`, `Stored/File/Sync/Secure/ModelState`, `Slice`,
  `OptionalSlice`, `DependencySlice`) drop `@ObservedObject app` for a computed
  `app` and call `registerObservation()` when read. This also removes the
  per-wrapper Linux/Windows fork for the `app` reference.
- Removed `Application: ObservableObject`; the enclosing-instance subscript that
  supports ObservableObject host view models is retained, as is `ObservedDependency`.

Note: reactive view updates require verification on a real Apple target; CI here
covers compilation, unit tests, and warnings-as-errors only.
- SecureState's setter wrote through the Keychain (not the cache), so it called
  the old objectWillChange; route it through notifyChange() instead.
- Drop the now-orphaned 'ObjectWillChangePublisher == ObservableObjectPublisher'
  constraint on consume(object:), which relied on Application's removed
  ObservableObject conformance.
Document the v3 breaking changes (raised platform floors, strict Swift 6 /
ExistentialAny, the Observation/@observable migration and notifyChange(), and
the additive SwiftData ModelState feature) and link it from the README.
…ages

Localize the 3.0 documentation into every language the repo carries
(de, es, fr, hi, pt, ru, zh-CN):

- Add translated `usage-modelstate.md` and `upgrade-to-v3.md` to each language.
- Add the ModelState section to each `usage-overview.md`.
- Update each `usage-syncstate.md` custom-Application example to call
  `notifyChange()` instead of `objectWillChange.send()` (including English).
- Update each translated README's requirements to the new platform floors and
  add the SwiftData (ModelState) feature bullet plus the two new doc links.
Use withObservationTracking to assert that reading a property wrapper registers
an observation dependency and that mutating the underlying state fires onChange
(the same registerObservation()/notifyChange() path SwiftUI relies on), plus a
negative case. Apple-only, since the cache->anchor bridge is Apple-only.
…pecs)

Integrate CorvidLabs fledge and spec-sync into the project:

- fledge.toml: build/test/lint/example tasks; the lint task runs build+test with
  -warnings-as-errors and the example smoke test, mirroring CI.
- .specsync/ (v4.3.1): config, registry (application, property-wrappers,
  swiftdata), version.
- specs/application: updated to v3 (Observation/@observable, notifyChange,
  raised floors, Swift 6 + ExistentialAny) plus filled context/requirements/
  tasks/testing.
- specs/swiftdata: new module documenting the SwiftData ModelContainer
  dependency and ModelState.

(property-wrappers spec follows in a separate commit.)
Fill the property-wrappers spec and its context/requirements/tasks/testing:
all state, dependency, and slice/constant wrappers; the Observation-based
reactivity (computed app + registerObservation/notifyChange); @ModelState;
and the DynamicProperty + enclosing-instance subscript behavior.
- Use the unqualified log(...) inside the nested Application.ModelState struct,
  matching FileState/StoredState (the qualified Application.log is kept in the
  top-level @ModelState wrapper, consistent with the other wrappers).
- Correct the @DependencySlice 'app' doc comment to reference a dependency.
Act on the maintainer review of the SwiftData / v3 work:

- ModelState semantics (0xLeif#7/0xLeif#8/0xLeif#9): `value` is now a read-only `models` (no more
  insert-only "set"); the destructive `reset()` is an explicit `deleteAll()` and
  `ModelState` no longer conforms to `MutableApplicationState` (so it isn't forced
  into value/reset semantics that don't fit). Removed `Application.reset(modelState:)`.
  Documented loudly that `models` performs a live fetch on every read.
- `@ModelState` wrapper is read-only; mutate through the projected value. Dropped
  the now-unneeded enclosing-instance subscript and the Combine import.
- Observation isolation (0xLeif#4/0xLeif#5): `changeAnchor`, `registerObservation()`, and
  `notifyChange()` are `@MainActor`; the cache observer routes through
  `MainActor.assumeIsolated`. Documented why the discarded anchor read is load-bearing.
- Scoped strict builds: `-warnings-as-errors` moved out of the CI command and into
  env-gated `unsafeFlags` on our targets only (APPSTATE_STRICT), so dependencies and
  downstream consumers are unaffected.
- Docs: replaced `try!` ModelContainer examples with explicit do/catch.
- Updated tests and the example for the new API.
The @mainactor isolation attempted for the anchor is incompatible with the
cross-thread Combine cache observer (a nonisolated sink cannot hop to a
@mainactor method without 'sending' non-Sendable self — Swift 6 region
isolation error on macOS). Removing the central observer instead would risk
silently dropping reactivity for some state types, which CI can't detect.

So notifyChange()/registerObservation()/changeAnchor stay nonisolated (the
proven, ObservationTests-covered design) and the thread-safety invariant is now
documented explicitly: all mutations funnel through notifyChange(), invoked
only from main-actor setters and the synchronous cache observer running during
them; reads are main-actor; the @observable registrar is itself synchronized.
Update usage-modelstate.md in all 8 languages and the SwiftData / property-wrappers
specs to the revised API: read-only `models` (live fetch per read) instead of a
read/write `value`; `deleteAll()` instead of `reset()`; `Application.reset(modelState:)`
removed; `@ModelState` wrapped value read-only (mutate via the projected value); and
`ModelContainer` examples use explicit do/catch instead of try!.
Document why v3 adopts @observable (modern, cross-platform Observation; drops the
NSObject/Combine ObservableObject coupling) and that the coarse, whole-registry
notification is unchanged from 2.x, with per-key observation noted as future work.
Include the trailing property-wrappers spec task update.
…egration-hsu92z

Add SwiftData integration (ModelContainer dependency + ModelState)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modernizes the AppState library for Swift 6 and Apple's Observation framework, raising platform requirements and introducing first-class SwiftData integration via ModelState and the @ModelState property wrapper. Feedback on these changes highlights a critical performance issue where ModelContainer is eagerly instantiated on every access, a thread-safety and main-thread publication issue in notifyChange(), and opportunities to optimize deletion using SwiftData's batch deletion API.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +92 to +108
func modelContainer(
_ container: @autoclosure () -> ModelContainer,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) -> Dependency<ModelContainer> {
dependency(
container(),
id: Application.codeID(
fileID: fileID,
function: function,
line: line,
column: column
)
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Performance Issue: Eager Instantiation of ModelContainer

In the current implementation, container is passed as an @autoclosure and evaluated immediately via container() on line 100.

Because modelContainer is defined as a computed property on Application extensions, every access to Application.shared.modelContainer (which happens on every model fetch, insert, delete, or save) evaluates this computed property and eagerly instantiates a new ModelContainer.

Creating a ModelContainer is an extremely heavy operation that sets up SQLite connections, performs migrations, and initializes the schema. Eagerly instantiating it on every access will cause severe performance degradation, high memory usage, and potential database locking or crashes.

Suggestion

Change the parameter to an @escaping closure and pass it lazily to the underlying dependency resolver so that the ModelContainer is only instantiated once when first resolved.

Suggested change
func modelContainer(
_ container: @autoclosure () -> ModelContainer,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) -> Dependency<ModelContainer> {
dependency(
container(),
id: Application.codeID(
fileID: fileID,
function: function,
line: line,
column: column
)
)
}
func modelContainer(
_ container: @escaping () -> ModelContainer,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) -> Dependency<ModelContainer> {
dependency(
container,
id: Application.codeID(
fileID: fileID,
function: function,
line: line,
column: column
)
)
}

Comment on lines +102 to +104
public func notifyChange() {
changeAnchor &+= 1
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Thread-Safety & Main-Thread Publication Issue

The notifyChange() method mutates changeAnchor directly. Since Application is not actor-isolated and notifyChange() can be called from background threads (e.g., when reacting to background iCloud sync notifications or background Combine publishers), this mutation is susceptible to data races.

Furthermore, mutating an @Observable property on a background thread that is currently being tracked by a SwiftUI view will trigger runtime warnings:
Publishing changes from background threads is not supported; make sure to publish values from the Main Thread.

Suggestion

Ensure that changeAnchor is always mutated on the main thread. This guarantees thread-safety and prevents SwiftUI background thread publication warnings.

    public func notifyChange() {
        if Thread.isMainThread {
            changeAnchor &+= 1
        } else {
            DispatchQueue.main.async {
                self.changeAnchor &+= 1
            }
        }
    }

Comment on lines +31 to +38
var modelContainer: Dependency<ModelContainer> {
modelContainer(
try! ModelContainer(
for: TodoItem.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Suggestion: Use Lazy Closure for ModelContainer

To align with the recommended lazy instantiation pattern for ModelContainer (preventing eager instantiation on every access), pass the initialization block as a closure.

Suggested change
var modelContainer: Dependency<ModelContainer> {
modelContainer(
try! ModelContainer(
for: TodoItem.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
)
}
var modelContainer: Dependency<ModelContainer> {
modelContainer {
try! ModelContainer(
for: TodoItem.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
}
}

Comment on lines +111 to +123
public func deleteAll() {
let context = context

do {
let models = try context.fetch(fetchDescriptor())

for model in models {
context.delete(model)
}

save(context: context, action: "Deleting")
} catch {
log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Performance Improvement: Use Batch Deletion

Currently, deleteAll() fetches all matching models into memory and deletes them one by one. For large datasets, this is highly inefficient, consumes significant memory, and can lead to out-of-memory crashes.

Since the minimum deployment targets for AppState 3.0 are iOS 17 and macOS 14, SwiftData's batch deletion API (context.delete(model:where:)) is unconditionally available. This performs a highly optimized database-level delete without loading any objects into memory.

Suggestion

Use context.delete(model:where:) with the predicate from the FetchDescriptor.

        @MainActor
        public func deleteAll() {
            let context = context

            do {
                try context.delete(model: Model.self, where: fetchDescriptor().predicate)
                save(context: context, action: "Deleting")
            } catch {
                log(
                    error: error,
                    message: "\(ModelState.emoji) ModelState Deleting",
                    fileID: #fileID,
                    function: #function,
                    line: #line,
                    column: #column
                )
            }
        }

@0xLeif

0xLeif commented Jun 9, 2026

Copy link
Copy Markdown
Owner Author

Absorbed into the new develop branch (origin/develop), which we're using to own this work off main.

develop = main + this PR's commits (fast-forwarded cleanly, since #147 was based on the current main), plus:

Review findings addressed (all four from the automated review):

  • CriticalmodelContainer(_:) no longer eagerly instantiates the ModelContainer on every \.modelContainer access; the @autoclosure thunk is forwarded to dependency(_:id:) so it's built once on first resolve. Public signature unchanged (non-breaking).
  • notifyChange() — asserts main-thread before mutating the @Observable anchor. (Application is non-Sendable, so the change can't be hopped to main on the caller's behalf; the documented invariant is enforced instead of silently raced.)
  • ModelState.deleteAll() — switched to batch context.delete(model:where:) (DB-level, no objects loaded).
  • SwiftDataExample — dropped try! for a do/catch + fatalError container factory.

Examples — added six standalone SwiftUI example packages (TodoCloud, SettingsKit, DataDashboard, SecureVault, SyncNotes, MultiPlatformTracker) with 69 passing tests, plus an Examples CI workflow.

Library: builds clean, 41 tests pass. Closing in favor of develop.

@0xLeif 0xLeif closed this Jun 9, 2026
0xLeif added a commit that referenced this pull request Jun 11, 2026
* Add SwiftData integration (ModelContainer dependency + ModelState)

Introduce a SwiftData integration that follows AppState's existing
persistence-type conventions, fully gated behind `canImport(SwiftData)`
and `@available` (iOS 17 / macOS 14 / tvOS 17 / watchOS 10 / visionOS 1).

- ModelContainer as an AppState dependency, with `Application.modelContext(_:)`
  to access the shared main-actor `ModelContext` from non-view code, and a
  `modelContainer(_:)` registration convenience.
- `Application.ModelState<Model>` (conforms to `MutableApplicationState`):
  reads via a `FetchDescriptor`, with `insert`/`delete`/`save`/`reset` and a
  `value` get (fetch) / set (insert new + save) accessor.
- `@ModelState` property wrapper exposing `[Model]`, with the projected value
  surfacing `insert`/`delete`/`save`.
- Public factory/accessor helpers: `modelState(...)`, `Application.modelState(_:)`,
  and `Application.reset(modelState:)`.
- Unit tests (in-memory container) covering the dependency, CRUD through the
  application and property wrapper, reset, and fetch-descriptor ordering.
- Runnable example SwiftPM package (Examples/SwiftDataExample) wired into macOS
  CI via `swift build`/`swift run` as a smoke test.
- Documentation: usage-modelstate guide plus README and usage-overview updates.

* Raise platform floors to iOS 17 / macOS 14 and pin Swift 6 language mode

Begin the v3 modernization. Raise the minimum deployment targets to
iOS 17 / watchOS 10 / macOS 14 / tvOS 17 / visionOS 1 and explicitly pin
`swiftLanguageModes: [.v6]`.

Because the new floors exactly match SwiftData's availability, the
`@available` annotations on the SwiftData integration are now redundant and
have been removed; the `#if canImport(SwiftData)` guards remain so Linux and
Windows (which have no SwiftData) still compile to nothing.

* CI: treat warnings as errors to enforce strict Swift 6 cleanliness

Add -Xswiftc -warnings-as-errors to the build and test steps on macOS,
Ubuntu, and Windows so any Swift 6 (or future-Swift) warning fails CI. This
establishes the zero-warning baseline for the v3 modernization.

* Adopt explicit existentials and enable the ExistentialAny upcoming feature

Future-proof against upcoming Swift by enabling the ExistentialAny upcoming
feature on the AppState target and annotating every existential type with
'any' (the *Managing dependency protocols, Loggable, and Error). This keeps
the library warning-clean as ExistentialAny moves toward becoming the default.

* Migrate reactivity from ObservableObject to the Observation framework

Adopt @observable for `Application` while keeping `NSObject` (so the @objc
iCloud `didChangeExternally` hook is unchanged). State and dependency values
still live in the untracked `Cache`, so a private `changeAnchor` bridges cache
changes to Observation:

- `Application` is now `@Observable`; `lock`/`cache`/`bag` are `@ObservationIgnored`.
- `registerObservation()` (read by every property wrapper's getter) registers the
  current tracking scope; `notifyChange()` (public) bumps the anchor to update
  observers. The cache observer and `DependencySlice` setters call `notifyChange()`.
- Property wrappers (`AppState`, `Stored/File/Sync/Secure/ModelState`, `Slice`,
  `OptionalSlice`, `DependencySlice`) drop `@ObservedObject app` for a computed
  `app` and call `registerObservation()` when read. This also removes the
  per-wrapper Linux/Windows fork for the `app` reference.
- Removed `Application: ObservableObject`; the enclosing-instance subscript that
  supports ObservableObject host view models is retained, as is `ObservedDependency`.

Note: reactive view updates require verification on a real Apple target; CI here
covers compilation, unit tests, and warnings-as-errors only.

* Fix Observation migration leftovers on Apple platforms

- SecureState's setter wrote through the Keychain (not the cache), so it called
  the old objectWillChange; route it through notifyChange() instead.
- Drop the now-orphaned 'ObjectWillChangePublisher == ObservableObjectPublisher'
  constraint on consume(object:), which relied on Application's removed
  ObservableObject conformance.

* Docs: add AppState 3.0 upgrade guide

Document the v3 breaking changes (raised platform floors, strict Swift 6 /
ExistentialAny, the Observation/@observable migration and notifyChange(), and
the additive SwiftData ModelState feature) and link it from the README.

* Docs: translate v3 guides and propagate notifyChange across all languages

Localize the 3.0 documentation into every language the repo carries
(de, es, fr, hi, pt, ru, zh-CN):

- Add translated `usage-modelstate.md` and `upgrade-to-v3.md` to each language.
- Add the ModelState section to each `usage-overview.md`.
- Update each `usage-syncstate.md` custom-Application example to call
  `notifyChange()` instead of `objectWillChange.send()` (including English).
- Update each translated README's requirements to the new platform floors and
  add the SwiftData (ModelState) feature bullet plus the two new doc links.

* Add Observation reactivity test for the @observable bridge

Use withObservationTracking to assert that reading a property wrapper registers
an observation dependency and that mutating the underlying state fires onChange
(the same registerObservation()/notifyChange() path SwiftUI relies on), plus a
negative case. Apple-only, since the cache->anchor bridge is Apple-only.

* Add fledge task runner and spec-sync setup (application + swiftdata specs)

Integrate CorvidLabs fledge and spec-sync into the project:

- fledge.toml: build/test/lint/example tasks; the lint task runs build+test with
  -warnings-as-errors and the example smoke test, mirroring CI.
- .specsync/ (v4.3.1): config, registry (application, property-wrappers,
  swiftdata), version.
- specs/application: updated to v3 (Observation/@observable, notifyChange,
  raised floors, Swift 6 + ExistentialAny) plus filled context/requirements/
  tasks/testing.
- specs/swiftdata: new module documenting the SwiftData ModelContainer
  dependency and ModelState.

(property-wrappers spec follows in a separate commit.)

* spec-sync: author the property-wrappers spec for v3

Fill the property-wrappers spec and its context/requirements/tasks/testing:
all state, dependency, and slice/constant wrappers; the Observation-based
reactivity (computed app + registerObservation/notifyChange); @ModelState;
and the DynamicProperty + enclosing-instance subscript behavior.

* Polish: align ModelState logging and doc wording with house style

- Use the unqualified log(...) inside the nested Application.ModelState struct,
  matching FileState/StoredState (the qualified Application.log is kept in the
  top-level @ModelState wrapper, consistent with the other wrappers).
- Correct the @DependencySlice 'app' doc comment to reference a dependency.

* Address review: ModelState API, observation isolation, scoped strict CI

Act on the maintainer review of the SwiftData / v3 work:

- ModelState semantics (#7/#8/#9): `value` is now a read-only `models` (no more
  insert-only "set"); the destructive `reset()` is an explicit `deleteAll()` and
  `ModelState` no longer conforms to `MutableApplicationState` (so it isn't forced
  into value/reset semantics that don't fit). Removed `Application.reset(modelState:)`.
  Documented loudly that `models` performs a live fetch on every read.
- `@ModelState` wrapper is read-only; mutate through the projected value. Dropped
  the now-unneeded enclosing-instance subscript and the Combine import.
- Observation isolation (#4/#5): `changeAnchor`, `registerObservation()`, and
  `notifyChange()` are `@MainActor`; the cache observer routes through
  `MainActor.assumeIsolated`. Documented why the discarded anchor read is load-bearing.
- Scoped strict builds: `-warnings-as-errors` moved out of the CI command and into
  env-gated `unsafeFlags` on our targets only (APPSTATE_STRICT), so dependencies and
  downstream consumers are unaffected.
- Docs: replaced `try!` ModelContainer examples with explicit do/catch.
- Updated tests and the example for the new API.

* Keep observation anchor nonisolated; document the concurrency invariant

The @mainactor isolation attempted for the anchor is incompatible with the
cross-thread Combine cache observer (a nonisolated sink cannot hop to a
@mainactor method without 'sending' non-Sendable self — Swift 6 region
isolation error on macOS). Removing the central observer instead would risk
silently dropping reactivity for some state types, which CI can't detect.

So notifyChange()/registerObservation()/changeAnchor stay nonisolated (the
proven, ObservationTests-covered design) and the thread-safety invariant is now
documented explicitly: all mutations funnel through notifyChange(), invoked
only from main-actor setters and the synchronous cache observer running during
them; reads are main-actor; the @observable registrar is itself synchronized.

* Docs/specs: sync ModelState references to read-only models / deleteAll()

Update usage-modelstate.md in all 8 languages and the SwiftData / property-wrappers
specs to the revised API: read-only `models` (live fetch per read) instead of a
read/write `value`; `deleteAll()` instead of `reset()`; `Application.reset(modelState:)`
removed; `@ModelState` wrapped value read-only (mutate via the projected value); and
`ModelContainer` examples use explicit do/catch instead of try!.

* Docs: justify the @observable migration; final spec sync

Document why v3 adopts @observable (modern, cross-platform Observation; drops the
NSObject/Combine ObservableObject coupling) and that the coarse, whole-registry
notification is unchanged from 2.x, with per-key observation noted as future work.
Include the trailing property-wrappers spec task update.

* Fix: address PR #147 review findings

- ModelContainer: forward the autoclosure thunk to dependency(_:id:) instead
  of evaluating it eagerly, so the heavy ModelContainer is built once on first
  access instead of rebuilt on every \.modelContainer read (critical). Public
  @autoclosure signature is unchanged — non-breaking.
- notifyChange(): assert main-thread before mutating the @observable anchor.
  Application is non-Sendable so the change cannot be hopped to main on the
  caller's behalf; the invariant is enforced instead of silently raced.
- ModelState.deleteAll(): use context.delete(model:where:) batch deletion
  (DB-level, no objects loaded) instead of fetch-all-then-loop.
- SwiftDataExample: drop try! for a do/catch + fatalError container factory,
  matching the library's documented idiom and the no-force-try convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add: six SwiftUI example packages + Examples CI

Folds in standalone SwiftPM example apps demonstrating AppState, each with a
passing test suite (69 tests total), verified against the 3.0.0 root:

- Moderate/TodoCloud      — @syncstate + @AppDependency (18 tests)
- Moderate/SettingsKit    — @StoredState + @slice (14 tests)
- Moderate/DataDashboard  — dependency injection + @appstate (8 tests)
- Moderate/SecureVault    — @securestate / Keychain (11 tests)
- Focused/SyncNotes       — @syncstate (7 tests)
- Focused/MultiPlatformTracker — @StoredState cross-platform (11 tests)

Also adds .github/workflows/examples.yml (tests the six + builds the SwiftData
example) and ignores nested .build/ directories.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Test: drive example SwiftUI to 100% coverage with ViewInspector

Adds ViewInspector (0.10.3) to each example's test target and exercises every
view body and action closure (tap, onSubmit, onDelete, setInput, isDisabled),
plus the live/non-mocked service implementations.

Coverage (regions/functions/lines), verified via llvm-cov:
- TodoCloud, SettingsKit, DataDashboard, SecureVault, SyncNotes,
  MultiPlatformTracker: 100% / 100% / 100%
- SwiftDataExample: 100% functions; the sole uncovered region is the defensive
  catch+fatalError in the in-memory ModelContainer factory — structurally
  uncoverable (SwiftData's init throws, try! is banned by convention, and
  executing the trap crashes the runner). Documented in-source.

Source simplifications made along the way (removed unreachable code):
- Dropped vestigial #available(watchOS 9.0) checks across examples (dead given
  the watchOS 11 deployment target).
- MultiPlatformTracker: simplified a constant-true ternary in decrement.
- Made nested view structs / shared test mocks internal for inspectability.
- SwiftDataExample: split into library + executable + test targets so the
  reusable types are testable; kept the clean do/catch+fatalError factory.

CI: examples.yml now runs Test Suite 'All tests' started at 2026-06-09 18:17:21.178.
Test Suite 'AppStatePackageTests.xctest' started at 2026-06-09 18:17:21.179.
Test Suite 'AppDependencyTests' started at 2026-06-09 18:17:21.179.
Test Case '-[AppStateTests.AppDependencyTests testComposableDependencies]' started.
Test Case '-[AppStateTests.AppDependencyTests testComposableDependencies]' passed (0.006 seconds).
Test Case '-[AppStateTests.AppDependencyTests testDependency]' started.
Test Case '-[AppStateTests.AppDependencyTests testDependency]' passed (0.002 seconds).
Test Suite 'AppDependencyTests' passed at 2026-06-09 18:17:21.187.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.008 (0.008) seconds
Test Suite 'AppStateTests' started at 2026-06-09 18:17:21.187.
Test Case '-[AppStateTests.AppStateTests testLoggingToggle]' started.
Test Case '-[AppStateTests.AppStateTests testLoggingToggle]' passed (0.002 seconds).
Test Case '-[AppStateTests.AppStateTests testPropertyWrappers]' started.
Test Case '-[AppStateTests.AppStateTests testPropertyWrappers]' passed (0.002 seconds).
Test Case '-[AppStateTests.AppStateTests testStateClosureCachesValueOnGet]' started.
Test Case '-[AppStateTests.AppStateTests testStateClosureCachesValueOnGet]' passed (0.002 seconds).
Test Case '-[AppStateTests.AppStateTests testState]' started.
Test Case '-[AppStateTests.AppStateTests testState]' passed (0.001 seconds).
Test Case '-[AppStateTests.AppStateTests testStateWithDifferentDataTypes]' started.
Test Case '-[AppStateTests.AppStateTests testStateWithDifferentDataTypes]' passed (0.002 seconds).
Test Suite 'AppStateTests' passed at 2026-06-09 18:17:21.198.
	 Executed 5 tests, with 0 failures (0 unexpected) in 0.010 (0.010) seconds
Test Suite 'ApplicationTests' started at 2026-06-09 18:17:21.198.
Test Case '-[AppStateTests.ApplicationTests testCustomFunction]' started.
Test Case '-[AppStateTests.ApplicationTests testCustomFunction]' passed (0.001 seconds).
Test Suite 'ApplicationTests' passed at 2026-06-09 18:17:21.199.
	 Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.001) seconds
Test Suite 'DependencySliceTests' started at 2026-06-09 18:17:21.199.
Test Case '-[AppStateTests.DependencySliceTests testApplicationSliceFunction]' started.
Test Case '-[AppStateTests.DependencySliceTests testApplicationSliceFunction]' passed (0.002 seconds).
Test Case '-[AppStateTests.DependencySliceTests testPropertyWrappers]' started.
Test Case '-[AppStateTests.DependencySliceTests testPropertyWrappers]' passed (0.002 seconds).
Test Suite 'DependencySliceTests' passed at 2026-06-09 18:17:21.203.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
Test Suite 'FileManagerExtensionTests' started at 2026-06-09 18:17:21.203.
Test Case '-[AppStateTests.FileManagerExtensionTests testWriteAndReadCodable]' started.
Test Case '-[AppStateTests.FileManagerExtensionTests testWriteAndReadCodable]' passed (0.003 seconds).
Test Case '-[AppStateTests.FileManagerExtensionTests testWriteAndReadData]' started.
Test Case '-[AppStateTests.FileManagerExtensionTests testWriteAndReadData]' passed (0.001 seconds).
Test Suite 'FileManagerExtensionTests' passed at 2026-06-09 18:17:21.207.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
Test Suite 'FileStateTests' started at 2026-06-09 18:17:21.207.
Test Case '-[AppStateTests.FileStateTests testFileState]' started.
Test Case '-[AppStateTests.FileStateTests testFileState]' passed (0.013 seconds).
Test Case '-[AppStateTests.FileStateTests testStoringViewModel]' started.
Test Case '-[AppStateTests.FileStateTests testStoringViewModel]' passed (0.008 seconds).
Test Suite 'FileStateTests' passed at 2026-06-09 18:17:21.228.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.020 (0.020) seconds
Test Suite 'KeychainTests' started at 2026-06-09 18:17:21.228.
Test Case '-[AppStateTests.KeychainTests testKeychainContains]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainContains]' passed (0.045 seconds).
Test Case '-[AppStateTests.KeychainTests testKeychainInitKeys]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainInitKeys]' passed (0.002 seconds).
Test Case '-[AppStateTests.KeychainTests testKeychainInitValues]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainInitValues]' passed (0.033 seconds).
Test Case '-[AppStateTests.KeychainTests testKeychainRequiresFailure]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainRequiresFailure]' passed (0.002 seconds).
Test Case '-[AppStateTests.KeychainTests testKeychainRequiresSuccess]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainRequiresSuccess]' passed (0.025 seconds).
Test Case '-[AppStateTests.KeychainTests testKeychainValues]' started.
Test Case '-[AppStateTests.KeychainTests testKeychainValues]' passed (0.030 seconds).
Test Suite 'KeychainTests' passed at 2026-06-09 18:17:21.364.
	 Executed 6 tests, with 0 failures (0 unexpected) in 0.136 (0.137) seconds
Test Suite 'ModelStateTests' started at 2026-06-09 18:17:21.364.
Test Case '-[AppStateTests.ModelStateTests testDeleteAll]' started.
Test Case '-[AppStateTests.ModelStateTests testDeleteAll]' passed (0.011 seconds).
Test Case '-[AppStateTests.ModelStateTests testFetchDescriptorSorting]' started.
Test Case '-[AppStateTests.ModelStateTests testFetchDescriptorSorting]' passed (0.004 seconds).
Test Case '-[AppStateTests.ModelStateTests testInsertAndFetchThroughApplication]' started.
Test Case '-[AppStateTests.ModelStateTests testInsertAndFetchThroughApplication]' passed (0.002 seconds).
Test Case '-[AppStateTests.ModelStateTests testModelContextDependency]' started.
Test Case '-[AppStateTests.ModelStateTests testModelContextDependency]' passed (0.002 seconds).
Test Case '-[AppStateTests.ModelStateTests testProjectedValueCRUD]' started.
Test Case '-[AppStateTests.ModelStateTests testProjectedValueCRUD]' passed (0.005 seconds).
Test Case '-[AppStateTests.ModelStateTests testPropertyWrapperReadAndProjectedInsert]' started.
Test Case '-[AppStateTests.ModelStateTests testPropertyWrapperReadAndProjectedInsert]' passed (0.005 seconds).
Test Suite 'ModelStateTests' passed at 2026-06-09 18:17:21.393.
	 Executed 6 tests, with 0 failures (0 unexpected) in 0.028 (0.029) seconds
Test Suite 'ObservationTests' started at 2026-06-09 18:17:21.393.
Test Case '-[AppStateTests.ObservationTests testMutatingStateNotifiesObservers]' started.
Test Case '-[AppStateTests.ObservationTests testMutatingStateNotifiesObservers]' passed (0.001 seconds).
Test Case '-[AppStateTests.ObservationTests testReadingWithoutTrackedMutationDoesNotNotify]' started.
Test Case '-[AppStateTests.ObservationTests testReadingWithoutTrackedMutationDoesNotNotify]' passed (0.001 seconds).
Test Suite 'ObservationTests' passed at 2026-06-09 18:17:21.395.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.002) seconds
Test Suite 'ObservedDependencyTests' started at 2026-06-09 18:17:21.395.
Test Case '-[AppStateTests.ObservedDependencyTests testDependency]' started.
Test Case '-[AppStateTests.ObservedDependencyTests testDependency]' passed (0.001 seconds).
Test Suite 'ObservedDependencyTests' passed at 2026-06-09 18:17:21.396.
	 Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.001) seconds
Test Suite 'OptionalSliceTests' started at 2026-06-09 18:17:21.396.
Test Case '-[AppStateTests.OptionalSliceTests testApplicationSliceFunction]' started.
Test Case '-[AppStateTests.OptionalSliceTests testApplicationSliceFunction]' passed (0.002 seconds).
Test Case '-[AppStateTests.OptionalSliceTests testNil]' started.
Test Case '-[AppStateTests.OptionalSliceTests testNil]' passed (0.002 seconds).
Test Case '-[AppStateTests.OptionalSliceTests testPropertyWrappers]' started.
Test Case '-[AppStateTests.OptionalSliceTests testPropertyWrappers]' passed (0.002 seconds).
Test Suite 'OptionalSliceTests' passed at 2026-06-09 18:17:21.402.
	 Executed 3 tests, with 0 failures (0 unexpected) in 0.005 (0.005) seconds
Test Suite 'SecureStateTests' started at 2026-06-09 18:17:21.402.
Test Case '-[AppStateTests.SecureStateTests testSecureState]' started.
Test Case '-[AppStateTests.SecureStateTests testSecureState]' passed (0.048 seconds).
Test Case '-[AppStateTests.SecureStateTests testStoringViewModel]' started.
Test Case '-[AppStateTests.SecureStateTests testStoringViewModel]' passed (0.041 seconds).
Test Suite 'SecureStateTests' passed at 2026-06-09 18:17:21.491.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.089 (0.089) seconds
Test Suite 'SliceTests' started at 2026-06-09 18:17:21.491.
Test Case '-[AppStateTests.SliceTests testApplicationSliceFunction]' started.
Test Case '-[AppStateTests.SliceTests testApplicationSliceFunction]' passed (0.002 seconds).
Test Case '-[AppStateTests.SliceTests testPropertyWrappers]' started.
Test Case '-[AppStateTests.SliceTests testPropertyWrappers]' passed (0.002 seconds).
Test Suite 'SliceTests' passed at 2026-06-09 18:17:21.495.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
Test Suite 'StoredStateTests' started at 2026-06-09 18:17:21.495.
Test Case '-[AppStateTests.StoredStateTests testStoredState]' started.
Test Case '-[AppStateTests.StoredStateTests testStoredState]' passed (0.002 seconds).
Test Case '-[AppStateTests.StoredStateTests testStoringViewModel]' started.
Test Case '-[AppStateTests.StoredStateTests testStoringViewModel]' passed (0.003 seconds).
Test Suite 'StoredStateTests' passed at 2026-06-09 18:17:21.500.
	 Executed 2 tests, with 0 failures (0 unexpected) in 0.005 (0.006) seconds
Test Suite 'SyncStateTests' started at 2026-06-09 18:17:21.500.
Test Case '-[AppStateTests.SyncStateTests testFailEncodingSyncState]' started.
Test Case '-[AppStateTests.SyncStateTests testFailEncodingSyncState]' passed (0.007 seconds).
Test Case '-[AppStateTests.SyncStateTests testStoringViewModel]' started.
Test Case '-[AppStateTests.SyncStateTests testStoringViewModel]' passed (0.004 seconds).
Test Case '-[AppStateTests.SyncStateTests testSyncState]' started.
Test Case '-[AppStateTests.SyncStateTests testSyncState]' passed (0.003 seconds).
Test Suite 'SyncStateTests' passed at 2026-06-09 18:17:21.514.
	 Executed 3 tests, with 0 failures (0 unexpected) in 0.014 (0.014) seconds
Test Suite 'AppStatePackageTests.xctest' passed at 2026-06-09 18:17:21.515.
	 Executed 41 tests, with 0 failures (0 unexpected) in 0.330 (0.335) seconds
Test Suite 'All tests' passed at 2026-06-09 18:17:21.515.
	 Executed 41 tests, with 0 failures (0 unexpected) in 0.330 (0.337) seconds
◇ Test run started.
↳ Testing Library Version: 1902
↳ Target Platform: arm64e-apple-macos14.0
✔ Test run with 0 tests in 0 suites passed after 0.001 seconds. for SwiftDataExample (was build-only).

257 tests pass across the library (41) and seven examples (216).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Test: expand SwiftData + Observation (3.0.0) test coverage

Adds ViewInspector to the library test target and +97 tests for the new 3.0.0
surface (138 total, all passing):

- ModelStateCoverageTests: every modelState/modelContainer/modelContext overload,
  ModelState CRUD edge cases (save no-op, delete missing, deleteAll empty),
  multi-container isolation, feature/id scoping, FetchDescriptor sort/predicate/
  limit, and @ModelState from struct + class. Functions 100% on the SwiftData files.
- PropertyWrapperViewTests: every property wrapper (@appstate, @StoredState,
  @syncstate, @securestate, @FileState, @Slice/@OptionalSlice, @Constant/
  @OptionalConstant, @AppDependency, @ObservedDependency, @ModelState) rendered
  and driven inside a real SwiftUI view via ViewInspector.
- ObservationBridgeTests: registerObservation()/notifyChange() fires for each
  state wrapper, multiple observers, direct notifyChange(), and didChangeExternally().

Coverage: library 86.3%/92.5% -> 88.7%/94.1% (regions/lines); Application.swift
75%->96.7% lines; the SwiftData ModelState/ModelContainer files at 100% functions.

Remaining uncovered are structurally uncoverable: the three SwiftData catch
blocks (in-memory SwiftData raises uncatchable NSExceptions, not Swift errors),
the notifyChange() assert-false branch, and Linux-only #else branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add: runnable demo app, advanced SwiftData example, adversarial test suite

- Examples/DemoApp: an xcodegen-generated iOS host app cataloging every example
  (verified building + running on the iOS 17 simulator), with a SwiftData Lab
  section and an interactive 'Break It' stress screen that hammers AppState live.
- Examples/SwiftDataExample: expanded into a multi-model SwiftData Lab —
  TodoList/TodoItem/Tag with cascade + nullify @relationships, compound #Predicate
  queries with multi-sort/fetchLimit, @Attribute(.unique) upsert, a VersionedSchema
  V1->V2 + SchemaMigrationPlan, and SwiftUI views (SwiftDataLabView). 81 tests.
- Tests/AppStateTests/AdversarialBreakItTests: 58 'try to break it' tests across
  concurrency/volume/churn/malformed-data/SwiftData-edge/re-entrancy. Library suite
  196 tests, all passing. These surfaced real findings (see follow-up): a SyncState
  encode-ordering bug, Keychain set()/values() races, and an observation gap on the
  non-wrapper Application.state().value accessor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix: correctness bugs surfaced by the adversarial suite

- SyncState: encode the value BEFORE committing to the local fallback and iCloud.
  Previously storedState was written first, so a value that failed to JSON-encode
  (e.g. Double.infinity) poisoned the fallback and was read back even though iCloud
  never received it. A pre-existing test asserted that buggy behavior — updated.
- Keychain: serialize set()'s SecItemUpdate/SecItemAdd pair under the lock (two
  concurrent same-key writes could both attempt SecItemAdd), update the in-memory
  key index synchronously inside the lock (was a fire-and-forget Task that lagged),
  and have remove() drop the key from the index (it never did, so values() kept
  reporting removed keys). Keychain is now @unchecked Sendable with all mutable
  state lock-guarded.
- Observation: Application.state(_:).value (the imperative accessor) now registers
  the observation scope like the @appstate wrapper does, so reads through it drive
  withObservationTracking/SwiftUI updates.

Adds BugFixRegressionTests pinning each fix. Library suite: 199 tests, all passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Test: add XCUITest UI suite for the demo app

End-to-end UI tests (Examples/DemoApp/UITests) that launch the app and drive the
real SwiftUI on a simulator — 10 tests, all passing on iOS 26 (iPhone 16e):
- catalog lists every example; every screen is reachable and returns cleanly
- TodoCloud add todo, SettingsKit toggle, SyncNotes add note, Tracker increment,
  SecureVault login/logout, DataDashboard async load, SwiftData Lab create list,
  Break It stress workload survives

Adds an AppStateDemoUITests ui-testing target + scheme to project.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add: non-blocking background SwiftData + responsive stress UI

- SwiftDataExample: a @Modelactor BulkImporter that inserts thousands of models on
  a background context (off the main actor), batching + yielding, with progress and
  cancellation; plus a responsive BulkImportView (ProgressView + Cancel) that stays
  interactive while 10k items import. The main actor only updates small progress
  state. 113 tests.
- DemoApp Break It: rewrote every workload to run in a yielding Task (or off-main
  via Task.detached + concurrentPerform) so the heavy loops never block the run
  loop — the spinner animates and the list scrolls during the work. Fixes the
  earlier main-thread freeze.
- DemoApp catalog: added the Bulk Import screen under SwiftData.
- XCUITests: added testBulkImportRunsOffMainAndCompletes (Cancel is hittable while
  the import runs off-main = proof the UI is not blocked); updated Break It +
  reachability tests. 12 UI tests passing on iOS 26 simulator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* CI: fix cross-platform + SDK-version build failures

- Library: make the ViewInspector test dependency Apple-only (.when(platforms:)) —
  it imports SwiftUI, which doesn't exist on Linux/Windows, breaking those builds.
  The one test file using it is already #if-guarded off those platforms.
- SwiftDataExample: mark the VersionedSchema static constants nonisolated(unsafe)
  (Schema.Version / MigrationStage aren't Sendable on older SDKs, so strict
  concurrency rejected them on CI's Xcode 16.x while passing on newer local SDKs).
- CI: bump the macOS/examples workflows to xcode-version: latest-stable so the SDK
  is recent enough for ViewInspector 0.10.x (AttributedTextSelection, Map types).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* CI: guard adversarial suite to Apple platforms; fix toolchain mismatch

- AdversarialBreakItTests: scope the whole file to !os(Linux) && !os(Windows) and
  add 'import Observation'. It exercises Apple-only surface (Keychain, SecureState,
  SwiftData, Observation), so it broke the Linux/Windows library builds.
- Workflows: drop swift-actions/setup-swift and use the Xcode latest-stable bundled
  Swift instead. The standalone 6.1/6.2 toolchain mismatched latest-stable's 6.2.3
  SDK ('failed to build module Foundation/Combine/Darwin'); Xcode's bundled compiler
  matches its SDK and already parses the tools-version 6.2 example manifests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Refactor: make Keychain checked-Sendable (drop @unchecked)

Replace the NSLock + @mainactor mutable key index with:
- writeLock (NSLock) — serializes Keychain WRITE syscalls (set/remove) so the
  update-then-add sequence stays atomic.
- index: OSAllocatedUnfairLock<Set<Key>> — guards the in-memory key index in short
  critical sections that never span a syscall.

Both stored properties are immutable lets and Sendable, so Keychain conforms to
Sendable without @unchecked. Reads are now lock-free (a single system-serialized
syscall), so concurrent gets no longer block each other.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove examples and ViewInspector to keep 3.0.0 a lean library

Per review: AppState stays a library — runnable demos/examples live in a separate
repo, and a third-party SwiftUI-inspection test dependency does not belong in the
manifest.

- Remove the entire Examples/ tree (6 example packages, the SwiftData Lab, the
  DemoApp + XCUITests) and the examples CI workflow.
- Drop the ViewInspector package dependency from Package.swift and delete the one
  test that used it (PropertyWrapperViewTests). The Observation bridge is already
  covered dependency-free by ObservationTests/ObservationBridgeTests via
  withObservationTracking.
- macOS.yml: drop the now-removed SwiftData example build/run steps.

Library: 161 tests, all passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Docs + observation: finalize 3.0 docs; single observation path for State

- Observation: the @appstate wrapper no longer calls registerObservation() itself —
  Application.state(_:) already registers the Observation scope, so reading through
  it is the single path. Reading Application.state(_:).value directly now also
  participates in observation, which is how non-SwiftUI code (any object using
  withObservationTracking) can observe AppState. Documented in upgrade-to-v3.
- Docs: tightened the 3.0 docs to match the project's voice and removed AI-shaped
  phrasing — upgrade-to-v3 (skimmable breaking-change list), usage-modelstate
  (example-first rewrite), usage-overview (dropped a try! from a snippet), README.

Library: 161 tests, all passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Docs: regenerate de/es/fr/hi/pt/ru/zh-CN translations to match the 3.0 docs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* CI: fix DocC Pages deploy to use latest-stable Xcode on macos-15

The pinned setup-swift 6.1 + Xcode 16.0 caused the same SDK/compiler
mismatch we removed from the other workflows, and 16.0 lacks the iOS 17
SwiftData SDK the 3.0 target needs. Align with macOS.yml so the API
reference publishes when develop merges to main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address #148 review: ModelState strict throwing API + main-thread dependency notify

Resolves both gemini high-priority comments on PR #148:

- ModelState: insert/delete/save/deleteAll kept lenient (log-and-swallow)
  but now back onto a shared throwing core, with a `strict` facade
  exposing throwing variants so callers can surface/recover from failed
  writes. Dual lenient/strict access matches the Keychain precedent.
- Application.consume(_:): a dependency that publishes objectWillChange
  off the main thread previously called notifyChange() off-main (debug
  assert / changeAnchor race). Now delivers synchronously when already on
  main (preserving the synchronous withObservationTracking contract) and
  hops to main only when off-main.

Adds ModelState strict-API regression tests and documents `strict` in the
ModelState guide across all 8 languages. Full suite: 163 tests, 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Corvid Agent <95454608+corvid-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants