修复: aichat advance_task渲染显任务名 + 审批防双击 + time.ts销账
- UX-260618-14: advance_task 工具结果/审批卡显任务名替代UUID 前端 ToolCard 取 r.title fallback r.id + TASK_ID_TOOL_ARG 审批卡 task_id→title 回显 + i18n 后端 audit resolve_task_label 查 tasks 表(原误查 projects恒报不存在) + 删 tool_display_hint 短路让模板生效 - UX-260618-16: time.ts formatDate .value 已修(CR-24/25闭环),漏销账补登 - UX-260618-17: ProjectDetail handleApprovalMulti 补 submitting try/finally + 模板联动防双击
This commit is contained in:
@@ -341,11 +341,11 @@
|
||||
|
||||
> 用户实测报 2 bug。review 会话派 2 agent 并行核验源码,根因 + 修法定位完成,待主代实施。
|
||||
|
||||
- [ ] **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`
|
||||
- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`
|
||||
|
||||
- [ ] **UX-260618-15 [P1]** — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。后端每轮重试 emit AiError(`stream_recv.rs:200/258/298/358/398` 5 处 InitFailed 必 emit),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326`
|
||||
|
||||
- [ ] **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
|
||||
- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
|
||||
|
||||
- [ ] **UX-260618-17 [P1🟡]** — **ProjectDetail.handleApprovalMulti 漏 submitting 复位(防双击破口)**(CR-260618-25 前端审查发现·Agent B 对抗核验)。`ProjectDetail.vue:431-437` handleApprovalMulti 无 submitting set true/finally,模板 :196-202 只绑 `:disabled="multiDecisions.length === 0"` → 多选审批 approveHumanApproval IPC 进行中按钮不禁用,用户可重复点确认重复触发 IPC。同模板 handleApproval(:417-425)正确 try/finally,handleApprovalMulti 漏对齐。**修法**:顶 `submitting.value = true` + `try { ... } finally { submitting.value = false }`,对齐 handleApproval。— `src/views/ProjectDetail.vue`(:431-437 + :196-202)
|
||||
|
||||
|
||||
10
docs/待审查.md
10
docs/待审查.md
@@ -625,6 +625,16 @@
|
||||
1. `Knowledge.vue submitCreate`(:438)/ `ProjectDetail.vue handleApproval`(:417) 缺显式 `if(submitting) return` 入口 guard(其余 5 处有)。实际防并发由模板 `:disabled="submitting"` 兜住(按钮点不动·无重入),IPC 路径单调用,风险极低。可选补 guard 对齐范式一致性。
|
||||
2. `ConnectionPanel.vue saveConn`(:152) 是同步函数(无 await),submitting try/finally 同步置位即复位,此处 submitting 实际无防双击语义(纯占位/视觉)。无功能影响,可选移除或改 async。
|
||||
|
||||
### CR-260619-01 UX-14 advance_task渲染缺任务名 + UX-16 time.ts销账 + UX-17 handleApprovalMulti防双击(workflow wwchro468 UX-14实施 + 主代串行 UX-16/17·主代兜底核验) — 🟡 待审
|
||||
|
||||
- **范围**:
|
||||
- **UX-260618-14 advance_task 渲染缺任务名**(workflow wwchro468 general-purpose agent 实施 6 文件):前端 `ToolCard.vue` advance_task 渲染取 `r.title||r.id`(:474 formatToolResult / :891 toolResultSummary)+ 新增 `TASK_ID_TOOL_ARG` 映射(:799 advance_task→id / run_workflow→task_id)+ `displayArgValue` 接入 task_id→title(:834)+ i18n zh/en `aiTool.ts`(advancedTask/advancedTaskWithRounds 改 {title} + taskLabel/taskIdNotFound);后端 `audit.rs` 新增 `resolve_task_label`(查 tasks 表 :151 三臂 Ok(Some)/Ok(None)/Err 对齐 resolve_project_label)+ `build_approval_reason` advance_task 分支(:173 id 走 task 解析)+ `tool_registry.rs` 删 `tool_display_hint` 的 advance_task 项(:1575 让 :1549 模板「推进任务状态:{} → {}」生效)。
|
||||
- **UX-260618-16 time.ts .value 销账**:主代核验已修(CR-24/25 闭环),漏销账补登。
|
||||
- **UX-260618-17 handleApprovalMulti 补 submitting**(主代串行):`ProjectDetail.vue` :431 try/finally + 模板 :198 `:disabled="submitting || multiDecisions.length===0"` 对齐单选 handleApproval。
|
||||
- **验证(主代兜底,不信 agent 自报)**:cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证 resolve_task_label 三臂 / tool_display_hint 删 advance_task 项 / ToolCard title 取值。
|
||||
- **审查要点(供审查 agent)**:① UX-14 行为变更——审批 reason 文案(advance_task 从静态串「推进任务状态」→ 含任务标题+target_status)+ audit resolve 查 tasks 表(原误查 projects 恒报「项目已不存在」);② resolve_task_label 三臂语义对齐 resolve_project_label(空 id/Ok(None)/Err 分流);③ tool_display_hint 删 advance_task 后 run_workflow 保留轻量命中(模板 task_id 走通用裸显,本批不动);④ 跨项目任务 taskNameById 查 projectStore.tasks 查不到降级「任务不在当前列表」(非硬错);⑤ UX-17 handleApprovalMulti submitting 对齐 handleApproval 防双击。
|
||||
- **关联**:todo UX-260618-14/16/17 销账。
|
||||
|
||||
---
|
||||
|
||||
## 已审归档
|
||||
|
||||
@@ -8,7 +8,7 @@ use tauri::{AppHandle, Emitter, State};
|
||||
|
||||
use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
|
||||
use df_ai::provider::ChatMessage;
|
||||
use df_storage::crud::{AiToolExecutionRepo, ProjectRepo};
|
||||
use df_storage::crud::{AiToolExecutionRepo, ProjectRepo, TaskRepo};
|
||||
use df_storage::db::Database;
|
||||
use df_storage::models::AiToolExecutionRecord;
|
||||
|
||||
@@ -143,6 +143,26 @@ async fn resolve_project_label(db: &Arc<Database>, id: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// 查任务可读标签:id → "「任务标题」(id=x)",对齐 resolve_project_label 三臂语义。
|
||||
///
|
||||
/// UX-260618-14:advance_task 审批卡的 id 是 task_id,原 build_approval_reason 把 "id"
|
||||
/// 统一走 resolve_project_label(查 projects 表),误把任务 id 当项目 id 解析,永远落到
|
||||
/// "项目已不存在"。本方法改查 tasks 表,与 resolve_project_label 同 Ok(None)/Err 分流。
|
||||
async fn resolve_task_label(db: &Arc<Database>, id: &str) -> String {
|
||||
if id.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let repo = TaskRepo::new(db);
|
||||
match repo.get_by_id(id).await {
|
||||
Ok(Some(t)) => format!("「{}」(id={})", t.title, id),
|
||||
Ok(None) => format!("(任务已不存在, id={})", id),
|
||||
Err(e) => {
|
||||
tracing::warn!("resolve_task_label: 查询任务 id={} 失败,回退裸 id: {}", id, e);
|
||||
format!("(id={})", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 拼审批 reason:按工具名 + args 取可读字段,让用户知道"审批要做什么"。
|
||||
///
|
||||
/// 主要审批工具特化(带对象名/标识):
|
||||
@@ -170,6 +190,8 @@ async fn build_approval_reason(
|
||||
let val = s(key);
|
||||
if val.is_empty() { values.clear(); break; }
|
||||
match key {
|
||||
// advance_task 的 id 是 task_id(非项目 id),改查 tasks 表,避免误报"项目已不存在"
|
||||
"id" if tool_name == "advance_task" => values.push(resolve_task_label(db, &val).await),
|
||||
"id" | "project_id" => values.push(resolve_project_label(db, &val).await),
|
||||
_ => values.push(val.to_string()),
|
||||
}
|
||||
|
||||
@@ -1572,7 +1572,8 @@ pub(crate) fn tool_display_hint(name: &str) -> Option<&'static str> {
|
||||
"bind_directory" => Some("绑定目录"),
|
||||
"create_idea" => Some("捕获灵感"),
|
||||
"update_task" => Some("修改任务"),
|
||||
"advance_task" => Some("推进任务状态"),
|
||||
// advance_task 不在此轻量映射:让 build_approval_reason 走 display_hint_for_tool
|
||||
// 模板(含 target_status 详情),否则轻量命中"推进任务状态"丢失目标态信息(UX-260618-14)
|
||||
"run_workflow" => Some("触发工作流"),
|
||||
"delete_task" => Some("删除任务"),
|
||||
"update_idea" => Some("修改灵感"),
|
||||
|
||||
@@ -473,9 +473,10 @@ function formatToolResult(tc: AiToolCallInfo): string {
|
||||
: t('aiTool.updated')
|
||||
case 'advance_task':
|
||||
// UX-260618-09: review_rounds>0 显退回累加轮数,=0 回退原文案(首推无噪音)
|
||||
// UX-260618-14: 后端返 TaskRecord(含 title),优先显 title,无则回退 id(防裸 UUID)
|
||||
return typeof r.review_rounds === 'number' && r.review_rounds > 0
|
||||
? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds })
|
||||
: t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' })
|
||||
? t('aiTool.advancedTaskWithRounds', { title: r.title || r.id || '', status: r.status || '', n: r.review_rounds })
|
||||
: t('aiTool.advancedTask', { title: r.title || r.id || '', status: r.status || '' })
|
||||
case 'delete_task':
|
||||
return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective')
|
||||
case 'delete_file':
|
||||
@@ -796,6 +797,16 @@ function projectNameById(id: string): string | undefined {
|
||||
?? projectStore.deletedProjects.find(p => p.id === id)?.name
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 id 查任务标题:审批 advance_task/run_workflow 的 task_id 时,把裸 UUID 转标题。
|
||||
* 复用 project store 已加载的 state.tasks(仅当前项目范围),不新增网络请求。
|
||||
* state.tasks 非「全量」(按当前项目/筛选加载),查不到时调用方走裸 id fallback——
|
||||
* 审批卡 task_id 多属当前活动项目,命中率足够,跨项目任务降级显 id 可接受。
|
||||
*/
|
||||
function taskNameById(id: string): string | undefined {
|
||||
return projectStore.tasks.find(t => t.id === id)?.title
|
||||
}
|
||||
|
||||
/** 工具名 → 该工具中代表项目 id 的参数 key(仅项目类工具特化;delete_task 的 id 是任务 id,不在此列) */
|
||||
const PROJECT_ID_TOOL_ARG: Record<string, 'id' | 'project_id'> = {
|
||||
delete_project: 'id',
|
||||
@@ -806,6 +817,15 @@ const PROJECT_ID_TOOL_ARG: Record<string, 'id' | 'project_id'> = {
|
||||
create_task: 'project_id',
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具名 → 该工具中代表任务 id 的参数 key(任务类工具特化)。
|
||||
* advance_task 的任务 id 在 "id" key,run_workflow 在 "task_id" key。
|
||||
*/
|
||||
const TASK_ID_TOOL_ARG: Record<string, 'id' | 'task_id'> = {
|
||||
advance_task: 'id',
|
||||
run_workflow: 'task_id',
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批参数值展示(AR-3):对项目类工具的 id/project_id 特化——优先回显项目名,查不到则显示
|
||||
* 「项目已不存在」提示 + 原 id,避免用户只看到一串裸 id 不知道是什么数据。
|
||||
@@ -821,6 +841,16 @@ function displayArgValue(arg: { key: string; val: unknown }): string {
|
||||
return t('aiTool.projectIdNotFound', { id })
|
||||
}
|
||||
}
|
||||
// UX-260618-14:advance_task/run_workflow 的 task_id → 任务标题(store.tasks 查,无则标 id)
|
||||
const taskArgKey = TASK_ID_TOOL_ARG[props.tc.name]
|
||||
if (taskArgKey && arg.key === taskArgKey) {
|
||||
const id = typeof arg.val === 'string' ? arg.val : ''
|
||||
if (id) {
|
||||
const name = taskNameById(id)
|
||||
if (name) return t('aiTool.taskLabel', { name })
|
||||
return t('aiTool.taskIdNotFound', { id })
|
||||
}
|
||||
}
|
||||
return formatArgValue(arg.val)
|
||||
}
|
||||
|
||||
@@ -889,8 +919,9 @@ function toolResultSummary(tc: AiToolCallInfo): string {
|
||||
? t('aiTool.updatedTaskField', { id: r.id || '', field: r.field || '' })
|
||||
: t('aiTool.updated')
|
||||
case 'advance_task': return typeof r.review_rounds === 'number' && r.review_rounds > 0
|
||||
? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds })
|
||||
: t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' })
|
||||
// UX-260618-14: 优先显 title(后端返 TaskRecord),无则回退 id,防裸 UUID
|
||||
? t('aiTool.advancedTaskWithRounds', { title: r.title || r.id || '', status: r.status || '', n: r.review_rounds })
|
||||
: t('aiTool.advancedTask', { title: r.title || r.id || '', status: r.status || '' })
|
||||
case 'delete_project':
|
||||
case 'delete_task': return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective')
|
||||
case 'delete_file': return r.permanent
|
||||
|
||||
@@ -64,9 +64,9 @@ export default {
|
||||
|
||||
// UX-260616-04: formatToolResult covers all tools with human-readable summaries (missing keys)
|
||||
updatedTaskField: 'Task {id} updated field {field}',
|
||||
advancedTask: 'Task {id} advanced to {status}',
|
||||
// UX-260618-09: advance_task return-round accumulation (shown only when >0)
|
||||
advancedTaskWithRounds: 'Task {id} advanced to {status} (returned {n} times)',
|
||||
advancedTask: 'Task「{title}」 advanced to {status}',
|
||||
// UX-260618-09: advance_task return-round accumulation (shown only when >0); UX-260618-14: {title} = task name (TaskRecord), fallback id
|
||||
advancedTaskWithRounds: 'Task「{title}」 advanced to {status} (returned {n} times)',
|
||||
commandOk: 'Command succeeded ({ms} ms)',
|
||||
commandFailed: 'Command failed (exit code {code})',
|
||||
commandOutputLines: '{n} lines of output',
|
||||
@@ -97,6 +97,9 @@ export default {
|
||||
|
||||
projectLabel: '"{name}"',
|
||||
projectIdNotFound: '(project not found, id={id})',
|
||||
// UX-260618-14: advance_task/run_workflow task_id → task title (store.tasks lookup; fallback id)
|
||||
taskLabel: '"{name}"',
|
||||
taskIdNotFound: '(task not in current list, id={id})',
|
||||
|
||||
// AE-2025-05: High-risk approval second confirm (ask once more before approve; misclick cancellable)
|
||||
confirmHighDelete: 'Permanently delete? This action cannot be undone',
|
||||
|
||||
@@ -65,9 +65,9 @@ export default {
|
||||
|
||||
// UX-260616-04:formatToolResult 覆盖全部工具产出人类可读摘要(补缺失 key)
|
||||
updatedTaskField: '任务 {id} 已更新字段 {field}',
|
||||
advancedTask: '任务 {id} 已推进至 {status}',
|
||||
// UX-260618-09:advance_task 退回累加轮数(>0 才显)
|
||||
advancedTaskWithRounds: '任务 {id} 已推进至 {status}(已退回 {n} 次)',
|
||||
advancedTask: '任务「{title}」已推进至 {status}',
|
||||
// UX-260618-09:advance_task 退回累加轮数(>0 才显);UX-260618-14:{title} 显任务名(后端返 TaskRecord),回退 id
|
||||
advancedTaskWithRounds: '任务「{title}」已推进至 {status}(已退回 {n} 次)',
|
||||
commandOk: '命令成功({ms} 毫秒)',
|
||||
commandFailed: '命令失败(退出码 {code})',
|
||||
commandOutputLines: '{n} 行输出',
|
||||
@@ -99,6 +99,9 @@ export default {
|
||||
// 审批参数特化(id/project_id 回显项目名,查不到标注原 id)
|
||||
projectLabel: '「{name}」',
|
||||
projectIdNotFound: '(项目已不存在, id={id})',
|
||||
// UX-260618-14:advance_task/run_workflow 的 task_id 回显任务标题(查 store.tasks,无则标 id)
|
||||
taskLabel: '「{name}」',
|
||||
taskIdNotFound: '(任务不在当前列表, id={id})',
|
||||
|
||||
// AE-2025-05:High 风险审批二次确认文案(批准前再问一次,误点可取消)
|
||||
confirmHighDelete: '确定永久删除?此操作不可恢复',
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="multiDecisions.length === 0"
|
||||
:disabled="submitting || multiDecisions.length === 0"
|
||||
@click="handleApprovalMulti"
|
||||
>
|
||||
{{ $t('projectDetail.approvalConfirm', { count: multiDecisions.length }) }}
|
||||
@@ -430,10 +430,15 @@ const multiDecisions = ref<string[]>([])
|
||||
|
||||
async function handleApprovalMulti() {
|
||||
if (multiDecisions.value.length === 0) return
|
||||
submitting.value = true
|
||||
try {
|
||||
// 统一传 decisions 数组,store 按 select_type 分派单/多选
|
||||
await store.approveHumanApproval([...multiDecisions.value])
|
||||
multiDecisions.value = []
|
||||
showApprovalDialog.value = false
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── 取消审批(调 cancel_workflow_node IPC 置节点 Cancelled)──
|
||||
|
||||
Reference in New Issue
Block a user