Commit Graph

67 Commits

Author SHA1 Message Date
Cursor Agent 5b1283c3ed Merge branch 'cursor/pip-keep-alive-2b89' into feature/polish-style-packs
Resolve conflicts in CHANGELOG (keep both entries) and FlowSessionManager
(utterance PCM snapshot + PiP capture release after drain).

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:39:38 +00:00
Cursor Agent 70ba3a4359 feat(flow): add Picture in Picture keep-alive mode
- Add FlowKeepAliveMode (Dynamic Island vs PiP) with iCloud sync
- Settings: keep-alive picker; inactivity timeout only for Live Activity
- PiP: waveform sample-buffer controller, mic released between utterances
- PiP sessions have no idle expiry; user closing PiP ends the session
- FlowSessionManager branches hostReady, session start, and utterance paths
- UIBackgroundModes picture-in-picture; bilingual strings and tests

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:34:52 +00:00
Cursor Agent f108460302 merge: tail audio ASR fix (P0+P1) into feature/polish-style-packs
Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:34:05 +00:00
Cursor Agent f0240c3088 feat(flow): add whole-utterance batch ASR fallback (P1)
- Accumulate utterance PCM in FlowContinuousCapture for batch retry
- Run full-utterance transcribeChunk when stitched final lags partial
- Refactor Mac MLX tail drain to shared FlowUtteranceEndCoordinator
- Add FlowUtterancePCMStore, UtteranceBatchFallbackPolicy, and tests

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:15:31 +00:00
Cursor Agent c7ee891a90 fix(flow): improve tail capture and final chunk ASR recovery
- Add FlowUtteranceEndCoordinator with 350ms silence drain and 150ms post-roll
- Extend FinalChunkRecovery for short/empty final chunks in chunked pipeline
- Snapshot partial at mic stop and guard final transcript in FlowSessionManager
- Unify tail drain presets (iosFlow/macMLX) and expand diagnostics

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-26 10:13:40 +00:00
Rocky 937ce33f05 feat(polish): add configurable style packs
Add shared prompt composition, custom style management, iCloud sync, and native iOS/macOS selection interfaces so users can keep a consistent writing voice across devices.
2026-07-26 12:34:43 +08:00
Rocky e6f99d2744 fix(ios): address App Store review requirements
Use compliant permission prompts, align cloud ASR privacy disclosures, and default Flow sessions to a shorter inactivity window for safer resubmission.
2026-07-23 15:37:38 +08:00
Rocky c0c9dad149 feat(mac): add Qwen3 MLX streaming dictation
Replace the Sherpa offline pipeline with native MLX streaming, resilient model downloads, live transcript previews, and supporting tests and documentation.
2026-07-23 14:34:56 +08:00
Rocky c598ba77e6 feat: iOS appearance, iPad logo fix, demo seed, and landing hero polish
Add System/Light/Dark preference for iPhone and iPad, restore the iPad
sidebar brand mark, ship a DEBUG seed-demo URL for rich placeholder data,
and refresh GitHub Pages with the device composite, full-bleed pale-green
hero wash, and transparent Mac screenshot chrome.
2026-07-12 16:24:59 +08:00
Rocky 166019b384 feat: tip IAP, commercial GitHub Pages, and App Store screenshots
Add optional StoreKit support tip (features stay free), redesign the
landing page with zh/en + light/dark and product shots, and ship
iPhone/iPad/Mac App Store screenshot sets.
2026-07-12 11:00:57 +08:00
Rocky cc8dd1070a feat: macOS architecture, cloud ASR/LLM providers, and 6-step iOS onboarding
- Add macOS menu-bar dictation app with local ASR models (SenseVoice/Qwen3),
  global Option hotkey, and bottom overlay
- Add cloud ASR/LLM providers (Anthropic, Volcengine, Bailian, and more) with
  provider logos, model listing, and connection checks
- Add shared 7-day usage stats UI (UsageStatsCluster / SevenDayUsageChart)
- Add iOS onboarding step 6 for polish LLM setup; hide custom-language-model
  diagnostic toggle behind DEBUG
