MOB-40: reversible plugin merges into host build files (managed blocks)#33
Conversation
Plugin <uses-permission>s, AndroidManifest <application> components, and Gradle deps are spliced into host-owned, hand-edited files (AndroidManifest.xml / build.gradle), so a plain append-if-absent merge can't be undone — a removed plugin's lines linger (dangling <service> whose class is gone, orphan permission/dep). Unlike bridge_kt/res_files there's no ledger to prune a shared file. New MobDev.Plugin.ManagedBlock fences each contribution in begin/end marker comments and regenerates the whole region every build from the current plugin set: a removed plugin isn't in the fresh region so its lines vanish; content outside the fence is untouched. Line-based insert + strip give an exact round-trip (strip(place(x)) == x), so re-running is a fixed point. All three merge sites (merge_android_permissions / _manifest_components / _gradle_deps) rewired to it; still de-dupe against host-declared entries. Tests: ManagedBlock unit (insert/idempotent/replace/remove, strip round-trip) + removal + no-duplicate tests at all three sites. 427 plugin tests + 198 native_build/managed_block pass; credo --strict clean. Also relocates the MOB-39 + new_plugin-scaffold CHANGELOG entries from the already-released 0.6.15 (a squash-merge misfile) to [Unreleased]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review — approve-with-fixes (design is right; one must-fix + a scope gap)The managed-block / strip-and-regenerate approach is the correct shape, and one subtle thing is done well: each merge site dedupes against Two real gaps, one of them a must-fix: 🔴 Must-fix —
|
F1 (blocker): ManagedBlock.strip anchored on first-BEGIN→first-END, so a stray/duplicate BEGIN (interrupted write, hand-edit, bad merge) made it delete everything from the orphan through the next region's END — eating host lines between. Now anchors on the LAST BEGIN before the FIRST END (the removed span provably contains no other marker) and loops to clear duplicates. Regression tests: orphan-BEGIN-before-region keeps host lines; duplicate regions cleared; trailing orphan left alone. F2: pre-existing UNFENCED entries (from an older mob_dev's append) are indistinguishable from hand-authored ones, so MOB-40 is forward-only — softened the CHANGELOG claim and added a test documenting they're neither re-added nor auto-removed. Nits: EOF fallback now appends whole lines so strip round-trips; renamed the stale 'after the LAST uses-permission' test to the managed-region behavior. 617 plugin/native_build tests pass; credo --strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed fe15abb addressing both. 🔴 F1 (blocker) — orphan-BEGIN data loss → FIXED. Nice catch.
🟡 F2 — forward-only → wording softened + test. You're right that already-generated apps carry the old unfenced lines, and there's no safe migration: an unfenced entry is byte-identical whether an older mob_dev appended it or the host hand-wrote it — the exact ambiguity that motivated fencing means we can't reclaim them without risking deleting a host line. So I've made the claim accurate rather than pretend otherwise: the CHANGELOG now says reversible going forward, and a new test documents that a pre-existing unfenced entry is treated as host-authored (not double-added, not auto-removed) — regenerate or hand-remove to fence it. Nits: the EOF fallback (pathological manifest, no 617 plugin/native_build tests pass; |
Re-review (fe15abb) — fixes verified, approving ✅F1 (blocker) — resolved, and the fix is provably correct. F2 — resolved correctly by scoping the claim. Agreed with the reasoning: a pre-existing unfenced entry is byte-identical whether an old mob_dev appended it or the host hand-wrote it, so auto-reclaiming it would risk deleting a host line — the very ambiguity fencing exists to resolve. Documenting it as reversible going forward (+ the test that a pre-existing unfenced entry is treated as host-authored: not doubled, not auto-removed) is the honest, safe call rather than an unsafe backfill. Nits — EOF fallback now round-trips, stale test renamed. Good. 617 tests + credo clean, CI green, CLEAN. LGTM — approving for merge. Nice, thorough turnaround. |
Follow-up from #32 review (F4). Makes plugin contributions to host-owned build files reversible on plugin removal.
Problem
Plugin
<uses-permission>s, AndroidManifest<application>components (new in #32), and Gradle deps are spliced intoAndroidManifest.xml/build.gradle— files the host also hand-edits. A plain "append if absent" merge can't be undone, so a removed plugin's lines linger: a dangling<service>whose class is gone, an orphan permission/dep. Unlike the copied artifacts (bridge_kt,res_files) there's no ledger to prune, because the target is a shared, hand-authored file.Fix
New
MobDev.Plugin.ManagedBlock: fence each contribution in begin/end marker comments and regenerate the whole region every build from the current plugin set. A removed plugin simply isn't in the fresh region, so its lines disappear; anything outside the fence is untouched.insert_before/insert_before_index) andstrip/2removes exactly those lines, sostrip(place(x)) == xand re-running with an unchanged set is a fixed point.merge_android_permissions,merge_android_manifest_components,merge_gradle_deps) rewired to it — one consistent mechanism across permissions + components + deps, as the reviewer suggested rather than a component-only prune.//for Gradle.Tests
ManagedBlockunit tests (insert / idempotent / replace / remove / strip round-trip / malformed) + removal + no-duplicate tests at each of the three sites (e.g. "removing the plugin strips its managed region, keeping host perms"). 427 plugin tests + 198 native_build/managed_block pass;mix credo --strictclean.Also
Relocates the MOB-39 +
new_plugin-scaffold CHANGELOG entries from the already-released0.6.15(2026-06-23) to[Unreleased]— they were absorbed there by #32's squash-merge against a moved-on master.🤖 Generated with Claude Code