Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,8 @@ jobs:
run: cargo test --manifest-path src-tauri/Cargo.toml

windows:
name: Windows compile check
name: Windows
runs-on: windows-latest
# The Windows adapters are written but have never run on a Windows host, so
# this job measures the gap rather than gating the build. Remove
# continue-on-error once it passes and Windows is a supported target.
continue-on-error: true
steps:
- uses: actions/checkout@v4

Expand All @@ -97,16 +93,34 @@ jobs:
cache: pnpm

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri

- run: pnpm install --frozen-lockfile

# tauri::generate_context! needs the renderer output to exist.
- name: Build renderer
run: pnpm build

- name: Check
run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets
- name: Test renderer and release audit
run: pnpm test

- name: Format
run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check

- name: Clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings

# Real-host UI Automation fixtures stay ignored here: a CI runner has no
# interactive desktop to select text in.
- name: Test
run: cargo test --manifest-path src-tauri/Cargo.toml

- name: Package NSIS
run: pnpm tauri build --bundles nsis

- name: Audit Windows bundle
run: powershell -NoProfile -ExecutionPolicy Bypass -File tools/release/audit-windows-bundle.ps1 src-tauri/target/release
68 changes: 66 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,69 @@ jobs:

## Platform support

macOS only. The Windows adapters are written but have not been
qualified on a Windows host and are not distributed yet.
macOS (universal `.dmg` / `.app`) and Windows 10/11 x64 (NSIS
`*_x64-setup.exe`). Windows installers are **not Authenticode
signed**. See `docs/windows-signing-and-supply-chain.md`.

windows:
name: Windows x64
needs: macos
runs-on: windows-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri

- run: pnpm install --frozen-lockfile

- name: Build NSIS
run: pnpm tauri build --bundles nsis

- name: Audit Windows bundle
run: powershell -NoProfile -ExecutionPolicy Bypass -File tools/release/audit-windows-bundle.ps1 src-tauri/target/release

- name: SHA-256 checksums
shell: pwsh
run: |
$nsis = Get-ChildItem "src-tauri/target/release/bundle/nsis/*_x64-setup.exe" | Select-Object -First 1
$exe = Get-Item "src-tauri/target/release/desktop-translator.exe"
@(
(Get-FileHash -Algorithm SHA256 $nsis.FullName | ForEach-Object { "$($_.Hash.ToLower()) $($nsis.Name)" })
(Get-FileHash -Algorithm SHA256 $exe.FullName | ForEach-Object { "$($_.Hash.ToLower()) $($exe.Name)" })
) | Set-Content -Encoding ascii "windows-sha256.txt"
Get-Content "windows-sha256.txt"