- Unify iOS onboarding tagline with the macOS brand line ("开口即文字。")
- Rewrite README (Chinese-first, product-oriented) and refresh GitHub Pages
2026-07-11 19:10:20 +08:00
Rocky cdf833935a feat: harden Flow cold-start/force-quit and polish macOS dictation UX
Fix cold-start overlay recursion that overflowed the main-thread stack when
recording began while the ready overlay was still up; also remove temporary
on-screen Flow DEBUG panels after the orange-mic investigation, and land the
macOS overlay/catalog/layout polish plus related Flow recovery hardening.
2026-07-10 12:39:41 +08:00
Rocky dcb66a9849 feat: sync force-quit Flow teardown and polish macOS local ASR UX
End Live Activities and release the audio session synchronously on
applicationWillTerminate, and continue macOS local-model install,
onboarding, and settings polish on this branch.
2026-07-09 17:13:07 +08:00
Rocky 200265fbd6 feat(macos): local ASR model manager, menu-bar polish, and release 0.5.2
Adds a bundled local ASR model catalog for the macOS app with one-click
Sherpa Qwen3 / SenseVoice downloads (pause/resume, inline actions) and a
shared model storage directory used by MLX Qwen3. Fixes the light-mode
sidebar material and makes the menu-bar icon follow the system appearance
with a refreshed status mark. Renames the built product to OSGKeyboard.app.

Bumps version to 0.5.2 (build 19).
2026-07-09 08:55:37 +08:00
Rocky c2f07bd8d2 feat(macos): add macOS menu-bar app and harden cross-device iCloud sync
Introduce a standalone macOS menu-bar app (OSGKeyboardMac) that reuses the
platform-agnostic OSGKeyboardShared core: record -> cloud/local ASR -> polish
-> insert. Local mode uses Qwen3-ASR via mlx-swift-asr (macOS 15+, Apple
Silicon); iOS targets stay zero-SPM.

Harden iCloud sync for multi-device correctness:
- Per-field settings merge (appSettings.v2) so concurrent edits no longer
  clobber each other's unrelated fields.
- Per-device usage statistics (G-Counter) that sum instead of max().
- Tombstoned dictionary/history merge so deletes propagate and entries can't
  resurrect.
- API keys replicate via iCloud Keychain, never iCloud KVS JSON; pulling a
  legacy blob without key fields no longer wipes local Keychain entries.
- Add a low-risk "Sync Now" action in Settings.

Fix Flow keyboard mic state: stay orange until the host publishes a real ready
contract, share a single MicVoiceAvailability gate, and self-heal stale
cross-process heartbeat jitter instead of getting stuck.

Extract shared storage (SpeechHistoryStore/UsageStatisticsStore,
ConfigurationStore) into OSGKeyboardShared and add tests for the new
sync/merge logic.
2026-07-08 18:13:56 +08:00
Rocky 128aab1b02 feat: iCloud settings sync and cold-start return redesign
- Add iCloud key-value settings sync (engine/language/polish/Flow prefs);
  API keys stay on-device. New "Sync settings via iCloud" toggle.
- Redesign cold-start handoff: bottom-bar left-to-right swipe guidance,
  auto-dismiss on app switch, tap-anywhere to close, retained return link.
- Harden keyboard->app handoff with host-disconnected hint.
- Include prior Unreleased ASR fixes (route-change crash, fallback warning,
  multi-utterance recognition, local ASR diagnostics).

Release 0.5.0 (build 18).
2026-07-07 17:56:01 +08:00
Rocky bf844caa7f feat(dictionary): add iCloud KVS sync, cloud ASR, and lexicon expansion
Mirror the personal dictionary through iCloud Key-Value Store with
deterministic merge rules, main-app-only sync UI, and App Group as the
keyboard runtime cache. Add cloud-engine ASR with dictionary bias and
expand the bundled custom language model lexicon.
2026-07-06 22:44:14 +08:00
Rocky e30a3c5765 merge: tail-drain and chunked ASR hardening into main
Resolve FlowContinuousCapture by combining tail-drain gate/drain logic
with flow-abcd route-change and interruption recovery.
2026-07-06 18:21:34 +08:00
Rocky 8e6640c470 merge: local checkpoint e6e75ed (dictation transcript accumulator) into main 2026-07-06 18:19:52 +08:00
Rocky 9ef0196d52 feat(flow): harden host handoff, session UX, and Live Activity brand
- fix(keyboard): drop unusable extensionContext.open; report the real
  result of UIApplication.open and cancel the start watchdog immediately
  on failure with clear "open OSGKeyboard" guidance instead of a 30s spin
- feat(capture): recover from audio route changes (AirPods/wired headset
  plug-unplug) and phone-call interruptions by rebuilding the engine/tap
  against the live hardware format
- feat(home): add a "Start" button in the Home footer when a session is
  inactive and permissions are granted (no-jump manual restart)
- feat(hostapps): expand the return whitelist to 46 apps (Signal, Zoom,
  Instagram, X, Douyin, Zhihu, Bilibili, Things, Firefox, ...) with
  en/zh-Hans names and synced LSApplicationQueriesSchemes
- feat(island): use a "ready" checkmark instead of a mic glyph while idle
  and set a staleDate so an orphaned Live Activity fades after a force-quit
- feat(coldstart): use the template OSG brand mark instead of a system
  waveform glyph in the cold-start overlay
