feat(ux): R3 验收前清单 — AcceptPanel「本次将更新」预览

新 lib/review/accept-preview.ts (buildAcceptPreview):据现有 client state 列验收前预期项——晋升版次/提炼摘要/写回裁决/伏笔到期扫描/伏笔建议待登记。仅列后端 accept 事务确实做的事(核对 projects.py accept 端点),不声称改人物 latest_state,带「以验收回执为准」免责。
AcceptPanel 加 foreshadowCount prop + 未阻断时渲染清单;ReviewReport 传 foreshadow.length。
TDD: 4 测。前端门禁绿: lint/tsc/vitest 205/build。
This commit is contained in:
Yaojia Wang
2026-06-20 16:00:51 +02:00
parent d81583d5d1
commit 6e5d0e88f6
5 changed files with 136 additions and 5 deletions

View File

@@ -177,3 +177,4 @@ T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移无漂移users st
- [2026-06-19] @llm **K1.2 修头**Kimi Code 适配器 `kimi_code_headers()` 改为**** `{"User-Agent": "KimiCLI/1.5"}`删全部 X-Msh-* + `kimi_device_id`/`KIMI_DEVICE_ID`/确定性 device-id 机制K1.5 联网实测有效 token X-Msh-* 疑似触发 `401 Invalid Authentication`对齐 pi-kimi-coder = UA-only。改 `adapters/kimi_code.py` + `__init__` 导出 + 两测adapter 6 /factory 3 default_headers UA X-Msh-*)。 contracts C1扩头集 UA-only+ gotcha门禁绿ruff/format/mypy/pytest56 passed)。
- [2026-06-19] @backend **bugfix 空写章 prompt**`assemble`C5从不含项目 premise/logline/theme/title 且无写章指令 全新项目 premise产空 `stable_core`+`volatile`写章触发 LLM `400 message must not be empty`修复`MemoryRepos` 加第 8 repo `project:ProjectSpecRepo``SqlProjectSpecRepo` project_id `projects``assemble` 作品蓝本放入 `stable_core` 首段书级 spec缓存前缀 #9+ `volatile` 始终含请创作第 N 章的正文。」。 `domain/repositories.py`(+ProjectSpecView/Repo) · `memory/sql_repositories.py`(+SqlProjectSpecRepo, sql_memory_repos 装配) · `memory/assemble.py`(_build_spec_section + 注入)测试 `test_memory.py`(+bare-project/chapter-directive 两测+fake) · `test_projects.py`/`test_generation.py`( project= fake)。无 DDL/迁移只读既有列)。门禁全绿ruff/format/mypy(154 文件)/alembic check(无漂移)/pytest **438 passed** contracts C5 + gotcha消费方注意手搓 `MemoryRepos(...)` 须补 `project=`
- [2026-06-20] @frontend **UX R1+R2 完成**审稿裁决人体学纯前端无后端改)。R1冲突卡内联命中段预览——新纯函数 `lib/review/snippet.ts` `where` N 解析 1-based 段号 取终稿对应段截断预览无段号/越界/空段null 回退跳转」),`ConflictCard` `snippet` prop 渲染朱砂引用块R2冲突按 type 分组 + 严重度排序设定违例>能力不符>时间线倒错>地理矛盾>性格漂移,未知殿后)+ 组计数徽标 + 「跳到下一条未裁决」(按显示顺序环回)+ 主次裁决按钮(采纳改法=朱砂主、忽略/手改=次级)——新纯函数 `lib/review/grouping.ts``groupConflicts`/`displayOrder`/`nextUnresolvedInOrder`**分组只改显示顺序、每条携原始 index守 conflict_index 覆盖 range 不变量**`ReviewReport` 改分组渲染。TDDsnippet 8 测 + grouping 10 测。前端门禁绿lint/tsc/**vitest 201**(+18)/build。分支 `feat/ux-r1-r2-review-ergonomics`。下一步(按 planR3 验收前清单 → Tier4。
- [2026-06-20] @frontend**UX R3 完成**(验收前清单 HITL gateUX §7.4,纯前端无后端改,续 `feat/ux-r1-r2-review-ergonomics`)。新纯函数 `lib/review/accept-preview.ts``buildAcceptPreview`:据现有 client state 列「本次验收将更新」——晋升版次 + 提炼章节摘要 + 写回裁决(有冲突时) + 验收后伏笔到期扫描(可能置 OVERDUE) + 伏笔建议待登记提醒(有建议时))。**只列后端 accept 事务确实做的事**(核对 `projects.py` accept 端点promote+digest+decisions+overdue 扫描;**不**声称改人物 latest_state/自动落库伏笔建议);带「以上为预期,最终以验收回执为准」免责。`AcceptPanel``foreshadowCount` prop + 未阻断时渲染清单,`ReviewReport``foreshadow.length`。TDD4 测。前端门禁绿lint/tsc/**vitest 205**(+4)/build。下一步按 planTier4T4-a 顶部 AI 工具条 / T4-b 本章指令框+风格预设)。

View File

@@ -1,10 +1,13 @@
"use client";
import type { AcceptResponse } from "@/lib/api/types";
import { buildAcceptPreview } from "@/lib/review/accept-preview";
interface AcceptPanelProps {
conflictCount: number;
unresolvedCount: number;
// R3当前伏笔建议数验收前清单提醒只读不自动落库
foreshadowCount: number;
accepting: boolean;
result: AcceptResponse | null;
onAccept: () => void;
@@ -14,11 +17,16 @@ interface AcceptPanelProps {
export function AcceptPanel({
conflictCount,
unresolvedCount,
foreshadowCount,
accepting,
result,
onAccept,
}: AcceptPanelProps) {
const blocked = unresolvedCount > 0;
const preview = buildAcceptPreview({
conflictCount,
foreshadowSuggestions: foreshadowCount,
});
if (result) {
return (
@@ -56,11 +64,35 @@ export function AcceptPanel({
{unresolvedCount}
</p>
) : (
<p className="mb-2 text-xs text-ink-soft">
{conflictCount === 0
? "无冲突,可直接验收。"
: "全部冲突已裁决,可验收本章。"}
</p>
<>
<p className="mb-2 text-xs text-ink-soft">
{conflictCount === 0
? "无冲突,可直接验收。"
: "全部冲突已裁决,可验收本章。"}
</p>
{/* R3验收前「本次将更新」清单预期落库口径以验收回执为准。 */}
<div className="mb-3 rounded border border-line/70 bg-bg/50 p-3">
<h4 className="mb-1.5 text-xs font-semibold text-ink">
</h4>
<ul className="space-y-1.5">
{preview.map((item) => (
<li key={item.label} className="flex gap-2 text-xs text-ink-soft">
<span className="text-cinnabar" aria-hidden="true">
</span>
<span>
<span className="text-ink">{item.label}</span>
<span className="text-ink-soft">{item.detail}</span>
</span>
</li>
))}
</ul>
<p className="mt-2 text-[11px] text-ink-soft/70">
</p>
</div>
</>
)}
<button
type="button"

