Commit Graph
663 Commits
Author SHA1 Message Date
Neptrue_Lin 972cf729c9 Merge pull request #326 from ouchihao/codex/fix-298-numpad-punctuation
fix(windows): commit highlighted candidates with numpad punctuation
2026-09-11 20:18:16 +08:00
Neptrue_Lin 3eb82c7878 Merge branch 'develop' into feat/issue-234-rime-dict-import 2026-09-11 20:06:16 +08:00
Ouchihao 1d2431add6 fix(windows): commit highlighted candidates with numpad punctuation 2026-09-11 19:32:17 +08:00
fanlusky 73ec6fe9c9 perf(ci): cache the Server MSVC build with sccache
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.
2026-09-11 03:46:30 +08:00
Lu Fan(SonnyCalcr) 0eaa35eed1 Merge pull request #274 from linyanm/fix/issue-155-settings-lag
fix(server,ui-html): 降低设置窗口拖动与工具栏首屏卡顿
2026-09-10 21:33:31 +08:00
fanlusky 9447881c54 Merge branch 'develop' into fix/dictionary-replay-non-ascii-path
Conflict resolutions:

* windows/src/Utils/FanyUtils.h -- develop deleted GetIMEDataDirPath too
  (via #273), so both sides had identical content and the whole-file
  conflict was only develop's .gitattributes LF normalisation. Took
  develop's version; the symbol is now gone repo-wide.

* server/src/skin/candidate_skin_catalog.cpp -- #273 landed the same
  wide-path manifest read on develop. Took develop's code verbatim so
  this hunk is a no-op against develop, keeping only the comment that
  explains why toml::parse_file(manifest.string()) is unusable here.

* server/tests/src/test_quanpin_scheme.cpp (x2) -- union: this branch's
  test::Utf8(db_path) plus develop's `both` autocorrect-flag local.

Verified: server Release build clean, 282 tests pass.
2026-09-10 20:50:17 +08:00
fanlusky 093b485124 Merge branch 'develop' into fix/issue-242-settings-cannot-save
冲突解决:

- windows/src/Utils/FanyUtils.h:develop 通过新增的 .gitattributes 把整个文件
  从 CRLF 规范化成 LF,与本分支删除 GetIMEDataDirPath() 声明的改动整文件冲突。
  取 develop 的 LF 版本,再删掉该声明。
- server/tests/src/test_config_non_ascii_path.cpp:仅 include 块冲突,取两侧并集
  (algorithm/filesystem/fstream/iterator/string/system_error)。另修正一处错误的
  自动合并——develop 新增的候选回退字体断言本属于
  config_round_trips_under_non_ascii_profile_path,git 却按上下文并进了本分支新增的
  config_migrates_legacy_acp_mangled_path,已移回原测试。
2026-09-10 19:33:43 +08:00
Lu Fan(SonnyCalcr) 184444cf7f Merge pull request #272 from linyanm/fix/issue-267-d2d-toolbar-size
fix(server): 设置变更时重建 D2D 悬浮工具栏外观
2026-09-10 19:18:07 +08:00
Jia Sui fe7b4ab37d fix(server): 修复 test_quanpin_scheme 在 CI MSVC 下编译失败
- 跨不同长度数组的初始化列表推导不出共同指针类型(C3535):改为边界对
  + lambda 遍历,语义与原双表循环一致
- QuanpinAutocorrectCutRejectsInputsOutOfScope 引用了另一用例的局部 none:
  改为字面量 0u
- 根因:本地验证时构建失败被日志截断掩盖,ctest 跑了陈旧二进制给出假绿;
  已改为显式检查构建退出码并确认测试用例进入二进制
2026-09-10 17:42:55 +08:00
Jia Sui 2fcd57c88d style(server): 全拼纠错相关代码过 scripts/format.sh
- 生成脚本 Count 行模板改为单行,与 clang-format 输出一致,
  生成器 --check 与 format.sh --check 从此不再互斥
- test_quanpin_scheme 格式化
2026-09-10 17:32:17 +08:00
Jia Sui f8f13e7083 feat(server): 全拼纠错分类开关配置接线与候选标记
- 配置拆为 quanpin.autocorrect_transposition/neighbor 平铺键(默认关),
  旧键 autocorrect 废弃不读取不迁移;写入时段缺失自动追加,
  出厂模板无 [quanpin] 段时设置页保存不再静默失败
- engine_input_session 用 Engine 命名常量合成位掩码注入
- settings_app 补 configSnapshot quanpin 段与 ApplyConfigUpdate 路由
- BuildCurrentCandidatePage 按 corrected_from 给 view.text 追加纠错标记,
  msimeui 与 WebView2 两渲染路径共同源单点注入,提交/词频路径不受污染
- 生成脚本 add_variant 键长下限 2→3(2 字母空间归简拼),重建表 747+3244
- 测试:AC9 配置往返与旧键忽略、开关矩阵适配新签名、守卫用例
2026-09-10 17:31:51 +08:00
Neptrue_Lin ff7650bba2 Merge pull request #293 from Aegon8719/feat/issue-55-fallback-fonts
issue-55 候选窗支持可配置的有序回落字体
2026-09-10 17:14:29 +08:00
Aegon 8a78b02271 Merge branch 'develop' into feat/issue-52-shortcuts 2026-09-09 18:18:21 +08:00
Aegon8719 dea9292814 issue-55 候选窗支持可配置的有序回落字体 2026-09-09 18:03:56 +08:00
Aegon8719 848cf6a288 issue-288 修复候选窗在光标坐标无效时提前显示的问题 2026-09-09 11:40:26 +08:00
fanlusky ac8394291b fix(tests): keep the shared test header free of engine dependencies
The portable test target builds on a bare Linux runner with no vcpkg tree, so
the UTF-8 path helper cannot live in test_framework.h -- including the engine's
data_path.h there broke the sanitizer build. Move it to test_utf8_path.h, which
only the Windows tests that need it include.
2026-09-09 04:48:56 +08:00
fanlusky 55605973d8 fix: build filesystem paths as UTF-8, not through the ANSI code page
Narrow strings in this repo are UTF-8, but std::filesystem converts narrow
strings through the system ANSI code page. The two agree only on ASCII, so any
path crossing that boundary breaks for users whose profile directory is not
ASCII -- and on a code page that cannot represent the characters at all,
path::string() throws rather than mangling.

The user dictionary replay tool hit exactly that. It handed the journal API
paths converted with path::string(); on a Chinese user name the conversion
threw, the exception escaped main(), and MSVC fastfailed the process with
0xC0000409. The installer treats any non-zero exit from replay as fatal, by
design, so those users could not upgrade at all. Build the paths from the wide
form via path_from_utf8/path_to_utf8 instead, and wrap main() so an unexpected
failure reports a diagnosable message and exit 1 rather than a fastfail code.

The skin catalog had the same defect: toml::parse_file(manifest.string())
routed the manifest path through the ANSI code page, and skins live under the
user profile. The throw escaped past the toml handlers around it. Read the file
through the wide path and parse the text instead.

Also drop FanyUtils::GetIMEDataDirPath, which built a path from the ANSI
environment block and dereferenced getenv without a null check. It had no
callers.

Tests cover the three layers this spans: the conversion contract itself, the
journal API in-process, the replay tool as the installer actually launches it,
and the skin catalog. Each non-ASCII case is paired with an ASCII control so a
failure says whether the path encoding or the data caused it. The replay and
skin tests were both confirmed to fail with their fix reverted.

Test fixtures pass paths through the new test::Utf8 helper rather than
path::string(), since the temp directory sits inside the user profile too.
2026-09-09 04:35:34 +08:00
fanlusky 28689361ad fix(ci): unblock packaging on the self-hosted runners
Two independent failures kept the v0.6.5 installer from being built after
the pipe probe was fixed.

The package job died in "Verify the release commit is on main" with git
exiting 128 on dubious ownership: runner instance 2 leaves _work owned by
SYSTEM while the job runs as `runner`. actions/checkout is unaffected, so
the first plain git call is what fails, and only jobs scheduled onto that
instance see it. Mark the workspace as safe in the three self-hosted jobs.

Neither vcpkg manifest carried a builtin-baseline, so vcpkg resolved ports
against the registry at HEAD and every build depended on a live fetch of
microsoft/vcpkg. When that fetch degraded, the ports simply vanished:

    Fetching registry information from https://github.com/microsoft/vcpkg (HEAD)...
    fmt does not exist
    utfcpp does not exist

Build TSF x64 failed this way twice and passed twice on the same runner,
which is what an unpinned registry looks like. Pin both manifests to
04a9d8e5, the commit vcpkg-lock.json already records as resolved.
2026-09-09 02:18:57 +08:00
fanlusky 0e003fa095 perf(server): time each segment of the per-keystroke candidate build
PrepareCandidateList runs on the shared task thread, so a stall inside it
delays the hide/show messages queued behind it and reaches the screen as
candidate-window flicker. The diagnostic log showed an identical workload
take 8 ms once and 688 ms moments later, but not which segment paid for it.

Split the function into measured segments and emit one "candidate build"
line per keystroke, separating the engine candidate lookup from the
user-dictionary fixed-position pass -- the latter opens a SQLite
connection and runs its schema DDL on every call.

Measurement only; no behaviour change.
2026-09-08 22:53:59 +08:00
fanlusky bb53763e66 fix(server): only grace-delay a candidate hide the worker delivered late
The unconditional 50ms hide grace cost typing its snap: every commit left the
candidate window up for an extra frame or three, and when the next word started
inside the window the pending hide was cancelled outright, so the box stopped
disappearing between words and just morphed instead.

Only a hide that waited behind a stalled task thread can describe state the user
has already typed past, and only that one has a show queued behind it worth
waiting for. Measure the queue wait where the task is dispatched and mark the
posted message; normal typing never reaches the threshold, so it hides
immediately exactly as before.

The wait is now logged per hide task, so the threshold can be checked against a
real capture rather than assumed.
2026-09-08 22:53:59 +08:00
fanlusky cc81f794bb fix(server): stop candidate strobing when the worker queue backs up
Diagnostic logs from a busy machine show the candidate host repainting far
more often than the input changes. Three separate causes, all in the UI
thread's handling of the worker's posted messages:

- A HideCandidate for one keystroke of a burst (backspacing the composition
  empty, then typing again) cloaks a visible window and un-cloaks it ~30ms
  later. Defer the hide on a short grace timer and drop it when a show
  arrives first; a real commit or focus loss still hides, 50ms later.
- Each keystroke posts two WM_SHOW_MAIN_WINDOW messages, since the candidate
  rebuild and the English/cloud merge both request a show. Skip a repaint
  whose preedit, page, selection and caret match the frame already on screen
  within a short window.
- When the worker stalls it drains several keystrokes at once and posts a
  show for each. The handler always renders the newest published state, so an
  older queued show can only repaint what the newer one is about to; drop it
  when a newer show is already queued.

The stall itself (PrepareCandidateList blocking the shared task thread, worst
case 688ms against a median of 11ms) is untouched here.
2026-09-08 22:53:59 +08:00
linym c07aa8e878 fix(server,ui-html): 降低设置窗口激活卡顿 (#155)
激活时不再重推整份配置;HTML 按 UTF-8 二进制读取;契约脚本移到标记之后;最大化按钮矩形用 rAF 合并。
2026-09-08 22:47:26 +08:00
linym 075fbb2a84 fix(server): persist settings after leftover non-ASCII config paths
Elevated setup left high-IL config.toml that medium-IL Settings never
rewrote on machines hit by the ANSI path bug. Make the data directory
writable, migrate ACP-mangled leftovers, rebuild unparseable files, and
keep remaining asset paths on wchar_t.

Fixes #242
2026-09-08 22:42:26 +08:00
linym f588b7f5e8 fix(server): rebuild D2D toolbar when scale or icon size changes
D2D bakes user scale into Visuals at RebuildScene. RelayoutHost alone left the live toolbar unchanged after settings updates.
2026-09-08 22:26:31 +08:00
linyanmandlinym 6e03f57747 fix(server): 全拼导入按汉字数选择切分并识别文件编码 (#270)
无分隔拼音按汉字数另选完整切分,避免西安/xian 误报音节数不一致。设置页按 BOM 识别 UTF-8/UTF-16,无效 UTF-8 再回退 GBK。带撇号的水杉导出三列 TSV 本身已合法,回归测试钉住该格式。

Co-authored-by: linym <lin.yanming@foxmail.com>
2026-09-08 22:52:45 +09:00
linym 2776e157b0 style(server): wrap dictionary import test to satisfy clang-format 2026-09-08 20:58:57 +08:00
linym 28a0c03500 feat(server): 批量导入兼容 Rime userdb.txt 与 dict.yaml 2026-09-08 20:45:14 +08:00
fanlusky 4b3da57134 fix(emoji-panel): use Chinese nav titles to match the tab labels
The sub-page nav titles were English ("Sticker", "GIF", "Kaomoji",
"Symbols") while the Clipboard page was already Chinese, and the text
labels the tabs fall back to when no icon font carries the glyph are all
Chinese. On a Windows 10 machine that actually hits that fallback, the user
taps a tab labelled 贴纸 / 颜文字 / 符号 and lands on a page headed
"Sticker" / "Kaomoji" / "Symbols".

Translate the titles to match the tab labels and drop the per-page font and
size branch, which only existed because Clipboard was the one Chinese title.
Rename kClipboardTitleFontSize to kNavTitleFontSize now that every page uses
it, and move it next to the other nav metrics. "GIF" stays as is: it is an
acronym and the tab label already reads GIF.
2026-09-08 16:33:18 +08:00
Lu Fan(SonnyCalcr) 880c04f6bb Merge pull request #264 from fanlusky/fix/icon-font-fallback-win10
fix(server): fall back to text when the icon font lacks the glyph
2026-09-08 16:01:03 +08:00
Aegon8719 6ff1cff42d issue-52 加入简繁切换快捷键;增加候选词翻页快捷键组合、增加以词定字快捷键组合;
issue-55 候选词与辅助码间保留一个半角空格。
2026-09-08 14:07:48 +08:00
fanlusky 43555460db fix(server): fall back to text when the icon font lacks the glyph
The floating toolbar and the emoji panel hardcoded "Segoe Fluent Icons",
which ships with Windows 11 only. DirectWrite substitutes a font silently
instead of failing, so on Windows 10 every private-use codepoint the toolbar
draws (U+E982, U+F110, U+F138, ...) rendered as a blank box: the buttons
still worked but no longer showed any state.

Resolve the font per codepoint rather than per family, because "Segoe MDL2
Assets" is a subset of Segoe Fluent Icons and swapping the family alone would
still draw tofu wherever a glyph is missing:
- add msimeui::ResolveIconGlyph(), which probes the installed icon fonts with
  IDWriteFont::HasCharacter() and returns the first family that really carries
  the glyph, or no family at all;
- give every toolbar and tab icon a text label to fall back on (中/英, 半/全,
  简/繁, ...) when neither font has the glyph;
- drop the unused ToolbarIconButton::SetGlyph/SetLabel, which would bypass the
  resolved family.

Add unit tests for the resolver, covering the missing-glyph path that cannot
be reached on a host that has Segoe Fluent Icons installed.
2026-09-08 12:41:01 +08:00
fanlusky de4d3acf45 fix(config): handle non-ASCII (e.g. Chinese) user profile paths
std::filesystem::path(std::string) and path::string() convert through the
system ANSI code page, so a non-ASCII (e.g. Chinese) user profile path on a
non-UTF-8 (GBK 936) system was corrupted and every config read/write failed
with "设置保存失败"; on a code page that cannot represent the characters
path::string() threw, crashing the Server at startup in a watchdog restart loop.

Never route config paths through the ANSI narrow encoding:
- build g_config_path and the legacy voice path from the wide accessors;
- read config by opening the wide path + toml::parse(text) instead of
  toml::parse_file(path.string()) (LoadImeConfig, MigrateLegacyVoiceInputConfig);
- build and open the TSF shared config path as a wide std::filesystem::path.

Add a regression test that round-trips config read/write under a Chinese
profile path.
2026-09-08 02:20:37 +08:00
Evan Hu 84ef90cc8d fix(server): test valid escaped CSS URLs 2026-09-07 13:37:22 +09:00
Evan Hu 8972593b3e style(server): format skin CSS policy 2026-09-07 13:32:09 +09:00
Evan Hu 818069f9c3 fix(server): make release probes non-interactive 2026-09-07 13:28:25 +09:00
Neptrue_Lin 4b95c90fde Merge pull request #228 from AaGMix/feat/installer-pdb
feat(installer): include release PDB symbols
2026-09-07 09:46:06 +08:00
AaGMix e9a2c91214 feat(installer): include release PDB symbols 2026-09-07 02:10:38 +08:00
Lu Fan(SonnyCalcr) 5a0b4dc4c2 Merge pull request #206 from LLLin000/fix/custom-translation-settings
fix(server): save custom translation settings
2026-09-07 02:10:37 +08:00
fanlusky 2098206019 fix(server): avoid showing raw preedit for sentence candidates 2026-09-07 00:00:51 +08:00
LLLin000 190a657c57 fix(server): persist custom translation settings 2026-09-06 22:28:31 +08:00
Evan 8f190102d0 Merge pull request #199 from ouchihao/codex/fix-185-horizontal-translations
feat(server): show translations in horizontal candidate windows
2026-09-06 19:50:37 +09:00
Evan Hu 205cafa4f5 fix(skin): @import 剥离只匹配真正的 at-rule;补转义绕过的不变量说明与用例
自查时发现的两处。

**@import 匹配过宽会损坏合法皮肤。** 此前在任意位置匹配 `@import`,于是 `.a{content:"@import //x";color:red}` 会被从 `@` 切到该声明的分号,留下一个未闭合的字符串和一份坏掉的样式表——为了保护皮肤反而弄坏了一个本来没问题的皮肤。改成只在 at-rule 能出现的位置匹配:样式表开头,或 `;` / `}` 之后;并要求关键字后面不是字母数字,避免 `@importantthing` 被当成 `@import`。

**CSS 转义不是漏洞,但原因不显然,值得钉死。** 样式表可以写 `url("https:\/\/host/x.png")`,浏览器解转义后得到一个可用的远程 URL,而字面文本里没有 `://`,分类器给出的是 Embed 而不是 Drop。这是安全的,但安全性来自 Embed 能产出什么:要么把文件内联成 data: 负载,要么回退到 candidate-skins 虚拟主机下的 URL,两者都无法命名皮肤作者选定的 origin。转义过的 `..` 同理——它以字面转义文本的形式进入文件系统查找,不会穿越,也永远不会被写回文档。

也就是说,真正保护候选窗的不变量是「Embed 永不产出攻击者命名的 origin」,而不是「Drop 拦住了远程 URL 的每一种写法」。把这条写进头文件,并补一个用例钉住「转义写法永远不落到 Keep」——将来若有人改动 Embed 的回退逻辑让它原样输出 URL,这个洞会重新打开,用例会先失败。

本机以 -Wall -Wextra -Wpedantic -Werror + ASan/UBSan 编译运行 8/8 通过,portable 目标经 CMake + ctest 验证,clang-format 干净。
2026-09-06 19:33:39 +09:00
Ouchihao 3fc7ba295f feat(server): show translations in horizontal candidate windows 2026-09-06 18:09:01 +08:00
Evan Hu e645c9abaa fix(skin): 修复 GCC 下的符号比较告警与 clang-format
两处 CI 反馈:

towlower() 在 glibc 上返回 wint_t(无符号),与 wchar_t 比较触发 -Wsign-compare。macOS 上 wchar_t 是 32 位有符号、towlower 返回 int,所以本机以 -Wall -Wextra 编译不会告警。显式转回 wchar_t 再比较。

windows_webview2.cpp 里那处调用的换行不符合仓库的 clang-format 配置,按配置重排。

重新以 -Wall -Wextra -Wpedantic -Werror + ASan/UBSan 构建运行,6 个用例全过。
2026-09-06 18:28:30 +09:00
Evan Hu 1c25466511 test(ci): 给解析不可信输入的宿主无关代码加 ASan / UBSan
MSIME-Linux 的 ci.yml 注释里已经写下了这个结论:「前端层才是解析不可信输入的那层……Windows 前端上次审计修的就是一批 race 和空指针,现有的 Debug 构建一个都抓不到。」Linux 仓照这个结论补了 sanitizer job,Windows 仓一条都没有。

给整个 Server 上 sanitizer 意味着要在 Windows runner 上做一份带 sanitizer 的 Boost、curl、sqlite 和 WebView2,代价很高。这里换一个能立刻落地的做法:把宿主无关的解析与策略代码单独建成一个不依赖任何包管理器的小目标,在 Linux runner 上用 ASan + UBSan 跑,几秒钟完成,每个 PR 都跑。

覆盖面确实小于全量 sanitize 构建,但它是真的在跑。目标从皮肤 CSS 策略起步,后续凡是能与 Windows 头文件解耦的、读取用户没写过的数据的逻辑(词库文件、IPC 负载、配置解析)都可以加进这个列表。

该目标同时以 -Wall -Wextra -Wpedantic -Werror 编译,与 Linux、Apple 两仓的标准一致。本机已用 ASan+UBSan 构建并运行通过。
2026-09-06 18:11:37 +09:00
Evan Hu 87e842f6a3 build(windows): TSF、Server 与设置程序开启 /W4
/W4 已经是本仓的既有约定——ui/ 的五个 target 和 server/ 的四个辅助可执行文件都在用。漏掉的恰好是三个最大的:注入到 Chrome、QQ、Office 等宿主进程里的 TSF DLL,拥有全部网络调用、WebView2 宿主与皮肤加载的 Server 主进程,以及设置程序。这三个此前都跑在默认的 /W3 上。

暂不加 /WX:存量告警要先在 Windows 上读完并清掉,在那之前把告警变成错误只会逼出一堆 /wd 抑制。/permissive- 同样留到能在 Windows 上构建验证的后续改动,本次改动无法在本机编译验证这一项。
2026-09-06 18:11:37 +09:00
Evan Hu 680fdfa648 fix(skin): 丢弃第三方皮肤 CSS 里的远程引用,不再原样注入候选窗
皮肤是用户往 %LOCALAPPDATA%\metasequoiaime\skins 里丢的文件夹,是彻头彻尾的第三方内容,而它的 CSS 会被内联进候选窗文档。此前 IsRewritableSkinCssUrl() 对含 :// 的 URL 直接返回 false,而 false 分支把原始 url("…") 原封不动写回样式表——也就是说 `background-image: url(https://attacker/beacon.png)` 会在每一次候选窗渲染时向外发一个请求,泄露「此人此刻正在打字」以及 IP。这直接推翻了 PRIVACY.md 的 "Local conversion never contacts the network",而那句话是这个项目最强的信任资产。

问题出在这个判断只有两态:不可重写就等于放行。实际上有三类:

- Embed——皮肤包内的相对路径,读文件内联;
- Keep——本身就是惰性的 data: 负载和文档内 #fragment 引用,照原样保留;
- Drop——任何会让候选窗向皮肤包之外取数据的写法。丢掉 URL 后声明不完整,CSS 会整条丢弃,这正是想要的结果。

Drop 覆盖 http/https/ftp 等带 scheme 的、协议相对的 //host/x(不写 scheme 但照样出网)、路径穿越,以及会逃出皮肤包的绝对路径。

url() 不是唯一入口:`@import "https://…"` 根本不含 url() token,扫描器永远看不到它。补 StripRemoteImports() 做前置处理,只删远程 @import,本地的保留。

skin.preview 来自皮肤自己的 skin.toml,此前直接进 EmbedSkinCssUrl。现在同样过一遍分类,不因为它是通过 manifest 字段到达就被信任。

判断逻辑抽到 server/src/webview2/skin_css_policy.{h,cpp},不依赖任何 Windows API,配 6 个用例覆盖三类判定与两种 @import 写法。本机以 -Wall -Wextra 编译运行全部通过。
2026-09-06 18:05:09 +09:00
fanlusky 8544e74dc5 fix: Fix the position issue of the WebView2 candidate window when it touches the screen edge. 2026-09-06 14:59:19 +08:00
Evan 688972b28c Merge remote-tracking branch 'origin/main' into pr184-merge
# Conflicts:
#	server/src/session/input_session.h
2026-09-06 15:31:05 +09:00
Evan Hu ac805078e4 refactor(server): carry engine online queries through asynchronous candidate tasks 2026-09-06 13:24:03 +09:00