Files
OSGKeyboard/project.yml
T
Rocky bec36befa2 feat: comprehensive rewrite — push-to-talk pipeline, Typeless UI, Chinese
This is a major rewrite of OpenLessKeyboard, renamed to OSGKeyboard
and rebuilt end-to-end. 59 files changed (+3205/-1550).

Architecture
------------
- Rename project, targets, directories from OpenLess* to OSGKeyboard*
  (OpenLess / OpenLessKeyboard / OpenLessShared / OpenLessTests).
- AudioCaptureService rewritten as @unchecked Sendable class with
  OSAllocatedUnfairLock instead of an actor, so it survives Swift 6
  strict-concurrency checks while still serialising engine + converter
  state correctly.
- Single design system (Palette / Spacing / Radius / TypeStyle /
  Motion) lifted into OSGKeyboardShared so the host app and the
  keyboard extension stay in lock-step.

Push-to-talk — first-principles fix
-----------------------------------
- App Group + audio-input entitlements were stripped by Xcode's
  Automatic Signing. They are now declared in project.yml so
  'xcodegen generate' re-emits them every time. iOS Developer
  Account is untouched; only the App Group capability was added.
- State machine uses a real stored `phase` (was a derived shim
  that locked out every press after the first because
  recordStream was never nilled after the pipeline finished).
- Microphone permission is requested inside pressBegan (async
  Task) so the press flow optimistically enters .recording;
  permission denial surfaces a short error and returns to idle.
- Replaced LongPressGesture(0.15s) with a DragGesture +
  TapGesture pair separated by pressArmed, so a single tap no
  longer fires both onPressBegan and onTap simultaneously.
- Real RMS / peak level meter from the AVAudioEngine tap (was a
  pseudo-random walk); the visible waveform is now driven by
  actual audio.
- SFSpeechRecognizer(locale:) with selectable ASR locales
  (auto / zh-Hans / zh-Hant / en-US / ja-JP / ko-KR) for
  first-class Chinese / English / Japanese / Korean dictation,
  with on-device recognition when supported.
- AVAudioSession now deactivates on stop so other apps' audio
  routing is restored.

Keyboard UI — Typeless-inspired layout
---------------------------------------
- Hero area is 280 pt with a 96 pt record disc, breathing outer
  ring, and a 12-bar waveform driven by the real RMS.
- inputView.allowsSelfSizing + a heightAnchor constraint so iOS
  no longer crops the keyboard under the Spotlight bar / home
  indicator.
- Top bar: mode chip (Off / 转写 / 润色) + locale chip
  (Auto / 简体 / 繁體 / EN / 日 / 한) + status badge + ⚙.
- Bottom bar: globe / delete / 空格 / return — all 40 pt and
  balanced.
- RecordButton onPressEnded is now safe to fire from a quick
  press; pressArmed prevents double-firing.

LLM / Polishing
---------------
- LLMClient: stopped leaking the server response body in errors
  (server body is now logged at debug, never surfaced to UI);
  added a dedicated .rateLimited case for 429.
- PolishingService timeout 8s → 12s to accommodate slower
  domestic LLM providers.
- AppGroupStore.defaultSystemPrompt is now provider-aware
  (Chinese for zhipu/moonshot/qwen/deepseek, English otherwise).

Onboarding & Settings
---------------------
- Re-themed OnboardingView / HomeView / SettingsView on the
  new design system.
- ProviderPickerSection now shows 6 providers (OpenAI, DeepSeek,
  Qwen DashScope, 智谱 GLM, 月之暗面 Moonshot, Custom) with
  blurb + selected accent.
- PickerRow for Mode and ASR locale; System Prompt editor with
  reset-to-default.
- API settings page "Get an API key" used SwiftUI Link, which
  has a hit-test bug on iOS 18 that ate gestures from adjacent
  TextFields (manifested as "typing jumps to a website"). It is
  now an explicit Button + contentShape + .submitLabel(.done) on
  the fields.

Polish & tests
--------------
- LLMClientTests: 4 unit tests passing (ProviderConfig
  persistence + OpenAI request/response + HTTP error + missing
  key); test App Group renamed to the correct identifier.
- ProviderConfig.apply now captures the previous provider id
  *before* mutating, so switching providers actually resets the
  system prompt to the new default.

Build
-----
- Swift 6 strict concurrency, iOS 18.0 deployment target.
- Tested on Xcode 26 + iPhone 17 Pro simulator. A real device on
  iOS 27 beta aborts with __abort_with_payload (dispatch
  library ABI mismatch); use an iOS 18 real device or the
  iOS 26 simulator for now.

