Переглянути джерело

M9: app icon, version stamping, install notes

Draw the icon (a 4x4 pad grid with pad 1 lit) with tools/icon and
generate the Windows and macOS icons from it; replace the Wails-branded
macOS assets. Stamp production builds with git describe and set the
build files to 0.5.0. Add what-it-does and per-system install notes to
the README. Remove unused iOS assets and template placeholders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude 1 день тому
батько
коміт
9fe5f8700e

+ 21 - 0
README.md

@@ -2,6 +2,23 @@
 
 Application to create basic MPC Sample starter projects from your desktop.
 
+**v0.5.0 is a beta for testing.** Expect rough edges, and keep copies of projects you care about.
+
+## What it does
+
+- Build kits for the Akai MPC Sample on your computer: assign WAV files to pads (banks A–H, 16 pads each), tune them, set mute groups, and move, copy or swap pads.
+- Set up sequences: name, tempo and length. Notes are recorded on the device.
+- Load a project saved on the device, change it, and export it again.
+- Export to a folder (for example straight onto the device's card) or a zip. A project is a `.xpj` file plus a `<name>_[ProjectData]` folder with the samples.
+
+## Install
+
+Builds are not signed yet, so each system warns the first time you open mpckit.
+
+- **Windows 10 and 11:** run `mpckit.exe`. If SmartScreen says "Windows protected your PC", choose **More info**, then **Run anyway**. mpckit uses the WebView2 runtime, which is part of Windows 11 and current Windows 10.
+- **macOS:** move `mpckit.app` to Applications and open it. macOS will say it can't check the app for malicious software. Open **System Settings → Privacy & Security**, scroll to the message about mpckit and choose **Open Anyway**, then confirm.
+- **Linux:** make the file executable (`chmod +x mpckit`) and run it. It needs the GTK 4 and WebKitGTK 6.0 libraries, which current desktop distributions include or offer in their package manager.
+
 ## Development
 
 mpckit is a [Wails v3](https://v3.wails.io) app: Go for all file work, Svelte + TypeScript for the window.
@@ -23,6 +40,10 @@ wails3 build       # builds bin/mpckit (bin\mpckit.exe on Windows)
 
 The first run installs the frontend packages and generates `frontend/bindings` from the Go service.
 
+`wails3 build` stamps the version from the latest git tag (`git describe`), so a build of the `v0.5.0` tag shows "Version v0.5.0" in About. Builds of other commits show the tag plus the commit, and `wails3 dev` shows "dev".
+
+The app icon is drawn by `go run ./tools/icon build/appicon.png`. The Windows and macOS icon files are generated from that PNG during the build.
+
 Tests: `go test ./...`. On Linux without the GTK packages, use `CGO_ENABLED=0 go test -tags purego ./...` instead.
 
 `go run ./cmd/mpckit-cli` builds a project from the command line, `go run ./tools/checkpoint-a` builds the device test pack (see its README), and `go run ./tools/xpjdiff A.xpj B.xpj` compares two projects (the .xpj contents and the data folders).

+ 4 - 0
Taskfile.yml

@@ -2,6 +2,10 @@ version: '3'
 
 vars:
   APP_NAME: "mpckit"
+  # Shown in the About dialog of production builds: the git tag (v0.5.0) on
+  # a tagged commit, otherwise the tag plus commits since and the commit id.
+  VERSION:
+    sh: git describe --tags --always --dirty 2>/dev/null || echo dev
   BIN_DIR: "bin"
   PACKAGE_MANAGER: '{{.PACKAGE_MANAGER | default "npm"}}'
   VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'

+ 2 - 3
build/Taskfile.yml

@@ -191,17 +191,16 @@ tasks:
       - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true{{if eq .OBFUSCATED "true"}} -obfuscated{{end}} -ts -i
 
   generate:icons:
-    summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms).
+    summary: Generates the Windows `.ico` and Mac `.icns` from appicon.png (drawn by `go run ./tools/icon build/appicon.png`).
     run: once
     dir: build
     sources:
       - "appicon.png"
-      - "appicon.icon"
     generates:
       - "darwin/icons.icns"
       - "windows/icon.ico"
     cmds:
-      - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin
+      - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
 
   dev:frontend:
     summary: Runs the frontend in development mode

+ 0 - 9
build/appicon.icon/Assets/wails_icon_vector.svg

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg width="100%" height="100%" viewBox="0 0 583 533" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
-    <g transform="matrix(1,0,0,1,-246,-251)">
-        <g id="Ebene1">
-            <path d="M246,251L265,784L401,784L506,450L507,450L505,784L641,784L829,251L682,251L596,567L595,567L596,251L478,251L378,568L391,251L246,251Z"/>
-        </g>
-    </g>
-</svg>

+ 0 - 51
build/appicon.icon/icon.json

@@ -1,51 +0,0 @@
-{
-  "fill" : {
-    "automatic-gradient" : "extended-gray:1.00000,1.00000"
-  },
-  "groups" : [
-    {
-      "layers" : [
-        {
-          "fill-specializations" : [
-            {
-              "appearance" : "dark",
-              "value" : {
-                "solid" : "srgb:0.92143,0.92145,0.92144,1.00000"
-              }
-            },
-            {
-              "appearance" : "tinted",
-              "value" : {
-                "solid" : "srgb:0.83742,0.83744,0.83743,1.00000"
-              }
-            }
-          ],
-          "image-name" : "wails_icon_vector.svg",
-          "name" : "wails_icon_vector",
-          "position" : {
-            "scale" : 0.85,
-            "translation-in-points" : [
-              0.0,
-              0.0
-            ]
-          }
-        }
-      ],
-      "shadow" : {
-        "kind" : "neutral",
-        "opacity" : 0.5
-      },
-      "specular" : true,
-      "translucency" : {
-        "enabled" : true,
-        "value" : 0.5
-      }
-    }
-  ],
-  "supported-platforms" : {
-    "circles" : [
-      "watchOS"
-    ],
-    "squares" : "shared"
-  }
-}

BIN
build/appicon.png


+ 2 - 2
build/config.yml

@@ -11,7 +11,7 @@ info:
   description: "Desktop kit editor for the MPC Sample" # The application description
   copyright: "(c) 2026 bajdev" # Copyright text
   comments: "Not affiliated with Akai Professional" # Comments
-  version: "0.0.1" # The application version
+  version: "0.5.0" # The application version
   # cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional)
   #                               # Should match the name of your .icon file without the extension
   #                               # If not set and Assets.car exists, defaults to "appicon"
@@ -24,7 +24,7 @@ info:
 #   # The display name shown under the app icon (CFBundleDisplayName/CFBundleName)
 #   displayName: "My Product"
 #   # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion)
-#   version: "0.0.1"
+#   version: "0.5.0"
 #   # The company/organisation name for templates and project settings
 #   company: "My Company"
 #   # Additional comments to embed in Info.plist metadata

BIN
build/darwin/Assets.car


+ 2 - 2
build/darwin/Info.dev.plist

@@ -17,9 +17,9 @@
 		<key>CFBundlePackageType</key>
 		<string>APPL</string>
 		<key>CFBundleShortVersionString</key>
-		<string>0.0.1</string>
+		<string>0.5.0</string>
 		<key>CFBundleVersion</key>
-		<string>0.0.1</string>
+		<string>0.5.0</string>
 		<key>LSMinimumSystemVersion</key>
 		<string>12.0.0</string>
 		<key>NSAppTransportSecurity</key>

+ 2 - 2
build/darwin/Info.plist

@@ -17,9 +17,9 @@
 		<key>CFBundlePackageType</key>
 		<string>APPL</string>
 		<key>CFBundleShortVersionString</key>
-		<string>0.0.1</string>
+		<string>0.5.0</string>
 		<key>CFBundleVersion</key>
-		<string>0.0.1</string>
+		<string>0.5.0</string>
 		<key>LSMinimumSystemVersion</key>
 		<string>12.0.0</string>
 		<key>NSHighResolutionCapable</key>

+ 1 - 1
build/darwin/Taskfile.yml

@@ -47,7 +47,7 @@ tasks:
       - wails3 tool prepare-macos-output --dir "{{.BIN_DIR}}"
       - '{{if eq .OBFUSCATED "true"}}garble {{.GARBLE_ARGS}} build{{else}}go build{{end}} {{.BUILD_FLAGS}} -o "{{.OUTPUT}}"'
     vars:
-      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
+      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -X git.monoexp.com/bajdev/mpckit/internal/app.Version={{.VERSION}}"{{end}}'
       DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
       OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
     env:

BIN
build/darwin/dmg-background.png


BIN
build/darwin/dmg-file-icon.icns


BIN
build/darwin/dmg-file-icon.png


BIN
build/darwin/icons.icns


+ 0 - 116
build/ios/Assets.xcassets

@@ -1,116 +0,0 @@
-{
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  },
-  "images" : [
-    {
-      "filename" : "icon-20@2x.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "icon-20@3x.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "icon-29@2x.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "icon-29@3x.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "icon-40@2x.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "icon-40@3x.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "icon-60@2x.png",
-      "idiom" : "iphone",
-      "scale" : "2x",
-      "size" : "60x60"
-    },
-    {
-      "filename" : "icon-60@3x.png",
-      "idiom" : "iphone",
-      "scale" : "3x",
-      "size" : "60x60"
-    },
-    {
-      "filename" : "icon-20.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "icon-20@2x.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "20x20"
-    },
-    {
-      "filename" : "icon-29.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "icon-29@2x.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "29x29"
-    },
-    {
-      "filename" : "icon-40.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "icon-40@2x.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "40x40"
-    },
-    {
-      "filename" : "icon-76.png",
-      "idiom" : "ipad",
-      "scale" : "1x",
-      "size" : "76x76"
-    },
-    {
-      "filename" : "icon-76@2x.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "76x76"
-    },
-    {
-      "filename" : "icon-83.5@2x.png",
-      "idiom" : "ipad",
-      "scale" : "2x",
-      "size" : "83.5x83.5"
-    },
-    {
-      "filename" : "icon-1024.png",
-      "idiom" : "ios-marketing",
-      "scale" : "1x",
-      "size" : "1024x1024"
-    }
-  ]
-}