- perf(keyboard): rely on Darwin notifications as the primary session
  signal and drop the fallback poll from 1s to 3s
- fix(keyboard): remove the redundant "Start" text button next to the
  inactive-session hint on the keyboard
- docs: correct the per-take cap to 210s (3.5 min) and document that
  force-quitting no longer resurrects the session (tracker + READMEs)
2026-07-06 17:56:10 +08:00
Rocky e6e75eda31 checkpoint before checking out cursor/flow-abcd-host-return-2b89 2026-07-06 15:58:20 +08:00
Cursor Agent 13cd7b30f9 feat(flow): implement ABCD session policy and host return whitelist
- Scheme A: on-demand session start, inactivity-based expiry, handoff auto-recording
- Scheme B: cold-start overlay with swipe guidance and return alert
- Scheme C+D: HostAppURLRegistry (20 apps) and sourceApplication capture
- Settings: skip app switch toggle and inactivity duration picker
- Add LSApplicationQueriesSchemes for canOpenURL checks

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-06 07:38:26 +00:00
Cursor Agent 8b105430e3 fix(flow): drain trailing audio and harden chunked ASR pipeline
- P0/P1: tail-drain state machine in FlowContinuousCapture with converter
  flush; FlowSessionManager awaits drain before finalize
- P2: FlowCaptureTailDrain policy/tracker, pipeline diagnostics, unit tests
- P3: short final chunk merged re-transcription in ChunkedUtterancePipeline
- P4: UtteranceTranscriptStitcher composedSafely fallback; preview path parity
  in LiveDictationController

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-06 05:13:58 +00:00
Rocky 537a68552a perf(asr): speed up local Flow dictation and land CLM/keyboard refactor
Reduce perceived latency from key release to final text:
- Adaptive chunking: 2.5s first chunk + 5s follow-ups so short
  utterances start on-device recognition while still recording.
- Session-level ASR warmup and audio-format cache reuse to remove
  per-utterance cold-start of SpeechAnalyzer.
- Mirror live pipelined partials to the keyboard transcript line via
  a new flow.transcriptionPartial App Group key + Darwin ping.

Also commits the accumulated custom language model, Flow session,
keyboard extension restructure, and Xiaomi MiMo provider work in
progress on this branch.
2026-07-06 00:00:19 +08:00
Rocky 05e005e9ce feat: cursor navigation, key sounds, dictionary tooling, key security
Batch of in-progress app work from the working tree.

- feat(keyboard): CursorNavigation + CursorDragPad for caret movement;
  KeyboardSoundFeedback for system key click sounds
- feat(dictionary): DictionaryAliasGenerator + PersonalDictionaryEntrySheet;
  TranscriptPostProcessor quality gate; retire DictionaryLearner
- feat(ui): TabBarVisibility handling; drop PageHeaderRow /
  PageHeaderConfirmButton; refresh views and localizable strings
- fix(security): move the hardcoded DeepSeek key out of
  PreconfiguredKeys.swift into a gitignored PreconfiguredKeys.local.swift
  (seeded from .example by generate-xcodeproj.sh)
- docs(agents): add Conventional Commits versioning + bilingual changelog rules
- chore(gitignore): ignore PreconfiguredKeys.local.swift, .cache/, pycache

Custom language model / lexicon work stays on
feature/custom-language-model-asr. Changelog bullets added under
[Unreleased]; no version bump.
2026-07-05 18:27:23 +08:00
Cursor Agent 4ab60ba3dc Remove manual polish scenario UI; rely on auto AppContext only
- Hide AppContext chip and remove manual override from keyboard
- Remove polish scenario picker and custom system prompt from settings
- Remove scenario section from onboarding
- Translation prompt uses auto-detected AppContext instead of polish scenario
- Delete ScenarioPrompt, ScenarioStyleDirective, PolishScenario, and related views

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 09:26:51 +00:00
Cursor Agent a115d90da7 fix: PersonalDictionaryView preview and AppGroupStore dictionary writes
Use ThemedRoot in preview instead of inaccessible ThemePalette().
Add setPersonalDictionary(_:) for let AppGroupStore bindings; use
it from PersonalDictionaryView and DictionaryLearner.

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 08:19:28 +00:00
Cursor Agent 87708e7976 fix: use AppGroupStore setter methods from keyboard extension
AppGroupStore is a struct; property setters are mutating and cannot
be called on a let binding. Add setOnboardingPage and
setHasCompletedOnboarding helpers (like setDetectedAppContext).

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 08:17:55 +00:00
Cursor Agent 3f60436d84 fix: check indentation with substring contains, not per-character
contains(where:) on String iterates Characters; comparing each to
multi-character strings like "\n    " is a type error. Use
tail.contains("\n    ") || tail.contains("\t") instead.

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 08:14:50 +00:00
Cursor Agent 693ea8962e fix: correct PersonalDictionary init parameter syntax
Use 'version: Int = 1' instead of invalid 'version: 1' so Swift
synthesizes the default initializer and PersonalDictionary() compiles.

Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 08:13:15 +00:00
Rocky ce2fdae11b Merge pull request #7 from hkgood/feature/in-keyboard-onboarding-and-context-chip
feat(keyboard): in-keyboard onboarding overlay + AppContext chip
2026-07-03 15:57:00 +08:00
Cursor Agent 8ce84d2339 Merge branch 'audit/appstore-prep' into main
Co-authored-by: Rocky <hkgood@users.noreply.github.com>
2026-07-03 07:56:07 +00:00
Mavis 0401f67a89 feat(keyboard): in-keyboard onboarding overlay + AppContext chip
Two long-standing UX papercuts, fixed without leaving the keyboard:

