craft.setBadge(count) works on iOS and silently does nothing on Android.
The Kotlin
@JavascriptInterface
fun setBadge(count: Int) {
// Android doesn't have native badge support, but we can use notification channels
// This is a simplified implementation - for full support, use ShortcutBadger library
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = NotificationManagerCompat.from(activity)
// Badge is typically shown via notification count
// Full implementation would require creating a notification
}
} catch (e: Exception) {
log("Badge not supported: ${e.message}")
}
}
@JavascriptInterface
fun clearBadge() {
setBadge(0)
}It constructs a NotificationManagerCompat, discards it, and returns. count is never read. clearBadge delegates to it.
Why it is silent rather than merely broken
Every part of the surface reports success:
- the injected JS exposes it —
setBadge: function(count) { CraftAndroid.setBadge(count) } craft.d.ts:506declaressetBadge(count: number): voidfor both platforms- the
catchonly fires on an exception, and nothing here throws — so not even a log line
And the same call works on iOS: bridge_mobile_display.zig implements setBadge/clearBadge for real, including badge authorization. So a page that sets an unread count is correct on one platform and quietly wrong on the other, with nothing anywhere to indicate which.
Same class as the bugs this repo has been finding
- the
ota*methods — page-callable, no handler behind them registerBackgroundTask— inserts into aSetnothing reads, resolvesregistered: true, and never callsBGTaskScheduler.registercraftShortcut/craftOTAProgress/craftOTAStatus(#127) — subscribed by both bridges, dispatched by neither
The shared shape is an implementation that returns successfully without doing the thing.
What to do about it
Android genuinely has no first-party badge API — the comment is right about that. Launchers implement it through vendor-specific broadcasts, which is what ShortcutBadger wraps, and there is no supported route that works across OEMs.
So the honest options are to implement it via a notification with setNumber, which is what NotificationManagerCompat was reached for and would at least do something on launchers that read notification counts; or to make it refuse, so a page can tell. What it should not do is what it does now.
Meanwhile
Both are recorded in deliberate_deferrals in android_conformance_test.zig with the anti-rot guards the iOS table uses — a row Zig starts serving fails the build, and so does a row naming an action the Kotlin no longer has. Migrating them to Zig now would mean porting a no-op, which is what the iOS table already refuses to do for startVideoRecording (#115) for the same reason: there is no working contract to carry across.