Steven
|
2c338490e5
|
fix(auth): 令牌到期自动续上,不再用半小时就被踢回登录页
CI / backend (push) Canceled after 0s
服务端一直签 access(30 分钟)和 refresh(14 天)两个令牌,但两个客户端拿到 401 都是直接清
令牌跳登录页,refresh 从来没被用过——"14 天有效"只是纸面参数,真实寿命就是 30 分钟。
- web/client.ts:请求前剩不到 90 秒先换;撞 401 先换再重放一次原请求;并发共用一次刷新;
上传附件同一条路
- android/Api.kt:call 拆 call/callOnce,2002 时用 Mutex 串行刷新后重放
- 后端 /auth/refresh 看一眼账号状态:停用的账号不能靠 refresh 一直续(2012)
- 新增 session-refresh.mjs:假 access + 真 refresh 要能留住人,两个都假才回登录页
|
2026-11-03 22:53:43 +08:00 |
|
Steven
|
02ef47e753
|
docs(roadmap): 换成放宽后的生产阅读页实拍
CI / backend (push) Canceled after 0s
|
2026-11-03 12:25:19 +08:00 |
|
Steven
|
da31083df2
|
fix(knowledge): 阅读页正文列放宽,宽屏上不再留白比字多
CI / backend (push) Canceled after 0s
1440 的屏幕上 40rem 正文只有 640px,两边各空出半屏,用户直接发截图说"留白比内容都多"。
正文列 56rem(约 55 个汉字一行)、卡片 78rem,行宽到头但不用来回扫行。
e2e 的宽度断言跟着改成 720–920px,避免以后再窄回去没人拦。
|
2026-11-03 12:23:53 +08:00 |
|
Steven
|
35fdea7260
|
test(web): e2e 与投图脚本改成密码登录,补生产的阅读页实拍
CI / backend (push) Canceled after 0s
生产短信登录已经关了,脚本里那条发码路径跑不通;统一走 PASSWORD,读不到才回落到短信。
|
2026-11-03 12:15:31 +08:00 |
|
Steven
|
d34a6ac69e
|
feat(knowledge): 阅读改成独立整页,抽屉只用来编辑
CI / backend (push) Canceled after 0s
知识原来一律在抽屉里打开,正文贴着屏幕右半边,长文读十几屏眼睛一直在右边。
阅读是「坐下来读一会儿」,编辑是「改两句就走」,两件事不该共用一个容器。
- 新增阅读页 /w/:wsId/knowledge/:itemId:正文列限到 40rem 居中,28px 标题,
更新/发布/切片行,标签与来源,动作行放编辑与发布/归档/恢复
- 列表页与搜索结果改成跳阅读页,不再弹抽屉
- KnowledgeDrawer 加 initialEditing:进来就是编辑器,保存或取消直接关
- 文案常量搬到 lib/knowledge.ts,列表页与阅读页共用一份
顺带修三个 e2e 脚本:新建后进阅读页没有「关闭」按钮可点、侧栏色点按钮让树
顺序断言多出空串、翻页脚本还在用已关闭的短信登录(改密码登录)。
|
2026-11-03 12:12:51 +08:00 |
|
Steven
|
127303b7aa
|
docs(roadmap): 记下邮箱注册的生产验收,附注册页实拍
CI / backend (push) Canceled after 0s
|
2026-11-03 01:06:59 +08:00 |
|
Steven
|
b5170ad1ce
|
feat(auth): 邮箱验证码注册,生产重新有了自助入口
CI / backend (push) Canceled after 0s
短信登录关掉后,生产没有任何注册入口——这一版补上最普通的那条路:
填邮箱、收验证码、设密码,注册完直接进首页。
先发码再注册,不是"注册时顺带发信"
POST /auth/email/code 只做三件事:查重、限流、发码。
填完密码才被告知"这个邮箱注册过了"最气人,所以占用(2019)在发码前就回。
邮箱既是账号名也是唯一联系方式,所以必须真验一次码
验证通过时间落 app_user.email_verified_at,/auth/me 回 email_verified。
"随手填一个"会让账号变成没人能找回的孤岛。
app_user.phone 改为可空(迁移 0011)
邮箱注册的账号本来就没有手机号。user.phone 对这类账号回空串,
成员列表同理,不再伪装成一串星号。
防刷
同邮箱 60 秒一次(2022)、验证码 10 分钟有效、错 5 次即作废(2021)。
6 位数可以被刮,不设上限等于把注册口子交给脚本。
本机联调开关 EMAIL_CODE_DEV_ECHO
为 true 时把码回给调用方且不发真信——否则每跑一次 e2e 就往真实收件箱塞一封。
生产打开会让 validate_runtime() 拒绝启动。
前端
新增 /register(邮箱、验证码、密码、确认、昵称;60 秒倒计时;
服务端关着注册时整页只说"联系管理员开通"),登录页按 /auth/methods
决定要不要显示「还没有账号?用邮箱注册」。
验证
pytest 139 passed、ruff check / format 全绿
tsc --noEmit 0、vite build 通过
e2e register.mjs 7 项:登录页入口 → 拿码 → 注册落进首页 → 退出用邮箱+密码登回来
→ 同一邮箱再发码回 2019 → 控制台零报错
e2e login-email-smtp.mjs 15 项回归通过(含真发一封测试信)
e2e account.mjs 14 项、knowledge.mjs 全绿
顺带修的两处测试
test_account 的字段白名单补 email_verified(接口确实多了这个字段);
test_config_prod 用 Settings(_env_file=None, ...) 构造,别被本机 .env 里的
EMAIL_CODE_DEV_ECHO=true 带偏——那个开关在生产是硬错误,本机却是跑 e2e 必需的。
|
2026-11-03 01:04:26 +08:00 |
|
Steven
|
c0c5230fd1
|
docs(roadmap): 补登录方式与系统设置的上线情况,附生产实拍
CI / backend (push) Canceled after 0s
|
2026-11-03 00:07:53 +08:00 |
|
Steven
|
742af4dd02
|
test(web): 账号页按钮改名后顺手修 e2e,别让脚本等一个不存在的按钮
|
2026-11-03 00:07:53 +08:00 |
|
Steven
|
7558637567
|
feat(auth): 短信登录默认关,换成手机号/邮箱 + 密码,发信邮箱进后台
CI / backend (push) Canceled after 0s
用户要求:内测的固定验证码 123456 等于"知道手机号就能进任何账号",先关掉;
登录改用账号密码,并多一种"邮箱当账号名"的方式;发信邮箱要能在后台改。
短信那条路(默认关):
- `SMS_LOGIN_ENABLED=false`,`/auth/sms/send` 与 `/auth/login` 统一回 2017,不是静默失败;
- 生产校验改成"开着短信登录就必须配真通道",不开时 provider 是 dev 也不拦;
- 新增 `GET /auth/methods`,登录页据此渲染,关着的路不出现(少一次"点进去才发现不能用")。
邮箱登录:
- `app_user.email` + `uq_app_user_email`(`lower(email)` 部分索引),登录与绑定都归一成小写;
- `POST /auth/login/password` 收 `phone` 或 `email`(二选一),失败统一 2013、连错 10 次锁 15 分钟;
- 绑定在 `PATCH /auth/me`:格式错 2018、被占用 2019、留空串表示解绑。
系统设置(跨租户,只有平台管理员能改):
- 新表 `system_setting`(不挂租户、无 RLS,写权限由 `deps.platform_admin` 把关);
- `GET/PUT /settings/smtp`、`POST /settings/smtp/test`,授权码只写不读,留空表示沿用旧值;
- `services/mail.py` 只用标准库 smtplib:465 走 SSL、587 走 STARTTLS,认证/拒收/网络失败都归 2020 但消息不同;
- Web 新增「系统设置」页(状态卡 + 测试发信),侧栏只对平台管理员显示入口;账号页补「邮箱」。
验证:pytest 130 passed、ruff check/format 全绿、tsc 通过、vite build 通过;
`web/e2e/login-email-smtp.mjs` 15 项全绿(含用 126 授权码真发一封测试邮件)。
顺带记下的两个坑:`platform_admin_phones` 是 list,pydantic-settings 会按 JSON 解析,
要用 `NoDecode` 交给 before-validator 拆逗号;迁移号写成 0009 会撞既有的 0009,一并改成 0010。
|
2026-11-03 00:00:11 +08:00 |
|
Steven
|
23687d6e37
|
style(web): 任务与工单列表标题也加重一档,三个列表看起来是一套
知识页的标题改成 15px/semibold 之后,任务和工单还是 text-sm font-medium,
同一个产品里扫列表时视觉层级不一致。这里统一成 `text-[15px] font-semibold leading-6`,
理由和知识页一样:标题是卡片上唯一要一眼看到的东西,不能和类型、分类、标签那排 chip 等重。
|
2026-11-02 23:59:59 +08:00 |
|
Steven
|
0660e5cbda
|
fix(web): 知识卡片标题加重一档,扫列表时先看到标题而不是一排 chip
CI / backend (push) Canceled after 0s
|
2026-11-02 23:02:25 +08:00 |
|
steven
|
2032e9d981
|
fix(knowledge): 选中分类加左侧色条与更实的描边,管理入口常显
CI / backend (push) Canceled after 0s
|
2026-11-02 20:42:44 +08:00 |
|
steven
|
0b4e829d8b
|
test(knowledge): 颜色校验走 422,修正过长行
CI / backend (push) Canceled after 0s
|
2026-11-02 19:34:20 +08:00 |
|
steven
|
71982f9fa4
|
feat(knowledge): 分类可改名/配色/删除,选中态与加载态更清楚
CI / backend (push) Canceled after 0s
分类之前只能新增,建错了只能干看着。补上三件事:
- knowledge_category 加 color 列(迁移 0009,CHECK 卡 #RRGGBB),
PATCH 支持改名与改色,空串表示取消上色;知识列表带出分类色,前端不用再查。
- 分类行悬停出「⋯」:改名、选色板/手填色值、建子分类、删除(有知识时确认后 force)。
- 选中分类改成主色浅底 + 描边 + 色点,列表头显示当前筛选;切分类时列表淡出并转圈,
不再一动不动等请求回来。
顺带给卡片画分类色上边线:扫一眼就能分出哪几条属于同一类。
|
2026-11-02 19:24:44 +08:00 |
|
steven
|
d7c9670880
|
feat(design): 主色改靛蓝 #4F46E5,Web 与安卓及 Logo 同步
CI / backend (push) Canceled after 0s
- primary 换成 Indigo 阶(600 即 #4F46E5),中性色转 Slate 轴、纸面更冷更白
- 实心主色面(主按钮、工作区选中)用 600,链接/浅底文字仍用 700 保对比度
- 信息色 sky 调深到 #16697C,与靛蓝同屏能分清
- Logo / favicon / PWA 图标 / 安卓自适应图标统一为靛蓝底 + 白线 + 淡紫点,tools/make-icons.py 重新生成
- 文档与 e2e 断言同步(主题色 #4f46e5)
|
2026-11-02 15:33:26 +08:00 |
|
steven
|
288e27e465
|
fix(web): 知识正文按阅读字号排版,重写 Markdown 样式
CI / backend (push) Canceled after 0s
- 正文从 14px 提到 16px / 1.85,不再比编辑器(0.9rem)还小
- 标题靠字重与留白分层,h2 加细分隔线;行内代码、代码块、引用、表格、图片统一重排
- 正文限宽 42rem 居中,长段落不用来回扫行
- 评论与工单说明走 prose-dadao--sm(14px),抽屉里不喧宾夺主
|
2026-11-02 14:29:35 +08:00 |
|
steven
|
684b9b2604
|
fix(web): PWA manifest 主题色跟随新主色
CI / backend (push) Canceled after 0s
安装到主屏后的启动背景与状态栏色此前仍是旧的墨绿/暖白;e2e 断言与文档同步更新。
|
2026-11-01 19:17:35 +08:00 |
|
steven
|
4796b695a3
|
fix(design): 微调中性色与信息色,蓝系下对比度达标
CI / backend (push) Canceled after 0s
ink-500 加深到 4.9:1,sky 往青绿拉开与主色区分;sky 芯片文字 4.6:1。安卓 Theme.kt 同步。
|
2026-11-01 19:14:54 +08:00 |
|
steven
|
b3cd115bd9
|
feat(design): 主色由墨松绿改为景泰蓝蓝系,Logo 与图标同步
CI / backend (push) Canceled after 0s
纸白底改冷调与主色同轴;语义色 sage/wheat/rust 不变,sky 提亮与主色区分。Logo 三段线改浅蓝白、圆点改亮蓝;favicon/PWA 图标/安卓自适应图标/状态栏一并重绘,新增 tools/make-icons.py 作为唯一出处。
|
2026-11-01 18:48:26 +08:00 |
|
Steven
|
694b643683
|
test(web): 用界面把知识列表翻到底,确认「加载更多」不再 500
CI / backend (push) Canceled after 0s
|
2026-11-01 02:58:11 +08:00 |
|
Steven
|
b662429842
|
fix(knowledge): 翻页游标的字段名写错,点「加载更多」必 500
CI / backend (push) Canceled after 0s
知识列表拿游标做 keyset 分页时写成了 `cur.item_id`,而 `Cursor` 只有
`created_at` 和 `id` 两个字段——属性不存在,直接 AttributeError,
前端只看到"服务器内部错误"。
库里不足一页(默认 30 条)时永远走不到这条分支,所以一直没暴露;
旧备忘导进来 84 条之后,用户点「加载更多」就撞上了。
补一条回归用例:造 25 条、每页 10 条翻到底,断言不重不漏、末页游标为空。
|
2026-11-01 02:55:23 +08:00 |
|
Steven
|
d24be46d8c
|
docs(roadmap): PWA 的截图放进 2.Design/screens,文档里指对地方
CI / backend (push) Canceled after 0s
|
2026-10-31 04:35:49 +08:00 |
|
Steven
|
483b554c3c
|
feat(web): Web 装到桌面(PWA),带安装引导与发版提示
CI / backend (push) Canceled after 0s
用 vite-plugin-pwa 生成 manifest 与 Service Worker,不自己写缓存逻辑。
- 装到桌面:manifest 名字/主题色/三个图标(any + maskable + apple-touch-icon),
图标和页头、安卓桌面是同一枚;首页底部给安装引导,能装就弹「安装」,
iOS 给「分享 → 添加到主屏幕」,关掉记在 localStorage
- 发版提示:registerType=prompt,收到新版本只在底部提示,点「刷新」才换。
故意不自动刷新——Vditor、图表是懒加载分片,后台换 chunk 会让面板 404
- 缓存边界:只预缓存页面骨架(约 0.8MB),/api/ 一律 NetworkOnly,
离线宁可报错也不端旧数据;/vditor/ 走 CacheFirst,不拖慢首次安装
- 顺手修:UpdatePrompt 居中改用 flex 包一层,animate-slide-up 的 keyframes
自带 transform,会和 -translate-x-1/2 抢属性,手机窄屏会把卡片推出屏幕
验证:web/e2e/pwa.mjs 19 项全绿(manifest/SW 激活/预缓存/安装引导/iOS 文案/
发版提示/断网仍能打开);tsc 通过、vite build 通过。
已知限制:Service Worker 只在 https 或 localhost 注册,生产入口还是 http,
所以内网 IP 上装不了——需要域名 + 证书反代或隧道,已写进 DEPLOY.md 第 6 节。
|
2026-10-31 04:33:43 +08:00 |
|
Steven
|
370d8923c8
|
test(web): 生产核对脚本自己等节流,别把 429 当成登录坏了
|
2026-10-31 04:33:34 +08:00 |
|
Steven
|
429905892c
|
docs(roadmap): 记下账号与外壳这一轮,包括生产上抓到的 http 安全上下文 bug
CI / backend (push) Canceled after 0s
|
2026-10-31 04:17:33 +08:00 |
|
Steven
|
e3f1310cff
|
fix(web): 内网 http 下写请求与复制邀请链接都会失败
CI / backend (push) Canceled after 0s
crypto.randomUUID() 与 navigator.clipboard 只在安全上下文(https / localhost)里存在,
生产入口是纯 http,于是:
- 所有 POST/PATCH/DELETE 在拼 Idempotency-Key 时直接抛 TypeError,
登录页只显示"登录失败",GET 一切正常,所以这个坑一直没暴露
- 「复制邀请链接」永远回"浏览器不允许自动复制"
改成幂等键自己拼一个 v4(getRandomValues 不受安全上下文限制,退回 Math.random),
复制走 navigator.clipboard → document.execCommand("copy") 两级降级。
顺带说明:这是生产上"能看不能写"的真正原因,不是权限或接口问题。
|
2026-10-31 04:13:13 +08:00 |
|
Steven
|
096acb00e7
|
feat(web): 首页换成和工作区同一套外壳,补上账号设置页
CI / backend (push) Canceled after 0s
首页原来是居中窄栏,点进工作区又变全屏,进出像换了个网站。现在把首页也做成
全宽 + 左侧栏(SideRail 抽出来给首页和账号页共用),工作区卡片改成网格,
新建工作区是网格里的虚线卡片——宽度不再跳。
账号页 /account:基本信息(头像首字母、昵称、只读手机号、加入时间)、
登录密码(设置/修改 + 确认输入)、退出登录。工作区侧栏底部的用户块点进去就是它。
e2e account.mjs 14 项全绿,其中一条断言进工作区后侧栏宽度和首页差值 < 1px。
|
2026-10-31 04:05:05 +08:00 |
|
Steven
|
a5510cdb79
|
feat(auth): 手机号也能用密码登录,验证码之外多一条路
短信通道挂了人就进不来,所以给账号加第二把自助的钥匙:设过密码的用密码进,
没设过的照旧只用验证码,不强制。
- app_user.password_hash(迁移 0008 加列)+ scrypt 哈希(标准库实现,
n=2^14/r=8/p=1,存 scrypt$n$r$p$salt$hash),不引 passlib
- POST /auth/login/password:没设过密码与密码错都回 2013,不区分账号是否存在;
15 分钟内连错 10 次 → 2016,期间正确密码也挡住
- POST /auth/password:设/改共用,改要先验旧密码,新旧相同算弱密码 2015
- PATCH /auth/me 改昵称;用户输出走白名单 _user_out,密码哈希一个字节都不出去
- 登录页顶部「验证码 / 密码」两段切换
tests/test_account.py 10 项;全量 pytest 113 passed,ruff 全绿。
|
2026-10-31 04:04:55 +08:00 |
|
Steven
|
29aae12224
|
fix(app): 开完安装权限回到 App,再点那个按钮要能装上
CI / backend (push) Canceled after 0s
只做了"送去系统设置",没接"回来":用户开完开关回到对话框,
按钮还是「去开启安装权限」,而 openInstallPermission() 发现已授权就直接 return,
点了毫无反应——只能杀掉 App 重来。
现在点之前先复查 canInstall():权限开了就直接交给安装器,没开才跳设置页。
顺带补上模拟器上的全流程验证与截图(弹窗、权限提示、系统安装器、首页入口)。
|
2026-10-31 03:25:11 +08:00 |
|
Steven
|
7668799040
|
feat(app): 安卓应用内升级,安装包由后端发布
CI / backend (push) Canceled after 0s
需求是"改完 Android 端怎么送到手机上"。不发应用商店,也不想每次手动传包,
于是做了最小闭环:后端存一份安装包清单,App 启动时查、下载、交给系统安装器。
后端
- 新增 /app/release/latest 与 /app/release/download/{file},都免登录:
还没装上新包的客户端未必带着有效令牌,检查更新不该被登录挡住
- download_url 用相对路径,客户端拿自己配置的 API 地址拼——同一份清单要在
内网、公网、模拟器之间共用,写死域名只会让其中一个坏掉
- has_update 只比对 version_code,不比版本名字符串(0.10.0 与 0.9.0 的字典序是坑)
- 下载只认清单里登记的文件名,含 ..%2F 一律 9001
- scripts/publish_apk.py 负责复制 APK 并写 latest.json(sha256/size/published_at),
backend/static/apk/ 不进版本库
安卓
- AppUpdater:查清单 → 下到应用自己的 updates 目录(先写 .part 再改名)→
校验 sha256 → 经 FileProvider 交给系统安装器
- UpdateDialog:版本号、更新说明、下载进度、失败重试;「允许安装未知应用」
是系统开关,只跳设置页,不代点
- 启动时自动查一次,首页底部另有「检查更新」入口
- 图标换成和 Web 的 <Logo/> 同一套图形:墨绿圆角底、三条米白圆头线、陶土点;
同时出 mipmap 各密度 PNG、自适应图标与 512px 预览
- versionCode=2 / versionName=0.2.0;发布签名从 android/keystore.properties 读,
没有该文件时回落调试签名,保证别人 clone 下来也能 assembleRelease
文档
- API.md 第 7 节:两个接口、清单字段、签名与 sha256 的约定、9001
- DEPLOY.md 第 5 节:出包命令(JDK 17 + Gradle 8.9 + 干净的 GRADLE_USER_HOME)、
发布清单、传到服务器、验证
- ROADMAP 第 6 节记录本次范围与本机踩过的构建环境坑
验证
- 后端 pytest 103 passed;ruff check / format --check 全绿
- assembleRelease 成功,app-release.apk 7.4 MB,apksigner 验签显示 CN=Dadao Task
- 本地与生产 GET /app/release/latest?version_code=1 → has_update=true,
version_code=2 → false;下载返回 200 / application/vnd.android.package-archive
|
2026-10-31 03:15:00 +08:00 |
|
Steven
|
35fe4d92cb
|
fix(android): 知识 tab 顺序与 Web 对齐,「全部」默认选中
CI / backend (push) Canceled after 0s
- knowledgeTabs 改成 all/draft/published/archived,tab 默认 "all"。
- 空态文案按 tab 区分(全部 / 某一类 / 某个分类),不再写死"待核对"。
- 与 Web 版同一行为:进知识库先看全部,待核对是待办而不是门面。
|
2026-10-31 02:27:55 +08:00 |
|
Steven
|
50227c2114
|
feat(knowledge): 「全部」排到 tab 第一位并默认选中,正文编辑区跟着屏高走
CI / backend (push) Canceled after 0s
- 知识列表 tab 顺序改成 全部 / 待核对 / 已发布 / 已归档,默认停在「全部」:
进知识库先看"我这儿都有些什么",待核对是待办,不该当门面。
- 正文编辑器不再是写死的 240px:新增 useEditorHeight(reserve),
按 window.innerHeight 减掉标题/分类/标签/附件/底部按钮算高度
(新建留 420、编辑留 340,最低 360、最高 960,窗口 resize 时重算),
新建面板与详情编辑两处共用,长文一屏能写完一段。
- 截图脚本 shots-knowledge-view.mjs 支持 EDIT=1 出编辑态图、ALL=1 留在默认 tab;
新增 2.Design/screens/knowledge-tab-all.png、knowledge-edit-tall.png。
- 验证:tsc --noEmit 通过、vite build ✓、e2e knowledge.mjs 9 项与
knowledge-notebook.mjs 17 项全绿(含"侧栏分类按树排""已发布也有编辑")。
|
2026-10-31 02:25:25 +08:00 |
|
Steven
|
b29286778f
|
fix(knowledge): 分类侧栏按树的顺序排,已发布的知识也能直接改
CI / backend (push) Canceled after 0s
两个都是"看着不对劲"的问题:
1. 侧栏把二级分类全堆在一级分类后面(按 depth 排序的副作用),
「技术」和它的上级「我的备忘」隔着十几行,看着像一棵假树。
后端改成按树的先序返回(父分类后紧跟它的子分类,兄弟仍按 sort_order、name),
前端照 depth 缩进就是个真树;二级以下再加一个「·」标记,小屏也分得清。
2. 点开已发布的知识只有「归档」,没有改的入口——因为后端原来硬性拒绝
修改已发布内容(7003)。但"发布"是给团队看的,写错了总要能改:
现在已发布的可以直接 PATCH,改完立刻重建检索切片(chunk_count 当场更新),
状态和版本号照常推进;归档的仍然要先「恢复成草稿」再改。
Web 与安卓同步:抽屉/详情里已发布也显示「编辑」,安卓分类标签区不再锁。
验证:pytest 95 passed(新增"分类按树返回"用例、发布后可改用例)、
ruff 全绿、tsc 0、vite build ✓;`knowledge-notebook.mjs` 17 项、
`knowledge.mjs` 9 项 e2e 全绿。安卓仍未编译(JDK 25 / Gradle 8.7 不兼容)。
|
2026-10-31 02:06:49 +08:00 |
|
Steven
|
441b1b93e1
|
feat(knowledge): 点开知识先看到排好版的正文,要改再点「编辑」
CI / backend (push) Canceled after 0s
之前抽屉一打开就是 Vditor 编辑器,正文是一堆 `##` 和 `-` 的原文,
导入的 84 条旧备忘看着尤其像草稿。现在分成「看」和「写」两态:
- 默认只读:按 Markdown 渲染(markdown-it + DOMPurify),分类与标签是 chip,
附件列表只读;底部是「编辑」和状态动作,右侧显示发布于何时。
- 点「编辑」才挂编辑器;「取消」直接丢弃未保存改动(重新拉一次详情)。
- 安卓详情页同样用 Markwon 把正文排版后再显示,要改走「编辑正文」。
顺带修了 `web/e2e/knowledge.mjs` 里一条早就失效的断言:它等的是
「已发布 · 版本 N」,但界面上没有这行字(git 历史里也没有),
改成等抽屉底部的「发布于 …」。
验证:pytest 94 passed、ruff 全绿、tsc 0、vite build ✓;
`knowledge-notebook.mjs` 16 项、`knowledge.mjs` 7 项 e2e 全绿。
安卓本轮仍未编译(本机 JDK 25 与 Gradle 8.7/AGP 8.7.3 不兼容)。
|
2026-10-31 01:34:25 +08:00 |
|
Steven
|
bbd30ec85e
|
feat(knowledge): 导入脚本能指定环境与账号,生产也导了一份
CI / backend (push) Canceled after 0s
同一套备忘录要在 dev 和生产各导一次,所以把写死的东西参数化:
--api 指定目标环境、--phone 指定记到谁名下、--workspace 指定工作区名。
令牌缓存按「环境 + 手机号」分文件(.local/memo-token-<phone>-<host>.txt):
之前只按账号存,混着用会出现"刚登录完下一个请求就 401"——dev 的令牌拿不到生产的库。
生产(openclaw-01)实测:13605513683 名下「我的备忘」84 条(83 发布 + 1 归档)、
18 个分类,时间戳与 dev 一致。
|
2026-10-31 00:46:01 +08:00 |
|
Steven
|
039e5b903c
|
feat(knowledge): 旧 memos 备忘搬进知识库的导入脚本
CI / backend (push) Canceled after 0s
一次性的搬迁脚本:memo_app(MySQL)→ 知识库。放在 scripts/ 下是为了留个凭据,
说明这 84 条备忘是怎么变成知识的,以及哪些东西刻意没搬。
- 走 HTTP 接口而不是直连库:直接 INSERT 会绕过版本号、审计和切片导入,
导进去的知识搜不到——检索只认 published 且有 chunk 的文档。
- 分类照搬两层(接口上限三级),标签照搬,全部按 note 发布,归档那条保持归档。
- 正文里的图片文件在旧机器上已经找不到了,所以把  换成一行
「原图未迁移:…(旧路径 …)」,不留碎图,也不丢掉原路径。
- 按标题去重、分类复用,中断后可以重跑;token 缓存到 .local/ 避免撞上验证码节流。
- 旧库时间戳是 UTC,接口不接受客户端指定 created_at,所以额外生成一条 SQL
把 item.created_at/updated_at 与 published_at 对回原值。
实测:84 条(83 published + 1 archived)、18 个分类、语义检索可命中。
|
2026-10-31 00:26:14 +08:00 |
|
steven
|
8b6594bf10
|
fix(deploy): 脚本先快照再执行,避免 git reset 改写正在运行的自身
CI / backend (push) Canceled after 0s
|
2026-10-30 22:25:30 +08:00 |
|
steven
|
2ff1f6e9cd
|
fix(deploy): CORS 用 JSON 数组、Redis 口令说明,部署时自动刷新 systemd 单元
CI / backend (push) Canceled after 0s
|
2026-10-30 22:01:00 +08:00 |
|
steven
|
2a118ad407
|
fix(deploy): venv 引导(缺 ensurepip)与国内镜像
CI / backend (push) Canceled after 0s
|
2026-10-30 21:53:42 +08:00 |
|
steven
|
ea5d28304e
|
fix(db): 0006/0007 迁移补上 APP_ROLE 导入
|
2026-10-30 21:53:42 +08:00 |
|
steven
|
57767c77a1
|
feat(deploy): openclaw-01 生产部署脚本、systemd 单元与部署文档
|
2026-10-30 18:49:31 +08:00 |
|
steven
|
9ac3b46430
|
feat(auth): 内测登录改为显式开关,并支持后端单端口托管前端产物
|
2026-10-30 18:49:30 +08:00 |
|
steven
|
2dbe4cf675
|
feat(db): 迁移里的数据库角色名改为环境变量注入
|
2026-10-30 18:49:30 +08:00 |
|
Steven
|
7c84ff3b8d
|
feat(knowledge): 知识能挂附件,新建时就能传
CI / backend (push) Canceled after 0s
知识是备忘录,备忘录不能只有字:手册、日志、导出表格都得能挂上去。
附件本来就落在 item 上(`ALLOWED_KINDS` 早含 knowledge),缺的是"新建时
还没有条目 id"这一段。
- 懒建草稿:`KnowledgeComposer` 第一次传附件(或往正文贴图)时先 `POST /knowledge`
存一条草稿拿到 id,`draftingRef` 去重防止贴图与上传同时打出两条草稿;
之后的保存走 `PATCH /knowledge/{id}`。没传过附件就取消,库里不留空条目。
- `RichTextEditor` 增 `ensureItemId`:没有 itemId 时先把草稿存下来再传图,
于是"新建时不能贴图"这条限制也没了;工具栏上传按钮按是否有落点显隐。
- 新建面板加了附件区:有草稿 id 就是正常的 `AttachmentPanel`(上传/下载/删),
没有就先给一个「添加附件」按钮,点一下建草稿。
- 安卓知识详情加附件区:列表(文件名 + 大小)、`OpenDocument` 上传任意文件、
点「打开」用浏览器看签名直链。编辑器原本只能插图,现在手册/日志也能传。
- 文档:API.md 说明附件三类事项共用同一套接口,以及懒建草稿的先后顺序;
ROADMAP 加 3.13。
验证:后端 pytest 94 passed、ruff 全绿;Web `tsc --noEmit` 通过、`vite build` 通过;
`knowledge-notebook.mjs` 15 项全绿(新增「新建面板里能上传附件」「详情抽屉里能看到
刚传的附件」「附件挂在知识上,详情回读得到」)。安卓端仍是 JDK 25 + Gradle 8.7
不兼容,本次未编译验证。
|
2026-10-30 16:18:32 +08:00 |
|
Steven
|
ffae7e380f
|
fix(knowledge): PATCH 知识时带上 doc_type 直接 500
`patch_knowledge` 把 `doc_type`/`summary` 也塞进了给 `items` 表的 UPDATE,
于是 SQLAlchemy 报 `CompileError: Unconsumed column names: doc_type`——改类型的
请求一律 500。这两个字段本来就属于 `knowledge_doc`,下面几行已有赋值,重复写
进 items 的 values 是错的。
现在 `values` 只放 items 的列(title/body_md),`doc_changed`(doc_type/summary)
单独参与"要不要推进 version"的判断,语义不变:只要这条知识的样子变了,
version 就得 +1,否则下一次保存会拿旧 version 撞 4009。
补一个用例:PATCH 同时改 title/body_md/doc_type/tag_names 应该 200,
doc_type 生效、version 前进一步。这个 bug 之前没暴露,是因为 Web 抽屉只改
标签和分类,没人用 PATCH 改过类型。
|
2026-10-30 16:18:05 +08:00 |
|
Steven
|
9a382e4e8c
|
fix(knowledge): 新建知识的正文换成富文本编辑器,不再是纯文本框
CI / backend (push) Canceled after 0s
「记一条」面板里正文是个 textarea,排版、表格、贴图都没有——创建时反而比
详情页更寒酸。现在新建面板直接用详情同款 Vditor 编辑器,创建与修改一个模样。
- `RichTextEditor` 的 `itemId` 改为可选:新建时还没有条目 id,正文照写,
只是上传按钮不摆出来;真去贴图会提示「先保存这条内容,再回来贴图」。
- `KnowledgeComposer` 的正文换用 `RichTextEditor`(minHeight 320),页脚提示
按有没有条目 id 给不同文案。
- 安卓:新建对话框不再放纯文本正文框,保存后直接进 `RichTextEditorScreen`
写正文(排版、表格、贴图都在那儿),写完回到列表并展开详情。
- e2e 增加一条断言:新建面板里的正文就是富文本编辑器(aria-label=正文 的
contenteditable),正文按真实键盘输入后 Markdown 正常落库。
验证:`tsc --noEmit` 通过、`vite build` 通过、`knowledge-notebook.mjs` 11 项
全绿(新增「新建面板的正文是富文本编辑器」)。安卓端本机仍是 JDK 25 +
Gradle 8.7/AGP 8.7.3 不兼容,本次未编译验证。
|
2026-10-30 15:52:09 +08:00 |
|
Steven
|
ed31af3f46
|
feat(knowledge): 知识改成备忘录形态,一次写全 + 三级分类,抽屉加宽
CI / backend (push) Canceled after 0s
知识以前只能"填个标题就完成",正文、标签、归类都要点进去再补。现在新建
就是一张完整的备忘录:标题、类型、分类、标签、正文一次写完,存完直接打开
详情继续编辑;知识页左侧多了工作区三级分类树,可按分类子树 / 未分类 / 标签
筛选。凡是填信息的抽屉都加宽了,还能一键展开整屏。
后端
- 新增 `knowledge_category`(迁移 0007):`workspace_id NOT NULL` + 自引用
`parent_id`、`depth` 1..3 检查约束、同层重名唯一索引(lower(name))、
RLS 策略与 `dadao_app` 授权;`knowledge_doc.category_id` 外键 ON DELETE
SET NULL。
- 新增 `/knowledge/categories` 增删改查:超三级 `7007`、同层重名 `7008`、
跨租户或不存在 `7006`;`move=true` 才改父级,成环与移动后超过三级的子树
一律拒绝,删除有知识的分类要 `force=true`(知识变未分类)。
- 知识列表支持 `category_id`(含子树)、`uncategorized`、`tag`;输出带
`category`(含 `path` / `full_path`)。
- PATCH 只改分类或标签也会推进 `version`,避免"只动标签"之后下次保存撞
乐观锁 `4009`。
Web
- `Sheet` 增 `size="md/lg/xl"` 与展开整屏按钮;任务、工单、知识抽屉统一
加宽(`TaskDetail` / `Tickets` / `Knowledge`)。
- 知识页:左侧三级分类树(新建、按子树筛选、未分类)、`KnowledgeComposer`
一次写全新的备忘录、列表卡片露出分类路径与标签、详情抽屉里可直接改分类
与标签。
- 抽屉详情只拉一次:重复的 effect 或迟到的响应不再覆盖用户正在敲的内容。
安卓
- `Api.kt`:分类模型与接口、新建/更新带 `tag_names`/`category_id`、
列表带分类与标签筛选。
- 知识页重做:新建改为近全屏大对话框(标题/类型/分类/标签/正文),列表
顶部按分类筛选,详情里能改分类与标签后单独保存。
文档与测试
- `2.Design/API.md`:5.1/5.3/5.4 补分类与筛选参数,新增 5.5 分类接口章节,
错误码补 `7006`/`7007`/`7008`。
- `2.Design/ROADMAP-M0-M1.md`:新增 3.11(三级分类)、3.12(备忘录形态)。
- 新增 `backend/tests/test_knowledge_categories.py`(8 例):备忘形态落库、
三级到底、移动越界与成环、重名、按子树/未分类/标签/关键词筛选、删分类需
`force`、跨租户隔离。
- 新增 `web/e2e/knowledge-notebook.mjs`(10 项,全绿):建两级分类 → 一次
写全 → 卡片与筛选 → 抽屉改标签/分类 → 正文落库 → 重名 7008。
验证:`pytest` 93 passed、`ruff check` / `ruff format --check` 全过、
`tsc --noEmit` 通过、`vite build` 通过、`knowledge-notebook.mjs` 10 项全绿。
安卓端本机只有 JDK 25,Gradle 8.7 / AGP 8.7.3 不支持,本次未做编译验证。
|
2026-10-30 03:51:43 +08:00 |
|
Steven
|
91b3dde670
|
feat(notify): 站内通知与手工开单,指派/改状态/评论/验收都有人收到
CI / backend (push) Canceled after 0s
M2 最后一块:让"该我办了"能出现在系统里,并且允许不经过任务直接开单。
通知(backend/app/services/notify.py 统一出口)
- 七类触发:task_assigned、task_status_changed、item_commented、
ticket_assigned、ticket_submitted、ticket_approved/rejected、task_unblocked
- 不通知自己:notify() 按 actor_id 跳过,调用方不用各自判断
- 已读:GET /notifications、/unread-count、POST /read、/read-all
手工开单
- POST /tickets:直接指定人、初始 open,与任务升级单共用同一条 kind='ticket' item,
ticket_ext.parent_item_id 为空,验收通过不回流任务
- 指派前 _ensure_member 校验在册成员,越界指派返回 1001(避免"挂在看不到的名字上")
前端
- Web:通知页 /w/:wsId/notifications(未读/全部,点开标已读并跳目标);
工单页"新建"面板;AppShell 每 30 秒对未读数,标已读后 dispatch notifications:changed 立刻刷新;
任务与工单支持 ?open= 深链落地即展开
- 安卓:五个 tab 加"通知",未读数 30 秒刷新;工单动作按需求人/办理人/管理员显隐;手工开单对话框
验证
- pytest 85 passed(新增 backend/tests/test_notifications.py 6 个用例)
- ruff check / format --check 全 0
- tsc --noEmit 通过;vite build 成功
- web/e2e/notifications.mjs 12 项断言全绿(手工开单 → B 收到 → 点开跳转 → 未读归零)
- 安卓:本机 JDK 25 与 Gradle 8.7 / AGP 8.7.3 不兼容,本次未做编译验证,
装回 JDK 17 后第一件事是 assembleDebug
|
2026-10-30 02:19:42 +08:00 |
|
dadao
|
d079564d6c
|
feat(knowledge): 语义检索打通——切片、混合打分、只搜已发布
CI / backend (push) Canceled after 0s
之前知识只能"存",不能"找"。这一版把链路补上:切片 → 向量 → 打分 → 带来源返回。
- chunk 表 + 迁移 0006:正文按标题归段切、代码块整块保留、超长按句切、
相邻片重叠 80 字;RLS 与 GRANT 与其它租户表一致
- 向量提供方做成协议:内置 hash(字符二元组,零依赖可复现,作为基线)
与 OpenAI 兼容端点两种,换实现不动业务代码
- 检索是混合打分:向量 0.6 + pg_trgm 0.4。专有编号、单号这类必须原样命中的词
靠词法兜,换个说法表达的同一件事靠向量兜
- 三条纪律:只搜 published(过滤写在 SQL 里)、低于阈值宁可不返回、
每条命中都带来源标题与 item_id
- 发布时自动建索引,改完正文用 POST /knowledge/{id}/reindex 重建
- Web 知识页加检索框与命中卡片;新增 e2e/retrieval.mjs 6 项
为什么不是 pgvector:本机 Docker 起不来、fnsvr-01 没有该扩展且明确不动。
先用 real[] + SQL 余弦把链路跑通,将来换列类型与索引即可,接口不变。
后端 79 passed,e2e smoke/knowledge/richtext/retrieval 全绿。
|
2026-10-29 04:28:09 +08:00 |
|