🤖 Generated with Claude Code
2026-06-18 01:22:12 +08:00

165 lines
4.9 KiB
YAML

# XcodeGen configuration for OSGKeyboard
# Run `xcodegen generate` to create OSGKeyboard.xcodeproj
# Repo only tracks project.yml; .xcodeproj is gitignored.
name: OSGKeyboard
options:
bundleIdPrefix: com.osgkeyboard
deploymentTarget:
iOS: "18.0"
developmentLanguage: en
createIntermediateGroups: true
generateEmptyDirectories: true
groupSortPosition: top
settings:
base:
SWIFT_VERSION: "6.0"
IPHONEOS_DEPLOYMENT_TARGET: "18.0"
ENABLE_USER_SCRIPT_SANDBOXING: YES
SWIFT_STRICT_CONCURRENCY: complete
GENERATE_INFOPLIST_FILE: NO
ENABLE_MODULE_VERIFIER: YES
CODE_SIGN_STYLE: Automatic
CODE_SIGN_IDENTITY: "Apple Development"
DEVELOPMENT_TEAM: ""
MARKETING_VERSION: "0.1.0"
CURRENT_PROJECT_VERSION: "1"
targets:
# =========================================================
# 主 App
# =========================================================
OSGKeyboard:
type: application
platform: iOS
sources:
- path: OSGKeyboard
entitlements:
path: OSGKeyboard/OSGKeyboard.entitlements
properties:
com.apple.security.application-groups:
- group.com.osgkeyboard.shared
com.apple.security.device.audio-input: true
resources:
- path: OSGKeyboard/Assets.xcassets
info:
path: OSGKeyboard/Info.plist
properties:
CFBundleDisplayName: OSGKeyboard
CFBundleShortVersionString: "$(MARKETING_VERSION)"
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
UILaunchScreen:
UIColorName: "BackgroundColor"
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
NSMicrophoneUsageDescription: "OSGKeyboard needs microphone access to transcribe your voice into text."
NSAppTransportSecurity:
NSAllowsArbitraryLoads: false
CFBundleURLTypes:
- CFBundleURLName: com.osgkeyboard.ios.settings
CFBundleURLSchemes:
- osgkeyboard
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.ios
TARGETED_DEVICE_FAMILY: "1,2"
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
dependencies:
- target: OSGKeyboardShared
embed: true
- target: OSGKeyboardExt
# Keyboard Extension is a plugin of the main App; embed it.
# =========================================================
# Keyboard Extension
# =========================================================
OSGKeyboardExt:
type: app-extension
platform: iOS
sources:
- path: OSGKeyboardExt
settings:
base:
IPHONEOS_DEPLOYMENT_TARGET: "18.0"
entitlements:
path: OSGKeyboardExt/OSGKeyboardExt.entitlements
properties:
com.apple.security.application-groups:
- group.com.osgkeyboard.shared
info:
path: OSGKeyboardExt/Info.plist
properties:
CFBundleDisplayName: OSGKeyboard
CFBundleShortVersionString: "$(MARKETING_VERSION)"
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
NSExtension:
NSExtensionAttributes:
IsASCIICapable: false
PrefersRightToLeft: false
PrimaryLanguage: "en-US"
RequestsOpenAccess: true
NSExtensionPointIdentifier: com.apple.keyboard-service
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).KeyboardViewController
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.ios.keyboard
TARGETED_DEVICE_FAMILY: "1,2"
dependencies:
- target: OSGKeyboardShared
embed: false
# =========================================================
# Shared Framework (主 App 与扩展共用)
# =========================================================
OSGKeyboardShared:
type: framework
platform: iOS
sources:
- path: OSGKeyboardShared
info:
path: OSGKeyboardShared/Info.plist
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.ios.shared
DEFINES_MODULE: YES
SKIP_INSTALL: YES
BUILD_LIBRARY_FOR_DISTRIBUTION: NO
DYLIB_INSTALL_NAME_BASE: "@rpath"
APPLICATION_EXTENSION_API_ONLY: YES
ENABLE_MODULE_VERIFIER: YES
# =========================================================
# 单元测试
# =========================================================
OSGKeyboardTests:
type: bundle.unit-test
platform: iOS
sources:
- path: OSGKeyboardTests
info:
path: OSGKeyboardTests/Info.plist
dependencies:
- target: OSGKeyboard
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.osgkeyboard.ios.tests
schemes:
OSGKeyboard:
build:
targets:
OSGKeyboard: all
OSGKeyboardExt: all
run:
config: Debug
test:
config: Debug
targets:
- OSGKeyboardTests
archive:
config: Release