Commit Graph
28 Commits
Author SHA1 Message Date
rocky d540a58594 fix(account): defer operation-error dismissal until the alert update ends 2026-10-20 20:06:36 +02:00
rocky 03bfbbe08b feat(account): add QR invitation poster and 8-character invitation codes
- Invite opens an appearance-matched poster with the app icon, inviter name,
  QR code, and short code; tap for a full-brightness QR view; save (add-only
  Photos access), share, or copy the link. Mac shows the poster in a popover
  and can save it as PNG.
- Accounts get look-alike-free 8-character codes. "I have a code" accepts a
  code, link, or pasted message during the server-defined binding window.
- Permanently rejected redemptions show a specific message and drop the
  stored code instead of retrying on every sign-in.
2026-10-20 20:06:36 +02:00
rocky eaacc35cf7 feat(styles): learn the personal reply style incrementally
Regeneration merges the on-device learned evidence with dictation and reply
choices since the last generation instead of re-reading only the newest
~2,500 characters. It unlocks after 500 new characters, fades traits that
stop getting support, and fully relearns styles without saved evidence.
2026-10-20 20:05:35 +02:00
rocky f71f598ac7 fix(keyboard): stop showing the edit hint after every insertion 2026-10-20 20:05:29 +02:00
rocky 1ddbd28371 feat(keyboard): show dictation progress as a fill on the microphone capsule 2026-10-20 20:05:21 +02:00
rocky be56d2210c fix(asr): keep recordings when streaming recognition fails or stalls
- Keep recording after a streaming ASR error and retry the captured PCM
  through batch ASR before reporting a failure.
- Treat an expired ASR wait as a stall so batch fallback still runs.
- Prime the managed Volcengine stream on mic touch and adopt it only while
  fresh and open.
- Auto-scroll the live transcript to the newest recognized text.
2026-10-20 20:05:08 +02:00
rocky f7a031cc1d test(manifest): register PolishStylesL10nTests in the polish group 2026-10-20 20:04:55 +02:00
rocky 7ae9dbcde7 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	docs/index.html
#	docs/sitemap.xml
2026-10-07 11:19:12 +08:00
rocky ec963e3a79 docs(site): add Search Console verification meta, GA4 gtag, and website analytics notice 2026-10-07 11:16:29 +08:00
rocky a2abfc5e8c fix: keyboard stability, host-only analytics, and auto-reply dedupe
- record analytics/usage only in the host app; the extension no longer opens the
  SQLite queues, so hiding the keyboard cannot hold WAL locks (0xdead10cc)
- sync clipboard history writes across processes so deletes made on one side
  are not reverted by the other
- guard memory-relief reentrancy on controller and telemetry sides to break
  the recursive crash loop
- resolve credentials off the main thread and migrate legacy keys once per
  install, so Keychain reads cannot be caught in the suspend window
- gate Volcengine ASR ping completion to once (SIGTRAP on double resume)
- treat same-text captures as the same logical copy and dedupe auto actions
  by content fingerprint within 30s, stopping Universal Clipboard storms
- realign version line to 2.1.0 (build 110)
2026-09-25 12:14:29 +08:00
rockyandClaude Opus 5 0023741455 feat(app): add crash visibility, memory relief, and clipboard reply work
Squashes the working tree that had accumulated on cloud-agent. Two
strands of work, kept in one commit at the maintainer's request.

Crash and stability (this session):

- Subscribe to MetricKit so crash, hang, CPU, and disk-write diagnostics
  are recorded on device. Keyboard-extension memory kills are the most
  likely way a user loses the keyboard mid-sentence and iOS does not
  classify them as crashes, so they never reach App Store Connect.
- Upload those reports to POST /v1/analytics/diagnostics from internal
  builds only (local Debug and TestFlight), reusing the analytics
  installation identifier and skipping entirely when analytics are off.
  App Store builds keep everything on device.
- Shed the Chinese engine, English lexicon, and in-flight pipeline work
  at the 40 MiB and 48 MiB budget thresholds instead of waiting for a
  system memory warning that jetsam often preempts.
- Install audio taps with the input node's live format through one
  shared adaptive resampler, so an audio-route change mid-recording no
  longer raises an uncatchable format-mismatch exception. Covers the
  dictation preview and the Mac recorder, which still used the pattern
  the Flow capture path had already moved away from.
- Harden the librime bridge: never pass a nil string across a nonnull
  boundary when a user dictionary holds malformed UTF-8, null-check the
  engine API vector, and distrust a candidate count without its array.
- Guard the expanded candidate grid against a list that shrinks while
  the collection view is still laying out against the previous count.
- Resolve localized text without the App Group so the screen shown when
  that container is missing renders its message instead of crashing on
  launch, which is the one situation it exists to handle.
- Gate CI on the App Group and Keychain wiring that the keyboard
  extension hard-depends on, with a self-test proving the check fails.

Clipboard, reply, and semantics (pre-existing working tree):

- Unify the reply center, refresh keyboard AI features, and add native
  event and reminder exporters.
- Expand clipboard semantic routing with authored corpora and a
  product-blind holdout, plus the scripts that build and validate them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 17:31:29 -07:00
rockyandClaude Opus 5 628e983794 merge: bring local uncommitted work onto the cloud-agent branch
Merges the wip/local-snapshot-20260901 safety commit (94 uncommitted files:
in-flight Account/Mac/Tip work, clipboard-semantics improvements, and the
feature-preview recording tooling) into the newer cloud-agent line.