View File

@@ -376,6 +376,7 @@ export function ReviewReport({
<AcceptPanel
conflictCount={conflictCount}
unresolvedCount={resolved ? 0 : unresolved}
foreshadowCount={foreshadow.length}
accepting={accept.status === "accepting"}
result={accept.result}
onAccept={() => void onAccept()}

View File

@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import { buildAcceptPreview } from "./accept-preview";
describe("buildAcceptPreview", () => {
it("always previews version promotion, digest, and overdue scan", () => {
const items = buildAcceptPreview({
conflictCount: 0,
foreshadowSuggestions: 0,
});
const labels = items.map((it) => it.label);
expect(labels).toContain("晋升章节版次");
expect(labels).toContain("提炼章节摘要");
expect(labels).toContain("扫描伏笔到期");
});
it("omits the decisions line when there are no conflicts", () => {
const items = buildAcceptPreview({
conflictCount: 0,
foreshadowSuggestions: 0,
});
expect(items.some((it) => it.label === "写回冲突裁决")).toBe(false);
});
it("includes the decisions line with the conflict count", () => {
const items = buildAcceptPreview({
conflictCount: 3,
foreshadowSuggestions: 0,
});
const decisions = items.find((it) => it.label === "写回冲突裁决");
expect(decisions?.detail).toContain("3");
});
it("reminds about pending foreshadow suggestions only when present", () => {
expect(
buildAcceptPreview({
conflictCount: 0,
foreshadowSuggestions: 0,
}).some((it) => it.label === "伏笔建议待登记"),
).toBe(false);
const items = buildAcceptPreview({
conflictCount: 0,
foreshadowSuggestions: 2,
});
const foreshadow = items.find((it) => it.label === "伏笔建议待登记");
expect(foreshadow?.detail).toContain("2");
});
});

View File

@@ -0,0 +1,49 @@
// R3 · 验收前「本次将更新」清单HITL gateUX §7.4)。
// 只列后端 accept 事务确实会做的事projects.py accept 端点):晋升版次 + 提炼章节摘要
// + 写回裁决 + 验收后伏笔到期扫描。**不**声称改人物 latest_state / 自动落库伏笔建议
// (那些不在 accept 事务里)。最终口径以后端 AcceptResponse 回执为准。
// 纯逻辑,便于 node 环境单测。
export interface AcceptPreviewInput {
conflictCount: number;
foreshadowSuggestions: number;
}
export interface AcceptPreviewItem {
label: string;
detail: string;
}
// 据现有客户端 state 生成验收前预览项(非落库口径,仅给作者一个「将要发生什么」的预期)。
export function buildAcceptPreview({
conflictCount,
foreshadowSuggestions,
}: AcceptPreviewInput): AcceptPreviewItem[] {
const items: AcceptPreviewItem[] = [
{
label: "晋升章节版次",
detail: "终稿晋升为新的接受版本(旧草稿保留可追溯)",
},
{
label: "提炼章节摘要",
detail: "从终稿提炼一行摘要,追加到记忆真源",
},
];
if (conflictCount > 0) {
items.push({
label: "写回冲突裁决",
detail: `${conflictCount} 条裁决落库留痕`,
});
}
items.push({
label: "扫描伏笔到期",
detail: "验收后台扫描,可能将逾期伏笔置为 OVERDUE",
});
if (foreshadowSuggestions > 0) {
items.push({
label: "伏笔建议待登记",
detail: `${foreshadowSuggestions} 条只读建议,验收不自动落库,需你另行登记`,
});
}
return items;
}