cheney 4a04720a5e feat(V0.2): OTA 版本与判新口径 + Tab 结构调整
1) 版本对齐分支
- package.json / tauri.conf.json / Cargo.toml / Cargo.lock: 0.1.0 -> 0.2.0, OTA 包版本变为 0.2.0-ota
- 新增 scripts/ci/check-version.mjs: 分支 dev-x.y 与 package.json 版本不一致时 CI 直接失败
- docker-build.yml / ota-build.yml: min_binary_version 与前端版本解耦, 固定 0.1.0
  (现有 APK 原生层仍是 0.1.0, 抬高会导致旧 APK 收不到更新)

2) 我的页 OTA「开发版本」开关 (默认关, 持久化 localStorage.iboard_ota_dev_mode)
- 关: 按版本号逐字段比较 (src/utils/version.js), 服务端 version 更高才算新
- 开: 按 build sequence 判新 (hotswap checkUpdate), 每次构建都能推到设备
- Web 端: 开发口径退化为版本不同即算新; 生产口径严格比较大小

3) 打卡 Tab 移到最左并默认选中, 移除特效开关
- BottomNav: 打卡/宏观/监控/我; 删除 home 图标
- App: / 与 404 均重定向到 /checkin
- RingProgress/Celebrate: 删除 effectsEnabled/setEffectsEnabled, 特效恒开
- HabitView: 去掉头部「特效开/关」按钮

4) 取消首页 Tab, 报警/新闻并入宏观
- 新增 pages/macro/AlertsView.jsx、NewsView.jsx; 删除 HomePage.jsx
- MacroPage: 6 个子 Tab (油价/经济数据/策略研究/经济文章/报警/新闻), 报警角标保留
- 新闻详情 /news/:id -> /macro/news/:id (宏观 Tab 保持高亮)

5) 文档: 需求文档补 F-OT 需求点与已决策项; 设计文档新增 §10 变更记录与判新设计;
   界面设计.html 11 屏 (Tab 4 项/去掉特效开关/宏观合并/我的 OTA 开关 关+开 两态)
2026-10-22 17:17:29 +08:00
2026-08-03 18:09:08 +08:00
2026-08-07 17:10:07 +08:00
2026-08-03 18:05:50 +08:00
2026-04-25 19:48:19 +08:00
2026-08-07 14:36:57 +08:00
2026-08-07 14:36:57 +08:00
2026-08-03 18:05:50 +08:00
2026-08-03 18:09:08 +08:00
2026-08-06 17:27:23 +08:00

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# iBoard
用于收集和整理经济数据,但是以策略为主体重新组织。

## 版本管理

版本号遵循 `主版本.次版本.修订号`,**唯一手动管理点是 `package.json` 的 `version` 字段**(当前 `0.2.0`)。
版本号必须与所在分支对应:`dev-0.2` 分支上就该是 `0.2.x`(CI 有 `scripts/ci/check-version.mjs` 强校验,不一致直接失败)。
发版时只改这一处,以下位置必须保持一致(改版本时一并核对):

| 文件 | 字段 | 说明 |
| --- | --- | --- |
| `package.json` | `version` | **唯一源**,前端 `src/version.js` 与 OTA 打包脚本均读取此值 |
| `src-tauri/tauri.conf.json` | `version` | Tauri 应用版本(Android/桌面包版本) |
| `src-tauri/Cargo.toml` | `[package].version` | Rust 包版本,需与 tauri.conf.json 一致 |
| `src-tauri/Cargo.lock` | `name = "iboard"` 条目 | 同上,随 Cargo.toml 一起改 |

说明:
- `src/version.js` 从 `package.json` 取值,前端"我"页与 OTA 检查接口展示的即此版本,不要另写死版本号。
- OTA 升级包的 `version` 字段 = `<package.json version>-ota`(见 `scripts/build-ota.mjs`),
  所以版本号错位会直接导致线上 OTA 判新失准。
