feat(client): refresh brand to Heicode H logo + align macOS build script
- Replace logo source with new glassy gradient H (heicode-logo.jpg)
- Regen all icon sizes via scripts/rebuild-icons.py (PIL + ICO + ICNS):
32/64/128/128@2x/icon.png + Square{30,44,71,89,107,142,150,284,310}+StoreLogo
+ multi-res icon.ico + icon.icns
- Update public/app-icon.png to 1024×1024 derived
- Align cc-haha/desktop/scripts/build-macos-arm64.sh to Heicode branding:
APP_BUNDLE_NAME=HeiCode.app, APP_BUNDLE_ID=com.heicode.desktop,
DMG volume=Heicode, default DMG=Heicode_0.1.0_aarch64.dmg,
cleanup paths use heicode-desktop crate name (mirrors Cargo.toml rename)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 414 KiB |
@@ -9,12 +9,14 @@ REPO_ROOT="$(cd "${DESKTOP_DIR}/.." && pwd)"
|
|||||||
TARGET_TRIPLE="aarch64-apple-darwin"
|
TARGET_TRIPLE="aarch64-apple-darwin"
|
||||||
TAURI_TARGET_DIR="${DESKTOP_DIR}/src-tauri/target"
|
TAURI_TARGET_DIR="${DESKTOP_DIR}/src-tauri/target"
|
||||||
CANONICAL_OUTPUT_DIR="${DESKTOP_DIR}/build-artifacts/macos-arm64"
|
CANONICAL_OUTPUT_DIR="${DESKTOP_DIR}/build-artifacts/macos-arm64"
|
||||||
APP_BUNDLE_NAME="Claude Code Haha.app"
|
APP_BUNDLE_NAME="HeiCode.app"
|
||||||
APP_BUNDLE_ID="com.claude-code-haha.desktop"
|
APP_BUNDLE_ID="com.heicode.desktop"
|
||||||
|
DMG_VOLUME_NAME="Heicode"
|
||||||
|
DEFAULT_DMG_BASENAME="Heicode_0.1.0_aarch64.dmg"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Build Claude Code Haha desktop for macOS Apple Silicon and output a DMG.
|
Build Heicode desktop for macOS Apple Silicon and output a DMG.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
./desktop/scripts/build-macos-arm64.sh [extra tauri build args...]
|
./desktop/scripts/build-macos-arm64.sh [extra tauri build args...]
|
||||||
@@ -79,10 +81,10 @@ rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/bundle"
|
|||||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/release/bundle"
|
rm -rf "${DESKTOP_DIR}/src-tauri/target/release/bundle"
|
||||||
rm -rf "${DESKTOP_DIR}/dist"
|
rm -rf "${DESKTOP_DIR}/dist"
|
||||||
rm -f "${DESKTOP_DIR}/tsconfig.tsbuildinfo"
|
rm -f "${DESKTOP_DIR}/tsconfig.tsbuildinfo"
|
||||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/build/claude-code-desktop-"*
|
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/build/heicode-desktop-"*
|
||||||
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/.fingerprint/claude-code-desktop-"*
|
rm -rf "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/.fingerprint/heicode-desktop-"*
|
||||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/claude_code_desktop-"*
|
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/heicode_desktop-"*
|
||||||
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/libclaude_code_desktop-"*
|
rm -f "${DESKTOP_DIR}/src-tauri/target/${TARGET_TRIPLE}/release/deps/libheicode_desktop-"*
|
||||||
|
|
||||||
echo "[build-macos-arm64] Rebuilding frontend (tsc + vite)..."
|
echo "[build-macos-arm64] Rebuilding frontend (tsc + vite)..."
|
||||||
(cd "${DESKTOP_DIR}" && bun run build)
|
(cd "${DESKTOP_DIR}" && bun run build)
|
||||||
@@ -159,15 +161,15 @@ build_canonical_dmg() {
|
|||||||
local staging_dir
|
local staging_dir
|
||||||
local rw_dmg
|
local rw_dmg
|
||||||
|
|
||||||
staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/cc-haha-dmg.XXXXXX")"
|
staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/heicode-dmg.XXXXXX")"
|
||||||
rw_dmg="$(mktemp "${TMPDIR:-/tmp}/cc-haha-rw.XXXXXX").dmg"
|
rw_dmg="$(mktemp "${TMPDIR:-/tmp}/heicode-rw.XXXXXX").dmg"
|
||||||
|
|
||||||
cp -R "${app_bundle}" "${staging_dir}/"
|
cp -R "${app_bundle}" "${staging_dir}/"
|
||||||
ln -s /Applications "${staging_dir}/Applications"
|
ln -s /Applications "${staging_dir}/Applications"
|
||||||
|
|
||||||
# Create a read-write DMG first so we can customize the Finder layout
|
# Create a read-write DMG first so we can customize the Finder layout
|
||||||
hdiutil create \
|
hdiutil create \
|
||||||
-volname "Claude Code Haha" \
|
-volname "${DMG_VOLUME_NAME}" \
|
||||||
-srcfolder "${staging_dir}" \
|
-srcfolder "${staging_dir}" \
|
||||||
-ov \
|
-ov \
|
||||||
-format UDRW \
|
-format UDRW \
|
||||||
@@ -188,7 +190,7 @@ build_canonical_dmg() {
|
|||||||
# 所以这里允许 osascript 非零退出,只 warn,不让 set -e 炸掉整个脚本。
|
# 所以这里允许 osascript 非零退出,只 warn,不让 set -e 炸掉整个脚本。
|
||||||
if ! osascript <<APPLESCRIPT
|
if ! osascript <<APPLESCRIPT
|
||||||
tell application "Finder"
|
tell application "Finder"
|
||||||
tell disk "Claude Code Haha"
|
tell disk "${DMG_VOLUME_NAME}"
|
||||||
open
|
open
|
||||||
set current view of container window to icon view
|
set current view of container window to icon view
|
||||||
set toolbar visible of container window to false
|
set toolbar visible of container window to false
|
||||||
@@ -274,7 +276,7 @@ if [[ -n "${LATEST_APP}" ]]; then
|
|||||||
rm -f "${CANONICAL_OUTPUT_DIR}/"*.dmg
|
rm -f "${CANONICAL_OUTPUT_DIR}/"*.dmg
|
||||||
build_canonical_dmg \
|
build_canonical_dmg \
|
||||||
"${CANONICAL_OUTPUT_DIR}/${APP_BUNDLE_NAME}" \
|
"${CANONICAL_OUTPUT_DIR}/${APP_BUNDLE_NAME}" \
|
||||||
"${CANONICAL_OUTPUT_DIR}/$(basename "${LATEST_DMG:-Claude Code Haha_0.1.0_aarch64.dmg}")"
|
"${CANONICAL_OUTPUT_DIR}/$(basename "${LATEST_DMG:-${DEFAULT_DMG_BASENAME}}")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > "${CANONICAL_OUTPUT_DIR}/BUILD_INFO.txt" <<EOF
|
cat > "${CANONICAL_OUTPUT_DIR}/BUILD_INFO.txt" <<EOF
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 414 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,83 @@
|
|||||||
|
"""Regenerate Tauri/desktop icons from heicode-logo.jpg."""
|
||||||
|
from pathlib import Path
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
SRC = ROOT / "heicode-logo.jpg"
|
||||||
|
TAURI_ICONS = ROOT / "cc-haha" / "desktop" / "src-tauri" / "icons"
|
||||||
|
PUBLIC = ROOT / "cc-haha" / "desktop" / "public"
|
||||||
|
|
||||||
|
def load_trimmed_square():
|
||||||
|
img = Image.open(SRC).convert("RGB")
|
||||||
|
# Find bbox of non-white pixels (treat >=250 RGB as white).
|
||||||
|
gray = img.convert("L")
|
||||||
|
mask = gray.point(lambda v: 0 if v >= 250 else 255)
|
||||||
|
bbox = mask.getbbox()
|
||||||
|
if bbox:
|
||||||
|
img = img.crop(bbox)
|
||||||
|
# Pad to square on white.
|
||||||
|
w, h = img.size
|
||||||
|
side = max(w, h)
|
||||||
|
canvas = Image.new("RGB", (side, side), (255, 255, 255))
|
||||||
|
canvas.paste(img, ((side - w) // 2, (side - h) // 2))
|
||||||
|
return canvas
|
||||||
|
|
||||||
|
def to_rgba_with_transparent_white(img: Image.Image, threshold: int = 248) -> Image.Image:
|
||||||
|
rgba = img.convert("RGBA")
|
||||||
|
pixels = rgba.load()
|
||||||
|
w, h = rgba.size
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
r, g, b, _ = pixels[x, y]
|
||||||
|
if r >= threshold and g >= threshold and b >= threshold:
|
||||||
|
pixels[x, y] = (r, g, b, 0)
|
||||||
|
return rgba
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base = load_trimmed_square()
|
||||||
|
print(f"Trimmed source: {base.size}")
|
||||||
|
|
||||||
|
# PNG outputs (RGB on white → looks correct on any background, matches glass edges).
|
||||||
|
sizes = {
|
||||||
|
TAURI_ICONS / "32x32.png": 32,
|
||||||
|
TAURI_ICONS / "64x64.png": 64,
|
||||||
|
TAURI_ICONS / "128x128.png": 128,
|
||||||
|
TAURI_ICONS / "128x128@2x.png": 256,
|
||||||
|
TAURI_ICONS / "icon.png": 1024,
|
||||||
|
PUBLIC / "app-icon.png": 1024,
|
||||||
|
}
|
||||||
|
for path, size in sizes.items():
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
base.resize((size, size), Image.LANCZOS).save(path, "PNG")
|
||||||
|
print(f"wrote {path} ({size})")
|
||||||
|
|
||||||
|
# Windows Square*Logo / StoreLogo
|
||||||
|
win_sizes = {
|
||||||
|
"Square30x30Logo.png": 30,
|
||||||
|
"Square44x44Logo.png": 44,
|
||||||
|
"Square71x71Logo.png": 71,
|
||||||
|
"Square89x89Logo.png": 89,
|
||||||
|
"Square107x107Logo.png": 107,
|
||||||
|
"Square142x142Logo.png": 142,
|
||||||
|
"Square150x150Logo.png": 150,
|
||||||
|
"Square284x284Logo.png": 284,
|
||||||
|
"Square310x310Logo.png": 310,
|
||||||
|
"StoreLogo.png": 50,
|
||||||
|
}
|
||||||
|
for name, size in win_sizes.items():
|
||||||
|
path = TAURI_ICONS / name
|
||||||
|
base.resize((size, size), Image.LANCZOS).save(path, "PNG")
|
||||||
|
print(f"wrote {path} ({size})")
|
||||||
|
|
||||||
|
# ICO with multi-resolution.
|
||||||
|
ico_path = TAURI_ICONS / "icon.ico"
|
||||||
|
base.save(ico_path, format="ICO", sizes=[(16,16),(24,24),(32,32),(48,48),(64,64),(128,128),(256,256)])
|
||||||
|
print(f"wrote {ico_path}")
|
||||||
|
|
||||||
|
# ICNS (PIL supports it). Required sizes: 16,32,64,128,256,512,1024.
|
||||||
|
icns_path = TAURI_ICONS / "icon.icns"
|
||||||
|
base.save(icns_path, format="ICNS", sizes=[(16,16),(32,32),(64,64),(128,128),(256,256),(512,512),(1024,1024)])
|
||||||
|
print(f"wrote {icns_path}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||