From 8c9ba89a748d4c99810b31f3b455d1ed43d9b61f Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 01:44:59 +0800
Subject: [PATCH] Prepare 2.1.40 multi-platform package build

---
 privatevoice.src/app.go                                 |    4 
 privatevoice.src/scripts/build-local-macos-universal.sh |  132 ++++++++++++++++++++++++++++++++++++++++++++
 privatevoice.src/scripts/build-windows-preview.sh       |    4 
 CHANGELOG.md                                            |   16 +++++
 privatevoice.src/build/darwin/Info.plist                |    4 
 CODEGRAPH.md                                            |   16 +++++
 6 files changed, 169 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2261dad..6d96398 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
 # Changelog
 
+## v2.1.40 (2026-07-02)
+
+### 多平台测试包整理
+
+- **同步 macOS 与 App 版本元数据**:将 macOS `Info.plist` 与 App 内显示版本统一到 `2.1.40 (build 20260702.0143)`,避免 macOS 包继续显示旧 build。
+- **新增 macOS universal 本地 DMG 构建入口**:可在同一 build 下生成 `arm64`、`x86_64` 和 `universal` 三种 macOS DMG,方便分别验证 Apple Silicon、Intel Mac 和双架构通用包。
+- **继续提供 Windows x64 预览包**:保留 2.1.39 的 Windows 炫彩 overlay 预览能力,并统一升级到本次 build。
+- **Release 目录收敛**:本版本作为整理后的最新版四包基线,旧产物统一归档到 `X-旧版build成果`。
+
+### 构建
+
+- build: `20260702.0143`
+- 说明: 本版本为 macOS 三架构 DMG + Windows x64 预览 zip 的多平台测试包整理基线。
+
+---
+
 ## v2.1.39 (2026-07-01)
 
 ### Windows 动态指示视觉升级
diff --git a/CODEGRAPH.md b/CODEGRAPH.md
index 3d3b984..c25f132 100644
--- a/CODEGRAPH.md
+++ b/CODEGRAPH.md
@@ -3,7 +3,7 @@
 ## Project
 
 - Product: Wails v3 / Go desktop dictation app.
-- Current branch purpose: Windows technical preview based on `v2.1.38-build20260629.2014-diagnostic`.
+- Current branch purpose: cross-platform local package handoff based on `v2.1.40-build20260702.0143`.
 - Protected macOS behavior: existing macOS App Store / local DMG build lines are not changed by this branch.
 
 ## Core Entrypoints
@@ -47,6 +47,17 @@
   - `QA-WINDOWS-PREVIEW.ps1`
   - `README-WINDOWS-PREVIEW.txt`
 
+## macOS Local Package Scope
+
+- Local DMG script: `privatevoice.src/scripts/build-local-macos.sh`.
+- Universal DMG script: `privatevoice.src/scripts/build-local-macos-universal.sh`.
+- Local macOS package set for this branch:
+  - `arm64` DMG for Apple Silicon.
+  - `x86_64` DMG for Intel Mac.
+  - `universal` DMG for Intel + Apple Silicon.
+- Mainline macOS local packages keep `LSMinimumSystemVersion=14.0` and the default Qwen3-capable dependency line.
+- Monterey-compatible packages remain a separate build line through `privatevoice.src/scripts/build-local-macos-monterey.sh`.
+
 ## Standard Verification Commands
 
 Run from `privatevoice.src`:
@@ -56,6 +67,9 @@
 go test ./...
 GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go test -exec=/usr/bin/true ./...
 ./scripts/build-windows-preview.sh