+ 0 - 61
build/ios/Info.dev.plist

@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>CFBundleExecutable</key>
-    <string>mpckit</string>
-    <key>CFBundleIdentifier</key>
-    <string>com.monoexp.mpckit.dev</string>
-    <key>CFBundleName</key>
-    <string>mpckit (Dev)</string>
-    <key>CFBundleDisplayName</key>
-    <string>mpckit (Dev)</string>
-    <key>CFBundlePackageType</key>
-    <string>APPL</string>
-    <key>CFBundleShortVersionString</key>
-    <string>0.0.1-dev</string>
-    <key>CFBundleVersion</key>
-    <string>0.0.1</string>
-    <key>LSRequiresIPhoneOS</key>
-    <true/>
-    <key>MinimumOSVersion</key>
-    <string>15.0</string>
-    <key>UILaunchStoryboardName</key>
-    <string>LaunchScreen</string>
-    <key>UIRequiredDeviceCapabilities</key>
-    <array>
-        <string>arm64</string>
-    </array>
-    <key>UISupportedInterfaceOrientations</key>
-    <array>
-        <string>UIInterfaceOrientationPortrait</string>
-        <string>UIInterfaceOrientationLandscapeLeft</string>
-        <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>UISupportedInterfaceOrientations~ipad</key>
-    <array>
-        <string>UIInterfaceOrientationPortrait</string>
-        <string>UIInterfaceOrientationPortraitUpsideDown</string>
-        <string>UIInterfaceOrientationLandscapeLeft</string>
-        <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>NSAppTransportSecurity</key>
-    <dict>
-        <key>NSAllowsArbitraryLoads</key>
-        <true/>
-        <key>NSAllowsLocalNetworking</key>
-        <true/>
-    </dict>
-    <!-- Development mode enabled -->
-    <key>WailsDevelopmentMode</key>
-    <true/>
-    
-    <key>NSHumanReadableCopyright</key>
-    <string>(c) 2026 bajdev</string>
-    
-    
-    <key>CFBundleGetInfoString</key>
-    <string>Not affiliated with Akai Professional</string>
-    
-</dict>
-</plist>