1. **First-launch onboarding inside the keyboard** — iOS keyboard
   extensions *cannot* programmatically switch back to the previous
   app after jumping out, so the old flow (jump to host app → user
   has to manually navigate back) was 5+ taps of friction. The new
   `KeyboardOnboardingOverlay` keeps the user inside the keyboard
   for steps 1 (welcome), 2 (mic permission), 3 (speech permission),
   and 5 (API key hint). The only step that *must* leave is step 4
   ("Enable Keyboard"), which jumps to `Settings.app` via
   `UIApplication.openSettingsURLString` — on return,
   `viewWillAppear` calls `autoAdvancePastKeyboardSetupStepIfNeeded`
   which silently advances past that step if the keyboard is now
   enabled. Net UX: user types in their app, keyboard walks them
   through setup, normal UI appears as soon as setup is done.

2. **Per-app context chip on the keyboard top bar** — the v0.3.0
   intelligent-prompt pipeline already adapted tone to detected
   context (code/email/chat/document), but without a UI cue the user
   had no way to know which mode was active or override the heuristic
   when it guessed wrong. The new `AppContextChip` surfaces the
   detected context; tap-to-override writes back to
   `AppGroupStore.setDetectedAppContext` so the next LLM call
   picks up the new tone immediately. Wired into `pressBegan` so
   the chip updates in real time as the user types into different
   fields.

### Files added
- `OSGKeyboardExt/Views/AppContextChip.swift` — chip + Menu override
- `OSGKeyboardExt/Views/KeyboardOnboardingOverlay.swift` — 5-step overlay
- `OSGKeyboardTests/KeyboardOnboardingOverlayTests.swift` — round-trip + enum surface tests

### Files modified
- `OSGKeyboardShared/Services/KeyboardState.swift`
  + `hasCompletedOnboarding`, `onboardingPage`, `appContext`
  + `setAppContext`, `advanceOnboarding`, `completeOnboarding`
  + `requestMicPermission`, `requestSpeechPermission`, `openSystemSettings`
- `OSGKeyboardShared/Services/AppGroupStore.swift`
  + `hasCompletedOnboarding` / `onboardingPage` accessors (mirror of
    `ProviderConfig` keys, so the keyboard extension never has to
    instantiate the host-app config)
- `OSGKeyboardExt/KeyboardViewController.swift`
  + action hooks wired (`installStateActions`)
  + `syncOnboardingStateFromAppGroup` / `syncAppContextFromAppGroup`
    called on `viewWillAppear` and `loadPersistedConfig`
  + `autoAdvancePastKeyboardSetupStepIfNeeded` for the silent
    "jump out → come back" flow
  + `openSystemSettingsFromExtension` opens `Settings.app` via
    `UIApplication.openSettingsURLString` (the only system URL
    the extension is allowed to open)
  + `detectAndStoreAppContext` mirrors to `state.appContext` so
    the chip updates without waiting for `viewWillAppear`
- `OSGKeyboardExt/Views/KeyboardRootView.swift`
  + overlay mounted in `ZStack` over normal UI (animated)
  + AppContextChip in top bar (hidden during onboarding)
- `OSGKeyboardExt/{en,zh-Hans}.lproj/Keyboard.strings`
  + onboarding copy + chip labels (35 keys per language)

### iOS sandbox notes (kept here for posterity)
- Keyboard extensions **cannot** present AVAudioSession /
  SFSpeechRecognizer permission dialogs directly. The overlay's
  step 2/3 buttons optimistically advance; the actual permission
  is granted when the user first opens the host app (which the
  step-5 "Open OSGKeyboard" button triggers). This is the same
  pattern the previous "jump to host app" flow used — just
  without the broken return trip.
