Commit Graph
87 Commits
Author SHA1 Message Date
Steven d21394a5ac docs(roadmap): 补微信接入的生产实拍,记下回调地址漏前缀的坑
CI / backend (push) Canceled after 0s
2026-11-05 22:08:24 +08:00
Steven c194609193 fix(integrations): 回调地址漏了 /api/v1,生产上打过去是 405
CI / backend (push) Canceled after 0s
生产把应用挂在域名根、路由带 /api/v1 前缀,request.base_url 里没有前缀,
拼出来的地址少了这一段;本地被 Vite 的代理掩盖了。改成从 base_url 截站点根、
前缀一律取 settings.api_prefix,并补一条断言把这个差异钉住。
2026-11-05 22:06:37 +08:00
Steven 90b99f8253 feat(integrations): 微信里发一句话,变成一条任务或知识
CI / backend (push) Canceled after 0s
一条通道一张令牌,机器人把消息 POST 到 /integrations/clawbot/{token},
正文按前缀落到任务或知识草稿:没有前缀就是任务,「知识:」「笔记:」记成知识,
「帮助」只回用法。首行当标题、其余当正文,`#标签` 摘出来挂到事项上。
同一个 msg_id 只处理一次——微信会重推,重推不能变成两条任务。

为什么不塞进 system_setting 的 JSON:一个工作区可能挂多个通道,还得能单独停用。
令牌库里只存 sha256,只在创建/重新生成时显示一次;收消息时还不知道是哪个工作区,
所以 bot_channel 的 RLS 额外放行 token_hash = current_setting('app.bot_token_hash'),
读到通道再 bind_tenant 进上下文,按通道创建者记账。

- 后端:models_integrations.py(bot_channel / bot_message)、迁移 0012(RLS)、
  api/integrations.py(收消息 + 管理接口)、错误码 9002/9003
- Web:/w/:wsId/integrations「接入」页——建通道、复制回调地址与令牌、最近消息、
  停用/换令牌/删除,只有 owner/admin 能改
- 测试:test_clawbot.py 10 例;web/e2e/integrations.mjs 7 项(冒充 clawbot 真推一条)

验证:pytest 150 passed,ruff check/format 全绿,tsc 0,vite build 通过,e2e 7 项全绿。
2026-11-05 22:04:34 +08:00
steven 33a3fc03e0 fix(tasks): 任务页「全部」挪到第一个 tab
CI / backend (push) Canceled after 0s
先看全貌再收窄更顺;Web 默认仍停在「今天」,安卓默认仍是「进行中」,都不靠下标取默认值。
两端一起改,免得同一页两个顺序。
2026-11-05 18:54:59 +08:00
steven 2a9d72a974 fix(android): 知识正文支持表格与删除线,加载提示挪到标题行
CI / backend (push) Canceled after 0s
- 加 markwon ext-tables / ext-strikethrough:同一个 markdown 在 Web 里是表格,
  在 app 里原来会把 | 管道符原样铺出来,现在按 Web 的纸面色画(1dp 浅线、表头浅底)
- 「载入中…」原来挂在分类 chip 行末尾,chip 横向滚动时被挤出屏幕,等于没提示;
  挪到「知识」标题右侧,任何滚动位置都看得见
2026-11-05 17:54:53 +08:00
steven 29805bd61e feat(android): 登录改手机号/邮箱+密码并支持邮箱注册,知识分类可改名配色删除
CI / backend (push) Canceled after 0s
- 生产已关短信登录,登录页先读 /auth/methods 再决定入口:默认手机号/邮箱+密码,
  后端开着短信时才出现验证码登录;邮箱账号 phone 为空,User.phone 改可空
- 新增邮箱验证码注册(一步建号即登录态)
- 知识:分类色点/选中描边/加载态,管理面板可改名、选色(10 色板或手填 hex)、
  建子分类、删除(知识回到未分类),卡片上边线用分类色
- 正文 16sp / 行高 1.7,列表标题 SemiBold,与 Web 阅读排版对齐
- README 更新登录方式与结构说明
2026-11-05 15:55:31 +08:00
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 发布,归档那条保持归档。
- 正文里的图片文件在旧机器上已经找不到了,所以把 ![](path) 换成一行
  「原图未迁移:…(旧路径 …)」,不留碎图,也不丢掉原路径。
- 按标题去重、分类复用,中断后可以重跑;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