* fix(server): 保留候选文本中的逗号和私用字符
* fix(server): render candidate frame from one snapshot
* fix(server): use captured preedit for sticky card key
* fix(server): dedup candidate frames by item-aware signature
Candidate text now keeps raw commas, so the comma-joined candidate_string
could make ["a,b","c"] and ["a","b,c"] share one frame signature and drop
a real page change as a duplicate repaint. Length-prefix each item instead.
Also serialize the WebView candidate items with the replace error handler
so an invalid UTF-8 translation gloss cannot throw on the UI thread, and
let CandidateWindowContent own its preedit and page snapshot rather than
referencing caller locals.
---------
Co-authored-by: fanlusky <1730976608@qq.com>
* ci: cache Engine compilation with sccache
* ci: verify Engine cache across commits
* ci: harden Engine sccache setup
- Fall back to plain cl.exe when the sccache server is unreachable
- Only report sccache stats when sccache was actually installed
- Also launch C compiles through sccache, matching the Server job
- Note why the Engine job must stay on Ninja Multi-Config
---------
Co-authored-by: fanlusky <1730976608@qq.com>
The Server job's Build step dominates the pipeline (~6m37s of a ~9.5m job;
the dictionary/integration test is only ~43s). sccache caches individual
MSVC compiles across runs so an incremental change rebuilds only what it
touched instead of the whole server + engine tree.
sccache only caches an MSVC compile whose debug info is embedded in the
object, so switch the Server build's Release debug info from /Zi to /Z7.
The linker's /DEBUG still emits the beside-the-executable PDB the release
job validates, so the symbol layout is unchanged.
Its compiler-launcher hook is honoured only by the Ninja and Makefile
generators, so configure the Server build with Ninja Multi-Config (keeping
the --config Release / bin/Release layout the later steps depend on) and
add an MSVC-environment step, since Ninja does not locate cl.exe on its own.
Ports metasequoiaime/MSIME-Windows#153 onto the consolidated repository. That pull request was written against the six-repository layout: it edited product-ci.yml, which no longer exists, and re-pinned Server, UiHtml and Installer, which are directories now.
Three of its pinned commits had not reached this repository at all. The consolidation took each source repository at its main, and Server, UiHtml and Installer each landed two more commits afterwards, so dropping the pins as merely obsolete would have silently dropped the work the pull request exists to consume. The two substantive ones are ported here: Server drops its vendored miniaudio.h and builds against the engine's MetasequoiaIme::VoiceCapture, and the installer collects helpcodes from the engine. UiHtml's was an engine gitlink bump, which one submodule resolves on its own.
The engine is pinned to d0dc0c2b rather than to the b7c799ef that pull request named. b7c799ef predates 403b4a4d, which is what this repository pins today and is the commit that added pagination to the settings dictionary contract, so taking the pull request's pin verbatim would have reverted a contract main already depends on and stripped offset, limit and hasMore back out of the generated web bindings. d0dc0c2b is the engine's main and is the same commit the dictionary release declares as its source, so the code and the data now name one commit.
The helpcodes moved into the engine, so MSIME-HelpCode stops being a product input: the gitlink that pins the contracts pins the tables too. product-lock.json is down to the engine and the dictionary release, and the dictionary itself now ships from MSIME-Engine. Its manifest states which commit built the data and whether that tree was clean, and the lock verifies both, so matching digests can no longer stand in for a source nobody can rebuild from. The retired MSIME-Dict repository stays valid for the one tag that shipped from it and nothing else.
The release job that packages now checks out submodules, because the helpcodes it collects live in one.
check-dictionary-contract.py compares scripts/dictionary_product.py against the engine's own copy, so the job needs the submodule's contents and not just its gitlink. The reusable workflow this job replaced checked out submodules; the rewrite dropped that.
The script reported a missing file as "differs from the pinned Engine contract", which describes a drift that had not happened and sends the reader looking for a content change. It now says the file is missing and how to get it.
The product lock existed because five repositories had to be pinned to reproduce one product. Four of them are directories now, so a commit of this repository already pins them and there is nothing left for the lock to say about them. What it still records is what the product consumes from outside: the engine submodule, the helpcodes and the dictionary release.
CI gains a job per component instead of a reusable workflow that checked out locked commits and rebuilt them. The release workflow no longer stages five sibling checkouts under historical names and no longer carries a second sparse checkout for its own scripts; Prepare-PackageFiles.ps1 already took the source directory names as parameters, so it is told the new ones, plus where the product-wide notice now lives.
Two checks the removed cross-repository pins used to provide are replaced in kind: the pages are verified against the engine submodule's web contract in CI rather than by locking UiHtml and Engine together, and the engine bump triage that Server carried moves here and now also says so when a bump leaves the lock behind.
Every other piece of shell in these workflows lives in scripts/ci and the workflow only wires it up. The language probe went in inline, so it did not: twenty lines of PowerShell building an .iss heredoc inside a YAML block scalar, which is the least reviewable and least runnable place to put it.
Same check, now in check-inno-language.ps1, and runnable by hand on a Windows machine. It falls back to the system temp directory when RUNNER_TEMP is unset so that running it outside CI works.
Every release build since the packaging pipeline landed has failed at "Compile the installer", which is why the last eight versions are still unpublished drafts:
Error on line 63 in msime_setup.iss: Couldn't open include file
"c:\program files (x86)\inno setup 6\Languages\ChineseSimplified.isl"
install-inno-language.ps1 derived the destination from `Get-Command ISCC.exe`. On the windows-2025 image Inno Setup comes from Chocolatey, so that resolves to the shim at C:\ProgramData\Chocolatey\bin\ISCC.exe and the file was written to C:\ProgramData\Chocolatey\bin\Languages\. ISPP resolves compiler: against the real installation. The step printed "Installed ... (21516 bytes)" and exited 0, and the build failed two steps later pointing at a path nothing had written to, which is why this survived eight releases.
The installation is now located by a file only it has -- ISPPBuiltins.iss or Default.isl, neither of which sits beside a shim -- checking the uninstall registry key, the two Program Files locations and any ISCC.exe on PATH, and failing with the list it tried rather than falling back to a guess.
ci.yml now compiles a throwaway script whose only content is a [Languages] entry pointing at compiler:Languages\ChineseSimplified.isl. Asserting the file exists at a path this script computed would only restate the script's own belief; compiling is what proves ISCC agrees. The URL and digest pin moved into the script so that check does not need a second copy of them.
MSIME-Windows#121 records that CI cannot be made a required check here, because release-please's
pull request never gets one: GITHUB_TOKEN may not start workflows, so the pull_request run is
created in action_required and waits for a human forever.
I previously wrote on that issue that the only way out was a GitHub App or PAT. That was wrong.
workflow_dispatch is the exception GitHub allows GITHUB_TOKEN to fire, and MSIME-Apple's release
automation has been relying on it all along. Checked its release branch: workflow_dispatch runs
exist there and succeed, alongside pull_request runs that do not.
Also checked the two alternatives that looked cheaper, and neither works:
- A push trigger on release-please--** produces nothing. Every run on that branch in MSIME-Linux
is a pull_request event; a GITHUB_TOKEN push creates no run at all. MSIME-Linux#31 merged on
the belief that it does.
- The approval policy is not the cause. All six repositories are already on
first_time_contributors_new_to_github, the loosest setting, and bot-triggered runs are still
gated. Comparing runs, the gate tracks triggering_actor exactly: github-actions[bot] gives
action_required, houko gives success.
So merge-release-pr.sh now dispatches ci.yml on the release branch, polls for the run at that exact
head commit, and waits for it with --exit-status before merging. The checks land on the pull
request's head commit, which is what required_status_checks needs.
It also re-reads main and the pull request head afterwards: main can advance during a build, and
merging then would rebase the release commit onto something that was never tested.
ci.yml gains workflow_dispatch so it can be dispatched at all. The paths-ignore stays: it keeps the
unapprovable pull_request run out of the checks list, leaving only the dispatched run's results,
which avoids two check runs with the same name and different outcomes on one commit.
The prepare job gains actions: write, without which gh workflow run cannot dispatch.
release-please opens its pull request as github-actions[bot] using GITHUB_TOKEN, and GitHub does not let GITHUB_TOKEN start workflows, so CI on that pull request lands in action_required and waits for someone to press approve. That stalled the release: nothing moves until the pull request is merged, and the pull request looks unfinished until CI runs.
The prepare job now merges the pull request it just created. The resulting push runs this workflow again, release-please converts the release commit into the draft release, and the build jobs take over, so a conventional commit on main now reaches a published installer without anyone clicking through.
Merging without CI is safe here because the pull request only rewrites version.txt, CHANGELOG.md and .release-please-manifest.json. For the same reason the CI workflow now ignores those three paths on pull requests, which stops each release from parking an unapprovable run in the checks list.
This is the same shape as MSIME-Apple's merge-release-pr.sh step.