diff --git a/ui-html/.github/dependabot.yml b/ui-html/.github/dependabot.yml
new file mode 100644
index 00000000..4fff89a6
--- /dev/null
+++ b/ui-html/.github/dependabot.yml
@@ -0,0 +1,27 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ groups:
+ actions:
+ patterns:
+ - "*"
+ commit-message:
+ prefix: "chore(deps)"
+ open-pull-requests-limit: 5
+
+ # The settings page is the one part of this repository that is built rather than shipped as
+ # source, and it sits on the Windows release path, so its dependencies need watching.
+ - package-ecosystem: "npm"
+ directory: "/webview2/settings/ime-settings"
+ schedule:
+ interval: "monthly"
+ groups:
+ npm:
+ patterns:
+ - "*"
+ commit-message:
+ prefix: "chore(deps)"
+ open-pull-requests-limit: 5
diff --git a/ui-html/.github/workflows/ci.yml b/ui-html/.github/workflows/ci.yml
new file mode 100644
index 00000000..4976aa23
--- /dev/null
+++ b/ui-html/.github/workflows/ci.yml
@@ -0,0 +1,53 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.event.pull_request.head.ref || github.ref_name }}
+ cancel-in-progress: true
+
+jobs:
+ settings:
+ name: Build settings page
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ defaults:
+ run:
+ working-directory: webview2/settings/ime-settings
+ steps:
+ - name: Check out source
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
+ with:
+ version: 10
+
+ - name: Set up Node
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 22
+ cache: pnpm
+ cache-dependency-path: webview2/settings/ime-settings/pnpm-lock.yaml
+
+ - name: Install
+ run: pnpm install --frozen-lockfile
+
+ # Same command the Windows release workflow runs. Until now it was only ever exercised
+ # during a release, so a break here surfaced while cutting a version.
+ - name: Build
+ run: pnpm build
+
+ - name: Check the output the installer collects
+ run: |
+ test -f dist/index.html
+ echo "dist contents:"
+ ls -la dist
diff --git a/ui-html/README.md b/ui-html/README.md
index 1264953f..2603956a 100644
--- a/ui-html/README.md
+++ b/ui-html/README.md
@@ -1,19 +1,55 @@
# Metasequoia IME UI HTML
-This is html&css&js codes of [MetasequoiaImeTsf](https://github.com/metasequoiaime/MetasequoiaImeTsf).
+水杉输入法 Windows 端的界面资源。Server 加载这里的页面来渲染候选窗、悬浮工具栏、托盘菜单和设置窗口。
-For debug:
+配套仓库:[MSIME-Windows](https://github.com/metasequoiaime/MSIME-Windows)(TSF 前端)、[MSIME-Server](https://github.com/metasequoiaime/MSIME-Server)(后端与窗口宿主)。
-```shell
-git clone https://github.com/metasequoiaime/MetasequoiaImeUiHtml.git
-New-Item -ItemType SymbolicLink -Path "C:\Users\\AppData\Local\metasequoiaime\html" -Target ".\MetasequoiaImeUiHtml"
+## 目录
+
+| 路径 | 内容 |
+|---|---|
+| `webview2/candwnd/` | 候选窗,含 `skins/` 下的皮肤 |
+| `webview2/ftb/` | 悬浮工具栏 |
+| `webview2/menu/` | 托盘菜单 |
+| `webview2/settings/ime-settings/` | 设置页,**唯一需要构建**的部分,Vite + TypeScript |
+| `webview2/default-themes/`、`webview2/other-themes/` | 主题 |
+| `sciter/` | 旧 Sciter 渲染后端的资源,当前默认后端是 WebView2 |
+
+除设置页外都是直接分发的静态资源,改完即生效。
+
+## 构建设置页
+
+设置页是发布安装包的必要输入之一,`pnpm build` 的产物 `dist/` 会被打包脚本收集。
+
+```bash
+cd webview2/settings/ime-settings
+corepack enable
+pnpm install --frozen-lockfile
+pnpm build
```
-e.g.
+pnpm 版本由 `package.json` 的 `packageManager` 字段固定,`corepack enable` 之后不用自己装。开发时用 `pnpm dev` 起本地服务。
-```shell
-git clone https://github.com/metasequoiaime/MetasequoiaImeUiHtml.git
-New-Item -ItemType SymbolicLink -Path "C:\Users\sonnycalcr\AppData\Local\metasequoiaime\html" -Target "C:\Users\sonnycalcr\EDisk\CppCodes\IMECodes\MetasequoiaImeUiHtml"
+## 本地调试
+
+把本仓链接到 Server 的数据目录,改页面就不用重新打包:
+
+```powershell
+$target = Join-Path $env:LOCALAPPDATA 'metasequoiaime\html'
+if (Test-Path -LiteralPath $target) {
+ Remove-Item -LiteralPath $target -Recurse -Force
+}
+New-Item -ItemType SymbolicLink -Path $target -Target (Resolve-Path .).Path
```
-Note: here we must use absolute path.
+必须用绝对路径,`Resolve-Path` 就是用来把当前目录转成绝对路径的。设置页走这条路时链接的仍是仓库目录,页面读的是 `webview2/settings/ime-settings/dist/`,所以改完设置页要重新 `pnpm build`。
+
+## 与 C++ 侧的边界
+
+窗口归 Server,页面归本仓。HWND、尺寸、位置、DPI、Z-order 和 WebView2 controller 的生命周期都在 `MSIME-Server/src/window/` 与 `src/webview2/`;页面结构、样式和浏览器端交互在这里。
+
+C++ → 页面主要经 WebView2 导航和脚本执行,页面 → C++ 经 `window.chrome.webview.postMessage(...)`。**改消息 `type`、JSON 字段、页面导出的 JS 函数或 DOM 标识时,必须同步检查 Server 的消息解析与拼接代码**,两边没有编译期约束。
+
+候选内容和输入状态的权威在 Server 和引擎,页面只负责展示与发出用户动作;不要在页面侧复制候选选择、翻页、输入模式或配置持久化的状态机。
+
+完整的跨仓约定见 [MSIME-Windows 的 AGENTS.md](https://github.com/metasequoiaime/MSIME-Windows/blob/main/AGENTS.md)。
diff --git a/ui-html/webview2/settings/ime-settings/package.json b/ui-html/webview2/settings/ime-settings/package.json
index 4698a87e..6795809b 100644
--- a/ui-html/webview2/settings/ime-settings/package.json
+++ b/ui-html/webview2/settings/ime-settings/package.json
@@ -8,6 +8,7 @@
"build": "tsc && vite build",
"preview": "vite preview"
},
+ "packageManager": "pnpm@10.34.5",
"devDependencies": {
"@types/chrome": "^0.1.29",
"@types/node": "^24.10.1",
diff --git a/ui-html/webview2/settings/ime-settings/pnpm-workspace.yaml b/ui-html/webview2/settings/ime-settings/pnpm-workspace.yaml
index efc037aa..09a02ca1 100644
--- a/ui-html/webview2/settings/ime-settings/pnpm-workspace.yaml
+++ b/ui-html/webview2/settings/ime-settings/pnpm-workspace.yaml
@@ -1,2 +1,4 @@
+allowBuilds:
+ esbuild: true
onlyBuiltDependencies:
- esbuild