- `BUILD_ID` 每次编译自动生成(见 `vite.config.js` 的 `__BUILD_ID__`),无需手动维护。
- `ota-package/latest.json` 为运行时产物,不纳入版本管理。

## 分支与 CI/CD (Gitea Actions)

分支按版本命名:`dev-0.1`、`dev-0.2`…… 对应 `docs/V<版本>/` 下的需求/设计文档。

| 分支 | 触发的 workflow | 行为 |
| --- | --- | --- |
| `main` / `master` | `docker-build.yml` | 生产发布:构建前端 → 生成签名 OTA 包 → 打 Docker 镜像 → 数据库自动升级 → 重启容器 `iboard` (宿主 3001) |
| `dev-*` | `docker-build.yml` | **与生产同环境**:推送即构建、迁移数据库、部署(开发分支直接顶掉生产,含 OTA 热更) |
| `android-*` | `android-build.yml` | 打签名 release APK 并上传到 Gitea Generic Package Registry |
| 手动 | `ota-build.yml` / `sync-registry.yml` | 单独出 OTA 包 / 同步 secrets 到注册中心 |

数据库升级约定:
- 唯一入口是 `prisma/migrations/` 下的迁移文件,由 `npx prisma migrate deploy` 在**启新容器之前**执行
  (幂等,无新迁移时为 no-op);迁移失败会中断 workflow,不会用新镜像顶掉仍在运行的旧容器。
- `dev-*` 与 `main/master` 共用同一个 MariaDB 库(同一个 `DATABASE_URL` secret),
  因此开发分支上的迁移会直接作用到该库结构,合入前请确认迁移可回滚。
- 本地改了 `prisma/schema.prisma` 后:写迁移 SQL → `npx prisma migrate deploy` 验证 → 提交迁移目录。

- 首页主要是放经济文章
- 数据主要是放各种经济数据和图表。
- 策略是放按照经济理论组织的相关数据和图表的对比。



## 首页
首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。


### 新闻模块
增加一个新闻模块,数据来源是每日夜间 3点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序10条。
存入数据库。


### 报警模块
World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。

## 监控
监控分为服务器监控和服务监控两部分。
服务器监控对接 komari,地址:https://komari.honor3.com/



## 前端 OTA 升级

