fix(navigation): preserve initial transition - #103
Conversation
GenericJam
commented
Aug 30, 2026
Adversarially reviewed and device-verified. Approve. Traced every interleaving of the new one-shot reservation (render cast before/after activate, superseded frames carrying the transition, crash-restart, switch_tab): each animates the reserved transition exactly once or drops the stale pre-activation frame. Host: focused suites 109/109, full suite 1260 passed / 0 failures. Device (pool sim, screen-recorded): a probe screen firing 3 self-messages from mount slides in from the right already showing 'repaints: 3' — the reserved transition survived the race, no unanimated flash, no double transition. reset_to overrides verified both directions. Zero file overlap with the #98/#92/#91 merges; merge-tree clean. Non-blocking follow-ups: (1) set_active/1's doc still claims the router calls it — it's now test-only, update or deprecate; (2) no unit test pins the mismatched-ref take_transition clause (stale outgoing-screen repaint must not consume the incoming reservation) — implicitly covered end-to-end, worth a one-liner; (3) an explicit non-:none transition on first render silently overrides the reservation — unreachable today, deserves a comment on the precedence choice; (4) activate/2 spec could use Mob.Socket's transition() type. Downstream note: with #100 + this, directional reset wipes are fully usable; switch_tab/2 intentionally carries no transition, so directional tab wipes go through reset_to/4. Merging. |
Uh oh!
There was an error while loading. Please reload this page.
…emantics navigation.md still described tab_bar/drawer as rendering native chrome (UITabBarController / NavigationBar) — since 0.7.33 the runtime backs the declaration with real per-stack state but draws no chrome; switching is programmatic. New 'Tabs and multi-stack state' section documents lazy materialization, parking, independent histories, back-at-secondary-root, the orphan stack, and the MOB-115/116/117 gaps. Directional-reset docs gain the ArgumentError validation and transition-survives-coalescing behavior (#100/#103). screen_lifecycle.md gains crash/restart semantics (per-screen isolation, restart cap, re-mount + load_state), per-screen self() and message delivery, terminate/2 reality (pop stops the leaving screen only), and multi-stack system-back. Mob.App.tab_bar/1 and drawer/1 docstrings no longer claim chrome that is not drawn. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eature coverage (#105) * docs(navigation): multi-stack state, honest tab/drawer claims, back semantics navigation.md still described tab_bar/drawer as rendering native chrome (UITabBarController / NavigationBar) — since 0.7.33 the runtime backs the declaration with real per-stack state but draws no chrome; switching is programmatic. New 'Tabs and multi-stack state' section documents lazy materialization, parking, independent histories, back-at-secondary-root, the orphan stack, and the MOB-115/116/117 gaps. Directional-reset docs gain the ArgumentError validation and transition-survives-coalescing behavior (#100/#103). screen_lifecycle.md gains crash/restart semantics (per-screen isolation, restart cap, re-mount + load_state), per-screen self() and message delivery, terminate/2 reality (pop stops the leaving screen only), and multi-stack system-back. Mob.App.tab_bar/1 and drawer/1 docstrings no longer claim chrome that is not drawn. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(testing): document Mob.ScreenCase and settle/2; remove nonexistent screen_pid/1 The testing guide never mentioned Mob.ScreenCase (#44), the blessed in-BEAM unit-test path — it now leads the guide. The old sync-point advice referenced Mob.Test.screen_pid/1, which does not exist, and :sys.get_state on :mob_screen, which stopped being sufficient when rendering moved to Mob.Sender (MOB-110) and :mob_screen became the navigation owner (MOB-112); both are replaced with Mob.Test.settle/2 and an explanation of the three processes it drains. Unit-test examples updated for the process model (dispatch is synchronous; get_socket is the natural sync point after send). Mob.Test's moduledoc claimed tap/2 goes through handle_event/3; it sends {:tap, tag} to handle_info/2 like a real native tap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(components,theming): Sheet section, handle-pool limits, font tokens components.md had no coverage of Mob.UI.sheet/2 (0.7.29) or intrinsic content detents (0.7.32) beyond the surface-matrix row — new ':sheet' section documents presence-is-presentation, detents including [:content] / [{:content, max_height: n}], exactly-once {:dismiss, tag}, and the iOS scrim limitation. New 'Handle limits' section covers the 256-handle tap pool and the 256-slot native component pool with {:error, :component_slots_exhausted} (0.7.28 behavior). :text gains the font prop (named font tokens, 0.7.25). theming.md never mentioned fonts — adds the fonts:/font_fallback: token type with a pointer to Styling → Custom fonts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: fix tap examples to handle_info/2 and stale module/function names README and several guides showed UI taps handled by handle_event("tap", %{"tag" => ...}) — a real tap delivers {:tap, tag} to handle_info/2, so those example screens would never respond on a device. README's diagram and testing snippet updated for the per-screen process model and Mob.ScreenCase. getting_started referenced Mob.Nav.push/2, which does not exist (Mob.Socket.push_screen is the API). event_audit's list-select re-emitter is Mob.Screen.Server since MOB-113. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agentic): split into single-agent and agent-team halves; new practices Part 1 (Working with one agent) keeps the existing content in order and adds: verify effects not exit codes (assert the app answers after a deploy), the honesty contract (success = a handler ran — assert on state change after a tap, settle-window caveat), match the evidence to the question (frames for layout, screenshots-with-tolerance for appearance, recordings for motion), lifecycle-event simulation recipes (simctl push .apns, adb broadcast / cmd notification post), and environment discipline (complete .tool-versions incl. zig/JDK, the MOB_DIR/MOB_DEV_DIR/MOB_NEW_DIR override chain). Part 2 (Working with agent teams) is new: one driver per device with lease discipline (humans outrank agents), unique node names per session (mob.connect --name), per-task git worktrees, the mob.push/mob.watch fan-out hazard (they reach every live node, no device scoping — fleets deploy per device or push over their own dist connection), and durable artifacts as the handoff medium between context windows. The standard agent loop gains a settle/2 step before native-side reads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(mix): group new modules in hexdocs sidebar Mob.Router joins Navigation; Mob.Screen.Server, Mob.Listener and Mob.Sender get a Runtime Processes group; Mob.ScreenCase joins Testing & Debugging. All five were shipping ungrouped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: document #80's honest tap returns and pixel sampling Written minutes before #80 merged, the honesty-contract and evidence-matching sections claimed no pixel-sampling API existed and leaned solely on state-change assertions. Now that tap_xy/3 reports observed effect, both guides document the contract: :ok only when an event reached the BEAM within 300ms, else {:error, :no_view_at_point | :no_element_at_point | :no_effect} — plus the platform limits that make :no_effect legitimate (SwiftUI on_tap containers, physical-device injection) and the serial-harness assumption the 300ms window shares with state-change checks. Evidence matching now splits exact-color decisions (sample_color/2: real pixels, dominant/average as 0xAARRGGBB, iOS debug-build only) from holistic visual parity (screenshots with tolerance). testing.md gains matching sections. No change needed for #77: push_notifications.md already described tap-to-open from a killed app, which that fix made true. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…emantics navigation.md still described tab_bar/drawer as rendering native chrome (UITabBarController / NavigationBar) — since 0.7.33 the runtime backs the declaration with real per-stack state but draws no chrome; switching is programmatic. New 'Tabs and multi-stack state' section documents lazy materialization, parking, independent histories, back-at-secondary-root, the orphan stack, and the MOB-115/116/117 gaps. Directional-reset docs gain the ArgumentError validation and transition-survives-coalescing behavior (#100/#103). screen_lifecycle.md gains crash/restart semantics (per-screen isolation, restart cap, re-mount + load_state), per-screen self() and message delivery, terminate/2 reality (pop stops the leaving screen only), and multi-stack system-back. Mob.App.tab_bar/1 and drawer/1 docstrings no longer claim chrome that is not drawn. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Verification
Downstream tracking: app-6kn