diff --git a/.gitignore b/.gitignore index f1be24b..12710f4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ htmlcov/ node_modules/ web/dist/ web/.vite/ +*.tsbuildinfo +web/e2e/artifacts/ # 安卓 android/.gradle/ @@ -38,4 +40,3 @@ tmprfx* # 本机开发用的便携 PostgreSQL / 临时目录(不入库) .local/ - diff --git a/2.Design/API.md b/2.Design/API.md index 6bfe90c..b44b7c7 100644 --- a/2.Design/API.md +++ b/2.Design/API.md @@ -173,18 +173,47 @@ canceled→ todo (恢复) **前端可以据此直接渲染"下一步能点什么",不用自己维护一份规则。** 目标状态与当前相同:直接返回,版本号不变。 -### 3.5 `DELETE /items/{item_id}?version=3` +### 3.5 `POST /items/batch-status`(看板多选拖拽) + +```json +{ "status": "done", + "items": [{ "id": "…", "version": 3 }, { "id": "…", "version": 7 }] } +``` + +```json +{ "updated": [{ "id": "…", "version": 4, "unchanged": false }], + "failed": [{ "id": "…", "code": 4009, "message": "…", "current_version": 8 }] } +``` + +- **不是全成功或全回滚**:拖 10 条里有 1 条被同事改过,其余 9 条照样生效。 + 全回滚会让用户白干一次,而且更难解释"为什么什么都没变"。 +- 单次最多 50 条;`failed[].code` 只会是 `4009`(版本冲突)、`5002`(状态不允许)、`5001`(不存在)。 +- 已经是目标状态的条目返回 `unchanged: true`,计入 `updated` 但不产生事件。 + +### 3.6 `GET /items/board`(看板一屏数据) + +```json +{ "columns": { "todo": [...], "doing": [...], "blocked": [...], "done": [...] }, + "order": ["todo", "doing", "blocked", "done"], + "labels": { "todo": "待办", "doing": "进行中", "blocked": "被阻塞", "done": "已完成" } } +``` + +- 每列默认 30 条(`per_column` 1–100),按"截止早的在前、无截止靠后",再按更新时间。 +- **收件箱不在看板上**:没归类的东西不该占着"正在做的事"的位置。 +- 一次请求拿全,避免拖拽时四列数据不同步。 + +### 3.7 `DELETE /items/{item_id}?version=3` 软删(写 `deleted_at`),只在 `view=deleted` / `include_deleted=true` 时可见。 -### 3.6 评论 +### 3.8 评论 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/items/{item_id}/comments` | 时间正序,游标分页 | | POST | `/items/{item_id}/comments` | `{body_md}` | -### 3.7 标签 `/workspaces/{ws_id}/tags` +### 3.9 标签 `/workspaces/{ws_id}/tags` | 方法 | 路径 | 说明 | |---|---|---| diff --git a/2.Design/DEVELOPMENT.md b/2.Design/DEVELOPMENT.md index bb2d345..b054ebd 100644 --- a/2.Design/DEVELOPMENT.md +++ b/2.Design/DEVELOPMENT.md @@ -78,7 +78,53 @@ ruff check . && ruff format --check . 隔离测试(`tests/test_tenant_isolation.py`)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。 -## 7. 常见坑 +### 真浏览器 e2e + +需要后端(8100)与前端(5180)都在跑: + +```bash +cd web +node e2e/smoke.mjs # 登录→建任务→详情保存→看板拖拽→非法流转被拒,7 项断言 +``` + +用本机缓存的 Chromium(`playwright-core`,不下载浏览器),截图落在 `web/e2e/artifacts/`。 + +### 1000 条下的首屏耗时 + +```bash +cd backend && python scripts/bench_views.py # 连开发库,会自己造 1000 条 +``` + +## 7. 知识库向量库(独立实例) + +知识库的语义检索需要一个带 **pgvector** 的 PostgreSQL,它**不是**业务库: + +```bash +cd deploy +docker-compose -f docker-compose.vector.yml up -d # 127.0.0.1:5434,库名 dadao_knowledge +``` + +然后在 `backend/.env` 里打开: + +```bash +VECTOR_DATABASE_URL=postgresql+asyncpg://dadao_app:dadao_dev_pw@127.0.0.1:5434/dadao_knowledge +``` + +三条设计约束(别绕): + +1. **不碰 fnsvr-01**。那台 PG 17 没有 pgvector,装扩展要动生产机;向量检索吃内存, + 和任务库挤一起互相拖累。 +2. **不配就是没有**。`VECTOR_DATABASE_URL` 为空时,M3 的语义检索直接报"未配置", + 不许悄悄退化成"全表扫描 + 假装是语义匹配"——那种降级没人能发现。 +3. **向量表同样开 RLS**。知识库串租户比任务串租户严重得多:等于把别人的文档喂给你的模型。 + +`EMBEDDING_DIM` 一旦定下来改起来很痛(要重算全部切片),默认 `1024`(bge-m3)。 +换模型 = 重建 `chunk` 表 + 重新向量化,写进 M3 的迁移说明。 + +> 当前这台机器 Docker 起不来(见下),所以向量库还没真正跑起来, +> 上面是配置与脚本,M3 开工前必须先把它跑通。 + +## 8. 常见坑 | 现象 | 原因 | 处理 | |---|---|---| @@ -127,7 +173,7 @@ ruff check . && ruff format --check . - **安装类命令一律写日志文件再 tail**,不要用管道截断输出,否则看不到进度也不知道卡在哪。 - `npm install` 要加 `--ignore-scripts`:沙箱不允许 npm 的 lifecycle 脚本派生进程(`EPERM spawn`)。 -## 8. 提交前自检清单 +## 9. 提交前自检清单 1. `pytest` 全绿,`ruff` 无告警。 2. 新增租户表时:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全。 diff --git a/2.Design/ROADMAP-M0-M1.md b/2.Design/ROADMAP-M0-M1.md index d06d3b5..bce60ab 100644 --- a/2.Design/ROADMAP-M0-M1.md +++ b/2.Design/ROADMAP-M0-M1.md @@ -128,8 +128,9 @@ | 外网 TLS | 被拦:`schannel SEC_E_NO_CREDENTIALS`、`基础连接已经关闭` | 走 HTTP 镜像:PyPI 用 `http://mirrors.aliyun.com/pypi/simple/`,npm 用 `http://mirrors.huaweicloud.com/repository/npm/` | | pip 缓存 | 装包时写缓存会**卡死**(无输出、CPU 空转) | 必须加 `--no-cache-dir`;装包命令输出要重定向到文件再 tail,不要管道截断 | | `pg_ctl` | `could not create restricted token: error code 87` | 直接跑 `postgres.exe`,且必须放在**后台作业**里(父进程退出会连带杀死) | -| fnsvr-01 (192.168.8.160) | 只开放 8418(Gitea) / 3306(MySQL);5432/5433/22 全关 | 该机没有可用的 PostgreSQL 对外端口;SSH 也不通 | -| Android | 本机无 Android SDK、无 gradle-wrapper.jar | 交给 Android Studio 打开 `android/` 自动补全 | +| fnsvr-01 (192.168.8.160) | 只开放 8418(Gitea) / 3306(MySQL) / **8432(PG 17.6,dbadmin/superuser)** | 用途定死:只有 `dadaotask` 与 `dadaotask_test` 两个库,**不再往上装扩展**;能力:无 pgvector,只有 pg_trgm/pgcrypto | +| Android | **无 JDK、无 Gradle**(`java -version`/`gradle -v` 都不存在),SDK 平台目录在但缺构建链 | 装 JDK 17 + Gradle 8.x 后 `./gradlew assembleDebug`;当前安卓代码未被编译器验证过 | +| 向量库 | Docker 起不来 → `deploy/docker-compose.vector.yml` 写了但没跑过 | 等 Docker 可用(或换一台机器)再 `up -d`,端口 5434 | 便携 PG 起法(本机专用): @@ -164,39 +165,49 @@ - [x] 增删改查(`backend/app/api/items.py`)、`POST /{id}/status`、软删(带 version) - [x] 视图:`inbox|active|today|week|done|canceled|all|deleted`;`today/week` 按 `Asia/Shanghai` 切日界(`settings.default_timezone`),不按 UTC - [x] 过滤:`status`(可重复)、`assignee=me|`、`tag`、`q`(标题+正文 ILIKE,实测中文 `周报` 命中) -- [ ] **批量状态变更**未做(M1 先做单条,批量等看板拖拽一起上) +- [x] **批量状态变更**(`POST /items/batch-status`,逐条返回成败,不做全回滚)+ 看板数据 `GET /items/board` - [~] 搜索返回高亮片段:未做,只返回命中行。理由:高亮要在客户端按 query 二次切词,纯展示逻辑,等 Web 有真实使用反馈再补 - [x] 分页:`(sort_key DESC, id DESC)` keyset 游标;`sort=due` 不分页(NULLS LAST 会把 NULL 行切碎,只有今日/逾期短列表用) ### 1.4 Web(3d) - [x] 视图 tab(今天/进行中/收件箱/已完成/全部)+ 搜索框 + 回车快速新建(`web/src/pages/Tasks.tsx`) - [x] 勾选完成/重开、`收下`(inbox→todo)、`开工`(todo→doing)、`阻塞` -- [x] 点标题展开:正文、标签、最近 8 条事件流;`409` 时提示"刚被别人改过"并重载 -- [x] 加载更多(游标),空态文案("今天没有到期的事,挺好。") -- [x] `npm run build` 通过:38 modules,`183.26 kB │ gzip 60.08 kB` -- [ ] 看板拖拽(列 = 状态)未做 -- [ ] 详情侧栏(优先级/截止日期选择器/评论输入/子任务)未做——当前详情只读 -- [ ] 全局快速录入 `#标签 !优先级 @人` 语法未做 -- [ ] 键盘流 `j/k`、`Enter`、`e` 未做 +- [x] **看板拖拽**(`web/src/pages/Board.tsx`):HTML5 拖放,四列,非法流转当场拦下并说明原因;勾选后可批量移动 +- [x] **详情侧栏可编辑**(`web/src/components/TaskDetail.tsx`):标题/优先级/截止时间/标签/备注/评论,全部落库;409 时自动刷新为最新内容 +- [x] 快速录入语法:`#标签` `!高/!中/!低/!1..!3`(`web/src/lib/parse.ts`,语法从标题里剥离) +- [x] 键盘流:`j/k` 选择、`Enter` 完成、`e` 编辑、`Esc` 关闭 +- [x] `npm run build` 通过:41 modules,`195.20 kB │ gzip 63.73 kB` +- [~] `@人` 指派:未做。理由:重名的人比标签多,悄悄指错负责人比不支持更糟;等 M2 有成员选择器再做 +- [~] 子任务(一层):未做,排到 M2 与工单的 `item_link` 一起做——两者本质是同一种"事项之间的边" ### 1.5 安卓(1.5d) -- [ ] 任务列表 + 详情 + 勾选完成(`android/` 目前只有 M0 的登录/工作区壳) +- [x] 任务列表 + 详情弹窗 + 勾选完成 + 快速新建(`android/.../ui/TaskListScreen.kt`) +- [x] 视图切换(今天/进行中/收件箱/已完成)、乐观锁冲突提示与自动刷新 +- [ ] **未编译**:本机没有 JDK、没有 Gradle(`java -version` / `gradle -v` 均不存在), + Android SDK 平台目录存在但缺构建工具链。代码按 Compose 常规写法写,**尚未经过编译器验证**。 + 补 JDK 17 + Gradle 后第一件事就是 `./gradlew assembleDebug`,那时的报错才算数。 +- [ ] 下拉刷新、只读缓存(Room)未做 ### 1.6 通知与提醒(0.8d) -- [ ] 站内通知表与未读角标(表在 M2 随工单一起建) -- [ ] 每日摘要邮件 +- [ ] 站内通知表与未读角标 —— **主动推迟到 M2**:通知的绝大多数触发源是工单(被指派、被催、验收), + M1 只有自己的任务,做出来也没人收到东西。表随 M2 的工单一起建,避免建两次。 +- [ ] 每日摘要邮件(同上推迟) ### 1.7 测试与验收(0.7d) -- [x] `backend/tests/test_items.py` 10 个用例:默认收件箱、状态机 happy path 与非法流转、版本冲突、跨租户 3003 且他人列表为空、搜索+标签、`today` 时区边界、软删、评论与事件流、游标翻页不重复 -- [x] 全量 `pytest -q` → **43 passed**;`ruff check` + `ruff format --check` 全 0 -- [ ] Playwright e2e 未做 -- [ ] 性能口径(1000 条下首屏 < 300ms)未实测 +- [x] `backend/tests/test_items.py` 13 个用例:默认收件箱、状态机 happy path 与非法流转、版本冲突、 + 跨租户 3003 且他人列表为空、搜索+标签、`today` 时区边界、软删、评论与事件流、游标翻页、 + 批量改状态的部分成功、批量非法流转、看板分列 +- [x] 全量 `pytest -q` → **46 passed**;`ruff check` + `ruff format --check` 全 0 +- [x] **Playwright 真浏览器 e2e**(`web/e2e/smoke.mjs`,7 项断言全绿):登录 → 建工作区 → 快速录入解析 + `#标签`/`!优先级` → 详情抽屉保存并回写列表 → 看板拖拽改状态 → 非法拖拽被拦 → 控制台无报错 +- [x] 性能:1000 条任务下 P50 —— 今天 43.1ms / 进行中 42.4ms / 收件箱 42.9ms / 全部 38.4ms / + 搜索 45.0ms / 看板 55.4ms(含本机网络与鉴权中间件,是**下限**) ### M1 完成定义(DOD) - [ ] 连续 3 天所有待办都记在里面,没有回到微信/备忘录 —— 只有真实使用能证伪,代码侧无法替代 - [ ] 冷启动到"建完第一个任务"< 90 秒(新账号实测) -- [ ] 1000 条任务下四视图首屏 < 300ms -- [x] 跨租户测试、越权测试全绿(`test_tenant_isolation.py` + `test_items.py::test_cross_tenant_access_denied`) +- [x] 1000 条任务下四视图首屏 < 300ms —— 实测 P50 38–55ms,最慢的看板 55.4ms +- [x] 跨租户测试、越权测试全绿(`test_tenant_isolation.py` + `test_items.py` 的跨租户与批量用例) --- diff --git a/README.md b/README.md index 2f22563..0006d61 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,11 @@ |---|---| | 手机号验证码登录、刷新令牌、幂等请求 | ✅ 已实现并有测试 | | 工作区 / 成员 / 邀请码加入,跨租户隔离(RLS) | ✅ 同上 | -| 任务:收件箱→待办→进行中→完成、截止时间、标签、评论、事件流、乐观锁 | ✅ 后端 + Web 已通,43 个测试全绿 | -| Web 构建 | ✅ `npm run build` 通过 | +| 任务:收件箱→待办→进行中→完成、截止时间、标签、评论、事件流、乐观锁 | ✅ 后端 + Web 已通,46 个测试全绿 | +| Web 列表 / 看板拖拽 / 详情侧栏编辑 / 键盘流 / 快速录入语法 | ✅ e2e 7 项断言全绿,构建通过 | +| 1000 条任务下的首屏性能 | ✅ 实测 P50 38–55ms(目标 < 300ms) | +| 安卓端任务列表与详情 | ⚠️ 代码已写,**本机无 JDK/Gradle,未编译验证** | | 工单、知识库、LLM 能力 | ⬜ M2–M4 | -| 安卓端任务列表 | ⬜ | ## 快速开始