+ 0 - 80
build/ios/Info.plist

@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>CFBundleExecutable</key>
-    <string>mpckit</string>
-    <key>CFBundleIdentifier</key>
-    <string>com.monoexp.mpckit</string>
-    <key>CFBundleName</key>
-    <string>mpckit</string>
-    <key>CFBundleDisplayName</key>
-    <string>mpckit</string>
-    <key>CFBundlePackageType</key>
-    <string>APPL</string>
-    <key>CFBundleShortVersionString</key>
-    <string>0.0.1</string>
-    <key>CFBundleVersion</key>
-    <string>0.0.1</string>
-    <key>LSRequiresIPhoneOS</key>
-    <true/>
-    <key>MinimumOSVersion</key>
-    <string>15.0</string>
-    <key>UILaunchStoryboardName</key>
-    <string>LaunchScreen</string>
-    <key>UIRequiredDeviceCapabilities</key>
-    <array>
-        <string>arm64</string>
-    </array>
-    <key>UISupportedInterfaceOrientations</key>
-    <array>
-        <string>UIInterfaceOrientationPortrait</string>
-        <string>UIInterfaceOrientationLandscapeLeft</string>
-        <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>UISupportedInterfaceOrientations~ipad</key>
-    <array>
-        <string>UIInterfaceOrientationPortrait</string>
-        <string>UIInterfaceOrientationPortraitUpsideDown</string>
-        <string>UIInterfaceOrientationLandscapeLeft</string>
-        <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
-    <key>NSAppTransportSecurity</key>
-    <dict>
-        <key>NSAllowsArbitraryLoads</key>
-        <false/>
-        <key>NSAllowsLocalNetworking</key>
-        <true/>
-    </dict>
-    
-    
-    <key>NSHumanReadableCopyright</key>
-    <string>(c) 2026 bajdev</string>
-    
-    
-    <key>CFBundleGetInfoString</key>
-    <string>Not affiliated with Akai Professional</string>
-    
-    <!--
-      Privacy usage descriptions. iOS hard-crashes (and the App Store rejects the
-      build) the moment a permission-gated API is called without the matching
-      key. The Wails runtime exposes camera, microphone, photo library, location,
-      motion and biometric APIs; uncomment the key for each feature your app
-      actually uses and replace the string with a real, user-facing reason. Only
-      ship keys for features you use — unused keys draw App Store review scrutiny.
-
-    <key>NSCameraUsageDescription</key>
-    <string>This app uses the camera to capture photos and videos.</string>
-    <key>NSMicrophoneUsageDescription</key>
-    <string>This app uses the microphone when recording video.</string>
-    <key>NSPhotoLibraryUsageDescription</key>
-    <string>This app accesses your photo library to select images.</string>
-    <key>NSLocationWhenInUseUsageDescription</key>
-    <string>This app uses your location while you are using it.</string>
-    <key>NSMotionUsageDescription</key>
-    <string>This app uses motion data from device sensors.</string>
-    <key>NSFaceIDUsageDescription</key>
-    <string>This app uses Face ID to authenticate you.</string>
-    -->
-</dict>
-</plist>

