Removes assets and docs that are zero-references in the codebase and the
GitHub Pages site (scanned with ripgrep across *.swift / *.md / *.html).
Removed assets (no Swift / NSImage / UIImage / Image callers):
- 19 provider logo imagesets in OSGKeyboard/Assets.xcassets/:
anthropic, apple, ark, codingplanx, cometapi, custom, deepseek,
gemini, groq, mimo, minimax, mistral, moonshot, openai, openrouter,
qwen, siliconflow, xai, zhipu
- docs/assets/badges/appstore-{en,zh}.svg (README badges in use are
ios-{en,zh}.svg and macos-{en,zh}.svg)
Removed dead code:
- OSGKeyboardShared/Models/ProviderLogo.swift (no callers; only maps
provider ids to the now-deleted logo imagesets)
Removed placeholder screenshots:
- docs/screenshots/6.1/ and docs/screenshots/6.7/ (10 PNGs generated
by scripts/generate_screenshot_placeholders.py; never replaced with
real captures, no README/HTML/Swift references)
Removed zero-reference planning / audit md (no code, no README, no
CHANGELOG, no GitHub Pages HTML references):
- docs/ACCOUNT_MANAGED_GATEWAY_TESTING.md
- docs/APPSTORE_METADATA.md
- docs/STOREKIT_TESTING.md
- docs/TYPING_KEYBOARD_BASELINE.md
- docs/ios-pip-voice-session-plan.md
- docs/keyboard-accuracy-plan.md
- docs/keyboard-memory-budget.md
- docs/local-asr-architecture.md
- docs/mac-parity-audit-2026-08-23.md
- docs/personal-dictionary-icloud-kvs-checklist.md
- docs/screenshots/README.md (described the deleted placeholders)
Kept (still referenced):
- Brand marks: OSGBrandMark, OSGLogoWide, OSGStatusMark, osglogo
- docs/TESTING.md (referenced by README.md / README.en.md / CONTRIBUTING.md)
- docs/FLOW_BLUETOOTH_TESTING.md (referenced by docs/TESTING.md)
- docs/RELEASE_NOTES_1.8.0.md (referenced by CHANGELOG.md)
- docs/assets/{badges,whats-new,logo,app-icon,hero-devices} (GitHub Pages)
- AppStoreScreenshots/, marketing/ (App Store /策划素材, not in repo refs)
Updated .gitignore so the deleted md cannot be re-added locally and
pushed back by accident. Asset placeholders, dead code, and unused
badges are also covered by patterns under the same rationale.
No version bump: this is a pure cleanup (chore:) with no user-visible
behavior change.
- Add Generated/, CorpusRegistry, v6 boundary/blind evaluation
corpora, and sealed blessing benchmark review queues to the
generated-artifact ignore list so they stay out of the source
tree after reruns.
Add the AI keyboard tab, Agent settings, and user-owned LLM key path for 1.7.0, including streaming answers and web-search transports without the built-in DeepSeek fallback.
Replace the Sherpa offline pipeline with native MLX streaming, resilient model downloads, live transcript previews, and supporting tests and documentation.
End Live Activities and release the audio session synchronously on
applicationWillTerminate, and continue macOS local-model install,
onboarding, and settings polish on this branch.
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.
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.
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.
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.
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