Bump to 0.7.31 — frame-tracker ownership fix + honest platform docs - #90
Conversation
Release-gate review of 0.7.29..master (the policy that landed in 0.7.30) caught three things in the MOB-103 work that 0.7.30 already shipped. 1. iOS: a frame tracker whose write was REFUSED lost its ownership token. `record` assigned box.seq unconditionally, so a generation-refused write (returns 0) overwrote a valid seq with 0 — and mob_unregister_frame ignores seq 0, permanently disabling that tracker's own cleanup. It bites when an outgoing screen's .move writes are refused by the generation gate AND the incoming screen's element with the same :id isn't laid out (a lazy row below the fold): the id stays in the tree so the purge keeps it, nothing deletes it, and Mob.Test.tap_id taps the old screen's coordinates. Keeping the last SUCCESSFUL seq is strictly safe — the compare-and-delete still refuses to delete whenever an incoming tracker has since claimed the id. The .opacity (reset) transition doesn't hit this, which is the tell: it doesn't move the outgoing frame, so nothing gets refused. 2. Mob.Test.element_frames/1 documented the drop-when-not-laid-out behaviour as if it were cross-platform. It is iOS-only: Android clears frames wholesale on a navigation transition and never per element (MobBridge.setRootJson), so a scrolled-away lazy_list row keeps its frame and tap_id happily taps it. The docs now say so plainly rather than implying parity. Android parity is worth its own issue. 3. CHANGELOG's top section was still [Unreleased] when 0.7.30 was tagged, so release.yml's `## [<version>]` extraction found nothing and published 0.7.30 with empty release notes. Retitled to [0.7.30] so the notes exist for anyone reading back, with this release's entries above it. Also wires `make -C test/native run` into the macOS CI job. That harness is the only automated guard on the registry's ownership/generation/purge rules and it reproduces the algorithm rather than linking it, so running it is what keeps the copy honest — it was committed in 0.7.30 but nothing ran it. Preflight: format, credo --strict, compile --warnings-as-errors, 1111 tests, clang-format, and the native harness (20/20) all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Android had no dismiss sender at all, so mob_new's generated sheet renderer
fell back to nativeSendTap and delivered {:tap, tag}. Mob.UI.sheet/2 documents
:on_dismiss as {:dismiss, tag} (lib/mob/ui.ex:287) and iOS has always sent that
(mob_send_dismiss, ios/mob_nif.m:233), so a screen written to the contract never
matched: Mob.Screen forwards the unmatched message to handle_info, which raises
FunctionClauseError and takes the screen down — or silently drops it with a
catch-all, in which case the BEAM never learns the sheet closed and can't
re-present it.
sendEvent already parameterises the atom, so this is the tap sender with
"dismiss" instead of "tap", plus the mob_beam.h declaration the generated
beam_jni.c thunk compiles against.
Paired with mob_new's MOB-104 change, which adds the nativeSendDismiss extern
and JNI thunk and switches sendDismissOnce over. The coupling is compile-
enforced: mob_new's "beam_jni.c passes clang -fsyntax-only" test builds the
rendered thunk against MOB_DIR's mob_beam.h, so mob_new CI fails until this
lands. mob releases first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…oss-refs
- CHANGELOG: 0.7.31 never mentioned mob_send_dismiss, the new exported symbol
mob_new's generated beam_jni.c links against. Someone hitting the
"call to undeclared function 'mob_send_dismiss'" build error would have
searched the changelog and found nothing. Added, with the Android behaviour
change spelled out: anyone who worked around the old bug by matching
{:tap, tag} for a sheet dismissal has a dead clause now. Also reinstated the
[Unreleased] heading that the previous commit deleted outright, so the next
change has somewhere to land.
- test/native: the harness narrated the PRE-fix caller ("refused write leaves
seq 0, so disappear can't delete") while asserting behaviour the fixed caller
no longer produces — it passed by a different mechanism than the comment
claimed, and the scenario the fix actually exists for had no coverage at all.
Reverting MobRootView.swift left CI green. Case 4 now describes the retained-
token model, and 4b covers the untested case: outgoing writes refused AND no
incoming tracker ever claims the id, so the retained token is the only thing
that can clean up. Both caller behaviours are modelled side by side, since the
difference lives in Swift and this harness only has the C half.
- lib/mob/test.ex: the Android caveat said parity was "tracked separately"
without naming an issue, unlike every other cross-reference here. Filed and
cited MOB-105.
- CHANGELOG: fixed the dangling "note the Unreleased entry above" in 0.7.29,
left pointing at a heading the previous commit renamed.
Preflight: format, 1111 tests, clang-format, native harness all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
First run of the release gate that landed in 0.7.30. Reviewing
0.7.29..mastercaught three things in the MOB-103 work that 0.7.30 already shipped.1. iOS frame tracker lost its ownership token on a refused write (real bug)
recordassignedbox.sequnconditionally, so a generation-refused write (returns 0) overwrote a valid seq with 0 — andmob_unregister_frameignores seq 0, permanently disabling that tracker's own cleanup.Failure: push from a screen tagging
:id"save"to one whose"save"is a lazy row below the fold. The outgoing screen's.movewrites are refused by the generation gate,box.seqbecomes 0, its.onDisappearis a no-op, the id stays in the tree so the purge keeps it — andMob.Test.tap_idtaps the old screen's coordinates.Keeping the last successful seq is strictly safe: the compare-and-delete still refuses to delete whenever an incoming tracker has claimed the id since. The
.opacity(reset) transition doesn't hit this, which is the tell — it doesn't move the outgoing frame, so nothing gets refused.2.
element_frames/1docs implied cross-platform parity that doesn't existThe drop-when-not-laid-out behaviour is iOS only. Android clears frames wholesale on a navigation transition and never per element (
MobBridge.setRootJson), so a scrolled-awaylazy_listrow keeps its frame andtap_idhappily taps it. Verified in the template source. Docs now say so plainly instead of implying parity; Android parity wants its own issue.3. 0.7.30 published with empty release notes
CHANGELOG's top section was still
## [Unreleased]when 0.7.30 was tagged, sorelease.yml's## [<version>]extraction found nothing. Retitled to[0.7.30]so the notes exist for anyone reading back. Exactly the version/release-seam class the gate exists to catch.Also
Wires
make -C test/native runinto the macOS CI job. That harness is the only automated guard on the registry's ownership/generation/purge rules, and it reproduces the algorithm rather than linking it — running it is what keeps the copy honest. It was committed in 0.7.30 but nothing ran it.Preflight
mix format --check-formatted,mix credo --strict,mix compile --warnings-as-errors, 1111 tests,clang-format, and the native harness (20/20) all clean.🤖 Generated with Claude Code