`
+- [x] `FlowLevelStore`(audio 线程写、main 线程读)
+
+### `OSGKeyboard/Services/FlowSessionManager.swift`
+- [x] 会话生命周期 + heartbeat + 过期
+- [x] 轮询 `keyboardRecordingState` → utterance gating → `ASRService`
+- [x] 回写 `transcriptionResult/transcriptionError`
+- [x] 主线程发布 `audioLevels`
+- [x] **不再**使用 playback keep-alive / `LiveDictationController` for Flow
+
+### `OSGKeyboard/Info.plist` 与 `project.yml`
+- [x] `UIBackgroundModes: audio`
+- [x] 麦克风 / 语音识别权限说明
+
+---
+
+## Phase 2 · 键盘主链路切换
+
+### `OSGKeyboardExt/KeyboardViewController.swift`
+- [x] `pressBegan()` 会话判断分流
+- [x] 会话有效:写 `recording`;无效:`openHostApp(startflow)` 或提示
+- [x] `pressEnded()` 写 `stopped`
+- [x] 结果轮询 + `insertText`
+- [ ] `KeyboardRootView` 会话 UI 细化(可选)
+
+---
+
+## Phase 3 · 体验与稳定性强化
+
+### 主 App / 键盘协同
+- [x] 心跳超时判死(`FlowSessionBridge.isSessionActive`)
+- [x] 识别超时保护(30s finalize)
+- [x] `audioLevels` 共享
+- [ ] Darwin 通知(SwiftSpeak 模式,降低轮询延迟)
+- [ ] 会话死掉后一键重启 UI
+- [ ] `AudioRouteCoordinator`(蓝牙/路由切换)
+
+### 测试
+- [x] App Group 状态机单测
+- [ ] Flow continuous capture 单测(需 device / mock)
+- [ ] 端到端真机回归
+
+---
+
+## 3) 任务追踪面板
+
+## A. 已完成
+- [x] A1–A4:架构研究、蓝图、追踪文档
+- [x] B1:Phase 1 Flow 基础设施(含音频层修正)
+- [x] B2:Phase 2 键盘主链路(核心路径)
+
+## B. 待执行
+- [ ] B3:Phase 3 体验增强(Darwin、路由、恢复 UI)→ 见 §7.7 批次 F
+- [ ] B4:端到端真机回归 → 见 §7.7;本地/在线主路径已通过
+- [ ] **Phase 4**:§7.2–7.7 批次 A–F
+
+---
+
+## 5) 验收标准(Definition of Done)
+
+### Flow 核心(Phase 1–2)
+- [x] 本地 / 云端模式均可回填(真机已验证)
+- [ ] 连续 20 次语音输入,会话有效期间 **无需** 反复跳主 App
+- [ ] Console **无** `Session activation failed` / playback↔record 循环
+- [ ] 键盘波形随说话变化(`audioLevels` 非零)
+- [ ] 微信/备忘录/Safari 稳定回填
+
+### Phase 4 新增
+- [ ] 打开 App 后 **自动** 语音会话(权限齐全时)
+- [ ] 杀 App 再开 → **冷启动恢复**(未过期)
+- [ ] 键盘 **点按** 开始/结束;60s 倒计时 + 最后 10s 变红
+- [ ] Onboarding **分步权限** 完整可走通
+- [ ] 隐私政策 URL 可访问;App 内可打开
+- [ ] App Store 隐私标签与政策一致
+- [ ] 单测覆盖核心状态迁移
+
+---
+
+## 6) 真机验证清单(最小)
+
+1. 打开 App → **自动**进入语音会话(或 Home 卡片显示「进行中」)
+2. 切备忘录 → **点按**键盘麦开始 → 再点结束 → **文字出现**
+3. **不跳主 App**,重复 5 次
+4. 本地模式 + 云端模式各测 1 次
+5. 60s 倒计时 + 最后 10 秒变红;到点自动识别
+6. 杀 App 再开 → 会话恢复(未过期时)
+7. 预览 sheet「开始/停止录音」仍可用(Flow 未启动时)
+
+---
+
+## 7) Phase 4 · 产品体验与上架合规(2026-06 拍板)
+
+> 以下为用户/产品确认规格。**实现顺序建议:B → C → D → A → E → F。**
+
+### 7.1 已锁定决策
+
+| 主题 | 决定 |
+|------|------|
+| 麦克风交互 | **点按开始 / 再点结束**;识别中不可取消 |
+| 会话未启动 | 保持现有逻辑(拉 App / 提示去主 App) |
+| 权限引导 | 欢迎 → 麦克风 → 语音识别 → 键盘+完全访问 → 引擎/API;**仅首次或权限未定时** |
+| 自动开语音会话 | 进 App 且权限齐全 → **自动开**;有效会话 → **续期**;**不设关闭开关** |
+| 冷启动恢复 | 参考 TypeWhisper `checkExistingSession` |
+| Home 按钮 | 自动开后 **隐藏「启动」**;**保留「结束」**;失败显示原因 + 去设置 |
+| 单次录音上限 | **60s**;键盘到点 auto `stopped`;倒计时 **A(剩余)+ C(最后 10s 变红)**,显示在按钮内 |
+| 隐私政策 URL | **GitHub Pages**(仓库站点,如 `…/privacy`) |
+| 自动开会话开关 | **先不加** |
+
+### 7.2 批次 A · App Store 合规(上架前必做)
+
+- [x] **A1** 隐私政策页面(GitHub Pages `privacy.html` / `docs/privacy`),en + zh-Hans
+- [x] **A2** App 内入口:设置页 + Onboarding 底部「隐私政策」链接
+- [ ] **A3** App Store Connect「App 隐私」问卷与政策一致
+- [ ] **A4** 复核 / 更新 `PrivacyInfo.xcprivacy`(主 App + 键盘扩展;麦克风、UserDefaults 等)
+- [x] **A5** 更新 `NSMicrophoneUsageDescription` / `NSSpeechRecognitionUsageDescription`(含 Flow 自动会话说明)
+- [x] **A6** 「完全访问」专项说明(Onboarding + 设置:用途、不上传击键)
+- [ ] **A7** 云端模式披露:润色时仅文字发往用户配置的 API
+- [ ] **A8**(可选)支持邮箱 / 用户协议
+
+### 7.3 批次 B · 分步权限引导
+
+- [x] **B1** Onboarding 麦克风页(说明 + 按钮触发系统弹窗)
+- [x] **B2** Onboarding 语音识别页
+- [x] **B3** 键盘 + 「允许完全访问」图文 + 跳转系统设置
+- [x] **B4** 与 `PermissionPrimer` 合并;仅首次 / 权限未定时展示
+- [x] **B5** 权限被拒降级页(去设置)
+
+### 7.4 批次 C · 语音会话自动化
+
+- [x] **C1** 进 App + 权限 OK → 自动 `startSession`(`OSGKeyboardApp` / Home)
+- [x] **C2** 冷启动 `checkExistingSession`(`FlowSessionManager` init)
+- [x] **C3** Home 卡片 UX:进行中 + 结束;失败原因 + 去设置;隐藏手动「启动」
+- [x] **C4** 与 `scenePhase.active` 续期对齐
+
+### 7.5 批次 D · 键盘点按录音 + 60s 倒计时
+
+- [x] **D1** `RecordButton` 改为 toggle(替换长按手势)
+- [x] **D2** 识别中禁用按钮
+- [x] **D3** 按钮内剩余时间倒计时(`M:SS`)
+- [x] **D4** 最后 10 秒变红/橙(A+C)
+- [x] **D5** 60s 到点自动 `stopped` → 「识别中…」
+- [x] **D6** 无障碍 / 占位文案改为「点按说话」类
+
+### 7.6 批次 E · 多语言完善
+
+- [ ] **E1** 键盘扩展:移除 `KeyboardL10n` 硬编码,统一 `Localizable.strings`
+- [ ] **E2** `KeyboardViewController` 硬编码中文迁入 strings
+- [ ] **E3** 批次 B/C/D/A 新增文案 en + zh-Hans 成对
+
+### 7.7 批次 F · Phase 3 收尾
+
+- [ ] **F1** B3:会话过期键盘提示、Darwin(可选)、路由(可选)
+- [ ] **F2** B4:全场景回归(含自动开、60s、杀 App 恢复)
+- [ ] **F3** 更新 §5 验收勾选
+
+### 7.8 任务追踪(Phase 4)
+
+- [x] P4-0:产品规格拍板(交互、权限、自动会话、60s、隐私 URL)
+- [ ] P4-A:上架合规批次(A1/A2/A5 代码侧已完成;A3/A4 待人工)
+- [x] P4-B:权限引导
+- [x] P4-C:会话自动化
+- [x] P4-D:键盘点按 + 倒计时
+- [x] P4-E:多语言(核心文案;KeyboardL10n 硬编码待全量迁移)
+- [ ] P4-F:Phase 3 收尾 + B4 回归
+
+---
+
+## 4) 完成记录
+
+| 日期 | 任务ID | 变更摘要 | 状态 |
+|---|---|---|---|
+| 2026-06-19 | A1-A4 | 架构研究、方案选择、蓝图与追踪文档 | Done |
+| 2026-06-19 | B1-B2 | Flow IPC + 键盘链路;修正 continuous capture 音频层 | Done |
+| 2026-06-19 | B4-partial | 真机:本地 + 在线 Flow 可用 | Done |
+| 2026-06-19 | P4-0 | Phase 4 产品规格与合规任务清单拍板 | Done |
+
diff --git a/docs/.nojekyll b/docs/.nojekyll
new file mode 100644
index 0000000..e69de29
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..d0c481e
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+ OSGKeyboard
+
+
+
+
+
+
+
+
+
Voice dictation keyboard for iOS
+
+ OSGKeyboard is a custom keyboard extension that transcribes your voice on-device
+ and optionally polishes the result through an LLM you configure.
+ Works in WeChat, Notes, Mail, ChatGPT, and anywhere a keyboard appears.
+
+
+
+
+
+
Privacy at a glance
+
+ Audio is processed on your device for transcription. We do not log ordinary keystrokes.
+ In Cloud polish mode, only the transcribed text (not audio) is sent to your chosen API provider.
+
+
+
+
+
+
+
+
+
+ OSGKeyboard
+ 点按说话,任意 App 里获得润色文字。
+
+
+
+
iOS 语音输入键盘
+
+ OSGKeyboard 是一款自定义键盘扩展:在设备端转写语音,并可选通过你配置的 LLM 润色结果。
+ 适用于微信、备忘录、邮件、ChatGPT 等所有出现键盘的场景。
+
+
+
+
+
+
隐私摘要
+
+ 音频在设备端转写。我们不会记录普通击键内容。
+ 云端润色模式下,仅发送转写文字(非音频)到你选择的 API 服务商。
+
+
+
+
+
+
+
+
diff --git a/docs/privacy/index.html b/docs/privacy/index.html
new file mode 100644
index 0000000..f6c28bc
--- /dev/null
+++ b/docs/privacy/index.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ OSGKeyboard Privacy Policy
+
+
+
+ 中文
+ OSGKeyboard Privacy Policy
+ Last updated: June 19, 2026
+ OSGKeyboard is a custom iOS keyboard that turns your voice into text. This policy explains what data the app processes and how it is used.
+
+ What we collect
+
+ - Voice audio — captured only while you actively record. On-device mode transcribes locally with Apple’s speech APIs; raw audio is not uploaded by OSGKeyboard.
+ - Transcribed text — in Cloud polish mode, the final text (not audio) may be sent to the LLM provider you configure (e.g. OpenAI) for punctuation and formatting.
+ - API credentials — stored in the iOS Keychain on your device and shared only between the main app and keyboard extension via an App Group.
+ - App preferences — engine mode, language, and keyboard settings stored in App Group UserDefaults on your device.
+
+
+ What we do not collect
+
+ - We do not log or upload ordinary keystrokes you type with the keyboard.
+ - We do not operate analytics or advertising SDKs.
+ - We do not sell personal data.
+
+
+ Permissions
+
+ - Microphone — required for voice input and background voice sessions.
+ - Speech recognition — required for on-device transcription.
+ - Full Access — required so the keyboard can reach the microphone, read your API key, and communicate with the main app. Full Access does not grant us access to everything you type; we do not exfiltrate keystrokes.
+
+
+ Third parties
+ When you choose Cloud polish mode, transcribed text is sent to the API endpoint you configure. That provider’s privacy policy applies to those requests.
+
+ Data retention
+ Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.
+
+ Contact
+ Questions: open an issue at github.com/hkgood/OSGKeyboard.
+
+
+ OSGKeyboard 隐私政策
+ 更新日期:2026 年 6 月 19 日
+ OSGKeyboard 是一款 iOS 自定义键盘,可将语音转为文字。本政策说明应用处理哪些数据及用途。
+
+ 我们处理的数据
+
+ - 语音音频 — 仅在你主动录音时采集。本地模式在设备端通过 Apple 语音识别转写,OSGKeyboard 不会上传原始录音。
+ - 转写文字 — 云端润色模式下,最终文字(非音频)可能发送到你配置的 LLM 服务商以整理标点和格式。
+ - API 凭证 — 保存在设备 Keychain,仅通过 App Group 在主 App 与键盘扩展间共享。
+ - 应用偏好 — 引擎、语言等设置保存在设备 App Group 中。
+
+
+ 我们不收集的内容
+
+ - 我们不会记录或上传你平时在键盘上的击键内容。
+ - 我们不会集成广告或第三方分析 SDK。
+ - 我们不会出售个人数据。
+
+
+ 权限说明
+
+ - 麦克风 — 语音输入与后台语音会话所需。
+ - 语音识别 — 端侧转写所需。
+ - 完全访问 — 使键盘能使用麦克风、读取 API Key 并与主 App 通信。完全访问不代表我们会收集全部击键内容。
+
+
+ 第三方
+ 选择云端润色时,转写文字会发往你配置的 API,该服务商的隐私政策适用于相关请求。
+
+ 数据保留
+ 设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。
+
+ 联系
+ 问题反馈:github.com/hkgood/OSGKeyboard
+
+
diff --git a/project.yml b/project.yml
index 9973f0b..c34423f 100644
--- a/project.yml
+++ b/project.yml
@@ -55,8 +55,8 @@ targets:
# each request. The first entry below becomes each process's
# default access group, so the Keychain helper does not need to
# specify kSecAttrAccessGroup explicitly.
- com.apple.security.keychain-access-groups:
- - com.osgkeyboard.shared
+ keychain-access-groups:
+ - $(AppIdentifierPrefix)com.osgkeyboard.shared
resources:
- path: OSGKeyboard/Assets.xcassets
- path: OSGKeyboard/en.lproj
@@ -80,8 +80,10 @@ targets:
- UIInterfaceOrientationPortrait
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
- NSMicrophoneUsageDescription: "OSGKeyboard needs microphone access to transcribe your voice into text."
- NSSpeechRecognitionUsageDescription: "OSGKeyboard uses on-device speech recognition to transcribe your dictation. Audio never leaves your device."
+ NSMicrophoneUsageDescription: "OSGKeyboard uses the microphone for voice dictation and keeps a background audio session active while a voice session is running."
+ NSSpeechRecognitionUsageDescription: "OSGKeyboard uses on-device speech recognition to transcribe your voice. Audio is processed on your device and is not uploaded for transcription."
+ UIBackgroundModes:
+ - audio
NSAppTransportSecurity:
NSAllowsArbitraryLoads: false
CFBundleURLTypes:
@@ -111,8 +113,9 @@ targets:
excludes:
- "en.lproj"
- "zh-Hans.lproj"
- - path: OSGKeyboardExt/en.lproj
- - path: OSGKeyboardExt/zh-Hans.lproj
+ resources:
+ - path: OSGKeyboardExt/en.lproj/Localizable.strings
+ - path: OSGKeyboardExt/zh-Hans.lproj/Localizable.strings
settings:
base:
IPHONEOS_DEPLOYMENT_TARGET: "26.0"
@@ -124,16 +127,16 @@ targets:
# Shared with the host app so the keyboard extension can read the
# user's LLM API key. See OSGKeyboard/OSGKeyboard.entitlements
# for the full rationale.
- com.apple.security.keychain-access-groups:
- - com.osgkeyboard.shared
+ keychain-access-groups:
+ - $(AppIdentifierPrefix)com.osgkeyboard.shared
info:
path: OSGKeyboardExt/Info.plist
properties:
CFBundleDisplayName: OSGKeyboard
CFBundleShortVersionString: "$(MARKETING_VERSION)"
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
- NSMicrophoneUsageDescription: "OSGKeyboard needs microphone access to transcribe your voice into text."
- NSSpeechRecognitionUsageDescription: "OSGKeyboard uses on-device speech recognition to transcribe your dictation. Audio never leaves your device."
+ NSMicrophoneUsageDescription: "OSGKeyboard uses the microphone for voice dictation and keeps a background audio session active while a voice session is running."
+ NSSpeechRecognitionUsageDescription: "OSGKeyboard uses on-device speech recognition to transcribe your voice. Audio is processed on your device and is not uploaded for transcription."
NSExtension:
NSExtensionAttributes:
IsASCIICapable: false