Turning a Compose Multiplatform wasmJs app into a PWA with
deliberate software updates and a correctly separated data cache — the files,
ready to copy.
This is the companion bundle to the article Shipping a Kotlin Compose Multiplatform app as a PWA. The article argues the why; these are the files that implement it.
You do not need Claude Code to use this.
It is packaged as a skill for Claude
Code, because that is how it was built and applied to a real production
app. Ignore that framing and what remains is a folder of plain files with
{{PLACEHOLDER}} markers, plus prose you can read straight
through: SKILL.md works as an ordinary step-by-step
guide.
If you do use Claude Code, drop this folder into
.claude/skills/ and it will run the conversion, ask which update
notification you want, and audit an existing data layer against the checks in
Phase B.
| SKILL.md | The procedure, end to end: retiring an older service worker, precaching, the startup gate, the update notification, then the data-layer audit and the server headers. |
| references/ |
Why the update flow is built the way it is — the startup gate, why intent is captured at updatefound, and how to reach clients stuck behind an old cache-first worker. |
| references/ |
The caching model, offline behaviour, the server cache headers, and the verification checklist. |
| references/ |
The four update-notification styles and their trade-offs, plus how a server-driven minimum-version gate differs from all of them. |
| registerServiceWorker.js | Registration, the startup gate that stops the old version flashing up, and the deferred-activation logic behind the “New Software” affordance. |
| workbox-config-for-wasm.js | Precaches the software and nothing else — deliberately no runtimeCaching. |
| legacy-sw-tombstone.js | Retires a previously deployed hand-written worker, including clients that can no longer load your new page. |
| index.html · manifest.json | Page shell with the splash element, and the web app manifest. |
PwaUpdate.*.kt |
The expect/actual bridge between the service worker and Compose — common, wasmJs, no-op for other targets. |
| app-update-ui.kt | The pulsating button and confirm dialog. |
| offline-indicator.kt | Data-status indicator variants; pick one and delete the rest. |
Store.*.kt |
An app-owned data cache for projects that have none — common, wasmJs (Cache API), jvm (files), memory. Most apps should keep what they already have. |
| data-poll-template.kt | The conditional-GET request shape, worth reading even if you keep your own layer. |
| wasmJs-main.kt | The main() that awaits the startup gate before mounting Compose. |
| gradle-wiring.md | Version-catalog entries and the plugin to apply. |
| scripts/latest_version.sh | Resolves the newest published version of a plugin or artifact, so nothing is hardcoded. |
Validated 2026-07-25 against Kotlin 2.4.10, Compose Multiplatform 1.11.1, Gradle 9.5.1, Ktor 3.5.1, Workbox 7.4.0 and the ComposePWA plugin 0.7.0-alpha03, by converting a real production app and deploying it.
A few claims are pinned to those versions and deserve re-checking if you are
far from them: the JsException behaviour of a failed fetch, the
cleanupOutdatedCaches predicate, and anything about the plugin,
which is a 0.x alpha. SKILL.md says which, and why.