The emoji, keyboard and handwriting panels embed the GUI framework's icon through a relative path that was written when msimeui was a submodule under server/. It now lives one level further out, at the repository root.
Found by the resource compiler rather than by the sweep that moved the submodules: that sweep matched CMake, JSON, PowerShell, Python, shell and YAML by extension, and .rc was not on the list. Re-checked by resolving every relative path literal in every tracked file regardless of extension; these three were the only ones left that did not resolve.
The product README was under windows/ after the move, but it was never a component readme: it is the download page, the feature guide and the contribution guide for the whole product, so it goes back to the root and windows/ gets a real component readme.
AGENTS.md at the root now carries the repository map, the component boundaries that used to be cross-repository rules, and the release process, since the scripts that process drives live at the root. The component files keep what is genuinely theirs and say which paths they are relative to. References that named sibling repositories now name sibling directories, and the lock documentation records that Server, the pages, the GUI framework and the installer are no longer inputs to pin.
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.
Windows, Server and UiHtml each carried their own gitlink to MSIME-Engine, all three pinned to the same commit, and Server carried a gitlink to MSIME-UI that is now the ui/ component. Git only reads the root .gitmodules, so the nested ones had to go regardless.
The engine and the two third-party dependencies now live once at vendor/MetasequoiaImeEngine, vendor/opencc and vendor/cpp-pinyin. Server reaches them through MSIME_VENDOR_DIR and builds ui/ as an out-of-tree subdirectory; the Windows tip's relative contract includes gained the extra level; the ui-html contract sync and the Server env/table generators follow the engine to its new location.
NormalizeClipboardText caps an entry at kMaxChars with text.resize(). wchar_t is UTF-16 on Windows, so that cap counts code units, and a cut landing between a high and low surrogate leaves a lone high surrogate. The result is not valid UTF-16, and it is what gets serialised into the history store and read back.
It takes a non-BMP character — an emoji, or a supplementary-plane Han character, both of which this IME can produce — sitting exactly at the boundary. The entry then carries an unpaired surrogate for as long as it stays in the history.
The cut now steps back one unit when it would split a pair, dropping the incomplete character instead of half of it. Entries that do not end on a surrogate are unaffected.
MSIME-Linux does not have this: its normalize() truncates with g_utf8_offset_to_pointer, which is codepoint aware. The two implementations otherwise agree exactly, including kMaxItems, kMaxChars and the promote-to-front policy.
src/d2d/candidate_window_d2d.{cpp,h} is 528 lines that no target compiles: it appears in no add_executable source list in CMakeLists.txt, and nothing outside the directory names candidate_window_d2d or CandidateWindowD2D. The d2d1 in CMakeLists.txt is the Windows system library, not this directory. It was last modified on 2025-06-25.
It is an earlier candidate window, superseded twice over: the shipped candidate window is the WebView2 page from MSIME-UiHtml, and the native GUI work now lives in the msimeui framework. Keeping a third, unbuilt implementation in the tree means every candidate window change invites the question of whether this one needs the same fix, and the answer is always no because it never runs.
The history keeps it if the Direct2D drawing is ever wanted as a reference.
The engine pin moved 55839c8 -> 127019f in the previous commit, and that range contains 86c5fa7 "fix(quanpin): enumerate alternative segmentations for four-syllable input". QuanpinDictionaryOnlyTriesMultipleSegmentationsForAtMostThreeSyllables asserted the behaviour that fix deliberately changed, so it failed here and, through the product lock, in MSIME-Windows' locked-Server integration job as well.
The engine is the authoritative layer: quanpin/quanpin_dictionary.cpp declares kMaxSyllablesForMultipleSegmentations = 4 and gates enumeration on segments.size() <= that. Four syllables now enumerate alternatives; five still do not. The test says exactly that, with a tbl_6_x fixture row so the five-syllable case would have something to find if the bound ever moved silently.
The engine absorbed the six local mode queries during the cross-platform work,
and MSIME-Linux has been driving them in production since. This repository kept
its own implementations of the same six features, so date-time, emoji, kaomoji,
jianpin, quick-phrase and unicode each existed twice and were free to drift.
Deletes this repository's copies and points the eight call sites in
event_listener.cpp plus the two async workers at metasequoia::local_modes.
The shuangpin profile is the part to look at. The old modules resolved the
*configured* scheme themselves:
GetShuangpinProfile(shuangpin_schema.empty() ? GetConfiguredShuangpinSchema()
: shuangpin_schema)
The engine takes a resolved ShuangpinProfile and defaults it to Xiaohe. Taking
that default would have decoded J mode, emoji and kaomoji as Xiaohe for every
user on Ziranma, Shoudao or Microsoft shuangpin, so every call site passes the
configured profile explicitly through a ConfiguredShuangpinProfile() helper.
The database path needs the same care in the other direction. The old modules
took a UTF-8 std::string and handed it straight to sqlite3_open_v2; the engine
takes a std::filesystem::path and converts back with path_to_utf8 before
opening. The two async workers therefore bridge with path_from_utf8 rather than
constructing a path from the narrow string, which on Windows would interpret a
non-ASCII data directory in the active code page.
The four existing test files are kept and re-pointed at the engine functions
with every expectation byte-identical, including the seventeen date-time format
strings and the four shuangpin schemes jianpin covers. They are the equivalence
check: if the engine's port diverged from what this repository shipped, those
assertions fail rather than the difference reaching users.
* chore: advance the engine submodule to current main
Six commits, none of them C++: an AGENTS.md, a README update, and the vcpkg
git-registry change that this repository already took for itself in #18.
git diff --name-status over the range lists exactly AGENTS.md, README.md and
vcpkg-configuration.json, so the engine's behaviour is unchanged.
The engine's vcpkg-configuration.json has no effect here: manifest mode reads
only the top-level manifest, and this repository has its own.
* chore(deps): check submodules daily instead of monthly
The gitsubmodule ecosystem was on a monthly schedule while MSIME-Engine takes
around fifteen commits a day. Dependabot has opened zero submodule PRs here so
far, so every engine pin move has been noticed and written by hand, which is
the whole reason the three frontends kept drifting onto three different engine
commits.
Daily turns that into one PR per submodule per day at most. The github-actions
ecosystem stays monthly; action pins do not move nearly as often.
* ci: let inert engine bumps merge themselves
Dependabot now checks submodules daily, which would mean roughly one engine
bump PR per repository per day. Most of them carry nothing that can change
behaviour: MSIME-Engine takes around fifteen commits a day and a large share
are documentation, CI wiring and dependency manifests.
This workflow classifies the engine range on a dependabot bump and enables
auto-merge only when every changed file is provably inert. Everything else gets
a needs-review label and a comment naming the files that held it back.
The rule is an allowlist rather than a blocklist, and that distinction is the
point. Today Apple pinned an engine range whose only non-documentation entries
were CMakeLists.txt, .gitmodules and a bare utfcpp submodule pointer. A
blocklist keyed on source extensions would have waved the utfcpp bump through,
because a nested submodule appears in the compare API as a bare path with no
extension and no patch. The allowlist holds it.
Verified the classifier against both real ranges before committing: f91cd20 ->
main is inert as expected, and 8cc306c -> main is held with CMakeLists.txt,
.gitmodules and utfcpp named as the reasons.
#22 added Provision dictionary data and Test steps to ci.yml, so the section
saying CI never runs the suite was inverted the moment that landed. ctest runs
on every PR and a failing assertion turns the build red.
The part worth writing down is what #22 had to set up to make that work: the
tests read the real msime.db, others.db and english.db from the data root plus
the full five helpcode schemes, none of which this repository carries. A local
run without that data does not fail with a missing-file error, it fails with
empty candidate sets and assertion messages that read like logic bugs, so the
section now says to check the data root before debugging a failure. Also
records METASEQUOIA_IME_DATA_DIR as the override.
Restores BUILD_TESTING to its previous value after the engine subdirectory
instead of hardcoding ON, the same save-and-restore this file already does with
BUILD_SHARED_LIBS around cpp-pinyin. #26 put the flag back with a literal ON,
which would quietly re-enable this repository's own tests/ for anyone who
configured with -DBUILD_TESTING=OFF.
CMakeLists.txt hardcoded ${CMAKE_SOURCE_DIR}/build/vcpkg_installed in three places, for the WebView2
loader import library and two include paths, so the binary directory had to be literally named
build. The sqlite3 copy nearby already used ${CMAKE_BINARY_DIR}; the other three now match it.
The symptom is this repository's own vcpkg-release preset, whose binaryDir is build-release:
cmake --preset=vcpkg-release fails to link MetasequoiaImeSettings with LNK1181, and so does
scripts/lcompile-release.ps1, which uses it.
CI now builds into build-release. The two expressions resolve to the same path when the directory
happens to be called build, so leaving CI alone would make this a rename that proves nothing.
Rebased onto #22, whose dictionary provisioning and ctest step are kept as they are.
main is red. ctest reports eleven failures, all of the form:
Unable to find executable: .../build/MetasequoiaImeEngine/Release/test_platform_api.exe
#14 began consuming the engine through add_subdirectory(... EXCLUDE_FROM_ALL), and the engine
registers eleven add_test() entries behind BUILD_TESTING. EXCLUDE_FROM_ALL then keeps the
executables those entries point at out of the build, so ctest has eleven registered tests it cannot
run. It went unnoticed until #22 turned ctest on.
Turning BUILD_TESTING off across that one add_subdirectory suppresses the registration. The engine's
own CI runs those tests on Ubuntu, macOS and Windows, so running them from here would be redundant
even if they did build. BUILD_TESTING is restored immediately afterwards so this repository's own
tests/ still register; the engine guards its test block on the same flag, and CMP0077 is NEW at
cmake_minimum_required 3.25, so its include(CTest) will not override the normal variable.
MetasequoiaImeServerTests itself passes, in 25.6 seconds, with the dictionary data #22 provisions.
The section heading still said CI runs none of the tests, which contradicted
the body right under it: the target is configured and compiled on every run,
and a compile error in tests/src turns CI red. Only the running is missing.
Adds the two facts the section was short of. tests/CMakeLists.txt:78 registers
an add_test entry, so ctest has something to run rather than an empty set, and
the Configure step passes neither -DBUILD_TESTING=OFF nor anything else that
would drop the target, which is why it is always built.
Replaces "手动跑一次测试工程" with the command that actually works against the
build directory CI and the local scripts use.
* chore: consume the engine as a library and stop prepare_env clobbering the build
The engine submodule was pinned at a197746, eight commits behind the engine's
main branch and older than the commit that first gave the engine a CMakeLists
of its own. That is why this repository listed the engine's translation units
by hand: at the pinned commit there was no target to link against. There is
one now, and Apple and Linux already consume it, so do the same here.
- advance the engine submodule from a197746 to 8cc306c
- add_subdirectory the engine and link MetasequoiaIme::Engine from the server,
the dictionary replay tool and the settings executable, dropping the two
hand-maintained source lists
- point the engine and msimeui submodule URLs at MSIME-Engine and MSIME-UI,
and update the README, which still used the pre-rename repository names
prepare_env.py, which the README tells every new contributor to run first,
overwrote four tracked build files from templates under scripts/config_files
that had fallen years behind:
- CMakeLists.txt was replaced by a 147-line copy of what is now a 400-line
build, dropping opencc, cpp-pinyin, msimeui, settings and the panels
- tests/CMakeLists.txt was replaced by an unrelated generic WinCppTemplate
- CMakePresets.json lost the vcpkg-release preset that llaunch-release.ps1
needs
Running the documented first build step therefore broke the build. The script
now only writes the two files that are genuinely generated, and rewrites
CMakePresets.json in place instead of copying a stale template over it.
The .clangd generation addressed template lines by index and the indices had
drifted: the Boost path was written over the WebView2 include and the Sciter
path over a comment, leaving clangd unable to resolve the WebView2 headers.
The template now carries named placeholders, and the script fails loudly if
any are left unsubstituted.
Also remove src/sciter. It is a third candidate window backend that no
CMakeLists has referenced for some time; the shipped backends are d2d and
webview2, and those are the only two ui_backend accepts.
* docs: keep the historical checkout directory names in the build steps
The clone URLs had to change, since the old ones only resolved through
GitHub's rename redirect, but cloning without an explicit target would also
have renamed the working directories. MSIME-Windows's AGENTS.md records that
the historical directory names are deliberate: msime-installer's
Prepare-PackageFiles.ps1 expects siblings called MetasequoiaImeServer and
MetasequoiaImeDict, and the release workflow checks the current repositories
out into exactly those paths. Clone into them explicitly instead.
* chore: re-pin the engine at the WIN32_LEAN_AND_MEAN fix
Moves the submodule from 8cc306c to f91cd20. That carries metasequoiaime/MSIME-Engine#14,
which stops the engine exporting WIN32_LEAN_AND_MEAN as a PUBLIC compile definition and so
unblocks MetasequoiaImeSettings, whose WebView2.h include failed with 101 instances of C2146
against the trimmed Windows.h the definition forced on every consumer.
The range also picks up the two dependabot merges the engine took in the meantime: #12 bumps
utfcpp and #13 bumps the pinned action SHAs.
* build(tests): link the engine target instead of listing its sources
The test executable hand-listed the same 41 engine translation units the root
CMakeLists.txt did, and compiled them without NOMINMAX. That went unnoticed
while the pinned engine predated core/key_event.h; now that the engine headers
pull in Windows.h, the min and max macros collide with std::max and the build
fails with C2589 in quanpin_query.cpp.
Linking MetasequoiaIme::Engine fixes it at the source: NOMINMAX is one of the
definitions the engine exports PUBLIC precisely because it changes the declared
API surface.
* docs(agents): correct the tests wiring and describe the new prepare_env
Two sections went stale or were inaccurate.
The tests section said the root CMakeLists.txt does not reference tests/ at
all. It does: include(CTest) at :8 defaults BUILD_TESTING to ON and :411
add_subdirectory(tests) picks it up, so the test target is configured and
compiled on every CI run. This PR proved it from the other side, by breaking
the test target's build with a NOMINMAX regression that CI caught. The real
gap is narrower and still worth the warning: ci.yml has no ctest step, so the
executable is built and never run.
The generated-files section described prepare_env.py copying templates over
CMakeLists.txt and CMakePresets.json by fixed line index. This PR removes that
behaviour, so the section now describes what the script actually does.
The baseline bump in #17 broke main. builtin-baseline names a commit in the vcpkg clone on the
machine, and the one bundled with the Windows runner is shallow, so it does not have a commit from
yesterday:
fatal: path 'versions/baseline.json' exists on disk, but not in '04a9d8e5...'
while loading baseline version for curl
The old baseline worked only because it was old enough to be present locally, which means the
mechanism was going to fail on any sufficiently fresh pin.
Declaring the default registry as a git registry makes vcpkg fetch the pinned commit itself. That is
what MSIME-Windows already does, and MSIME-Engine#17 has now confirmed the same fix on all three
platforms. vcpkg-configuration.json existed here as an empty object, so this fills it in.
The pinned baseline was 66c0373d, committed 2026-01-16 and 2870 commits behind. Nothing was watching
it: the dependabot template in this org declares only github-actions and gitsubmodule.
Eight ports move. The pairing worth watching is spdlog 1.15.1 to 1.17.0 alongside fmt 11.0.2 to
12.2.0, since spdlog builds against fmt and a mismatched pair is a familiar way to break a build.
webview2 also moves a long way, 1.0.2277.86 to 1.0.4191.47.
CI builds Release here but runs no tests, so a green check means it compiles and nothing more. The
WebView2 loader and the candidate window are the parts to exercise by hand before trusting this.
MSIME-Windows carries an AGENTS.md that opens by saying it governs the whole project, but it is
invisible from here. This points at it and records what is specific to the Server.
Two of the entries are defects rather than conventions, written down because they are load-bearing
until someone fixes them. The binary directory has to be literally named build, since CMakeLists.txt
hardcodes CMAKE_SOURCE_DIR/build/vcpkg_installed in three places for the WebView2 loader; the repo's
own vcpkg-release preset sets binaryDir to build-release and therefore does not link. And the 34
test sources under tests/ are a separate project the root CMakeLists.txt never references, with no
ctest step in CI, so a green check says only that the Server compiles.