From 8fe4d7503ae42a5aafc4d4eaceb1b632fa5e18dc Mon Sep 17 00:00:00 2001 From: Peng Cheng Date: Tue, 23 Jun 2026 21:46:03 +0800 Subject: [PATCH] fix(macos): demo-blocking polish + stable dev signing Three issues that would look bad on camera, plus the signing fix for the "re-authorize on every rebuild" pain: - AudioCaptureService.checkPermission was a query with side effects: it set a verbose lastError (surfaced as a red banner) and opened System Settings on its own. That fired even when the ViewModel was about to fall back to microphone-only capture, so screen-recording denial showed a scary error instead of silently degrading. Made it a pure query; the VM now owns the decision (mic fallback vs. a concise actionable notice). - The Ollama status indicator showed "stopped" on launch until first use, because nothing probed it. Probe checkStatus()/checkHealth() in the VM initializer so the indicator reflects reality immediately. - Stable local code-signing identity: ad-hoc signatures change every build, invalidating TCC permissions and forcing re-authorization each time. package-app.sh now signs with a stable "LCT Dev" identity when present (ad-hoc fallback for CI), and scripts/dev-cert.sh creates it once. Also investigated an intermittent launch-time auto-capture: not reproducible on clean launch (5/5 idle); appears to have been lingering instances during rapid test cycling. Latent risk noted: window-close-doesn't-quit + global hotkeys could allow multi-instance confusion (follow-up: single-instance guard). Co-Authored-By: Claude Opus 4.8 --- .../LCTMac/Services/AudioCaptureService.swift | 11 ++-- macos/LCTMac/ViewModels/TranscriptionVM.swift | 7 +++ macos/Scripts/dev-cert.sh | 51 +++++++++++++++++++ macos/package-app.sh | 15 +++++- 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100755 macos/Scripts/dev-cert.sh diff --git a/macos/LCTMac/Services/AudioCaptureService.swift b/macos/LCTMac/Services/AudioCaptureService.swift index a001753..8d96a02 100644 --- a/macos/LCTMac/Services/AudioCaptureService.swift +++ b/macos/LCTMac/Services/AudioCaptureService.swift @@ -141,15 +141,14 @@ class AudioCaptureService: NSObject, ObservableObject, @unchecked Sendable { } appLog("[AudioCaptureService] --------- Permission Check Failed ---------") - appLog("[AudioCaptureService] Showing error and opening settings.") - + + // Pure query: don't set lastError or open System Settings here. The caller + // (TranscriptionViewModel) decides whether this is fatal — if microphone + // capture is enabled it silently falls back instead of surfacing an error. Task { @MainActor in self.hasPermission = false - self.lastError = "Screen recording permission denied. Please:\n1. Open System Settings > Privacy & Security > Screen & System Audio Recording\n2. Enable LCTMac\n3. Restart the application" } - - AudioCaptureService.openScreenRecordingSettings() - + return false } diff --git a/macos/LCTMac/ViewModels/TranscriptionVM.swift b/macos/LCTMac/ViewModels/TranscriptionVM.swift index 5f7c8e1..9d5a21b 100644 --- a/macos/LCTMac/ViewModels/TranscriptionVM.swift +++ b/macos/LCTMac/ViewModels/TranscriptionVM.swift @@ -94,6 +94,13 @@ class TranscriptionViewModel: ObservableObject { if let persistenceError = AppSettings.consumeLastPersistenceError() { notice = .warning(persistenceError, autoDismiss: false) } + + // Probe Ollama on launch so the status indicator reflects reality + // immediately, instead of showing the default "stopped" until first use. + Task { + await ollamaGuardian.checkStatus() + isOllamaConnected = await ollamaService.checkHealth() + } } // MARK: - Setup diff --git a/macos/Scripts/dev-cert.sh b/macos/Scripts/dev-cert.sh new file mode 100755 index 0000000..af10996 --- /dev/null +++ b/macos/Scripts/dev-cert.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Create a stable local code-signing identity ("LCT Dev") for development. +# +# Why: ad-hoc signatures (codesign --sign -) change on every build, and macOS +# ties TCC permissions (screen recording, microphone, speech) to the signing +# identity. So each rebuild looks like a brand-new app and forces you to +# re-grant permissions. A stable self-signed identity fixes that — grant once, +# then permissions persist across rebuilds. +# +# Run once: ./scripts/dev-cert.sh (then rebuild with ./package-app.sh) +# The certificate is local-only, needs no Apple account, and never leaves your Mac. +set -euo pipefail + +IDENTITY="LCT Dev" + +if security find-identity -p codesigning 2>/dev/null | grep -q "$IDENTITY"; then + echo "Identity '$IDENTITY' already exists — nothing to do." + exit 0 +fi + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +cat > "$WORK/cert.conf" <<'EOF' +[req] +distinguished_name = dn +x509_extensions = ext +prompt = no +[dn] +CN = LCT Dev +[ext] +basicConstraints = critical, CA:false +keyUsage = critical, digitalSignature +extendedKeyUsage = critical, codeSigning +EOF + +openssl req -x509 -newkey rsa:2048 -keyout "$WORK/key.pem" -out "$WORK/cert.pem" \ + -days 3650 -nodes -config "$WORK/cert.conf" 2>/dev/null + +# macOS `security` can't read modern OpenSSL PKCS#12: force legacy algorithms +# and a non-empty password (empty-password p12 import is buggy on macOS). +openssl pkcs12 -export -legacy -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 \ + -inkey "$WORK/key.pem" -in "$WORK/cert.pem" -out "$WORK/cert.p12" \ + -name "$IDENTITY" -passout pass:lctdev 2>/dev/null + +# -A lets codesign use the private key without a keychain prompt on every build. +security import "$WORK/cert.p12" -k ~/Library/Keychains/login.keychain-db -P lctdev -A + +echo "Created code-signing identity '$IDENTITY'." +echo "Rebuild with ./package-app.sh. You'll re-grant permissions ONCE more" +echo "(new identity), after which they persist across all future rebuilds." diff --git a/macos/package-app.sh b/macos/package-app.sh index e1741d0..60a1d69 100755 --- a/macos/package-app.sh +++ b/macos/package-app.sh @@ -22,5 +22,16 @@ BUILD=$(git rev-list --count HEAD 2>/dev/null || echo "1") /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$APP/Contents/Info.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD" "$APP/Contents/Info.plist" -codesign --force --sign - "$APP" -echo "Packaged $APP $VERSION ($BUILD) — launch with: open $APP" +# Sign with a stable local identity if one exists, so macOS TCC permissions +# (screen recording, microphone, speech) survive rebuilds instead of being +# invalidated every time — ad-hoc signatures change on each build, which forces +# re-authorization. Create the dev identity once with: ./scripts/dev-cert.sh +# Falls back to ad-hoc on CI / machines without the cert. +SIGN_IDENTITY="${LCT_SIGN_IDENTITY:-LCT Dev}" +if security find-identity -p codesigning 2>/dev/null | grep -q "$SIGN_IDENTITY"; then + codesign --force --sign "$SIGN_IDENTITY" "$APP" + echo "Packaged $APP $VERSION ($BUILD), signed as '$SIGN_IDENTITY' — launch with: open $APP" +else + codesign --force --sign - "$APP" + echo "Packaged $APP $VERSION ($BUILD), ad-hoc signed — launch with: open $APP" +fi