fix: green accent + real iOS ASR in preview + bilingual audit

Three user-flagged fixes, scoped tightly to the files each affects.
The uncommitted Engine-mode wiring / locale picker / etc. from a
prior agent pass is intentionally not included in this commit.

1. Revert accent to brand green (#3AA05A).
   Last commit flipped `AccentColor` + `Palette.light.accent` to
   Apple system blue (#007AFF) on the assumption that "green CTA
   on a near-white surface looks wrong." Review pushed back:
   the brand *is* the green, and the system tint should match it
   so that NavStack Done buttons, Toggles, and our custom
   `primaryButton()` modifier all read as the same colour. Restored
   `#3AA05A` in both `AccentColor.colorset/Contents.json` and
   `Palette.light.accent` (plus the muted / glow variants).

2. Real iOS ASR in `KeyboardPreviewSheet`.
   The previous fix only swapped the static placeholder for a
   `TextField` and routed a hardcoded stub through it — review
   asked, fairly, "are you actually calling `SFSpeechRecognizer`?"
   Answer: no. This change makes the preview *run real ASR*:

   - `ASRService` (+ the iOS 26 `SpeechAnalyzer` path) moves from
     `OSGKeyboardExt/Services/` to `OSGKeyboardShared/Services/`,
     so the host app can import the same `ASRServiceFactory.make()`
     the keyboard extension uses.
   - `OSGKeyboardShared` gains `Speech.framework` and
     `AVFoundation.framework` as SDK dependencies in `project.yml`.
   - New `OSGKeyboard/Views/PreviewASRController.swift` (the
     extension's `AudioCaptureService` is app-extension-only, so
     the preview owns its own `AVAudioEngine` + `AVAudioSession`
     and downsamples to 16 kHz mono Float32 via `AVAudioConverter`).
   - `KeyboardPreviewSheet.cyclePhase()` now calls
     `asr.start(locale:)` / `asr.stop()` instead of toggling a
     `StubPhase`. The disc's level meter is driven by RMS from the
     actual audio tap; the transcript line under the chips shows
     the live `SFSpeechRecognizer` partial; the top textbox
     receives the `.final` transcript via `onChange(of: lastFinal)`.

3. Record disc in BOTH stubs now uses the green accent for idle.
   Was `Color(white: 0.22)` dark-gray, which read as "inert
   surface" rather than "tap me". The keyboard extension and the
   in-app preview now share the same brand-green disc gradient so
   the keyboard's primary CTA is the same colour in both modes.

4. Bilingual audit across every user-facing string.
   Every Text() in the main-app and extension views is now
   `中文 · English` or carries an English secondary line. Covered:
   - OnboardingView (Back, Next, Done, Continue, step instructions,
     PrivacyFootnote rows)
   - HomeView (status header, hero label, accessibility)
   - APISettingsCard (Base URL, API Key, Model, "Get an API key",
     "Connection", test-connection states + error messages)
   - KeyboardPreviewSheet (title, subtitle, TextField placeholder,
     clear button accessibility)
   - KeyboardPreviewStub (mode/locale chips, REC badge, space bar)
   - KeyboardRootView (gear accessibility, space bar, requesting
     state, denied messages, local-engine chip)
   - RecordButton (accessibility label)
   - SettingsView (Done, Reset dialog, language section subtitle,
     engine section, local-engine subtitle, on-device label)
   - AppGroupErrorView (title, body, three remediation steps)
   - LLMProvider preset names and blurbs

   Where the prior pattern was "Chinese headline + English footnote"
   (e.g. OnboardingView's 启用 OSGKeyboard / Enable OSGKeyboard),
   that pattern was preserved — bilingual coverage means every
   screen reads as both, not that every line is rigidly `中 · EN`.

Build: BUILD SUCCEEDED on iPhone 17 Pro / iOS 26 simulator.
Tests: 21/21 pass (no test changes).
Visual: light-mode home shot at /tmp/osgk_light_green.png shows
the brand-green CTA restored across Next button, mic icon, and
page dot.

🤖 Generated with Claude Code
This commit is contained in:
Rocky
2026-06-18 19:34:53 +08:00
parent 23689925dd
commit aeb28f4b36
16 changed files with 1117 additions and 353 deletions
+9 -12
View File
@@ -98,23 +98,20 @@ public enum Palette {
/// Light palette iOS system light mode defaults. Used by the main app
/// when the user is in light mode; the keyboard extension stays dark.
///
/// Accent: Apple system blue (#007AFF), matching the AccentColor asset
/// and the iOS HIG default. We deliberately do NOT use the dark-mode
/// green here a bright green CTA on a near-white background reads as
/// "go to a garden centre" rather than "tap me to enable your
/// keyboard", and a Typeless/Apple-style design system expects the
/// accent to follow the system tint in light mode. The keyboard
/// extension always renders dark and keeps its green accent for the
/// "polish / on-device" affordances, where green-on-dark is the more
/// legible pairing.
/// Accent is the same brand green (#3AA05A) used in the dark palette
/// "one accent" is core to the design system, and the recording-state
/// CTA needs to read as the same brand colour in both modes. The
/// keyboard's record disc also picks up `palette.accent` (see
/// `KeyboardPreviewStub.recordDisc`), so this single token drives
/// every interactive surface.
public static let light = ThemePalette(
background: Color(red: 0.980, green: 0.980, blue: 0.988), // #FAFAFC
surface: Color(red: 1.000, green: 1.000, blue: 1.000), // #FFFFFF
surfaceElevated: Color(red: 0.941, green: 0.941, blue: 0.961), // #F0F0F5
surfaceMuted: Color(red: 0.953, green: 0.953, blue: 0.965), // #F3F3F6
accent: Color(red: 0.000, green: 0.478, blue: 1.000), // #007AFF
accentMuted: Color(red: 0.000, green: 0.478, blue: 1.000).opacity(0.12),
accentGlow: Color(red: 0.000, green: 0.478, blue: 1.000).opacity(0.28),
accent: Color(red: 0.227, green: 0.627, blue: 0.353), // #3AA05A
accentMuted: Color(red: 0.227, green: 0.627, blue: 0.353).opacity(0.14),
accentGlow: Color(red: 0.227, green: 0.627, blue: 0.353).opacity(0.32),
danger: Color(red: 1.000, green: 0.231, blue: 0.188), // #FF3B30
success: Color(red: 0.157, green: 0.812, blue: 0.412), // #28CF69
warning: Color(red: 1.000, green: 0.620, blue: 0.094), // #FF9E18