- `UIApplication.openSettingsURLString` is the only system URL
  reachable from `extensionContext.open`. Both step 4 and the
  "Open Settings" button route through `HostAppLauncher` so the
  responder-chain fallback also kicks in if needed.

Co-authored-by: Mavis <Mavis@hkgood.dev>
2026-07-03 07:55:12 +00:00
Mavis 947d6220ff Merge branch 'feature/intelligent-polish-and-personal-dict' into main
Resolved real conflicts introduced by translation-polish-2 (#3) and
docs refresh (#4) being merged to main during development:

- OSGKeyboardShared/Models/ProviderConfig.swift
  Add polishIntensity field alongside translationTargetLocaleId /
  polishScenarioId / handednessPreference. Both new fields coexist.

- OSGKeyboardShared/Services/AppGroupStore.swift
  Keep main's translation setters; add polishIntensity setter,
  detectedAppContext setter, and personalDictionary getter/setter.

- OSGKeyboardShared/Services/PolishingService.swift
  Merge v0.2.1 translate-and-polish path with v0.3.0 context-aware
  intelligent prompt. New polish() entry point accepts both modes;
  translation mode still uses TranslationPrompt, polish mode now
  uses buildPrompt(for:context:).

- OSGKeyboard/Views/SettingsView.swift
  Compose localEngineSettingsSection (main) with polishIntensitySection,
  languageAndModelsSection, systemPromptLinkSection, personalDictionaryLinkSection (feature).

- OSGKeyboardShared/{en,zh-Hans}.lproj/Shared.strings
  Concat scenario keys + polish intensity / app context / dictionary keys.

Auto-merged without conflict:
- AppGroupStore new methods (localASRBackend, etc.)
- FlowSessionManager, KeyboardViewController (auto-merged)
- MaterialIcon, HistoryView (auto-merged, my icons added on top)

Co-authored-by: Mavis <Mavis@hkgood.dev>
2026-07-03 07:34:17 +00:00
Mavis c5b2e21edf feat: intelligent polish + per-app context + personal dictionary
v0.3.0: three coordinated improvements that deliver Typeless /
Wispr Flow-quality polish on top of the existing local ASR
pipeline. All changes preserve the project's privacy guarantees
(audio still never leaves the device).

## 1. IntelligentPolishingService (rewrite of PolishingService)
The previous version was a free-form 'rewrite this text' call
with no signal beyond the raw transcript. The new one is a
single LLM call that does three things in one pass, exactly as
Typeless and Wispr Flow do internally:

  1. ASR error correction (homophones, near-misses, missing chars)
  2. Polish (drop filler words, fix grammar, add punctuation)
  3. Style adaptation per app context (code / email / chat / doc)

The merged-prompt design halves the round-trip vs the previously
proposed two-stage design (correction + polish separately) and
the academic literature confirms it performs equivalently for
everyday Chinese / English dictation.

## 2. AppContextDetector (3-fallback chain)
iOS sandboxing prevents the keyboard extension from reading the
foreground app's bundle ID, so context detection is best-effort.
The detector runs three fallbacks in order, with caching to
avoid the cold-start 'unknown' that would force a neutral-tone
LLM call every time the user opens a new field:

  1. Heuristic on the text at the cursor (code / email / chat / doc)
  2. 30-minute cache of the last successful detection
  3. Time-of-day + weekend heuristic as a soft default

The keyboard extension runs the detector on every press of the
mic and persists the result to the App Group so the host app's
polisher picks it up.

## 3. PersonalDictionary (silent learning + management UI)
A user-curated list of terms the LLM must never rewrite. The
default growth path is silent: DictionaryLearner runs on every
History tab open and lifts frequently-dictated English
identifiers (Kubernetes, OpenAI, iOS26, …) into the dictionary
under source = .history. Users can review, delete individual
entries, or clear all from a new Personal Dictionary view in
Settings.

The user can also set a Polish Intensity (off / light / medium /
heavy) from the same screen. Default is medium, which is what
Typeless and Wispr Flow also use.

## Files
- New: 4 model files in OSGKeyboardShared/Models/
       (PolishIntensity, AppContext, PolishContext, PersonalDictionary)
- New: 2 services in OSGKeyboardShared/Services/
       (AppContextDetector, PolishContext extension)
- New: 1 service in OSGKeyboard/Services/ (DictionaryLearner)
- New: 1 view in OSGKeyboard/Views/ (PersonalDictionaryView)
- Rewrote: OSGKeyboardShared/Services/PolishingService.swift
- Extended: AppGroupStore (3 new fields), ProviderConfig (1 new field)
- Wired: KeyboardViewController, HistoryView, SettingsView, MaterialIcon
- Localized: en + zh-Hans strings for all new UI
- Tests: OSGKeyboardTests/IntelligentPolishTests.swift (16 tests)

## Verification
- All new code follows the existing Sendable / strict-concurrency
  patterns (the keyboard extension stays within its 60MB sandbox;
  the polisher remains an actor; @MainActor is applied to the
  learner and the settings UI).
- Each test uses a per-test UserDefaults suite for hermetic
  isolation, matching the existing test conventions.
- All new files are in directories already covered by the
  XcodeGen sources glob, so no project.yml change is needed.

## Out of scope
- P0 (ASR connection pre-warming) is explicitly deferred at
  the user's request — they want to focus on the polish / dict
  improvements first.
- The Cloud polish (WebSocket) work is not touched.

## Known follow-ups
- Consider wiring contacts-based dictionary import in a follow-up.
- Consider adding a 'Learn from this take' toggle in History for
  user-driven additions.
- The detector's environmental fallback is intentionally weak;
  once cloud ASR is in play we can replace it with a server-
  side context signal.
2026-07-03 07:18:02 +00:00
Rocky bc77cabb62 feat: keyboard bottom-row layout, handedness preference, and screen wake lock
Rework the action cluster to a mic-above-bottom-row layout, add left/right
handedness setting that swaps delete and return, and keep the screen awake
during Flow recording sessions.
2026-07-02 21:01:54 +08:00
Rocky 1bdb8824ac feat: polish scenarios and stabilize keyboard layout height
Add preset-driven polish scenarios (Settings, onboarding, ScenarioChip)
with ScenarioPrompt and style directives; drive keyboard height from
content (240pt) and use viewIsAppearing encapsulated-height offset for
smoother keyboard switches; remove redundant StatusBadge and hide system
dictation via hasDictationKey.
2026-06-29 00:08:00 +08:00
rocky 4fec0da7f0 feat(translation-polish-2): local-engine dedicated section + PreconfiguredKeys for DeepSeek
- Add PreconfiguredKeys.swift: placeholder constant for the DeepSeek
  API key the local engine uses. DEBUG build asserts at launch when
  the placeholder is still in place so nobody ships an always-401
  build by accident. Replace TODO_FILL_LATER_DEEPSEEK_KEY before
  distributing.

- PolishingService.polishRemote:
  * Adopted (A) path: when effective provider is DeepSeek, take the
    apiKey from PreconfiguredKeys.deepseek instead of store.apiKey.
    Refuse the round-trip with PolishError.missingAPIKey when the
    placeholder is still in place.
  * Adopted fix for pre-existing typo: store.model.isEmpty ?
    preset.defaultModel : store.model (the right-hand side was
    defaultModel on both branches, silently ignoring the user's
    custom model field).

- LocalEngineSettingsRows.LocalModelsGroup:
  * Dropped the inline 'uses DeepSeek' caption — it added visual
    weight without telling the user anything they couldn't infer.
  * Translation row now lives inside the group so the local engine
    reads as one cohesive card.
  * Group owns its surface card chrome (palette.surface + rounded
    border) — callers no longer need to wrap it externally.

- SettingsView:
  * languageAndModelsSection: dropped the trailing TranslationPickerRow
    and the inner LocalModelsGroup (now lifted to its own section).
  * New localEngineSettingsSection: renders only when
    engineMode == 'local', wrapping LocalModelsGroup with a
    'settings.localEngine.title' header.
  * body: added the new section between languageAndModelsSection and
    the cloud-only systemPromptLinkSection.

- Localizable.strings (en + zh-Hans):
  * Removed settings.localModels.cloudPolish.caption (no references).
  * Added settings.localEngine.title = 'Local engine' / '本地引擎'.

DeepSeek key pre-fill tripwire + local-engine UI cohesion. No main
merge, no PR.
2026-06-25 17:34:02 +08:00
rocky 93b6aa6c02 feat(translation-polish): dual-engine translation UX + provider picker filter + topbar cleanup
- Add LLMProvider.isUserSelectable (default true) and filter
  ProviderPickerSection on it; next pass can hide non-user presets
  (e.g. a future DeepSeek key-preset) without changing call sites.
- KeyboardRootView: hide TranslationChip when off (matches user's
  mental model of an opt-in feature), drop the 'warming' branch
  (Qwen3 download UX was removed with the backend in v0.2.0), unify
  chip pill height to minHeight 28 + vertical 6 for visual rhythm
  across all topbar chips.
- TranslationChip: drop isLocal warning path — both engines now
  run the translate-and-polish step (local routes through DeepSeek
  via ProviderConfig.localModeProviderId).
- OnboardingView: cloud engine branch now wraps the translation
  row in the same surface card chrome as the local branch.
- Strings: drop keyboard.models.warming (no longer referenced).

DeepSeek key pre-fill deferred to a follow-up.
2026-06-25 15:16:33 +08:00
rocky 0956bb8534 feat(translation-v2): final review — dual-engine translation row + DeepSeek polish override + dead code cleanup 2026-06-25 14:25:06 +08:00
rocky 9b759281cc feat(translation-v2): refactor translationEnabled into computed + onboarding row visibility 2026-06-25 13:52:13 +08:00
zhongshu 4d92999347 feat(translation-ui): tighten settings/onboarding UX around the translation feature
UI refinements on top of the translation pipeline (feature/translation@HEAD):

1. Onboarding engine page now hosts a translation row.
   APISetupPage renders the same TranslationPickerRow used in the
   language tab, so first-time users can pick a target language
   before they ever see the keyboard. Same persisted bindings; same
   'needs cloud' hint when the local engine is active.

2. Local engine hides the provider / API card unconditionally.
   Removed the 'local + cloud polish on → show API fields' branch
   from SettingsView. Provider/base URL/API key/model controls have
   no use in local mode (translation is cloud-only anyway), and
   exposing them invited users to fill in a DeepSeek key they
   can't use.

3. 'Cloud polish after ASR' toggle loses its long subtitle.
   The descriptive copy in LocalEngineSettingsRows.cloudPolishRow
   was a wall of text that explained things visible elsewhere in
   Settings. Title + switch is enough; the CloudPolishDisclosureBanner
   (rendered by EnginePickerSection when cloud is active) already
   covers the 'this sends text to your API' disclosure.

4. Translation row becomes a single dropdown with a 'Don't
   translate' default.
   TranslationPickerRow replaced with a one-row Menu picker:
   '不翻译 / English / 中文 (简体) / 中文 (繁體) / 日本語 / 한국어 /
   Français / Deutsch / Español / Русский / Português'.
   '不翻译' maps to translationEnabled=false; any locale maps to
   translationEnabled=true + translationTargetLocaleId=<id>.
   TranslationLanguageCatalog gains an 'off' sentinel so the picker's
   single binding stays a plain String.

5. Language tab reorder.
   SettingsView.languageAndModelsSection: ASR locale ('识别语言')
   now sits above the local-models block; translation row sits at
   the bottom. The reading order follows the pipeline direction
   (input → post-processing → post-post-processing).

Localization:
  - 'settings.translation.title' → '翻译' / 'Translation'
  - new 'settings.translation.off' / 'settings.translation.hint.needsCloud'
  - dropped unused subtitle / target-language keys

xcodebuild scheme=OSGKeyboard config=Debug destination=iPhone 17
Simulator: BUILD SUCCEEDED (0 warning, 0 error).
2026-06-25 13:23:54 +08:00
rocky deddb49d56 feat(translation): add post-ASR translation mode for cloud engine
Adds an opt-in translation pipeline that reuses the existing
PolishingService + LLMClient + AppGroupStore chain. Translation is
implemented as a new PolishMode (.translate(targetLocaleId:)); all
existing call sites are unchanged.

Settings:
- New TranslationPickerRow in the language tab (Toggle + 10-locale
  picker: en/zh-Hans/zh-Hant/ja/ko/fr/de/es/ru/pt), persisted to the
  App Group so the keyboard extension can read it during live dictation.
- 5 new strings per language (en + zh-Hans).

Keyboard:
- New TranslationChip on the top bar to the right of LocaleChip;
  same Menu pattern, lets users toggle or quickly switch target
  language without leaving the keyboard.
- PolishingService dispatches .translate with a parameterised prompt
  (en/zh variants selected by provider id); PolishingService.error
  gains a translationNotAvailable case so local-engine users get a
  clear inline warning when the toggle is on but cloud is off.
- 6 new strings per language (en + zh-Hans) for the chip + banner.

Local engine policy:
- Translation is cloud-only by design (local engine stays ASR-only
  to honour the no-roundtrip promise). Chip shows a 'cloud required'
  state and raw transcript still inserts on failure — no data loss.

Build:
- OSGKeyboardShared adds TranslationLanguage enum (10 locales) and
  TranslationPrompt factory.
- 4 new files, 9 modified. xcodebuild scheme=OSGKeyboard
  config=Debug destination=iPhone 17 Simulator: BUILD SUCCEEDED
  (0 warning, 0 error).

Also pins DEVELOPMENT_TEAM in project.yml for TestFlight uploads
(3 targets; Team X329MZU23S).
2026-06-25 12:45:44 +08:00
rocky c07cf4db9f refactor: drop Qwen3 CoreML ASR, add local-engine cloud polish toggle
Rolls back the v0.2.0 Qwen3 CoreML on-device ASR stack and replaces the
'local engine' UX with iOS 26 SpeechAnalyzer + DictationTranscriber only.

The 'Cloud polish after ASR' toggle (ProviderConfig.localModeCloudPolishEnabled)
lets users opt into a post-ASR DeepSeek round-trip from the local engine.
Defaults to off so the local engine stays genuinely local. New PolishError.missingAPIError
surfaces an inline 'fill in your key' warning when the toggle is on but the
Keychain is empty. DeepSeek preset default model bumped to deepseek-v4-flash.

Deleted:
  - OSGKeyboard/ThirdParty/Qwen3Speech/ (74 files, ~16k LoC)
  - OSGKeyboard/Services/ModelManager.swift (492)
  - OSGKeyboard/Services/OnDeviceModelWarmup.swift (197)
  - OSGKeyboard/Services/Qwen3ASRService.swift (257)
  - OSGKeyboard/Services/ModelDownloadSourcePicker.swift (126)
  - OSGKeyboard/Views/OnDeviceModelsView.swift (184)
  - OSGKeyboard/Views/DownloadConfirmSheet.swift (96)
  - OSGKeyboardShared/Models/OnDeviceModel.swift (140)
  - OSGKeyboardShared/Services/OnDeviceModelStatus.swift (104)
  - Qwen3ASRServiceProvider registration in OSGKeyboardApp
  - Qwen3Speech package declaration in project.yml
  - 5 .qwen3ASR enum / branch reference sites in HomeView, OnboardingView,
    LocalEngineSettingsRows, FlowSessionManager, ASRService, EngineServiceLabel
  - Two pre-existing Swift 6 strict-concurrency errors in
    LiveDictationController + FlowSessionManager (the weak [weak self] in
    detached-task MainActor.run blocks) that were blocking clean builds

Added:
  - LocalModelsGroup: 'Built-in iOS SpeechAnalyzer' badge + 'Cloud polish
    after ASR' Switch toggle
  - PolishingService: honour localModeCloudPolishEnabled; new .missingAPIKey
    error case with localised warning
  - AppGroupStore.localModeCloudPolishEnabled (mirrored into App Group
    so the keyboard extension honours the toggle during live dictation)
  - SettingsView: show provider/api sections when local-mode cloud polish
    is on so the user can paste a DeepSeek key
  - FlowSessionManager: route through PolishingService for local + polish-on
    flow; translate missingAPIKey into a polished warning
  - KeyboardViewController: handle PolishingService.PolishError.missingAPIKey
    in the keyboard-side live polish path
  - CHANGELOG v0.2.1: documents the rollback + new toggle
  - README.md / README.zh.md: engine matrix section, data flow note

Verified: xcodebuild -scheme OSGKeyboard -destination 'generic/platform=iOS Simulator'
build succeeds under SWIFT_STRICT_CONCURRENCY=complete.
2026-06-24 01:51:34 +08:00
Rocky df1c5ff32c feat: migrate on-device Qwen3 ASR to CoreML for background Flow dictation
Replace MLX GPU inference with CoreML bundles so transcription continues
while the host app is backgrounded. Adds model download and warm-up,
vendored Qwen3Speech, and updates onboarding, settings, and copy for the
~1.6 GB CoreML package (iOS 18+).
2026-06-23 00:46:58 +08:00
Rocky 5e5122f172 chore: adopt source-available license and strengthen privacy disclosures
Replace MIT with a restrictive source-available license and align README/docs copy. Add cloud polish acknowledgment, privacy manifest updates, voice history disclosure, export compliance metadata, and support links for App Store readiness.
2026-06-21 16:45:29 +08:00
Rocky 993784d223 feat: polish first-launch onboarding and post-setup permission guidance
Streamline the five-step flow with smarter skip logic, a centered welcome intro, clearer zh copy, keyboard setup detection via the extension, and home tips when permissions are still missing after onboarding completes.
2026-06-21 16:14:13 +08:00
Zhongshu Audit 97a410ca39 docs: clarify LiveDictationController and DictationBridge status
Both classes are kept in v0.1.2 (they serve the in-app keyboard
preview and the one-shot host-app dictation handoff path), but
their primary role was taken over by FlowSessionManager +
FlowContinuousCapture in v0.1.1. Add a STATUS section to each
file header explaining the current role and listing the call
sites, so future readers do not assume they are dead code and
try to delete them (which would break PreviewASRController and
KeyboardPreviewSheet).

Refs: AUDIT_APPSTORE.md P0-3
2026-06-20 21:33:51 +08:00
Rocky db5151ad9a feat: flow session reliability, keyboard UX, and audio pre-roll fix
Renew voice sessions while the host app stays foreground, auto-start flow from the keyboard with a Start action, and prevent leading audio loss via pre-roll buffering and faster signal polling.
2026-06-20 00:57:16 +08:00
Rocky 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.
2026-06-20 00:31:58 +08:00