Conflict resolutions:
- docs/*: honour the branch's deliberate removal in eac44da; drop the now
  dangling docs/archive/README.md that only pointed at the deleted files.
- CHANGELOG: both sides describe separate [Unreleased] features; keep both.
- ClipboardSkillSemanticRanker: take the branch's reply routing, which retires
  ranking playfulReply/acceptInvitation/... as separate skills in favour of
  ordinary/formal/playful variants inside one reply action. Keep the local
  link/phone/CJK-density work, which is orthogonal and still referenced.
- OfficialSkillCatalogTests: keep the local "installed exactly once" assertion;
  it matches the test's own comment and does not hardcode the default set.
- ClipboardSkillSemanticRankerTests: keep both sides' disjoint test functions;
  retarget the two link/phone cases at the branch's row, which no longer
  surfaces the retired specialised reply skills.

Verified: xcodebuild build succeeds and the 77 ranker/catalog/analyzer tests
pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 12:34:23 -07:00
rockyandClaude Opus 5 5d51c5bf77 chore: snapshot uncommitted local work before rebasing onto cloud-agent
Safety commit so the 94 uncommitted files in the working tree (in-flight
Account/Mac/Tip work plus the feature-preview recording tooling) survive the
move onto cursor/cloud-agent-1787824905651-vxuwc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 11:28:07 -07: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
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 dc9697bf3d docs(zh): 同步 banner 和 XcodeGen 构建说明到中文 README 2026-06-24 17:06:45 +08:00
rocky bdb9379ab0 docs: add XcodeGen build setup instructions to README 2026-06-24 16:29:07 +08:00
rocky 752d2741dc docs: add banner about default branch rename main→0.2 2026-06-24 15:59:12 +08:00
rocky 5dff95a227 ci: trigger on 0.1/0.2 branches after rename 2026-06-24 15:58:59 +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 38ad66f07d [JJC-20260618-005-C-2] Refactor: replace 166 Palette.xxx with @Environment(\.themePalette) (ARCH-A2) 2026-06-18 12:02:58 +08:00
rocky ccf2ea3f90 [JJC-20260618-005-C-1] Sync NSSpeechRecognitionUsageDescription into project.yml
- Add NSSpeechRecognitionUsageDescription to OSGKeyboard target
- Add NSMicrophoneUsageDescription + NSSpeechRecognitionUsageDescription to OSGKeyboardExt target
- Regenerated OSGKeyboardExt/Info.plist reflects project.yml (single source of truth)
- BUILD SUCCEEDED, 8/8 tests pass
2026-06-18 11:55:23 +08:00
rocky 9fa64223af [JJC-20260618-005-B] Fix 6 critical bugs from review
- BUG-1: Restore KeyboardRootView top highlight + divider
- BUG-2: App Group unavailable -> friendly error view (no fatalError)
- BUG-3: pressBegan race condition (sync phase to .requestingPermissions)
- BUG-4: cancelled LLMError no longer re-inserts original text
- BUG-6: mode switch during recording flushes partial
- ARCH-A1: State.Phase add .requestingPermissions + .denied(Reason)

Notes:
- 8/8 tests pass (LLMClientTests)
- BUILD SUCCEEDED for both OSGKeyboard and OSGKeyboardExt
- New file AppGroupErrorView.swift auto-registered via XcodeGen
- Info.plist left untouched (NSSpeechRecognitionUsageDescription kept from A block)
2026-06-18 11:51:26 +08:00
rocky ed11a11329 [JJC-20260618-005-A] Review-driven P0 polish
6 review-driven small fixes to the P0 commit series (e0b92ff / e93bab5 /
2e2d8e3 / 013c498). No behavior changes, no new features, no push yet.

Code:
- RED-1: Remove empty init() from OSGKeyboardApp; @StateObject is initialized
  inline and the only thing init() did was print a DEBUG log.
- RED-4: Re-add .preferredColorScheme(.dark) to the 3 keyboard extension
  #Preview blocks. Custom keyboards always render dark; the preview should
  match.
- RED-6: ThemedRoot #Preview blocks now use the new EnvironmentKey
  (\.themePalette) via a private ThemedPreviewContent, instead of hard-coding
  Palette.dark / Palette.light.
- BUG-5: ThemedRoot body now also fills the background with the active
  palette's background color (repeats the colorScheme ternary — accepted
  because body must be a single expression).

Docs:
- DOC-1: CHANGELOG [Unreleased] top note: this is a v0.1.0 → v0.1.1 polish,
  nothing removed, iOS 26 SpeechAnalyzer still 0.2.0.
- DOC-2: README + README.zh.md Limitations: explicit note for iOS 26+ users
  in v0.1.1.

Verified: xcodebuild Debug-iphonesimulator BUILD SUCCEEDED, 8/8 unit
tests pass on iPhone 17 sim (iOS 26.3.1).
2026-06-18 11:29:14 +08:00
rocky 07067ca6c5 feat: initial release v0.1.0
- Custom Keyboard Extension with push-to-talk UI
- iOS 26 SpeechAnalyzer + DictationTranscriber (iOS 18 SF fallback)
- OpenAI-compatible LLM client (4 built-in providers + custom)
- 3-page onboarding flow + provider config UI
- App Group shared storage for cross-process config
- 8s LLM timeout with raw-transcript fallback
- App Store privacy manifests for both targets
- SwiftLint + XcodeGen + GitHub Actions CI
- Unit tests (4/4 passing)
2026-06-17 23:08:58 +08:00