diff --git a/AGENTS.md b/AGENTS.md
index 3dd95b4..f060e7b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,4 +1,4 @@
-# AGENTS.md — 班级小助手(Class Helper)
+# AGENTS.md — ClassHelper(Class Helper)
本文件写给在本仓库里干活的 AI 编码代理。先读这里,再动代码。
文中「本机环境」「已知坑」两节按**实测**写成;若与本机现状不符,以本机实测为准并顺手改掉这里。
@@ -28,7 +28,7 @@
▼
Socket.IO ──► class:{classId} 房间 ──► 学生桌面客户端实时更新 UI
-ClassIsland(教室机器)──► 班级小助手联动插件 ──► /api/integrations/classisland/*(设备令牌鉴权)
+ClassIsland(教室机器)──► ClassHelper联动插件 ──► /api/integrations/classisland/*(设备令牌鉴权)
▲ │
└──── 老师下发的提醒(上报返回值里顺带带回)◄┘
```
@@ -73,18 +73,24 @@ docs/ **文档站**(docfx 项目;不进 pnpm workspac
docfx.json / toc.yml(顶栏)+ 各章节自己的 toc.yml(侧栏)
get-started/ app/ management/ dev/(四章内容)
management/{production,linux-deploy,mysql}.md(部署与换库)
- dev/winisland-design-tokens.md / screenshots/(回归留档的截图)
+ dev/winisland-design-tokens.md
+ images/(**展示图**:文档站 / README / 官网共用的同一份,1× 逻辑像素)
+ screenshots/(回归留档的截图:island / client / web-mobile / classisland,
+ 页面不展示;两者分工见 docs/dev/testing.md「截图留档」)
templates/classhelper/(模板覆盖:_master.tmpl + public/main.css + 图标)
build.mjs(构建,注入版本号与 404 的 base)/ serve.mjs(本地预览 :5181)
_site/(构建产物,gitignored)
⚠ 站点的顶栏版本徽标取自根 package.json(build.mjs 用 --metadata 注入),
不要在文档里再写一份版本号。
+ ⚠ 页面里的照片一律指向 images/ —— 它就是 README 与官网吃的那一份。
+ 别再往 screenshots/ 引图:那是冒烟脚本滚动的留档,随时被下一次验证覆盖。
⚠ 模板目录里的 logo.png / favicon.png 由 `pnpm icons` 派生(见 §5 第 62 条),
别手工替换;改图标只改 build/classhelper.png 再跑 pnpm icons。
⚠ 文档站只发布 docs/ 这一棵树,页面里**不要**写指向 docs/ 之外的相对链接
(如 ../AGENTS.md)—— 发布出去全是 404,要用 GitHub 的绝对 URL。
website/ 产品官网(**纯静态、无构建步骤**,不进 pnpm workspace)
- index.html / assets/{styles.css,motion.js,main.js,icon.png,shots/}
+ index.html(门面)/ download.html(下载页,首页的「立即下载」跳到它)
+ assets/{styles.css,motion.js,main.js,download.js,icon.png,shots/}
serve.mjs(本地预览)/ tools/capture-shots.mjs(实机图采集)
404.html(**站点根** 404,含 {{BASE}} 占位符,由 build-pages.mjs 注入)
⚠ serve.mjs 把 ../docs/_site 挂在 /docs/ 下,为的是让本地与线上同形 ——
@@ -93,11 +99,19 @@ website/ 产品官网(**纯静态、无构建步骤**,
(main.js 会跳过它)。拆成 per-char 的 inline-block 后每个字各自建一层合成,
祖先的 background-clip: text 就穿不过去,渐变会整行失效。
⚠ assets/motion.js 是动效层:曲线与弹簧 token 逐条取自 beUI
- (beui.dev/components/motion,见 README §7「官网」)。
+ (beui.dev/components/motion,见 docs/reference.md 的「官网」一节)。
改官网动效请改 token 表,不要在组件里另写 duration / cubic-bezier。
- ⚠ assets/shots/ 里全是**跑起来截的**实机图,重采集步骤见 README
- §7「采集实机图」;客户端那两张要配合冒烟的
+ ⚠ assets/shots/ 里全是**跑起来截的**实机图,重采集步骤见
+ docs/dev/testing.md 的「截图留档」;客户端那两张要配合冒烟的
ELECTRON_CLIENT_SHOTS_DIR / ELECTRON_SMOKE_ISLAND_* 开关。
+ ⚠ download.html 的版本清单是**当场**读 GitHub Releases 的
+ (assets/download.js)—— 渲染出的卡片与文件行都是拉到数据之后才建的,
+ 建完会派发一次 ch:content,由 main.js 补挂按压反馈(新增动态内容照这个来)。
+ 读不到时退回 并按发布命名约定拼直链 ——
+ 那个 meta 是 version.mjs 的落点之一,发版必须一起改。
+ 命名约定与 README「开始使用」表里那三个名字要对齐
+ (ClassHelper-<版本>-x64-client-setup.exe / -client-portable.exe /
+ -x64-server-setup.exe),它们是离线兜底链接的唯一依据。
packages/shared/src/ types.ts / constants.ts / permissions.ts / utils.ts / index.ts
packages/server/
prisma/schema.prisma 数据模型(14 个 model,无 enum、无 @db.*;连接串在 prisma.config.ts)
@@ -122,7 +136,7 @@ packages/desktop-client/
src/main/ 主进程:index / config(含作业短语与看板偏好)/ ipc / island / tray / update / logger / smoke
src/preload/ contextBridge 白名单桥(index.ts + island.ts,**不暴露 ipcRenderer 本体**)
src/island/ 灵动岛渲染进程:IslandApp.vue / spring.ts / squircle.ts / main.ts
- src/renderer/ 学生端渲染进程:api / cache(IndexedDB) / stores / views / island / router
+ src/renderer/ ClassHelper 班级端渲染进程:api / cache(IndexedDB) / stores / views / island / router
src/types/desktop.d.ts 主进程 ↔ 渲染进程契约
scripts/ build-main / dev / smoke / dist-win
packages/classisland-plugin/ .NET 8 插件(独立于 pnpm workspace,不参与 pnpm install)
@@ -187,12 +201,12 @@ pnpm build:classisland-plugin
| -------------- | ------------------------------- | ------------ |
| 管理员 | `admin` | `admin123` |
| 教师 | `teacher1` / `teacher2` | `teacher123` |
-| 学生端班级账号 | 班级码 `G101` / `G102` / `G203` | `123456` |
+| ClassHelper 班级端班级账号 | 班级码 `G101` / `G102` / `G203` | `123456` |
> 密码是**种子脚本里硬编码**的(`admin123` / `teacher123` / 班级 `123456`);
> `.env` 的 `DEFAULT_CLASS_PASSWORD` 只影响**新建**班级时的初始班级密码。
> **学生没有个人账号**(2026-10-01 清理):`student01…15` 只是名单记录(成绩/未交/叫人/已读按名单),
-> `User.passwordHash` 为空串、不能登录,也没有任何密码接口;学生端统一用班级码 + 班级密码。
+> `User.passwordHash` 为空串、不能登录,也没有任何密码接口;ClassHelper 班级端统一用班级码 + 班级密码。
> 班级码 `G203` 是「高二(3)班」,不是 `G103`。
---
@@ -228,8 +242,8 @@ pnpm build:classisland-plugin
| `pnpm dist:server` / `dist:win` / `dist:dir` / `dist:all` | 打包服务端安装程序 / 客户端安装包 / 免安装目录 / 全部 |
| `pnpm dist:server:linux` | 打包 **Linux 服务端安装包**(`classhelper-server-linux-x64-<版本>.tar.gz` + `.sha256`)—— 见 §5 第 59 条 |
| `pnpm icons` | 从 `build/classhelper.png` 派生全部图标(PWA / favicon / 托盘 / 安装包 / 插件 / 官网 / 文档站 / 界面内品牌标) |
-| `pnpm version:check` | 逐处列出并比对 13 个落点的版本号,不一致即 exit 1(见 §4.4「版本迭代」) |
-| `pnpm version:bump patch\|minor\|major` | **改版本号的唯一入口**:一次改写全部 13 处(含插件清单、C# 里的常量,以及官网上手写的三处版本号) |
+| `pnpm version:check` | 逐处列出并比对 14 个落点的版本号,不一致即 exit 1(见 §4.4「版本迭代」) |
+| `pnpm version:bump patch\|minor\|major` | **改版本号的唯一入口**:一次改写全部 14 处(含插件清单、C# 里的常量、官网上手写的三处,以及下载页的兜底值) |
| `pnpm docs:build` | 构建文档站到 `docs/_site`(需要 .NET SDK 8 + `dotnet tool install -g docfx`) |
| `pnpm docs:serve` | 构建文档站并起本地预览 → http://127.0.0.1:5181 |
| `pnpm docs:check` | 构建文档站并把 docfx 的警告当错误(发布前用;最常见的警告是"某个 .md 没被任何 toc.yml 引用") |
@@ -254,11 +268,11 @@ pnpm build:classisland-plugin
```
releases/
client/<版本>/
- 安装包/ 班级小助手-<版本>-x64-setup.exe / -portable.exe(+ .blockmap)
+ 安装包/ ClassHelper-<版本>-x64-setup.exe / -portable.exe(+ .blockmap)
免安装/ electron-builder 的 win-unpacked 内容
构建中间/ builder-*.yml、*.nsis.7z
server/<版本>/
- 安装包/ 班级小助手服务端-<版本>-x64-setup.exe
+ 安装包/ ClassHelper服务端-<版本>-x64-setup.exe
免安装/ classhelper-server 免安装目录(含 .env、node.exe、data/、logs/)
linux-x64/ classhelper-server-linux-x64-<版本>.tar.gz + .sha256 + 同内容解包目录
构建中间/ server-installer.generated.nsi
@@ -296,9 +310,11 @@ pnpm dist:classisland-plugin
> 算一次发布:攒够一批功能 / 修好一个用户能感知的 bug / 要给别人一份新安装包。
> 不算:改注释、改文档、重构、加测试。
-**同一个版本号写在 13 处(11 个文件)里**(5 份 `package.json`、`deploy/package.runtime.json`、
-两个 `docker-compose*.yml` 的镜像 tag、插件 `manifest.yml`、以及插件 C# 里的 `PluginVersion` 常量)。
-手工改必然漏一处,而漏掉的那处**不会报错** —— 只会在某个不常走的分支上表现为"版本号对不上"。
+**同一个版本号写在 14 处(12 个文件)里**(5 份 `package.json`、`deploy/package.runtime.json`、
+两个 `docker-compose*.yml` 的镜像 tag、插件 `manifest.yml`、插件 C# 里的 `PluginVersion` 常量、
+官网首页上三处手写值,以及下载页 `` 那个兜底值)。
+手工改必然漏一处,而漏掉的那处**不会报错** —— 只会在某个不常走的分支上表现为"版本号对不上"
+(官网那三处的后果是首页一直挂着旧版本号;下载页那一处的后果是离线访客点到一串不存在的直链)。
所以**改版本号的唯一入口是脚本**:
```bash
@@ -331,7 +347,7 @@ pnpm build && pnpm verify:web
# ④ 打安装包(产物落在 releases/<组件>/<版本>/)
pnpm dist:all
-pnpm verify:packaged --exe "releases/client/<版本>/免安装/班级小助手.exe"
+pnpm verify:packaged --exe "releases/client/<版本>/免安装/ClassHelper.exe"
# ⑤ 整理跨组件哈希清单 releases/SHA256SUMS-<版本>.txt(人读;Linux 那一行由 CI 合并进来)
# ⑥ 提交并推上去
@@ -346,6 +362,29 @@ gh release upload v<版本> \
releases/SHA256SUMS-<版本>.txt --clobber
```
+> **上传前把三个 EXE 改成 ASCII 名。** 构建产物是中文名(客户端 `ClassHelper-<版本>-x64-setup.exe`
+> / `-x64-portable.exe`,服务端 `ClassHelper 服务端-<版本>-x64-setup.exe`),而 Release 上的名字
+> 是给别人看、要贴进脚本的 —— 一律用 ASCII 形式,与构建产物一一对应(内容相同、sha256 相同):
+>
+> | 构建产物 | Release 上的名字 |
+> | --------------------------- | ------------------------------------------- |
+> | `<客户端>-x64-setup.exe` | `ClassHelper-<版本>-x64-client-setup.exe` |
+> | `<客户端>-x64-portable.exe` | `ClassHelper-<版本>-x64-client-portable.exe` |
+> | `<服务端>-x64-setup.exe` | `ClassHelper-<版本>-x64-server-setup.exe` |
+>
+> **这三个名字是外部契约**:README 的「开始使用」表、官网下载页的离线兜底直链都按它们拼 ——
+> 改名要三处一起改。上传前改名比上传后 `gh release edit` 省事:
+>
+> ```bash
+> V=<版本>
+> mkdir -p .cache/release-staging
+> cp "releases/client/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-client-setup.exe"
+> cp "releases/client/$V/安装包/"*-x64-portable.exe ".cache/release-staging/ClassHelper-$V-x64-client-portable.exe"
+> cp "releases/server/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-server-setup.exe"
+> cp "releases/classisland-plugin/$V/安装包/ClassHelper.ClassIslandPlugin.cipx" .cache/release-staging/
+> gh release upload v$V .cache/release-staging/* "releases/SHA256SUMS-$V.txt" --clobber
+> ```
+
**第 ⑦ 步的顺序有讲究**:`.github/workflows/release-linux-server.yml` 由 tag 推送触发,
它在**最后一步**才把自己的那一行**合并进** Release 上已有的 `SHA256SUMS-<版本>.txt`
(`grep -v` 掉旧的 linux 行再追加,Windows / 插件那几行原样保留)。
@@ -368,6 +407,7 @@ gh workflow run release-linux-server.yml -f tag=v<版本> -f attach=true
```
https://lovechengke.github.io/ClassHelper/ ← 官网(website/,纯静态无构建)
+https://lovechengke.github.io/ClassHelper/download.html ← 下载页(同一棵树里的一个页面)
https://lovechengke.github.io/ClassHelper/docs/ ← 文档站(docfx 构建 docs/)
```
@@ -378,7 +418,11 @@ https://lovechengke.github.io/ClassHelper/docs/ ← 文档站(docfx 构建
> 以及文档站模板 `docs/templates/classhelper/layout/_master.tmpl` 里那个 `{{_rel}}../` 的「官网」入口。
- 顶栏的版本徽标取自根 `package.json`(`docs/build.mjs` 用 `--metadata _chVersion=…` 注入),
- **不要在文档里再写一份版本号**;官网首屏那排芯片里的 `v1.1.2` 是手写的,发版时要一起改;
+ **不要在文档里再写一份版本号**;官网首页那排芯片里的 `v1.1.2` 与下载页的
+ `` 是手写的,发版时要一起改(都在 `pnpm version:bump` 的 14 个落点里);
+- 下载页的版本清单**不在站点里存副本**:每次打开都由 `assets/download.js` 当场读
+ GitHub Releases(匿名接口 60 次/小时/IP),读不到才退回上面那个 meta 按发布命名约定拼直链。
+ 所以发完 Release 不用重新部署这一页,但**兜底值必须跟着发版改**;
- 首次启用需要在仓库 Settings → Pages 把 Source 选成「GitHub Actions」(只需一次);
- 404 页的 `
+
[](https://github.com/LoveChengke/ClassHelper/releases/latest)
[](https://github.com/LoveChengke/ClassHelper/releases)
@@ -16,7 +16,7 @@

[](https://github.com/LoveChengke/ClassHelper)
-班级小助手是给中小学班级用的一套信息管理系统。
|
+| 仪表盘 |
|
| 通知发布 |
|
## 开始使用
-| 装在哪 | 装什么 | 从 Releases 里拿 |
-| ----------------------- | ------------------------------------------------- | --------------------------------------------------------- |
-| 教师的电脑 / 学校服务器 | **服务端**(内含 Web 管理端与 Node 运行时) | `班级小助手服务端-<版本>-x64-setup.exe` |
-| 教室机器 / 学生电脑 | **学生客户端** | `班级小助手-<版本>-x64-setup.exe`,或 `-portable.exe` 免安装 |
-| 教室机器的 ClassIsland | **联动插件** | `ClassHelper.ClassIslandPlugin.cipx` |
+| 装在哪 | 装什么 | 从 Releases 里拿 |
+| ----------------------- | ------------------------------------------------- | --------------------------------------------------------------------------- |
+| 教师的电脑 / 学校服务器 | **服务端**(内含 Web 管理端与 Node 运行时) | `ClassHelper-<版本>-x64-server-setup.exe` |
+| 教室机器 / 学生电脑 | **学生客户端** | `ClassHelper-<版本>-x64-client-setup.exe`,或 `-client-portable.exe` 免安装 |
+| 教室机器的 ClassIsland | **联动插件** | `ClassHelper.ClassIslandPlugin.cipx` |
+
+也可以在官网的[下载页](https://lovechengke.github.io/ClassHelper/download.html)上挑:
+选一个版本,四个包排成四张卡,文件名、体积与 SHA256 清单都在那儿 ——
+那一页的清单是**当场**读 GitHub Releases 的,显示什么就能下什么。
服务端**装上就算跑起来了**:双击安装程序,建库、建号、开机自启都由它自己办,
装完在浏览器打开 `http://<服务器地址>:4000` 就能登录。教师的电脑、学校的服务器、
@@ -208,7 +212,7 @@ pnpm dev # 后端 4000 + Web 端 5
| -------------- | --------------- | ------------ |
| 管理员 | `admin` | `admin123` |
| 教师 | `teacher1` | `teacher123` |
-| 学生端(班级) | 班级码 `G101` | `123456` |
+| ClassHelper 班级端(班级) | 班级码 `G101` | `123456` |
- [AGENTS.md](AGENTS.md) —— 工程约定与本机踩坑,**改代码前先读它**
- [docs/reference.md](docs/reference.md) —— 完整参考(功能行为、接口、验收记录)
diff --git a/docs/app/island.md b/docs/app/island.md
index 49f1ece..6f3bf89 100644
--- a/docs/app/island.md
+++ b/docs/app/island.md
@@ -14,14 +14,14 @@ description: 桌面客户端的通知浮窗:胶囊与卡片、多条通知的
## 各形态长什么样
-| 形态 | 截图 | 说明 |
-| ---------------------- | ----------------------------------------------------------------- | ------------------------------------------------- |
-| 非上课时段的「新消息」 |  | 268×44,直接出现(无入场动画),点击后展开 |
-| 点开后的详情卡 |  | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 |
-| 下课后自动弹出 |  | 上课期间暂存的通知,下课后自动弹出 |
-| 上课期间的紧急通知 |  | 卡片**内部**红色光晕 + 紧急角标,无需点击 |
-| 新作业 |  | 青蓝描边 + 书本图标 |
-| 叫人 |  | 琥珀金卡片 + 「叫人」徽标 + 「收到」按钮 |
+| 形态 | 截图 | 说明 |
+| ---------------------- | ----------------------------------------------------- | ------------------------------------------------- |
+| 非上课时段的「新消息」 |  | 268×44,直接出现(无入场动画),点击后展开 |
+| 点开后的详情卡 |  | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 |
+| 下课后自动弹出 |  | 上课期间暂存的通知,下课后自动弹出 |
+| 上课期间的紧急通知 |  | 卡片**内部**红色光晕 + 紧急角标,无需点击 |
+| 新作业 |  | 青蓝描边 + 书本图标 |
+| 叫人 |  | 琥珀金卡片 + 「叫人」徽标 + 「收到」按钮 |
## 什么时候弹、弹成什么样
diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md
index c27d59b..b27b66e 100644
--- a/docs/dev/architecture.md
+++ b/docs/dev/architecture.md
@@ -55,7 +55,8 @@ class-helper/
│ ├── index.md # 首页
│ ├── get-started/ app/ management/ dev/ # 四个章节
│ ├── templates/classhelper/ # docfx 模板覆盖(_master.tmpl + public/main.css + 图标)
-│ ├── screenshots/ # 回归测试自动留档的截图(island / client / web-mobile / classisland)
+│ ├── images/ # 展示图:文档站 / README / 官网共用的同一份(1× 逻辑像素)
+│ ├── screenshots/ # 回归测试留档的截图(island / client / web-mobile / classisland),页面不展示
│ ├── build.mjs # 构建入口(注入版本号与 404 的 base)
│ ├── serve.mjs # 本地预览(:5181)
│ └── _site/ # 构建产物(gitignore)
diff --git a/docs/dev/release.md b/docs/dev/release.md
index 758cd76..4f4aa69 100644
--- a/docs/dev/release.md
+++ b/docs/dev/release.md
@@ -19,22 +19,24 @@ description: 改版本号、跑验收、打安装包、发 GitHub Release、发
| 要给别人一份新安装包 | 只在自己机器上跑 |
| 插件单独发补丁(只动插件第 4 段版本号) | — |
-## 版本号写在哪(13 处 / 11 个文件)
+## 版本号写在哪(14 处 / 12 个文件)
-同一个版本号分散在 13 个落点上,**手工改必然漏一处**,而漏掉的那处不会报错:
+同一个版本号分散在 14 个落点上,**手工改必然漏一处**,而漏掉的那处不会报错:
-| 文件 | 内容 |
-| ---------------------------------------------------------------- | ------------------------------- |
-| `package.json` | 单一来源(服务端运行时读它) |
-| `packages/{shared,server,web-admin,desktop-client}/package.json` | 各包的版本 |
-| `deploy/package.runtime.json` | 安装包 / 容器里的运行时依赖清单 |
-| `deploy/docker-compose.yml`、`docker-compose.sqlite.yml` | 镜像 tag 的默认值 |
-| `packages/classisland-plugin/manifest.yml` | 插件清单(四段:`x.y.z.0`) |
-| `packages/classisland-plugin/src/Services/BridgeService.cs` | 插件上报的 `PluginVersion` 常量 |
-| `website/index.html` × 3 | 官网顶栏徽标 / 首屏芯片 / 页脚 |
+| 文件 | 内容 |
+| ---------------------------------------------------------------- | ---------------------------------- |
+| `package.json` | 单一来源(服务端运行时读它) |
+| `packages/{shared,server,web-admin,desktop-client}/package.json` | 各包的版本 |
+| `deploy/package.runtime.json` | 安装包 / 容器里的运行时依赖清单 |
+| `deploy/docker-compose.yml`、`docker-compose.sqlite.yml` | 镜像 tag 的默认值 |
+| `packages/classisland-plugin/manifest.yml` | 插件清单(四段:`x.y.z.0`) |
+| `packages/classisland-plugin/src/Services/BridgeService.cs` | 插件上报的 `PluginVersion` 常量 |
+| `website/index.html` × 3 | 官网顶栏徽标 / 首屏芯片 / 页脚 |
+| `website/download.html`(``) | 下载页读不到版本清单时的兜底版本号 |
-最后一行值得单独说:官网是**纯静态站、没有构建步骤**,没有哪段代码会去读 `package.json`,
-所以那三处只能靠脚本一起改 —— 漏了的话首页会一直挂着旧版本号,而那是别人看到的第一眼。
+最后两行值得单独说:官网是**纯静态站、没有构建步骤**,没有哪段代码会去读 `package.json`,
+所以那几处只能靠脚本一起改 —— 漏了的话首页会一直挂着旧版本号(而那是别人看到的第一眼),
+下载页则会给离线访客一串指向**不存在版本**的直链。
所以**改版本号的唯一入口是脚本**:
@@ -97,7 +99,7 @@ pnpm dist:all # = dist:server + dist:win + dist:classisland-pl
打完之后**对打包副本再验一次**(开发产物通过 ≠ 用户机器上的副本通过):
```bash
-pnpm verify:packaged --exe "releases/client/<版本>/免安装/班级小助手.exe"
+pnpm verify:packaged --exe "releases/client/<版本>/免安装/ClassHelper.exe"
```
> Linux 包**只能在 Linux 上构建**(依赖是平台相关的),所以不在这条命令里 ——
@@ -127,16 +129,29 @@ git push origin master
### 7. 开 Release
-```bash
-gh release create v1.1.3 --title "v1.1.3" --generate-notes
+上传前先把产物改成 ASCII 名 —— 构建产物是中文名,Release 上的名字是给别人看、要贴进脚本的:
-gh release upload v1.1.3 \
- releases/client/<版本>/安装包/*.exe \
- releases/classisland-plugin/<版本>/安装包/*.cipx \
- releases/server/<版本>/安装包/*.exe \
- releases/SHA256SUMS-<版本>.txt --clobber
+```bash
+V=1.1.3
+mkdir -p .cache/release-staging
+cp "releases/client/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-client-setup.exe"
+cp "releases/client/$V/安装包/"*-x64-portable.exe ".cache/release-staging/ClassHelper-$V-x64-client-portable.exe"
+cp "releases/server/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-server-setup.exe"
+cp "releases/classisland-plugin/$V/安装包/ClassHelper.ClassIslandPlugin.cipx" .cache/release-staging/
+
+gh release create v$V --title "v$V" --generate-notes
+gh release upload v$V .cache/release-staging/* "releases/SHA256SUMS-$V.txt" --clobber
```
+| 构建产物 | Release 上的名字 |
+| --------------------------- | -------------------------------------------- |
+| `<客户端>-x64-setup.exe` | `ClassHelper-<版本>-x64-client-setup.exe` |
+| `<客户端>-x64-portable.exe` | `ClassHelper-<版本>-x64-client-portable.exe` |
+| `<服务端>-x64-setup.exe` | `ClassHelper-<版本>-x64-server-setup.exe` |
+
+这三个名字是**外部契约**:README 的「开始使用」表、官网下载页读不到版本清单时拼的兜底直链,
+都按它们写;改名要三处一起改。
+
`gh release create` 会**创建 tag 并推送**,这一步会自动触发
[`release-linux-server.yml`](https://github.com/LoveChengke/classhelper/blob/master/.github/workflows/release-linux-server.yml)
构建 Linux 包(ubuntu runner)。它跑完之后会:
@@ -169,8 +184,12 @@ https://lovechengke.github.io/ClassHelper/docs/ ← 文档站
(一个仓库在 Pages 上只有一个站点,所以是拼在一起发的 —— 官网在根、文档站在 `/docs/` 下。)
文档站顶栏的版本徽标取自根 `package.json`,所以第 1 步改完就跟着变了;
-官网首屏那排芯片、顶栏徽标与页脚里的版本号也是**由 `pnpm version:bump` 一起改的**
-(它们是那 13 个落点里的三个)。
+官网首屏那排芯片、顶栏徽标与页脚里的版本号,以及下载页那个兜底版本号,
+也都是**由 `pnpm version:bump` 一起改的**(它们是那 14 个落点里的四个)。
+
+> 官网的「立即下载」会跳到 `/download.html`,那一页的版本清单是**当场**从 GitHub Releases 读的
+> (读不到才退回上面那个兜底版本号),所以发完 Release 不用重新部署那一页 —— 但**兜底值要跟着发版一起改**,
+> 不然离线访客点到的就是旧版本的直链。
### 9. 检查
diff --git a/docs/dev/testing.md b/docs/dev/testing.md
index 9675cae..f599dc3 100644
--- a/docs/dev/testing.md
+++ b/docs/dev/testing.md
@@ -80,20 +80,43 @@ pnpm build && pnpm verify:web
## 截图留档
-回归测试顺手把截图存到 `docs/screenshots/`(island / client / web-mobile / classisland 四组),
-文档站里用的就是这些图。要重新采集:
+截图分两套,各管一件事:
+
+| 目录 | 谁在用 | 谁产出 |
+| ------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
+| `docs/images/` | **文档站、README、官网用的是同一份图**,页面上展示的都是它 | `capture-shots.mjs`(Web / 手机)+ 客户端冒烟(客户端 / 灵动岛) |
+| `docs/screenshots/` | 回归留档,页面不展示;`verify:*` 的像素级断言比对的就是它们 | 各冒烟脚本自动写入(island / client / web-mobile / classisland 四组) |
+
+**展示图重新采集**(保证文档站与 README、官网用的是同一份图):
+
+```bash
+pnpm dev:server # 后端同时托管 Web 管理端
+CAPTURE_OUT=docs/images,website/assets/shots node website/tools/capture-shots.mjs # → web-*.jpg / mobile-*.jpg
+
+# 客户端与灵动岛走的是冒烟脚本。它除了正片,还会顺手落一份「岛外留白对照图」
+# (*-window.png / *-backdrop.png),所以先落到临时目录,再挑要用的拷进去。
+ISLAND_SHOTS_DIR="$PWD/.cache/shots" ELECTRON_CLIENT_SHOTS_DIR="$PWD/.cache/shots" \
+ELECTRON_SMOKE_ISLAND_ACCENT='#0a84ff' ELECTRON_SMOKE_ISLAND_STYLE=black \
+ELECTRON_SMOKE_USER=teacher1 ELECTRON_SMOKE_PASSWORD=123456 \
+ node packages/desktop-client/scripts/smoke.mjs
+cp .cache/shots/{island-*,client-*}.png docs/images/
+rm -f docs/images/*-window.png docs/images/*-backdrop.png
+cp docs/images/{island-*,client-*}.png website/assets/shots/ # 官网那份保持同步
+```
+
+**回归留档重新采集**(同一套冒烟,把输出指到留档目录):
```bash
pnpm dev:server
-ISLAND_SHOTS_DIR=F:/ClassHelper/.cache/island-shots \
-ELECTRON_CLIENT_SHOTS_DIR=F:/ClassHelper/.cache/client-shots \
+ISLAND_SHOTS_DIR="$PWD/.cache/island-shots" \
+ELECTRON_CLIENT_SHOTS_DIR="$PWD/.cache/client-shots" \
ELECTRON_SMOKE_ISLAND_ACCENT='#0a84ff' ELECTRON_SMOKE_ISLAND_STYLE=black \
ELECTRON_SMOKE_USER=teacher1 ELECTRON_SMOKE_PASSWORD=123456 \
node packages/desktop-client/scripts/smoke.mjs
```
-官网的实机图则用 `website/tools/capture-shots.mjs`(采集步骤见 `README.md` §7)。
-
+> 展示图都是**逻辑像素 1×**(灵动岛的 268×44、424×230 就是这个数,不再乘 2),
+> 页面按原始尺寸显示即可 —— 换图时别顺手放大,否则整页的尺寸对不上。
> 采集脚本会把窗口真的弹在屏幕上。客户端留档会**临时停掉 CSS 动画**再截图 ——
> 窗口被遮挡时 Chromium 冻结动画,而页面入场动画的起始帧是 `opacity: 0`,
> 不停掉的话截出来是**整片空白**(DOM 里文本齐全,只有图是空的)。
diff --git a/docs/index.md b/docs/index.md
index 87572f5..5c44921 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,12 +1,12 @@
---
title: 首页
-description: 班级小助手(Class Helper)的官方文档:安装部署、功能说明、运维手册与开发参考。
+description: ClassHelper(Class Helper)的官方文档:安装部署、功能说明、运维手册与开发参考。
---
-# 班级小助手文档
+# ClassHelper文档
-班级小助手(Class Helper)是给中小学班级用的一套信息管理系统。**一份代码,四个交付物**:
-装在服务器上的后端、给老师用的 Web 管理端、装在教室机器的桌面客户端(学生端 / 教室大屏),
+ClassHelper(Class Helper)是给中小学班级用的一套信息管理系统。**一份代码,四个交付物**:
+装在服务器上的后端、给老师用的 Web 管理端、装在教室机器的桌面客户端(ClassHelper 班级端 / 教室大屏),
以及装在 [ClassIsland](https://classisland.tech) 上的联动插件。
@@ -16,7 +16,7 @@ description: 班级小助手(Class Helper)的官方文档:安装部署、
-
-
+
+
+
版本 )(\d+\.\d+\.\d+)( ·)/,
},
+ {
+ file: 'website/download.html',
+ label: '(版本清单的兜底值)',
+ pattern: /()/,
+ },
];
const ALL_TARGETS = [...PRODUCT_TARGETS, ...PLUGIN_TARGETS, ...SITE_TARGETS];
diff --git a/website/404.html b/website/404.html
index af89c73..f9a9c6c 100644
--- a/website/404.html
+++ b/website/404.html
@@ -13,7 +13,7 @@