From efc1313d871d5a42ced0f75111a53cfd64be5077 Mon Sep 17 00:00:00 2001 From: Bepr4 <63661977@qq.com> Date: Mon, 24 Aug 2026 08:56:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=84=E5=AE=A1=E5=99=A8?= =?UTF-8?q?=E8=B7=A8=E7=BB=84=E4=BB=B6=E4=BF=AE=E6=94=B9=E9=A1=B9=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E4=B8=A2=E5=A4=B1=E5=B9=B6=E5=B1=95=E5=BC=80=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E6=96=87=E6=A1=A3=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DiffView 聚焦 effect 依赖补上 before/after:stage 文本到达、 MergeView 重建后重新应用选区和滚动,跨组件点击不再停在顶部; - 非可编辑面板加 drawSelection,聚焦范围在浏览器中可见; - 移除 collapseUnchanged,评审始终可读完整未改动段落; - 滚动容器移到 .cm-mergeView 外层,编辑器高度自适应内容; - 新增 3 项 Vitest 覆盖折叠移除与聚焦时序,共 9 项通过; - README 与 explanation 同步当前检查数量。 --- .gitignore | 1 + README.md | 6 +- .../explanation/local-markdown-reviewer.md | 4 +- reviewer/src/client/DiffView.tsx | 12 +-- reviewer/src/client/styles.css | 12 ++- reviewer/tests/DiffView.test.tsx | 78 +++++++++++++++++++ 6 files changed, 99 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index c03053c..6a6198c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ uploads/ artifacts/ outputs/ experiments/ +research-wiki/scratch/ *.cleaned.md report.json diff --git a/README.md b/README.md index ed85f30..e4b8479 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ profile 表达论文、政务文档、RAG、文档对比等不同需求。 - 已有独立的 `reviewer/` 本地只读前端,服务只消费一次已发布的产物和定位文件,并与报告层共用纯 Python 快照重放逻辑, 不导入组件、不调用流水线,也不重新清洗; - 第一批流水线已对 5 份论文 Markdown 完成保存型实验,5/5 成功,共记录 155 条修改,第二次运行零修改; -- 当前基础检查为 Ruff、mypy、229 项 pytest 测试,以及前端 ESLint、TypeScript、6 项 Vitest 测试和生产构建, +- 当前基础检查为 Ruff、mypy、229 项 pytest 测试,以及前端 ESLint、TypeScript、9 项 Vitest 测试和生产构建, 实际命令见本文“当前可用检查”。 项目还没有面向任意数据集的完整清洗规则集、Markdown/HTML 通用 parser、profile 格式、通用文件输入接口、公共 CLI、 @@ -196,8 +196,8 @@ find research-wiki -maxdepth 2 -type f | sort git status --short ``` -上述 Python 验收已于 2026-08-23 在 Python 3.13.11 环境实际运行:Ruff 通过,mypy 检查 43 个源码、测试和实验脚本文件 +上述 Python 验收已于 2026-08-24 在 Python 3.13.11 环境实际运行:Ruff 通过,mypy 检查 43 个源码、测试和实验脚本文件 无问题,pytest 共 229 项测试通过。评审器验收也在当前用户 nvm 的 Node.js 24.19.0 环境实际运行:ESLint 和 TypeScript -通过,Vitest 共 6 项测试通过,生产构建成功;并以一批真实的 5 文档、8 组件、155 条修改产物验证了接口读取和逐阶段哈希。 +通过,Vitest 共 9 项测试通过,生产构建成功;并以一批真实的 5 文档、8 组件、155 条修改产物验证了接口读取和逐阶段哈希。 当前环境没有可用的图形浏览器,因此页面视觉布局尚未进行真实浏览器人工验收。`requires-python` 仍以 `pyproject.toml` 声明的 Python 3.11 及以上为准;本次结果不等于已经在每个受支持版本上完成兼容性验证。 diff --git a/research-wiki/explanation/local-markdown-reviewer.md b/research-wiki/explanation/local-markdown-reviewer.md index 5913ee7..584d21f 100644 --- a/research-wiki/explanation/local-markdown-reviewer.md +++ b/research-wiki/explanation/local-markdown-reviewer.md @@ -93,10 +93,10 @@ Markdown 只作为文本交给 CodeMirror,不进入 `innerHTML`。第一版不 ## 7. 当前验证结果和边界 -2026-08-23 使用 Python 3.13.11 和当前用户 nvm 中的 Node.js 24.19.0 完成: +2026-08-24 使用 Python 3.13.11 和当前用户 nvm 中的 Node.js 24.19.0 完成: - Python Ruff、mypy 和 229 项 pytest 通过; -- reviewer ESLint、TypeScript、6 项 Vitest 和生产构建通过; +- reviewer ESLint、TypeScript、9 项 Vitest 和生产构建通过; - 新运行 `clindb-first-batch-reviewer-v1` 的 5 份论文全部 `success`,共 155 条实际修改; - 5 份原文运行前后哈希不变; - 本地 API 成功校验 5 份文档、8 个组件和 40 个组件阶段; diff --git a/reviewer/src/client/DiffView.tsx b/reviewer/src/client/DiffView.tsx index e933456..1807e84 100644 --- a/reviewer/src/client/DiffView.tsx +++ b/reviewer/src/client/DiffView.tsx @@ -1,7 +1,7 @@ import { markdown } from "@codemirror/lang-markdown"; import { MergeView } from "@codemirror/merge"; -import { EditorState } from "@codemirror/state"; -import { EditorView, lineNumbers } from "@codemirror/view"; +import { EditorSelection, EditorState } from "@codemirror/state"; +import { drawSelection, EditorView, lineNumbers } from "@codemirror/view"; import { useEffect, useRef } from "react"; interface DiffViewProps { @@ -45,6 +45,7 @@ const readOnlyExtensions = [ markdown(), EditorState.readOnly.of(true), EditorView.editable.of(false), + drawSelection(), EditorView.lineWrapping, editorTheme, ]; @@ -64,7 +65,6 @@ export function DiffView({ before, after, beforeLabel, afterLabel, focusRange }: orientation: "a-b", gutter: true, highlightChanges: true, - collapseUnchanged: { margin: 4, minSize: 8 }, }); merge.current = view; return () => { @@ -73,6 +73,8 @@ export function DiffView({ before, after, beforeLabel, afterLabel, focusRange }: }; }, [before, after]); + // 切换组件时 stage 文本先到、旧视图上的提前滚动会被重建覆盖,所以聚焦 + // 必须等 MergeView 换成目标文本之后再执行。 useEffect(() => { const view = merge.current; if (view === null || focusRange === null || focusRange === undefined) { @@ -81,11 +83,11 @@ export function DiffView({ before, after, beforeLabel, afterLabel, focusRange }: const anchor = Math.min(Math.max(focusRange.start, 0), view.a.state.doc.length); const head = Math.min(Math.max(focusRange.end, anchor), view.a.state.doc.length); view.a.dispatch({ - selection: { anchor, head }, + selection: EditorSelection.range(anchor, head), effects: EditorView.scrollIntoView(anchor, { y: "center" }), }); view.a.focus(); - }, [focusRange]); + }, [focusRange, before, after]); return (
diff --git a/reviewer/src/client/styles.css b/reviewer/src/client/styles.css index 84c4a79..17348db 100644 --- a/reviewer/src/client/styles.css +++ b/reviewer/src/client/styles.css @@ -355,18 +355,22 @@ p { border-left: 1px solid #dcdbd3; } -.diff-host, -.diff-host > .cm-mergeView { +.diff-host { height: 510px; } -.diff-host .cm-mergeViewEditors { +.diff-host > .cm-mergeView { height: 100%; + overflow-y: auto; + overscroll-behavior: contain; +} + +.diff-host .cm-mergeViewEditors { + min-height: 100%; } .diff-host .cm-editor { min-width: 0; - height: 100%; } .changes-panel { diff --git a/reviewer/tests/DiffView.test.tsx b/reviewer/tests/DiffView.test.tsx index 7da8f4d..ad2c268 100644 --- a/reviewer/tests/DiffView.test.tsx +++ b/reviewer/tests/DiffView.test.tsx @@ -1,8 +1,22 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; +import { EditorView } from "@codemirror/view"; import { DiffView } from "../src/client/DiffView.js"; +function leftPaneSelection(): { from: number; to: number } | null { + const pane = document.querySelector(".diff-host .cm-editor"); + if (pane === null) { + return null; + } + const view = EditorView.findFromDOM(pane as HTMLElement); + if (view === null) { + return null; + } + const { from, to } = view.state.selection.main; + return { from, to }; +} + describe("DiffView", () => { it("keeps Markdown and raw HTML as inert editor text", () => { render( @@ -18,4 +32,68 @@ describe("DiffView", () => { expect(document.querySelector("img")).toBeNull(); expect(document.querySelector("script")).toBeNull(); }); + + it("keeps long unchanged sections available in the full document view", () => { + const before = Array.from({ length: 30 }, (_, index) => `line ${index + 1}`); + const after = [...before]; + after[14] = "changed line 15"; + + render( + , + ); + + expect(document.querySelector(".cm-collapsedLines")).toBeNull(); + }); + + it("re-applies the focus selection after the compared texts change", () => { + const longText = (mark: string) => + Array.from({ length: 30 }, (_, index) => (index === 14 ? mark : `line ${index + 1}`)).join("\n"); + + const { rerender } = render( + , + ); + + // 模拟点击跨组件修改项:focusRange 先落在旧文本上,stage 文本随后到达。 + rerender( + , + ); + rerender( + , + ); + + // 最终视图必须是组件 2 的文本,且选区重新落在新文本的目标范围上。 + expect(screen.getByRole("region", { name: "组件 2 执行前与组件 2 执行后对比" })).toBeInTheDocument(); + expect(leftPaneSelection()).toEqual({ from: 58, to: 71 }); + }); + + it("applies the focus selection when it arrives with the initial texts", () => { + render( + , + ); + + expect(leftPaneSelection()).toEqual({ from: 0, to: 1 }); + }); });