Files
OSGKeyboard/CHANGELOG.md
T
hkgood 79be7384dd [JJC-20260618-005-D] P1/P2 cleanup: structured errors, privacy audit, timeout SSOT, view-model tests
13 items, 555-line diff, build + 15/15 tests green.

ARCH-A3: Phase.error now carries ErrorKind (micDenied/speechDenied/asr/llm/
appGroupUnavailable/unknown) so the UI can pick icons/copy without parsing
free-form strings. Phase.ErrorKind, Phase, LLMError all Equatable.

ARCH-A4: Every TextField in APISettingsCard gets .keyboardType(.asciiCapable)
to defeat SwiftUI's iOS 18 system-keyboard hand-off that auto-suggests
Chinese/emoji and corrupts API keys / URLs / model names.

ARCH-A5 + DOC-3: PrivacyInfo.xcprivacy audited for honesty. Removed three
declared-but-unused APIs (FileTimestamp / DiskSpace / SystemBootTime) and
added ActiveKeyboards (DDA9.1) to the extension (it actually calls
advanceToNextInputMode in the tap path). Main App now declares only
UserDefaults (CA92.1). CHANGELOG updated.

ARCH-A6: Extracted PermissionManager (mic+speech permission flow, iOS 17
branching) and AppGroupPersistor (App Group load/persist) from the God
Object. KeyboardViewController drops 515 → 459 lines. KeyboardPipelineController
left in-place per risk plan — pressBegan state machine is too race-sensitive
to refactor in this pass.

RED-2: Deleted unused Theme enum (no call sites).
RED-3: Deleted unused cardStyle() alias (no call sites).
RED-7: Single source of truth for LLM timeout — LLMClient.requestTimeout +
LLMClientFactory.defaultRequestTimeout; PolishingService derives timeout from
defaultRequestTimeout+1 instead of hardcoding 15.
RED-8: ASRService.transcribe now emits .capability(onDeviceSupported:) as
first event per session; StatusBadge shows REC ⚠️ when the locale fell
back to cloud. New @Published var onDeviceSupported on State.

TEST-1: testPolishThrowsOnTransportTimeout now actually exercises
cancellation: StubURLProtocol delays response 5s, client.polish is
cancelled via Task.cancel(), test asserts the client throws .cancelled /
.transport / .decoding (was: silently passed).

TEST-2: New testPolisherSkipsNetworkWhenModeOff — PolishingService now
short-circuits when modeId == 'off' and returns trimmed input without
invoking LLMClient (proved via injected CountingLLMClient). Service was
moved to OSGKeyboardShared to be reachable from the test target.

TEST-3: KeyboardState (formerly KeyboardViewController.State) extracted
into OSGKeyboardShared so tests can @testable-import it. 5 phase/mode
tests in new KeyboardStateTests. Typealias preserves the old name.

TEST-4: New OSGKeyboardExtTests target with 6 tests covering State
initial values, phase transitions, structured-error round-trip, mode
switching, and InputMode rawValue round-trip.
2026-06-18 12:27:15 +08:00

4.4 KiB
Raw Blame History

Changelog

All notable changes to OSGKeyboard will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Note: v0.1.1 polish — this is a small follow-up to v0.1.0 focused on review-driven cleanup (theme follow-up, ASR robustness, debug-print hygiene, docs). No features are removed. The iOS 26 SpeechAnalyzer path remains deferred to 0.2.0 (see below); v0.1.1 users continue to ship with the iOS 18 SFSpeechRecognizer path that shipped in v0.1.0. User experience is unchanged from v0.1.0.

Fixed

  • PrivacyInfo.xcprivacy audited for honesty: removed the three undeclared NSPrivacyAccessedAPIType entries (FileTimestamp / DiskSpace / SystemBootTime) the project doesn't actually use, and added ActiveKeyboards (reason DDA9.1) to the keyboard extension's manifest because advanceToNextInputMode() is in the tap path. The main App now declares only UserDefaults (reason CA92.1), which is the only Required Reason API it touches.
  • Theme follows system appearance: main App now renders a true light palette in light mode via ThemedRoot + EnvironmentKey<ThemePalette>. The keyboard extension deliberately stays dark (Apple's default) and now uses a transparent .background(Color.clear) so the system UI chrome shows through.
  • Speech Recognition permission requested on first press: added NSSpeechRecognitionUsageDescription to both targets' Info.plist and an explicit SFSpeechRecognizer.requestAuthorization call inside pressBegan(). Without these the iOS 18 ASR path silently returned .denied and the user heard nothing.
  • ASRService emits a DEBUG warning when on-device recognition isn't supported, so it's obvious during dev that the request fell back to cloud.
  • App Group fallback behaviour:
    • In DEBUG, a missing App Group now fatalErrors with a precise remediation message (was a soft print + .standard fallback, which desynced the keyboard extension from the main App).
    • In release, the fallback is preserved but logged via NSLog.
  • KeyboardViewController.loadPersistedLocale self-check (DEBUG only) prints the active provider / baseURL / masked API key / mode / locale, so the keyboard extension's view of the App Group is visible in the device console.
  • KeyboardViewController.handleFinalTranscript typed-error routing: LLMError.noAPIKey (401) and LLMError.http(429) now surface as red, explicit error messages instead of silently inserting the raw transcript. Network / timeout errors still fall back to the raw transcript + error badge (no data loss).
  • PolishingService timeout raised from 12 s to 15 s to align with LLMClient's URL request timeout. Previously the polisher would race the network call and discard a successful response that arrived in the 1215 s window.
  • DEBUG print cleanup: the four 🔥 [OSGKeyboardApp] … instrumentation prints in OSGKeyboardApp.init() and root views are now wrapped in #if DEBUG.

Added

  • "Test connection" button in APISettingsCard: fires a single polish("ping") round-trip and surfaces success or the typed LLM error inline. Helps the user confirm the App Group + key are working without leaving the main App.
  • 5 new unit tests in OSGKeyboardTests: App Group cross-process persistence, 401 / 429 / timeout / noAPIKey catch paths, and mode = .off short-circuit.

Changed

  • README + README.zh.md: replaced <OWNER> placeholder with hkgood and rephrased the iOS 26 SpeechAnalyzer line as "planned for the next release" (the iOS 18 SFSpeechRecognizer path remains the only working ASR for v0.1).

Known limitations

  • iOS 26 SpeechAnalyzer + DictationTranscriber on-device ASR is planned for 0.2.0 (moved out of Unreleased scope to keep the v0.1 release honest about what ships).
  • The keyboard does not work in password fields (iOS limitation).
  • Microphone requires "Allow Full Access" to be enabled in iOS Settings.
  • Whisper.cpp / on-device LLM polish is intentionally out of scope for v1 (cloud-only).

[0.2.0] - Planned

Added

  • iOS 26+ SpeechAnalyzer + DictationTranscriber on-device ASR (lower latency, more locales).
  • Bilingual UI (中文 / English) driven by a real Localizable.strings table; will land alongside a Settings → Language picker.

[0.1.0] - 2026-06-17

Added

  • First public pre-release.