[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.
This commit is contained in:
@@ -16,24 +16,6 @@
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<string>CA92.1</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<string>C617.1</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<string>E174.1</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<string>35F9.1</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
@@ -27,7 +27,6 @@ struct APISettingsCard: View {
|
||||
title: "Base URL",
|
||||
placeholder: "https://api.openai.com/v1",
|
||||
text: $config.baseURL,
|
||||
keyboard: .URL,
|
||||
autocap: false
|
||||
)
|
||||
Divider().background(palette.divider)
|
||||
@@ -37,7 +36,6 @@ struct APISettingsCard: View {
|
||||
title: "Model",
|
||||
placeholder: "gpt-4o-mini",
|
||||
text: $config.model,
|
||||
keyboard: .default,
|
||||
autocap: false
|
||||
)
|
||||
if let url = LLMProvider.provider(id: config.providerId).apiKeyURL {
|
||||
@@ -97,6 +95,7 @@ struct APISettingsCard: View {
|
||||
SecureField("sk-…", text: $config.apiKey)
|
||||
}
|
||||
}
|
||||
.keyboardType(.asciiCapable)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled(true)
|
||||
.font(TypeStyle.body)
|
||||
@@ -113,15 +112,20 @@ struct APISettingsCard: View {
|
||||
title: String,
|
||||
placeholder: String,
|
||||
text: Binding<String>,
|
||||
keyboard: UIKeyboardType,
|
||||
autocap: Bool
|
||||
) -> some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title)
|
||||
.font(TypeStyle.caption)
|
||||
.foregroundStyle(palette.textSecondary)
|
||||
// `.asciiCapable` is the *minimum* contract for these fields:
|
||||
// API keys, base URLs, and model names are all ASCII by spec,
|
||||
// and SwiftUI's `.URL` keyboard on iOS 18 still occasionally
|
||||
// hands control to the system keyboard which then auto-suggests
|
||||
// Chinese / emoji completions that corrupt the value. Forcing
|
||||
// `.asciiCapable` keeps the system out of the way.
|
||||
TextField(placeholder, text: text)
|
||||
.keyboardType(keyboard)
|
||||
.keyboardType(.asciiCapable)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(autocap ? .sentences : .never)
|
||||
.font(TypeStyle.body)
|
||||
|
||||
Reference in New Issue
Block a user