+ 0 - 53
build/ios/LaunchScreen.storyboard

@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
-    <device id="retina6_12" orientation="portrait" appearance="light"/>
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <scenes>
-        <!--View Controller-->
-        <scene sceneID="EHf-IW-A2E">
-            <objects>
-                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
-                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
-                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="mpckit" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
-                                <rect key="frame" x="0.0" y="397" width="393" height="43"/>
-                                <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
-                                <nil key="textColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Desktop kit editor for the MPC Sample" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
-                                <rect key="frame" x="0.0" y="448" width="393" height="21"/>
-                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
-                                <nil key="textColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            
-                        </subviews>
-                        <viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                        <constraints>
-                            <constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
-                            <constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/2" constant="-20" id="moa-c2-u7t"/>
-                            <constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
-                            
-                            <constraint firstItem="MN2-I3-ftu" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="8" symbolic="YES" id="cPy-rs-vsC"/>
-                            <constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="OQL-iM-xY6"/>
-                            <constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="Dti-5h-tvW"/>
-                            
-                        </constraints>
-                    </view>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="53" y="375"/>
-        </scene>
-    </scenes>
-</document>

+ 0 - 11
build/ios/entitlements.plist

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <!-- iOS development entitlements. Applied to device builds only
-         (task ios:package IOS_PLATFORM=device). Add capability keys here
-         (e.g. aps-environment) as your app needs them. -->
-    <key>get-task-allow</key>
-    <true/>
-</dict>
-</plist>

+ 0 - 230
build/ios/project.pbxproj

@@ -1,230 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {};
-	objectVersion = 56;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		C0DEBEEF0000000000000001 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000002 /* main.m */; };
-		C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000101 /* UIKit.framework */; };
-		C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000102 /* Foundation.framework */; };
-		C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000103 /* WebKit.framework */; };
-		C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000104 /* Security.framework */; };
-		C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000105 /* CoreFoundation.framework */; };
-		C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000106 /* libresolv.tbd */; };
-		C0DEBEEF00000000000000F7 /* mpckit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DEBEEF0000000000000107 /* mpckit.a */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXFileReference section */
-		C0DEBEEF0000000000000002 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
-		C0DEBEEF0000000000000003 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		C0DEBEEF0000000000000004 /* mpckit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mpckit.app"; sourceTree = BUILT_PRODUCTS_DIR; };
-		C0DEBEEF0000000000000101 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000102 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000103 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000104 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000105 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000106 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.text-based-dylib-definition; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
-		C0DEBEEF0000000000000107 /* mpckit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "mpckit.a"; path = "../../../bin/mpckit.a"; sourceTree = SOURCE_ROOT; };
-/* End PBXFileReference section */
-
-/* Begin PBXGroup section */
-		C0DEBEEF0000000000000010 = {
-			isa = PBXGroup;
-			children = (
-				C0DEBEEF0000000000000020 /* Products */,
-				C0DEBEEF0000000000000045 /* Frameworks */,
-				C0DEBEEF0000000000000030 /* main */,
-			);
-			sourceTree = "<group>";
-		};
-		C0DEBEEF0000000000000020 /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				C0DEBEEF0000000000000004 /* mpckit.app */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		C0DEBEEF0000000000000030 /* main */ = {
-			isa = PBXGroup;
-			children = (
-				C0DEBEEF0000000000000002 /* main.m */,
-				C0DEBEEF0000000000000003 /* Info.plist */,
-			);
-			path = main;
-			sourceTree = SOURCE_ROOT;
-		};
-		C0DEBEEF0000000000000045 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				C0DEBEEF0000000000000101 /* UIKit.framework */,
-				C0DEBEEF0000000000000102 /* Foundation.framework */,
-				C0DEBEEF0000000000000103 /* WebKit.framework */,
-				C0DEBEEF0000000000000104 /* Security.framework */,
-				C0DEBEEF0000000000000105 /* CoreFoundation.framework */,
-				C0DEBEEF0000000000000106 /* libresolv.tbd */,
-				C0DEBEEF0000000000000107 /* mpckit.a */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		C0DEBEEF0000000000000040 /* mpckit */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "mpckit" */;
-			buildPhases = (
-				C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */,
-				C0DEBEEF0000000000000050 /* Sources */,
-				C0DEBEEF0000000000000056 /* Frameworks */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = "mpckit";
-			productName = "mpckit";
-			productReference = C0DEBEEF0000000000000004 /* mpckit.app */;
-			productType = "com.apple.product-type.application";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		C0DEBEEF0000000000000060 /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 1500;
-				ORGANIZATIONNAME = "bajdev";
-				TargetAttributes = {
-					C0DEBEEF0000000000000040 = {
-						CreatedOnToolsVersion = 15.0;
-					};
-				};
-			};
-			buildConfigurationList = C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */;
-			compatibilityVersion = "Xcode 15.0";
-			developmentRegion = en;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-			);
-			mainGroup = C0DEBEEF0000000000000010;
-			productRefGroup = C0DEBEEF0000000000000020 /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				C0DEBEEF0000000000000040 /* mpckit */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		C0DEBEEF0000000000000056 /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				C0DEBEEF00000000000000F7 /* mpckit.a in Frameworks */,
-				C0DEBEEF00000000000000F1 /* UIKit.framework in Frameworks */,
-				C0DEBEEF00000000000000F2 /* Foundation.framework in Frameworks */,
-				C0DEBEEF00000000000000F3 /* WebKit.framework in Frameworks */,
-				C0DEBEEF00000000000000F4 /* Security.framework in Frameworks */,
-				C0DEBEEF00000000000000F5 /* CoreFoundation.framework in Frameworks */,
-				C0DEBEEF00000000000000F6 /* libresolv.tbd in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
-		C0DEBEEF0000000000000055 /* Prebuild: Wails Go Archive */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-			);
-			name = "Prebuild: Wails Go Archive";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "set -e\nAPP_ROOT=\"${PROJECT_DIR}/../../..\"\nPLATFORM=${PLATFORM_NAME:-iphonesimulator}\nSDK_PATH=${SDKROOT:-$(xcrun --sdk ${PLATFORM} --show-sdk-path)}\nif [ ${PLATFORM} = iphonesimulator ]; then GO_TARGET=arm64-apple-ios15.0-simulator; MIN_FLAG=-mios-simulator-version-min=15.0; else GO_TARGET=arm64-apple-ios15.0; MIN_FLAG=-miphoneos-version-min=15.0; fi\nexport GOOS=ios\nexport GOARCH=arm64\nexport CGO_ENABLED=1\nexport CGO_CFLAGS=\"-isysroot ${SDK_PATH} -target ${GO_TARGET} ${MIN_FLAG}\"\nexport CGO_LDFLAGS=\"-isysroot ${SDK_PATH} -target ${GO_TARGET}\"\ncd \"${APP_ROOT}\"\n# Ensure overlay exists\nif [ ! -f build/ios/xcode/overlay.json ]; then\n  wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true\nfi\n# Always rebuild the Go c-archive; Go's build cache keeps this fast\n# and avoids stale binaries when .go sources change.\necho \"Building Go c-archive...\"\ngo build -buildmode=c-archive -overlay build/ios/xcode/overlay.json -o \"bin/mpckit.a\"\n";
-		};
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
-		C0DEBEEF0000000000000050 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				C0DEBEEF0000000000000001 /* main.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin XCBuildConfiguration section */
-		C0DEBEEF0000000000000090 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				INFOPLIST_FILE = main/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					"-ObjC",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = "com.monoexp.mpckit";
-				PRODUCT_NAME = "mpckit";
-				CODE_SIGNING_ALLOWED = NO;
-				SDKROOT = iphoneos;
-			};
-			name = Debug;
-		};
-		C0DEBEEF00000000000000A0 /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				INFOPLIST_FILE = main/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 15.0;
-				OTHER_LDFLAGS = (
-					"$(inherited)",
-					"-ObjC",
-				);
-				PRODUCT_BUNDLE_IDENTIFIER = "com.monoexp.mpckit";
-				PRODUCT_NAME = "mpckit";
-				CODE_SIGNING_ALLOWED = NO;
-				SDKROOT = iphoneos;
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		C0DEBEEF0000000000000070 /* Build configuration list for PBXNativeTarget "mpckit" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				C0DEBEEF0000000000000090 /* Debug */,
-				C0DEBEEF00000000000000A0 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Debug;
-		};
-		C0DEBEEF0000000000000080 /* Build configuration list for PBXProject "main" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				C0DEBEEF0000000000000090 /* Debug */,
-				C0DEBEEF00000000000000A0 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Debug;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = C0DEBEEF0000000000000060 /* Project object */;
-}

