fix(desktop): add Dock icon and fix undeletable failed clusters#4896
fix(desktop): add Dock icon and fix undeletable failed clusters#4896devantler wants to merge 3 commits into
Conversation
The KSail.app bundle never carried an icon: the Info.plist had no CFBundleIconFile key and neither make-macos-app.sh nor the desktop GoReleaser config placed an .icns in Contents/Resources, so macOS showed the generic application icon in the Dock and Finder. Add make-icns.sh, which generates AppIcon.icns from a square source PNG (desktop/resources/icon.png, the KSail logo) using sips + iconutil. This is macOS-only, which is fine because the .app bundle is only ever built on macOS. Wire it into the Info.plist (CFBundleIconFile=AppIcon), the local make-macos-app.sh build, and the release GoReleaser post-hook + archive so both local and Homebrew-cask bundles ship the icon. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oved A cluster left in the Failed phase by a failed create could never be removed from the web UI / desktop app. Deleting it returned 204 but the background job then called provisioner.Delete, which returned ErrClusterNotFound (the failed create produced no deletable cluster), so the entry was re-pinned Failed. The row could only be cleared by restarting the app or falling back to `ksail cluster delete --name`. Make runDelete idempotent: probe Exists first and treat "already gone" as success so the tracked entry is cleared. Genuine deletion errors still surface as Failed. Adds regression tests covering both paths (verified the new test fails without the fix). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes two desktop-app issues: (1) missing macOS Dock/Finder icon for the KSail.app bundle, and (2) inability to delete UI-tracked clusters that are left in Failed with no underlying cluster to delete (desktop app and ksail ui share this backend).
Changes:
- Make local-cluster deletion effectively idempotent by probing
Provisioner.Existsbefore callingDelete, so “already gone” clears the tracked entry. - Add regression tests ensuring failed/no-underlying clusters can be deleted, while real delete failures still surface as
Failed. - Add macOS icon generation (
.icns) and wire it into both local.appassembly and the desktop GoReleaser pipeline, plus emitCFBundleIconFilein the bundle plist.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/cli/clusterapi/local_service.go | Updates async delete workflow to clear UI entries when the underlying cluster does not exist. |
| pkg/cli/clusterapi/local_service_test.go | Expands the fake provisioner behavior and adds regression/guard tests for delete semantics. |
| desktop/scripts/make-macos-app.sh | Generates and embeds AppIcon.icns into the local .app bundle during assembly. |
| desktop/scripts/make-info-plist.sh | Adds CFBundleIconFile so macOS uses the embedded icon. |
| desktop/scripts/make-icns.sh | New script to generate AppIcon.icns from a source PNG via sips/iconutil. |
| .goreleaser.desktop.yaml | Adds icon generation hook and includes the .icns in the archived .app bundle. |
❌MegaLinter analysis: Error❌ COPYPASTE / jscpd - 7 errors❌ REPOSITORY / osv-scanner - 1 error❌ ACTION / zizmor - 1 error✅ Linters with no issuesactionlint, bash-exec, git_diff, hadolint, jsonlint, lychee, markdown-table-formatter, markdownlint, prettier, prettier, shellcheck, shfmt (1 fix), stylelint, syft, trivy-sbom, trufflehog, v8r, v8r, yamllint Notices📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining See detailed reports in MegaLinter artifacts
|
Address review feedback: replace the Exists pre-check in runDelete with unconditional Delete + errors.Is(err, clustererr.ErrClusterNotFound). The pre-check duplicated the provisioners' own existence check and left a TOCTOU window (Exists could return true and the cluster vanish before Delete, returning ErrClusterNotFound and re-pinning the job Failed). Handling the not-found error directly is race-free and covers the failed -create, deleted-out-of-band, and TOCTOU cases alike. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Coverage OverviewLanguages: Go Go / code-coverage/goThe overall coverage in the branch is 55%. Coverage data for the branch is not yet available. Show a code coverage summary of the most covered files.
Code Coverage is in Public Preview. Learn more and provide us with your feedback. |

Fixes two issues reported in the KSail desktop app.
1. Missing Dock icon
Root cause: the
KSail.appbundle never carried an icon — the Info.plist had noCFBundleIconFilekey, and neithermake-macos-app.shnor the desktop GoReleaser config placed an.icnsinContents/Resources. macOS therefore showed the generic application icon in the Dock and Finder.Fix:
desktop/scripts/make-icns.shgeneratesAppIcon.icnsfrom a square source PNG viasips+iconutil(macOS-only — fine, the.appis only built on macOS).desktop/resources/icon.png(the KSail logo; the only square icon in the repo — the docs logos are wide wordmarks).make-info-plist.shnow emitsCFBundleIconFile = AppIcon.make-macos-app.shbuild and the release GoReleaser post-hook + archive, so local and Homebrew-cask bundles both ship the icon.No Go change needed. Verified that assembling the bundle yields a valid 1024px
AppIcon.icnsatContents/Resources/AppIcon.icnswith the matching plist key.2. Failed clusters can't be deleted
Root cause (reproduced against the live
uiserverbackend): a cluster left in theFailedphase by a failed create could never be removed from the UI.DELETEreturned 204, but the async job then calledprovisioner.Delete, which returnedErrClusterNotFound(a failed create produces no deletable cluster), so the entry was re-pinnedFailed. The only escapes were restarting the app orksail cluster delete --name <name>.Fix:
runDeleteinpkg/cli/clusterapi/local_service.gois now idempotent — it probesExistsfirst and treats "already gone" as success so the tracked entry clears. Genuine deletion failures still surface asFailed. This also benefitsksail ui, which shares the same backend.Tests: added
TestDeleteClearsFailedClusterWithNoUnderlyingCluster(the regression) andTestDeleteKeepsClusterFailedWhenDeletionErrors(guards against masking real failures). Confirmed the regression test fails without the fix (error="cluster not found", matching the live repro). The test fake'sDeletenow mirrors real provisioners (returnsErrClusterNotFoundfor an absent cluster).Validation
go build ./...,go test -race ./pkg/cli/clusterapi/...,golangci-lint run, andshellcheckon the scripts all pass. Reproduced and fixed against real Kind/K3d clusters viaksail ui+ the REST API; all test clusters cleaned up.Out of scope (separate, confirmed gap)
Deleting a CLI-created cluster (one with KSail mirror-registry containers) via the desktop removes the node but orphans the
<name>-docker.io/-ghcr.ioregistry containers — the CLI delete cleans these up, the desktop path doesn't. Happy to address in a follow-up.🤖 Generated with Claude Code