jif2.zhang 32f404ac90
Docker Build and Push / build-image (push) Successful in 1m34s
fix(任务): 修复列表与看板拖拽「能触发但放不下」—— uuid 主键被 Number() 成 NaN
0.2.1 切本地库后 Task.id / BoardColumn.id 为 uuid 字符串, 拖拽落点却用
Number(dataset.xxxId) 取值得到 NaN:
  - 看板 hitColumn 返回 NaN -> NaN 为 falsy, onDrop 永不触发; 目标列也不高亮
  - 列表 colId/taskId 为 NaN -> 组内命中与换列 findIndex 恒失败, 松手无操作
(打卡侧仍走 REST 数字 id, 故未受影响)

- 新增 src/pages/task/ids.js: idStr / sameId / datasetId, 统一按字符串比对
- useColumnDrag / useRowDrag: 落点、组内命中、换列、提交判定改用 sameId
- ProjectView: moveToColumn / reorderTask / changeStatus 改用 sameId,
  落点为空直接忽略, 不把坏 id 写进本地库
- api/task.js: update 拦截 "NaN"/"undefined" 列 id, 报「看板列无效」

验证: headless Edge + CDP 派发真实指针事件驱动两个 hook (uuid 数据),
修复前 4 条落点断言全挂, 修复后全部通过。
2026-10-27 10:06:37 +08:00
2026-08-03 18:09:08 +08:00
2026-08-07 17:10:07 +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:09:08 +08:00

iBoard

跨平台个人看板 (Tauri + React + Express + MariaDB)。用于收集和整理经济数据,但是以策略为主体重新组织。

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

首页

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

新闻模块

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

报警模块

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

监控

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


分支规则

分支类型与职责

分支 职责 部署 合并方向
master 主分支(生产分支) ✅ 唯一部署出口,push 即发布生产 —
dev-* 开发集成分支,如 dev-0.2 ❌ 不部署(只跑 CI 校验) 合到 master 后由 master 部署
tmp-* 临时分支,针对单个问题的修改 ❌ 不部署 合到对应 dev-* 后删除
android-* Android 外壳构建分支 ❌ 不部署,只触发 APK 构建 构建前须与 master 同步

关于 main:团队约定里的「main 主分支」在本仓库落地为 master(仓库默认分支即 master,不另建 main)。 下文所有「main 主分支」均指 master。

流程

tmp-xxx  ──合并──▶  dev-0.2  ──合并──▶  master  ──push──▶  自动部署生产
   │                    │
   │                    └──push──▶ 只跑 CI(版本一致性校验),不部署
   │
   └── 问题解决后删除

android-1.0 ── 从 master 同步代码后 push ──▶ 只触发 APK 构建 / 发布到 Packages
  1. 日常开发:从 dev-* 拉 tmp-*,改完合回 dev-*,tmp-* 用完即删。
  2. 上线:dev-* 验收通过后合并到 master,推送 master 才部署生产。
  3. 打 Android 包:android-* 与 master 同步(git merge master 或 git reset --hard origin/master)后推送, 触发 android-build.yml,产物发布到 Gitea Generic Packages。

CI 触发范围

工作流 触发分支 动作
docker-build.yml 仅 master 构建前端 + 生成签名 OTA 包 + 构建镜像 + 迁移数据库 + 部署生产
android-build.yml android-* 构建并签名 release APK,上传到 Packages
ota-build.yml 仅手动 workflow_dispatch 单独构建/签发 OTA 包
sync-registry.yml 见文件 同步注册中心

⚠️ 重要变更(2026-10):dev-* 分支已取消 push 自动部署。 此前 dev-* 与 master 共用同一套生产环境,推送开发分支等于直接发布,容易把半成品带上线。 现在开发分支的提交只有合并进 master 并推送后才会部署。

日常操作规则(工作目录约定)

  • 主工作目录(iboard/)默认停留在 dev-0.2,所有修改都在 dev-0.2 上提交;dev-0.2 可随时 push(只跑 CI 校验,不部署)。

  • 独立功能开发使用独立分支 + 独立工作目录(如离线编辑在 iboard-dev-0.2.1/ 的 tmp-offline-edit 分支),不占用 dev-0.2。

  • 发布流程(仅在明确需要发布时执行,结束后立刻回到 dev-0.2):

    git switch master
    git merge --ff-only dev-0.2
    git push origin master        # push 即部署生产
    git switch dev-0.2
    

版本管理

版本号构成与口径(2026-10-25 决议)

V MAJOR.MINOR.PATCH   例: V0.2.0
       │      └── PATCH  小版本号: 仅在明确提出时自增, 否则沿用 (记录见 CHANGELOG.md)
       └── MINOR  大版本迭代, 对应一个开发分支 (V0.2 ↔ dev-0.2)

前端与外壳解绑,独立维护、独立发版(2026-10-25 决议):

组 版本 管理点 产物 发版节奏
前端 (Web/OTA) 0.2.0 package.json 的 version(唯一手动管理点,只写裸版本号,不允许带 -ota) 前端运行时 APP_VERSION = 0.2.0;OTA 包 version = 0.2.0-ota 随 master push 高频发版
外壳 0.2.0 src-tauri/tauri.conf.json 的 version(须与 Cargo.toml/Cargo.lock 组内一致) APK versionName、桌面安装包版本;后续 Windows 外壳同理 独立分支(如 android-*)低频发版
  • 两组版本号不要求相等,CI(scripts/ci/check-version.mjs)只校验组内一致 + 前端版本与 dev-<major>.<minor> 分支前缀对应。
  • 分支口径:dev-0.2 开发,发布一律走 master。

