Repository navigation
📢 重要通知
- 外部代理模式下,请确认系统里安装的 CA 与
ca/目录中的那一张一致。若ca/曾被删除重建,系统证书库里可能残留旧 CA,外部客户端会因证书不被信任而连不上;删掉旧的、重新安装ca/akagi-ng-ca.cer即可。内置窗口不受影响——它现在自己信任代理签发的证书
🎨 UI/UX 增强
- 重做暗色模式背景:暗色此前沿用亮色的四角渐变,只是把不透明度从 0.2 提到 0.3——四角同时被点亮,中间反而是一片灰紫。现在四种色相改为带实心核心的模糊色块,底色降到
zinc-950,再加一层细噪点压掉大面积暗色渐变必然出现的色带。 - 界面统一到一套尺度:按钮曾有四种高度、三种焦点环、六种圆角(其中三种在 token 之外),还有一批在 Tailwind v4 下根本不生效的动效时长(
duration-*只改过渡,不改animate-in)。现在尺寸收成1–4(24/32/36/40px)、圆角收成--radius-1..6、.focus-ring是唯一的焦点样式,IconButton是独立组件且aria-label类型必填——纯图标按钮不填无障碍标签无法通过编译 - 无障碍语义按 Radix 对齐:
CapsuleSwitch与主题开关改用ToggleGroup,渲染成真正的单选组(role="radiogroup"+role="radio"+aria-checked),不再用aria-pressed冒充;Alert不再在容器上挂role="alert";SettingsItem补上aria-describedby - 退出动画在任何退出方式下都会播放:此前只挂在某一个按钮上,从托盘退出、从系统退出、或从启动错误页退出都会直接硬关闭
- 设置面板只保留标题:描述行只是在重复标题
- 四个语种的界面文案重写:在线设置里「开启后……」改成完整句子,密钥尾号、超时、套餐等标签补上冒号与量词;「在线服务暂时不可用 / 已回退到本地模型」改成能区分「本次回退」与「持续中断」的说法
🐛 问题修复
- 加杠那一瞬间会多推一条无意义的推荐:决策点判定对任何加杠、拔北都会询问,不区分座次。你自己加杠之后引擎仍停在
WaitAct,decide()照样返回一组非强制动作,于是先发出一条推荐、摸到岭上牌后又发出一条——同一个决策出现两行,而第一行针对的是已经交出回合的手牌。现在只在他家宣言时询问,那里抢杠才是真实选项 - 模型指示器的黄灯状态永远不可达:熔断器在第一次请求失败时就布防,一次超时同时报告了「已回退」与「持续中断」,HUD 取更具体的那个,于是任何网络抖动都直接变成闪红五秒。现在两个标志重新对应两件不同的事:第一次失败只算临时回退,下一次决策立刻重试;从连续第二次超时起才按五秒起步、逐次翻倍退避
- 启动游戏的错误提示在两种情况下都在说谎:一是页面还没加载完就关掉窗口,Electron 报
ERR_FAILED,被当成 URL 有问题,于是让你去检查一个根本没问题的设置;二是外部代理模式下内置窗口从不信任我们自己的 CA,代理重签的 TLS 被 Chromium 拒绝,每次启动都失败。现在关闭窗口被识别为无操作,证书信任只放行那张 CA 真正签过的叶子证书 - 代理没运行时提示指错了方向:代理连接失败与 URL 错误此前共用一个提示。失败原因现在随结果返回(IPC 会把异常压成字符串,而只有渲染进程知道该说哪种语言),代理问题直接说明代理未启动
⚡ 改进与优化
- 归档旁边不再有解包目录:
.node无法从 asar 内直接dlopen,Electron 会先把它解到临时文件,asarUnpack就是为此存在的,而它同时产生了app.asar.unpacked。原生推理引擎现在直接随包放在resources/engine/,改用 napi 生成 loader 本来就优先生效的NAPI_RS_NATIVE_LIBRARY_PATH指过去,于是整个目录消失。顺带把永不启用的tls-impersonate预编译文件(含 darwin / linux 各两份)一并排除 - 图标按平台分发:Windows 包不再携带 318 KB 的
.icns和 macOS 托盘模板,反之亦然 - 安装后体积 339.6 MB(原 353.1 MB),压缩包 145.4 MB(原 146.3 MB)
🛠️ 维护与重构
- 一个协调器接管所有退出路径:
ShutdownCoordinator经before-quit进入,幂等(第二次点击并入第一次),把两段时长下发给渲染进程并等待确认、以超时兜底;游戏窗口与 HUD 改用setOpacity原生淡出——此前那个事件发给了承载远程内容、根本没有监听者的游戏窗口 - 渲染进程的每个事件都有了类型:主进程主动推送的 8 个通道此前在各调用点都是裸字符串,一侧改名另一侧毫无察觉;其中两个还用
as强转,payload 掉字段会一路走到 HUD 才炸。现在api/events.ts统一定义,引擎 payload 在边界解析,渲染不了的值丢弃并记日志 - 窗口的会话规则(分区、权限白名单、代理、证书信任)从
game-window抽到game-session,启动失败契约抽到game-launch,game-window因此比改动前还短 - 主题、设置、尺寸各自只剩一个来源;
Header的 8 个 prop 收成 3 个,Dashboard从 174 行拆到 88 行 - 在线账户的几个操作补齐
try/catch/finally,按钮不会因为一次异常永久卡在加载态
📦 更新依赖
- Electron 44 → 45(Chromium 156、Node 24.22.1),运行时体积减少 11.4 MB
- i18next 26.5.11、react-i18next 17.1.9、react-router 8.5.1、lucide-react 1.64.0、react-toastify 11.2.0、typescript-eslint 8.72.1
🧪 测试与质量
- 全仓 548 个单元测试、23 个集成测试、60 个 Rust 测试
- 新增
game-launch-failure.test.ts,把「用户关窗」「重定向取消」「代理不可达」「URL 真的错了」四种情况分别钉住
📝 文档更新
- README 的 AI 模型特性补上 MJOT 云端推理
📢 Important Notices
- In external proxy mode, check that the certificate installed in your system is the same one in
ca/. If that folder was ever deleted and regenerated, the old CA can linger in the OS store and external clients will fail to trust the proxy; delete it and installca/akagi-ng-ca.ceragain. The built-in window is unaffected — it trusts the proxy's certificates itself now
🎨 UI/UX Enhancements
- *Refresh dark mode background: it used to reuse the light theme's four corner washes at a higher opacity, which lit every corner at once and left the middle a flat grey-purple. The four hues are blurred colour fields with a solid core now, over a darker base, with a fine grain layer that removes the banding a large dark gradient always shows.
- The interface is on one scale: buttons had four heights, three focus rings, six corner radii (three of them outside the token set), and a batch of durations Tailwind v4 never applied, since
duration-*sizes transitions rather thananimate-in. Sizes are1–4(24/32/36/40px), radii are--radius-1..6,.focus-ringis the only focus treatment, andIconButtonis its own component witharia-labelrequired — an icon-only control cannot ship without a label - Semantics now match what Radix renders:
CapsuleSwitchand the theme switch areToggleGroups, which render as a real radio group (role="radiogroup"+role="radio"+aria-checked) instead of borrowingaria-pressed;Alertno longer putsrole="alert"on a container;SettingsItemgainedaria-describedby - The exit animation plays whichever way you quit: it was wired to one button, so quitting from the tray, from the OS, or from the startup error page skipped straight to a hard close
- The settings panel is a title and nothing else: the description underneath only repeated the heading
- Notices were rewritten in all four locales: hints read as full sentences, labels carry their colon and measure word, and the online-model states distinguish "stepped aside for this decision" from "sustained outage"
🐛 Bug Fixes
- An added kan produced a recommendation nobody could act on: the decision-point test asked at every added kan and pulled north regardless of seat. After your own added kan the engine still reports itself as ready to act, so a row appeared for a hand that had already given up its turn, then a second row when the replacement tile arrived — two rows for one decision. It only asks when another seat declares now, where robbing the tile is a real option
- The amber status of model status indicator light was unreachable: the circuit breaker armed itself on the first failed request, so a single timeout reported both "fell back" and "sustained outage". The HUD picks the more specific of the two, which meant every blip went straight to pulsing red for five seconds. A first failure is a hiccup again — the next decision retries at once — and the backoff only starts from the second consecutive failure
- The launch error toast lied in two different ways: closing the window before the page arrived surfaced as
ERR_FAILEDand was reported as a broken URL, sending you to check a setting that was never wrong; and in external proxy mode the window never trusted our own certificate authority, so every launch failed on TLS. Closing a window you just opened is correctly a no-op now, and the trust only accepts leaf certificates that CA actually signed - An unavailable proxy was blamed on your settings: proxy failures and URL failures shared one message. The reason travels with the result now (IPC flattens an exception into a string, and only the renderer knows which language to say it in), so an absent proxy says so
⚡ Improvements & Refinements
- Nothing is unpacked next to the archive any more: Windows will not
dlopena.nodethat only exists inside asar, so Electron extracts it to a temp file first — that is whatasarUnpackis for, and it is what producedapp.asar.unpacked. The native engine ships toresources/engine/instead, reached throughNAPI_RS_NATIVE_LIBRARY_PATH, which napi's generated loader already prefers over its own path guessing; the directory is gone. Unused prebuilds of a module we never enable went with it, including the darwin and linux builds - Icons ship per platform: a Windows install no longer carries a 318 KB
.icnsand the macOS tray templates, and vice versa - 339.6 MB installed (was 353.1 MB), 145.4 MB compressed (was 146.3 MB)
🛠️ Maintenance & Refactoring
- One coordinator owns every quit path:
ShutdownCoordinatoris reached throughbefore-quitand is idempotent, so a second click joins the first. It hands the renderer both durations and waits for an acknowledgement, with a timeout as the backstop, while the game and HUD windows fade throughsetOpacity— the event used to go to the game window, which hosts remote content and listens for nothing - Every event the renderer receives is typed: the eight channels the main process pushes on its own were raw strings at each call site, so a rename on one side stayed silently correct on the other; two were coerced with
as, which let a payload that lost a field reach the HUD and throw there. They are named once inapi/events.ts, and engine payloads are parsed at the boundary — a value that cannot be rendered is dropped and logged - The window's session rules (partition, permissions, proxy, certificate trust) moved out of
game-windowintogame-session, and the launch contract intogame-launch, which leavesgame-windowshorter than before these fixes - Theme, settings and sizes each have one source now;
Headerwent from eight props to three, andDashboardfrom 174 lines to 88 - The online account actions gained their
try/catch/finally, so a single exception can no longer leave a button spinning forever
📦 Dependency Updates
- Electron 44 → 45 (Chromium 156, Node 24.22.1), 11.4 MB smaller at runtime
- i18next 26.5.11, react-i18next 17.1.9, react-router 8.5.1, lucide-react 1.64.0, react-toastify 11.2.0, typescript-eslint 8.72.1
🧪 Testing & Quality
- 548 unit tests, 23 integration tests and 60 Rust tests in total
game-launch-failure.test.tspins the four cases apart: the user closing the window, a cancelled navigation, an unreachable proxy, and a URL that really is wrong
📝 Documentation
- README's AI model section mentions MJOT online inference
Full Changelog: v1.2.2...v1.2.3