Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2fb1b3d
fix(linux-egui): 听写可用性两修 —— Menu 热键 / 录音掉线不丢字(+ 诊断日志) (#1255)
TheTouYu Oct 22, 2026
60f67a9
build(ios): 对齐 Beta 并隔离移动端构建与测试 (#1254)
appergb Oct 23, 2026
af14756
chore(release): prepare 2.0.2 Beta.2 build 20261023
appergb Oct 23, 2026
1db451a
ci: add one-off iOS release asset uploader
appergb Oct 23, 2026
e04d692
Revert "ci: add one-off iOS release asset uploader"
appergb Oct 23, 2026
38fbe1c
feat(linux-egui): 重做日志后端——级别可设、噪声清零、盲区补齐 (#1259)
aeoform Oct 23, 2026
f88bf24
fix(windows): keep WASAPI resources on their owning thread
appergb Oct 25, 2026
99b0557
fix(sync): scope events to service origin
Oct 25, 2026
f71ffd5
feat(sync): sync portable preferences and enable cursor context
Oct 25, 2026
c9f49c6
fix(app): align cursor context contracts and CI fixtures
appergb Oct 25, 2026
39e83af
chore(release): prepare 2.0.2 Beta.3
appergb Oct 26, 2026
ee0e3cd
feat(audio): add optional playback echo cancellation (#1228)
HKLHaoBin Oct 27, 2026
79e873c
feat(linux-egui): 在 Linux 端整体删除 Less Computer (#1274)
aeoform Oct 27, 2026
caa1b20
perf(audio): optimize cross-platform processing and complete native a…
aeoform Oct 27, 2026
51d417a
ci(release): tag 推送后自动补鸿蒙与 Linux 包 (#1273)
aeoform Oct 27, 2026
d37394e
fix(textstream): 快照前缀被改写时保留权威全文,不再中止整条转写 (#1257)
solodddd Oct 27, 2026
1fe6ad5
feat(updater): 更新清单改由 CDN 提供,客户端不再读 release 里的 json (#1277)
aeoform Oct 27, 2026
bddad88
fix: stabilize Sherpa offline decode diagnostics (#1282)
H-Chris233 Oct 28, 2026
3bebfca
fix(windows): 修复多屏胶囊切回单屏时原文反复弹出,无需重启 (#1284)
HKLHaoBin Oct 28, 2026
6d258df
ci: 按 Cargo 依赖图判定平台范围,砍掉重复跑与独立胶囊 job (#1279)
aeoform Oct 28, 2026
ace8c3a
fix(macos): only reset TCC grants when the signing requirement change…
DragonNameLeo Oct 28, 2026
ac0b159
feat(android): IME association fixes, learning, English typing and sp…
DepengWang Oct 28, 2026
0212024
fix(capsule): touch Tauri handles only on the event-loop thread
DepengWang Oct 27, 2026
a689f20
fix(llm): 修复 Gemini 在裸 base URL 与当前模型下校验失败 (#1246)
DragonNameLeo Oct 29, 2026
2fd7d53
docs(sponsors): 更新 Mivra 赞助商信息 (#1292)
H-Chris233 Oct 29, 2026
e4457ef
fix(windows): stop trusting recycled HWNDs in the capsule hit-test su…
lxcadoza993 Oct 29, 2026
5dc6e7b
fix(ci): scope capsule main-thread dispatch to Windows
H-Chris233 Oct 29, 2026
54ef630
fix(ci): keep main-thread storage independent of moved Windows imports
H-Chris233 Oct 29, 2026
1328820
fix(windows): resolve capsule layout-watch handles on the main thread
H-Chris233 Oct 29, 2026
3a0bb31
Merge pull request #1293 from Open-Less/fix/pr-1287-windows-capsule-ci
H-Chris233 Oct 29, 2026
bafb73c
Merge remote-tracking branch 'origin/beta' into appergb/branch-consol…
Oct 30, 2026
cde4131
Revert "feat(updater): 更新清单改由 CDN 提供,客户端不再读 release 里的 json (#1277)"
Oct 30, 2026
93704bf
fix(ci): fail open for impact classifier changes
Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
120 changes: 120 additions & 0 deletions .github/workflows/check-ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: iOS tests and device build

on:
pull_request:
paths:
- 'openless-all/ios/**'
- 'openless-all/app/crates/openless-core/**'
- 'openless-all/app/crates/micyou-*/**'
- 'openless-all/app/contract/**'
- 'openless-all/app/assets/**'
- '.github/workflows/check-ios.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ios-checks-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0

jobs:
changes:
# `test` builds and lints the shared Core for iOS, so it must run whenever the
# Rust graph moves. `device-build` (13 minutes on a 10x-cost macOS runner)
# only proves the Xcode/Rime project layer still builds — shared-crate edits
# cannot reach it, so it is gated on the project layer alone.
name: Changed areas
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
# Dashes are not valid inside an output reference, so the area name and the
# output key differ here on purpose.
device: ${{ steps.detect.outputs.device }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect iOS project changes
id: detect
working-directory: openless-all/app
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
verdict=$(bash scripts/ci-changed-areas.sh ios-device "$BASE_SHA")
# Fail open: only an explicit `false` skips the device build.
decision=$( [ "$verdict" = 'false' ] && echo false || echo true )
echo "ios-device=$decision"
echo "device=$decision" >> "$GITHUB_OUTPUT"

test:
runs-on: xcode-27
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.99.0
with:
components: clippy
- name: Record the unchanged shared source baseline
run: |
git diff --exit-code HEAD -- openless-all/app
git rev-parse HEAD:openless-all/app
- name: Prepare the isolated mobile Core
run: python3 openless-all/ios/scripts/prepare-core.py
- name: Test shared keyboard contracts
run: swift test --package-path openless-all/ios/Packages/OpenLessShared
- name: Test mobile Core and iOS Host
run: cargo test --manifest-path openless-all/ios/CoreHost/Cargo.toml --workspace --lib --locked
- name: Check mobile Core
run: cargo clippy --manifest-path openless-all/ios/CoreHost/Cargo.toml -p openless-core --all-targets --locked -- -D warnings
- name: Test interface resources, predictions and native audio
working-directory: openless-all/ios
run: |
python3 scripts/test-interface-resources.py
python3 scripts/build-predictions.py
bash scripts/test-predictions.sh
bash scripts/test-audio.sh
- name: Verify shared sources were not modified
run: git diff --exit-code -- openless-all/app

device-build:
needs: [changes, test]
if: needs.changes.outputs.device != 'false'
runs-on: xcode-27
timeout-minutes: 60
defaults:
run:
working-directory: openless-all/ios
steps:
- uses: actions/checkout@v6
- name: Prepare native build tools and dependencies
run: |
xcodebuild -version
command -v cmake >/dev/null || brew install cmake
bash scripts/bootstrap.sh
bash scripts/bootstrap-local-speech.sh
- name: Build Rime and test composition
run: |
bash scripts/build-rime.sh host
bash scripts/test-rime.sh
bash scripts/build-rime.sh device
- name: Build the mobile Core and refresh attribution
run: |
bash scripts/build-core.sh device
python3 scripts/collect-rust-licenses.py
- name: Build and validate the unsigned iPhone app
env:
OPENLESS_DERIVED_DATA: ${{ runner.temp }}/ios-device
run: |
python3 scripts/validate-project.py
bash scripts/build.sh device Release unsigned
python3 scripts/package-ipa.py \
"$OPENLESS_DERIVED_DATA/Build/Products/Release-iphoneos/OpenLess.app" \
"$RUNNER_TEMP/OpenLess-ci-unsigned.ipa"
- name: Verify shared sources were not modified
run: git diff --exit-code -- ../app
70 changes: 54 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ name: CI
# macOS/Windows/Android 不跑完整 Tauri bundle;Linux 则运行完整 deb/rpm 发版构建校验。

on:
# Feature branches are covered by their pull request. Running both produced two full
# matrices for the same commit (measured: 8 of 42 CI runs were such duplicates).
# The default branches keep a push run because that run is the baseline evidence and
# the only writer of the Rust/Playwright caches.
push:
branches: ['**']
branches: [main, beta]
pull_request:
branches: [main, beta]
workflow_dispatch:
Expand All @@ -19,6 +23,11 @@ on:
type: choice
options: [all, macos]
default: all
macos_runner:
description: macOS runner (Intel fallback when hosted ARM capacity is unavailable)
type: choice
options: [macos-latest, macos-15-intel]
default: macos-latest

# Cancel old runs when the same PR is pushed repeatedly; workflow_dispatch is isolated by run_id.
concurrency:
Expand All @@ -38,6 +47,9 @@ jobs:
msrv: ${{ steps.detect.outputs.msrv }}
linux: ${{ steps.detect.outputs.linux }}
harmony: ${{ steps.detect.outputs.harmony }}
frontend: ${{ steps.detect.outputs.frontend }}
android: ${{ steps.detect.outputs.android }}
capsule: ${{ steps.detect.outputs.capsule }}
# Validate the pinned minimum compiler whenever native sources can change.
msrv_needed: ${{ steps.detect.outputs.msrv }}
steps:
Expand All @@ -53,16 +65,21 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
for area in tauri msrv linux harmony; do
for area in tauri msrv linux harmony frontend android capsule; do
verdict=$(bash scripts/ci-changed-areas.sh "$area" "$BASE_SHA")
# 失败即运行:脚本读不到差异时返回 true,这里也只接受明确的 false。
echo "$area=$( [ "$verdict" = 'false' ] && echo false || echo true )" >> "$GITHUB_OUTPUT"
decision=$( [ "$verdict" = 'false' ] && echo false || echo true )
# 打出来才能事后判断某次跳过是「够不到」还是「读不到差异」。
echo "$area=$decision"
echo "$area=$decision" >> "$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'
# Browser-rendered surface only: 15 minutes of Playwright on a change that
# cannot alter what Chromium/WebKit paints is pure cost.
if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.frontend != 'false'
runs-on: ubuntu-24.04
defaults:
run:
Expand Down Expand Up @@ -110,7 +127,8 @@ jobs:

android-check:
needs: [changes]
if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.tauri != 'false'
# Android compiles its own Rust graph: a bundle-only change cannot break it.
if: (github.event_name != 'workflow_dispatch' || inputs.platform != 'macos') && needs.changes.outputs.android != 'false'
name: Android cargo check
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down Expand Up @@ -258,6 +276,9 @@ jobs:
-x :app:rustBuildX86_64Debug
--no-daemon

- name: Check Android emulator cleanup
run: bash scripts/android-emulator-cleanup.test.sh

- name: Enable KVM for Android instrumentation tests
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
Expand Down Expand Up @@ -373,8 +394,17 @@ jobs:
trap - EXIT
if [ -n "$emulator_pid" ]; then
if kill -0 "$emulator_pid" 2>/dev/null; then
timeout 10s adb -s emulator-5554 emu kill >/dev/null 2>&1 || true
timeout --kill-after=5s 10s adb -s emulator-5554 emu kill >/dev/null 2>&1 || true
kill "$emulator_pid" 2>/dev/null || true
# The emulator can ignore SIGTERM while shutting down.
for attempt in {1..10}; do
kill -0 "$emulator_pid" 2>/dev/null || break
sleep 1
done
if kill -0 "$emulator_pid" 2>/dev/null; then
echo 'Android emulator did not exit; sending SIGKILL'
kill -KILL "$emulator_pid" 2>/dev/null || true
fi
fi
wait "$emulator_pid" 2>/dev/null || true
fi
Expand All @@ -399,6 +429,8 @@ jobs:
# Rime is staged for this emulator ABI; do not launch the Tauri Rust build.
src-tauri/gen/android/gradlew --project-dir src-tauri/gen/android \
:app:connectedX86_64DebugAndroidTest -x :app:rustBuildX86_64Debug --no-daemon
# Regression evidence written by ImeAutoTextIntegrationTest (#1230).
adb -s emulator-5554 logcat -d -s Ime1230Evidence:I '*:S'

- name: Upload Android emulator failure log
if: failure()
Expand Down Expand Up @@ -549,15 +581,14 @@ jobs:
run: node scripts/frontend-test-runner.mjs --platform-only windows

native-capsule:
# The capsule crates compile into the desktop shells, so a break there would
# otherwise only surface inside the long Tauri matrix. They are checked on
# both desktop platforms inside this workflow rather than in a second
# workflow: one run per pull request, one `Changed areas` gate, one cache
# policy. Being a `push` job on the default branches is also what keeps the
# Rust cache warm - a pull-request-only workflow can never save a tree the
# next pull request is allowed to restore.
# The capsule crates compile into the desktop shells. They get their own macOS + Windows
# runners only because their `ui,host` feature set is not built anywhere else (the shells
# use `build:native-capsule:dev`), and that warm cache makes the job ~40 s. It is gated on
# its own area, so the common PR pays zero runners for it: measured on run 40510306688,
# moving these three commands into `cross-platform` cost 117 s on the critical-path Windows
# job, which is more than the two runners it saved.
needs: [changes]
if: needs.changes.outputs.tauri != 'false'
if: needs.changes.outputs.capsule != 'false'
name: Native capsule (${{ matrix.os }})
strategy:
fail-fast: false
Expand Down Expand Up @@ -599,7 +630,8 @@ jobs:
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 }}
# Dispatch-only fallback; push/PR runs keep the default matrix and all test gates.
runs-on: ${{ matrix.label == 'macOS' && github.event_name == 'workflow_dispatch' && inputs.macos_runner || matrix.os }}
timeout-minutes: 60
env:
# The shared Core and full Tauri test binary compile together; limit concurrency
Expand Down Expand Up @@ -749,6 +781,12 @@ jobs:
if: runner.os == 'Windows'
run: python scripts/test-recorder-module.py

- name: Run Windows audio COM lifecycle tests
# These tests use independent processes for COM/enumerator lifetime checks and do not
# require a microphone. Device-dependent capture/playback tests remain explicitly ignored.
if: runner.os == 'Windows'
run: cargo test --locked --manifest-path src-tauri/vendor/cpal/Cargo.toml --lib --test windows_lifecycle

- name: Check Tauri backend with Rust 1.99 MSRV
if: runner.os == 'Windows' && needs.changes.outputs.msrv_needed == 'true'
run: cargo +1.99.0 check --locked --manifest-path src-tauri/Cargo.toml
Expand Down Expand Up @@ -805,7 +843,7 @@ jobs:
# Run the declared minimum toolchain even when stable has the same version.
if: needs.changes.outputs.msrv != 'false'
name: macOS Rust 1.99 MSRV
runs-on: macos-latest
runs-on: ${{ github.event_name == 'workflow_dispatch' && inputs.macos_runner || 'macos-latest' }}
timeout-minutes: 60
env:
CARGO_BUILD_JOBS: 2
Expand Down
45 changes: 41 additions & 4 deletions .github/workflows/release-harmony.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ name: Release HarmonyOS
#
# Dispatch it after the tag exists; it rebuilds that tag, signs it with material
# from Secrets, attaches the HAP to the Release and uploads it to AppGallery.
# `release-tauri.yml` dispatches it automatically once the desktop/Android assets
# for a `v*-tauri` tag are published (see `dispatch-platform-releases`), so a tag
# push ships the HAP instead of depending on somebody remembering this page. The
# manual dispatch stays the repair path for a published tag, and the channel it
# runs with is `vars.HARMONY_AUTO_CHANNEL` (default `none`) on the automatic one.
on:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -113,6 +118,33 @@ jobs:
with:
node-version: '22'

# The HAP build shells out to `npm run build` for the ArkWeb bundle and to
# `cargo build --target aarch64-unknown-linux-ohos` for the Rust host, so it
# needs the app's `node_modules` and the Tier-2 OHOS target std. A hosted
# runner ships neither, and both gaps only surface minutes into the build -
# the first real dispatch of this workflow died with `can't find crate for
# `core`` (error[E0463]) after compiling the Rime/NDK tree. Install them up
# front, exactly like `ci.yml`'s `harmony-check` does.
- name: Cache npm from beta
uses: ./.github/actions/cache-npm

- name: Install frontend dependencies
working-directory: openless-all/app
run: npm ci

- uses: dtolnay/rust-toolchain@stable
with:
# Tier-2 OHOS target; `rustup target list` lists it for stable.
targets: aarch64-unknown-linux-ohos

- uses: swatinem/rust-cache@v2
with:
workspaces: 'openless-all/app -> target'
# Same key as `ci.yml`'s `harmony-check`: a tag build restores the tree
# that job already paid for, and a tag never writes to the cache.
key: harmony-host
save-if: ${{ github.ref == 'refs/heads/beta' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}

# No toolchain cache here either: `prepare-harmony-sdk.sh` downloads the
# checksum-pinned official tools in under two minutes, and a 1.77 GB entry
# that a tag build rarely restores would sit in the 10 GB budget and evict
Expand Down Expand Up @@ -287,10 +319,15 @@ jobs:
if: inputs.upload_to_appgallery && inputs.channel != 'none'
working-directory: openless-all/app
# A fork without AGC credentials still gets the GitHub Release; the step
# reports that it skipped instead of failing the whole release.
run: >-
# Only the signed package is uploaded; the unsigned one is what the
# GitHub Release distributes.
# reports that it skipped instead of failing the whole release. Only the
# signed package is uploaded - the Release carries the unsigned one.
#
# Keep this a literal block: a folded `>-` scalar starting with a comment
# swallowed the command line (`# ... node scripts/upload-appgallery.mjs`)
# and the step died with `--channel: command not found` (127) the first
# time it ever ran.
run: |
set -euo pipefail
node scripts/upload-appgallery.mjs \
--channel "${{ inputs.channel }}" \
--file target/harmony-packages/OpenLess_harmony_arm64-signed.app \
Expand Down
19 changes: 12 additions & 7 deletions .github/workflows/release-linux-egui.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
name: Release Linux egui

# Linux ships in the same GitHub Release as the desktop and Android assets.
# Linux ships in the same GitHub Release as the desktop, Android and HarmonyOS
# assets.
#
# The entry point stays manual: the tag is created by an administrator after the
# Linux host/UI acceptance in docs/linux-egui-handoff/07-acceptance.md, and this
# workflow only rebuilds that already-reviewed tag. It never reacts to Tauri
# tags, so a desktop release can never ship an unaccepted Linux package by
# accident; but once dispatched, the packages are attached to the Release
# automatically instead of being uploaded by hand.
# The entry point is dispatch-only (never a `push:` trigger), so nothing can
# rebuild Linux packages from an arbitrary commit: `release-tauri.yml` dispatches
# it automatically for a `v*-tauri` tag once the desktop assets are published,
# and an administrator keeps the manual dispatch as the repair path. Packaging
# runs the same Core/host/deb/rpm verification as CI and then attaches the
# verified packages to that Release automatically - nothing is uploaded by hand.
#
# Because the tag now ships Linux unattended, the L01-L12 device acceptance in
# docs/linux-egui-handoff/07-acceptance.md is a **pre-tag** gate: a tag must not
# be cut for a Linux state that has not been accepted on real hardware.
on:
workflow_dispatch:
inputs:
Expand Down
Loading
Loading