build 标识

  • 格式统一为 YYYYMMDDHHmmss(北京时间):
    • 前端 BUILD_ID:vite 编译时生成(vite.config.js#makeBuildId),无需手动维护;
    • OTA 包 build_id:打包时按同一格式生成(scripts/build-ota.mjs)。
  • OTA 包的 sequence(Unix 毫秒,单调递增)保留,仅用于判新与热插拔协议,不再用于展示。

PATCH 小版本号

  • 仅在明确提出时自增(改 package.json version 第三位),否则一直沿用。
  • 每次自增在 CHANGELOG.md 追加一行(patch 号 / 日期 / commit / 要点)。

改版本号时要同步的位置

组 文件 字段 说明
前端 package.json version 前端唯一源,src/version.js 与 OTA 打包脚本均读取此值
外壳 src-tauri/tauri.conf.json version APK 的 versionName、桌面安装包版本
外壳 src-tauri/Cargo.toml [package].version Rust 包版本,须与 tauri.conf.json 一致
外壳 src-tauri/Cargo.lock name = "iboard" 条目 随 Cargo.toml 一起改

APK 版本号是怎么来的

tauri android init 会按 src-tauri/tauri.conf.json 的 version 生成 src-tauri/gen/android/app/tauri.properties(该目录被 gitignore,CI 每次 android init 重新生成):

tauri.android.versionName = 0.2.0     # 人类可见版本,与 tauri.conf.json 一致
tauri.android.versionCode = 2000      # 整数,升级判定用 = major*1000000 + minor*1000 + patch

注意:

  • versionCode 必须单调递增才能覆盖安装,所以外壳版本号只能往上走(0.2.0 → 2000 > 0.1.0 → 1000)。
  • 本地如果 APK 装完显示旧版本号,是 src-tauri/gen/android 缓存没刷新, 删掉该目录重新 npx tauri android init 即可。

其它

  • src/version.js 从 package.json 取值,前端运行时与 OTA 检查接口展示的即此版本,不要另写死版本号。
  • min_binary_version 与这里的版本号是两件事:它是 OTA 包要求的最低原生二进制版本 (CI 里由 OTA_MIN_BINARY 显式指定,当前 0.1.0)。前端版本升到 0.2.0 时它不应跟着抬, 否则装了 0.1.0 APK 的设备会直接收不到热更。
  • ota-package/latest.json 为运行时产物,不纳入版本管理。

"我的"页版本展示口径

行 展示
当前版本 v0.2.0 · build 20261025204300(前端 APP_VERSION + BUILD_ID 原始格式)
运行资源 OTA 覆盖包版本 / 内置
新版本 v0.2.0-ota · build 20261025210000(OTA 包 build_id)
发布说明 触发构建的 commit message
提交 commit sha 前 10 位
页面底部 外壳 v0.2.0(Tauri API 读取;Web 端显示 —)

前端 OTA 升级

只升级前端资源 (dist/),不触碰 Tauri 框架/原生层,不重打 APK。 底层使用 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 优先命中覆盖包

发布说明与提交号(2026-10-25 起)

  • CI(docker-build.yml)把触发构建的 commit message(标题+正文,去空行,截断 400 字)作为 notes 写入 latest.json;
  • commit sha 经 --commit 参数单独写入 commit 字段;
  • "我的"页:发布说明展示 commit message,「提交」行展示短 sha。

首次部署

  1. 生成签名密钥 (一次性):
    # 安装 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):
    "hotswap": {
      "endpoint": "https://your-server/api/ota/check/{{current_sequence}}",
      "pubkey": "RWR...",
      "require_https": true
    }
    
  4. 服务端环境变量 OTA_PACKAGE_DIR 指向 ota-package/ 所在目录。

发布新版前端

npm run build                # 生成 dist/
node scripts/build-ota.mjs   # 打成 tar.gz + 签名 + latest.json
# sequence 默认 = Date.now(), 也可 --sequence 43 手动指定
# 也可加 --notes "修复登录按钮" --commit <sha> --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(可选,不填用时间戳)和 notes(可选)
  3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig
  4. 下载 artifact,scp 到服务器 OTA_PACKAGE_DIR 目录
  5. 客户端"我"页 [检查更新] 即可触发升级

注意:

  • CI 用的私钥必须与 tauri.conf.json 里 plugins.hotswap.pubkey 配对;换密钥时需要同时更新 CI secret 和 conf。
  • 不要把私钥 commit 到 git,整个 ota-package/hotswap.key 已被 .gitignore 排除。

数据库升级约定

  • 唯一入口是 prisma/migrations/ 下的迁移文件,由 npx prisma migrate deploy 在启新容器之前执行 (幂等,无新迁移时为 no-op);迁移失败会中断 workflow,不会用新镜像顶掉仍在运行的旧容器。
  • 由于只有 master 会部署,迁移真正作用到生产库的唯一时机就是 master 发布; 开发分支上的迁移不会被自动应用,请在合并到 master 前确认可回滚。
  • 本地改了 prisma/schema.prisma 后:写迁移 SQL → npx prisma migrate deploy 验证 → 提交迁移目录。
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%