Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ These are the things we've burned ourselves on. Following them isn't optional.
build a tree and hand it to `Mob.Sender.render/5`; the sender is the only
caller. See `decisions/2026-08-28-sender-serialises-render.md`.

Native event handles encode the render generation on both platforms.
`clear_taps` advances the build generation, and `set_root` commits the table,
count, and generation under the same mutex. Treating a handle as a bare slot
can route a callback from an old native tree into the current screen. A sender
must also copy the tag into its delivery environment while holding that
mutex; the table's `tag_env` may be freed as soon as the lock is released.
Change events and animation-delayed dismissals may cross one render when
both retained registrations have identical PID and tag identity; taps and
gestures stay generation-strict. Invalidate the building table's generation
at `clear_taps` so stale lookup never observes a partially rebuilt table.

4. **TDD discipline in mob_dev.** Every new public function gets a test.
`mob_dev/CLAUDE.md` makes this explicit. Don't bypass — the tests are how we
catch the multi-step regressions like the iOS-device deploy chain.
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ Full module documentation: [hexdocs.pm/mob](https://hexdocs.pm/mob).

## [Unreleased]

### Fixed
- **Stale native callbacks no longer route to replacement handlers.** Android
and iOS event handles now carry their render generation, with the handler
table, count, and generation committed atomically. Taps and gestures from an
old native tree are rejected; change-family events and animation-delayed
sheet dismissals may cross any number of renders while the slot's consecutive
committed registrations retain identical PID and tag identity, preserving
in-flight intent without permitting delivery to a replacement handler.
Building tables are generation-invalid
until committed, so stale lookup cannot observe partially rebuilt handlers.
Event tags are copied while their registry lock is held, closing the iOS and
Android environment-lifetime race. Persistent component handles also carry a
per-slot generation so callbacks from reclaimed slots cannot reach a new
component. Rejections are visible in debug native logs. Generated Android
projects must pair this with the companion generator update that keys list
state independently of the full event handle. Deregistering an already-stale
component handle now returns `badarg` rather than silently succeeding.

## [0.7.37] - 2026-08-30

### Added
Expand Down
1 change: 1 addition & 0 deletions android/jni/mob_erts.zig
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ pub inline fn enif_make_uint64(env: ?*ErlNifEnv, i: u64) ERL_NIF_TERM {
pub extern fn enif_alloc_env() ?*ErlNifEnv;
pub extern fn enif_free_env(env: ?*ErlNifEnv) void;
pub extern fn enif_make_copy(dst: ?*ErlNifEnv, src_term: ERL_NIF_TERM) ERL_NIF_TERM;
pub extern fn enif_compare(lhs: ERL_NIF_TERM, rhs: ERL_NIF_TERM) c_int;
pub extern fn enif_send(
caller_env: ?*ErlNifEnv,
to_pid: *const ErlNifPid,
Expand Down
Loading
Loading