deddb49d56bb5da03890e409e14c1af745fa4a4e
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
38a36ed504 |
feat: tab shell, home/onboarding UI, settings and keyboard polish
Introduce MainTabView with history and Liquid Glass dock; refresh home and onboarding layouts; unify accent green and provider localization; refine settings/API rows; redesign keyboard mic and flanking controls; set Utility iPhone-only targets and Flow session reliability fixes. |
||
|
|
ef8115508c |
fix: onboarding permission refresh, keyboard l10n, and flow recording
Refresh mic/speech status when returning from TCC dialogs, persist onboarding page across Settings trips, load extension strings from .lproj bundles via ExtL10n, and keep flow result polling alive across host-app jumps. |
||
|
|
7f059dbd45 |
feat: TypeWhisper Flow sessions, Phase 4 UX, and GitHub Pages privacy site
Migrate keyboard dictation to continuous Flow sessions with auto-start, tap-to-toggle recording, 60s countdown, five-step onboarding, and App Group IPC. Add docs/ GitHub Pages site with en/zh privacy policy for App Store compliance. |
||
|
|
12608fe618 |
design: drop disc drop-shadow, scale up processing-state spinner
Two design cleanups, both for visual polish on the record disc
(the hero control of the keyboard preview AND the live keyboard
extension). The shadows were a leftover from the iOS 18 mockup
that visually fought the brand-green disc; the processing-state
spinner was the system default at 1.1x, which on a 96-120pt disc
reads as "a dot in the middle" rather than "an active process".
Changes:
1) Remove the disc body's drop shadow on BOTH surfaces.
- `KeyboardPreviewStub.swift:244` — `.shadow(color: .black.opacity(0.4),
radius: 10, y: 6)` deleted. Hardcoded dark shadow on a
light surface, completely theme-blind (looked like a 2009
skeuomorphic button).
- `RecordButton.swift:112` — `.shadow(color: .black.opacity(0.45),
radius: 14, y: 8)` deleted. Same problem on the real
keyboard's disc, plus the shadow competed with the existing
"halo" radial gradient and the outer breathing ring.
The internal "ambient glow" (radial green gradient inside the
disc on idle) is NOT touched — that's brand colour, not a
drop shadow, and matches the live keyboard's idle accent.
2) Scale up the processing-state spinner on BOTH surfaces.
- Preview: `ProgressView().tint(.white).scaleEffect(1.1)` →
`ProgressView().progressViewStyle(.circular).tint(palette.textPrimary)
.scaleEffect(2.5)`.
- 1.1x (~22pt) → 2.5x (~50pt) = 52% of the 96pt disc.
- Tint changed from hardcoded `.white` to `palette.textPrimary`
because the processing-state disc gradient is
`surfaceElevated → surface` (light grey in light mode);
a white spinner on a light grey disc is invisible regardless
of scale.
- Real keyboard: `ProgressView().scaleEffect(1.2)` →
`ProgressView().scaleEffect(2.5)`. Same absolute spinner
size (~50pt) — 42% of the 120pt disc — so the user gets the
same visual weight whether they're looking at the in-app
preview or the live keyboard. Tint was already
`palette.textPrimary` so no change there.
Proportion rationale: ~50% of the disc diameter is the sweet
spot. 100% would crowd the disc's edge, <30% reads as
"decoration" rather than "process". 50pt absolute size matches
the same spinner across both surfaces — same weight, different
disc sizes — so the user doesn't have to re-learn the visual
language when they switch from preview to real keyboard.
Tests: 27/27 pass (no behavioural change, just visual).
Build: BUILD SUCCEEDED.
🤖 Generated with Claude Code
|
||
|
|
b1635d5d35 |
feat: iOS-style localization + onboarding engine picker + UX fixes
Five user-flagged issues addressed in this commit. Some of the
uncommitted files belong to a prior agent pass and are included as-is
so this is a clean working tree.
1. Remove globe (nextKeyboard) button from keyboard bottom bar.
iOS already provides a globe key in the system keyboard strip
for next-keyboard switching, so the in-extension one was
redundant. The bar is now: ⌫ (delete) [space] ↩ (return).
Affected: OSGKeyboardExt/Views/KeyboardRootView.swift and
OSGKeyboard/Views/KeyboardPreviewStub.swift (preview mirrors).
2. Fix TabView with .page style auto-jumping on TextField focus.
SwiftUI's `.tabViewStyle(.page(...))` wraps content in a
UIPageViewController, which has a long-standing iOS 18 bug
where the keyboard-showing layout reflow on a TextField focus
is misread as a horizontal swipe — the page jumps back to
step 1 the moment the user starts typing. Replaced the
TabView with a ZStack + conditional view + transition. We give
up swipe-to-page, but Back/Next buttons + page dots are the
canonical onboarding affordance and the user is one tap from
the next page anyway.
3. Onboarding APISetupPage now offers Engine choice (Local vs
Cloud), matching the in-app Settings page. First-run users can
pick the on-device engine and skip the API-key setup entirely.
Extracted the engine section from SettingsView into a shared
`EnginePickerSection` component used by both. Cloud path
keeps the provider + API fields; Local path shows a
"no API key needed" confirmation card.
4. APISettingsCard test-connection error messages are now built
with NSLocalizedString + String.localizedStringWithFormat (for
the interpolated HTTP status / reply preview). Status badge
labels are also NSLocalizedString-backed.
5. iOS-standard localization.
- New `en.lproj/Localizable.strings` and `zh-Hans.lproj/
Localizable.strings` (in both the main app and the keyboard
extension bundles — each .appex has its own bundle).
- `project.yml` sets `CFBundleDevelopmentRegion: en` and
`CFBundleLocalizations: [en, zh-Hans]`; the two .lproj dirs
are added as resources.
- Every `Text("...")` / `Button("...")` / `Label("...")` /
`accessibilityLabel(Text("..."))` in user-facing views was
rewritten to use `Text("key")` (SwiftUI auto-resolves
string-literal `LocalizedStringKey`s against the strings
file) or `NSLocalizedString("key", comment: "")` for
interpolated / dynamic values. The hardcoded
"中 · EN" / "EN · 中" pattern is gone.
- Two helper signatures that took `String` for the title/body
of a row (`footnoteRow`, `sectionHeader`) are now
`LocalizedStringKey` so the row labels are looked up.
- `Label("key", systemImage: "...")` and
`.confirmationDialog(LocalizedStringKey("key"), ...)` and
`.navigationTitle(LocalizedStringKey("key"))` are used where
`String` would just print the key.
- Preview-onboarding `APISetupPage` updated to use the same
engine picker as Settings (issue 3), with a section that
only renders the provider + API fields when the user picks
Cloud.
Verified on iOS 26 simulator with system language set to both
zh-Hans (default) and en: the same screen renders "按住说话,松开
即得润色文字。" / "下一步" in Chinese, and "Hold to talk. Release
for polished text, in any app." / "Next" in English — no
"中 · EN" doubling, no missing keys.
Build: BUILD SUCCEEDED.
Tests: 21/21 pass.
🤖 Generated with Claude Code
|
||
|
|
aeb28f4b36 |
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
|
||
|
|
38ad66f07d | [JJC-20260618-005-C-2] Refactor: replace 166 Palette.xxx with @Environment(\.themePalette) (ARCH-A2) | ||
|
|
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
|