- uses: actions/attest-build-provenance@v2
with:
subject-path: src-tauri/target/release/bundle/nsis/*_x64-setup.exe

- name: Upload audited Windows artifacts
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
$nsis = Get-ChildItem "src-tauri/target/release/bundle/nsis/*_x64-setup.exe" | Select-Object -First 1
$exe = Get-Item "src-tauri/target/release/desktop-translator.exe"
gh release upload "$env:RELEASE_TAG" $nsis.FullName $exe.FullName "windows-sha256.txt" --clobber
6 changes: 6 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ involved. Textbook attribution and source links remain visible in the study UI.
Translation lookup follows this order: personal wordbook, active downloaded
textbook, then the configured online translation provider. A textbook hit is
promoted into the personal wordbook for later study.

Windows 10/11 x64 is distributed as an unsigned NSIS installer. Compile, gating
CI, and silent install/launch/uninstall were recorded on Windows 11 25H2 x64;
interactive overlay and speech fixtures remain manual-only. See
[README.md](README.md) and
[docs/windows-signing-and-supply-chain.md](docs/windows-signing-and-supply-chain.md).
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,23 @@ The study window brings four tools together:
| Platform | Status |
| --- | --- |
| macOS 11+ | Supported |
| Windows 10/11 | Implemented but unqualifiednot distributed yet |
| Windows 10/11 x64 | Packaged (unsigned NSIS)compile, CI, and installer smoke passed; interactive UI fixtures remain manual-only |
| Linux | Not planned |

> [!NOTE]
> The Windows adapters (UI Automation selection, low-level mouse hook, SAPI
> speech) are written and compile in CI, but they have never been exercised on a
> real Windows host, so no Windows build is published. See
> [`docs/platform-test-matrix.md`](docs/platform-test-matrix.md) for what is and
> is not qualified.
> Windows 11 25H2 x64 (build 26200.9168) produced
> `Desktop Translator_0.3.0_x64-setup.exe` via `pnpm tauri build --bundles nsis`.
> Silent current-user install, launch, reinstall, and uninstall succeeded.
> UI Automation selection, the non-activating overlay against another app, SAPI,
> the credential prompt, start-at-login, and Vocabulary Study chrome were **not**
> exercised interactively on this host. See
> [`docs/platform-test-matrix.md`](docs/platform-test-matrix.md) and
> [`docs/windows-signing-and-supply-chain.md`](docs/windows-signing-and-supply-chain.md).

## Install

### macOS

Download the `.dmg` from the
[latest release](https://github.com/Ldsystem/desktop-translator/releases/latest)
and drag the app into Applications. The build is universal, so one download
Expand All @@ -123,6 +128,18 @@ runs natively on both Apple Silicon and Intel Macs.
> xattr -dr com.apple.quarantine "/Applications/Desktop Translator.app"
> ```

### Windows 10/11 x64

Download `Desktop Translator_*_x64-setup.exe` from the same Releases page and
run it. The installer is NSIS, current-user, and fetches WebView2 with the
Microsoft download bootstrapper when the runtime is missing. It does not
require Node.js, Rust, Python, or an administrator account.

> [!IMPORTANT]
> Windows builds are **not Authenticode-signed**. SmartScreen may warn on first
> run; use **More info → Run anyway**. See
> [`docs/windows-signing-and-supply-chain.md`](docs/windows-signing-and-supply-chain.md).

## Setup

Two things are needed before the first online translation. Vocabulary Study is
Expand Down Expand Up @@ -162,8 +179,8 @@ menu-bar menu, choose a service, and use its native credential prompt:
> never silently falls back to another online provider.

Credentials are entered in native secure prompts and stored in the macOS
Keychain. They never pass through the WebView and are never written to the
settings file.
Keychain or Windows Credential Manager. They never pass through the WebView and
are never written to the settings file.

## Usage

Expand Down Expand Up @@ -214,7 +231,8 @@ pnpm tauri dev
| `pnpm tauri dev` | Run the app against a live-reloading renderer |
| `pnpm check` | Typecheck, frontend tests, and renderer build |
| `pnpm test:platform` | Rust unit and integration tests |
| `pnpm tauri build` | Produce a `.app` and `.dmg` |
| `pnpm tauri build` | Produce a `.app` and `.dmg` on macOS |
| `pnpm tauri build --bundles nsis` | Produce the Windows x64 NSIS setup exe |

> [!TIP]
> On macOS, run [`tools/macos/create-dev-signing-identity.sh`](tools/macos/create-dev-signing-identity.sh)
Expand Down
14 changes: 14 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

## 安装

### macOS

从 [最新 Release](https://github.com/Ldsystem/desktop-translator/releases/latest) 下载 `.dmg`,
将应用拖入“应用程序”文件夹。安装包为通用版本,同时支持 Apple 芯片和 Intel Mac,
并已包含界面、数据库运行库和离线入门词书,不需要安装 Node.js、Python 或 SQLite。
Expand All @@ -47,6 +49,17 @@
xattr -dr com.apple.quarantine "/Applications/Desktop Translator.app"
```

### Windows 10/11 x64

从同一 Releases 页面下载 `Desktop Translator_*_x64-setup.exe`(NSIS,当前用户安装)。
若本机没有 WebView2,安装程序会通过微软官方 bootstrapper 下载。不需要 Node.js、Rust、
Python 或管理员权限。构建**未经 Authenticode 签名**,SmartScreen 可能提示
“更多信息 → 仍要运行”。

本机 Windows 11 25H2 x64 已完成编译、CI 门禁和静默安装/启动/卸载;针对第二个应用的
划词叠加层、SAPI、凭据提示等交互夹具仍待手动执行。详见英文
[README.md](README.md) 与 [平台矩阵](docs/platform-test-matrix.md)。

## 初次设置

1. 在“系统设置 → 隐私与安全性 → 辅助功能”中允许桌面翻译,然后从菜单栏退出并重新打开。
Expand Down Expand Up @@ -75,5 +88,6 @@ pnpm tauri dev
| `pnpm check` | TypeScript 检查、前端测试和构建 |
| `pnpm test:platform` | Rust 单元与集成测试 |
| `pnpm tauri build` | 生成自包含的 `.app` 和 `.dmg` |
| `pnpm tauri build --bundles nsis` | 生成 Windows x64 NSIS 安装包 |

详细的架构、平台支持和权限说明请参阅 [英文 README](README.md)。
44 changes: 25 additions & 19 deletions docs/platform-test-matrix.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Platform Qualification Matrix

Updated: 2026-08-13
Updated: 2026-08-24

This matrix separates deterministic automated evidence from real-host evidence. A row is
`Passed` only when the stated fixture was exercised on the named host. `Pending` is not
Expand All @@ -10,27 +10,28 @@ treated as release evidence.

- **macOS host:** macOS 26.5.2 (25F84), Apple silicon arm64, 16 GiB RAM,
Node.js 20.20.0, pnpm 10.30.2, rustc 1.97.1.
- **Windows host:** pending access to a Windows 10/11 x64 host with a normal
unelevated session and UI Automation enabled.

> The Windows target does not compile. The non-gating `windows-latest` job in
> `.github/workflows/ci.yml` first ran on 2026-08-13 and reported drift against
> `windows` crate 0.62: `apply_non_activating_tool_window` is not exported from
> `platform::windows` (`overlay.rs:365`), `LOCALE_NAME_MAX_LENGTH` is no longer
> under `Win32::Globalization` (`speech.rs:16`), argument types changed at
> `selection.rs:304` and `speech.rs:183`, and `SPEAKFLAGS` no longer implements
> `BitOr` (`speech.rs:219`). Windows qualification therefore starts from a
> compile repair, not from host fixtures.
- **Windows host:** Windows 11 25H2 x64 (build 26200.9168), unelevated session,
Node.js via Volta, pnpm 11.20.0, rustc 1.98.0. Display topology is a single
primary 1536×960 32-bit surface (`\\.\DISPLAY1`); mixed-DPI and multi-monitor
placement were **not** available on this host.

> Compile against locked `windows` 0.62 is repaired. Gating Windows CI now runs
> renderer build, `cargo fmt`, Clippy `-D warnings`, `cargo test`, NSIS package,
> and `tools/release/audit-windows-bundle.ps1`. Real-host UI Automation selection,
> overlay against a second app, SAPI, credential prompt, start-at-login, and
> Vocabulary Study chrome remain **manual-only** until those fixtures are run
> interactively. See [`windows-signing-and-supply-chain.md`](windows-signing-and-supply-chain.md).

## Deterministic gates

| Gate | macOS result | Windows result | Evidence |
| --- | --- | --- | --- |
| Frontend typecheck, tests, production build | Passed | Pending | `pnpm check`; 6 files and 22 tests passed |
| Rust unit and integration suite | Passed | Pending | `cargo test --manifest-path src-tauri/Cargo.toml`; 73 passed and 6 explicit manual fixtures ignored |
| Strict Rust lint and formatting | Passed | Pending | `cargo fmt --check`; `cargo clippy --all-targets -- -D warnings` |
| Native unsigned release build | Passed | Pending | `pnpm tauri build` on macOS |
| Performance harness parser and budgets | Passed | Pending | `pnpm test:perf`; process-tree parsing, percentile calculation, CLI forwarding, and multi-budget failures |
| Frontend typecheck, tests, production build | Passed | Passed | `pnpm check` on this Windows host; 12 files and 80 tests passed including the NSIS audit suite |
| Rust unit and integration suite | Passed | Passed | `cargo test --manifest-path src-tauri/Cargo.toml`; 135 passed, 1 ignored network fixture |
| Strict Rust lint and formatting | Passed | Passed | `cargo fmt --check`; `cargo clippy --all-targets -- -D warnings` |
| Native unsigned release build | Passed | Passed | `pnpm tauri build --bundles nsis`; `Desktop Translator_0.3.0_x64-setup.exe` 3 589 056 bytes SHA-256 `9DD565CF2FA5487D90EDD3B8638C92C76B88C32CBEBED1054A885CD0D799DCC5`; exe 9 397 248 bytes SHA-256 `FF33835546FF6A6F6D4DAE30241DA3871956D6C9D08C3D46AE601AFB53A21ECF` |
| Installer silent install / launch / reinstall / uninstall | n/a | Passed | NSIS `/S` current-user install to `%LOCALAPPDATA%\Desktop Translator`, process started, `/S` reinstall, `uninstall.exe /S` removed the directory |
| Performance harness parser and budgets | Passed | Passed (parser) | `pnpm test:perf` is included in `pnpm check`; warmed RSS/latency on Windows remain pending |

## Selection and lifecycle fixtures

Expand All @@ -48,7 +49,7 @@ treated as release evidence.
| Protected/secure field suppression | Pending | Pending | Run the ignored secure-field fixture and verify no overlay, log, or request |
| Unsupported canvas/PDF control | Pending | Pending | Verify no overlay when AX/UIA exposes no selected range and geometry |
| Permission denied or elevated target | Pending | Pending | Revoke macOS Accessibility and test guidance; test a Windows elevated target from an unelevated app |
| Mixed-DPI, multi-monitor, and work-area edges | Automated | Pending | Placement and macOS display-normalization tests pass; real multi-display placement remains pending |
| Mixed-DPI, multi-monitor, and work-area edges | Automated | Topology limited | Windows unit placement tests pass; this host has one 1536×960 display so mixed-DPI/multi-monitor was not exercised |
| Display topology change | Automated | Pending | Pure placement recomputation test passes; real hot-plug remains pending |
| Sleep/wake and observer restart | Automated | Pending | Observer stop/restart lifecycle tests pass; real sleep/wake remains pending |
| Double/triple-click word or paragraph selection | Automated | Pending | macOS gesture-state tests pass; real-host fixture remains pending |
Expand Down Expand Up @@ -105,9 +106,14 @@ Current macOS pre-WebView release measurement:

## Release blockers

Windows compile, NSIS packaging, gating CI, and silent installer smoke passed on
Windows 11 25H2 x64. Interactive UI Automation selection, overlay, SAPI,
credential prompt, start-at-login, and Vocabulary Study chrome are still
manual-only.

Task 008 cannot be accepted until:

1. the Windows deterministic build and real-host fixture rows pass on Windows 10/11;
1. remaining Windows real-host UI fixture rows are executed on Windows 10/11;
2. pending macOS real-host fixtures are executed;
3. warmed resource measurements and externally observed latency samples pass;
4. manual theme, reduced-motion, and real multi-display checks are recorded.
Expand Down
64 changes: 64 additions & 0 deletions docs/windows-signing-and-supply-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Windows signing and software supply chain

This document separates **controls this repository already implements** from
**external certificate and account work** that must not be stored in Git.

Windows release artifacts are currently **unsigned**. That is intentional until
a publisher certificate is procured outside this repository. Do not buy, commit,
log, or embed a code-signing certificate here.

## Implemented in CI and packaging

| Control | Where it lives | Status |
| --- | --- | --- |
| Lockfile integrity | `pnpm-lock.yaml`, `src-tauri/Cargo.lock`, `pnpm install --frozen-lockfile` | Implemented |
| Gating Windows compile and package | `.github/workflows/ci.yml` Windows job | Implemented |
| NSIS x64 installer + WebView2 download bootstrapper | `src-tauri/tauri.conf.json` `bundle.windows` | Implemented |
| Bundle audit (exe, NSIS x64 setup, no developer runtimes, textbook remains embedded) | `tools/release/audit-windows-bundle.ps1` | Implemented |
| Artifact hashes | Release job writes SHA-256 for the NSIS setup and native exe | Implemented |
| Provenance attestation | `actions/attest-build-provenance` on tag/workflow_dispatch only | Implemented |
| Least-privilege installation | NSIS `installMode: currentUser` (no administrator required) | Implemented |
| Credential isolation | Native Windows Credential Manager via `keyring`; credentials never enter the WebView, settings JSON, logs, or docs | Implemented |
| Fork / pull-request isolation | `release.yml` does not run on `pull_request`; it never receives signing secrets | Implemented |
| macOS path isolation | Windows and macOS release jobs are separate; artifacts keep platform-specific names | Implemented |

Dependency review for pull requests remains the GitHub-hosted default for this
public repository. There is no committed SBOM generator yet; tag provenance
attestations are the implemented substitute.

## External prerequisites (not in this repository)

These require a human publisher identity and must use a protected GitHub
Environment or an offline ceremony. Untrusted forks must never receive them.

| Prerequisite | Purpose |
| --- | --- |
| Authenticode code-signing certificate | Publisher identity for the NSIS setup and exe |
| Certificate custody and PIN/HSM or cloud KMS | Private key never in Git, logs, or `pull_request` secrets |
| RFC 3161 timestamping authority | Signatures remain verifiable after the cert expires |
| Publisher name alignment | SmartScreen reputation accrues to one stable identity |
| SmartScreen / Microsoft reputation | New publishers are warned until reputation exists |
| Malware scanning of published installers | Optional extra gate before a production tag |
| Key rotation and revocation plan | Replace a compromised or expired cert; publish a new installer |
| Rollback | Yank or replace a GitHub Release; do not reuse a burned version |

Until those exist, document SmartScreen warnings as expected for unsigned
installers. Users may need to choose **More info → Run anyway**. That is not a
substitute for Authenticode.

## Certificate custody rules

- Store signing material only in a protected GitHub Environment or an external
signing service.
- Grant `id-token` / `attestations` on the tag workflow; do not pass
`WINDOWS_CERTIFICATE*` (or any analog) into pull-request jobs.
- Rotate by issuing a new cert, signing a new installer, and publishing new
hashes. Revoke the old cert with the CA when it is compromised.
- Never put `.pfx`, passwords, or thumbprints that unlock a private key into
`src-tauri/tauri.conf.json` on this branch.

## Related macOS note

macOS ad-hoc signing and Accessibility identity are documented in
[`macos-development-signing.md`](macos-development-signing.md). Windows does not
use that runner. Do not copy macOS signing identities into the Windows job.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "desktop-translator",
"private": true,
"version": "0.3.0",
"version": "0.4.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading