tools: green 'live' dev-alt icon variant (--icon live) (#1145)
A dev alt aimed at production home needs to be tellable from one aimed at the dev environment — and two running alts must never share an icon. Green = live by convention; make-dev-icon.swift grows a variant table.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
11 files changed,
+61
−16
CLAUDE.md+6 −2ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/Contents.json+15 −0ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_1024.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_128.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_16.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_256.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_32.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_512.pngbinaryios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_64.pngbinarytools/dev+18 −5tools/make-dev-icon.swift+22 −9
diff --git a/CLAUDE.md b/CLAUDE.md| index 44a32aa..4449c4b 100644 |
| --- a/CLAUDE.md |
| +++ b/CLAUDE.md |
| @@ -657,9 +657,13 @@ Simple command-line interface for common dev tasks. Run from anywhere in the rep |
| All build and test commands are thin wrappers around the underlying toolchain (xcodebuild, go, npm). The `dev` subcommands orchestrate the dev server (`tools/lchat-dev-up.mjs`) and app seeding scripts. The `run` commands automatically build if needed, so you can skip the explicit build step. **Reuse and extend this as the single entry point for all dev workflows** — don't hand-roll xcodebuild invocations or embed them in Claude prompts. When adding new dev commands, document them here and in the script's usage header. |
| -🤖 **Dev alts (`./tools/dev alt [slug]`)** build the same LChatMac target under an |
| +🤖 **Dev alts (`./tools/dev alt [slug] [--icon live]`)** build the same LChatMac |
| +target under an |
| alternate identity — bundle id `…messenger.mac.alt-<slug>`, name "Eleven Dev", |
| -the orange dev icon (`AppIconDev`, regenerate with `swift tools/make-dev-icon.swift`), |
| +the orange dev icon (`AppIconDev`; `--icon live` picks the green `AppIconDevLive` |
| +— the convention for an alt aimed at PRODUCTION home, so two alts with |
| +different aims never share an icon; regenerate either with |
| +`swift tools/make-dev-icon.swift [live]`), |
| Sparkle auto-checks off — so a dev build runs **beside** the Sparkle-updated app |
| instead of replacing it. The keychain access group is unchanged, so an alt sees |
| the signed-in spaces: **it's the real account, not a sandbox** (and while it |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/Contents.json b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/Contents.json| new file mode 100644 |
| index 0000000..11bdd1e |
| --- /dev/null |
| +++ b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/Contents.json |
| @@ -0,0 +1,15 @@ |
| +{ |
| + "images" : [ |
| + { "idiom" : "mac", "size" : "16x16", "scale" : "1x", "filename" : "icon_16.png" }, |
| + { "idiom" : "mac", "size" : "16x16", "scale" : "2x", "filename" : "icon_32.png" }, |
| + { "idiom" : "mac", "size" : "32x32", "scale" : "1x", "filename" : "icon_32.png" }, |
| + { "idiom" : "mac", "size" : "32x32", "scale" : "2x", "filename" : "icon_64.png" }, |
| + { "idiom" : "mac", "size" : "128x128", "scale" : "1x", "filename" : "icon_128.png" }, |
| + { "idiom" : "mac", "size" : "128x128", "scale" : "2x", "filename" : "icon_256.png" }, |
| + { "idiom" : "mac", "size" : "256x256", "scale" : "1x", "filename" : "icon_256.png" }, |
| + { "idiom" : "mac", "size" : "256x256", "scale" : "2x", "filename" : "icon_512.png" }, |
| + { "idiom" : "mac", "size" : "512x512", "scale" : "1x", "filename" : "icon_512.png" }, |
| + { "idiom" : "mac", "size" : "512x512", "scale" : "2x", "filename" : "icon_1024.png" } |
| + ], |
| + "info" : { "version" : 1, "author" : "xcode" } |
| +} |
| \ No newline at end of file |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_1024.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_1024.png| new file mode 100644 |
| index 0000000..2bdfc8e |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_1024.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_128.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_128.png| new file mode 100644 |
| index 0000000..38bd2fc |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_128.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_16.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_16.png| new file mode 100644 |
| index 0000000..b640843 |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_16.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_256.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_256.png| new file mode 100644 |
| index 0000000..727163d |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_256.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_32.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_32.png| new file mode 100644 |
| index 0000000..212363b |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_32.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_512.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_512.png| new file mode 100644 |
| index 0000000..f0eeec0 |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_512.png differ |
diff --git a/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_64.png b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_64.png| new file mode 100644 |
| index 0000000..44b3629 |
| Binary files /dev/null and b/ios/LChatMac/Assets.xcassets/AppIconDevLive.appiconset/icon_64.png differ |
diff --git a/tools/dev b/tools/dev| index 78552b8..1c134a8 100755 |
| --- a/tools/dev |
| +++ b/tools/dev |
| @@ -382,13 +382,24 @@ const commands = { |
| // effect on next launch, delete to go back to production): |
| // defaults write com.elevenmessenger.messenger.mac.alt-<slug> \ |
| // home_base_override https://home.dev.elevenmessenger.com |
| -async function altMac(rawSlug) { |
| +async function altMac(args) { |
| + // --icon live picks the green set (the convention: green = a dev build |
| + // aimed at PRODUCTION home), so two alts with different aims never share |
| + // an icon. Default stays the orange AppIconDev. |
| + const ICONS = { dev: "AppIconDev", live: "AppIconDevLive" }; |
| + const iconAt = args.indexOf("--icon"); |
| + const iconKey = iconAt >= 0 ? args[iconAt + 1] : "dev"; |
| + if (!ICONS[iconKey]) { |
| + console.error(`Bad --icon — expected: ${Object.keys(ICONS).join(", ")}`); |
| + process.exit(1); |
| + } |
| + const rawSlug = args.filter((a, i) => !a.startsWith("--") && i !== iconAt + 1)[0]; |
| const slug = (rawSlug || "dev").toLowerCase().replace(/[^a-z0-9-]/g, ""); |
| if (!slug) { |
| console.error("Bad slug — letters/digits/hyphens only."); |
| process.exit(1); |
| } |
| - const name = slug === "dev" ? "Eleven Dev" : `Eleven Dev ${slug}`; |
| + const name = slug === "dev" ? "Eleven Dev" : `Eleven Dev ${slug[0].toUpperCase()}${slug.slice(1)}`; |
| const suffix = `.alt-${slug}`; |
| if (needsXcodegen()) { |
| console.log("Generating Xcode project..."); |
| @@ -404,7 +415,7 @@ async function altMac(rawSlug) { |
| "-allowProvisioningUpdates", |
| `LCHAT_MAC_ALT_SUFFIX=${suffix}`, |
| `LCHAT_MAC_DISPLAY_NAME=${name}`, |
| - "LCHAT_MAC_APPICON=AppIconDev", |
| + `LCHAT_MAC_APPICON=${ICONS[iconKey]}`, |
| "LCHAT_MAC_SPARKLE_AUTOCHECK=NO", |
| "build", |
| ], { cwd: IOS_DIR, encoding: "utf8", env: goEnv(), maxBuffer: 64 * 1024 * 1024 }); |
| @@ -431,7 +442,9 @@ if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") { |
| console.log(" ./tools/dev build mac Build native Mac app"); |
| console.log(" ./tools/dev build ios Build native iOS app"); |
| console.log(" ./tools/dev run mac Launch the Mac app"); |
| - console.log(" ./tools/dev alt [slug] Build + launch a side-by-side dev alt (orange icon)"); |
| + console.log(" ./tools/dev alt [slug] [--icon live] Build + launch a side-by-side dev alt"); |
| + console.log(" (orange icon; --icon live = green, the"); |
| + console.log(" convention for an alt aimed at prod home)"); |
| console.log(" ./tools/dev run ios Run iOS app on simulator"); |
| console.log(" ./tools/dev up Start local dev server"); |
| console.log(" ./tools/dev seed-mac Seed Mac app with dev credentials"); |
| @@ -445,7 +458,7 @@ if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") { |
| } |
| if (process.argv[2] === "alt") { |
| - Promise.resolve(altMac(process.argv[3])).catch((e) => { |
| + Promise.resolve(altMac(process.argv.slice(3))).catch((e) => { |
| console.error(`Error: ${e.message}`); |
| process.exit(1); |
| }); |
diff --git a/tools/make-dev-icon.swift b/tools/make-dev-icon.swift| index 6515bc0..91f1fe2 100644 |
| --- a/tools/make-dev-icon.swift |
| +++ b/tools/make-dev-icon.swift |
| @@ -1,27 +1,40 @@ |
| #!/usr/bin/swift |
| -// 🤖 Regenerates the Mac dev-alt icon set (AppIconDev.appiconset) from the |
| -// canonical app icon: the SAME purple "11" tile, hue-rotated to orange, so a |
| -// dev build is unmistakable in the Dock without inventing a new mark |
| -// (CLAUDE.md: never hand-roll a substitute glyph). Run from the repo root: |
| +// 🤖 Regenerates a Mac dev-alt icon set from the canonical app icon: the |
| +// SAME purple "11" tile, hue-rotated so a dev build is unmistakable in the |
| +// Dock without inventing a new mark (CLAUDE.md: never hand-roll a |
| +// substitute glyph). Two variants: "dev" (orange, the default) and "live" |
| +// (green — the convention for a dev build aimed at PRODUCTION home, so two |
| +// running alts with different aims never share an icon). Run from the repo |
| +// root: |
| // |
| -// swift tools/make-dev-icon.swift |
| +// swift tools/make-dev-icon.swift # AppIconDev (orange) |
| +// swift tools/make-dev-icon.swift live # AppIconDevLive (green) |
| // |
| // Committed output; re-run only if the canonical icon changes. |
| import AppKit |
| import CoreImage |
| +// Purple sits ~253°; orange ~30° (-220°), green ~130° (-123°). White and |
| +// near-white (the "11") are hue-less, so they ride through untouched. |
| +let variants: [String: (set: String, angle: Double)] = [ |
| + "dev": ("AppIconDev", -220.0), |
| + "live": ("AppIconDevLive", -123.0), |
| +] |
| +let name = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "dev" |
| +guard let variant = variants[name] else { |
| + fatalError("unknown variant \(name) — expected: \(variants.keys.sorted().joined(separator: ", "))") |
| +} |
| + |
| let src = "ios/LChatMac/Assets.xcassets/AppIcon.appiconset/icon_1024.png" |
| -let outDir = "ios/LChatMac/Assets.xcassets/AppIconDev.appiconset" |
| +let outDir = "ios/LChatMac/Assets.xcassets/\(variant.set).appiconset" |
| guard let image = CIImage(contentsOf: URL(fileURLWithPath: src)) else { |
| fatalError("can't read \(src) — run from the repo root") |
| } |
| -// Purple sits ~253°, orange ~30°: rotate the hue down to orange. White |
| -// and near-white (the "11") are hue-less, so they ride through untouched. |
| let hue = CIFilter(name: "CIHueAdjust", parameters: [ |
| kCIInputImageKey: image, |
| - kCIInputAngleKey: NSNumber(value: -220.0 * .pi / 180.0), |
| + kCIInputAngleKey: NSNumber(value: variant.angle * .pi / 180.0), |
| ])! |
| guard let tinted = hue.outputImage else { fatalError("hue adjust failed") } |