Symptom

Krill Desktop built .deb (Linux) and .dmg (macOS) artifacts, and the Compose nativeDistributions block listed TargetFormat.Msi with packageName/vendor already set — but no pipeline ever produced a Windows installer, so Windows users had nothing to download.

Root cause

The gap was infrastructure, not application code: jpackage can only emit a .msi from a Windows host, and there was no CI job running on a Windows runner. Windows desktop is the same jvm("desktop") target as Linux/macOS, so no new Kotlin target or expect/actual was needed — only a Windows build host, a little packaging metadata (icon, upgrade GUID, Start-menu group), and a publish workflow.

Fix

composeApp/build.gradle.kts: add a windows { } block under nativeDistributionsiconFile → a new src/desktopMain/resources/icon.ico (generated from the same 1024×1024 source art as the macOS .icns), a stable upgradeUuid (30894E47-…) so newer installers upgrade in place rather than side-by-side, menuGroup = "Krill", and perUserInstall = true (no admin elevation). packageVersion already reads version.txt via desktopPackageVersion, so the in-MSI version tracks the release for free. New .github/workflows/Deploy Windows.yml runs on the GitHub-hosted windows-latest runner, installs the WiX Toolset v3 (jpackage’s MSI backend, no longer preinstalled on the image), builds :composeApp:packageReleaseMsi, and publishes versioned + latest keys to s3://cms.krill.systems/distro/windows/ with a CloudFront invalidation (E1MKRDHOFUF59Y, the same distribution the macOS/ screenshot/SDK-docs workflows use). A signtool Authenticode step is gated on a WINDOWS_SIGNING_CERT secret — v1 ships unsigned. A Download-category post documents the link and the SmartScreen “More info → Run anyway” steps.

Prevention

Pending Windows-host verification (tracked on the PR, not yet run here)

The build config, workflow, icon, and docs were authored on Linux. The following require a Windows machine and are checklisted on the PR for manual completion before the first publish is trusted: