A deadline that had already passed
- android
- capacitor
- maintenance
- operations
On Monday 8 September I opened the Play Console to change an email address. Two minutes of work. While the page was open I read a grey line under the KitDesk listing that said to update the target API level by 31 August 2026 in order to release updates.
That date was a week gone.
Google Play requires an app to target Android 16, API level 36, when you publish it or when you update it. KitDesk targeted 35. Nothing was broken for anybody using it. The app was installed, it worked, the API behind it was up, and the store page looked exactly as it had in June. What had quietly stopped was my ability to change any of that. There is no error until you try to ship, and I had not tried to ship since the summer. It is the same gap I wrote about on the VPS, where three kernels sat installed and none of them were running: the state on disk and the state in use are different questions, and only one of them has a dashboard.
Google does allow an extension request to 1 November 2026, which I did not know at the time and have not used.
The number was not the fix
The obvious move is to open android/variables.gradle, change 35 to 36, and push. It would have built. It would also have shipped an app with its own header sitting underneath the status bar.
I know that because I had left myself a note about it. There was a values-v35/styles.xml in the repo setting android:windowOptOutEdgeToEdgeEnforcement, with a comment above it saying that Google removes that opt-out at API 36, and that the real fix is Capacitor 7.
Here is what that attribute was doing. From Android 15, apps draw edge to edge by default, under the status bar at the top and the navigation bar at the bottom. The opt-out attribute switched that off. At target 36 the attribute is ignored entirely, so the file was no longer protecting anything, and Capacitor 6 does not apply window insets on its own. Bump the number alone and the layout goes under the clock on every phone running a recent Android.
That comment is the only reason this was an afternoon rather than a week and a bad review.
What a version bump actually contained
Seven changes, and one of them is the one everybody quotes.
- Capacitor 6.2.1 to 7.6.9, across core, android, ios and cli, with splash-screen at 7.0.5
minSdkVersion22 to 23, because Capacitor 7 requires it, which drops Android 5.1compileSdkVersionandtargetSdkVersion35 to 36- Android Gradle Plugin 8.7.2 to 8.9.3, in two separate files
- Gradle wrapper 8.9 to 8.11.1, because AGP 8.9 needs it
- Java 17 to 21 in
codemagic.yaml values-v35/deleted, and"adjustMarginsForEdgeToEdge": "force"added tocapacitor.config.json
Three of those are worth expanding, because each one has a way of being wrong that still compiles.
Capacitor 7 does not target 36 for you. I read the defaults out of node_modules/@capacitor/android/capacitor/build.gradle rather than out of a release note, and the version installed on my disk, 7.6.9, still defaults compileSdk and targetSdkVersion to 35. It defaults minSdkVersion to 23 and compiles at JavaVersion.VERSION_21. So the upgrade everybody calls the fix for API 36 supplies the machinery for 36 and not the number itself. You set that yourself, in your own variables.gradle, and if you assume the framework carried it you ship 35 again.
“force” rather than “auto”. Capacitor’s inset handling has an automatic mode, and automatic sounds like the safe choice. It decides by checking for Android 15 and for that same opt-out attribute. Neither of those tests means anything once the app targets 36, so the mode that looks cautious is the one that reasons from two dead facts. If that config line ever gets tidied away, the header goes back under the clock.
The Gradle plugin lives in two files. android/build.gradle and android/capacitor-cordova-android-plugins/build.gradle both declare it. Change one and you have two versions of AGP on one classpath.
The other thing on the same screen
The reason I had opened the console in the first place turned out to be worse than the deadline, in a smaller way.
Two of my five published listings gave a support email address on a domain with no DNS behind it. KitDesk’s was on a domain I stopped running last year, the un-hyphenated near-twin of this site’s, which my own llms.txt explicitly tells crawlers belongs to a different person. Pocket Reset’s was on a domain that has never resolved at all. Google requires a working support address on every listing. Nothing checks that it works.
So for months, anyone who tapped the support link and wrote in got a bounce, or more likely got nothing they understood, and concluded the app was abandoned. I have no way of knowing how many. Both now point at an address on a domain with real MX records, and saving that field publishes to the live listing immediately with no review. The fix took about forty seconds. That is the part I keep thinking about.
What I have not done
Nothing above has been compiled or run. This machine has no JDK and no Android SDK, because the builds run on Codemagic, so what I have is a set of changes that are correct as far as reading the source can establish and not one line further. The edge-to-edge behaviour needs looking at on a real phone before that release goes anywhere near production. It is precisely what the deleted file existed to prevent, and a visual regression on a live app is a worse outcome than being a fortnight late.
The honest status is: it should be right, and it is not yet proven.
None of this was a failure of the code. The code did not change. The platform moved, the requirement moved with it, and the only notice was a line of grey text on a page I had no reason to open. That is the shape of most maintenance work. It is also why my website care plan exists, which covers sites rather than app store releases, but sells the same thing: somebody reading the platform’s notices on a Monday, rather than the customer finding out first.