+./scripts/build-local-macos.sh arm64
+./scripts/build-local-macos.sh x86_64
+./scripts/build-local-macos-universal.sh
 ./scripts/verify-windows-qa-evidence.sh /path/to/qa-evidence-YYYYMMDD-HHMMSS.zip
 ```
 
diff --git a/privatevoice.src/app.go b/privatevoice.src/app.go
index ce6e70c..3cfd3b4 100755
--- a/privatevoice.src/app.go
+++ b/privatevoice.src/app.go
@@ -28,8 +28,8 @@
 )
 
 const (
-	appVersion        = "2.1.39"
-	appBuild          = "20260701.2356"
+	appVersion        = "2.1.40"
+	appBuild          = "20260702.0143"
 	appDisplayVersion = appVersion + " (build " + appBuild + ")"
 	appName           = "PrivateVoice Dictation"
 
diff --git a/privatevoice.src/build/darwin/Info.plist b/privatevoice.src/build/darwin/Info.plist
index 4612d46..3ff6829 100755
--- a/privatevoice.src/build/darwin/Info.plist
+++ b/privatevoice.src/build/darwin/Info.plist
@@ -17,9 +17,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>2.1.38</string>
+	<string>2.1.40</string>
 	<key>CFBundleVersion</key>
-	<string>20260629.2014</string>
+	<string>20260702.0143</string>
 	<key>ITSAppUsesNonExemptEncryption</key>
 	<false/>
 	<key>LSApplicationCategoryType</key>
diff --git a/privatevoice.src/scripts/build-local-macos-universal.sh b/privatevoice.src/scripts/build-local-macos-universal.sh
new file mode 100755
index 0000000..cccf815
--- /dev/null
+++ b/privatevoice.src/scripts/build-local-macos-universal.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+#
+# build-local-macos-universal.sh - Build a locally signed universal macOS DMG.
+#
+# The script reuses the existing per-architecture local build script, then
+# combines the app binary and bundled dylibs with lipo before re-signing.
+#
+set -euo pipefail
+
+APP_NAME="PrivateVoice Dictation"
+DEFAULT_LOCAL_IDENTITY="PrivateVoice Local Code Signing"
+LEGACY_LOCAL_IDENTITY="VoiceSnap Local Code Signing"
+LOCAL_IDENTITY="${PRIVATEVOICE_LOCAL_SIGN_IDENTITY:-${VOICESNAP_LOCAL_SIGN_IDENTITY:-}}"
+KEYCHAIN="${PRIVATEVOICE_LOCAL_KEYCHAIN:-${VOICESNAP_LOCAL_KEYCHAIN:-$HOME/Library/Keychains/login.keychain-db}}"
+LOCAL_BUILD_FLAVOR="${PRIVATEVOICE_LOCAL_BUILD_FLAVOR:-local}"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+INFO_PLIST="$PROJECT_DIR/build/darwin/Info.plist"
+ENTITLEMENTS="$PROJECT_DIR/build/darwin/entitlements.plist"
+VERSION="$(defaults read "$INFO_PLIST" CFBundleShortVersionString 2>/dev/null || echo "2.1.1")"
+BUILD_ID="$(defaults read "$INFO_PLIST" CFBundleVersion 2>/dev/null || date +%Y%m%d.%H%M)"
+
+ARM_BUILD_DIR="$PROJECT_DIR/build/local/arm64"
+X86_BUILD_DIR="$PROJECT_DIR/build/local/x86_64"
+BUILD_DIR="$PROJECT_DIR/build/local/universal"
+ARM_APP="$ARM_BUILD_DIR/$APP_NAME.app"
+X86_APP="$X86_BUILD_DIR/$APP_NAME.app"
+APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
+DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-build$BUILD_ID-universal-$LOCAL_BUILD_FLAVOR.dmg"
+COMPAT_DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-universal-$LOCAL_BUILD_FLAVOR.dmg"
+
+step() {
+  echo ""
+  echo "==> $1"
+}
+
+fail() {
+  echo "ERROR: $1" >&2
+  exit 1
+}
+
+resolve_local_identity() {
+  if [[ -z "$LOCAL_IDENTITY" ]]; then
+    if security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$DEFAULT_LOCAL_IDENTITY"; then
+      LOCAL_IDENTITY="$DEFAULT_LOCAL_IDENTITY"
+    elif security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LEGACY_LOCAL_IDENTITY"; then
+      LOCAL_IDENTITY="$LEGACY_LOCAL_IDENTITY"
+    else
+      fail "Local signing identity not found; run build-local-macos.sh first"
+    fi
+  fi
+
+  if ! security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LOCAL_IDENTITY"; then
+    fail "Local signing identity not available to codesign: $LOCAL_IDENTITY"
+  fi
+}
+
+sign_item() {
+  local item="$1"
+  codesign --force \
+    --options runtime \
+    --timestamp=none \
+    --entitlements "$ENTITLEMENTS" \
+    --sign "$LOCAL_IDENTITY" \
+    "$item"
+}
+
+step "Building arm64 app"
+"$SCRIPT_DIR/build-local-macos.sh" arm64
+
+step "Building x86_64 app"
+"$SCRIPT_DIR/build-local-macos.sh" x86_64
+
+step "Preflight checks"
+[[ -d "$ARM_APP" ]] || fail "arm64 app not found at $ARM_APP"
+[[ -d "$X86_APP" ]] || fail "x86_64 app not found at $X86_APP"
+[[ -f "$ENTITLEMENTS" ]] || fail "entitlements.plist not found at $ENTITLEMENTS"
+resolve_local_identity
+
+step "Assembling universal app"
+rm -rf "$BUILD_DIR"
+mkdir -p "$BUILD_DIR"
+ditto "$ARM_APP" "$APP_BUNDLE"
+
+lipo -create \
+  "$ARM_APP/Contents/MacOS/$APP_NAME" \
+  "$X86_APP/Contents/MacOS/$APP_NAME" \
+  -output "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
+
+for arm_dylib in "$ARM_APP"/Contents/Frameworks/*.dylib; do
+  dylib_name="$(basename "$arm_dylib")"
+  x86_dylib="$X86_APP/Contents/Frameworks/$dylib_name"
+  out_dylib="$APP_BUNDLE/Contents/Frameworks/$dylib_name"
+  [[ -f "$x86_dylib" ]] || fail "Matching x86_64 dylib not found: $x86_dylib"
+  lipo -create "$arm_dylib" "$x86_dylib" -output "$out_dylib"
+done
+
+step "Code signing universal app"
+for dylib in "$APP_BUNDLE"/Contents/Frameworks/*.dylib; do
+  sign_item "$dylib"
+done
+sign_item "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
+sign_item "$APP_BUNDLE"
+
+step "Verifying universal app"
+codesign -vvv --deep --strict "$APP_BUNDLE"
+lipo -archs "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
+
+step "Creating universal DMG"
+DMG_TEMP="$BUILD_DIR/dmg-staging"
+rm -rf "$DMG_TEMP" "$DMG_PATH" "$COMPAT_DMG_PATH"
+mkdir -p "$DMG_TEMP"
+cp -R "$APP_BUNDLE" "$DMG_TEMP/"
+ln -s /Applications "$DMG_TEMP/Applications"
+hdiutil create -volname "$APP_NAME" \
+  -srcfolder "$DMG_TEMP" \
+  -ov -format UDZO \
+  "$DMG_PATH"
+rm -rf "$DMG_TEMP"
+
+step "Signing universal DMG"
+codesign --force --timestamp=none --sign "$LOCAL_IDENTITY" "$DMG_PATH"
+hdiutil verify "$DMG_PATH"
+cp "$DMG_PATH" "$COMPAT_DMG_PATH"
+
+step "Universal local build complete"
+echo "  App: $APP_BUNDLE"
+echo "  DMG: $DMG_PATH"
+echo "  Compatibility DMG: $COMPAT_DMG_PATH"
+echo "  Signing identity: $LOCAL_IDENTITY"
diff --git a/privatevoice.src/scripts/build-windows-preview.sh b/privatevoice.src/scripts/build-windows-preview.sh
index f715c3d..4fe9bf1 100755
--- a/privatevoice.src/scripts/build-windows-preview.sh
+++ b/privatevoice.src/scripts/build-windows-preview.sh
@@ -4,8 +4,8 @@
 ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
 cd "$ROOT_DIR"
 
-APP_VERSION="2.1.39"
-APP_BUILD="20260701.2356"
+APP_VERSION="2.1.40"
+APP_BUILD="20260702.0143"
 ARCH="${1:-amd64}"
 
 if [[ "$ARCH" != "amd64" ]]; then

--
Gitblit v1.9.3