只升级前端资源 (`dist/`), **不触碰** Tauri 框架/原生层, 不重打 APK.
底层使用 [`tauri-plugin-hotswap`](https://github.com/denniskribl/tauri-plugin-hotswap)
(签名/回滚/资源替换全部由插件处理).

### 工作流

```
开发者                                   服务端
npm run build                ──┐
scripts/build-ota.mjs          │  生成 iboard-ota.tar.gz + minisign 签名
                               │  输出 latest.json (sequence/version/...)
                               ▼
                          ota-package/   ── 部署到服务器 ──▶   /srv/iboard/ota-package
                                                                    │
                                                                    ▼
                                                          GET /api/ota/check/<seq>
客户端 (Tauri)                                                     │
启动 ─▶ notifyReady()                                              ▼
我页 ─▶ checkUpdate()  ◀──── 200 + manifest (含 url/signature) ─────┘
       applyUpdate()  ─▶  下载 tar.gz ─▶ minisign 校验 ─▶ 解压到 AppData/ota-dist
       location.reload() ─▶  后续资源从 tauri://localhost 优先命中覆盖包
```

### 首次部署

1. **生成签名密钥** (一次性):
   ```bash
   # 安装 minisign: Windows (scoop) / macOS (brew) / Linux (apt)
   node scripts/ota-genkey.mjs
   # 产物: ota-package/hotswap.{key, pub}
   ```
2. **把公钥写入 `src-tauri/tauri.conf.json`** 的 `plugins.hotswap.pubkey` 字段
   (脚本会打印).
3. **配置 endpoint** (`src-tauri/tauri.conf.json`):
   ```json
   "hotswap": {
     "endpoint": "https://your-server/api/ota/check/{{current_sequence}}",
     "pubkey": "RWR...",
     "require_https": true
   }
   ```
4. **服务端环境变量** `OTA_PACKAGE_DIR` 指向 `ota-package/` 所在目录.

### 发布新版前端

```bash
npm run build                # 生成 dist/
node scripts/build-ota.mjs   # 打成 tar.gz + 签名 + latest.json
# sequence 默认 = Date.now(), 也可 --sequence 43 手动指定
# 也可加 --notes "修复登录按钮" --min-binary 0.1.0
```

然后把 `ota-package/` 下两个文件 (`iboard-ota.tar.gz` + `latest.json`) 同步到
服务端 `OTA_PACKAGE_DIR` 指向的目录. sequence 单调递增即可.

### 客户端使用

打开 App 的"我"页, 点 [检查更新] → [立即升级] 即可. 失败会自动回滚到内置 dist.

> **注意:** Tauri 2 的 dev 模式 (`tauri dev`) 会代理到 Vite dev server, **OTA 不生效**.
> OTA 仅在 release/打包构建中可用, 测试请用 `tauri build` 产物.

### 客户端判新口径(V0.2 新增)

"我"页 OTA 组内有一个 **开发版本** 开关(默认关闭),切换"怎么算新版本":

| 开关 | 判新依据 | 适用 |
| --- | --- | --- |
| 关(默认) | **版本号**逐字段比大小(`0.2.0-ota` > `0.1.0` 才算新) | 生产。旧分支被重建(sequence 变大但版本号更小)时不会把设备拉回旧版 |
| 开 | **build sequence**(服务端 sequence 比本地大就算新) | 开发联调。每次构建都能推到设备 |

- 开关值持久化在设备端 `localStorage.iboard_ota_dev_mode`(`'1'`/`'0'`),默认 `'0'`。
- 版本比较在 `src/utils/version.js`(纯函数,`src/api/ota.js` 统一封装两种模式)。
- **`min_binary_version` 与应用版本是两件事**:前者是前端 bundle 要求的最低原生二进制版本,
  当前 APK 原生层仍是 `0.1.0`,所以 CI 里固定传 `--min-binary 0.1.0`(见 `docker-build.yml` 的
  `OTA_MIN_BINARY`)。只改前端版本号时**不要**跟着抬这个值,否则旧 APK 会收不到更新。

### Web 端

Web 浏览器访问 (同源) 时, 服务端返回简化版 manifest, 仅显示"有新版本请刷新".
Web 端不会自动下载包, 也不会替换前端代码.


### CI 自动签名 (Gitea Actions)

.gitea/workflows/ota-build.yml 是手动触发的 OTA 打包工作流 (每次发版前点一次).

**配置 secret** (Gitea 仓库 -> Settings -> Actions -> Secrets):
- MINISIGN_PRIVATE_KEY: 完整 .key 文件内容, **整段** (含首行 `untrusted comment: minisign encrypted secret key` 和 base64 行), 整段作为多行 secret 粘贴即可.

**使用流程**:
1. 在 Gitea Actions 页面选 OTA Frontend Build & Sign -> Run workflow
2. 填 sequence (可选, 不填用时间戳) 和 
otes (可选)
3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig
4. 下载 artifact, scp 到服务器 OTA_PACKAGE_DIR 目录
5. 客户端"我"页 [检查更新] 即可触发升级

**注意**:
- CI 用的私钥必须**与** 	auri.conf.json 里 plugins.hotswap.pubkey **配对**; 换密钥时需要同时更新 CI secret 和 conf.
- 不要把私钥 commit 到 git, 整个 ota-package/hotswap.key 已被 .gitignore 排除.
S
Description
No description provided
Readme
26 MiB
Languages
TypeScript 68.9%
JavaScript 25.5%
CSS 2.6%
Rust 1.7%
Shell 0.8%
Other 0.4%