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.
This commit is contained in:
Rocky
2026-07-05 18:27:23 +08:00
parent 074e24d87f
commit 05e005e9ce
60 changed files with 3247 additions and 1388 deletions
@@ -622,7 +622,7 @@ final class FlowSessionManager: ObservableObject {
// so the keyboard can show the "fill in your key" hint
// inline rather than a generic failure message. The raw
// transcript is still delivered no data loss.
let warning = Self.warningFromPolishError(error) ?? chunkNote
let warning = Self.warningFromPolishError(error, engineMode: engineMode) ?? chunkNote
FlowDiagnostics.log(
"polish failed after \(String(format: "%.1f", Date().timeIntervalSince(polishStarted)))s: " +
"\(error.localizedDescription)"
@@ -667,11 +667,14 @@ final class FlowSessionManager: ObservableObject {
/// v0.2.0: surface the local-mode cloud-polish error path with a
/// localised hint ("please fill in your DeepSeek key in Settings")
/// rather than letting the keyboard show a generic network error.
private static func warningFromPolishError(_ error: Error) -> String? {
private static func warningFromPolishError(_ error: Error, engineMode: String) -> String? {
guard let polishError = error as? PolishingService.PolishError,
polishError == .missingAPIKey else {
return nil
}
if engineMode == "local" {
return AppL10n.string("flow.warning.localPolishUnavailable")
}
return AppL10n.string("flow.warning.cloudPolishMissingKey")
}