+ 1 - 1
build/linux/Taskfile.yml

@@ -59,7 +59,7 @@ tasks:
     cmds:
       - '{{if eq .OBFUSCATED "true"}}garble {{.GARBLE_ARGS}} build{{else}}go build{{end}} {{.BUILD_FLAGS}} -o {{.OUTPUT}}'
     vars:
-      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
+      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -X git.monoexp.com/bajdev/mpckit/internal/app.Version={{.VERSION}}"{{end}}'
       DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
       OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
     env:

+ 1 - 1
build/linux/nfpm/nfpm.yaml

@@ -6,7 +6,7 @@
 name: "mpckit"
 arch: ${GOARCH}
 platform: "linux"
-version: "0.0.1"
+version: "0.5.0"
 section: "default"
 priority: "extra"
 maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>

+ 1 - 1
build/windows/Taskfile.yml

@@ -57,7 +57,7 @@ tasks:
       - cmd: rm -f *.syso
         platforms: [linux, darwin]
     vars:
-      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
+      BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -X git.monoexp.com/bajdev/mpckit/internal/app.Version={{.VERSION}} -H windowsgui"{{end}}'
     env:
       GOOS: windows
       CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'

BIN
build/windows/icon.ico


+ 4 - 4
build/windows/info.json

@@ -1,12 +1,12 @@
 {
 	"fixed": {
-		"file_version": "0.0.1",
-		"product_version": "0.0.1"
+		"file_version": "0.5.0",
+		"product_version": "0.5.0"
 	},
 	"info": {
 		"0409": {
-			"FileVersion": "0.0.1",
-			"ProductVersion": "0.0.1",
+			"FileVersion": "0.5.0",
+			"ProductVersion": "0.5.0",
 			"CompanyName": "bajdev",
 			"FileDescription": "Desktop kit editor for the MPC Sample",
 			"LegalCopyright": "(c) 2026 bajdev",

+ 4 - 4
build/windows/msix/app_manifest.xml

@@ -9,13 +9,13 @@
   
   <Identity 
     Name="com.example.mpckit"
-    Publisher="CN=My Company"
+    Publisher="CN=bajdev"
     Version="0.1.0.0"
     ProcessorArchitecture="x64" />
   
   <Properties>
-    <DisplayName>My Product</DisplayName>
-    <PublisherDisplayName>My Company</PublisherDisplayName>
+    <DisplayName>mpckit</DisplayName>
+    <PublisherDisplayName>bajdev</PublisherDisplayName>
     <Description>A mpckit application</Description>
     <Logo>Assets\StoreLogo.png</Logo>
   </Properties>
@@ -31,7 +31,7 @@
   <Applications>
     <Application Id="com.example.mpckit" Executable="mpckit" EntryPoint="Windows.FullTrustApplication">
       <uap:VisualElements
-        DisplayName="My Product"
+        DisplayName="mpckit"
         Description="A mpckit application"
         BackgroundColor="transparent"
         Square150x150Logo="Assets\Square150x150Logo.png"

+ 8 - 8
build/windows/msix/template.xml

@@ -10,13 +10,13 @@
     <Installer
         Path="mpckit"
         Arguments=""
-        InstallLocation="C:\Program Files\My Company\My Product">
+        InstallLocation="C:\Program Files\mpckit">
     </Installer>
     <PackageInformation
-        PackageName="My Product"
-        PackageDisplayName="My Product"
-        PublisherName="CN=My Company"
-        PublisherDisplayName="My Company"
+        PackageName="mpckit"
+        PackageDisplayName="mpckit"
+        PublisherName="CN=bajdev"
+        PublisherDisplayName="bajdev"
         Version="0.1.0.0"
         PackageDescription="A mpckit application">
         <Capabilities>
@@ -27,7 +27,7 @@
             <Application
                 Id="com.example.mpckit"
                 Description="A mpckit application"
-                DisplayName="My Product"
+                DisplayName="mpckit"
                 ExecutableName="mpckit"
                 EntryPoint="Windows.FullTrustApplication">
                 
@@ -41,8 +41,8 @@
         </Dependencies>
         <Properties>
             <Framework>false</Framework>
-            <DisplayName>My Product</DisplayName>
-            <PublisherDisplayName>My Company</PublisherDisplayName>
+            <DisplayName>mpckit</DisplayName>
+            <PublisherDisplayName>bajdev</PublisherDisplayName>
             <Description>A mpckit application</Description>
             <Logo>Assets\AppIcon.png</Logo>
         </Properties>

+ 1 - 1
build/windows/nsis/wails_tools.nsh

@@ -14,7 +14,7 @@
     !define INFO_PRODUCTNAME "mpckit"
 !endif
 !ifndef INFO_PRODUCTVERSION
-    !define INFO_PRODUCTVERSION "0.0.1"
+    !define INFO_PRODUCTVERSION "0.5.0"
 !endif
 !ifndef INFO_COPYRIGHT
     !define INFO_COPYRIGHT "(c) 2026 bajdev"

+ 1 - 1
build/windows/wails.exe.manifest

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
-    <assemblyIdentity type="win32" name="com.monoexp.mpckit" version="0.0.1" processorArchitecture="*"/>
+    <assemblyIdentity type="win32" name="com.monoexp.mpckit" version="0.5.0" processorArchitecture="*"/>
     <dependency>
         <dependentAssembly>
             <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>

+ 35 - 0
pull-requests/m9-packaging/m9-icon-version-and-install-notes.md

@@ -0,0 +1,35 @@
+# M9 (part 1): icon, version stamping, install notes
+
+Branch: `m9-packaging` → `main`
+
+M9 is "icons, version stamping, builds for the three systems, README with install notes, licence". This PR covers everything except the builds themselves. Those depend on which machines (or GitHub Actions) will build for Windows, Linux and macOS, still to be decided.
+
+## What changed
+
+- **App icon:** a 4×4 pad grid in the app's colours, with pad 1 (bottom left, as on the device) lit in the orange accent, on the dark background. It is drawn by a small Go program, `tools/icon`, so it can be changed and regenerated: `go run ./tools/icon build/appicon.png`.
+  - `build/windows/icon.ico` and `build/darwin/icons.icns` are generated from it, as is the macOS disk-image file icon.
+  - The disk-image background (it had the Wails logo) is now plain light grey (`go run ./tools/icon -dmg …`); Finder labels icons there in dark text.
+  - Removed the template's macOS Icon Composer bundle (`build/appicon.icon`) and its compiled `build/darwin/Assets.car`. Both showed the Wails logo, and `Assets.car` takes precedence over `icons.icns` on current macOS. The bundle can only be compiled on macOS 26 with Xcode 26, so the icons task now uses `appicon.png` alone.
+- **Version stamping:** production builds (`wails3 build`, `wails3 package`) set the version shown in About from `git describe --tags --always --dirty`.
+  - A build of the `v0.5.0` tag shows `v0.5.0`. Other commits show the tag plus the commit, or just the commit id while there are no tags. `wails3 dev` still shows `dev`.
+  - The Windows file version, macOS bundle version, Linux package version and installer version are now 0.5.0 (were the template's 0.0.1).
+- **README:** what the app does, the beta note, and install notes per system. Builds are unsigned, so each system warns once: SmartScreen on Windows, "Open Anyway" in Privacy & Security on macOS, `chmod +x` and the GTK 4 / WebKitGTK 6.0 libraries on Linux. Also how versions are stamped and how the icon is made.
+- **Template leftovers:** removed the unused `build/ios` folder; filled the MSIX manifests' "My Company" / "My Product" placeholders with bajdev / mpckit.
+- **Licence:** already done (MIT, copyright 2026 bajdev). The About dialog and README already say mpckit is not affiliated with Akai.
+
+## Testing
+
+On the server:
+- `go vet` and `go test ./...` pass.
+- The Taskfile parses (`wails3 task --list-all`). A dry run of the Windows build shows `-X …internal/app.Version=<git describe>` in the `go build` flags.
+- A Windows build with `-X …Version=v0.5.0-check` contains that string.
+- `tools/icon` regenerates `build/appicon.png` byte for byte.
+
+**On the laptop:**
+1. `wails3 build` produces `bin\mpckit.exe`. Explorer, the taskbar and the window show the new icon (Windows caches icons; if Explorer still shows the old one, rename the file or sign out and back in).
+2. Run `bin\mpckit.exe` and open About: the version is the commit id (no tags yet; after merge it becomes `v0.5.0` once tagged).
+3. Right-click `mpckit.exe` → Properties → Details: product name mpckit, version 0.5.0, copyright (c) 2026 bajdev.
+
+## Next (M9 part 2)
+
+After this is merged: tag `v0.5.0` on the merge commit, build from the tag on each system (Windows laptop, Linux desktop, and a Mac when one is available), and check each build runs and shows `v0.5.0` in About. That is M9's done-when. If the repo is mirrored to GitHub, an Actions workflow could build all three instead.

+ 146 - 0
tools/icon/main.go

@@ -0,0 +1,146 @@
+// Command icon draws the mpckit app icon: a 4x4 pad grid on the app's dark
+// background, with pad 1 (bottom left, as on the device) lit in the accent
+// colour. It writes a 1024x1024 PNG; `wails3 generate icons` makes the
+// Windows and macOS icons from it.
+//
+//	go run ./tools/icon build/appicon.png
+//
+// With -dmg it writes the background of the macOS disk image instead: a
+// plain light 540x380 image (Finder labels the icons with dark text).
+//
+//	go run ./tools/icon -dmg build/darwin/dmg-background.png
+package main
+
+import (
+	"flag"
+	"fmt"
+	"image"
+	"image/color"
+	"image/png"
+	"math"
+	"os"
+)
+
+const (
+	size = 1024
+	ss   = 4 // supersampling per axis, for smooth edges
+)
+
+// Colours from the app (frontend/src/app.css).
+var (
+	bg      = rgb(0x15, 0x17, 0x1A)
+	edge    = rgb(0x35, 0x3A, 0x43)
+	pad     = rgb(0x33, 0x38, 0x42)
+	padEdge = rgb(0x5A, 0x61, 0x6E)
+	accent  = rgb(0xF5, 0xA5, 0x24)
+)
+
+func rgb(r, g, b uint8) color.NRGBA { return color.NRGBA{r, g, b, 0xFF} }
+
+type rect struct{ x0, y0, x1, y1, r float64 }
+
+// inside reports whether (x, y) is inside the rounded rectangle.
+func (q rect) inside(x, y float64) bool {
+	if x < q.x0 || x >= q.x1 || y < q.y0 || y >= q.y1 {
+		return false
+	}
+	cx := math.Max(q.x0+q.r, math.Min(x, q.x1-q.r))
+	cy := math.Max(q.y0+q.r, math.Min(y, q.y1-q.r))
+	return (x-cx)*(x-cx)+(y-cy)*(y-cy) <= q.r*q.r
+}
+
+func (q rect) inset(d float64) rect {
+	return rect{q.x0 + d, q.y0 + d, q.x1 - d, q.y1 - d, math.Max(q.r-d, 0)}
+}
+
+type layer struct {
+	shape rect
+	col   color.NRGBA
+}
+
+func main() {
+	dmg := flag.Bool("dmg", false, "write the disk image background instead of the icon")
+	flag.Parse()
+	if flag.NArg() != 1 {
+		fmt.Fprintln(os.Stderr, "usage: icon [-dmg] out.png")
+		os.Exit(2)
+	}
+	if *dmg {
+		img := image.NewNRGBA(image.Rect(0, 0, 540, 380))
+		for i := 0; i < len(img.Pix); i += 4 {
+			img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = 0xF2, 0xF3, 0xF5, 0xFF
+		}
+		write(flag.Arg(0), img)
+		return
+	}
+
+	// Body: a rounded square with a margin, as macOS icons have; the
+	// same image works for Windows and Linux.
+	body := rect{64, 64, 960, 960, 200}
+	layers := []layer{{body, edge}, {body.inset(10), bg}}
+
+	// 4x4 pads, 140 px with 32 px gaps, centred in the body.
+	const cell, gap = 140.0, 32.0
+	start := (size - (4*cell + 3*gap)) / 2
+	for row := 0; row < 4; row++ {
+		for col := 0; col < 4; col++ {
+			x := start + float64(col)*(cell+gap)
+			y := start + float64(row)*(cell+gap)
+			r := rect{x, y, x + cell, y + cell, 28}
+			if row == 3 && col == 0 { // pad 1: bottom left
+				layers = append(layers, layer{r, accent})
+				continue
+			}
+			layers = append(layers, layer{r, padEdge}, layer{r.inset(8), pad})
+		}
+	}
+
+	img := image.NewNRGBA(image.Rect(0, 0, size, size))
+	for py := 0; py < size; py++ {
+		for px := 0; px < size; px++ {
+			var r, g, b, a float64
+			for sy := 0; sy < ss; sy++ {
+				for sx := 0; sx < ss; sx++ {
+					x := float64(px) + (float64(sx)+0.5)/ss
+					y := float64(py) + (float64(sy)+0.5)/ss
+					// Topmost layer that covers the point wins.
+					for i := len(layers) - 1; i >= 0; i-- {
+						if layers[i].shape.inside(x, y) {
+							c := layers[i].col
+							r += float64(c.R)
+							g += float64(c.G)
+							b += float64(c.B)
+							a++
+							break
+						}
+					}
+				}
+			}
+			if a == 0 {
+				continue // transparent outside the body
+			}
+			img.SetNRGBA(px, py, color.NRGBA{
+				uint8(r/a + 0.5), uint8(g/a + 0.5), uint8(b/a + 0.5),
+				uint8(255*a/(ss*ss) + 0.5),
+			})
+		}
+	}
+
+	write(flag.Arg(0), img)
+}
+
+func write(path string, img image.Image) {
+	f, err := os.Create(path)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := png.Encode(f, img); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+	if err := f.Close(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}