fix: 3 review issues from the keyboard preview / onboarding flow
1) Preview chips weren't actually buttons.
`modeChip` and `localeChip` in `KeyboardPreviewStub` were
decorative HStacks — no `Button`, no action, no callback. The
chevron-down glyph made them *look* like pickers, so a user
tapping them got nothing. The screenshot the user shared
("润色 ▾" / "中文(简体) ▾") shows exactly that surface.
Fix: wrap each chip in a `Button(action: ...)` with
`.buttonStyle(.plain)`. The stub now takes `modeId`, `localeId`,
`onModeCycle`, `onLocaleCycle` and the sheet's `cycleMode` /
`cycleLocale` advance the config:
- mode cycles [off → transcribe → polish] (mirrors Settings)
- locale cycles [auto → zh-Hans → zh-Hant → en-US → ja-JP → ko-KR]
Mid-recording locale switches call `asr.stop()` because ASR
sessions are bound to the locale they were started with.
`modeId == "off"` also stops any in-flight recording so the
disc isn't recording into a mode that won't insert.
The mode chip's icon also follows the mode (mic.slash /
mic / wand) as a redundant visual cue, and both chips get
accessibility labels (preview.modeChip.cycle /
preview.localeChip.cycle) so VoiceOver users can use them.
2) Onboarding's "Next" stays enabled when local engine is picked
but no API key is filled in. Root cause: `ProviderConfig.isConfigured`
checks `!apiKey.isEmpty && !baseURL.isEmpty && !model.isEmpty` —
it never asks whether the user *needs* a key. The local engine
(on-device ASR) doesn't round-trip through the LLM, so an
empty key on the local path is correct, not a configuration gap.
Fix: short-circuit `isConfigured` to `true` when
`engineMode == "local"`. The onboarding "Next" button is
already disabled on the API page when `!isConfigured`; this
just makes the gate respect the engine choice. New test
`testIsConfiguredTrueForLocalEngineWithoutAPIKey` locks the
behaviour in (local → true, cloud → false, flip back).
3) Add the two new accessibility keys to all four
`Localizable.strings` files (en + zh-Hans, main app + ext)
so VoiceOver and the cycle button labels resolve in both
languages.
Build: BUILD SUCCEEDED.
Tests: 22/22 pass (1 new).
🤖 Generated with Claude Code
This commit is contained in:
@@ -46,6 +46,28 @@ final class LLMClientTests: XCTestCase {
|
||||
XCTAssertTrue(config2.isConfigured)
|
||||
}
|
||||
|
||||
/// Local engine path: with `engineMode = "local"`, `isConfigured`
|
||||
/// must return `true` even when the API key is empty — onboarding
|
||||
/// gates the "Next" button on this property, and the local path
|
||||
/// never needs a key. Regression: see commit `isConfigured` fix
|
||||
/// that exposed this gate.
|
||||
func testIsConfiguredTrueForLocalEngineWithoutAPIKey() {
|
||||
let suiteName = "group.com.osgkeyboard.shared.tests"
|
||||
let defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
|
||||
let config = ProviderConfig(defaults: defaults)
|
||||
// No apiKey, no baseURL, no model — cloud would fail.
|
||||
XCTAssertFalse(config.isConfigured)
|
||||
// Switch to local engine: should flip to true regardless of
|
||||
// the missing cloud fields.
|
||||
config.engineMode = "local"
|
||||
XCTAssertTrue(config.isConfigured)
|
||||
// And back to cloud: should flip to false again.
|
||||
config.engineMode = "cloud"
|
||||
XCTAssertFalse(config.isConfigured)
|
||||
}
|
||||
|
||||
// MARK: - OpenAICompatibleClient
|
||||
|
||||
func testPolishSendsCorrectRequestAndDecodesResponse() async throws {
|
||||
|
||||
Reference in New Issue
Block a user