Repository navigation
feat: complete command workspace task, map, and summary modules - #151
Conversation
- add typed map-view API models and client request for case map data - load map records with request versioning to prevent stale responses - render text-based situation cards for places, clues, tasks, and last-seen data - add object-type filtering, coordinate availability counts, and refresh support - expose precision, review status, source, and event timing for each record - provide loading, empty, and error states for map service degradation - cover map-view endpoint usage and text fallback rendering in tests
- expose commander-only endpoint to list current case members - document member listing in API reference and OpenAPI specification - add frontend task types and APIs for listing, creating, and updating tasks - render task board with volunteer status controls and commander cancellation - allow commanders to assign confirmed-clue tasks to authorized volunteers
- add case summary and clue response interfaces - add API client for the case summary endpoint - load and reset summaries with case workspace state - show confirmed information, pending verification, and safety reminders - provide a commander-only summary refresh action
- add API client for posting task feedback to the review endpoint - show feedback input for volunteers with active tasks - submit feedback as pending review without creating confirmed facts - clear submitted feedback and handle request loading and errors
📝 WalkthroughWalkthrough新增案件接管、成员查询、任务协作、地图态势和授权摘要流程,并同步完善前后端 API 契约、权限控制、家庭工作区限制及相关测试。 Changes案件工作区协作能力
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Commander
participant DashboardPage
participant CaseWorkspacePage
participant cases_api
participant Backend
Commander->>DashboardPage: 打开指挥总览
DashboardPage->>cases_api: 请求案件列表与待受理队列
cases_api->>Backend: GET /api/cases/command-intake
Backend-->>cases_api: 返回最小接案信息
cases_api-->>DashboardPage: 返回待受理案件
Commander->>CaseWorkspacePage: 选择并受理案件
CaseWorkspacePage->>cases_api: POST /api/cases/{case_id}/accept-command
cases_api->>Backend: 创建 commander 成员关系并记录审计
Backend-->>CaseWorkspacePage: 返回 CaseDetail
CaseWorkspacePage->>cases_api: 请求任务、成员、地图和授权摘要
cases_api->>Backend: 调用案件协作 API
Backend-->>CaseWorkspacePage: 返回角色裁剪数据
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Preview readyThe preview for this build is available at https://pr-151-5f3c338a8005.angui.cg8.site. Temporary demo accounts (all use the password
|
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/CaseWorkspacePage.test.tsx (1)
6-38: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winmock 工厂缺少新增 API 函数,导致任务看板/摘要相关调用在所有 commander 模式测试中静默抛错
vi.mock('../api/cases', ...)的返回对象未包含listCaseTasks、listCaseMembers、getCaseSummary、createCaseTask、updateTaskStatus、submitTaskFeedback,而CaseWorkspacePage.tsx的TaskBoard/CaseCollaborationPanel挂载时会调用其中若干函数。由于vi.mock会完全替换模块导出,未列出的导出在测试中即为undefined,调用会抛出TypeError: ... is not a function。这个异常会被组件内部try/catch吞掉并塞进各自的本地错误状态,因此不会让测试直接失败,但意味着本文件里所有mode="commander"的测试(包括本次新增的地图态势测试)实际上从未验证过任务看板与授权摘要这两个新功能的集成是否正确,也会在每次渲染时静默产生额外的内部错误状态。♻️ 建议补全 mock
const mocked = vi.hoisted(() => ({ auth: { token: 'test-session' as string | null }, getCase: vi.fn(), getCaseMapView: vi.fn(), + getCaseSummary: vi.fn(), + listCaseTasks: vi.fn(), + listCaseMembers: vi.fn(), + createCaseTask: vi.fn(), + updateTaskStatus: vi.fn(), + submitTaskFeedback: vi.fn(), getCasePublicProgress: vi.fn(), getCaseResourceConfiguration: vi.fn(), listCases: vi.fn(), listCaseClues: vi.fn(), listCasePois: vi.fn(), addCaseMember: vi.fn(), reviewClue: vi.fn(), })) ... vi.mock('../api/cases', () => ({ getCase: (...args: unknown[]) => mocked.getCase(...args), getCaseMapView: (...args: unknown[]) => mocked.getCaseMapView(...args), + getCaseSummary: (...args: unknown[]) => mocked.getCaseSummary(...args), + listCaseTasks: (...args: unknown[]) => mocked.listCaseTasks(...args), + listCaseMembers: (...args: unknown[]) => mocked.listCaseMembers(...args), + createCaseTask: (...args: unknown[]) => mocked.createCaseTask(...args), + updateTaskStatus: (...args: unknown[]) => mocked.updateTaskStatus(...args), + submitTaskFeedback: (...args: unknown[]) => mocked.submitTaskFeedback(...args), ... }))建议默认
mockResolvedValue为空数组/空分页,避免为每个既有测试都补参数。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 6 - 38, 补全 CaseWorkspacePage.test.tsx 中 api/cases 的 vi.mock 工厂,加入 listCaseTasks、listCaseMembers、getCaseSummary、createCaseTask、updateTaskStatus 和 submitTaskFeedback 的 mocked 实现;为读取接口设置默认空数组或空分页的 resolved value,并为写入接口提供可解析的默认 mock,确保 TaskBoard 与 CaseCollaborationPanel 在 commander 模式测试中正常挂载且不产生被吞掉的 TypeError。
🧹 Nitpick comments (1)
src/application/services/case_service.rs (1)
814-828: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win逐成员串行查询造成 N+1
for membership in memberships内对每个成员分别调用users::Entity::find_by_id和global_capabilities_for_user,共产生2N+1次数据库查询。案件成员数量增长后会线性增加数据库往返次数。可考虑先收集user_id列表,用Column::Id.is_in(user_ids)和对应的能力表批量查询后在内存中做映射拼装,将查询次数降为常数级。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/case_service.rs` around lines 814 - 828, 重构包含成员组装逻辑的函数,避免在 `for membership in memberships` 循环中逐个调用 `users::Entity::find_by_id` 和 `global_capabilities_for_user`。先收集所有 `user_id`,使用 `Column::Id.is_in(user_ids)` 批量查询用户及对应能力数据,再通过内存映射构建 `CaseMemberResponse`,将数据库查询次数降为常数级并保留现有错误与字段转换行为。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/pages/CaseWorkspacePage.tsx`:
- Line 787: Update the volunteer feedback section in CaseWorkspacePage so it
renders and allows submission only when both task.status and the case
detail.status are active. Guard the existing active-task feedback form with
detail.status === 'active', while preserving the current feedback state,
validation, and sendFeedback flow.
- Around line 761-768: Update TaskBoard.load in
frontend/src/pages/CaseWorkspacePage.tsx:761-768 to use a requestVersion ref,
increment or capture the request version consistently with the file’s existing
loader pattern, and only apply setTasks/setMembers when the response version is
current. Apply the same stale-response protection to loadSummary at
frontend/src/pages/CaseWorkspacePage.tsx:931-934, guarding setSummary; both
sites require direct changes.
- Around line 1014-1017: Update the read-only summary JSX in CaseWorkspacePage
around the summary render to display the existing CaseSummary fields
excluded_directions, task_status, and current_focus, alongside confirmed_clues
where applicable. Add clear labeled sections distinguishing confirmed
information, pending verification, excluded directions, task status/current
focus, and safety_reminders, with appropriate empty-state text while preserving
the existing generated_at and source_scope metadata.
- Line 788: Update the risk_notes field handler in the manual task form so
safety_briefing is assigned the current event.target.value on every change
instead of reusing the existing draft value. Keep risk_notes synchronized with
the same complete text so createCaseTask persists and displays the full safety
briefing.
---
Outside diff comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 6-38: 补全 CaseWorkspacePage.test.tsx 中 api/cases 的 vi.mock 工厂,加入
listCaseTasks、listCaseMembers、getCaseSummary、createCaseTask、updateTaskStatus 和
submitTaskFeedback 的 mocked 实现;为读取接口设置默认空数组或空分页的 resolved value,并为写入接口提供可解析的默认
mock,确保 TaskBoard 与 CaseCollaborationPanel 在 commander 模式测试中正常挂载且不产生被吞掉的
TypeError。
---
Nitpick comments:
In `@src/application/services/case_service.rs`:
- Around line 814-828: 重构包含成员组装逻辑的函数,避免在 `for membership in memberships` 循环中逐个调用
`users::Entity::find_by_id` 和 `global_capabilities_for_user`。先收集所有 `user_id`,使用
`Column::Id.is_in(user_ids)` 批量查询用户及对应能力数据,再通过内存映射构建
`CaseMemberResponse`,将数据库查询次数降为常数级并保留现有错误与字段转换行为。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a81de4c-c3de-42fe-a420-185122a29153
📒 Files selected for processing (8)
docs/API.mddocs/openapi.yamlfrontend/src/api/cases.test.tsfrontend/src/api/cases.tsfrontend/src/pages/CaseWorkspacePage.test.tsxfrontend/src/pages/CaseWorkspacePage.tsxsrc/api/routes/cases.rssrc/application/services/case_service.rs
| const load = useCallback(async () => { | ||
| if (!token) return | ||
| setLoading(true); setError('') | ||
| try { | ||
| const [page, nextMembers] = await Promise.all([listCaseTasks(token, detail.id), isCommander ? listCaseMembers(token, detail.id) : Promise.resolve([])]) | ||
| setTasks(page.items); setMembers(nextMembers) | ||
| } catch (cause) { setError(messageFrom(cause)) } finally { setLoading(false) } | ||
| }, [detail.id, isCommander, token]) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
两处新增加载函数遗漏了本文件已建立的“过期响应”防护模式
本文件对每个会随案件切换而重新拉取数据的 loader 都使用了 requestVersion/*RequestVersion 引用来丢弃切换案件后才返回的过期响应(loadDetail、loadClueQueue、loadPublicProgress、CaseSituationPanel.loadMapView,并有对应测试如“does not let an earlier detail request overwrite the most recently selected case”“discards a stale family public-progress response after switching cases”)。这次新增的两个 loader 没有沿用该模式:快速切换案件时,旧案件的任务/成员/摘要响应可能在新案件渲染后才 resolve,从而用旧案件数据覆盖当前案件的任务看板或授权摘要,属于跨案件的过期数据展示问题。
frontend/src/pages/CaseWorkspacePage.tsx#L761-L768:为TaskBoard.load增加requestVersion引用,仅在版本匹配时才setTasks/setMembers。frontend/src/pages/CaseWorkspacePage.tsx#L931-L934:为loadSummary增加同样的版本防护,仅在版本匹配时才setSummary。
📍 Affects 1 file
frontend/src/pages/CaseWorkspacePage.tsx#L761-L768(this comment)frontend/src/pages/CaseWorkspacePage.tsx#L931-L934
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/CaseWorkspacePage.tsx` around lines 761 - 768, Update
TaskBoard.load in frontend/src/pages/CaseWorkspacePage.tsx:761-768 to use a
requestVersion ref, increment or capture the request version consistently with
the file’s existing loader pattern, and only apply setTasks/setMembers when the
response version is current. Apply the same stale-response protection to
loadSummary at frontend/src/pages/CaseWorkspacePage.tsx:931-934, guarding
setSummary; both sites require direct changes.
| <section className="border-b border-slate-200 px-5 py-5 sm:px-6" aria-label="任务看板"> | ||
| <div className="flex items-start justify-between gap-3"><div><h3 className="m-0 text-base font-bold text-slate-950">任务看板</h3><p className="mb-0 mt-1 text-xs leading-5 text-slate-600">任务只由指挥人工创建和分配;状态、人员与失败结果均以服务端返回为准。</p></div><Button size="sm" variant="ghost" isDisabled={loading} onPress={() => void load()}><RefreshCw size={16} />刷新任务</Button></div> | ||
| {error && <div className="mt-3"><Message tone="error">{error}</Message></div>} | ||
| {loading ? <p className="mb-0 mt-3 text-sm text-slate-500">正在加载任务…</p> : tasks.length === 0 ? <p className="mb-0 mt-3 text-sm text-slate-500">当前没有你可查看的任务。</p> : <div className="mt-3 grid gap-3 lg:grid-cols-2">{tasks.map((task) => <article key={task.id} className="rounded-md border border-slate-200 bg-white p-3"><div className="flex flex-wrap items-center gap-2"><Chip size="sm" variant="soft"><Chip.Label>{taskStatusLabels[task.status]}</Chip.Label></Chip><Chip size="sm" variant="soft"><Chip.Label>{task.risk_level} 风险</Chip.Label></Chip></div><h4 className="mb-0 mt-3 text-sm font-semibold text-slate-950">{task.title}</h4><p className="mb-0 mt-1 text-sm text-slate-700">{task.objective}</p><p className="mb-0 mt-2 text-xs text-slate-500">区域:{task.area_text} · 截止:{formatDate(task.due_at) ?? '待补充'}</p><p className="mb-0 mt-1 text-xs text-slate-600">安全提示:{task.safety_briefing}</p>{detail.access_role === 'volunteer' && <><div className="mt-3 flex flex-wrap gap-2">{task.status === 'assigned' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'accepted')}>接受</Button>}{task.status === 'accepted' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'active')}>开始执行</Button>}{task.status === 'active' && <><Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'blocked')}>标记受阻</Button><Button size="sm" variant="primary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'completed')}>完成</Button></>}{task.status === 'blocked' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'active')}>继续执行</Button>}</div>{task.status === 'active' && <div className="mt-3"><TextArea aria-label={`${task.title} 任务反馈`} value={feedback[task.id] ?? ''} maxLength={4000} rows={2} placeholder="提交现场反馈;将进入人工审核,不会直接成为确认事实" onChange={(event) => setFeedback((current) => ({ ...current, [task.id]: event.target.value }))} fullWidth /><div className="mt-2 flex justify-end"><Button size="sm" variant="secondary" isDisabled={!feedback[task.id]?.trim() || busy === `feedback-${task.id}`} onPress={() => void sendFeedback(task.id)}>提交待审核反馈</Button></div></div>}</>}{isCommander && !['completed', 'cancelled'].includes(task.status) && <div className="mt-3"><Button size="sm" variant="ghost" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'cancelled')}>取消任务</Button></div>}</article>)}</div>} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
志愿者反馈框未校验案件状态
反馈输入区仅按 task.status === 'active' 显示,但 docs/API.md 明确要求“仅允许该任务受领志愿者在任务和案件均为 active 时提交”。若案件已变为 resolved/closed 而任务仍是 active,UI 仍会展示反馈框,提交后才会被服务端拒绝,与文件中其它表单统一用 detail.status !== 'closed'(甚至更严格的 active 判断)做前置校验的风格不一致。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/CaseWorkspacePage.tsx` at line 787, Update the volunteer
feedback section in CaseWorkspacePage so it renders and allows submission only
when both task.status and the case detail.status are active. Guard the existing
active-task feedback form with detail.status === 'active', while preserving the
current feedback state, validation, and sendFeedback flow.
| <div className="flex items-start justify-between gap-3"><div><h3 className="m-0 text-base font-bold text-slate-950">任务看板</h3><p className="mb-0 mt-1 text-xs leading-5 text-slate-600">任务只由指挥人工创建和分配;状态、人员与失败结果均以服务端返回为准。</p></div><Button size="sm" variant="ghost" isDisabled={loading} onPress={() => void load()}><RefreshCw size={16} />刷新任务</Button></div> | ||
| {error && <div className="mt-3"><Message tone="error">{error}</Message></div>} | ||
| {loading ? <p className="mb-0 mt-3 text-sm text-slate-500">正在加载任务…</p> : tasks.length === 0 ? <p className="mb-0 mt-3 text-sm text-slate-500">当前没有你可查看的任务。</p> : <div className="mt-3 grid gap-3 lg:grid-cols-2">{tasks.map((task) => <article key={task.id} className="rounded-md border border-slate-200 bg-white p-3"><div className="flex flex-wrap items-center gap-2"><Chip size="sm" variant="soft"><Chip.Label>{taskStatusLabels[task.status]}</Chip.Label></Chip><Chip size="sm" variant="soft"><Chip.Label>{task.risk_level} 风险</Chip.Label></Chip></div><h4 className="mb-0 mt-3 text-sm font-semibold text-slate-950">{task.title}</h4><p className="mb-0 mt-1 text-sm text-slate-700">{task.objective}</p><p className="mb-0 mt-2 text-xs text-slate-500">区域:{task.area_text} · 截止:{formatDate(task.due_at) ?? '待补充'}</p><p className="mb-0 mt-1 text-xs text-slate-600">安全提示:{task.safety_briefing}</p>{detail.access_role === 'volunteer' && <><div className="mt-3 flex flex-wrap gap-2">{task.status === 'assigned' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'accepted')}>接受</Button>}{task.status === 'accepted' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'active')}>开始执行</Button>}{task.status === 'active' && <><Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'blocked')}>标记受阻</Button><Button size="sm" variant="primary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'completed')}>完成</Button></>}{task.status === 'blocked' && <Button size="sm" variant="secondary" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'active')}>继续执行</Button>}</div>{task.status === 'active' && <div className="mt-3"><TextArea aria-label={`${task.title} 任务反馈`} value={feedback[task.id] ?? ''} maxLength={4000} rows={2} placeholder="提交现场反馈;将进入人工审核,不会直接成为确认事实" onChange={(event) => setFeedback((current) => ({ ...current, [task.id]: event.target.value }))} fullWidth /><div className="mt-2 flex justify-end"><Button size="sm" variant="secondary" isDisabled={!feedback[task.id]?.trim() || busy === `feedback-${task.id}`} onPress={() => void sendFeedback(task.id)}>提交待审核反馈</Button></div></div>}</>}{isCommander && !['completed', 'cancelled'].includes(task.status) && <div className="mt-3"><Button size="sm" variant="ghost" isDisabled={busy === task.id} onPress={() => void changeStatus(task.id, 'cancelled')}>取消任务</Button></div>}</article>)}</div>} | ||
| {isCommander && detail.status !== 'closed' && <form className="mt-5 grid gap-3 border-t border-slate-200 pt-5 lg:grid-cols-2" onSubmit={(event) => { event.preventDefault(); if (!token) return; setBusy('create'); setError(''); createCaseTask(token, detail.id, draft).then(async () => { setDraft(emptyTask()); await load() }).catch((cause) => setError(messageFrom(cause))).finally(() => setBusy('')) }}><h4 className="m-0 text-sm font-semibold text-slate-950 lg:col-span-2">人工创建并分配任务</h4><Field label="关联的已确认线索" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.source_clue_id} onChange={(event) => setDraft({ ...draft, source_clue_id: event.target.value })}><option value="">请选择</option>{confirmedClues.map((clue) => <option key={clue.id} value={clue.id}>{clue.content.slice(0, 80)}</option>)}</select></Field><Field label="志愿者" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.volunteer_user_id} onChange={(event) => setDraft({ ...draft, volunteer_user_id: event.target.value })}><option value="">请选择</option>{volunteers.map((member) => <option key={member.user_id} value={member.user_id}>{member.display_name}</option>)}</select></Field><Field label="任务标题" required><Input required value={draft.title} maxLength={200} onChange={(event) => setDraft({ ...draft, title: event.target.value })} fullWidth /></Field><Field label="任务区域" required><Input required value={draft.area_text} maxLength={500} onChange={(event) => setDraft({ ...draft, area_text: event.target.value })} fullWidth /></Field><Field label="目标" required><TextArea required value={draft.objective} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, objective: event.target.value })} fullWidth /></Field><Field label="截止时间" required><Input required type="datetime-local" value={draft.due_at} onChange={(event) => setDraft({ ...draft, due_at: event.target.value ? new Date(event.target.value).toISOString() : '' })} fullWidth /></Field><Field label="背景说明" required><TextArea required value={draft.background} maxLength={10000} rows={2} onChange={(event) => setDraft({ ...draft, background: event.target.value })} fullWidth /></Field><Field label="风险说明与安全提示" required><TextArea required value={draft.risk_notes} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, risk_notes: event.target.value, safety_briefing: draft.safety_briefing || event.target.value })} fullWidth /></Field><Field label="预期反馈" required><TextArea required value={draft.expected_feedback} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, expected_feedback: event.target.value })} fullWidth /></Field><div className="flex items-end"><Button type="submit" size="sm" variant="primary" isDisabled={busy === 'create' || confirmedClues.length === 0 || volunteers.length === 0}>创建并分配</Button></div></form>} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
创建任务表单中 safety_briefing 逻辑错误,会被“卡”在首字符
risk_notes 的 onChange 里:safety_briefing: draft.safety_briefing || event.target.value。首次输入后 draft.safety_briefing 变为非空字符串,此后每次按键该表达式都会短路返回旧值,safety_briefing 永远停留在第一次输入时的内容(例如只有第一个字符),而 risk_notes 却会正常更新为完整文本。该字段会原样展示给志愿者(安全提示:{task.safety_briefing})并随 createCaseTask 持久化,属于安全提示信息错误/不完整的功能性缺陷。
As per path instructions, frontend changes should focus on data exposure and user-visible failure paths; incorrect safety-briefing text delivered to field volunteers is exactly such a failure path.
🐛 建议修复
-<TextArea required value={draft.risk_notes} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, risk_notes: event.target.value, safety_briefing: draft.safety_briefing || event.target.value })} fullWidth />
+<TextArea required value={draft.risk_notes} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, risk_notes: event.target.value, safety_briefing: event.target.value })} fullWidth />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {isCommander && detail.status !== 'closed' && <form className="mt-5 grid gap-3 border-t border-slate-200 pt-5 lg:grid-cols-2" onSubmit={(event) => { event.preventDefault(); if (!token) return; setBusy('create'); setError(''); createCaseTask(token, detail.id, draft).then(async () => { setDraft(emptyTask()); await load() }).catch((cause) => setError(messageFrom(cause))).finally(() => setBusy('')) }}><h4 className="m-0 text-sm font-semibold text-slate-950 lg:col-span-2">人工创建并分配任务</h4><Field label="关联的已确认线索" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.source_clue_id} onChange={(event) => setDraft({ ...draft, source_clue_id: event.target.value })}><option value="">请选择</option>{confirmedClues.map((clue) => <option key={clue.id} value={clue.id}>{clue.content.slice(0, 80)}</option>)}</select></Field><Field label="志愿者" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.volunteer_user_id} onChange={(event) => setDraft({ ...draft, volunteer_user_id: event.target.value })}><option value="">请选择</option>{volunteers.map((member) => <option key={member.user_id} value={member.user_id}>{member.display_name}</option>)}</select></Field><Field label="任务标题" required><Input required value={draft.title} maxLength={200} onChange={(event) => setDraft({ ...draft, title: event.target.value })} fullWidth /></Field><Field label="任务区域" required><Input required value={draft.area_text} maxLength={500} onChange={(event) => setDraft({ ...draft, area_text: event.target.value })} fullWidth /></Field><Field label="目标" required><TextArea required value={draft.objective} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, objective: event.target.value })} fullWidth /></Field><Field label="截止时间" required><Input required type="datetime-local" value={draft.due_at} onChange={(event) => setDraft({ ...draft, due_at: event.target.value ? new Date(event.target.value).toISOString() : '' })} fullWidth /></Field><Field label="背景说明" required><TextArea required value={draft.background} maxLength={10000} rows={2} onChange={(event) => setDraft({ ...draft, background: event.target.value })} fullWidth /></Field><Field label="风险说明与安全提示" required><TextArea required value={draft.risk_notes} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, risk_notes: event.target.value, safety_briefing: draft.safety_briefing || event.target.value })} fullWidth /></Field><Field label="预期反馈" required><TextArea required value={draft.expected_feedback} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, expected_feedback: event.target.value })} fullWidth /></Field><div className="flex items-end"><Button type="submit" size="sm" variant="primary" isDisabled={busy === 'create' || confirmedClues.length === 0 || volunteers.length === 0}>创建并分配</Button></div></form>} | |
| {isCommander && detail.status !== 'closed' && <form className="mt-5 grid gap-3 border-t border-slate-200 pt-5 lg:grid-cols-2" onSubmit={(event) => { event.preventDefault(); if (!token) return; setBusy('create'); setError(''); createCaseTask(token, detail.id, draft).then(async () => { setDraft(emptyTask()); await load() }).catch((cause) => setError(messageFrom(cause))).finally(() => setBusy('')) }}><h4 className="m-0 text-sm font-semibold text-slate-950 lg:col-span-2">人工创建并分配任务</h4><Field label="关联的已确认线索" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.source_clue_id} onChange={(event) => setDraft({ ...draft, source_clue_id: event.target.value })}><option value="">请选择</option>{confirmedClues.map((clue) => <option key={clue.id} value={clue.id}>{clue.content.slice(0, 80)}</option>)}</select></Field><Field label="志愿者" required><select required className="min-h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm" value={draft.volunteer_user_id} onChange={(event) => setDraft({ ...draft, volunteer_user_id: event.target.value })}><option value="">请选择</option>{volunteers.map((member) => <option key={member.user_id} value={member.user_id}>{member.display_name}</option>)}</select></Field><Field label="任务标题" required><Input required value={draft.title} maxLength={200} onChange={(event) => setDraft({ ...draft, title: event.target.value })} fullWidth /></Field><Field label="任务区域" required><Input required value={draft.area_text} maxLength={500} onChange={(event) => setDraft({ ...draft, area_text: event.target.value })} fullWidth /></Field><Field label="目标" required><TextArea required value={draft.objective} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, objective: event.target.value })} fullWidth /></Field><Field label="截止时间" required><Input required type="datetime-local" value={draft.due_at} onChange={(event) => setDraft({ ...draft, due_at: event.target.value ? new Date(event.target.value).toISOString() : '' })} fullWidth /></Field><Field label="背景说明" required><TextArea required value={draft.background} maxLength={10000} rows={2} onChange={(event) => setDraft({ ...draft, background: event.target.value })} fullWidth /></Field><Field label="风险说明与安全提示" required><TextArea required value={draft.risk_notes} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, risk_notes: event.target.value, safety_briefing: event.target.value })} fullWidth /></Field><Field label="预期反馈" required><TextArea required value={draft.expected_feedback} maxLength={4000} rows={2} onChange={(event) => setDraft({ ...draft, expected_feedback: event.target.value })} fullWidth /></Field><div className="flex items-end"><Button type="submit" size="sm" variant="primary" isDisabled={busy === 'create' || confirmedClues.length === 0 || volunteers.length === 0}>创建并分配</Button></div></form>} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/CaseWorkspacePage.tsx` at line 788, Update the risk_notes
field handler in the manual task form so safety_briefing is assigned the current
event.target.value on every change instead of reusing the existing draft value.
Keep risk_notes synchronized with the same complete text so createCaseTask
persists and displays the full safety briefing.
Source: Path instructions
| <div className="rounded-md border border-slate-200 bg-white p-3"> | ||
| <div className="flex items-center justify-between gap-3"><h3 className="m-0 text-base font-bold text-slate-950">经授权案件摘要</h3><Button size="sm" variant="ghost" isDisabled={!token || busy !== ''} onPress={() => void loadSummary()}>刷新摘要</Button></div> | ||
| {!summary ? <p className="mb-0 mt-2 text-sm text-slate-500">正在加载服务端确定性摘要…</p> : <><p className="mb-0 mt-2 text-xs text-slate-500">生成于 {formatDate(summary.generated_at)} · 来源范围:{summary.source_scope.join('、')}</p><div className="mt-3 grid gap-3 sm:grid-cols-2"><div><strong className="text-sm text-slate-900">最后确认信息</strong><p className="mb-0 mt-1 text-sm leading-6 text-slate-700">{summary.last_confirmed_information?.content ?? '暂无已确认信息。'}</p></div><div><strong className="text-sm text-slate-900">待核实事项</strong><p className="mb-0 mt-1 text-sm leading-6 text-slate-700">{summary.pending_verification.length === 0 ? '暂无。' : `${summary.pending_verification.length} 项仍需人工核实,未作为确认事实发布。`}</p></div></div>{summary.safety_reminders.length > 0 && <ul className="mb-0 mt-3 list-disc space-y-1 pl-5 text-xs leading-5 text-slate-600">{summary.safety_reminders.map((item) => <li key={item}>{item}</li>)}</ul>}</>} | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
“经授权案件摘要”未展示 excluded_directions/task_status/current_focus
CaseSummary 类型包含 confirmed_clues、excluded_directions、current_focus、task_status 字段并已被 getCaseSummary 取回,但当前只渲染了 generated_at/source_scope/last_confirmed_information/pending_verification.length/safety_reminders。PR 目标明确要求摘要要“区分确认信息、待核实、已排除方向、任务状态与安全提醒”,目前已排除方向和任务状态在该只读摘要区完全缺失,与既定目标不符。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/CaseWorkspacePage.tsx` around lines 1014 - 1017, Update
the read-only summary JSX in CaseWorkspacePage around the summary render to
display the existing CaseSummary fields excluded_directions, task_status, and
current_focus, alongside confirmed_clues where applicable. Add clear labeled
sections distinguishing confirmed information, pending verification, excluded
directions, task status/current focus, and safety_reminders, with appropriate
empty-state text while preserving the existing generated_at and source_scope
metadata.
- expose pending active cases with minimal intake information - allow commanders to accept unassigned cases and gain case access - record commander acceptance in the audit log - add commander intake API client methods and response model - display and refresh pending intake cases in the commander workspace - document command intake and acceptance endpoints - allow family members to add other family members to cases
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/application/services/case_service.rs`:
- Around line 749-752: 同步更新 docs/API.md 中成员接口的接口表和授权说明,使其明确反映 CaseRole 权限:family
可添加 family 或 commander,避免继续表述为仅可邀请 commander;保留 commander
添加案件成员的现有说明,并确保相关授权矩阵与实际检查逻辑一致。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d06008d9-7b7f-4df0-9912-c630fa42f734
📒 Files selected for processing (6)
docs/API.mdfrontend/src/api/cases.tsfrontend/src/pages/CaseWorkspacePage.tsxsrc/api/models.rssrc/api/routes/cases.rssrc/application/services/case_service.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/api/cases.ts
- frontend/src/pages/CaseWorkspacePage.tsx
- expose a minimal queue of active cases awaiting commander acceptance - allow commander-capable accounts to accept pending cases atomically - create commander membership and audit acceptance events on acceptance - return conflicts for inactive or already accepted cases - restrict family invitations to family and commander case roles - document intake and acceptance endpoints in API and OpenAPI specs - add frontend coverage for accepting pending commander cases - add API tests for minimal queue visibility and duplicate acceptance
Preview readyThe preview for this build is available at https://pr-151-7977c541b84a.angui.cg8.site. Temporary demo accounts (all use the password
|
- hide family navigation from operational accounts - redirect operational users from the family route - add coverage for family workspace access rules
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
tests/api/case_members_post.rs (1)
103-150: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win补充接管接口的权限与状态负向覆盖。
当前仅覆盖成功和重复接管;还应验证无 commander capability 返回
403,以及非active案件返回409。这两项是accept_command_case的服务端访问控制与状态约束,避免后续回归放宽接管条件。As per path instructions,
tests/**: Check that changed behavior has meaningful positive, negative, and boundary-case coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/api/case_members_post.rs` around lines 103 - 150, Extend the test coverage around commander_accepts_a_minimal_pending_case_without_global_case_visibility and accept_command_case to verify that a caller without commander capability receives 403, and that attempting takeover for a non-active case receives 409. Keep the existing successful and duplicate-takeover assertions, and construct the requests using the test context’s existing roles and case-state setup.Source: Path instructions
frontend/src/pages/CaseWorkspacePage.test.tsx (1)
85-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win隔离新增 mock 实现,避免测试顺序依赖。
除非 Vitest 配置已全局重置 mock 实现,否则本用例留下的
listCommandIntake和acceptCommandCase返回值会被后续 commander 用例继承。请在测试生命周期中重置实现,确保每个用例只使用自己的 API 响应。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 85 - 95, Reset mock implementations between tests in the CaseWorkspacePage test lifecycle so the listCommandIntake and acceptCommandCase responses configured by this case do not leak into later commander cases. Use the existing beforeEach/afterEach setup or add per-test mock restoration, while preserving each test’s explicitly configured API responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/App.test.tsx`:
- Around line 73-77: Update the test case “redirects a commander away from the
family workspace” to assert the final route pathname is “/” after rendering
“/family”. Add a pathname probe or equivalent router-location assertion, while
retaining the existing rendering and role-based checks.
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 85-95: 增强“lets a commander accept a minimal pending case before
viewing it”测试:在 acceptCommandCase 成功后断言案件列表或查看入口已刷新并可见,确保受理后的案件可访问;另外新增
acceptCommandCase 返回冲突错误的失败路径,断言界面展示用户可见的错误信息,并保留现有 session 与案件 ID 参数校验。
---
Nitpick comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 85-95: Reset mock implementations between tests in the
CaseWorkspacePage test lifecycle so the listCommandIntake and acceptCommandCase
responses configured by this case do not leak into later commander cases. Use
the existing beforeEach/afterEach setup or add per-test mock restoration, while
preserving each test’s explicitly configured API responses.
In `@tests/api/case_members_post.rs`:
- Around line 103-150: Extend the test coverage around
commander_accepts_a_minimal_pending_case_without_global_case_visibility and
accept_command_case to verify that a caller without commander capability
receives 403, and that attempting takeover for a non-active case receives 409.
Keep the existing successful and duplicate-takeover assertions, and construct
the requests using the test context’s existing roles and case-state setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f607f7a7-dbf7-40e6-9724-acf1d49a92ef
📒 Files selected for processing (9)
docs/API.mddocs/openapi.yamlfrontend/src/App.test.tsxfrontend/src/App.tsxfrontend/src/components/AppShell.tsxfrontend/src/pages/CaseWorkspacePage.test.tsxsrc/api/routes/cases.rssrc/application/services/case_service.rstests/api/case_members_post.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/API.md
- src/api/routes/cases.rs
- src/application/services/case_service.rs
| it('redirects a commander away from the family workspace', async () => { | ||
| setAuth('member', ['commander']) | ||
| renderApp('/family') | ||
| expect(await screen.findByText('行动总览')).toBeInTheDocument() | ||
| expect(screen.queryByRole('link', { name: '家属端' })).not.toBeInTheDocument() |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
直接断言重定向目标,避免测试误判。
当前断言没有检查最终路径;“家属端”导航缺失对 commander 本来就成立,不能证明 /family 已重定向到 /。请增加 pathname 探针或等价的路由位置断言。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/App.test.tsx` around lines 73 - 77, Update the test case
“redirects a commander away from the family workspace” to assert the final route
pathname is “/” after rendering “/family”. Add a pathname probe or equivalent
router-location assertion, while retaining the existing rendering and role-based
checks.
| it('lets a commander accept a minimal pending case before viewing it', async () => { | ||
| mocked.listCases.mockResolvedValue([]) | ||
| mocked.listCommandIntake.mockResolvedValue([{ id: 'pending-case', case_code: 'AG-PENDING', created_at: '2026-07-24T00:00:00Z', last_seen_at: null, area_hint: 'Fictional north gate' }]) | ||
| mocked.acceptCommandCase.mockResolvedValue(detail('pending-case', 'Accepted case', 'commander')) | ||
|
|
||
| render(<CaseWorkspacePage mode="commander" />) | ||
|
|
||
| expect(await screen.findByText('AG-PENDING')).toBeInTheDocument() | ||
| fireEvent.click(screen.getByRole('button', { name: '受理案件' })) | ||
| await waitFor(() => expect(mocked.acceptCommandCase).toHaveBeenCalledWith('test-session', 'pending-case')) | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
补上受理后的状态与失败路径断言。
当前用例只验证 API 参数;即使受理后案件列表没有刷新、案件不可查看,或冲突错误被静默处理,测试仍会通过。请断言受理后的案件可见/队列已刷新,并覆盖 acceptCommandCase 返回冲突时的用户可见错误。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 85 - 95, 增强“lets
a commander accept a minimal pending case before viewing it”测试:在
acceptCommandCase 成功后断言案件列表或查看入口已刷新并可见,确保受理后的案件可访问;另外新增 acceptCommandCase
返回冲突错误的失败路径,断言界面展示用户可见的错误信息,并保留现有 session 与案件 ID 参数校验。
Preview readyThe preview for this build is available at https://pr-151-d08467dccc28.angui.cg8.site. Temporary demo accounts (all use the password
|
- let family members invite family members or commanders - keep volunteer invitations restricted to commanders - submit the selected permitted role when adding members - add coverage for family member invitation role options
There was a problem hiding this comment.
🧹 Nitpick comments (2)
frontend/src/pages/CaseWorkspacePage.test.tsx (2)
239-253: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win同时验证
commander角色确实会被提交。测试只选择
family并断言一次;即使实现始终传入family,当前测试也会通过。请再选择commander、提交另一邮箱,并断言addCaseMember的第四个参数为'commander'。As per path instructions,这能直接验证前端提交值与后端角色契约一致。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 239 - 253, Extend the test around the roleSelect and addCaseMember interactions to also select the commander option, submit a different member email, and assert mocked.addCaseMember is called with 'commander' as the fourth argument. Preserve the existing family-role assertion while adding coverage that the selected commander value is submitted correctly.Source: Path instructions
101-110: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win补充地图元数据断言。
当前只断言地点名称、文字地址和“仅文字地点”。即使页面丢失
location_precision、source或review_status的展示,测试仍会通过。请至少为精度、来源和审核状态增加用户可见断言,避免地图信息契约回归。As per path instructions,前端测试应覆盖 API 契约与用户可见的数据暴露。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 101 - 110, 补充 CaseWorkspacePage 测试中地图地点元数据的用户可见断言:在现有 “Fictional market”“North gate, fictional park” 和“仅文字地点”断言旁,验证 location_precision、source 与 review_status 对应的展示文本均存在。使用页面实际渲染的标签或文案,确保 API 返回的精度、来源和审核状态都被用户看到。Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 239-253: Extend the test around the roleSelect and addCaseMember
interactions to also select the commander option, submit a different member
email, and assert mocked.addCaseMember is called with 'commander' as the fourth
argument. Preserve the existing family-role assertion while adding coverage that
the selected commander value is submitted correctly.
- Around line 101-110: 补充 CaseWorkspacePage 测试中地图地点元数据的用户可见断言:在现有 “Fictional
market”“North gate, fictional park” 和“仅文字地点”断言旁,验证 location_precision、source 与
review_status 对应的展示文本均存在。使用页面实际渲染的标签或文案,确保 API 返回的精度、来源和审核状态都被用户看到。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: a83efc15-34be-4822-a92f-e5cf02f811b9
📒 Files selected for processing (2)
frontend/src/pages/CaseWorkspacePage.test.tsxfrontend/src/pages/CaseWorkspacePage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/CaseWorkspacePage.tsx
Preview readyThe preview for this build is available at https://pr-151-f417060f9fa5.angui.cg8.site. Temporary demo accounts (all use the password
|
- Include elder age in command intake API responses and schema - Preserve text area hints while exposing last-seen timestamps - Display pending intake details in commander workspace cards - Add commander intake metrics and status rows to the dashboard - Update API documentation, types, and intake acceptance tests
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src/pages/DashboardPage.tsx (1)
30-55: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win隔离待受理队列的失败,避免清空整个总览。
listCommandIntake()一旦拒绝,Promise.all会进入catch并清空普通案件;因此指挥官即使仍有可访问案件,也只能看到全页加载失败。将待受理请求降级为[]并保留警告,只有主案件请求失败时才清空总览;同时覆盖“待受理请求失败但普通案件仍展示”的回归场景。建议修复
- const [items, intakeCases] = await Promise.all([ + const [items, intakeResult] = await Promise.all([ listCases(token), - isCommander ? listCommandIntake(token) : Promise.resolve([]), + isCommander + ? listCommandIntake(token).then( + (cases) => ({ cases, failed: false }), + () => ({ cases: [], failed: true }), + ) + : Promise.resolve({ cases: [], failed: false }), ]) + const intakeCases = intakeResult.cases @@ - if (loaded.length !== items.length) { - setError('部分案件详情暂时不可用,统计数据可能不完整。') + if (loaded.length !== items.length || intakeResult.failed) { + setError('部分数据暂时不可用,统计数据可能不完整。') }As per path instructions,
frontend/**要关注“authentication state, validation, data exposure, and user-visible failure paths”。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/DashboardPage.tsx` around lines 30 - 55, Separate the listCommandIntake request from the Promise.all that loads ordinary cases so its rejection falls back to an empty intakeCases list and records a user-visible warning. Keep successful ordinary case loading and details rendering intact when intake loading fails; only the primary listCases failure should clear the overview and enter the existing fatal error path. Add or update regression coverage for a failed intake request with successfully displayed ordinary cases.Source: Path instructions
src/application/services/case_service.rs (1)
182-201: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift加数据库级约束,避免并发重复接管。
这里还是“先查再插入”的竞态;case_memberships目前只有(case_id, user_id)唯一约束,挡不住两个不同用户同时以commander身份接管同一案件。请为“每案仅允许一个 commander”补上数据库级约束,并把唯一冲突统一映射成409 Conflict,否则并发下要么落成重复接管,要么抛出数据库错误。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/case_service.rs` around lines 182 - 201, 为 case_memberships 增加数据库级的“每个 case_id 只能有一个 commander”唯一约束或等效部分唯一索引,避免并发接管产生重复记录;同时更新接管流程及 insert_membership 的错误处理,将该唯一约束冲突统一转换为 ApiError::Conflict,保留现有成功路径和重复接管提示语义。Source: Path instructions
🧹 Nitpick comments (3)
src/application/services/case_service.rs (1)
902-929: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift避免成员列表的串行 N+1 查询。
当前每个成员至少执行一次用户查询和一次全局能力查询,并且全部串行等待;成员数量增大时会显著放大数据库负载和接口延迟。请批量加载用户及能力,或增加分页并并行/批量组装响应。
As per path instructions:
src/**应重点关注可靠性和可扩展性风险。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/case_service.rs` around lines 902 - 929, Update list_case_members to eliminate the per-membership serial user and global-capabilities queries: batch-load all referenced users and their capabilities, then assemble responses from those results while preserving the existing ordering and error behavior. Use the membership user IDs and existing capability-loading symbols rather than issuing queries inside the loop.Source: Path instructions
frontend/src/pages/CaseWorkspacePage.test.tsx (2)
87-92: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win为新增的
elder_age补充渲染断言。Fixture 已加入
elder_age: 76,但测试没有验证该字段在受理卡片中可见,因此年龄字段即使被页面忽略,测试仍会通过。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 87 - 92, Update the CaseWorkspacePage test around the listCommandIntake fixture and rendered acceptance card to assert that the elder_age value 76 is visible. Keep the existing AG-PENDING assertion and verify the age field is rendered on the card.
221-255: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win补上指挥角色的正向提交路径。
该用例只实际提交了
family,尚未验证选择“指挥”后addCaseMember收到commander,因此测试名描述的另一条授权路径仍可能回归。建议补充
await waitFor(() => expect(mocked.addCaseMember).toHaveBeenCalledWith( 'test-session', 'case-family', 'family-member@demo.invalid', 'family', )) + + fireEvent.change(roleSelect, { target: { value: 'commander' } }) + fireEvent.change(screen.getByPlaceholderText('成员邮箱'), { + target: { value: 'commander@demo.invalid' }, + }) + fireEvent.click(screen.getByRole('button', { name: '添加案件成员' })) + await waitFor(() => expect(mocked.addCaseMember).toHaveBeenCalledWith( + 'test-session', + 'case-family', + 'commander@demo.invalid', + 'commander', + ))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 221 - 255, Extend the test case around the existing roleSelect and addCaseMember assertions to also select the “指挥” option, enter a commander email, and submit the form. Verify addCaseMember is called with the same session and case identifiers, the commander email, and the role value “commander”, while preserving the existing family-member submission coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/application/services/case_service.rs`:
- Around line 151-154: Update the case mapping around area_hint and elder_age so
the pending-case queue never exposes the raw profile.last_seen_location text.
Use an existing coarse-grained region field, or apply irreversible server-side
regionalization before assigning area_hint, and add a test verifying precise
location data is absent from queue responses.
---
Outside diff comments:
In `@frontend/src/pages/DashboardPage.tsx`:
- Around line 30-55: Separate the listCommandIntake request from the Promise.all
that loads ordinary cases so its rejection falls back to an empty intakeCases
list and records a user-visible warning. Keep successful ordinary case loading
and details rendering intact when intake loading fails; only the primary
listCases failure should clear the overview and enter the existing fatal error
path. Add or update regression coverage for a failed intake request with
successfully displayed ordinary cases.
In `@src/application/services/case_service.rs`:
- Around line 182-201: 为 case_memberships 增加数据库级的“每个 case_id 只能有一个
commander”唯一约束或等效部分唯一索引,避免并发接管产生重复记录;同时更新接管流程及 insert_membership
的错误处理,将该唯一约束冲突统一转换为 ApiError::Conflict,保留现有成功路径和重复接管提示语义。
---
Nitpick comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 87-92: Update the CaseWorkspacePage test around the
listCommandIntake fixture and rendered acceptance card to assert that the
elder_age value 76 is visible. Keep the existing AG-PENDING assertion and verify
the age field is rendered on the card.
- Around line 221-255: Extend the test case around the existing roleSelect and
addCaseMember assertions to also select the “指挥” option, enter a commander
email, and submit the form. Verify addCaseMember is called with the same session
and case identifiers, the commander email, and the role value “commander”, while
preserving the existing family-member submission coverage.
In `@src/application/services/case_service.rs`:
- Around line 902-929: Update list_case_members to eliminate the per-membership
serial user and global-capabilities queries: batch-load all referenced users and
their capabilities, then assemble responses from those results while preserving
the existing ordering and error behavior. Use the membership user IDs and
existing capability-loading symbols rather than issuing queries inside the loop.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d71175ea-b374-44e6-a6d6-642f0a4dedf2
📒 Files selected for processing (10)
docs/API.mddocs/openapi.yamlfrontend/src/api/cases.tsfrontend/src/pages/CaseWorkspacePage.test.tsxfrontend/src/pages/CaseWorkspacePage.tsxfrontend/src/pages/DashboardPage.test.tsxfrontend/src/pages/DashboardPage.tsxsrc/api/models.rssrc/application/services/case_service.rstests/api/case_members_post.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/API.md
- frontend/src/pages/CaseWorkspacePage.tsx
| area_hint: profile | ||
| .as_ref() | ||
| .and_then(|item| item.last_seen_location.clone()), | ||
| elder_age: profile.and_then(|item| item.age), |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
不要直接把原始最后出现地点作为待受理队列的 area_hint。
last_seen_location 是自由文本,可能包含精确地址;当前会在案件尚未被接管前直接返回给所有具备 commander 能力的账号,与接口声明“不暴露精确坐标/敏感案情”的隐私边界不一致。请改用单独的粗粒度区域字段,或在服务端进行不可逆的区域化处理,并补充精确地点不会出现在队列响应中的测试。
As per path instructions: src/** 应重点检查隐私与数据暴露风险。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/application/services/case_service.rs` around lines 151 - 154, Update the
case mapping around area_hint and elder_age so the pending-case queue never
exposes the raw profile.last_seen_location text. Use an existing coarse-grained
region field, or apply irreversible server-side regionalization before assigning
area_hint, and add a test verifying precise location data is absent from queue
responses.
Source: Path instructions
Preview readyThe preview for this build is available at https://pr-151-021ed1003572.angui.cg8.site. Temporary demo accounts (all use the password
|
- create a non-blocking, source-scoped summary draft after commander clue review - add commander-only endpoint to retrieve the latest pending review draft - update the workspace to load and display automatically generated drafts - remove manual draft generation from the standard review workflow - label deterministic fallback drafts without attributing them to an AI model - keep draft generation failures from blocking completed clue reviews - document automatic generation, fallback behavior, and internal regeneration use - add API coverage for retrieving the generated pending summary draft
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/application/services/case_collaboration_service.rs (1)
454-497: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win创建新自动草稿时关闭旧的待审核草稿。
每次审核都会插入新的
pending_review记录,但读取接口只返回最新一条;较早草稿会永久停留在待审核状态且无法被工作区审核。请在同一事务内将同案件旧 pending 草稿标记为非待审核状态,或改为更新现有 pending 草稿,并添加多次线索审核的覆盖。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/case_collaboration_service.rs` around lines 454 - 497, Update the draft creation flow around the summary_drafts ActiveModel insertion so that, within the same transaction, any existing pending_review draft for the case is transitioned to a non-pending status before inserting the new draft. Preserve the new draft’s initial status and audit behavior, and add coverage for repeated clue reviews to verify older pending drafts are no longer left reviewable.frontend/src/pages/CaseWorkspacePage.test.tsx (1)
6-44: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win补齐任务和摘要 API 的 mock 与默认成功响应。
渲染 commander 工作区会调用
listCaseTasks、listCaseMembers和getCaseSummary,但 mock 模块未导出它们;测试没有覆盖新增看板和摘要的正常加载路径,可能只是在吞掉运行时错误。请导出这些 mock,并为任务分页、成员列表、摘要和getLatestSummaryDraft设置有效默认值。As per path instructions, “
tests/**: Check that changed behavior has meaningful positive, negative, and boundary-case coverage.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 6 - 44, 补齐 CaseWorkspacePage 测试中的 API mock:在 mocked 状态及 ../api/cases mock 工厂中导出 listCaseTasks、listCaseMembers 和 getCaseSummary,并为 listCaseTasks 分页、成员列表、摘要及 getLatestSummaryDraft 配置有效的默认成功响应。更新 commander 工作区测试,覆盖看板和摘要正常加载,同时补充相关失败与边界场景,确保不是吞掉运行时错误。Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/openapi.yaml`:
- Around line 982-995: 补充 getLatestSummaryDraft 端点的响应契约,加入引用现有 Forbidden 响应定义的
403 状态码,保留当前 401、404 和 500 响应不变。
In `@src/api/routes/clues.rs`:
- Around line 26-35: 移除审核请求中对
crate::services::case_collaboration_service::create_summary_draft 的同步 await
调用;改为在审核自身事务成功提交后,将摘要生成任务写入可靠的后台队列或 outbox,并由异步 worker
执行该操作,使审核接口不等待摘要查询、写入及审计事务。
In `@tests/api/clue_review_patch.rs`:
- Around line 48-59: 扩展审核前的测试覆盖:在成功获取待审核草稿之前,使用 commander 身份断言无草稿时端点返回 JSON
null,并使用家属 token 请求同一 case-restricted 端点且断言返回 403;保留现有 commander 成功路径及其字段断言。
---
Outside diff comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 6-44: 补齐 CaseWorkspacePage 测试中的 API mock:在 mocked 状态及 ../api/cases
mock 工厂中导出 listCaseTasks、listCaseMembers 和 getCaseSummary,并为 listCaseTasks
分页、成员列表、摘要及 getLatestSummaryDraft 配置有效的默认成功响应。更新 commander
工作区测试,覆盖看板和摘要正常加载,同时补充相关失败与边界场景,确保不是吞掉运行时错误。
In `@src/application/services/case_collaboration_service.rs`:
- Around line 454-497: Update the draft creation flow around the summary_drafts
ActiveModel insertion so that, within the same transaction, any existing
pending_review draft for the case is transitioned to a non-pending status before
inserting the new draft. Preserve the new draft’s initial status and audit
behavior, and add coverage for repeated clue reviews to verify older pending
drafts are no longer left reviewable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 46ce09ba-9f6d-49f9-9ee8-40f99771499b
📒 Files selected for processing (9)
docs/API.mddocs/openapi.yamlfrontend/src/api/cases.tsfrontend/src/pages/CaseWorkspacePage.test.tsxfrontend/src/pages/CaseWorkspacePage.tsxsrc/api/routes/cases.rssrc/api/routes/clues.rssrc/application/services/case_collaboration_service.rstests/api/clue_review_patch.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/API.md
| get: | ||
| tags: [Cases] | ||
| operationId: getLatestSummaryDraft | ||
| summary: Get the latest pending automated summary draft | ||
| description: Commander only. A pending-review draft is created automatically after a commander completes a clue review. The response is null when there is no draft awaiting review. A deterministic, source-scoped fallback is used when AI generation is unavailable, and it is still subject to the same human review before publication. | ||
| security: [{ bearerAuth: [] }] | ||
| x-account-types: [member] | ||
| x-case-roles: [commander] | ||
| x-data-classification: case-restricted | ||
| responses: | ||
| "200": { description: Latest pending-review summary draft or null, content: { application/json: { schema: { oneOf: [{ $ref: "#/components/schemas/SummaryDraft" }, { type: "null" }] } } } } | ||
| "401": { $ref: "#/components/responses/Unauthorized" } | ||
| "404": { $ref: "#/components/responses/NotFound" } | ||
| "500": { $ref: "#/components/responses/InternalError" } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
补充 403 Forbidden 响应契约。
该端点要求 commander 角色,服务层会返回 403;当前文档只声明 401、404、500,生成客户端无法可靠处理正常的越权结果。
responses:
"200": { description: Latest pending-review summary draft or null, content: { application/json: { schema: { oneOf: [{ $ref: "`#/components/schemas/SummaryDraft`" }, { type: "null" }] } } } }
"401": { $ref: "`#/components/responses/Unauthorized`" }
+ "403": { $ref: "`#/components/responses/Forbidden`" }
"404": { $ref: "`#/components/responses/NotFound`" }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| get: | |
| tags: [Cases] | |
| operationId: getLatestSummaryDraft | |
| summary: Get the latest pending automated summary draft | |
| description: Commander only. A pending-review draft is created automatically after a commander completes a clue review. The response is null when there is no draft awaiting review. A deterministic, source-scoped fallback is used when AI generation is unavailable, and it is still subject to the same human review before publication. | |
| security: [{ bearerAuth: [] }] | |
| x-account-types: [member] | |
| x-case-roles: [commander] | |
| x-data-classification: case-restricted | |
| responses: | |
| "200": { description: Latest pending-review summary draft or null, content: { application/json: { schema: { oneOf: [{ $ref: "#/components/schemas/SummaryDraft" }, { type: "null" }] } } } } | |
| "401": { $ref: "#/components/responses/Unauthorized" } | |
| "404": { $ref: "#/components/responses/NotFound" } | |
| "500": { $ref: "#/components/responses/InternalError" } | |
| get: | |
| tags: [Cases] | |
| operationId: getLatestSummaryDraft | |
| summary: Get the latest pending automated summary draft | |
| description: Commander only. A pending-review draft is created automatically after a commander completes a clue review. The response is null when there is no draft awaiting review. A deterministic, source-scoped fallback is used when AI generation is unavailable, and it is still subject to the same human review before publication. | |
| security: [{ bearerAuth: [] }] | |
| x-account-types: [member] | |
| x-case-roles: [commander] | |
| x-data-classification: case-restricted | |
| responses: | |
| "200": { description: Latest pending-review summary draft or null, content: { application/json: { schema: { oneOf: [{ $ref: "`#/components/schemas/SummaryDraft`" }, { type: "null" }] } } } } | |
| "401": { $ref: "`#/components/responses/Unauthorized`" } | |
| "403": { $ref: "`#/components/responses/Forbidden`" } | |
| "404": { $ref: "`#/components/responses/NotFound`" } | |
| "500": { $ref: "`#/components/responses/InternalError`" } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/openapi.yaml` around lines 982 - 995, 补充 getLatestSummaryDraft
端点的响应契约,加入引用现有 Forbidden 响应定义的 403 状态码,保留当前 401、404 和 500 响应不变。
| // Summary generation is a non-blocking derivative of a completed human review. | ||
| // Its controlled rule-based fallback must never prevent the review itself from persisting. | ||
| let _ = crate::services::case_collaboration_service::create_summary_draft( | ||
| &state.db, | ||
| &auth, | ||
| &clue.case_id, | ||
| crate::models::CreateSummaryDraftRequest { content: None }, | ||
| &state.ai_gateway, | ||
| ) | ||
| .await; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
不要在审核请求中同步等待摘要生成。
let _ = ... .await 只忽略错误,仍会让审核响应等待摘要查询、写入和审计事务;数据库拥塞时会拖慢甚至挂住审核接口。将此派生操作写入可靠后台队列/outbox,并让审核提交在自身事务完成后立即返回。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/api/routes/clues.rs` around lines 26 - 35, 移除审核请求中对
crate::services::case_collaboration_service::create_summary_draft 的同步 await
调用;改为在审核自身事务成功提交后,将摘要生成任务写入可靠的后台队列或 outbox,并由异步 worker
执行该操作,使审核接口不等待摘要查询、写入及审计事务。
| let pending_draft = test::call_service( | ||
| &app, | ||
| test::TestRequest::get() | ||
| .uri(&format!("/api/cases/{case_id}/summary-drafts")) | ||
| .insert_header((header::AUTHORIZATION, format!("Bearer {commander_token}"))) | ||
| .to_request(), | ||
| ) | ||
| .await; | ||
| assert_eq!(pending_draft.status(), StatusCode::OK); | ||
| let pending_draft: Value = test::read_body_json(pending_draft).await; | ||
| assert_eq!(pending_draft["status"], "pending_review"); | ||
| assert_eq!(pending_draft["publication_eligible"], true); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
覆盖无草稿和非 commander 访问。
除当前成功路径外,请在审核前断言 commander 得到 JSON null,并用家属 token 断言该 case-restricted 端点返回 403。这能锁定 Option 响应和 commander 授权边界。
As per path instructions, “tests/**: Check that changed behavior has meaningful positive, negative, and boundary-case coverage.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/api/clue_review_patch.rs` around lines 48 - 59,
扩展审核前的测试覆盖:在成功获取待审核草稿之前,使用 commander 身份断言无草稿时端点返回 JSON null,并使用家属 token 请求同一
case-restricted 端点且断言返回 403;保留现有 commander 成功路径及其字段断言。
Source: Path instructions
Preview readyThe preview for this build is available at https://pr-151-bf250fbe7883.angui.cg8.site. Temporary demo accounts (all use the password
|
- Reject clue creation for resolved and closed cases in the case service - Hide the clue submission form when a case is not active - Add frontend coverage for resolved-case clue submission visibility - Extend API tests to reject clue creation on resolved cases - Update API and OpenAPI conflict documentation and error messages
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/src/pages/CaseWorkspacePage.test.tsx (1)
27-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win补齐显式 API mock,否则详情页测试会直接抛错。
CaseWorkspacePage已调用listCaseTasks、listCaseMembers、createCaseTask、updateTaskStatus、submitTaskFeedback、getCaseSummary和reviewSummaryDraft,但显式 mock 工厂没有导出这些函数;TaskBoard挂载时调用listCaseTasks会得到undefined。请为新增 API 提供
vi.fn()及工厂透传实现,并为相关测试设置默认返回值。As per path instructions:
tests/**应检查变更行为的有效测试覆盖。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/CaseWorkspacePage.test.tsx` around lines 27 - 44, 补齐 CaseWorkspacePage.test.tsx 中 mock 工厂对 listCaseTasks、listCaseMembers、createCaseTask、updateTaskStatus、submitTaskFeedback、getCaseSummary 和 reviewSummaryDraft 的显式导出:查询 API 使用 mocked 透传包装,写入 API 提供 vi.fn()。在相关测试的默认 mock 设置中为这些 API 配置符合页面渲染预期的返回值,确保 TaskBoard 及摘要流程不会因 undefined 直接抛错,并补充或更新 tests/** 下覆盖这些新增行为的有效测试。Source: Path instructions
src/application/services/case_service.rs (1)
182-201: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift补上受理的数据库级原子保护
case_memberships只有(case_id, user_id)唯一约束,挡不住两个不同 commander 同时受理同一案件;这里仍需要把“每个 case 仅一个 commander”的约束下沉到数据库,或改成原子插入/冲突处理,并补并发测试。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/case_service.rs` around lines 182 - 201, 在受理流程中更新 commander 成员创建逻辑,不能依赖 `case_memberships::Entity::find` 检查后再调用 `insert_membership`;改用数据库级唯一约束或原子插入并处理冲突,确保每个 case 只能存在一个 commander。同步补充并发受理测试,验证多个不同用户同时操作时仅一个成功且其余返回冲突。Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@frontend/src/pages/CaseWorkspacePage.test.tsx`:
- Around line 27-44: 补齐 CaseWorkspacePage.test.tsx 中 mock 工厂对
listCaseTasks、listCaseMembers、createCaseTask、updateTaskStatus、submitTaskFeedback、getCaseSummary
和 reviewSummaryDraft 的显式导出:查询 API 使用 mocked 透传包装,写入 API 提供 vi.fn()。在相关测试的默认 mock
设置中为这些 API 配置符合页面渲染预期的返回值,确保 TaskBoard 及摘要流程不会因 undefined 直接抛错,并补充或更新 tests/**
下覆盖这些新增行为的有效测试。
In `@src/application/services/case_service.rs`:
- Around line 182-201: 在受理流程中更新 commander 成员创建逻辑,不能依赖
`case_memberships::Entity::find` 检查后再调用
`insert_membership`;改用数据库级唯一约束或原子插入并处理冲突,确保每个 case 只能存在一个
commander。同步补充并发受理测试,验证多个不同用户同时操作时仅一个成功且其余返回冲突。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 51f532cf-566a-4a0d-be27-c3045edc762d
📒 Files selected for processing (6)
docs/API.mddocs/openapi.yamlfrontend/src/pages/CaseWorkspacePage.test.tsxfrontend/src/pages/CaseWorkspacePage.tsxsrc/application/services/case_service.rstests/api/case_clues_post.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/openapi.yaml
Preview readyThe preview for this build is available at https://pr-151-6fc13cd271af.angui.cg8.site. Temporary demo accounts (all use the password
|
Summary
Validation
Closes #24
Closes #29
Closes #31
Closes #77
Closes #78
Closes #79
Closes #80
Closes #81
Summary by CodeRabbit