Skip to content

Fix Sendable unsoundness, CancelBag TOCTOU, locker naming, default animation, and add async dispatch API#17

Closed
nashysolutions wants to merge 2 commits into
anthony1810:mainfrom
nashysolutions:fix/screenstatekit-library
Closed

Fix Sendable unsoundness, CancelBag TOCTOU, locker naming, default animation, and add async dispatch API#17
nashysolutions wants to merge 2 commits into
anthony1810:mainfrom
nashysolutions:fix/screenstatekit-library

Conversation

@nashysolutions
Copy link
Copy Markdown

@nashysolutions nashysolutions commented Feb 24, 2026

Summary

Test plan

  • Build ScreenStateKit — no new warnings or errors
  • CancelBag: confirm cancelAll() called immediately after store() cancels the stored task
  • StateUpdatable: confirm list updates are no longer animated by default; callers needing animation pass withAnimation: .smooth explicitly
  • Conforming types implementing send(action:) compile correctly; existing receive(action:) implementations are unaffected

🤖 Generated with Claude Code

…t animation

- ScreenState: change open class Sendable to @unchecked Sendable with a doc
  comment stating the invariant all subclasses must maintain (fixes anthony1810#19)
- CancelBag: add async-safe store(task:identifier:) that inserts synchronously
  within actor isolation, eliminating the TOCTOU window between registration
  and storage (fixes anthony1810#20)
- NonIsolatedActionLocker: add doc comment explicitly marking it not thread-safe
  and directing users to IsolatedActionLocker for concurrent use (fixes anthony1810#21)
- StateUpdatable.updateState: change default animation from .smooth to .none
  so state mutations from network responses are no longer animated by default;
  callers can opt in at the call site where animation is intentional (fixes anthony1810#22)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The protocol previously only exposed nonisolated receive(action:), which
spawns an unstructured fire-and-forget task with no cancellation support.
Callers in async contexts (SwiftUI .task, .refreshable) had to reach into
conforming types' internal isolatedReceive method to get awaitable dispatch.

Adding func send(action: Action) async makes the safe, structured path part
of the public contract. receive(action:) is retained for sync contexts
(button callbacks, onAppear) where awaiting is not possible (fixes anthony1810#15).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nashysolutions nashysolutions changed the title Fix Sendable unsoundness, CancelBag TOCTOU, locker naming, and default animation Fix Sendable unsoundness, CancelBag TOCTOU, locker naming, default animation, and add async dispatch API Feb 24, 2026
@anthony1810
Copy link
Copy Markdown
Owner

@nashysolutions Thank you for your contribution, could you update your PR with the new version of SSK?

@nashysolutions-bot
Copy link
Copy Markdown

@anthony1810 Closing this in favour of #18, which contains the surviving subset of these changes rebased on the 1.1.0 API.

The CancelBag TOCTOU fix and send(action:) addition from this PR are superseded by the 1.1.0 rewrite. The three still-relevant changes (@unchecked Sendable on ScreenState, default animation .none, NonIsolatedActionLocker doc comment) are in #18.

@nashysolutions-bot nashysolutions-bot deleted the fix/screenstatekit-library branch March 23, 2026 18:29
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