From 6f9af89583d542ca55e4dbe8b326a47629fc3957 Mon Sep 17 00:00:00 2001 From: TRIP <1933142963@qq.com> Date: Wed, 30 Sep 2026 20:32:53 +0800 Subject: [PATCH] release: OpenLess 2.0.0-Beta.4+build.20260930 (#1126) Integrate ASR/Omni fixes, API Route, Android IME and confirmation-based vocabulary learning. Complete settings transitions and macOS single-item credential storage. Synchronize all application versions to 2.0.0-Beta.4+build.20260930. Integrated PRs: #1116, #1120, #1121, #1123, #1124, #1067 and #1128. Linux #1117 remains independent and is excluded from automatic release. Validated: full cross-platform CI, 18 UI motion cases, 112 frontend test files, 1245 Core tests (1 ignored) and 572 macOS native tests. Co-authored-by: HKLHaoBin --- .github/workflows/android-apk.yml | 31 +- .github/workflows/check-linux-egui.yml | 6 +- .github/workflows/ci.yml | 81 +- .github/workflows/release-linux-egui.yml | 40 +- .github/workflows/release-tauri.yml | 201 +- .gitignore | 6 + README.md | 48 +- README.zh.md | 57 +- RELEASING.md | 31 +- docs/2.0-desktop-acceptance.md | 4 +- docs/2.0-requirements.md | 6 +- docs/android-build-time-research-1103.md | 46 - docs/android-ime.md | 60 + docs/architecture.md | 18 +- docs/ci-trigger-and-cache-policy.md | 10 +- .../android-architecture-guide.png | Bin 0 -> 2111260 bytes docs/images/android-ime/clipboard.jpg | Bin 0 -> 182778 bytes docs/images/android-ime/english.jpg | Bin 0 -> 131058 bytes docs/images/android-ime/stroke-chinese.jpg | Bin 0 -> 146036 bytes docs/images/android-ime/stroke-numeric.jpg | Bin 0 -> 128970 bytes docs/images/android-ime/voice-idle-green.jpg | Bin 0 -> 63583 bytes docs/images/android-ime/voice-idle-orange.jpg | Bin 0 -> 66334 bytes .../android-ime/voice-recording-red.jpg | Bin 0 -> 75931 bytes docs/images/android-ime/voice-thinking.jpg | Bin 0 -> 62039 bytes docs/index.md | 5 +- docs/linux-egui-command-event-baseline.json | 12 +- docs/windows-sherpa-onnx-asr-plan.md | 422 - openless-all/app/android/README.md | 9 +- .../assets/english-frequency.LICENSE.txt | 17 + .../app/android/assets/english-frequency.tsv | 20000 ++++++++++++++++ .../app/android/assets/openless_wordmark.jpg | Bin 0 -> 52370 bytes .../android/assets/pinyin_chars.LICENSE.txt | 38 + .../app/android/assets/pinyin_chars.tsv | 8225 +++++++ .../assets/pinyin_dictionary_meta.json | 18 + .../android/assets/pinyin_phrases.LICENSE.txt | 51 + .../app/android/assets/pinyin_phrases.tsv | 2006 ++ .../components/AndroidPermissionsPanel.tsx | 13 +- .../kotlin/EnglishCandidateProvider.kt | 170 + .../android/kotlin/EnglishUserFrequency.kt | 126 + .../app/android/kotlin/ImeLearningPolicy.kt | 34 + .../android/kotlin/LitePinyinController.kt | 186 + .../kotlin/LitePinyinLearnedPhrases.kt | 73 + .../android/kotlin/LitePinyinRepository.kt | 265 + .../android/kotlin/LitePinyinUserFrequency.kt | 49 + .../OpenLessAccessibilityCommandReceiver.kt | 5 + .../kotlin/OpenLessAccessibilityService.kt | 231 +- .../app/android/kotlin/OpenLessApplication.kt | 207 +- .../kotlin/OpenLessBackendWarmupActivity.kt | 826 +- .../app/android/kotlin/OpenLessBuildInfo.kt | 13 + .../kotlin/OpenLessClipboardHistory.kt | 10 +- .../android/kotlin/OpenLessContentWriter.kt | 70 +- .../app/android/kotlin/OpenLessImeService.kt | 4183 +++- .../OpenLessKeyboardSettingsActivity.kt | 757 +- .../app/android/kotlin/OpenLessNative.kt | 90 +- .../android/kotlin/OpenLessOverlayBridge.kt | 8 +- .../android/kotlin/OpenLessOverlayService.kt | 79 +- .../kotlin/OpenLessProcessRestartStats.kt | 68 + .../android/kotlin/OpenLessRuntimeService.kt | 127 +- .../android/kotlin/OpenLessSettingsExport.kt | 154 + .../kotlin/OpenLessVocabularyReceiver.kt | 80 + .../kotlin/OverlayPermissionActivity.kt | 3 +- .../app/android/kotlin/StrokeInput.kt | 6 +- .../android/kotlin/StrokeInputController.kt | 665 + .../android/kotlin/StrokePhraseRepository.kt | 98 +- .../app/android/kotlin/StrokeUserFrequency.kt | 46 +- .../androidTest/ImeLearningMigrationTest.kt | 45 + .../kotlin/test/ImeLearningPolicyTest.kt | 14 + openless-all/app/assets/remote-input/app.js | 339 +- openless-all/app/contract/backend-2.0.json | 432 +- .../app/crates/openless-core/src/api.rs | 1010 +- .../openless-core/src/api/cloud_note.rs | 108 + .../crates/openless-core/src/asr/bailian.rs | 84 +- .../src/asr/dashscope_multimodal.rs | 102 +- .../openless-core/src/asr/elevenlabs.rs | 2 +- .../app/crates/openless-core/src/asr/frame.rs | 9 +- .../app/crates/openless-core/src/asr/mimo.rs | 15 +- .../app/crates/openless-core/src/asr/pcm.rs | 22 +- .../openless-core/src/asr/qwen_realtime.rs | 83 +- .../openless-core/src/asr/stepfun_realtime.rs | 311 +- .../openless-core/src/asr/tencent_cloud.rs | 18 +- .../openless-core/src/asr/volcengine.rs | 243 +- .../crates/openless-core/src/asr/whisper.rs | 282 +- .../app/crates/openless-core/src/asr/xfyun.rs | 159 +- .../openless-core/src/cloud_providers.rs | 25 +- .../src/cloud_sync_e2ee/adapter.rs | 99 +- .../src/cloud_sync_e2ee/local.rs | 181 +- .../src/cloud_sync_e2ee/setup_prompt_tests.rs | 5 + .../src/cloud_sync_e2ee/tests.rs | 5 + .../src/cloud_sync_e2ee_documents/registry.rs | 16 +- .../src/cloud_sync_e2ee_documents/tests.rs | 3 +- .../src/cloud_sync_e2ee_store/tests.rs | 5 + .../crates/openless-core/src/coding_agent.rs | 48 +- .../openless-core/src/dictation_context.rs | 33 +- .../openless-core/src/dictation_engine.rs | 4 + .../app/crates/openless-core/src/domains.rs | 27 +- .../app/crates/openless-core/src/events.rs | 10 + .../openless-core/src/external_audio.rs | 5 +- .../src/external_audio/archive.rs | 6 +- .../openless-core/src/host_document/diff.rs | 48 +- .../openless-core/src/host_document/mod.rs | 10 +- .../src/host_document/observation.rs | 158 + .../crates/openless-core/src/less_computer.rs | 18 +- .../app/crates/openless-core/src/lib.rs | 4 +- .../crates/openless-core/src/llm_gemini.rs | 189 +- .../crates/openless-core/src/llm_protocol.rs | 15 +- .../crates/openless-core/src/marketplace.rs | 35 +- .../crates/openless-core/src/model_store.rs | 7 +- .../app/crates/openless-core/src/net.rs | 115 +- .../app/crates/openless-core/src/omni.rs | 79 +- .../openless-core/src/output_cleaning.rs | 6 +- .../app/crates/openless-core/src/polish.rs | 613 +- .../app/crates/openless-core/src/ports.rs | 4 + .../openless-core/src/preference_patch.rs | 74 + .../openless-core/src/prompt_compose.rs | 89 +- .../app/crates/openless-core/src/prompts.rs | 298 +- .../openless-core/src/provider_resolution.rs | 58 + .../openless-core/src/provider_rules.rs | 54 +- .../crates/openless-core/src/qa_service.rs | 33 +- .../openless-core/src/remote_input_service.rs | 11 +- .../openless-core/src/selection_service.rs | 53 +- .../src/selection_voice_intent.rs | 145 +- .../src/selection_voice_service.rs | 177 +- .../crates/openless-core/src/shared_types.rs | 897 +- .../openless-core/src/streaming_insert.rs | 29 +- .../openless-core/src/style_pack_archive.rs | 4 +- .../src/style_pack_store_tests.rs | 10 +- .../crates/openless-core/src/style_packs.rs | 105 +- .../app/crates/openless-core/src/types.rs | 1 + .../tests/auxiliary_voice_lifecycle.rs | 36 +- .../tests/remote_input_contract.rs | 2 +- .../openless-core/tests/selection_contract.rs | 8 +- .../tests/selection_voice_contract.rs | 294 + openless-all/app/e2e/motion.spec.ts | 383 + .../app/linux-egui/src/coding_agent.rs | 2 +- openless-all/app/linux-egui/src/i18n.rs | 1 + openless-all/app/linux-egui/src/main.rs | 3 +- .../app/linux-egui/src/preference_patch.rs | 53 +- .../app/linux-egui/src/remote_input.rs | 3 +- openless-all/app/motion-preview.html | 33 - openless-all/app/package-lock.json | 51 +- openless-all/app/package.json | 7 +- openless-all/app/playwright.config.ts | 29 + .../android-apk-workflow-contract.test.mjs | 19 + openless-all/app/scripts/build-mac.sh | 38 +- .../app/scripts/ci-disable-macos-qwen3.mjs | 28 +- .../configure-android-release-signing.mjs | 77 +- .../app/scripts/copy-android-scaffolding.mjs | 28 +- .../scripts/generate-english-dictionary.mjs | 64 + .../scripts/generate-pinyin-characters.mjs | 162 + .../app/scripts/generate-pinyin-phrases.mjs | 171 + .../history-detail-sibling-keys.test.mjs | 33 +- .../app/scripts/layout-mode-contract.test.mjs | 4 +- .../linux-egui-release-contract.test.mjs | 66 +- .../macos-capsule-spaces-contract.test.mjs | 63 +- .../merge-android-overlay-manifest.mjs | 52 +- .../merge-android-overlay-manifest.test.mjs | 48 + .../scripts/remote-input-audio-queue.test.mjs | 2 +- .../app/scripts/remote-input-locales.test.mjs | 2 +- .../shared-backend-wire-contract.test.mjs | 30 + ...windows-insertion-target-contract.test.mjs | 3 +- ...indows-startup-lifecycle-contract.test.mjs | 5 +- .../app/scripts/windows-ui-config.test.mjs | 10 +- openless-all/app/src-tauri/Cargo.lock | 8 +- openless-all/app/src-tauri/Cargo.toml | 24 +- openless-all/app/src-tauri/build.rs | 44 +- .../src-tauri/src/android/edit_observation.rs | 223 + openless-all/app/src-tauri/src/android/jni.rs | 157 +- openless-all/app/src-tauri/src/android/mod.rs | 4 + .../src-tauri/src/android/native_bridge.rs | 889 +- .../app/src-tauri/src/android/overlay.rs | 20 +- .../app/src-tauri/src/android/updater.rs | 3 +- .../src-tauri/src/android/updater_logic.rs | 5 +- .../src/asr/local/apple_speech_provider.rs | 587 +- .../app/src-tauri/src/asr/local/cache.rs | 45 +- .../src/asr/local/foundry_provider.rs | 32 +- .../src/asr/local/foundry_runtime.rs | 169 +- .../src-tauri/src/asr/local/local_provider.rs | 24 +- .../src/asr/local/mlx_qwen_engine.rs | 16 +- .../app/src-tauri/src/asr/local/mlx_worker.rs | 28 +- .../app/src-tauri/src/asr/local/mod.rs | 15 +- .../src-tauri/src/asr/local/qwen_engine.rs | 85 +- .../app/src-tauri/src/asr/local/qwen_ffi.rs | 10 +- .../app/src-tauri/src/asr/local/sherpa.rs | 20 +- .../src/asr/local/sherpa_provider.rs | 24 +- .../src-tauri/src/asr/local/sherpa_runtime.rs | 41 +- .../app/src-tauri/src/asr/local/test_run.rs | 38 +- .../src/asr/local/whisper_provider.rs | 24 +- .../src-tauri/src/coding_agent/commands.rs | 8 +- .../app/src-tauri/src/coding_agent/mod.rs | 2 +- .../app/src-tauri/src/combo_hotkey.rs | 42 +- .../app/src-tauri/src/commands/channels.rs | 24 +- .../app/src-tauri/src/commands/credentials.rs | 13 +- .../app/src-tauri/src/commands/dictation.rs | 6 +- .../app/src-tauri/src/commands/dictionary.rs | 45 +- .../app/src-tauri/src/commands/history.rs | 74 +- .../app/src-tauri/src/commands/hotkeys.rs | 35 +- .../app/src-tauri/src/commands/local_asr.rs | 13 +- .../app/src-tauri/src/commands/misc.rs | 66 +- .../app/src-tauri/src/commands/mod.rs | 70 +- .../src/commands/permissions_cmds.rs | 8 +- .../app/src-tauri/src/commands/providers.rs | 4 +- openless-all/app/src-tauri/src/commands/qa.rs | 91 +- .../src-tauri/src/commands/remote_input.rs | 13 +- .../src-tauri/src/commands/selection_voice.rs | 5 +- .../app/src-tauri/src/commands/settings.rs | 237 +- .../app/src-tauri/src/commands/style_packs.rs | 5 +- openless-all/app/src-tauri/src/coordinator.rs | 430 +- .../src/coordinator/capsule_focus.rs | 136 +- .../src-tauri/src/coordinator/hotkey_loops.rs | 313 +- .../coordinator/selection_voice_session.rs | 259 +- .../app/src-tauri/src/coordinator_state.rs | 103 +- .../app/src-tauri/src/core_adapters.rs | 169 +- .../app/src-tauri/src/device_watch.rs | 108 +- .../app/src-tauri/src/external_url.rs | 91 +- .../src-tauri/src/global_hotkey_runtime.rs | 6 +- .../app/src-tauri/src/host_document/macos.rs | 589 +- .../app/src-tauri/src/host_document/mod.rs | 236 +- .../src-tauri/src/host_document/windows.rs | 315 + openless-all/app/src-tauri/src/hotkey.rs | 270 +- openless-all/app/src-tauri/src/insertion.rs | 59 +- openless-all/app/src-tauri/src/lib.rs | 715 +- .../app/src-tauri/src/mobile_runtime.rs | 31 + .../app/src-tauri/src/mobile_stubs/hotkey.rs | 14 +- .../src-tauri/src/mobile_stubs/selection.rs | 17 +- openless-all/app/src-tauri/src/permissions.rs | 52 +- .../src-tauri/src/persistence/credentials.rs | 463 +- .../credentials/android_transfer.rs | 387 + .../persistence/credentials/macos_keychain.rs | 99 + .../persistence/credentials/macos_vault.rs | 495 + .../app/src-tauri/src/persistence/paths.rs | 54 +- openless-all/app/src-tauri/src/qa_hotkey.rs | 49 +- openless-all/app/src-tauri/src/recorder.rs | 263 +- .../app/src-tauri/src/remote_server/mod.rs | 154 +- .../src/remote_server/tls_identity.rs | 10 +- openless-all/app/src-tauri/src/selection.rs | 163 +- .../app/src-tauri/src/tauri_events.rs | 52 +- .../app/src-tauri/src/unicode_keystroke.rs | 189 +- .../app/src-tauri/src/windows_ime_profile.rs | 114 +- .../app/src-tauri/src/windows_ime_restore.rs | 59 +- .../app/src-tauri/src/windows_ime_session.rs | 39 +- openless-all/app/src-tauri/tauri.conf.json | 2 +- openless-all/app/src/App.tsx | 64 +- openless-all/app/src/components/AudioCue.tsx | 10 +- .../app/src/components/AutoUpdate.tsx | 271 +- .../app/src/components/AutoUpdateGate.tsx | 10 +- openless-all/app/src/components/Capsule.tsx | 235 +- .../src/components/CloudSyncSetupPrompt.tsx | 41 +- .../app/src/components/CoreStartupScreen.tsx | 6 +- .../app/src/components/FloatingShell.tsx | 215 +- .../app/src/components/GithubLoginModal.tsx | 23 +- .../src/components/GlobalDownloadProgress.tsx | 29 +- openless-all/app/src/components/Heatmap.tsx | 51 +- openless-all/app/src/components/Icon.tsx | 2 + .../app/src/components/InsertFallbackCard.tsx | 56 +- openless-all/app/src/components/Kbd.tsx | 9 +- .../app/src/components/LearnVocabulary.tsx | 87 + .../app/src/components/MobileMoreSheet.tsx | 20 +- .../app/src/components/MobileStyleSheet.tsx | 20 +- .../app/src/components/Onboarding.tsx | 17 +- .../app/src/components/SavedToast.tsx | 54 +- .../app/src/components/SettingsModal.tsx | 89 +- .../app/src/components/ShortcutRecorder.tsx | 53 +- openless-all/app/src/components/SiriGL.tsx | 189 +- .../app/src/components/SplashVideo.tsx | 30 +- .../app/src/components/ThinkingDots.tsx | 15 +- openless-all/app/src/components/Tooltip.tsx | 18 +- .../src/components/VocabSuggestionCard.tsx | 72 +- .../app/src/components/VoiceOrbStage.tsx | 37 +- .../app/src/components/WindowChrome.tsx | 12 +- .../app/src/components/chat/LiveWaveform.tsx | 7 +- .../app/src/components/chat/avatars.tsx | 46 +- .../app/src/components/chat/lib/utils.ts | 3 +- .../app/src/components/chat/lifecycle.ts | 17 +- .../app/src/components/chat/markdown.tsx | 13 +- .../app/src/components/chat/orbFeed.ts | 35 +- .../app/src/components/chat/ui/button.tsx | 5 +- openless-all/app/src/components/ui/Modal.tsx | 71 +- .../app/src/components/ui/SelectLite.tsx | 126 +- openless-all/app/src/i18n/de.ts | 66 +- openless-all/app/src/i18n/en.ts | 67 +- openless-all/app/src/i18n/es.ts | 67 +- openless-all/app/src/i18n/fr.ts | 67 +- openless-all/app/src/i18n/index.ts | 16 +- openless-all/app/src/i18n/ja.ts | 69 +- openless-all/app/src/i18n/ko.ts | 63 +- openless-all/app/src/i18n/zh-CN.ts | 59 +- openless-all/app/src/i18n/zh-TW.ts | 55 +- .../app/src/lib/activityMetrics.test.ts | 19 +- openless-all/app/src/lib/activityMetrics.ts | 31 +- openless-all/app/src/lib/advancedAsrConfig.ts | 12 +- openless-all/app/src/lib/appVersion.ts | 6 +- openless-all/app/src/lib/audioCue.test.ts | 62 +- openless-all/app/src/lib/audioCue.ts | 185 +- openless-all/app/src/lib/backendEvent.test.ts | 22 + openless-all/app/src/lib/backendEvent.ts | 11 + openless-all/app/src/lib/capsuleLayout.ts | 11 +- openless-all/app/src/lib/fontScale.ts | 15 +- .../app/src/lib/history-repolish.test.ts | 19 +- openless-all/app/src/lib/history-repolish.ts | 26 +- .../app/src/lib/history-retranscribe.ts | 6 +- openless-all/app/src/lib/hotkey.ts | 28 +- .../app/src/lib/insertTextAnimation.ts | 19 +- .../app/src/lib/ipc/asr-credentials.ts | 3 +- openless-all/app/src/lib/ipc/channels.ts | 27 +- openless-all/app/src/lib/ipc/chat-panel.ts | 12 +- openless-all/app/src/lib/ipc/coding-agent.ts | 12 +- openless-all/app/src/lib/ipc/history.ts | 22 +- openless-all/app/src/lib/ipc/hotkeys.ts | 6 +- openless-all/app/src/lib/ipc/index.ts | 8 +- openless-all/app/src/lib/ipc/less-computer.ts | 23 +- .../app/src/lib/ipc/mock-data.test.ts | 2 +- openless-all/app/src/lib/ipc/mock-data.ts | 20 +- .../lib/ipc/mock-provider-descriptors.json | 16 + .../ipc/provider-descriptors.generated.json | 16 + openless-all/app/src/lib/ipc/remote-server.ts | 6 +- .../src/lib/ipc/selection-voice-preview.ts | 4 +- openless-all/app/src/lib/ipc/settings.ts | 30 + openless-all/app/src/lib/ipc/splash.ts | 15 +- openless-all/app/src/lib/ipc/style-packs.ts | 7 +- openless-all/app/src/lib/ipc/utils.ts | 43 +- openless-all/app/src/lib/ipc/vocab.ts | 35 +- openless-all/app/src/lib/localAsr.ts | 16 +- openless-all/app/src/lib/motion.ts | 299 + openless-all/app/src/lib/navLabels.ts | 2 +- openless-all/app/src/lib/providerSetup.ts | 3 +- openless-all/app/src/lib/qaMarkdown.ts | 2 +- openless-all/app/src/lib/qaMessage.ts | 7 +- openless-all/app/src/lib/savedEvent.ts | 18 +- openless-all/app/src/lib/stylePackIcon.ts | 9 +- .../app/src/lib/translationTarget.test.ts | 10 +- openless-all/app/src/lib/translationTarget.ts | 19 +- openless-all/app/src/lib/types.ts | 404 +- openless-all/app/src/lib/unicode.test.ts | 5 +- openless-all/app/src/lib/unicode.ts | 17 +- openless-all/app/src/lib/useExitMount.ts | 21 +- openless-all/app/src/lib/useRafThrottle.ts | 20 +- .../app/src/lib/windowsKeyboardListToggle.ts | 9 +- openless-all/app/src/main.tsx | 24 +- openless-all/app/src/pages/Corrections.tsx | 13 +- openless-all/app/src/pages/History.tsx | 278 +- .../app/src/pages/LessComputerGlow.tsx | 52 +- .../app/src/pages/LessComputerPanel.tsx | 48 +- .../app/src/pages/LocalAsr/components.tsx | 36 +- openless-all/app/src/pages/LocalAsr/index.tsx | 196 +- openless-all/app/src/pages/Marketplace.tsx | 1360 +- openless-all/app/src/pages/Overview.tsx | 94 +- .../src/pages/SelectionVoiceIntentPicker.tsx | 20 +- openless-all/app/src/pages/Style.tsx | 36 +- openless-all/app/src/pages/Translation.tsx | 26 +- openless-all/app/src/pages/Vocab.tsx | 136 +- openless-all/app/src/pages/_atoms.tsx | 51 +- .../app/src/pages/settings/AboutSection.tsx | 16 +- .../src/pages/settings/AutoUpdateSection.tsx | 3 +- .../src/pages/settings/BetaChannelSection.tsx | 7 +- .../settings/ChannelEditorHostContext.ts | 11 +- .../app/src/pages/settings/ChannelList.tsx | 164 +- .../src/pages/settings/CheckUpdateButton.tsx | 42 +- .../pages/settings/ClaudeConsoleSection.tsx | 12 +- .../src/pages/settings/CodingAgentSection.tsx | 55 +- .../src/pages/settings/DataStorageSection.tsx | 19 +- .../src/pages/settings/DebugToolsSection.tsx | 27 +- .../src/pages/settings/LanguageSection.tsx | 2 +- .../src/pages/settings/LlmProtocolFields.tsx | 4 +- .../src/pages/settings/MarketplaceSection.tsx | 15 +- .../src/pages/settings/MicrophoneSelect.tsx | 15 +- .../settings/MultimodalPipelineSection.tsx | 45 - .../app/src/pages/settings/NetworkSection.tsx | 6 +- .../src/pages/settings/PermissionsSection.tsx | 11 +- .../app/src/pages/settings/ProviderForm.tsx | 3 +- .../src/pages/settings/ProvidersSection.tsx | 487 +- .../pages/settings/RecordingInputSection.tsx | 83 +- .../src/pages/settings/RemoteInputSection.tsx | 34 +- .../settings/SelectionWorkspaceSection.tsx | 2 +- .../src/pages/settings/ShortcutsSection.tsx | 15 +- .../settings/VocabularyLearningSection.tsx | 102 + .../app/src/pages/settings/credentialDraft.ts | 2 +- .../app/src/pages/settings/modelCatalog.ts | 3 +- .../settings/models/LocalModelPicker.tsx | 12 +- .../settings/models/LocalModelsSection.tsx | 24 +- .../src/pages/settings/models/modelsNav.ts | 7 +- .../app/src/pages/settings/navigation.test.ts | 89 +- .../app/src/pages/settings/navigation.ts | 51 +- .../pages/settings/navigationLocale.test.ts | 2 +- .../app/src/pages/settings/shared.tsx | 36 +- openless-all/app/src/pages/settings/tabs.tsx | 274 +- .../app/src/state/HotkeySettingsContext.tsx | 163 +- .../src/state/preferencesWriteGate.test.ts | 87 +- .../app/src/state/preferencesWriteGate.ts | 101 +- openless-all/app/src/styles/global.css | 247 +- openless-all/app/src/styles/overlays.css | 8 - openless-all/app/src/styles/tokens.css | 1 + openless-all/app/vite.config.ts | 5 +- openless-all/design_handoff_openless/App.html | 84 - .../design_handoff_openless/AppIcon.png | Bin 371995 -> 0 bytes .../OpenLess Redesign.html | 445 - .../OpenLess.standalone.html | 192 - .../design_handoff_openless/README.md | 393 - .../design_handoff_openless/capsule.jsx | 159 - .../design_handoff_openless/chrome.jsx | 134 - openless-all/design_handoff_openless/data.js | 45 - .../design_handoff_openless/design-canvas.jsx | 622 - .../design_handoff_openless/icons.jsx | 74 - .../design_handoff_openless/pages.jsx | 852 - .../design_handoff_openless/tokens.css | 87 - .../design_handoff_openless/tweaks-panel.jsx | 425 - .../design_handoff_openless/variants.jsx | 437 - scripts/audit-system-level.sh | 598 - scripts/bump-version.sh | 37 +- scripts/finding-helper.sh | 344 - 409 files changed, 57100 insertions(+), 15507 deletions(-) delete mode 100644 docs/android-build-time-research-1103.md create mode 100644 docs/android-ime.md create mode 100644 docs/images/android-ime/android-architecture-guide.png create mode 100644 docs/images/android-ime/clipboard.jpg create mode 100644 docs/images/android-ime/english.jpg create mode 100644 docs/images/android-ime/stroke-chinese.jpg create mode 100644 docs/images/android-ime/stroke-numeric.jpg create mode 100644 docs/images/android-ime/voice-idle-green.jpg create mode 100644 docs/images/android-ime/voice-idle-orange.jpg create mode 100644 docs/images/android-ime/voice-recording-red.jpg create mode 100644 docs/images/android-ime/voice-thinking.jpg delete mode 100644 docs/windows-sherpa-onnx-asr-plan.md create mode 100644 openless-all/app/android/assets/english-frequency.LICENSE.txt create mode 100644 openless-all/app/android/assets/english-frequency.tsv create mode 100644 openless-all/app/android/assets/openless_wordmark.jpg create mode 100644 openless-all/app/android/assets/pinyin_chars.LICENSE.txt create mode 100644 openless-all/app/android/assets/pinyin_chars.tsv create mode 100644 openless-all/app/android/assets/pinyin_dictionary_meta.json create mode 100644 openless-all/app/android/assets/pinyin_phrases.LICENSE.txt create mode 100644 openless-all/app/android/assets/pinyin_phrases.tsv create mode 100644 openless-all/app/android/kotlin/EnglishCandidateProvider.kt create mode 100644 openless-all/app/android/kotlin/EnglishUserFrequency.kt create mode 100644 openless-all/app/android/kotlin/ImeLearningPolicy.kt create mode 100644 openless-all/app/android/kotlin/LitePinyinController.kt create mode 100644 openless-all/app/android/kotlin/LitePinyinLearnedPhrases.kt create mode 100644 openless-all/app/android/kotlin/LitePinyinRepository.kt create mode 100644 openless-all/app/android/kotlin/LitePinyinUserFrequency.kt create mode 100644 openless-all/app/android/kotlin/OpenLessBuildInfo.kt create mode 100644 openless-all/app/android/kotlin/OpenLessProcessRestartStats.kt create mode 100644 openless-all/app/android/kotlin/OpenLessSettingsExport.kt create mode 100644 openless-all/app/android/kotlin/OpenLessVocabularyReceiver.kt create mode 100644 openless-all/app/android/kotlin/StrokeInputController.kt create mode 100644 openless-all/app/android/kotlin/androidTest/ImeLearningMigrationTest.kt create mode 100644 openless-all/app/android/kotlin/test/ImeLearningPolicyTest.kt create mode 100644 openless-all/app/crates/openless-core/src/api/cloud_note.rs create mode 100644 openless-all/app/crates/openless-core/src/host_document/observation.rs create mode 100644 openless-all/app/crates/openless-core/src/preference_patch.rs create mode 100644 openless-all/app/e2e/motion.spec.ts delete mode 100644 openless-all/app/motion-preview.html create mode 100644 openless-all/app/playwright.config.ts create mode 100644 openless-all/app/scripts/generate-english-dictionary.mjs create mode 100644 openless-all/app/scripts/generate-pinyin-characters.mjs create mode 100644 openless-all/app/scripts/generate-pinyin-phrases.mjs create mode 100644 openless-all/app/scripts/merge-android-overlay-manifest.test.mjs create mode 100644 openless-all/app/src-tauri/src/android/edit_observation.rs create mode 100644 openless-all/app/src-tauri/src/host_document/windows.rs create mode 100644 openless-all/app/src-tauri/src/persistence/credentials/android_transfer.rs create mode 100644 openless-all/app/src-tauri/src/persistence/credentials/macos_keychain.rs create mode 100644 openless-all/app/src-tauri/src/persistence/credentials/macos_vault.rs create mode 100644 openless-all/app/src/components/LearnVocabulary.tsx create mode 100644 openless-all/app/src/lib/motion.ts delete mode 100644 openless-all/app/src/pages/settings/MultimodalPipelineSection.tsx create mode 100644 openless-all/app/src/pages/settings/VocabularyLearningSection.tsx delete mode 100755 openless-all/design_handoff_openless/App.html delete mode 100755 openless-all/design_handoff_openless/AppIcon.png delete mode 100755 openless-all/design_handoff_openless/OpenLess Redesign.html delete mode 100755 openless-all/design_handoff_openless/OpenLess.standalone.html delete mode 100755 openless-all/design_handoff_openless/README.md delete mode 100755 openless-all/design_handoff_openless/capsule.jsx delete mode 100755 openless-all/design_handoff_openless/chrome.jsx delete mode 100755 openless-all/design_handoff_openless/data.js delete mode 100755 openless-all/design_handoff_openless/design-canvas.jsx delete mode 100755 openless-all/design_handoff_openless/icons.jsx delete mode 100755 openless-all/design_handoff_openless/pages.jsx delete mode 100755 openless-all/design_handoff_openless/tokens.css delete mode 100755 openless-all/design_handoff_openless/tweaks-panel.jsx delete mode 100755 openless-all/design_handoff_openless/variants.jsx delete mode 100644 scripts/audit-system-level.sh delete mode 100644 scripts/finding-helper.sh diff --git a/.github/workflows/android-apk.yml b/.github/workflows/android-apk.yml index 014e4c3f..0030092b 100644 --- a/.github/workflows/android-apk.yml +++ b/.github/workflows/android-apk.yml @@ -4,6 +4,8 @@ name: Android APK (debug) # - push v*-tauri tag → signed release APK + minisign + updater manifest → GitHub Release # - workflow_dispatch → signed release APK when ANDROID_KEYSTORE_* secrets exist # (overlay install + user data preserved); otherwise unsigned debug APK (annotated, non-blocking) +# - workflow_dispatch + signed_debug=true — debug APK signed with the release +# keystore (when secrets exist); artifact remains openless-android-debug-*. # # #1103 build-time changes: # - Do not wipe Cargo/Gradle/target before cache post-save (removed Free disk step). @@ -26,8 +28,12 @@ on: description: 'Dispatch-only fast release profile (no LTO, codegen-units=16). Ignored on tags.' type: boolean default: false + signed_debug: + description: 'Build debug mode but sign it with the release keystore (requires ANDROID_KEYSTORE_* secrets).' + type: boolean + default: false -# 同一 tag 重复推送只跑最新一次;workflow_dispatch 用 run_id 隔离避免互相取消。 +# Repeated pushes of the same tag run only the newest; workflow_dispatch isolates by run_id to avoid mutual cancellation. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }} cancel-in-progress: ${{ github.event_name == 'push' }} @@ -62,6 +68,7 @@ jobs: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + INPUT_SIGNED_DEBUG: ${{ github.event.inputs.signed_debug }} run: | set -euo pipefail is_tag=false @@ -89,6 +96,11 @@ jobs: echo "label=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" echo "signing=tag-release" >> "$GITHUB_OUTPUT" echo "signing_label=Tag release with release keystore" >> "$GITHUB_OUTPUT" + elif [[ "${INPUT_SIGNED_DEBUG:-false}" == "true" ]] && $has_keystore; then + echo "mode=debug" >> "$GITHUB_OUTPUT" + echo "label=signed-debug-run-${{ github.run_number }}" >> "$GITHUB_OUTPUT" + echo "signing=signed-debug" >> "$GITHUB_OUTPUT" + echo "signing_label=Manual dispatch debug APK signed with release keystore" >> "$GITHUB_OUTPUT" elif $has_keystore; then echo "mode=release" >> "$GITHUB_OUTPUT" echo "label=dispatch-run-${{ github.run_number }}" >> "$GITHUB_OUTPUT" @@ -100,6 +112,9 @@ jobs: echo "signing=debug-fallback" >> "$GITHUB_OUTPUT" echo "signing_label=Unsigned debug fallback (no keystore secrets)" >> "$GITHUB_OUTPUT" echo "::notice title=Unsigned debug APK::ANDROID_KEYSTORE_* secrets not configured. Building debug APK without release signing. Overlay install and user data preservation require the release keystore; uninstall before installing if replacing a signed build." + if [[ "${INPUT_SIGNED_DEBUG:-false}" == "true" ]]; then + echo "::warning::signed_debug was requested but Android keystore secrets are incomplete; falling back to an unsigned debug APK." + fi fi - name: Resolve ABI matrix @@ -146,7 +161,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - # Android 不使用本地 Qwen3/Whisper C 子模块。 + # Android does not use the local Qwen3/Whisper C submodules. submodules: false - name: Disable macOS-only Qwen3 MLX dependency @@ -286,13 +301,15 @@ jobs: run: node scripts/merge-android-updater-manifest.mjs - name: Configure Android release signing - if: needs.plan.outputs.mode == 'release' + if: needs.plan.outputs.mode == 'release' || needs.plan.outputs.signing == 'signed-debug' working-directory: openless-all/app env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + # signed_debug: also wire debug buildType to the release keystore + OPENLESS_SIGN_DEBUG: ${{ needs.plan.outputs.signing == 'signed-debug' && 'true' || 'false' }} run: node scripts/configure-android-release-signing.mjs - name: Prime Gradle wrapper @@ -313,6 +330,10 @@ jobs: - name: Build Android debug APK if: needs.plan.outputs.mode == 'debug' working-directory: openless-all/app + env: + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: npm run tauri:android:build:debug - name: Build Android release APK @@ -378,6 +399,10 @@ jobs: cat >> "$GITHUB_STEP_SUMMARY" << 'EOF' > **Signed release APK** (manual dispatch). Same keystore as tag releases — supports overlay install and preserves user data when upgrading from an existing signed install. EOF + elif [ "${{ needs.plan.outputs.signing }}" = "signed-debug" ]; then + cat >> "$GITHUB_STEP_SUMMARY" << 'EOF' + > **Signed debug APK** (manual dispatch). Debug mode is retained for diagnostics; the release keystore is used so `adb install -r` can upgrade the matching signed app without changing the debug artifact name. + EOF fi publish-android-release: diff --git a/.github/workflows/check-linux-egui.yml b/.github/workflows/check-linux-egui.yml index 0e19fba9..b592bf31 100644 --- a/.github/workflows/check-linux-egui.yml +++ b/.github/workflows/check-linux-egui.yml @@ -1,13 +1,13 @@ name: Linux egui build and backend checks -# Only the CI and tag-release workflows call this deb/rpm build and verification path. +# CI and independent manual Linux builds share this deb/rpm verification path. # No standalone push trigger: a Linux PR must show one combined Linux check. on: workflow_call: inputs: # 'none' skips a change set that is only prose (see # scripts/ci-changed-areas.sh): the callers that must always build the - # packages, including the tag release, leave the default in place. + # packages, including accepted tag builds, leave the default in place. scope: required: false type: string @@ -27,7 +27,7 @@ jobs: # A cold dependency tree costs ~18 minutes; anything past this is a hang. timeout-minutes: 90 env: - RELEASE_TAG: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') && github.ref_name || '' }} + RELEASE_TAG: ${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') && github.ref_name || '' }} # Debug info dominates both compile time and cache size for this host, and # the gate below never reads it. The Linux job keeps its own cache so a # red run still starts warm. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b5019e7..d4318b9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ on: options: [all, macos] default: all -# 同一 PR 快速重复推送时取消旧运行;workflow_dispatch 用 run_id 隔离。 +# Cancel old runs when the same PR is pushed repeatedly; workflow_dispatch is isolated by run_id. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -56,6 +56,34 @@ jobs: echo "$area=$( [ "$verdict" = 'false' ] && echo false || echo true )" >> "$GITHUB_OUTPUT" done + ui-motion: + name: UI motion (Chromium + WebKit) + needs: [changes] + if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.tauri != 'false' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + defaults: + run: + working-directory: openless-all/app + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: openless-all/app/package-lock.json + - run: npm ci + - run: npm run build + - run: npx playwright install --with-deps chromium webkit + - run: npm run test:ui-motion + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: ui-motion-failure + path: | + openless-all/app/test-results/ + openless-all/app/playwright-report/ + android-check: needs: [changes] if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.tauri != 'false' @@ -68,14 +96,15 @@ jobs: steps: - uses: actions/checkout@v4 with: - # Android 不使用任何桌面本地 ASR 子模块;macOS MLX 依赖不进入此任务。 + # Android uses no desktop local-ASR submodules; the macOS MLX dependency stays out of + # this job. submodules: false - # Android 不编译任何本地 ASR C 代码:build.rs 按 TARGET 解析排除 - # Android triple(不调 build_qwen_asr)。qwen3-asr-rs 是 path 依赖, - # Cargo 解析器跨所有 target 都要读它的 manifest——所以由下方的 - # ci-disable-macos-qwen3.mjs 删掉该依赖行,否则 cargo check 硬失败。 - # 去掉递归拉取省一次网络 fetch + 失败点。 + # Android compiles no local-ASR C code: build.rs excludes the Android triple by TARGET + # (does not call build_qwen_asr). qwen3-asr-rs is a path dependency, and the Cargo + # resolver must read its manifest for all targets — so the ci-disable-macos-qwen3.mjs + # step below removes that dependency line, otherwise cargo check hard-fails. + # Dropping recursive fetch saves one network fetch and one failure point. - name: Setup Android SDK + NDK uses: android-actions/setup-android@v3 with: @@ -229,15 +258,16 @@ jobs: if: needs.changes.outputs.tauri != 'false' name: ${{ matrix.label }} checks strategy: - # 一个平台挂掉不阻塞其他平台拿到验证结果。 + # One platform failing must not block other platforms from getting verification results. fail-fast: false matrix: include: ${{ fromJSON(github.event_name == 'workflow_dispatch' && inputs.platform == 'macos' && '[{"os":"macos-latest","label":"macOS","preflight":false}]' || '[{"os":"macos-latest","label":"macOS","preflight":false},{"os":"windows-latest","label":"Windows","preflight":true}]') }} runs-on: ${{ matrix.os }} timeout-minutes: 60 env: - # 新增 shared Core 后,macOS 首次编译同时构建 MLX C++ 依赖和完整 - # Tauri test binary;限制并发避免 arm64 runner 在峰值内存处被系统终止。 + # With the shared Core in place, macOS's first build compiles the MLX C++ dependencies and + # the full Tauri test binary together; limit concurrency so the arm64 runner is not OOM-killed + # at peak memory. CARGO_BUILD_JOBS: 2 CARGO_PROFILE_TEST_DEBUG: 0 CMAKE_BUILD_PARALLEL_LEVEL: 2 @@ -247,7 +277,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - # 只在 macOS 初始化 MLX 子模块;Windows 不解析该依赖。 + # Initialize the MLX submodule on macOS only; Windows does not resolve that dependency. submodules: ${{ matrix.os == 'macos-latest' && 'recursive' || 'false' }} - name: Disable macOS-only Qwen3 MLX dependency if: runner.os != 'macOS' @@ -259,7 +289,7 @@ jobs: cache: npm cache-dependency-path: openless-all/app/package-lock.json - # macOS MSRV 在独立 job 并行检查;Windows 保留原有检查顺序。 + # macOS MSRV is checked in a separate parallel job; Windows keeps the original check order. - uses: dtolnay/rust-toolchain@1.88.0 if: runner.os == 'Windows' @@ -343,22 +373,25 @@ jobs: if: runner.os == 'Windows' run: cargo check --locked --manifest-path src-tauri/Cargo.toml - # test 编译并运行 lib/bin,覆盖原 cargo check 的生产 binary 路径。 - # 避免先 metadata-only check、再为同一依赖图做一次 codegen。 + # test compiles and runs lib/bin, covering the production binary paths that plain cargo + # check misses. Avoids a metadata-only check followed by another codegen pass over the same + # dependency graph. - name: Run Rust backend unit tests if: runner.os != 'Windows' run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --bins --timings - name: Compile Rust backend unit tests (Windows) - # Windows runner 能链接 lib test binary,但干净镜像缺少可选 native runtime - # DLL entrypoint 时,进程会在 test harness 启动前退出。这里保留 cfg/link - # 覆盖;共享 Core 的公开 compatibility contract 由下一步实际执行。 + # A Windows runner can link the lib test binary, but on a clean image lacking an optional + # native runtime DLL entrypoint, the process exits before the test harness starts. Keep + # the cfg/link coverage here; the shared Core's public compatibility contract is actually + # executed in the next step. if: runner.os == 'Windows' run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib --no-run - name: Run Rust-only backend unit tests (Windows) - # 独立 test crate 不链接完整 Tauri app lib,只验证公开 Core contract。 - # Windows 专属 Tauri 测试在此 runner 只做上一步的 cfg/link 编译覆盖。 + # The standalone test crate does not link the full Tauri app lib; it verifies the public + # Core contract only. Windows-specific Tauri tests on this runner only get the cfg/link + # compile coverage from the step above. if: runner.os == 'Windows' run: cargo test --locked --manifest-path src-tauri/backend-tests/Cargo.toml @@ -371,10 +404,10 @@ jobs: run: cargo +1.88.0 test --locked --manifest-path src-tauri/backend-tests/Cargo.toml --no-run - name: Verify version sync across all 5 files - # 两个平台都跑这个校验:Windows runner 自带 git-bash,跨 shell 表现一致。 - # 一旦版本号 drift 立刻 fail,避免发版时再发现漏改。 - # 校验 5 处:package.json / package-lock.json (root + nested) / - # tauri.conf.json / Cargo.toml / Cargo.lock 的 [openless] 包。 + # Runs on both platforms: the Windows runner ships git-bash, giving consistent behavior + # across shells. Fail immediately on version drift instead of discovering it at release. + # Checks 5 places: package.json / package-lock.json (root + nested) / + # tauri.conf.json / Cargo.toml / the [openless] package in Cargo.lock. shell: bash run: | PKG=$(node -p "require('./package.json').version") @@ -382,7 +415,7 @@ jobs: LOCK_NESTED=$(node -p "require('./package-lock.json').packages[''].version") TAU=$(node -p "require('./src-tauri/tauri.conf.json').version") CRG=$(grep -E '^version = ' src-tauri/Cargo.toml | head -1 | sed -E 's/^version = "(.+)"$/\1/') - # Cargo.lock:找 [openless] 包紧跟的 version 行 + # Cargo.lock: find the version line right after the [openless] package name CARGO_LOCK_VER=$(awk 'BEGIN{found=0} /^name = "openless"$/{found=1; next} found && /^version = /{gsub(/"/,""); print $3; exit}' src-tauri/Cargo.lock) echo "package.json = $PKG" echo "package-lock root = $LOCK_ROOT" diff --git a/.github/workflows/release-linux-egui.yml b/.github/workflows/release-linux-egui.yml index bf5ef226..091f5000 100644 --- a/.github/workflows/release-linux-egui.yml +++ b/.github/workflows/release-linux-egui.yml @@ -1,12 +1,9 @@ name: Release Linux egui -# An admin pushes v*-tauri only after CI and Linux device acceptance. This -# workflow calls the exact same deb/rpm build used by CI, then attaches its -# verified assets to the shared Tauri/Android Release (Beta remains a draft). +# Independent Linux builds retain the same tests and deb/rpm packaging as CI. +# After product acceptance, an administrator builds the accepted release tag and +# explicitly attaches the verified Actions artifacts to that existing Release. on: - push: - tags: - - 'v*-tauri' workflow_dispatch: permissions: @@ -21,34 +18,3 @@ jobs: permissions: contents: read uses: ./.github/workflows/check-linux-egui.yml - - publish-linux-egui: - # Manual builds only upload Actions artifacts; they never change a Release. - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') - needs: build-linux-egui - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: write - steps: - - uses: actions/download-artifact@v8 - with: - name: openless-linux-egui-x86_64 - path: linux-egui-packages - - - name: Verify downloaded release assets - run: | - set -euo pipefail - test "$(find linux-egui-packages -maxdepth 1 -name '*.deb' | wc -l)" -eq 1 - test "$(find linux-egui-packages -maxdepth 1 -name '*.rpm' | wc -l)" -eq 1 - test "$(find linux-egui-packages -maxdepth 1 -name '*.AppImage' | wc -l)" -eq 0 - ( cd linux-egui-packages && sha256sum --check --strict SHA256SUMS ) - - - name: Attach Linux assets to the shared release draft - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - draft: ${{ endsWith(github.ref_name, '-beta-tauri') || contains(github.ref_name, '-Beta.') }} - prerelease: ${{ endsWith(github.ref_name, '-beta-tauri') || contains(github.ref_name, '-Beta.') }} - files: linux-egui-packages/* - fail_on_unmatched_files: true diff --git a/.github/workflows/release-tauri.yml b/.github/workflows/release-tauri.yml index d81e3438..fc3963ac 100644 --- a/.github/workflows/release-tauri.yml +++ b/.github/workflows/release-tauri.yml @@ -1,20 +1,21 @@ name: Release Tauri (cross-platform) # meta: ensure Actions indexes this workflow on forks (no behavior change). -# 触发条件: -# - 推 v*.*.*-tauri 形式的 tag(与老 Swift 版的 vX.Y.Z 区分开,不冲突) -# - 手动 dispatch(用于测试构建,不发版) +# Triggers: +# - push a v*.*.*-tauri tag (kept distinct from the legacy Swift vX.Y.Z tags, no conflict) +# - manual dispatch (for test builds, no release) # -# 输出: -# macOS arm64/x64 .dmg + Windows x64 .msi/.exe,自动作为 GitHub Release 资产上传。 -# Linux egui 由 release-linux-egui.yml 独立构建;本工作流不编译 Linux/Tauri。 +# Outputs: +# macOS arm64/x64 .dmg + Windows x64 .msi/.exe, uploaded automatically as GitHub Release assets. +# Linux egui is built separately by release-linux-egui.yml; this workflow does not build Linux/Tauri. # -# macOS 分发: -# - 配好 APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD / APPLE_ID / -# APPLE_PASSWORD / APPLE_TEAM_ID 后,Tauri 会做 Developer ID 签名和公证。 -# 用户从浏览器下载后不需要手工 xattr。 -# - 未配置 Apple secrets 时自动回退 ad-hoc 签名,GitHub Actions 会打印 warning。 -# - Windows 没签名(无证书),Win 11 SmartScreen 会警告 "未识别的发布者",用户点"仍要运行"。 -# - 任意一个 platform 失败不影响另一个继续构建(fail-fast: false)。 +# macOS distribution: +# - With APPLE_CERTIFICATE / APPLE_CERTIFICATE_PASSWORD / APPLE_ID / +# APPLE_PASSWORD / APPLE_TEAM_ID configured, Tauri performs Developer ID signing and +# notarization; users downloading from a browser do not need manual xattr. +# - Without Apple secrets, falls back to ad-hoc signing; GitHub Actions prints a warning. +# - Windows is unsigned (no certificate); Win 11 SmartScreen warns "Unknown publisher" and the +# user clicks "Run anyway". +# - One platform failing does not stop the other from building (fail-fast: false). on: push: @@ -28,7 +29,8 @@ on: options: [all, macos] default: all -# 同一 tag 重复推送只跑最新一次;workflow_dispatch 用 run_id 隔离避免互相取消。 +# Repeated pushes of the same tag run only the latest run; workflow_dispatch isolates by run_id +# to avoid mutual cancellation. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }} cancel-in-progress: ${{ github.event_name == 'push' }} @@ -48,18 +50,19 @@ jobs: env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - # 渠道由 tag 后缀决定: + # Channel is decided by the tag suffix: # v-beta-tauri / v-Beta.N-tauri - # → beta 渠道(GitHub Release 标 prerelease,manifest 文件名带 -beta 后缀, - # 正式版用户的 endpoint 拿不到) - # v-tauri → stable 渠道(正式版,文件名沿用旧约定,向后兼容) - # workflow_dispatch / 非 tag 触发时 github.ref_name 不是 tag 字符串, - # endsWith 返回 false,回退为 stable,不改变现有 dispatch 行为。 + # -> beta channel (GitHub Release marked prerelease, manifest filename + # carries a -beta suffix so stable users' endpoint never sees it) + # v-tauri -> stable channel (regular release; filenames follow the old + # convention for backward compatibility) + # On workflow_dispatch / non-tag triggers github.ref_name is not a tag string, endsWith + # returns false, and it falls back to stable — dispatch behavior unchanged. OPENLESS_RELEASE_CHANNEL: ${{ (endsWith(github.ref_name, '-beta-tauri') || contains(github.ref_name, '-Beta.')) && 'beta' || 'stable' }} steps: - uses: actions/checkout@v4 with: - # MLX 子模块只在 macOS 发布构建需要。 + # The MLX submodule is only needed for macOS release builds. submodules: ${{ startsWith(matrix.platform, 'macos') && 'recursive' || 'false' }} - name: Disable macOS-only Qwen3 MLX dependency @@ -76,7 +79,8 @@ jobs: with: targets: ${{ matrix.rust-target }} - # 在恢复缓存前设置实际编译环境,让 cache key 包含 macOS profile。 + # Set up the actual build environment before restoring caches so the cache key includes + # the macOS profile. - name: Configure macOS build environment if: startsWith(matrix.platform, 'macos') working-directory: openless-all/app @@ -199,12 +203,12 @@ jobs: fi done - # ── macOS:用我们自己的 build-mac.sh,统一处理签名、公证和 artifact 清理 ── + # ── macOS: use our own build-mac.sh, handling signing, notarization, and artifact cleanup ── - name: Build (macOS) if: startsWith(matrix.platform, 'macos') working-directory: 'openless-all/app' env: - INSTALL: '0' # CI 不要装到 /Applications,也不要 reset TCC + INSTALL: '0' # CI must not install to /Applications or reset TCC TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: bash scripts/build-mac.sh @@ -217,9 +221,9 @@ jobs: path: openless-all/app/src-tauri/target/cargo-timings/*.html if-no-files-found: ignore - # ── Windows:先 build OpenLessIme.dll(x64+x86),再跑 tauri bundle。 - # openless-ime.wxs 用 $(env.OPENLESS_IME_DLL_X64) / _X86 拿绝对路径, - # 跨 candle/light cwd 都能 resolve(Tauri wix bundler cwd 不固定)。 + # ── Windows: build OpenLessIme.dll (x64+x86) first, then run tauri bundle. + # openless-ime.wxs reads absolute paths from $(env.OPENLESS_IME_DLL_X64) / _X86, + # resolvable across candle/light cwd changes (Tauri's wix bundler cwd is not fixed). - name: Build Windows IME native DLLs if: matrix.platform == 'windows-latest' shell: pwsh @@ -243,20 +247,22 @@ jobs: "$($t.EnvName)=$dll" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 Write-Host "[ok] built $dll (exported $($t.EnvName))" - # bundle.resources 引用的是 src-tauri/openless-ime-payload/{x64,x86}/OpenLessIme.dll - # (仓库里 commit 的是 0 字节占位让 mac 本地 build 也能 resolve)。 - # 这里用真 dll 覆盖占位,让 NSIS / MSI 都装上真文件;NSIS hook 的 regsvr32 - # 同时会把 64 / 32 位 COM 注册到 HKLM\Software\Classes\CLSID 的 - # KEY_WOW64_64KEY / KEY_WOW64_32KEY 两侧(windows_ime_profile.rs 都会查)。 + # bundle.resources references src-tauri/openless-ime-payload/{x64,x86}/OpenLessIme.dll + # (the repo commits 0-byte placeholders so mac local builds can also resolve). + # Overwrite the placeholders with the real dlls so NSIS / MSI install real files; + # the NSIS hook's regsvr32 also registers the 64/32-bit COM classes under + # HKLM\Software\Classes\CLSID on both the KEY_WOW64_64KEY / KEY_WOW64_32KEY + # views (both are checked by windows_ime_profile.rs). $payloadDir = Join-Path $appRoot "src-tauri\openless-ime-payload\$($t.Folder)" New-Item -ItemType Directory -Force -Path $payloadDir | Out-Null Copy-Item -Force -Path $dll -Destination (Join-Path $payloadDir 'OpenLessIme.dll') Write-Host "[ok] copied real $($t.Folder) dll into bundle.resources payload path" } - # ── Windows tauri build:保持 bash shell,因为 PowerShell 调外部命令 - # 会把 '{"bundle":...}' 的内部双引号吃掉、让 tauri 收到无效 JSON。 - # 用 set +e + GITHUB_ENV 把 exit code 传到下一步给 Repair 判断。 + # ── Windows tauri build: keep the bash shell, because PowerShell invoking external + # commands strips the inner double quotes of '{"bundle":...}' and tauri receives + # invalid JSON. + # Use set +e + GITHUB_ENV to pass the exit code to the next step for Repair. - name: Build (Windows) if: matrix.platform == 'windows-latest' shell: bash @@ -276,13 +282,13 @@ jobs: msi_supported=0 echo "[info] Skipping MSI bundle for non-numeric prerelease version $app_version." fi - # 拆两轮跑:Tauri 的签名 / updater artifact 阶段是 post-bundle 钩子, - # 任意 bundler 失败会让 *所有* bundle 的 .sig 跳过。MSI 必踩 ICE80, - # 所以单一 `tauri build` 永远拿不到 NSIS 的 .exe.sig。 - # Pass 1:NSIS 独立跑——必须成功,产出 *_x64-setup.exe(.sig) 给 updater。 - # Pass 2:MSI 独立跑——允许失败,Repair 步骤兜底 light.exe 重链。 - # Beta 版跳过 MSI:Windows Installer / WiX 无法表示 `1.2.3-Beta.1` - # 这类非纯数字 prerelease 标识,稳定版仍然照常产出 MSI。 + # Run in two passes: Tauri's signing / updater artifact stage is a post-bundle hook, + # and any bundler failure makes *all* bundles skip their .sig. MSI always hits ICE80, + # so a single `tauri build` can never produce the NSIS .exe.sig. + # Pass 1: NSIS alone — must succeed, producing *_x64-setup.exe(.sig) for the updater. + # Pass 2: MSI alone — allowed to fail; the Repair step re-links with light.exe. + # Beta skips MSI: Windows Installer / WiX cannot represent non-numeric prerelease + # identifiers like `1.2.3-Beta.1`; stable still produces MSI as usual. if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then npm run tauri -- build --bundles nsis --config '{"bundle":{"createUpdaterArtifacts":true}}' nsis_exit=$? @@ -309,17 +315,18 @@ jobs: fi fi echo "TAURI_BUILD_EXIT=$msi_exit" >> "$GITHUB_ENV" - # NSIS 是 updater 的硬依赖,挂了就直接 fail step。 + # NSIS is a hard dependency of the updater; fail the step outright if it fails. if [ "$nsis_exit" -ne 0 ]; then echo "::error::NSIS bundle failed (exit $nsis_exit) — updater artifact unavailable." exit 1 fi - # MSI 失败不挡——下一步 Repair 用 light.exe 重链(带 -sice:ICE80)。 + # MSI failure does not block — the next Repair step re-links with light.exe (with -sice:ICE80). exit 0 - # ── 如果 tauri 在 wix link 阶段失败(candle 出了 wixobj,但 light 因 cwd - # 解析 wxs Source 找不到 IME DLL),从 appRoot 手动跑 light 兜底重链。 - # 这是本地 windows-package-msvc.ps1::Repair-TauriMsiBundle 的同款手术。 + # ── If tauri fails at the wix link stage (candle produced wixobj, but light cannot find + # the IME DLL because of cwd-relative wxs Source resolution), manually run light from + # appRoot to re-link. Same surgery as the local windows-package-msvc.ps1 + # ::Repair-TauriMsiBundle. - name: Repair Windows MSI if Tauri failed at WiX link if: matrix.platform == 'windows-latest' shell: pwsh @@ -359,10 +366,10 @@ jobs: $msiPath = Join-Path $bundleDir "OpenLess_${version}_x64_en-US.msi" Push-Location $appRoot try { - # -sice:ICE80:x86 IME DLL 与 x64 一同打进 INSTALLDIR\windows-ime\, - # 这是 32 位组件落在 64 位 Directory 下的合法场景(DLL 路径绝对指向, - # 不依赖 SysWOW64 重定向)。Tauri 自身没有暴露 light 透传参数,所以 - # 必须在这里抑制,否则 LGHT0204 必失败。 + # -sice:ICE80: the x86 IME DLL is bundled alongside x64 under INSTALLDIR\windows-ime\; + # a 32-bit component under a 64-bit Directory is legitimate here (the DLL path is + # absolute and does not rely on SysWOW64 redirection). Tauri exposes no pass-through + # for light arguments, so ICE80 must be suppressed here or LGHT0204 always fails. & $light -nologo -sice:ICE80 -ext WixUIExtension -ext WixUtilExtension -loc $locale -out $msiPath $mainObj $imeObj if ($LASTEXITCODE -ne 0) { throw "light.exe relink failed with exit $LASTEXITCODE" } } finally { @@ -414,26 +421,28 @@ jobs: OPENLESS_UPDATE_ARCH: ${{ matrix.updater-arch }} OPENLESS_UPDATE_REPO: Open-Less/openless OPENLESS_UPDATE_MIRROR_BASE_URL: https://fastgit.cc/https://github.com - # beta 渠道时输出 latest-{tgt}-{arch}-beta.json,stable 沿用旧文件名。 + # On the beta channel output latest-{tgt}-{arch}-beta.json; stable keeps the old filename. OPENLESS_RELEASE_CHANNEL: ${{ env.OPENLESS_RELEASE_CHANNEL }} - # Beta 渠道里脚本要把 manifest.url 写成 releases/download//... - # 而不是 releases/latest(后者永远 = Stable,Beta 用户按 url 下载会拉到错文件)。 - # workflow_dispatch 时 github.ref_name 不是 tag——但那种情况下 channel=stable, - # 脚本不会读这个字段,安全。 + # On the beta channel the script must write manifest.url as releases/download//... + # rather than releases/latest (the latter is always = Stable, so beta users downloading + # via url would fetch the wrong file). + # On workflow_dispatch github.ref_name is not a tag — but then channel=stable and the + # script does not read this field, so it is safe. OPENLESS_RELEASE_TAG: ${{ github.ref_name }} run: node scripts/write-updater-manifest.mjs - # ── 收集产物 ── + # ── Collect artifacts ── - name: List artifacts (debug) shell: bash working-directory: 'openless-all/app/src-tauri/target/release/bundle' run: ls -la macos/ dmg/ nsis/ msi/ 2>/dev/null || true - # 防御性步骤:剥掉 macOS 产物上任何残留扩展属性 / quarantine。 - # 理论上 GitHub Actions 输出的 .app/.dmg 不会带 com.apple.quarantine - # (xattr 也不会通过 actions/upload-artifact 跨机器持久化),但保留这一步 - # 让"云端 artifact 一定干净"成为可验证的承诺。用户下载后再被本地浏览器 - # 加 quarantine 时,按 release notes 的 `xattr -cr` 一行即可消除。 + # Defensive step: strip any residual extended attributes / quarantine from macOS bundles. + # In theory GitHub Actions output .app/.dmg never carries com.apple.quarantine (and xattr + # does not persist across machines via actions/upload-artifact), but keeping this step + # makes "cloud artifacts are always clean" a verifiable promise. If the user's local + # browser adds quarantine after download, the `xattr -cr` line in the release notes + # removes it. - name: Strip xattr / quarantine on macOS bundles if: startsWith(matrix.platform, 'macos') shell: bash @@ -490,11 +499,11 @@ jobs: openless-all/app/src-tauri/target/release/bundle/latest-windows-x86_64*.json if-no-files-found: error - # ── tag 推送时,同步上传到 GitHub Release ── - # 只有 leader job (darwin/aarch64) 把 release body 写到文件,其余 matrix job - # 的 body_path 留空,softprops/action-gh-release@v2 在 body 为空时会保留 - # existing release body 不动,避免每个 matrix job 都 append 一遍同样的 prelude - # 导致 release notes 重复 N 次。 + # ── On tag push, also upload to the GitHub Release ── + # Only the leader job (darwin/aarch64) writes the release body to a file; the other matrix + # jobs leave body_path empty. softprops/action-gh-release@v2 keeps the existing release + # body when body is empty, so each matrix job does not append the same prelude again and + # duplicate the release notes N times. - name: Prepare release body prelude if: matrix.updater-target == 'darwin' && matrix.updater-arch == 'aarch64' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri') shell: bash @@ -512,13 +521,12 @@ jobs: ### 渠道说明 - - 以 `-tauri` 结尾的 tag 是**正式版**(自动推送给所有 in-app 检查更新的用户)。 - - 以 `-beta-tauri` 结尾或包含 `-Beta.N-tauri` 的 tag 是 **Beta 版**(GitHub 标 pre-release,**不**通过 in-app updater 推送给正式版用户;只对在「设置 → 关于 → 加入 Beta 渠道」开关切到 Beta 的用户可见,且需要手动从此页面下载安装)。 + - `vX.Y.Z-tauri` 是**正式版**,使用 Stable 更新源。 + - `vX.Y.Z-Beta.N-tauri` 是 **Beta 版**(GitHub pre-release)。在「设置 → 关于与更新 → 加入 Beta 频道」选择 Beta 更新源,也可从本页下载安装包。历史 `-beta-tauri` 标签继续兼容,Beta 不推送给 Stable 用户。 ### 行为变更提示 - - 流式输入默认开启;不兼容场景会自动回落到一次性插入。可在「设置 → 高级」关闭。 - - 流式输入成功后默认把最终文本同步到剪贴板,方便再次粘贴;可在「设置 → 高级」关闭。 + - 输入方式、流式输入与剪贴板选项位于「设置 → 录音与输入」,按平台能力显示。 EOF echo "OPENLESS_RELEASE_BODY_PATH=$RUNNER_TEMP/release-body.md" >> "$GITHUB_ENV" @@ -531,17 +539,17 @@ jobs: # Keep Beta assets private until all platforms have built and the # administrator has verified the downloadable packages. draft: ${{ env.OPENLESS_RELEASE_CHANNEL == 'beta' }} - # beta 渠道的 release 必须标 prerelease=true:GitHub UI 会折叠它, - # 普通用户看不到;同时只上传 latest-*-beta.json,正式版用户的 - # endpoint(latest-*.json)永远不会被覆盖,保证 Beta 不溢出正式版。 + # Beta releases must be marked prerelease=true: GitHub UI collapses them and regular + # users do not see them; only latest-*-beta.json is uploaded, so stable users' + # endpoint (latest-*.json) is never overwritten and beta never leaks into stable. prerelease: ${{ env.OPENLESS_RELEASE_CHANNEL == 'beta' }} - # 非 leader job 的 OPENLESS_RELEASE_BODY_PATH 是空字符串,softprops 在 - # body 为空时走 `body || existing.body` 分支保留既有内容,不会覆盖。 - # leader job 用默认 append_body=false,每次完整覆盖 release body 为 - # "prelude + generated notes",re-run 同一 tag 时也保持 idempotent - # (append_body=true 会让 re-run 把上轮 body 拼到前面、再次复制)。 + # Non-leader jobs have OPENLESS_RELEASE_BODY_PATH as an empty string; softprops keeps + # the existing content via the `body || existing.body` branch when body is empty. + # The leader job uses the default append_body=false and fully overwrites the release + # body with "prelude + generated notes" each time, staying idempotent on re-runs of + # the same tag (append_body=true would prepend the previous body again and duplicate). body_path: ${{ env.OPENLESS_RELEASE_BODY_PATH }} - # generate_release_notes 也只在 leader 跑,避免 matrix jobs 重复生成。 + # generate_release_notes also runs only on the leader, avoiding duplicates from matrix jobs. generate_release_notes: ${{ matrix.updater-target == 'darwin' && matrix.updater-arch == 'aarch64' }} files: | openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg @@ -553,14 +561,16 @@ jobs: openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig openless-all/app/src-tauri/target/release/bundle/latest-*.json - # ── 正式版发布后,自动更新 Homebrew cask ── - # 为什么放进这条流水线,而不是单独的 `release: published` 工作流:softprops 用默认 - # GITHUB_TOKEN 创建的 Release 不会触发 `release` 事件的其它工作流(GitHub 防递归), - # 所以独立的 update-cask 工作流永远不会被自动触发(历史上只在一次手动场景跑过且失败)。 - # 用 needs: build 串在构建之后、同一条流水线里,才能保证每次正式发版都自动更新 cask。 + # ── After a stable release, update the Homebrew cask automatically ── + # Why in this pipeline instead of a separate `release: published` workflow: a Release created + # by softprops with the default GITHUB_TOKEN does not trigger other workflows on the `release` + # event (GitHub's anti-recursion rule), so a standalone update-cask workflow would never fire + # automatically. Chaining it with needs: build in the same pipeline is the only way to update + # the cask on every stable release. # - # 仅正式版:v*-tauri 且非 -beta-tauri。beta 不碰 Homebrew,避免把预发布版推给 - # `brew install --cask openless` 的用户。cask 文件在默认分支(beta)上,提交回该分支。 + # Stable only: v*-tauri and not -beta-tauri. Beta does not touch Homebrew, avoiding pushing a + # prerelease to `brew install --cask openless` users. The cask lives on the default branch + # (beta); commits go back to that branch. update-homebrew-cask: name: Update Homebrew cask (stable only) needs: build @@ -590,7 +600,8 @@ jobs: version="${TAG#v}" version="${version%-tauri}" - # 直接下载 DMG 自己算 sha256,不依赖 GitHub 资产 digest 字段的时序/可用性。 + # Download the DMGs directly and compute sha256 ourselves; do not depend on the + # timing/availability of GitHub's asset digest fields. mkdir -p "$RUNNER_TEMP/dmg" gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \ --dir "$RUNNER_TEMP/dmg" \ @@ -614,14 +625,16 @@ jobs: run: | set -euo pipefail cask="Casks/openless.rb" - # ⚠️ cask 第 2 行 `arch arm: "aarch64", intel: "x64"` 也含 `intel: "..."`, - # 裸 `s/intel: "..."/` 会把 arch 指令一起改成哈希 → Intel 用户 brew 装包 404。 - # 所以 intel 的 sed 用 `^[[:space:]]*intel:` 锚定 sha256 块那一行;arm 的 sed - # 靠 `sha256 arm: ` 前缀天然只命中 sha256 行。捕获组 \1 保留原缩进。 + # Warning: line 2 of the cask, `arch arm: "aarch64", intel: "x64"`, also contains + # `intel: "..."`. A bare `s/intel: "..."/` would rewrite the arch instruction with a + # hash too -> brew installs 404 for Intel users. So the intel sed anchors the sha256 + # line with `^[[:space:]]*intel:`, while the arm sed naturally matches only the sha256 + # line via the `sha256 arm: ` prefix. Capture group \1 preserves the original indent. sed -i "s/version \"[^\"]*\"/version \"$VERSION\"/" "$cask" sed -i "s/sha256 arm: \"[^\"]*\"/sha256 arm: \"$ARM_SHA\"/" "$cask" sed -i "s/^\([[:space:]]*\)intel: \"[^\"]*\"/\1intel: \"$INTEL_SHA\"/" "$cask" - # 防呆:arch 指令必须原样保留,且本次 sha256 必须确实写进去了,否则让 job 失败。 + # Guard: the arch instruction must remain untouched and both new sha256 values must + # actually be written, otherwise fail the job. grep -q 'arch arm: "aarch64", intel: "x64"' "$cask" \ || { echo "::error::arch 指令被 sed 误伤"; exit 1; } grep -q "\"$ARM_SHA\"" "$cask" && grep -q "\"$INTEL_SHA\"" "$cask" \ @@ -641,6 +654,6 @@ jobs: echo "cask 已是 $VERSION,无需提交" exit 0 fi - # [skip ci]:cask 文本改动不需要再跑一遍跨平台 ci.yml。 + # [skip ci]: cask text changes do not need another cross-platform ci.yml run. git commit -m "[cask] openless $VERSION (auto from release) [skip ci]" git push diff --git a/.gitignore b/.gitignore index e93cb50e..d859d8c6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ promo-openless/ promo-openless-v2/ docs/old-promo/ .worktrees/ +openless-all/app/test-results/ +openless-all/app/playwright-report/ # 宣传录屏 / 素材根目录(GB 级二进制,绝不入版本库) video-materials/ @@ -85,6 +87,10 @@ docs/plans/ /AGENTS.md /issue-*-plan.md +# 本地会话记录(Claude Code 对话存档),仅供本人下次取用,不入库 +/SESSION_NOTES.md +/SESSION_TRANSCRIPT.jsonl + # .github 内部协作 + 审计报告(ISSUE_TEMPLATE / workflows / pr template 仍保留发布) .github/COOPER_*.md .github/BUILD_TEST_REPORT.md diff --git a/README.md b/README.md index 4619efc3..9844e955 100644 --- a/README.md +++ b/README.md @@ -207,22 +207,22 @@ Every item below is one more layer sedimented into a default — a capability yo - **Toggle and push-to-talk** recording modes, plus a **MediaPlayPause trigger** so wired-earbud inline controls can start and stop recording. `Esc` cancels at any phase, including polish and insertion. - **Cloud ASR**: Volcengine streaming ASR (bigasr), Tencent Cloud Hunyuan realtime ASR (Hy-ASR), iFlytek realtime ASR (RTASR), Alibaba Cloud Bailian (classic realtime / Qwen3 realtime / Fun-ASR-Flash file transcription), StepFun StepAudio (batch + realtime), Zhipu GLM-ASR, Xiaomi MiMo ASR, OrcaRouter audio-input Gemini, ElevenLabs Scribe, OpenAI-compatible batch transcription (OpenAI Whisper / Groq / SiliconFlow SenseVoice / OpenRouter / ZenMux), and Apple Speech (macOS). - **Local ASR**: bundled Qwen3-ASR (0.6B / 1.7B) via vendored `Open-Less/qwen-asr` (macOS); Windows Foundry Local Whisper and sherpa-onnx (experimental) variants. -- **Polish providers**: Ark (Volcengine), DeepSeek, OpenAI, Google Gemini, Codex OAuth, SiliconFlow, Atlas Cloud, Xiaomi MiMo, Tencent Cloud TokenHub, CometAPI, OpenRouter, Requesty, OrcaRouter, Alibaba Cloud Coding Plan, CodingPlanX, MiniMax, StepFun, and OpenCode Zen — plus any OpenAI-compatible endpoint you bring. -- **Four output modes**: raw, light polish, structured (**AI-prompt mode**), and formal. Plus a **translation hotkey** that converts speech directly into the configured target language ([#43](../../issues/43)). -- **Selection-ask QA panel** — a separate hotkey opens a floating panel that runs voice Q&A against the highlighted text in any app ([#118](../../issues/118)). +- **Polish providers**: Ark (Volcengine), DeepSeek, OpenAI, Google Gemini, Codex OAuth, SiliconFlow, Atlas Cloud, Xiaomi MiMo, Tencent Cloud TokenHub, CometAPI, OpenRouter, Requesty, API Route, OrcaRouter, Alibaba Cloud Coding Plan, CodingPlanX, MiniMax, StepFun, and OpenCode Zen — plus any OpenAI-compatible endpoint you bring. +- **Four output modes**: raw, light polish, structured (**AI-prompt mode**), and formal. Plus a **translation hotkey** that converts speech directly into the configured target language ([#43](https://github.com/Open-Less/openless/issues/43)). +- **Selection-ask QA panel** — a separate hotkey opens a floating panel that runs voice Q&A against the highlighted text in any app ([#118](https://github.com/Open-Less/openless/issues/118)). - **Main window**: Overview / History / Vocab / Style / Marketplace / Settings. Persistent tray icon, plus a mini status capsule that floats on screen and follows the display you are typing on (multi-monitor). - **Local model management** — manage on-disk local-ASR model storage from Settings. - **Multilingual UI** — Settings → Language switches between 简体中文 / 繁體中文 / English / 日本語 / 한국어 (auto-detected on first launch). - **In-app auto-update on the Tauri hosts** — Settings → About → Check; signed updater artifacts via the Tauri updater plugin on macOS, Windows, and Android. Linux deb/rpm packages have no in-app updater or AppImage manifest. -- **Beta channel (opt-in)** — Settings → About → Join Beta channel exposes the latest pre-release build for manual download. Beta releases never reach Stable users automatically (see [Contributing workflow](#contributing-workflow)). -- **Distribution channels** — direct DMG/EXE from [Releases](../../releases), Homebrew Cask (add the project tap first; see installation below), and a Windows installer. Linux deb/rpm packages attach to the shared Release only after device acceptance and an admin's release tag. +- **Beta channel (opt-in)** — Settings → About & updates → Join Beta channel selects the Beta updater feed; packages are also available from Releases. Beta releases never reach Stable users automatically (see [Contributing workflow](#contributing-workflow)). +- **Distribution channels** — direct DMG/EXE from [Releases](https://github.com/Open-Less/openless/releases), Homebrew Cask (add the project tap first; see installation below), and a Windows installer. Linux deb/rpm packages require independent device acceptance and an explicit administrator upload to an existing Release. - **Single-instance lock** — prevents two OpenLess processes from racing the same hotkey edge. - Dictionary entries are injected as Volcengine ASR `context.hotwords` and as semantic hints during polish; hits accumulate per session. - Platform-native global hotkey: CGEventTap on macOS, low-level keyboard hook (`WH_KEYBOARD_LL`) on Windows. ## Download & install (end users) -Go to [Releases](../../releases) and download: +Go to [Releases](https://github.com/Open-Less/openless/releases) and download: - **macOS**: `OpenLess__aarch64.dmg` (Apple Silicon) or `OpenLess__x64.dmg` (Intel). Open it, drag the app to `/Applications`, **then run the following once in Terminal to bypass the Gatekeeper "damaged" warning** (the build is ad-hoc signed, not Apple-notarized): ```bash @@ -373,16 +373,16 @@ The dictionary handles your proper nouns, product names, names of people, and ne - Manually adding the correct spelling, a category, and notes. You do not need to maintain misspellings or context hints. - Enabled entries are sent to the ASR provider that supports hotwords (Volcengine `context.hotwords`, StepFun `hotwords`, Whisper-compatible `prompt` — except ZenMux, whose JSON protocol does not carry `prompt`/`hotwords`, Bailian vocabulary ID) so they are recognized correctly during transcription. iFlytek realtime ASR has no request-level hotword parameter — configure personalized hotwords in the iFlytek console instead. - Entries are also injected into the polish prompt: the model decides per sentence whether to substitute. If "Cloud" clearly refers to the AI product `Claude` in context, it is corrected; if it genuinely means cloud computing, it is left as is. -- **The dictionary learns from you.** When you hand-correct a word OpenLess just typed, a card appears asking whether to remember it. One ✓ and it is in — no settings page, no forms. Every suggestion is reviewed by you: nothing is ever added silently. Requires the opt-in **cursor context** setting below, and is macOS-only for now. +- **Learn from corrections (experimental).** Open Settings → Experiments & extensions → Learn from corrections to enable it and configure observation duration (10–60 seconds, default 60), suggestion duration (5–60 seconds, default 10), and maximum automatic phrase length (2–32 characters, default 12). It defaults to off and is not enabled by cursor context or cloud sync. On macOS, Windows and Android, supported editors can be observed after insertion. Suggestions require confirmation before expiry to enter the dictionary. Changing parameters stops the current observation and clears pending suggestions; new values apply to the next dictation. Android requires accessibility. When observation is unavailable, use **Remember a word** in history details; Android IME result editing also offers an unchecked dictionary option. Every path requires explicit confirmation and does not create global replacement rules. - **Entries that earn their keep get priority.** The hotword budget sent to ASR providers is finite (a few hundred characters). Entries are ranked by hit count, with a few reserved seats for words you just added by hand, so the terms you actually use keep their place instead of being pushed out by whatever you added most recently. ### Cursor context (opt-in, macOS) Settings → Privacy → Data storage → **Cursor context**. Off by default. -When on, each dictation reads a few hundred characters around your cursor **in the app you are writing in** and sends them with the polish request, so the model knows what you are writing about. Chinese homophones (接口/借口, 大鱼/大禹) are indistinguishable to an acoustic model but obvious from context. This is also what makes dictionary learning possible: OpenLess can only notice that you fixed a word if it can see the text it just typed. +When on, each dictation reads a few hundred characters around your cursor **in the app you are writing in** and sends them with the polish request, so the model knows what you are writing about. Chinese homophones (接口/借口, 大鱼/大禹) are indistinguishable to an acoustic model but obvious from context. Local vocabulary learning has a separate switch and does not require cursor context. Observed text is not sent to a model; words explicitly added to the dictionary participate in future ASR and polish requests as described above. -What it never reads: password fields, macOS Secure Input, password managers, and terminals — those are blocked before a single accessibility call is made. While the setting is off, no accessibility calls happen at all and the prompt is byte-for-byte identical to a build without the feature. +Cursor context excludes password fields, macOS Secure Input, known password managers and terminals. Turning it off stops reading cursor context for polishing; other authorized accessibility features, including local vocabulary learning and insertion, work independently. Turning vocabulary learning off stops observation and clears pending suggestions. The main window is organized as Home / History / Dictionary / Settings. The Dictionary tab opens a separate editor window when you click "New". The Home tab shows total dictation time, total characters, average characters per minute, estimated time saved, and dictionary participation statistics. @@ -402,44 +402,20 @@ The `v-tauri` / `v-Beta.N-tauri` workflows build the macOS, Wi The dictation pipeline: `hotkey edge → Recorder.start + ASR.openSession → [audio frames] → hotkey edge → Recorder.stop + ASR.sendLastFrame → Polish → Insert → History.save`. -See [AGENTS.md](AGENTS.md) for repository rules and [Architecture](docs/architecture.md) for module responsibilities and wiring. +See [Contributing workflow](#contributing-workflow) and [Releasing](RELEASING.md) for contribution and release rules, and [Architecture](docs/architecture.md) for module responsibilities and wiring. ## Roadmap Planned but not yet shipped: -- Cross-session style memory: polish learns the user's tone over time ([#46](../../issues/46)). +- Cross-session style memory: polish learns the user's tone over time ([#46](https://github.com/Open-Less/openless/issues/46)). - Snippets (no UI or trigger logic yet). - History enhancements: copy button, search, re-polish, re-insert. - A "Paste last result" hotkey. ## Maintainer release checklist -OpenLess ships two release channels. The branch name equals the channel name (see [Contributing workflow](#contributing-workflow)). - -### Common prep (both channels) - -- Bump the Tauri application version in **all five** locations: `package.json`, `package-lock.json` (root + nested entry under `packages.""`), `src-tauri/tauri.conf.json`, `src-tauri/Cargo.toml`, and `src-tauri/Cargo.lock` (look for the `name = "openless"` block). CI's `Verify version sync` step will otherwise fail the build. The root `Cargo.lock` belongs only to `openless-core` and `openless-linux-egui`. -- Run `INSTALL=0 ./scripts/build-mac.sh` and confirm the `.app` launches. -- Smoke-test on a clean machine: permission flow, hotkey, recording, ASR, polish, insertion, and clipboard fallback. -- Confirm that `TAURI_SIGNING_PRIVATE_KEY` and (for macOS) the Apple signing/notarization secrets are set on the repo. - -### Beta channel — `v-beta-tauri` - -1. Land changes onto the `beta` branch via PR review. -2. Push the tag **on `beta`**: `git tag v-beta-tauri && git push origin v-beta-tauri`. -3. CI tags the GitHub Release as `Pre-release` and uploads only `latest-{tgt}-{arch}-beta.json` updater manifests. The Stable users' `releases/latest` redirect is unaffected. -4. Announce in the appropriate channel (issue thread, QQ group) that opt-in Beta users can grab it from Settings → About → Join Beta channel. - -### Stable channel — `v-tauri` - -1. Merge `beta → main` after the Beta release has soaked sufficiently (or run a final two-platform smoke build directly). -2. Push the tag **on `main`**: `git tag v-tauri && git push origin v-tauri`. -3. CI publishes a normal GitHub Release and uploads `latest-{tgt}-{arch}.json` (no `-beta` suffix). All Stable users receive the update through the in-app updater. - -### Post-release verification (always run) - -Follow [RELEASING.md](RELEASING.md) and verify the release page's pre-release flag, asset-filename channel correctness, Stable user flow, Beta opt-in flow, and raw update endpoints. +[RELEASING.md](RELEASING.md) is the canonical release procedure, including administrator authority, version synchronization, platform acceptance, draft assets, signing, and updater verification. New Beta tags use `v-Beta.-tauri`; the historical `-beta-tauri` suffix is compatibility-only. ## Acknowledgements diff --git a/README.zh.md b/README.zh.md index 7443e966..dbddbc37 100644 --- a/README.zh.md +++ b/README.zh.md @@ -204,6 +204,8 @@ OpenLess 只做一件事:**把语音变成可用的书面文字(尤其是 AI 提 ## 当前状态 +本 Fork 的 Android 输入法架构、四个面板的使用说明、截图和从 fork 基线开始的功能时间线,见 [Android 输入法:架构、功能与 fork 演进](docs/android-ime.md)。 + 下面每一项,都是一层已经沉降为默认、你授权一次之后就不必再操心的能力——这就是开屏之后你所站立的基础设施: - 共享 Rust 后端位于 `openless-core`。macOS 与 Windows 使用薄 Tauri 2 宿主和 React/TypeScript 前端;Android 暂时保留 Tauri mobile 宿主;Linux 使用独立原生宿主,不编译 Tauri 或 WebKitGTK。 @@ -212,22 +214,22 @@ OpenLess 只做一件事:**把语音变成可用的书面文字(尤其是 AI 提 - **切换式与按住说话(push-to-talk)** 两种录音模式,外加 **MediaPlayPause 触发**,让有线耳机的线控也能开始 / 停止录音。`Esc` 可在任意阶段取消,包括润色与插入。 - **云端 ASR**:Volcengine 流式 ASR(bigasr)、腾讯云混元实时 ASR(Hy-ASR)、讯飞实时语音转写(RTASR)、阿里云百炼(经典实时 / Qwen3 实时 / Fun-ASR-Flash 录音文件)、阶跃星辰 StepAudio(批式 + 实时)、智谱 GLM-ASR、小米 MiMo ASR、OrcaRouter 音频输入 Gemini、ElevenLabs Scribe、OpenAI 兼容批量转写(OpenAI Whisper / Groq / 硅基流动 SenseVoice / OpenRouter / ZenMux),以及 Apple Speech(macOS)。 - **本地 ASR**:通过 vendored 的 `Open-Less/qwen-asr` 内置 Qwen3-ASR(0.6B / 1.7B)(macOS);Windows 上的 Foundry Local Whisper 与 sherpa-onnx(实验性)变体。 -- **润色提供方**:Ark(火山方舟)、DeepSeek、OpenAI、Google Gemini、Codex OAuth、硅基流动、Atlas Cloud、小米 MiMo、腾讯云 TokenHub、CometAPI、OpenRouter、Requesty、OrcaRouter、阿里云 Coding Plan、CodingPlanX、MiniMax、StepFun、OpenCode Zen,以及你自带的任意 OpenAI 兼容端点。 -- **四种输出模式**:原文、轻度润色、结构化(**AI 提示词模式**)、正式。另有一个**翻译快捷键**,将语音直接转换为所配置的目标语言([#43](../../issues/43))。 -- **选区问答面板**——一个独立快捷键打开浮动面板,针对任意应用中被高亮选中的文本进行语音问答([#118](../../issues/118))。 +- **润色提供方**:Ark(火山方舟)、DeepSeek、OpenAI、Google Gemini、Codex OAuth、硅基流动、Atlas Cloud、小米 MiMo、腾讯云 TokenHub、CometAPI、OpenRouter、Requesty、API Route、OrcaRouter、阿里云 Coding Plan、CodingPlanX、MiniMax、StepFun、OpenCode Zen,以及你自带的任意 OpenAI 兼容端点。 +- **四种输出模式**:原文、轻度润色、结构化(**AI 提示词模式**)、正式。另有一个**翻译快捷键**,将语音直接转换为所配置的目标语言([#43](https://github.com/Open-Less/openless/issues/43))。 +- **选区问答面板**——一个独立快捷键打开浮动面板,针对任意应用中被高亮选中的文本进行语音问答([#118](https://github.com/Open-Less/openless/issues/118))。 - **主窗口**:概览 / 历史 / 词典 / 风格 / 市场 / 设置。常驻托盘图标,以及一个浮于屏幕、并跟随你正在输入的显示器的迷你状态胶囊(多显示器)。 - **本地模型管理**——在设置中管理本地 ASR 模型在磁盘上的存储。 - **多语言界面**——设置 → 语言 可在 简体中文 / 繁體中文 / English / 日本語 / 한국어 之间切换(首次启动自动检测)。 - **Tauri 宿主内自动更新**——macOS、Windows 与 Android 通过 设置 → 关于 → 检查 获取签名产物;Linux deb/rpm 不提供应用内更新或 AppImage 更新清单。 -- **Beta 频道(可选加入)**——设置 → 关于 → 加入 Beta 频道,可下载最新预发布版本进行手动安装。Beta 版本绝不会自动推送给 Stable 用户(见[贡献流程](#贡献流程))。 -- **分发渠道**——从 [Releases](../../releases) 直接下载 DMG/EXE、Homebrew Cask(须先添加项目 tap,见下方安装步骤)、Windows 安装包。Linux deb/rpm 在真机验收、管理员创建发版 tag 后附到共用 Release。 +- **Beta 频道(可选加入)**——设置 → 关于与更新 → 加入 Beta 频道,切换至 Beta 更新源;也可在 Releases 下载安装包。Beta 版本绝不会自动推送给 Stable 用户(见[贡献流程](#贡献流程))。 +- **分发渠道**——从 [Releases](https://github.com/Open-Less/openless/releases) 直接下载 DMG/EXE、Homebrew Cask(须先添加项目 tap,见下方安装步骤)、Windows 安装包。Linux deb/rpm 在独立真机验收后,由管理员把已验证的包附到已有 Release。 - **单实例锁**——防止两个 OpenLess 进程争抢同一个快捷键边沿。 - 词典条目注入到支持热词的 ASR 提供方(Volcengine 的 `context.hotwords`、StepFun 的 `hotwords`、Whisper 兼容的 `prompt`(ZenMux 除外——其 JSON 协议不携带 `prompt`/`hotwords`)、百炼的 vocabulary_id),并在润色时作为语义提示;命中次数按会话累计。讯飞实时语音转写标准版没有请求级热词参数,需在讯飞控制台配置个性化热词。 - 平台原生全局快捷键:macOS 上为 CGEventTap,Windows 上为低级键盘钩子(`WH_KEYBOARD_LL`)。 ## 下载与安装(终端用户) -前往 [Releases](../../releases) 下载: +前往 [Releases](https://github.com/Open-Less/openless/releases) 下载: - **macOS**:`OpenLess__aarch64.dmg`(Apple Silicon)或 `OpenLess__x64.dmg`(Intel)。打开后将应用拖入 `/Applications`,**然后在终端执行一次以下命令,以绕过 Gatekeeper 的“已损坏”提示**(该构建为 ad-hoc 签名,未经 Apple 公证): ```bash @@ -378,16 +380,16 @@ OpenLess 的润色模型只重塑文本。它不回答问题、不执行任务 - 手动添加正确拼写、分类与备注。你无需维护错误拼写或上下文提示。 - 启用的条目作为 Volcengine ASR 的 `context.hotwords` 发送,以便在转写时被正确识别。 - 条目同样注入润色提示词:模型逐句判断是否替换。如果“Cloud”在上下文中明显指 AI 产品 `Claude`,就会被纠正;如果它确实指云计算,则保持原样。 -- **词典会自己长。** 当你手动改掉 OpenLess 刚打出来的某个词,屏幕角落会弹一张小卡片问你要不要记住它。点一下勾就进去了——不用打开设置页,不用填表。**每一条都由你过目,没有任何东西是悄悄加进去的。** 需要开启下面的「光标上下文」,目前仅 macOS。 +- **手改学词(实验)。** 在「设置 → 实验与扩展 → 手改学词」进入独立配置页,设置观察时长(10–60 秒,默认 60)、建议保留时长(5–60 秒,默认 10)及自动建议最大词长(2–32 个字符,默认 12)。功能默认关闭,不跟随光标上下文或云同步授权。macOS、Windows 和 Android 可在支持的编辑器中观察插入后的修改,候选需在有效期内逐条确认才加入词典。修改参数会结束当前观察并清空待确认建议,下次听写生效。Android 需要无障碍服务。无法观察时,可在历史详情点击「记住词汇」手动输入正确词;Android 输入法编辑结果也提供默认不勾选的加入词典选项。所有入口均需明确确认,不自动创建全局替换规则。 - **真正在用的词优先。** 发给 ASR 的热词预算是有限的(几百字符)。条目按命中次数排序,并给刚手动添加的词留几个保底席位——这样你天天在用的那些词不会被「最近刚加的」挤出去。 ### 光标上下文(需手动开启,仅 macOS) 设置 → 隐私 → 数据存储 → **光标上下文**。默认关闭。 -开启后,每次听写会读取**你正在写的那个应用里**光标附近的几百个字,随润色请求一起发出,让模型知道你在写什么。中文同音词(接口/借口、大鱼/大禹)声学模型分不出来,但上下文能分。词典的自我学习也建立在这之上——OpenLess 只有看得见自己刚打出去的文字,才可能发现你把某个词改掉了。 +开启后,每次听写会读取**你正在写的那个应用里**光标附近的几百个字,随润色请求一起发出,让模型知道你在写什么。中文同音词(接口/借口、大鱼/大禹)声学模型分不出来,但上下文能分。手改学词是独立的本地功能,不需要开启此设置。观察文本本身不会发送给模型;确认加入词典的词会按词典规则参与后续 ASR/润色。 -**永远不读的地方**:密码输入框、macOS Secure Input、密码管理器、终端——这些在发出任何一次辅助功能调用之前就被拦下。开关关闭时,一次辅助功能调用都不会发生,提示词与没有这个功能的版本逐字节相同。 +光标上下文排除密码输入框、macOS Secure Input、已知密码管理器和终端。关闭此开关后不会为润色读取光标上下文;其他已授权的辅助功能(如手改学词或插入)独立工作。手改学词关闭后会停止观察并清空待确认建议。 主窗口组织为 首页 / 历史 / 词典 / 设置。点击“新建”时,词典页会打开一个独立的编辑窗口。首页展示总听写时长、总字数、平均每分钟字数、估算节省的时间,以及词典参与统计。 @@ -407,51 +409,20 @@ egui UI ── Linux Adapter(无 Tauri/WebKitGTK)───┘ 听写流水线:`hotkey edge → Recorder.start + ASR.openSession → [audio frames] → hotkey edge → Recorder.stop + ASR.sendLastFrame → Polish → Insert → History.save`。 -仓库规则见 [AGENTS.md](AGENTS.md),模块职责与接线见[架构文档](docs/architecture.md)。 +贡献与发布规则见[贡献流程](#贡献流程)和[发布规范](RELEASING.md),模块职责与接线见[架构文档](docs/architecture.md)。 ## 路线图 已规划但尚未发布: -- 跨会话风格记忆:润色随时间学习用户的语气([#46](../../issues/46))。 +- 跨会话风格记忆:润色随时间学习用户的语气([#46](https://github.com/Open-Less/openless/issues/46))。 - 片段(Snippets,尚无 UI 或触发逻辑)。 - 历史增强:复制按钮、搜索、重新润色、重新插入。 - “粘贴上次结果”快捷键。 ## 维护者发布清单 -OpenLess 提供两个发布频道。分支名即频道名(见[贡献流程](#贡献流程))。 - -### 通用准备(两个频道) - -- 在 Tauri 应用的**全部五个**位置提升版本号:`package.json`、`package-lock.json`(根级 + `packages.""` 下的嵌套条目)、`src-tauri/tauri.conf.json`、`src-tauri/Cargo.toml`,以及 `src-tauri/Cargo.lock`(查找 `name = "openless"` 块)。否则 CI 的 `Verify version sync` 步骤会使构建失败。根 `Cargo.lock` 只属于 `openless-core` 与 `openless-linux-egui`。 -- 运行 `INSTALL=0 ./scripts/build-mac.sh`,确认 `.app` 能启动。 -- 在干净的机器上做冒烟测试:权限流程、快捷键、录音、ASR、润色、插入,以及剪贴板回退。 -- 确认 `TAURI_SIGNING_PRIVATE_KEY` 以及(macOS 所需的)Apple 签名 / 公证密钥已在仓库中配置。 -- Android tag 发版还需配置:`ANDROID_KEYSTORE_BASE64`、`ANDROID_KEYSTORE_PASSWORD`、`ANDROID_KEY_ALIAS`、`ANDROID_KEY_PASSWORD`(release APK 签名;minisign 仍用 `TAURI_SIGNING_PRIVATE_KEY`)。 - -### Beta 频道 — `v-beta-tauri` - -1. 通过 PR 评审把改动落到 `beta` 分支。 -2. **在 `beta` 上**推送标签:`git tag v-beta-tauri && git push origin v-beta-tauri`。 -3. CI 会把该 GitHub Release 标记为 `Pre-release`,并上传 `latest-{tgt}-{arch}-beta.json` 与 `latest-android-{arch}-beta.json` 更新清单。Stable 用户的 `releases/latest` 跳转不受影响。 -4. 在合适的渠道(issue 讨论串、QQ 群)公告:可选加入的 Beta 用户可从 设置 → 关于 → 加入 Beta 频道 获取。 - -### Stable 频道 — `v-tauri` - -1. 在 Beta 发布充分沉淀后,将 `beta → main` 合并(或直接运行一次最终的双平台冒烟构建)。 -2. **在 `main` 上**推送标签:`git tag v-tauri && git push origin v-tauri`。 -3. CI 发布一个正常的 GitHub Release,并上传 `latest-{tgt}-{arch}.json` 与 `latest-android-{arch}.json`(无 `-beta` 后缀)。所有 Stable 用户都会通过应用内更新器获得更新。 - -### 发布后验证(始终执行) - -按 [RELEASING.md](RELEASING.md) 验证发布页的 pre-release 标记、产物文件名与渠道、Stable 用户流程、Beta 选择流程和更新端点,并额外核对 Android: - -1. Release 页面含 `latest-android-aarch64.json`(Stable 无 `-beta` 后缀混用)。 -2. Beta pre-release 含 `latest-android-aarch64-beta.json`,URL 指向具体 tag。 -3. 真机:设置 → 关于 → 检查更新 → 下载 → 系统安装器 → 版本号递增。 -4. Beta 开关:高级 → 加入 Beta → 手动/自动检查 Beta manifest。 -5. 原始端点与 mirror 端点均可访问。 +[RELEASING.md](RELEASING.md) 统一维护管理员权限、版本同步、平台验收、草稿资产、签名和更新清单校验。新 Beta 标签使用 `v-Beta.-tauri`,历史 `-beta-tauri` 后缀仅作兼容。 ## 致谢 diff --git a/RELEASING.md b/RELEASING.md index f84c6995..a35ab60b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,7 +7,7 @@ day-to-day development. **Only repository administrators may create version tags and publish releases.** -- Only an admin may create a release tag (`v*-tauri`) or publish a GitHub Release. Linux assets are attached automatically by the tag workflow after product acceptance. +- Only an admin may create a release tag (`v*-tauri`) or publish a GitHub Release. Linux assets require a separate accepted build and explicit administrator upload. - Contributors (including AI agents) **must not** create release tags, publish releases, or trigger release automation. If a release is needed, **request an admin to cut it** — open an issue or ping a maintainer with the target version and the @@ -43,13 +43,19 @@ component, which is a 16-bit field. The full version still appears in the app an updater manifest; NSIS uses its supported numeric fallback for file metadata. See [the pinned NSIS bundler](https://github.com/tauri-apps/tauri/blob/tauri-cli-v2.10.1/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs#L149). -These tags build the macOS, Windows, and Android Tauri hosts and the independent Linux egui host in parallel. Linux is not part of the Tauri matrix: `.github/workflows/release-linux-egui.yml` builds only deb/rpm, attaches them to the same Release draft and publishes no in-app updater manifest or AppImage. Linux product acceptance must be completed **before** an admin pushes the shared tag. CI runs the same Linux packaging and verification path on PRs without attaching release assets. +These tags build the macOS, Windows, and Android Tauri hosts in parallel. Linux is +independent: `.github/workflows/release-linux-egui.yml` is manual-only, runs the +same Core/host/deb/rpm verification as CI, and uploads Actions artifacts. It does +not react to Tauri tags or change a GitHub Release. After Linux product acceptance, +an administrator may run it against the accepted existing release tag, verify its +commit and package checksums, and explicitly attach the deb/rpm files to that +Release. Linux has no in-app updater manifest or AppImage. For each new Linux package candidate, increment `openless-all/app/linux-egui/package-revision` from the last `-N` suffix (for example `2.0.0-Beta.2-79` → `2.0.0-Beta.2-80`). Both local packaging and the shared CI/tag build use this explicit revision; never drop it or derive it from the Tauri tag, whose SemVer remains `2.0.0-Beta.2`. -Under the [2026-09-06 2.0 requirements](docs/2.0-requirements.md), Windows and macOS must fully retain their respective Tauri 1.x features. The egui team owns Linux Host/UI work and Linux product acceptance. Although Linux application gaps did not previously block Windows/macOS delivery, this shared-tag workflow now attaches Linux packages automatically: do not cut a shared release tag until Linux is accepted as well. Existing Android builds do not expand this scope into a new full-support commitment. +Under the [2026-09-06 2.0 requirements](docs/2.0-requirements.md), Windows and macOS must fully retain their respective Tauri 1.x features. The egui team owns Linux Host/UI work and Linux product acceptance. Linux application gaps do not block the Windows/macOS delivery, but shared Core defects do. Linux packages must not be attached before their own product acceptance. Existing Android builds do not expand this scope into a new full-support commitment. ## Version-sync gate @@ -80,24 +86,25 @@ The script takes a plain `X.Y.Z`; for a prerelease version such as 3. CI is green on the commit being tagged. 4. The applicable [desktop feature and device acceptance](docs/2.0-desktop-acceptance.md), signing, and distribution requirements are met; green builds alone do not establish product readiness. Linux acceptance below is required before including Linux assets. 5. Then, and only then, push the release tag. -6. Beta tag workflows upload Tauri, Android and Linux egui assets to a shared draft. - Wait for **all three** workflows to succeed, verify the packages, Linux deb/rpm - checksums and Beta updater manifests, then publish that draft as a prerelease. +6. Beta tag workflows upload Tauri and Android assets to a shared draft. + Wait for **both** workflows to succeed, verify the packages and Beta updater + manifests, then publish that draft as a prerelease. Do not rerun an asset workflow after publication without first returning the release to draft. -Before pushing a tag that will automatically attach Linux assets, additionally require all of the following: +Before independently attaching Linux assets, additionally require all of the following: 1. The egui team has completed the [Linux Host/UI gaps and acceptance](docs/linux-egui-handoff/07-acceptance.md). The existing `eframe::App` is a starting implementation; its presence and successful packaging alone do not establish product completeness. 2. Linux core/host tests, dependency gates, and secret-surface gates are green on Ubuntu. 3. The Linux workflow verifies ELF dependencies, deb/rpm contents, desktop metadata, fcitx5 plugin paths and package SHA-256 checksums. There is no AppImage, minisign key or Linux updater manifest. -4. The shared tag points to the same commit whose CI and Linux product acceptance were reviewed. A `workflow_dispatch` build only uploads Actions artifacts, never Release assets. +4. The manual build targets the same existing tag whose commit, CI and Linux product acceptance were reviewed. `workflow_dispatch` only uploads Actions artifacts; an administrator separately uploads the verified packages to that Release. +5. Attach the Linux checksum file as `SHA256SUMS-linux` so it does not replace an existing desktop/Android checksum file. ## Process summary 1. Land work on `beta` via PRs (open PRs against `beta`, never `main`). 2. For a Stable release, a maintainer merges `beta → main`. 3. An **admin** bumps the Tauri version (five-location sync), verifies CI is green, - and pushes the release tag, which triggers the macOS/Windows/Android and Linux - asset workflows (only supported platforms generate auto-update manifests). -4. After Linux product acceptance and the shared tag's Tauri, Android and Linux - workflows pass, an admin reviews all assets and publishes the shared draft. + and pushes the release tag, which triggers the macOS/Windows/Android asset + workflows and their channel-specific updater manifests. +4. After both release workflows pass, an admin verifies and publishes the shared + draft. Linux packages are attached independently only after Linux acceptance. diff --git a/docs/2.0-desktop-acceptance.md b/docs/2.0-desktop-acceptance.md index 069337c5..275a04bc 100644 --- a/docs/2.0-desktop-acceptance.md +++ b/docs/2.0-desktop-acceptance.md @@ -1,6 +1,6 @@ # 桌面验收清单(Windows / macOS / Android) -状态:要求清单,不是通过声明;更新:2026-09-08。每个功能域给出源码入口与必须提供的真实证据;自动化测试通过不等于设备验收通过。证据分级见[范围](2.0-requirements.md)第 4 节。 +状态:要求清单,不是通过声明;更新:2026-09-29。每个功能域给出源码入口与必须提供的真实证据;自动化测试通过不等于设备验收通过。证据分级见[范围](2.0-requirements.md)第 4 节。 ## 1. 功能域 → 源码入口 → 验收要求 @@ -14,7 +14,7 @@ | 历史 | `src/pages/History.tsx`、`src-tauri/src/commands/history.rs` | 列表/详情切换(窄窗)、重润色结果保留、录音归档与导出 | | 词库与纠错 | `src/pages/{Vocab,Corrections}.tsx`、`src-tauri/src/commands/dictionary.rs`、Core `vocabulary.rs` / `correction.rs` | 词库管理、纠错记录展示/删除/批量清理、重启后持久化与错误反馈 | | 风格包与市场 | `src/pages/{Style,Marketplace}.tsx`、`src-tauri/src/commands/{style_packs,marketplace}.rs` | 导入/应用/导入导出失败反馈;市场浏览/安装/OAuth | -| 划词与 QA | `src/pages/{SelectionAsk,SelectionPolishPreview,SelectionVoiceIntentPicker,QaPanel}.tsx` | 触发→意图→预览→应用/取消/撤回;epoch 防旧事件覆盖 | +| 划词与 QA | `src/pages/{SelectionAsk,SelectionVoiceIntentPicker,QaPanel}.tsx`、Core `selection_voice_service.rs` | 选区编辑与空选区起草→意图→预览→应用/取消/撤回;传统/Omni 录音;终态不被迟到音量覆盖 | | Less Computer | `src/pages/LessComputer{Panel,Glow}.tsx`、`src-tauri/src/coding_agent/` | 工具审批/输出/取消;Windows Job 对象与进程清理 | | 手机输入 | `src-tauri/src/remote_server/`、远程输入页面 | 配对/TLS/会话桥接/弱网与陈旧地址处理 | | 胶囊与多屏 | `src/components/Capsule.tsx`、`src-tauri/src/coordinator/capsule_focus.rs` | 跟随光标所在屏、多显示器/多 Space 正确显示 | diff --git a/docs/2.0-requirements.md b/docs/2.0-requirements.md index 6906355a..f66f0bbc 100644 --- a/docs/2.0-requirements.md +++ b/docs/2.0-requirements.md @@ -1,13 +1,13 @@ # OpenLess 2.0:范围与完成条件 -状态:canonical(2026-09-07 以源码为准重写);更新:2026-09-07。 +状态:canonical;更新:2026-09-29。 ## 1. 平台与版本锚点 | 目标 | 载体 | 版本锚点 | | --- | --- | --- | | Windows / macOS / Android | Tauri 2 Host + React 界面(`src-tauri/` + `src/`) | `src-tauri/tauri.conf.json` 与 `package.json` 版本须同步;2.0 起点为 `v2.0.0-Beta.1-tauri`;identifier `com.openless.app` | -| Linux | egui/eframe Host + 原生 UI(`linux-egui/`,crate `openless-linux-egui`) | crate 与发布版本跟随 Tauri 应用版本,见 `linux-egui/Cargo.toml` | +| Linux | egui/eframe Host + 原生 UI(`linux-egui/`,crate `openless-linux-egui`) | 内部 crate 版本为 0.1.0;发行包使用应用版本与 `linux-egui/package-revision`,以发布工作流为准 | | 共享业务 | `crates/openless-core`(平台无关,crate 版本 0.1.0) | 公开合同 `openless-all/app/contract/backend-2.0.json` | 显示版本与内部 crate 版本是两个概念;发布版本号、标签与渠道以仓库发布流程(`RELEASING.md`、GitHub workflows)为准。 @@ -25,7 +25,7 @@ | --- | --- | | Windows | [桌面验收清单](2.0-desktop-acceptance.md)各域生产接线 + 安装、目标应用与设备验收 | | macOS | 同上,另加 Intel/Apple Silicon、权限、输入源、本地模型、签名与公证 | -| Android | APK 工作流与悬浮窗管线按 [Android 计划](android-mobile-apk-overlay-plan.md) | +| Android | APK 工作流与悬浮窗管线按 [Android 计划](android-mobile-apk-overlay-plan.md),原生键盘与后台恢复按 [Android 输入法](android-ime.md) | | Linux(本批移交) | 可接入的 Core + 交接资料完整;应用侧缺口按[登记表](linux-egui-handoff/02-gap-register.md) L01–L12 推进,不阻塞 Windows/macOS 交付 | | 共享 Core | 合同、迁移、会话与取消语义可被 Linux 直接接入,见[契约](linux-egui-backend-contract.md) | diff --git a/docs/android-build-time-research-1103.md b/docs/android-build-time-research-1103.md deleted file mode 100644 index 7ba32ae6..00000000 --- a/docs/android-build-time-research-1103.md +++ /dev/null @@ -1,46 +0,0 @@ -# Android APK 编译耗时调研(#1103) - -调研日期:2026-09-25。基线:上游 beta `d9113e0b03c0b5998079d5f43dcb33fb8bba50e7`。原始调研与后续实现记录;Beta 3 整合时采用 ABI 并行与缓存回写方案。 - -## 实测证据 - -- [tag run 35989822568](https://github.com/Open-Less/openless/actions/runs/35989822568):job 32 分 32 秒;Build Android release APK 30 分 31 秒。 -- Cargo 完成记录为 6m01s、2m14s、5m11s、5m27s、5m37s。前两轮都为 aarch64,第二轮重新编译应用及部分 Tauri crates。重复编译事实已确认,失效原因尚未确认。 -- [同 SHA 的 beta dispatch 35985550453](https://github.com/Open-Less/openless/actions/runs/35985550453)也有五轮编译;arm64 第二轮 2m15s。 -- tag Gradle 日志明确 cache-read-only=true;仓库默认分支为 beta,beta dispatch 为 false。不能用 post 为零秒单独证明清盘阻止保存。 -- beta dispatch 在清盘后仍保存约 171 MB Rust cache,随后 tag 命中同一缓存。这不证明 target 编译产物被保留:workflow 明确提前删除 target、Cargo registry/git 和 Gradle caches。 -- 独立前端构建约 15 秒;Tauri beforeBuildCommand 随后再次执行 npm run build。 - -## 建议顺序 - -1. 修复清盘与缓存生命周期,并更换缓存版本键,避免继续命中已有残缺缓存;验证连续两次运行的实际缓存内容及编译耗时。post action 在普通 steps 之后执行,仅把清盘移动到普通 steps 末尾无效。 -2. 日常 dispatch 可选 ABI,默认 arm64;正式 tag 保留全 ABI。进一步使用 ABI matrix 降低全量墙钟,分别衡量总 runner 分钟和排队时间;发布资产集中汇总,校验 ABI 完整性。 -3. 记录 Cargo timings/fingerprint 和两轮 arm64 调用参数、环境、生成文件差异,定位重复编译。不要直接绕过 Tauri/Gradle native 构建。 -4. 当前 release 使用 opt-level=3、thin LTO、codegen-units=1。试验仅 dispatch 的快速 profile:关闭 LTO、提高 codegen-units,同时保持签名;单独比较 APK 大小、运行表现和耗时。正式 tag 优化配置暂不改变。 -5. ci-disable-macos-qwen3.mjs 每次删除平台依赖后 cargo generate-lockfile。应研究保留已锁定版本的确定性处理,避免无关依赖升级及缓存键变化;stable Rust 也应考虑固定版本并有计划升级。 -6. Cargo timings 若表明依赖或主 crate 占比高,再审计 Android 不使用的依赖/features、缩小重编译边界。现有 openless-core 可作为评估起点,不能未经测量就大改架构。 -7. 次要优化:删除重复前端构建,验证 Gradle task-output cache,更强的 x64 runner、预置工具链环境,以及 artifact 压缩参数。现有四份上传合计约 14 秒,优先级低。 - -## 限制与官方资料 - -- [Cargo profiles](https://doc.rust-lang.org/cargo/reference/profiles.html):LTO/codegen-units 是构建速度与产物表现之间的取舍,不能承诺未经实测的收益。 -- [rust-cache](https://github.com/Swatinem/rust-cache):默认排除 workspace crates、清理 incremental;缓存命中不等于应用无需重编译。 -- [sccache Rust](https://github.com/mozilla/sccache/blob/main/docs/Rust.md):不能缓存涉及系统链接的 cdylib 等输出,因此不是 Tauri 主库的万能缓存;可单独评估依赖缓存收益。 -- [Gradle Actions v4](https://github.com/gradle/actions/blob/v4/docs/setup-gradle.md)与[GitHub cache scope](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching):默认分支写缓存与 ref 可见性应一并设计。 -- [Gradle build cache](https://docs.gradle.org/current/userguide/build_cache.html):任务输出缓存和依赖缓存不同;仅缓存输入输出定义完整的任务。 -- [Android NDK host](https://developer.android.com/ndk/guides/other_build_systems):Linux 官方 NDK 使用 x86_64 host 工具链,不能因为目标 APK 是 arm64 就直接换 Linux arm64 runner。 -- [Tauri CLI](https://v2.tauri.app/reference/cli/):Android build 会执行 beforeBuildCommand。 - -下述基准是优化前的测量;实际加速效果须以整合后上游仓库的完整运行统计为准。基准应覆盖冷缓存、相同依赖下的源码改动、依赖变更,以及正式全 ABI 构建;同时检查签名、APK ABI 和 updater manifest 完整性。 - -## 实现备注(#1103 跟进) - -重复 aarch64 根因已定位:Tauri CLI `android build` 在 `apk::build` 之前会对**第一个** target 调用 `first_target.build(...)`(注释为 initialize plugins),随后 Gradle 再对每个 ABI 执行 `tauri android android-studio-script`。因此首个 ABI 必然两次 cargo;第二轮约 2m 是因为 `write_options` / `inject_resources` 发生在首次编译之后,指纹变脏。不要绕过 Gradle/native 路径;用单 ABI dispatch + 全 ABI matrix 并行降低墙钟。 - - -## Beta 3 发布整合 - -- 正式 tag 仍使用原来的 release profile,四 ABI 并行构建后统一签名与生成 Beta updater manifest;快速 profile 只允许手动验证运行。 -- Gradle 缓存回写前保留编译缓存,但生成的 Kotlin DSL 只读取环境变量,不写入签名密码或 alias。实际 release build 步骤注入凭据;keystore 权限保持 0600。 -- APK 收集器使用 runner 既有 Python 3 标准库校验 ZIP 和 CRC,并检查完整 ABI 目录集合;损坏、未知 ABI、多 ABI、重复或缺失产物均有回归测试。 -- 未合入 #1106 中额外的 Linux egui 测试修补;本轮没有 Linux 代码或发布工作流变更。 diff --git a/docs/android-ime.md b/docs/android-ime.md new file mode 100644 index 00000000..cb831be0 --- /dev/null +++ b/docs/android-ime.md @@ -0,0 +1,60 @@ +# OpenLess Android IME:架构、功能与 fork 演进 + +这份页面记录 Android 输入法分支从 `beta` fork 基线到当前实现的功能演进,并把界面截图与实际源码入口对应起来。它描述的是当前代码状态,不把构建成功等同于完整真机验收。 + +## 当前架构 + +用户操作进入 `OpenLessImeService`,它提供语音、笔画、剪贴板和英文四个输入面板,并通过 Android `InputConnection` 将结果提交到当前应用。Rust/Tauri 负责 ASR、AI 处理和共享后端;`OpenLessRuntimeService` 作为 `START_STICKY` 前台运行时监督器持有 JNI Activity Context;`OpenLessBackendWarmupActivity` 承载 Tauri/WebView 设置页与启动入口。 + +主要源码入口: + +- `openless-all/app/android/kotlin/OpenLessImeService.kt`:四面板切换、语音生命周期、剪贴板/英文面板与跨面板 UI 基础设施。 +- `openless-all/app/android/kotlin/StrokeInputController.kt`:笔画编码、候选/联想查询与提交、数字符号子面板;这是 2026-09-24 完成的第一处面板拆分。 +- `openless-all/app/android/kotlin/OpenLessRuntimeService.kt`:运行时守护和 JNI Context 挂载。 +- `openless-all/app/android/kotlin/OpenLessBackendWarmupActivity.kt`:Tauri host、设置页和 WebView 冷启动恢复。 +- `openless-all/app/src-tauri/src/android/native_bridge.rs`:Rust ↔ Kotlin JNI 桥接。 + +## 已实现的 Android 输入能力 + +- 语音输入:录音、取消、原文/整理模式、波形/状态反馈,并通过 `commitText()` 写入当前光标位置。 +- 笔画输入:离线笔画字典、单字候选、确认后的词语联想、分词、简繁偏好、数字/符号面板、上滑数字和个人词频。 +- 剪贴板:历史记录、收藏/删除/分类、选择/复制/粘贴、纠正词写入全局词典。 +- 手改学词:独立授权默认关闭,无障碍服务在有界观察期内报告当前编辑器的文本变化,主进程 Core 判断建议与过期时间;确认卡或编辑结果的显式勾选才加入词典。跨进程共享截止时间,服务重连不延长观察。 +- 英文键盘:字母/数字/符号三层布局、英文候选词、个人词频、自定义词长按删除、按键预览和上滑输入数字/符号。 +- 跨应用插入:按可用性使用无障碍、Shizuku 或剪贴板回退;权限和输入法启用状态在 Android 侧单独管理。 + +## 2026-09-11 至 2026-09-24 的演进 + +`615d6ac4` 是 fork 后开发使用的 `beta` 基线,`0bc3a979` 是第一笔 Android IME 实现。之后的提交按功能阶段演进: + +1. IME 服务骨架与四面板交互。 +2. 笔画字典、索引、候选排序、联想、多编码、分词、简繁和个人词频。 +3. 剪贴板历史、纠正词桥接、英文键盘、候选词和手势预览。 +4. Runtime Service、Activity 生命周期、JNI Context、WebView 黑屏/冷启动恢复和重启诊断。 +5. 2026-09-23 至 24:英文上滑数字/符号和长按删词;候选 View 复用;联想词典预热、缓存和 Trie 建索引优化;新增 `StrokeInputController`。 + +完整提交时间线可用以下命令复核: + +```text +git log --reverse --format='%h %ad %s' --date=short 615d6ac4..HEAD +``` + +## 截图与使用说明 + +![OpenLess Android 架构与使用指南](images/android-ime/android-architecture-guide.png) + +| 面板 | 截图 | +| --- | --- | +| 语音:待机、录音、思考 | ![语音待机](images/android-ime/voice-idle-green.jpg) ![录音](images/android-ime/voice-recording-red.jpg) ![思考](images/android-ime/voice-thinking.jpg) | +| 笔画:中文与数字/符号 | ![中文笔画](images/android-ime/stroke-chinese.jpg) ![数字面板](images/android-ime/stroke-numeric.jpg) | +| 剪贴板与英文 | ![剪贴板](images/android-ime/clipboard.jpg) ![英文键盘](images/android-ime/english.jpg) | + +## 小米/Redmi 真机边界 + +现有开发记录确认 APK 已安装到包含小米设备在内的测试设备;但候选栏复用、控制器拆分和英文/笔画手势仍需要完整真机回归。正式发布前应继续覆盖:正常输入、分词联想、退格、数字/符号切换、简繁切换、跨键滑动、后台恢复、Activity 被系统回收后的冷启动,以及不同 OEM 的插入权限路径。 + +## 相关文档 + +- [Android 源码架构与开发记录](../openless-all/app/android/README.md) +- [根 README 的 Android 状态说明](../README.md#android-input-method) +- [Android 架构总览](architecture.md) diff --git a/docs/architecture.md b/docs/architecture.md index 408387d1..04306b68 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # OpenLess 2.0 架构 -状态:canonical,当前实现说明;更新:2026-09-23。平台范围见 [2.0 需求](2.0-requirements.md),文件定位见 [目录结构](structure.md)。 +状态:canonical,当前实现说明;更新:2026-09-30。平台范围见 [2.0 需求](2.0-requirements.md),文件定位见 [目录结构](structure.md)。 ## 1. 分层与工作区 @@ -45,6 +45,12 @@ flowchart TB Android 悬浮窗在录音中转入追问时,Host 先捕获原选区,Core 的 `stop_dictation_for_qa` 按原会话 ID 完成转写并释放听写资源,跳过听写润色与文字插入,再由 `QaApi::submit_captured_text` 接收已录问题和选区。重复手势由 Host 的异步锁合并;QA 不重新抓取已经变化的前台选区。 +Android IME 通过 `InputConnection` 插入文字;笔画、英文候选与轻量拼音由原生 Kotlin 控制器处理。`OpenLessApplication`、`OpenLessRuntimeService` 和 Warmup Activity 管理后台运行与恢复。用户操作与验收入口见 [Android 输入法](android-ime.md)。 + +设置保存使用 `get_settings_snapshot` / `update_setting_fields`:前端只提交字段差异,Core 按偏好修订号提交事务并在版本冲突时重试;`prefs:changed` 使前端重新读取快照。`preferencesWriteGate.ts` 将尚未完成的本地编辑叠加到已确认快照,失败时只撤回对应请求。 + +手改学词由 Core 的 `host_document/observation.rs` 和 `api.rs` 管理插入范围、观察代数、建议有效期与词典写入。独立本机授权默认关闭,配置位于「实验与扩展 → 手改学词」;观察时长 10–60 秒、建议有效期 5–60 秒、最大自动词长 2–32 个字符。设置改变会结束旧观察并清除旧建议,授权与参数不进入云同步。macOS AX、Windows UIA 与 Android 无障碍仅在当前编辑器内进行短时观察,密码框和已知敏感应用被排除;观察文本仅用于本地差分,确认前不写入词典。`PendingCorrection.expiresAtMs` 是 Web 和 Android 确认卡共用的截止时间;历史详情与 Android IME 保留显式确认加词入口,Linux 原生观察仍在独立验收范围。 + ## 3. Core 模块地图(按域,见 `src/lib.rs` pub mod 清单) - 听写链路:`dictation_engine` / `dictation_context` / `audio` / `external_audio` / `silence_auto_stop` / `streaming_insert` / `hotkey_interpreter` / `voice_session` @@ -77,6 +83,12 @@ Less Computer 面板将听写与直接语音提交分开:麦克风把转写填 选区直接润色在捕获文字和原输入目标后显示处理中提示,重复快捷键的 Busy 返回不覆盖该提示。已有语音选区入口在松开快捷键后继续显示思考动画,直到处理/替换完成,或交给确认和预览面板。录音提示音的 Web Audio context 在恢复超时或音频时钟冻结时丢弃并最多重试一次,重试沿用原请求的取消和迟到边界。 +Selection Voice 在有效输入目标中允许空选区:Core 将无草稿编辑转换为 Compose,生成正文后沿用预览/直接插入路径;已有选区仍按编辑或问答处理。Host 的胶囊所有权与会话 ID 绑定,结束录音后停止接收音量帧,终态通过 Done/Idle 收尾。Selection Voice 和 Less Computer 的辅助录音遵循当前多模态偏好;Omni 将音频转换为指令文字,后续意图、编辑与 Agent 调用仍由各自模型完成。 + +AI 服务设置在顶部选择传统或多模态模式,各配置入口等宽排布并完整显示,不依赖横向滑动。传统模式禁用多模态模型入口;多模态模式禁用语言模型、语音识别和本地模型入口。禁用入口名称置灰,名称下方附醒目的黄/琥珀色“当前模式未使用”提示,鼠标和键盘均不可进入;连接与扩展保持可用。多模态表单将供应商/模型及自定义高级字段并排,拉取模型与验证集中在页尾;桌面常用窗口内单页显示完整表单,窄屏或放大字体时保留内容的可访问性。切换模式时若当前页面已禁用,则转到当前模式可用的配置页;本地模型的内部跳转入口受相同限制。切换模式保留两组渠道和凭据。Omni 连接测试只验证文本请求,实际音频可用性需录音验证;DashScope 音频使用 `data:;base64,` 和独立 `format` 字段。API Route 作为可手动添加的 OpenAI 兼容润色渠道,不改变默认提供方。 + +界面动效由 `src/lib/motion.ts` 统一管理:设置面板以 360ms 淡入、上移并缩放到位,背景以 220ms 淡入;先绘制入场起点,再启动动画时钟。其他弹窗入场和配置内容切换 240ms、弹窗退出 180ms,主页面进入 160ms、退出 90ms;缓动读取 `tokens.css` 中的入场/退出曲线,设置入场使用 soft 曲线。动画使用透明度、位移及缩放,不动画布局尺寸。快速关闭、重开或切回原页从当前绘制状态续播,旧切页任务不能覆盖新选择。`useExitMount` 共用退出时长;系统开启减少动态效果时跳过动画等待。服务设置的模式高亮和分类指示条在选中项间移动,供应商配置切换淡入;加载图标与保存中状态保持真实反馈,临时合成层在完成或取消后释放。 + 界面启动等待所选语言资源就绪;语言选择持久化到 `ol.locale`,其他 WebView 通过存储事件同步,日期、数字和默认风格展示随语言变化。用户修改的风格名称、说明和内容保持原文。旧版两种强制排版字段只保留数据兼容,界面清除其布局效果,窄屏改由响应式布局处理。 ## 6. 存储与外部服务 @@ -97,7 +109,9 @@ Less Computer 面板将听写与直接语音提交分开:麦克风把转写填 模型远程元数据只在下载详情选择模型时读取,界面按模型与镜像合并请求并缓存五分钟。Sherpa 离线解码通过 `src-tauri/src/asr/local/blocking_decode.rs` 串行运行;超时或取消不会提前释放原生任务持有的模型和许可,任务结束后才允许下一次解码。长期参考数据、训练准备和历史快照不是 Core 的在线训练服务。 -macOS 凭据在 `persistence/credentials.rs` 使用单个 `credentials.v2` 钥匙串项目;首次迁移读取旧分块一次并保留旧项目供旧版本使用。读取失败返回错误,不能降级成“未配置”;ASR 配置状态从同一次成功读取的快照生成。其他平台保留各自存储限制与适配。 +macOS 凭据通过 `persistence/credentials/macos_vault.rs` 共用 `credentials.v2` 钥匙串项目与进程内缓存:服务凭据、云同步本地包装密钥和记住的同步密钥在同一次受保护读取中加载。同步密钥位于 `_macosLocalSyncKeys` 本机扩展,Core 的提供方快照、UI IPC 与云端导出均不包含该字段。提供方保存/恢复保留扩展;新增密钥经原生存储读回验证,遗忘以持久化空值阻止旧项复活并删除旧独立项。 + +第一次迁移仍须分别获得旧独立钥匙串项目的系统授权,不能取消或扩大它们的 ACL;成功读取后把本机密钥合入同一项目,后续启动只读取该项目。旧分块和迁移来源保留供旧版本使用,读失败不缓存为空、不扫描其他旧项目;可重试授权或读取。Keychain 外部修改在下一次进程启动生效。ASR 配置状态从同一次成功读取的快照生成。其他平台保留各自存储限制与适配。系统对独立项目的授权边界见 [Apple ACL 文档](https://developer.apple.com/documentation/security/access-control-lists)。 ## 7. 验证入口 diff --git a/docs/ci-trigger-and-cache-policy.md b/docs/ci-trigger-and-cache-policy.md index 4b0b9e61..a17666c8 100644 --- a/docs/ci-trigger-and-cache-policy.md +++ b/docs/ci-trigger-and-cache-policy.md @@ -1,6 +1,6 @@ # CI 触发范围与缓存配额 -状态:canonical;更新:2026-09-26。本文说明 CI 为什么在什么情况下跑哪些 job、缓存在 10 GB 预算内怎么分配,以及发版前怎么把耗时压下来。 +状态:canonical;更新:2026-09-29。本文说明 CI 为什么在什么情况下跑哪些 job、缓存在 10 GB 预算内怎么分配,以及发版前怎么把耗时压下来。 ## 1. 触发与门控 @@ -8,7 +8,7 @@ |---|---| | `pull_request` → `main` / `beta` | 按改动范围门控:改动够不到的平台直接跳过(见下) | | `push` → `main` / `beta` | 全量验证(合并后的基线必须有完整证据),不跳过任何 job | -| tag `v*-tauri` | 发版:调用同一个可复用 Linux 打包工作流,并发布 deb/rpm | +| tag `v*-tauri` | 发布 Tauri 桌面与 Android;Linux 包不随标签自动发布 | | `workflow_dispatch` | 全量;`platform=macos` 只跑 macOS(仅 macOS 验证入口) | 同一 PR 连续推送会取消旧运行(`concurrency.cancel-in-progress`)。 @@ -19,7 +19,7 @@ | 区域 | 跳过条件(全部改动都命中才算跳过) | 覆盖的 job | |---|---|---| -| `tauri` | 仅 `linux-egui/**`(但**不含**它的 `Cargo.toml`,那会动全工作区 lock)、`scripts/linux-fcitx5-plugin/**`、`docs/**`、`*.md`、`LICENSE` | macOS / Windows 桌面检查、Android 检查 | +| `tauri` | 仅 `linux-egui/**`(但**不含**它的 `Cargo.toml`,那会动全工作区 lock)、`scripts/linux-fcitx5-plugin/**`、`docs/**`、`*.md`、`LICENSE` | macOS / Windows 桌面检查、Android 检查、Chromium/WebKit UI 动效回归 | | `msrv` | 全部改动里没有任何 `.rs`、`Cargo.toml`、`Cargo.lock`、`rust-toolchain*`、`.github/**` | macOS Rust 1.88 MSRV | | `linux` | 全部改动都是散文(`docs/**`、`*.md`、`LICENSE`、`NOTICE`) | Linux Core/egui 测试与 deb/rpm 打包链 | @@ -31,7 +31,9 @@ 跳过只允许是“改动可达性可证明”的结果,不允许是“读不到差异”或“接线漏了”的兜底。 -`linux-egui-package` 通过可复用工作流的 `scope` 输入接收结果(`full` / `none`,**默认 `full`**):纯散文改动跳过整个 Linux 构建与打包链,而 push / tag / 发版调用不传该输入,因此永远全量构建。 +`linux-egui-package` 通过可复用工作流的 `scope` 输入接收结果(`full` / `none`,**默认 `full`**):纯散文改动跳过整个 Linux 构建与打包链。独立 Linux 手动构建沿用完整检查,只上传 Actions 产物;管理员在产品验收后,针对已接受的现有 tag 构建并单独附加包,见 [发布规范](../RELEASING.md)。 + +`ui-motion` 使用生产前端构建运行 `npm run test:ui-motion`,在 Chromium 与 WebKit 上验证快速往返切页、弹窗中断、手机抽屉退出以及减少动态效果下的模型入口规则。失败时保留浏览器 trace 和截图;浏览器 mock 验证不替代原生权限与设备交互。 ## 2. MSRV 为什么是独立 job diff --git a/docs/images/android-ime/android-architecture-guide.png b/docs/images/android-ime/android-architecture-guide.png new file mode 100644 index 0000000000000000000000000000000000000000..5d473381dc9b11535eeea1a06333303c00b06b5b GIT binary patch literal 2111260 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

OP6VX^ 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 mLbzhiI5{>7xf?>?z{B8)F(*BqhH9j< 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