From ca6782d37d52e5da36b63f9e390b9364e90afba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 8 Oct 2026 22:32:28 -0400 Subject: [PATCH] Add the project rules, requirements and task records --- .gitignore | 10 +- CLAUDE.md | 1 + PRD.md | 944 +++++++++++++++++++++++++++++++++++++ agents.md | 256 ++++++++++ scripts/review.sh | 50 ++ scripts/test-map.py | 95 ++++ tasks/TASKS.md | 177 +++++++ tasks/reviews/C00.1-r1.md | 13 + tasks/reviews/C00.1-r2.md | 14 + tasks/reviews/C00.1-r3.md | 13 + tasks/reviews/C00.2-r1.md | 15 + tasks/reviews/C00.2-r2.md | 13 + tasks/reviews/C00.3-r1.md | 11 + tasks/reviews/C00.3-r2.md | 19 + tasks/reviews/C00.3-r3.md | 17 + tasks/reviews/C00.4-r1.md | 29 ++ tasks/reviews/C00.4-r2.md | 52 ++ tasks/reviews/C00.4-r3.md | 37 ++ tasks/reviews/C00.4-r4.md | 5 + tasks/reviews/C00.4-r5.md | 1 + tasks/reviews/C01.1-r1.md | 28 ++ tasks/reviews/C01.1-r2.md | 13 + tasks/reviews/C01.2a-r1.md | 7 + tasks/reviews/C01.2a-r2.md | 16 + tasks/reviews/C01.2a-r3.md | 15 + tasks/reviews/C01.2a-r4.md | 1 + tasks/reviews/C01.2b-r1.md | 27 ++ tasks/reviews/C01.2b-r2.md | 17 + tasks/reviews/C01.2b-r3.md | 8 + tasks/reviews/C01.2c-r1.md | 11 + tasks/reviews/C01.2c-r2.md | 14 + tasks/reviews/C01.2c-r3.md | 9 + tasks/reviews/C05.1-r1.md | 28 ++ tasks/reviews/C05.1-r2.md | 32 ++ tasks/reviews/C05.1-r3.md | 11 + tasks/reviews/C05.2-r1.md | 7 + tasks/reviews/C05.2-r2.md | 5 + tasks/reviews/C05.2-r3.md | 1 + tasks/reviews/C05.3-r1.md | 7 + tasks/reviews/C05.3-r2.md | 3 + tasks/reviews/C05.3-r3.md | 3 + tasks/reviews/C05.3-r4.md | 5 + tasks/reviews/C05.3-r5.md | 1 + tasks/reviews/C05.4-r1.md | 11 + tasks/reviews/C05.4-r2.md | 9 + tasks/reviews/C05.4-r3.md | 3 + tasks/reviews/C05.4-r4.md | 1 + tasks/reviews/C05.5-r1.md | 13 + tasks/reviews/C05.5-r2.md | 11 + tasks/reviews/C05.5-r3.md | 9 + tasks/reviews/C05.5-r4.md | 11 + tasks/reviews/C05.5-r5.md | 5 + tasks/reviews/C05.5-r6.md | 1 + tasks/reviews/C05.6-r1.md | 7 + tasks/reviews/C05.6-r2.md | 5 + tasks/reviews/C05.6-r3.md | 5 + tasks/reviews/C06.1-r1.md | 15 + tasks/reviews/C06.1-r10.md | 1 + tasks/reviews/C06.1-r2.md | 13 + tasks/reviews/C06.1-r3.md | 9 + tasks/reviews/C06.1-r4.md | 7 + tasks/reviews/C06.1-r5.md | 7 + tasks/reviews/C06.1-r6.md | 7 + tasks/reviews/C06.1-r7.md | 5 + tasks/reviews/C06.1-r8.md | 3 + tasks/reviews/C06.1-r9.md | 3 + tasks/reviews/C06.2-r1.md | 17 + tasks/reviews/C06.2-r2.md | 13 + tasks/reviews/C06.2-r3.md | 7 + tasks/reviews/C06.2-r4.md | 3 + tasks/reviews/C06.2-r5.md | 1 + tasks/reviews/C06.3-r1.md | 17 + tasks/reviews/C06.3-r10.md | 3 + tasks/reviews/C06.3-r11.md | 1 + tasks/reviews/C06.3-r2.md | 17 + tasks/reviews/C06.3-r3.md | 9 + tasks/reviews/C06.3-r4.md | 7 + tasks/reviews/C06.3-r5.md | 7 + tasks/reviews/C06.3-r6.md | 3 + tasks/reviews/C06.3-r7.md | 5 + tasks/reviews/C06.3-r8.md | 3 + tasks/reviews/C06.3-r9.md | 5 + tasks/reviews/C06.4-r1.md | 7 + tasks/reviews/C06.4-r2.md | 1 + tasks/reviews/C06.5-r1.md | 9 + tasks/reviews/C06.5-r2.md | 7 + tasks/reviews/C06.5-r3.md | 3 + tasks/reviews/C07.1-r1.md | 21 + tasks/reviews/C07.1-r10.md | 3 + tasks/reviews/C07.1-r2.md | 15 + tasks/reviews/C07.1-r3.md | 13 + tasks/reviews/C07.1-r4.md | 9 + tasks/reviews/C07.1-r5.md | 11 + tasks/reviews/C07.1-r6.md | 7 + tasks/reviews/C07.1-r7.md | 7 + tasks/reviews/C07.1-r8.md | 7 + tasks/reviews/C07.1-r9.md | 5 + tasks/reviews/C07.2-r1.md | 11 + tasks/reviews/C07.2-r2.md | 13 + tasks/reviews/C07.2-r3.md | 3 + tasks/reviews/C07.3-r1.md | 9 + tasks/reviews/C07.3-r2.md | 5 + tasks/reviews/C07.3-r3.md | 5 + tasks/reviews/C07.4-r1.md | 13 + tasks/reviews/C07.4-r2.md | 9 + tasks/reviews/C07.4-r3.md | 5 + tasks/reviews/C08.1-r1.md | 19 + tasks/reviews/C08.1-r2.md | 13 + tasks/reviews/C08.1-r3.md | 7 + tasks/reviews/C08.2-r1.md | 5 + tasks/reviews/C08.2-r2.md | 5 + tasks/reviews/C08.2-r3.md | 5 + tasks/reviews/C08.3-r1.md | 9 + tasks/reviews/C08.3-r2.md | 7 + tasks/reviews/C08.3-r3.md | 3 + tasks/reviews/C08.4-r1.md | 11 + tasks/reviews/C08.4-r2.md | 5 + tasks/reviews/C08.4-r3.md | 3 + tasks/reviews/C08.5-r1.md | 17 + tasks/reviews/C08.5-r2.md | 7 + tasks/reviews/C08.5-r3.md | 7 + tasks/reviews/C08.6-r1.md | 9 + tasks/reviews/C08.6-r2.md | 5 + tasks/reviews/C08.6-r3.md | 3 + tasks/reviews/C09.1-r1.md | 19 + tasks/reviews/C09.1-r2.md | 11 + tasks/reviews/C09.1-r3.md | 5 + tasks/reviews/C09.2-r1.md | 11 + tasks/reviews/C09.2-r2.md | 3 + tasks/reviews/C09.2-r3.md | 3 + tasks/reviews/C09.2a-r1.md | 5 + tasks/reviews/C09.2a-r2.md | 3 + tasks/reviews/C09.2a-r3.md | 5 + tasks/reviews/C09.3-r1.md | 21 + tasks/reviews/C09.3-r2.md | 11 + tasks/reviews/C09.3-r3.md | 11 + tasks/reviews/C09.4-r1.md | 13 + tasks/reviews/C09.4-r2.md | 7 + tasks/reviews/C09.4-r3.md | 3 + tasks/reviews/C09.5-r1.md | 13 + tasks/reviews/C09.5-r2.md | 9 + tasks/reviews/C09.5-r3.md | 7 + tasks/reviews/C10.1-r1.md | 15 + tasks/reviews/C10.1-r2.md | 9 + tasks/reviews/C10.1-r3.md | 3 + tasks/reviews/C10.2-r1.md | 13 + tasks/reviews/C10.2-r2.md | 7 + tasks/reviews/C10.2-r3.md | 5 + tasks/reviews/C10.3-r1.md | 21 + tasks/reviews/C10.3-r2.md | 7 + tasks/reviews/C10.3-r3.md | 7 + tasks/reviews/C10.4-r1.md | 15 + tasks/reviews/C10.4-r2.md | 11 + tasks/reviews/C10.4-r3.md | 5 + tasks/reviews/C10.5-r1.md | 19 + tasks/reviews/C10.5-r2.md | 13 + tasks/reviews/C10.5-r3.md | 5 + tasks/reviews/C10.6-r1.md | 13 + tasks/reviews/C10.6-r2.md | 7 + tasks/reviews/C10.6-r3.md | 5 + tasks/reviews/C11.1-r1.md | 29 ++ tasks/reviews/C11.1-r2.md | 9 + tasks/reviews/C11.1-r3.md | 11 + tasks/reviews/C11.2-r1.md | 17 + tasks/reviews/C11.2-r2.md | 7 + tasks/reviews/C11.2-r3.md | 5 + tasks/reviews/C11.3-r1.md | 15 + tasks/reviews/C11.3-r2.md | 5 + tasks/reviews/C11.3-r3.md | 3 + tasks/reviews/C11.4-r1.md | 7 + tasks/reviews/C11.4-r2.md | 5 + tasks/reviews/C11.4-r3.md | 3 + tasks/reviews/C11.5-r1.md | 13 + tasks/reviews/C11.5-r2.md | 3 + tasks/reviews/C11.5-r3.md | 1 + tasks/reviews/C11.6-r1.md | 17 + tasks/reviews/C11.6-r2.md | 13 + tasks/reviews/C11.6-r3.md | 9 + tasks/reviews/C11.7-r1.md | 11 + tasks/reviews/C11.7-r2.md | 5 + tasks/reviews/C11.7-r3.md | 7 + tasks/reviews/C11.8-r1.md | 7 + tasks/reviews/C11.8-r2.md | 5 + tasks/reviews/C11.8-r3.md | 5 + tasks/reviews/C13.1-r1.md | 15 + tasks/reviews/C13.1-r2.md | 1 + tasks/reviews/C13.2-r1.md | 17 + tasks/reviews/C13.2-r2.md | 7 + tasks/reviews/C13.2-r3.md | 5 + tasks/reviews/C13.3-r1.md | 17 + tasks/reviews/C13.3-r2.md | 7 + tasks/reviews/C13.3-r3.md | 11 + tasks/reviews/C13.4-r1.md | 15 + tasks/reviews/C13.4-r2.md | 13 + tasks/reviews/C13.4-r3.md | 7 + tasks/reviews/C13.5-r1.md | 13 + tasks/reviews/C13.5-r2.md | 1 + tasks/reviews/C13.6-r1.md | 17 + tasks/reviews/C13.6-r2.md | 5 + tasks/reviews/C13.6-r3.md | 7 + tasks/reviews/C14.0-r1.md | 15 + tasks/reviews/C14.0-r2.md | 5 + tasks/reviews/C14.0-r3.md | 5 + tasks/reviews/C14.1-r1.md | 9 + tasks/reviews/C14.1-r2.md | 11 + tasks/reviews/C14.1-r3.md | 5 + tasks/reviews/C14.2-r1.md | 13 + tasks/reviews/C14.2-r2.md | 5 + tasks/reviews/C14.2-r3.md | 1 + tasks/reviews/C14.3-r1.md | 15 + tasks/reviews/C14.3-r2.md | 9 + tasks/reviews/C14.3-r3.md | 7 + tasks/reviews/C14.4-r1.md | 13 + tasks/reviews/C14.4-r2.md | 7 + tasks/reviews/C14.4-r3.md | 5 + tasks/reviews/C14.5-r1.md | 11 + tasks/reviews/C14.5-r2.md | 7 + tasks/reviews/C14.5-r3.md | 5 + tasks/reviews/C14.6-r1.md | 13 + tasks/reviews/C14.6-r2.md | 11 + tasks/reviews/C14.6-r3.md | 7 + tasks/reviews/C14.7-r1.md | 17 + tasks/reviews/C14.7-r2.md | 9 + tasks/reviews/C14.7-r3.md | 5 + tasks/reviews/C16.1-r1.md | 7 + tasks/reviews/C16.1-r2.md | 3 + tasks/reviews/C16.1-r3.md | 1 + tasks/reviews/C17.1-r1.md | 13 + tasks/reviews/C17.1-r2.md | 7 + tasks/reviews/C17.1-r3.md | 9 + tasks/reviews/C18.1-r1.md | 11 + tasks/reviews/C18.1-r2.md | 5 + tasks/reviews/C18.1-r3.md | 5 + tasks/reviews/C18.2-r1.md | 13 + tasks/reviews/C18.2-r2.md | 7 + tasks/reviews/C18.2-r3.md | 9 + tasks/reviews/C18.3-r1.md | 9 + tasks/reviews/C18.3-r2.md | 9 + tasks/reviews/C18.3-r3.md | 7 + tasks/reviews/C19.1-r1.md | 13 + tasks/reviews/C19.1-r2.md | 7 + tasks/reviews/C19.1-r3.md | 7 + tasks/reviews/C19.2-r1.md | 7 + tasks/reviews/C19.2-r2.md | 5 + tasks/reviews/C19.2-r3.md | 1 + tasks/reviews/C20.1-r1.md | 13 + tasks/reviews/C20.1-r2.md | 9 + tasks/reviews/C20.1-r3.md | 7 + tasks/reviews/R1.1-r1.md | 11 + tasks/reviews/R1.1-r2.md | 5 + tasks/reviews/R1.1-r3.md | 11 + tests/scripts/agents_md.py | 48 ++ 252 files changed, 3829 insertions(+), 8 deletions(-) create mode 100644 CLAUDE.md create mode 100644 PRD.md create mode 100644 agents.md create mode 100755 scripts/review.sh create mode 100644 scripts/test-map.py create mode 100644 tasks/TASKS.md create mode 100644 tasks/reviews/C00.1-r1.md create mode 100644 tasks/reviews/C00.1-r2.md create mode 100644 tasks/reviews/C00.1-r3.md create mode 100644 tasks/reviews/C00.2-r1.md create mode 100644 tasks/reviews/C00.2-r2.md create mode 100644 tasks/reviews/C00.3-r1.md create mode 100644 tasks/reviews/C00.3-r2.md create mode 100644 tasks/reviews/C00.3-r3.md create mode 100644 tasks/reviews/C00.4-r1.md create mode 100644 tasks/reviews/C00.4-r2.md create mode 100644 tasks/reviews/C00.4-r3.md create mode 100644 tasks/reviews/C00.4-r4.md create mode 100644 tasks/reviews/C00.4-r5.md create mode 100644 tasks/reviews/C01.1-r1.md create mode 100644 tasks/reviews/C01.1-r2.md create mode 100644 tasks/reviews/C01.2a-r1.md create mode 100644 tasks/reviews/C01.2a-r2.md create mode 100644 tasks/reviews/C01.2a-r3.md create mode 100644 tasks/reviews/C01.2a-r4.md create mode 100644 tasks/reviews/C01.2b-r1.md create mode 100644 tasks/reviews/C01.2b-r2.md create mode 100644 tasks/reviews/C01.2b-r3.md create mode 100644 tasks/reviews/C01.2c-r1.md create mode 100644 tasks/reviews/C01.2c-r2.md create mode 100644 tasks/reviews/C01.2c-r3.md create mode 100644 tasks/reviews/C05.1-r1.md create mode 100644 tasks/reviews/C05.1-r2.md create mode 100644 tasks/reviews/C05.1-r3.md create mode 100644 tasks/reviews/C05.2-r1.md create mode 100644 tasks/reviews/C05.2-r2.md create mode 100644 tasks/reviews/C05.2-r3.md create mode 100644 tasks/reviews/C05.3-r1.md create mode 100644 tasks/reviews/C05.3-r2.md create mode 100644 tasks/reviews/C05.3-r3.md create mode 100644 tasks/reviews/C05.3-r4.md create mode 100644 tasks/reviews/C05.3-r5.md create mode 100644 tasks/reviews/C05.4-r1.md create mode 100644 tasks/reviews/C05.4-r2.md create mode 100644 tasks/reviews/C05.4-r3.md create mode 100644 tasks/reviews/C05.4-r4.md create mode 100644 tasks/reviews/C05.5-r1.md create mode 100644 tasks/reviews/C05.5-r2.md create mode 100644 tasks/reviews/C05.5-r3.md create mode 100644 tasks/reviews/C05.5-r4.md create mode 100644 tasks/reviews/C05.5-r5.md create mode 100644 tasks/reviews/C05.5-r6.md create mode 100644 tasks/reviews/C05.6-r1.md create mode 100644 tasks/reviews/C05.6-r2.md create mode 100644 tasks/reviews/C05.6-r3.md create mode 100644 tasks/reviews/C06.1-r1.md create mode 100644 tasks/reviews/C06.1-r10.md create mode 100644 tasks/reviews/C06.1-r2.md create mode 100644 tasks/reviews/C06.1-r3.md create mode 100644 tasks/reviews/C06.1-r4.md create mode 100644 tasks/reviews/C06.1-r5.md create mode 100644 tasks/reviews/C06.1-r6.md create mode 100644 tasks/reviews/C06.1-r7.md create mode 100644 tasks/reviews/C06.1-r8.md create mode 100644 tasks/reviews/C06.1-r9.md create mode 100644 tasks/reviews/C06.2-r1.md create mode 100644 tasks/reviews/C06.2-r2.md create mode 100644 tasks/reviews/C06.2-r3.md create mode 100644 tasks/reviews/C06.2-r4.md create mode 100644 tasks/reviews/C06.2-r5.md create mode 100644 tasks/reviews/C06.3-r1.md create mode 100644 tasks/reviews/C06.3-r10.md create mode 100644 tasks/reviews/C06.3-r11.md create mode 100644 tasks/reviews/C06.3-r2.md create mode 100644 tasks/reviews/C06.3-r3.md create mode 100644 tasks/reviews/C06.3-r4.md create mode 100644 tasks/reviews/C06.3-r5.md create mode 100644 tasks/reviews/C06.3-r6.md create mode 100644 tasks/reviews/C06.3-r7.md create mode 100644 tasks/reviews/C06.3-r8.md create mode 100644 tasks/reviews/C06.3-r9.md create mode 100644 tasks/reviews/C06.4-r1.md create mode 100644 tasks/reviews/C06.4-r2.md create mode 100644 tasks/reviews/C06.5-r1.md create mode 100644 tasks/reviews/C06.5-r2.md create mode 100644 tasks/reviews/C06.5-r3.md create mode 100644 tasks/reviews/C07.1-r1.md create mode 100644 tasks/reviews/C07.1-r10.md create mode 100644 tasks/reviews/C07.1-r2.md create mode 100644 tasks/reviews/C07.1-r3.md create mode 100644 tasks/reviews/C07.1-r4.md create mode 100644 tasks/reviews/C07.1-r5.md create mode 100644 tasks/reviews/C07.1-r6.md create mode 100644 tasks/reviews/C07.1-r7.md create mode 100644 tasks/reviews/C07.1-r8.md create mode 100644 tasks/reviews/C07.1-r9.md create mode 100644 tasks/reviews/C07.2-r1.md create mode 100644 tasks/reviews/C07.2-r2.md create mode 100644 tasks/reviews/C07.2-r3.md create mode 100644 tasks/reviews/C07.3-r1.md create mode 100644 tasks/reviews/C07.3-r2.md create mode 100644 tasks/reviews/C07.3-r3.md create mode 100644 tasks/reviews/C07.4-r1.md create mode 100644 tasks/reviews/C07.4-r2.md create mode 100644 tasks/reviews/C07.4-r3.md create mode 100644 tasks/reviews/C08.1-r1.md create mode 100644 tasks/reviews/C08.1-r2.md create mode 100644 tasks/reviews/C08.1-r3.md create mode 100644 tasks/reviews/C08.2-r1.md create mode 100644 tasks/reviews/C08.2-r2.md create mode 100644 tasks/reviews/C08.2-r3.md create mode 100644 tasks/reviews/C08.3-r1.md create mode 100644 tasks/reviews/C08.3-r2.md create mode 100644 tasks/reviews/C08.3-r3.md create mode 100644 tasks/reviews/C08.4-r1.md create mode 100644 tasks/reviews/C08.4-r2.md create mode 100644 tasks/reviews/C08.4-r3.md create mode 100644 tasks/reviews/C08.5-r1.md create mode 100644 tasks/reviews/C08.5-r2.md create mode 100644 tasks/reviews/C08.5-r3.md create mode 100644 tasks/reviews/C08.6-r1.md create mode 100644 tasks/reviews/C08.6-r2.md create mode 100644 tasks/reviews/C08.6-r3.md create mode 100644 tasks/reviews/C09.1-r1.md create mode 100644 tasks/reviews/C09.1-r2.md create mode 100644 tasks/reviews/C09.1-r3.md create mode 100644 tasks/reviews/C09.2-r1.md create mode 100644 tasks/reviews/C09.2-r2.md create mode 100644 tasks/reviews/C09.2-r3.md create mode 100644 tasks/reviews/C09.2a-r1.md create mode 100644 tasks/reviews/C09.2a-r2.md create mode 100644 tasks/reviews/C09.2a-r3.md create mode 100644 tasks/reviews/C09.3-r1.md create mode 100644 tasks/reviews/C09.3-r2.md create mode 100644 tasks/reviews/C09.3-r3.md create mode 100644 tasks/reviews/C09.4-r1.md create mode 100644 tasks/reviews/C09.4-r2.md create mode 100644 tasks/reviews/C09.4-r3.md create mode 100644 tasks/reviews/C09.5-r1.md create mode 100644 tasks/reviews/C09.5-r2.md create mode 100644 tasks/reviews/C09.5-r3.md create mode 100644 tasks/reviews/C10.1-r1.md create mode 100644 tasks/reviews/C10.1-r2.md create mode 100644 tasks/reviews/C10.1-r3.md create mode 100644 tasks/reviews/C10.2-r1.md create mode 100644 tasks/reviews/C10.2-r2.md create mode 100644 tasks/reviews/C10.2-r3.md create mode 100644 tasks/reviews/C10.3-r1.md create mode 100644 tasks/reviews/C10.3-r2.md create mode 100644 tasks/reviews/C10.3-r3.md create mode 100644 tasks/reviews/C10.4-r1.md create mode 100644 tasks/reviews/C10.4-r2.md create mode 100644 tasks/reviews/C10.4-r3.md create mode 100644 tasks/reviews/C10.5-r1.md create mode 100644 tasks/reviews/C10.5-r2.md create mode 100644 tasks/reviews/C10.5-r3.md create mode 100644 tasks/reviews/C10.6-r1.md create mode 100644 tasks/reviews/C10.6-r2.md create mode 100644 tasks/reviews/C10.6-r3.md create mode 100644 tasks/reviews/C11.1-r1.md create mode 100644 tasks/reviews/C11.1-r2.md create mode 100644 tasks/reviews/C11.1-r3.md create mode 100644 tasks/reviews/C11.2-r1.md create mode 100644 tasks/reviews/C11.2-r2.md create mode 100644 tasks/reviews/C11.2-r3.md create mode 100644 tasks/reviews/C11.3-r1.md create mode 100644 tasks/reviews/C11.3-r2.md create mode 100644 tasks/reviews/C11.3-r3.md create mode 100644 tasks/reviews/C11.4-r1.md create mode 100644 tasks/reviews/C11.4-r2.md create mode 100644 tasks/reviews/C11.4-r3.md create mode 100644 tasks/reviews/C11.5-r1.md create mode 100644 tasks/reviews/C11.5-r2.md create mode 100644 tasks/reviews/C11.5-r3.md create mode 100644 tasks/reviews/C11.6-r1.md create mode 100644 tasks/reviews/C11.6-r2.md create mode 100644 tasks/reviews/C11.6-r3.md create mode 100644 tasks/reviews/C11.7-r1.md create mode 100644 tasks/reviews/C11.7-r2.md create mode 100644 tasks/reviews/C11.7-r3.md create mode 100644 tasks/reviews/C11.8-r1.md create mode 100644 tasks/reviews/C11.8-r2.md create mode 100644 tasks/reviews/C11.8-r3.md create mode 100644 tasks/reviews/C13.1-r1.md create mode 100644 tasks/reviews/C13.1-r2.md create mode 100644 tasks/reviews/C13.2-r1.md create mode 100644 tasks/reviews/C13.2-r2.md create mode 100644 tasks/reviews/C13.2-r3.md create mode 100644 tasks/reviews/C13.3-r1.md create mode 100644 tasks/reviews/C13.3-r2.md create mode 100644 tasks/reviews/C13.3-r3.md create mode 100644 tasks/reviews/C13.4-r1.md create mode 100644 tasks/reviews/C13.4-r2.md create mode 100644 tasks/reviews/C13.4-r3.md create mode 100644 tasks/reviews/C13.5-r1.md create mode 100644 tasks/reviews/C13.5-r2.md create mode 100644 tasks/reviews/C13.6-r1.md create mode 100644 tasks/reviews/C13.6-r2.md create mode 100644 tasks/reviews/C13.6-r3.md create mode 100644 tasks/reviews/C14.0-r1.md create mode 100644 tasks/reviews/C14.0-r2.md create mode 100644 tasks/reviews/C14.0-r3.md create mode 100644 tasks/reviews/C14.1-r1.md create mode 100644 tasks/reviews/C14.1-r2.md create mode 100644 tasks/reviews/C14.1-r3.md create mode 100644 tasks/reviews/C14.2-r1.md create mode 100644 tasks/reviews/C14.2-r2.md create mode 100644 tasks/reviews/C14.2-r3.md create mode 100644 tasks/reviews/C14.3-r1.md create mode 100644 tasks/reviews/C14.3-r2.md create mode 100644 tasks/reviews/C14.3-r3.md create mode 100644 tasks/reviews/C14.4-r1.md create mode 100644 tasks/reviews/C14.4-r2.md create mode 100644 tasks/reviews/C14.4-r3.md create mode 100644 tasks/reviews/C14.5-r1.md create mode 100644 tasks/reviews/C14.5-r2.md create mode 100644 tasks/reviews/C14.5-r3.md create mode 100644 tasks/reviews/C14.6-r1.md create mode 100644 tasks/reviews/C14.6-r2.md create mode 100644 tasks/reviews/C14.6-r3.md create mode 100644 tasks/reviews/C14.7-r1.md create mode 100644 tasks/reviews/C14.7-r2.md create mode 100644 tasks/reviews/C14.7-r3.md create mode 100644 tasks/reviews/C16.1-r1.md create mode 100644 tasks/reviews/C16.1-r2.md create mode 100644 tasks/reviews/C16.1-r3.md create mode 100644 tasks/reviews/C17.1-r1.md create mode 100644 tasks/reviews/C17.1-r2.md create mode 100644 tasks/reviews/C17.1-r3.md create mode 100644 tasks/reviews/C18.1-r1.md create mode 100644 tasks/reviews/C18.1-r2.md create mode 100644 tasks/reviews/C18.1-r3.md create mode 100644 tasks/reviews/C18.2-r1.md create mode 100644 tasks/reviews/C18.2-r2.md create mode 100644 tasks/reviews/C18.2-r3.md create mode 100644 tasks/reviews/C18.3-r1.md create mode 100644 tasks/reviews/C18.3-r2.md create mode 100644 tasks/reviews/C18.3-r3.md create mode 100644 tasks/reviews/C19.1-r1.md create mode 100644 tasks/reviews/C19.1-r2.md create mode 100644 tasks/reviews/C19.1-r3.md create mode 100644 tasks/reviews/C19.2-r1.md create mode 100644 tasks/reviews/C19.2-r2.md create mode 100644 tasks/reviews/C19.2-r3.md create mode 100644 tasks/reviews/C20.1-r1.md create mode 100644 tasks/reviews/C20.1-r2.md create mode 100644 tasks/reviews/C20.1-r3.md create mode 100644 tasks/reviews/R1.1-r1.md create mode 100644 tasks/reviews/R1.1-r2.md create mode 100644 tasks/reviews/R1.1-r3.md create mode 100755 tests/scripts/agents_md.py diff --git a/.gitignore b/.gitignore index 433c540..0a9880f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,18 +7,12 @@ node_modules/ .DS_Store .ruff_cache/ -# Internal working files, kept local -/agents.md -/CLAUDE.md -/PRD.md -/tasks/ +# Local only +/tasks/reviews/.pane /.pi/ /markdown-sample.md /docs/engineering-risks.md /docs/qa.md /docs/acceptance.md /docs/test-map.md -/scripts/review.sh -/scripts/test-map.py -/tests/scripts/agents_md.py /assets/papirus_icons-master.zip diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..79709e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@agents.md diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..e6af1c7 --- /dev/null +++ b/PRD.md @@ -0,0 +1,944 @@ +# Ely Office 产品需求文档 + +版本:1.0 +日期:2026-10-01 +状态:产品与工程实施基线 +目标平台:Linux、Windows、macOS +产品形态:开源、离线优先、可独立安装和运行的桌面应用套件 + +本文中的“必须”是发布要求。“默认”是初始设置,用户可以按文中规定修改。功能表中的所有条目均属于首个正式版本,不代表分阶段计划。性能数值是验收目标,不是已有测试结果。第三方项目提供的能力与 Ely 已完成的能力分开记录。 + +## 1. 产品定义 + +Ely Office 提供八个桌面应用。用户可以写文档、计算表格、制作演示、编辑 PDF、收发邮件、管理 Git 仓库、编辑 Markdown 和管理 Agent 会话。 + +办公文件保存在用户选择的目录。应用不要求注册 Ely 账号,不要求上传文件,不依赖在线文档服务器。全部应用使用 Ely GPUI Components 的设计体系。编辑、解析、计算和协议处理复用现有项目,不重写专业内核。 + +### 1.1 应用构成 + +| 应用 | 职责 | 内核或后端基线 | 主要数据 | +| --- | --- | --- | --- | +| ElyDoc | 分页文档编辑 | LibreOfficeKit,简称 LOK | DOCX、ODT | +| ElyTable | 电子表格编辑与计算 | LOK | XLSX、ODS、CSV | +| ElyShow | 演示文稿编辑与放映 | LOK | PPTX、ODP | +| ElyPDF | PDF 阅读、编辑、批注、表单和页面处理 | MuPDF;OCR 复用 Tesseract | PDF | +| ElyMail | 邮件收发、本地存储和搜索 | MailCore 2,加本地同步与存储 | 邮件、附件、草稿 | +| ElyTree | Git 图形化版本管理 | Git CLI | 本地 Git 仓库 | +| ElyMark | 本地 Markdown 编辑 | Milkdown 与 CodeMirror 6;参考 MarkText、ColaMD 的编辑与文件处理方式 | Markdown 与本地附件 | +| ElyAgent | 独立的 Agent 配置与会话管理 | 嵌入的 Kigi CLI Rust 代码 | 配置、会话记录、运行状态 | + +LOK、MuPDF 和 GPUI 是已确定的方向。MailCore 2、Markdown 编辑框架、本地 HTML 宿主和下文的发行默认值,是本 PRD 采用的工程基线,仍须通过三端接入验收。不得把未测试的接口写成已支持功能。 + +ElyAgent 参考 Magpie 的独立工具定位,但不要求复制其全部功能。它不是其他七个应用的聊天侧栏,也不是整个套件的控制中枢。[S19] + +### 1.2 固定决策 + +1. 首个正式版交付全部八个应用及本文定义的功能,不发布只有入口和占位界面的套件。 +2. Doc、Table、Show 共用 LOK 适配实现。PDF 使用 MuPDF。不开设替代内核路线。 +3. GPUI 负责应用界面。专业内容区域可以使用内核渲染结果或本地 Web 内容区域,但外观和行为必须遵守 Ely 规范。 +4. 应用可以独立启动、更新和卸载。安装 ElyMark、ElyTree 或 ElyAgent 不得强制安装 LOK。 +5. 用户文件采用通用格式。应用数据库只保存索引、配置和运行记录,不取代用户文件。 +6. 不建设云端文档平台、微服务系统、统一业务数据库或跨应用 Agent 编排系统。 + +### 1.3 成功标准 + +用户在断网电脑上安装发行包后,可以创建、修改、保存、重新打开和打印本文范围内的本地文件。文件中的受支持内容不丢失。界面不会切回上游产品的工具栏或高级设置窗口。邮件、远程 Git 和远程模型连接失败时,本地工作仍可继续。 + +“对标 Office”指完成本文列出的日常工作,不代表兼容微软所有历史格式、宏、插件和企业服务。不以组件数量、文件扩展名数量或界面截图代替验收。 + +## 2. 用户与工作场景 + +面向处理本地文件的个人、开发者和小团队。用户可能只安装一个应用,也可能安装整套产品。首次使用不要求建立工作区、创建账号或完成教程。 + +| 场景 | 用户操作 | 完成条件 | +| --- | --- | --- | +| 审阅合同 | 打开 DOCX,修改条款,添加批注,保存并导出 PDF | 原有表格、编号、页眉页脚和修订记录按支持范围保留 | +| 整理数据 | 导入 CSV,编辑公式,筛选、汇总并绘制图表 | 导入规则可检查,计算结果可复现,导出不静默丢失工作表 | +| 制作汇报 | 修改 PPTX 母版、图文和备注,在外接屏幕放映 | 编辑对象仍可修改,观众屏与演讲者视图分离 | +| 处理 PDF | 修改文字对象、整理页面、填写表单并删除敏感内容 | 保存后的 PDF 可在独立阅读器打开,删除内容不能从输出文件恢复 | +| 离线处理邮件 | 阅读已缓存邮件,检索附件,写回复 | 草稿和发件队列落盘;联网后的发送状态有证据 | +| 管理仓库 | 查看差异、分块暂存、提交、合并并处理冲突 | Git 仓库与终端操作结果一致,危险操作先说明影响 | +| 编写 Markdown | 编辑本地文档,插入图片,与外部编辑器共同使用 | 未修改内容不被无故重写,外部变更不覆盖未保存内容 | +| 管理 Agent | 配置后端,在多个目录启动会话,查看执行和产物 | 会话状态与后端一致,重启后的恢复能力如实显示 | + +## 3. 产品范围与离线边界 + +### 3.1 离线能力 + +| 应用 | 无网络时必须可用 | 需要连接时才可执行 | +| --- | --- | --- | +| Doc、Table、Show | 本地编辑、搜索、保存、导出、打印、放映 | 用户主动更新外部链接或打开远程资源 | +| PDF | 本地编辑、OCR、批注、表单、页面处理、导出 | 在线证书吊销查询、时间戳服务等外部请求 | +| Mail | 读取已缓存内容、搜索、写草稿、管理待发送邮件 | 首次授权、收取新邮件、下载未缓存内容、发送 | +| Tree | 查看已有历史、差异、暂存、提交、分支、合并、变基 | 访问远程仓库、下载尚未缓存的 LFS 对象 | +| Mark | 编辑、预览、搜索、公式与图表渲染、PDF/HTML 导出 | 用户主动加载远程图片或打开外部链接 | +| Agent | 查看本地配置和历史;运行依赖均已安装的本地后端 | 远程模型、远程工具及其授权 | + +应用资源、帮助、默认模板、Markdown 渲染资源和 PDF 中英文 OCR 语言数据随对应发行包提供。不从 CDN 加载脚本、字体或样式。断网不能阻止应用启动,也不能触发登录弹窗。 + +本地模型及第三方 Agent 不强制打包。离线使用它们的前提是用户已安装对应程序、模型和依赖。应用必须检查这些条件,不能把“能打开会话界面”显示成“模型可离线运行”。 + +### 3.2 不纳入产品范围 + +不建设实时多人协同、云盘、在线 Office 服务、组织管理后台、应用商店、计费系统、项目管理系统或完整日历产品。 + +不自研文字排版、公式计算、PDF 解析、邮件协议、Git 存储、模型推理和通用 Agent 执行框架。不承诺执行 VBA、ActiveX、COM 插件、Power Query、Power Pivot 或微软专有协作功能。 + +不提供远程桌面、跨电脑 Agent 调度、自动代发邮件、自动跨应用修改文件或无限制后台任务。用户可通过系统文件和明确的“用其他应用打开”操作连接应用。 + +这些是产品边界,不是推迟交付的清单。 + +## 4. 平台与发行 + +### 4.1 支持目标 + +以下版本是本项目的支持目标,不是上游库已经通过验证的结论。 + +| 平台 | 首版架构与验收环境 | 发行方式 | +| --- | --- | --- | +| Windows | Windows 11,x86_64 | 离线安装程序;支持按用户安装 | +| macOS | macOS 14 及以上,Apple Silicon 与 Intel | 按架构提供签名和公证后的应用包 | +| Linux | x86_64;Ubuntu 24.04 LTS;固定日期的 Arch/Omarchy 测试快照 | AppImage;提供原生包构建说明 | + +Linux 必须覆盖 Wayland 和 X11。macOS 两种架构分别测试,不以另一架构模拟运行代替。Windows ARM、Linux ARM、移动端和浏览器版不在首版支持清单。同时,针对 Omarchy Linux,需要适配 Omarchy Linux 的 Theme Sync 功能 + +必须发布图形后端、系统库和最低运行环境说明。不承诺任意虚拟机或无图形加速环境可运行;无法初始化图形后端时显示原因,不留下空白窗口。 + +### 4.2 安装与更新 + +全套发行包包含八个应用。每个应用也有独立发行包,并附带其所需运行库。用户不必安装开发工具、Node.js、Python、Docker 或文档服务器。开发和构建依赖不受此限制。 + +各应用共享同一套发布版本与设计规范。独立安装包自带所需运行库,允许必要的重复打包,不建设全局运行库管理器。升级一个应用不得替换其他应用依赖的内核版本。 + +自动检查更新默认关闭。提供手动检查、下载和离线安装。更新前要求保存编辑内容;更新失败可继续使用原版本。Linux 包管理器安装的版本不绕过包管理器自行覆盖文件。 + +卸载应用保留用户文档。删除缓存、账号或本地历史必须单独确认。不得把用户选择的工作目录视为应用缓存删除。 + +## 5. 统一 UI/UX + +### 5.1 组件使用 + +应用窗口、菜单、导航、标签页、工具栏、属性面板、对话框、通知和设置使用 Ely GPUI Components。优先复用现有组件。新增通用交互回到组件库实现,不在八个应用里复制。 + +仓库已有文档、Git、邮件、Agent 等组件分类,但任务清单中的 `host`、`prove` 等标签说明仍有宿主数据或接入要求。组件展示与应用能力分开验收。[S1][S2] + +不使用上游 Office 的窗口、菜单和侧栏作为正式界面。不得用颜色覆盖掩盖两套交互。平台原生文件选择器、打印对话框和凭据授权窗口属于允许的系统界面。 + +### 5.2 布局与外观 + +采用同一套浅色、深色和跟随系统主题。沿用组件库的颜色、字体、间距、圆角、边框和图标 token,不新建每应用主题。不使用渐变、霓虹、装饰性玻璃效果、大面积品牌色或多层卡片。 + +窗口主体按需要使用标题与标签区、左侧导航、中央内容区、右侧属性区和底部状态区。无内容的侧栏不占位。属性区只显示当前选择对象的属性。专业工具不同,但相同操作的位置、名称和图标相同。 + +ElyMark 默认只显示必要的标签、文件和编辑入口。ElyMail 使用文件夹、邮件列表和正文。ElyTree 使用仓库导航、提交或改动列表与差异区。不得为了布局相同而强迫所有应用常驻三栏。 + +界面主题不修改文档本身的字体、背景、单元格填色或打印颜色。深色阅读显示与输出内容分开。 + +### 5.3 共用交互 + +| 编号 | 要求 | 验收方式 | +| --- | --- | --- | +| UX-01 | 文件菜单中的新建、打开、保存、另存为、导出和打印使用统一命名 | 在各适用应用逐项检查 | +| UX-02 | 相同操作使用相同快捷键语义;macOS 使用 Command,其他平台使用 Ctrl | 从菜单和快捷键得到相同结果 | +| UX-03 | Tab 顺序可预测,焦点可见;Esc 关闭当前浮层,不连带退出文档 | 仅用键盘完成主要工作流程 | +| UX-04 | 禁用操作说明原因;混合选择显示“混合”,不冒用首个对象属性 | 多选不同格式内容后检查属性栏 | +| UX-05 | 长任务显示范围、状态、错误和可用的取消操作 | 导出、OCR、同步、Git 操作不阻塞窗口 | +| UX-06 | 失败提示保留用户输入,并提供重试或另存路径 | 模拟权限不足、断网和磁盘不足 | +| UX-07 | 危险操作说明具体对象和影响,不只显示“确定吗” | 检查删除、覆盖、硬重置和内容删除 | +| UX-08 | 支持简体中文与英文;错误正文不用未解释的内部错误码替代 | 切换语言后检查主要界面 | +| UX-09 | 中文输入法组合输入、候选框、撤销与跨区域焦点一致 | 三端分别测试拼音输入和中英文混排 | +| UX-10 | 多屏缩放变化不造成点击偏移、选区漂移或浮层遮挡 | 100%、150%、200% 缩放下跨屏拖动 | +| UX-11 | 正文、标签、错误和状态不只通过颜色表达 | 检查灰度显示和高对比主题 | +| UX-12 | 使用系统辅助技术所需的名称、角色、状态和文本信息 | 三端完成键盘与屏幕阅读器检查 | + +同一窗口只允许一个活动编辑焦点。菜单操作可作用于编辑器当前选区,但不得先清除选区。内核快捷键与 GPUI 快捷键不得重复执行同一操作。 + +## 6. 文件、保存与恢复 + +### 6.1 文件会话 + +Doc、Table、Show、PDF 和 Mark 的每个打开文件都有独立会话。会话记录文件位置、最后加载的文件指纹、编辑修订号、保存状态、光标或选区、缩放和恢复副本位置。 + +同一个文件再次打开时聚焦已有会话,或在用户要求下打开明确标注的只读视图。不允许两个窗口在不知情的情况下交替覆盖同一文件。 + +| 编号 | 要求 | +| --- | --- | +| FILE-01 | 新建文件在首次保存前没有隐含目标路径;关闭前处理未保存内容 | +| FILE-02 | 显示加载中、已保存、未保存、保存中、只读、外部已修改、保存失败等状态 | +| FILE-03 | 保存任务记录对应修订号;旧修订保存完成不得把更新的编辑标为已保存 | +| FILE-04 | 写入临时文件并检查写入结果,再替换目标;失败保留原文件与未保存内容 | +| FILE-05 | 保存前检查外部修改;冲突提供重新加载、保留本地副本和比较,不静默覆盖 | +| FILE-06 | 导出默认生成独立文件,不改变正在编辑文件的格式和路径 | +| FILE-07 | 复制、粘贴、拖放和附件导入失败时不删除原始数据 | +| FILE-08 | 保存、导出、打印都针对可识别的文档修订,不输出一半新一半旧的内容 | +| FILE-09 | 路径支持空格、中文、非 ASCII 字符和长文件名;保留原有大小写 | +| FILE-10 | 对损坏、加密、缺少字体和不支持的内容给出可执行提示 | + +本地文件系统采用原子替换能力。网络盘、云盘占位文件或不支持原子替换的目标必须保留上一份文件,并提示相应限制,不把本地原子保存保证套用于所有存储介质。 + +### 6.2 默认保存行为 + +Doc、Table、Show、PDF 默认显式保存,后台只生成恢复副本,不擅自覆盖源文件。用户可以对普通未签名文件启用自动保存。检测到外部变更或兼容风险后,停止自动覆盖并提示。 + +ElyMark 对已有路径文件默认自动保存:输入停止 1 秒后提交保存任务。无路径文件先生成恢复副本,首次保存仍由用户选择位置。自动保存失败后保持未保存状态。 + +恢复副本默认每 10 秒生成一次,采用后台任务。同一文档只允许一个恢复任务在运行。大文件无法完成时显示最后成功恢复时间,不宣称已经持久化。保留最近 20 个恢复点,最多 7 天;用户可更改容量上限。恢复内容不得同步到外部服务。 + +关闭时提供保存、放弃和取消。显式放弃后记录结果,不在下次启动时把它误判为崩溃恢复。删除敏感内容相关临时文件遵守 PDF 章节的单独规则。 + +### 6.3 崩溃与恢复 + +内核崩溃不得带走其他应用。当前应用显示故障、最近一次恢复时间和重新打开入口。恢复生成新副本,不先覆盖源文件。 + +能重新打开恢复文件,不等于恢复了崩溃前的全部撤销历史。界面必须区分内容恢复、光标恢复和历史恢复。未具备后端支持时不宣称三者全部恢复。 + +## 7. LOK 共用接入要求 + +LOK 提供 C/C++ 接口、分块渲染、输入事件、编辑命令和回调。相关编辑 API 中存在不稳定接口,必须锁定版本并维护回归测试。[S3][S4] + +LOK 负责文档模型、布局、计算、内容编辑与文件转换。GPUI 负责操作界面与显示内核输出。不得先转成另一份简单 JSON,再用组件库重新实现页面、表格或幻灯片编辑。 + +| 编号 | 要求 | +| --- | --- | +| LOK-01 | 使用随应用交付的固定 LOK 构建,隔离用户已安装的 LibreOffice 配置 | +| LOK-02 | 每项界面操作有明确的内核命令、参数、结果和状态回调映射 | +| LOK-03 | 处理内容区域失效通知,只更新必要图块;旧修订的画面不得覆盖新画面 | +| LOK-04 | 统一转换文档坐标、视图缩放、滚动和平台 DPI;输入与选区使用同一坐标规则 | +| LOK-05 | 字体、字号、样式、选择对象、修改状态和撤销可用性以内核为准 | +| LOK-06 | 复制粘贴覆盖纯文本、富文本、图片和表格;不只实现键盘事件转发 | +| LOK-07 | 中文输入法包含组合开始、更新、提交和取消;不拆成普通按键模拟输入 | +| LOK-08 | 页面设置、图表、样式、批注等需要的对话框由 Ely 实现;上游原生对话框不得泄露 | +| LOK-09 | 保存与导出只通过内核支持的过滤器;错误码不能被当成成功 | +| LOK-10 | 关闭文档释放内核对象、图块和临时文件;不终止其他应用的会话 | +| LOK-11 | 不执行宏和自动更新外部链接;打开后明确显示这些内容的处理状态 | +| LOK-12 | 维护功能到接口的映射表和三端测试,不以“LOK 本身支持”作为交付证据 | + +图块渲染不自动提供完整无障碍文本树。辅助技术需要的文档文本、光标和选择信息必须单独接入。无法完成某项必需交互时属于发布阻断问题,不以只读截图代替编辑。 + +## 8. ElyDoc + +### 8.1 定位与布局 + +用于合同、报告、方案、说明书和信函。默认采用分页视图。左侧放置大纲、搜索与页面导航,中央显示文档,右侧放置文字、段落、页面、对象或审阅属性。 + +新建默认格式为 DOCX,用户可以改为 ODT。已有文件保留原格式。格式转换必须由用户触发。 + +### 8.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| DOC-01 | 新建与模板 | 提供空白、报告和信函模板;支持从本地模板新建,不修改模板原件 | +| DOC-02 | 文字编辑 | 支持输入、选择、复制粘贴、撤销重做、字体、字号、粗斜体、上下标、颜色和高亮 | +| DOC-03 | 段落 | 支持对齐、缩进、行距、段前段后、制表位、项目符号与多级编号 | +| DOC-04 | 样式 | 支持应用、创建和修改字符及段落样式;标题样式驱动大纲和目录 | +| DOC-05 | 页面与分节 | 支持纸张、方向、页边距、分页符、分节、分栏;分页由内核负责 | +| DOC-06 | 页眉页脚 | 支持页码、首页不同、奇偶页不同;分节关系可查看和修改 | +| DOC-07 | 表格 | 支持插入、行列调整、合并拆分、边框底色、重复标题行与跨页控制 | +| DOC-08 | 图片与对象 | 支持插入、替换、裁剪、缩放、锚点和文字环绕;保留对象顺序 | +| DOC-09 | 引用结构 | 支持目录、脚注、尾注、书签、超链接、题注和交叉引用 | +| DOC-10 | 公式 | 使用内核已有公式能力插入与修改;导出后保留可编辑表示或说明格式限制 | +| DOC-11 | 审阅 | 支持批注、回复、修订记录、接受或拒绝修改;批注可定位到内容 | +| DOC-12 | 查找与检查 | 支持查找替换、大小写选项、字数统计;提供随包词典支持的离线拼写检查 | +| DOC-13 | 文档比较 | 对两个本地版本生成差异或审阅结果;保留两个源文件,不自动合并 | +| DOC-14 | 邮件合并文档 | 从 CSV 选择字段并预览,生成独立文档或 PDF;不自动发送邮件 | +| DOC-15 | 打印与导出 | 支持打印预览、页面范围、DOCX/ODT 保存与 PDF 导出;批注和修订输出方式可选择 | +| DOC-16 | 长文档导航 | 大纲、页码、搜索和书签能跳转;跳转后可回到原位置 | + +### 8.3 关键流程与限制 + +打开合同后先检查密码、字体和可识别的兼容风险。用户修改条款、添加批注后保存。另用目标 Office 应用打开保存结果,检查内容、编号、表格、修订和页码。截图相近但修订丢失,仍判失败。 + +不执行 VBA、嵌入式程序或 ActiveX。对不能保证往返保存的宏文件、受保护对象和专有嵌入内容,打开时进入只读或风险状态。转换为 DOCX/ODT 时另存新文件,不覆盖原件。 + +## 9. ElyTable + +### 9.1 定位与布局 + +用于清单、预算、数据整理、公式计算、图表和汇总分析。上方提供名称框与公式栏,中间是内核表格视图,下方是工作表标签和选择统计。属性面板按单元格、图表或数据区域变化。 + +新建默认格式为 XLSX。公式、单元格值、工作表和依赖关系只由 LOK 维护。组件库中的表格逻辑不作为第二个计算内核。 + +### 9.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| TABLE-01 | 工作簿 | 新建、保存、重命名、复制、移动、隐藏和删除工作表;删除前说明引用影响 | +| TABLE-02 | 区域编辑 | 支持单元格和区域选择、插入删除、行列尺寸、合并、冻结、隐藏和填充柄 | +| TABLE-03 | 公式输入 | 支持公式栏、引用选取、函数提示、错误定位及相对、绝对、混合和跨表引用 | +| TABLE-04 | 公式计算 | 覆盖算术、逻辑、条件统计、文本、日期、查找和动态数组;使用 LOK 计算结果 | +| TABLE-05 | 计算控制 | 支持自动计算、手动重算、循环引用提示;加载和保存后保持所选计算规则 | +| TABLE-06 | 名称与引用 | 支持命名区域、范围更新、引用跳转和引用错误提示 | +| TABLE-07 | 格式 | 支持数字、货币、百分比、日期、自定义格式、对齐、边框、填色和自动换行 | +| TABLE-08 | 数据整理 | 支持排序、筛选、去重、查找替换、分列和粘贴为值或公式 | +| TABLE-09 | 规则 | 支持数据验证、下拉选择、输入提示、无效数据提示和条件格式 | +| TABLE-10 | 汇总分析 | 支持数据透视表的创建、字段调整、聚合方式和刷新;显示源数据范围 | +| TABLE-11 | 图表 | 支持柱、线、条、散点和饼图;编辑数据范围、轴、图例和标题 | +| TABLE-12 | CSV/TSV | 导入前选择编码、分隔符、引号与列类型;保留账号、编号和前导零 | +| TABLE-13 | 导出 | 保存 XLSX/ODS;CSV/TSV 导出明确只含哪个工作表,并提示公式和样式损失 | +| TABLE-14 | 打印 | 支持打印区域、重复标题、分页、方向、缩放和 PDF 导出 | +| TABLE-15 | 保护 | 支持内核已实现的工作表保护、锁定单元格和编辑许可;不绕过密码 | +| TABLE-16 | 大数据浏览 | 只渲染可视区域;滚动不为所有空单元格创建 UI 对象 | + +公式验收至少包含 SUM、AVERAGE、COUNT、SUMIF/SUMIFS、COUNTIF/COUNTIFS、IF/IFS、IFERROR、ROUND、文本与日期函数、INDEX/MATCH、VLOOKUP/XLOOKUP、FILTER、SORT 和 UNIQUE。该清单是产品目标;必须确认所锁定 LOK 版本及文件过滤器能完成计算和往返保存,不能因函数名可输入就认定已支持。 + +### 9.3 数据正确性 + +日期系统、时区相关输入、区域小数点和分隔符应有明确处理规则。数值和显示格式分开比较。导入 CSV 不自动把长编号转换成科学计数法,也不把所有文本都作为公式执行。 + +不支持的函数显示错误或兼容提示,并保留原公式和可用的缓存值;不能用 0、空值或旧缓存冒充重算结果。外部数据链接离线时保留可识别的旧值,显示未刷新状态。 + +不纳入 Power Query、Power Pivot、Excel VBA、外部数据库连接管理和专有插件。包含这些内容的文件按兼容规则处理,不承诺原样编辑往返。 + +## 10. ElyShow + +### 10.1 定位与布局 + +用于创建、编辑和播放演示文稿。左侧显示幻灯片缩略图,中间显示编辑画布,右侧显示当前对象或页面属性,下方提供备注。新建默认格式为 PPTX。 + +### 10.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| SHOW-01 | 幻灯片管理 | 支持新建、复制、删除、重排、隐藏、章节管理和页面尺寸设置 | +| SHOW-02 | 模板与母版 | 支持主题、母版、布局与占位符;修改母版后更新其引用页面 | +| SHOW-03 | 文字 | 支持文本框、段落、列表、字体、颜色、尺寸和自动适配规则 | +| SHOW-04 | 图形 | 支持基本图形、连接线、填充、描边、旋转、透明度和阴影 | +| SHOW-05 | 图片 | 支持本地插入、替换、裁剪、缩放和图层顺序 | +| SHOW-06 | 对象操作 | 支持多选、对齐、分布、组合、锁定、吸附、参考线和对象列表 | +| SHOW-07 | 表格与图表 | 插入并修改表格和常用图表;数据与对象在保存后仍可编辑 | +| SHOW-08 | 备注与审阅 | 支持演讲备注、批注和定位;备注不出现在观众屏上 | +| SHOW-09 | 动画与切换 | 支持淡入淡出等基础动画、顺序、持续时间和页面切换;提供预览 | +| SHOW-10 | 媒体 | 插入并播放本地音视频;丢失链接和不支持的编码有提示 | +| SHOW-11 | 放映 | 支持全屏、从当前页播放、跳页、黑屏、激光指示和退出 | +| SHOW-12 | 多屏 | 支持选择观众屏和演讲者视图,显示当前页、下一页、备注与计时 | +| SHOW-13 | 输出 | 支持 PPTX/ODP、PDF、页面图片和讲义打印;导出范围可选择 | +| SHOW-14 | 演示准备 | 检查缺失字体、缺失媒体、外部链接和不支持对象,并给出文件位置 | + +### 10.3 放映与兼容 + +放映使用内核能力及必要的平台媒体支持,不把页面截图轮播当成完整放映。缩略图与动画预览不阻塞编辑。 + +基础动画、媒体播放和演讲者视图必须分别验证 LOK 接口与平台实现。上游桌面应用具备某功能,不表示 LOK 接入已经完成。未完成这些功能不得以隐藏按钮方式通过首版验收。 + +首版媒体验收覆盖本地 MP4 的 H.264/AAC 组合与常用音频文件。发行前检查解码依赖及许可;媒体不能播放时保留文件和对象,显示原因。嵌入媒体随文档保存;外部链接媒体按链接处理,不伪装成已嵌入。 + +不承诺任意 PowerPoint 高级特效和专有对象的等效行为。不能编辑的对象采用兼容提示,不能静默栅格化后继续宣称可编辑。 + +## 11. ElyPDF + +### 11.1 定位与内核 + +提供 PDF 阅读、对象编辑、批注、表单、页面组织、OCR、内容删除和签名相关操作。MuPDF 通过 C 接口接入 Rust;不为使用 MuPDF 强制引入 Python。MuPDF 是底层库,编辑器交互由 Ely 实现。[S6][S7] + +左侧提供缩略图、书签和附件,中央显示页面,右侧显示编辑、批注或表单属性。所有模式使用同一套选择、撤销、保存和状态规则。 + +### 11.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| PDF-01 | 阅读 | 支持连续、单页、双页、缩放、旋转显示、书签、文本选择与全文搜索 | +| PDF-02 | 页面组织 | 支持插入、删除、重排、旋转、裁剪、拆分、合并和提取;修改后更新页码与引用 | +| PDF-03 | 文字编辑 | 支持编辑可识别的现有文字对象、插入文字、调整字体字号颜色和文本框;不通过白块遮盖冒充编辑 | +| PDF-04 | 图像与对象 | 支持插入、替换、移动、缩放和删除图片及受支持对象 | +| PDF-05 | 批注 | 支持高亮、下划线、删除线、便签、形状、手绘、印章与批注列表 | +| PDF-06 | 表单 | 创建和编辑基础 AcroForm 字段;支持文本、复选、单选、选择框和填写后保存 | +| PDF-07 | 文档结构 | 支持书签、链接、元数据和附件的查看与管理 | +| PDF-08 | OCR | 对扫描页进行本地中英文识别,生成可搜索文字层;可选页面范围、语言和覆盖策略 | +| PDF-09 | 内容删除 | 标记区域或文本,预览后执行真正删除;输出文件不保留被删除对象与旧修订内容 | +| PDF-10 | 文档清理 | 提供清除元数据、批注、附件和隐藏内容的明确选项;显示处理范围 | +| PDF-11 | 密码 | 在用户提供授权密码后打开;支持输出加密文件和权限设置;不破解未知密码 | +| PDF-12 | 签名 | 插入可视签名;使用用户提供的本地证书创建数字签名;显示已签文件的验证状态 | +| PDF-13 | 输出 | 保存 PDF、导出页面图片与文本、打印;可检查压缩和图像降采样设置 | +| PDF-14 | 文件恢复 | 支持修复后打开的副本和错误报告,不覆盖损坏原件 | + +OCR 复用 Tesseract API 和现成语言数据。识别结果是新增的文字层,不把扫描图片自动宣称为可无损重排的段落。[S21] + +### 11.3 文字编辑边界 + +PDF 编辑以现有页面与对象为单位。支持文本框内修改和必要的局部重排,不承诺 Word 式跨页重排。无法识别为文字的路径或扫描内容,不显示“直接编辑原文字”。 + +字体缺失、字形不全或原字体不能用于新增内容时,提示用户选择可用字体。替换可能改变布局时提供预览。不得先删除原内容,再因新文字绘制失败留下空白。 + +### 11.4 内容删除与签名规则 + +真正删除包括应用删除操作、重新写出文件及清理不再引用的对象。敏感内容删除不得使用保留原始修订的增量保存方式。验收同时检查文本提取、对象内容和图片区域,不能只看页面上的黑色矩形。 + +内容删除默认输出新文件。明确告知用户:原件、旧备份、外部同步历史和已经发送的副本仍可能包含原内容。清理 Ely 自己生成的临时副本时说明范围;不声称完成了存储介质级安全擦除。 + +可视签名不等于数字签名。离线验证须分别显示签名完整性、本地证书信任情况以及吊销状态是否核验。无法联网检查时显示“未核验吊销状态”,不显示笼统的“全部有效”。 + +修改已签文件前说明对签名的影响,默认另存未签修改副本。创建新的数字签名后不得再用普通自动保存改写文件。 + +XFA 动态表单、任意嵌入脚本执行、专业印前分色及 PDF 到 Office 的无损逆向转换不在范围内。 + +## 12. ElyMail + +### 12.1 定位与后端 + +提供多账号邮件客户端,不建设邮件托管服务。MailCore 2 负责 IMAP、POP、SMTP 和邮件解析等能力。Ely 负责账号流程、本地存储、搜索、同步队列和界面。MailCore 2 官方列出 C++ API 与三端支持,但发行前仍须验证实际构建和依赖。[S10] + +默认布局为账号与文件夹、邮件列表、阅读区。写信可在独立窗口打开。HTML 正文和富文本撰写区使用本地内容宿主,不能把正文 HTML 解析当成完整页面渲染。 + +### 12.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| MAIL-01 | 账号 | 支持多个 IMAP/SMTP 账号、POP3 账号、服务器配置、TLS 与连通性检查 | +| MAIL-02 | 授权 | 提供 Gmail 与 Microsoft 邮箱的 OAuth 流程;支持服务商允许的应用密码或自定义认证配置 | +| MAIL-03 | 邮箱视图 | 支持统一收件箱、账号独立视图、文件夹、未读、标记和附件筛选 | +| MAIL-04 | 阅读 | 支持纯文本、HTML、内嵌图片、附件、原始邮件头与会话线程 | +| MAIL-05 | 写信 | 支持收件人、抄送、密送、回复、回复全部、转发、签名、附件和纯文本/富文本撰写 | +| MAIL-06 | 草稿 | 编辑中保存本地草稿;可选择同步到服务端草稿箱;恢复不依赖网络 | +| MAIL-07 | 发送 | 提供持久化发件队列、重试、取消待发送、附件大小提示与发送结果 | +| MAIL-08 | 组织 | 支持已读未读、标记、移动、归档、删除与恢复;映射到服务端实际能力 | +| MAIL-09 | 搜索 | 搜索本地已缓存的发件人、收件人、主题、正文、日期和附件文件名 | +| MAIL-10 | 附件 | 支持下载、离线保留、另存、预览和用 Ely 应用打开;显示缓存状态 | +| MAIL-11 | 规则 | 支持按发件人、收件人、主题设置本地过滤规则,执行移动、标记或归档 | +| MAIL-12 | 联系人 | 维护本地地址补全和联系人,支持 vCard 导入导出,不建设完整通讯录服务 | +| MAIL-13 | 迁移 | 支持 EML 查看与导出、MBOX 导入导出;导入数据不直接写入远程邮箱 | +| MAIL-14 | 同步控制 | 支持手动刷新、运行期间定时同步、暂停和按文件夹离线下载 | +| MAIL-15 | 通知 | 支持新邮件通知与静音;锁屏通知默认不显示正文 | + +Gmail 和 Microsoft 的 IMAP/SMTP OAuth 有各自的应用注册、权限与服务端条件。正式版内置登录必须完成适用的注册及审核。不得把用户自行申请开发者凭据作为普通账号接入的默认步骤,也不得承诺绕过组织禁用 IMAP/SMTP 的策略。[S11][S12] + +### 12.3 缓存与同步 + +首次连接默认下载邮件头及最近 90 天的正文。附件按需下载,用户可为文件夹启用完整离线保留。界面显示最后同步时间、下载范围、尚未缓存的内容和失败原因。搜索结果注明只覆盖本地缓存。 + +IMAP 消息身份包含账号、邮箱、UIDVALIDITY 与 UID,不能只用主题或 Message-ID 作为同步主键。线程结合邮件引用关系组织,不把同主题的所有邮件强行合成一组。 + +离线移动、删除和标记先进入本地待同步队列。服务端状态已变化时重新核对,不无限重试已不存在的消息。POP3 的“保留服务端副本”默认开启,不提供伪造的 IMAP 文件夹同步能力。 + +附件编辑先创建工作副本。修改附件不覆盖原始 MIME 邮件;用户需要发送修改结果时,将工作副本添加到新草稿。 + +### 12.4 发送状态 + +发件状态必须区分:草稿、排队、正在发送、服务器已接受、失败、结果未知和已取消。 + +SMTP 返回成功后才标记服务器已接受。这不等于收件人已收到或已阅读。服务器已接受但保存“已发送”副本失败时,只重试保存副本,不重发邮件。 + +传输结束后连接中断、无法确认服务器是否接受时,进入结果未知。保留 Message-ID 和发送记录,允许检查后处理,不自动重发造成重复邮件。 + +用户在离线状态点击发送,即授权该队列项在恢复连接后尝试发送;界面当时必须说明,并允许取消。应用重启后,对遗留的结果未知项不得自动再次发送。关闭应用时说明未发送队列是否仍会运行;没有后台进程时不声称会继续发送。 + +HTML 邮件默认阻止远程图片和跟踪资源,禁用脚本。用户可以对当前邮件或发件人授权加载。邮件内容无法调用编辑器的本地文件桥接接口。 + +完整日历、Exchange 本地部署专有协议、企业归档、S/MIME 与 PGP 密钥管理不在范围内。 + +## 13. ElyTree + +### 13.1 定位与后端 + +提供独立 Git 桌面客户端。使用 Git CLI 执行仓库操作,使用现有 Git、差异和树形组件呈现结果。不重写 Git 对象存储、索引和合并算法。 + +通过参数数组启动 Git,不拼接 shell 命令字符串。状态读取使用适合程序处理的格式,例如 `git status --porcelain=v2 -z`,不解析终端彩色输出。[S13] + +### 13.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| TREE-01 | 仓库 | 支持初始化、打开、克隆、最近仓库和多仓库切换;显示实际路径 | +| TREE-02 | 工作区 | 区分未跟踪、未暂存、已暂存、冲突和忽略文件;响应外部 Git 操作 | +| TREE-03 | 差异 | 支持行内/并排差异、空白选项、文件重命名和图片差异预览 | +| TREE-04 | 暂存 | 支持按文件、差异块及可验证的行选择暂存和取消暂存;提交前显示实际索引内容 | +| TREE-05 | 提交 | 支持提交、修改最近提交、提交信息、身份与已有 Git 签名设置 | +| TREE-06 | 历史 | 支持提交图、提交详情、文件历史、Blame 和提交搜索 | +| TREE-07 | 分支与标签 | 支持创建、切换、重命名、删除分支,以及创建和删除标签 | +| TREE-08 | 合并与变基 | 支持 merge、rebase、cherry-pick、revert,以及继续、中止和错误恢复 | +| TREE-09 | 冲突 | 显示 base、当前侧和另一侧,提供块级选择与手动编辑;保存后明确标记已解决 | +| TREE-10 | 暂存工作 | 支持 stash 创建、查看、应用、弹出和删除;应用失败保留原 stash | +| TREE-11 | 远程 | 支持远程地址管理、fetch、pull、push、上游分支和进度取消 | +| TREE-12 | 仓库结构 | 支持 worktree、submodule 的状态与基础管理;显示 LFS 指针及对象可用状态 | +| TREE-13 | 恢复与清理 | 提供 reflog 查看、恢复引用、丢弃改动和清理未跟踪文件;危险操作先预览 | +| TREE-14 | 外部工具 | 支持打开终端、文件管理器和指定编辑器;可在 ElyMark 打开 Markdown | + +### 13.3 操作规则 + +同一仓库的写操作串行执行。外部 Git 进程占用锁时等待或提示,不擅自删除锁文件。用户从终端切换分支后,应用必须重新读取工作区、索引和 HEAD。 + +强制推送默认采用带租约保护的方式,显示远端、分支及待覆盖影响。普通按钮不得隐含硬重置。丢弃工作区文件、清理未跟踪文件和删除分支的确认不可复用为以后所有危险操作的永久授权。 + +合并与变基中的两侧名称按当前操作解释,不能固定把“ours”标为用户期望保留的一侧。冲突编辑结果与暂存动作分开。取消差异查看不改变仓库。 + +认证复用 Git credential helper、系统 SSH 和用户已有签名工具。密钥不写入应用数据库。终端交互、凭据提示和 hook 失败必须可见,不隐藏输出后只给出“操作失败”。 + +Git 与必要的 LFS 运行程序随 ElyTree 发行包提供,允许用户切换到已验证的系统版本。显示当前路径和版本。代理、凭据和签名遵守所选 Git 环境,不维护一套不可见的替代配置。 + +DOCX、XLSX、PPTX 和 PDF 默认按二进制文件展示变更。可提供预览,但不承诺文本级合并。ElyTree 不强制其他应用把文件放入 Git 仓库。 + +Git 托管、PR 审核平台、Issue 系统、CI 平台和远程开发环境不在范围内。 + +## 14. ElyMark + +### 14.1 定位与编辑框架 + +提供以本地 Markdown 文件为中心的编辑器。支持可视化编辑、源码编辑和只读预览。文件可由其他编辑器、Git 或 Agent 修改,不要求导入专有知识库。 + +默认使用 Milkdown 承担可视化编辑,CodeMirror 6 承担源码编辑。两者接入同一个文件会话,只允许当前模式提交修改,不同时维护两份独立可写文档。Milkdown 的 Markdown 与编辑模型转换需要单独处理源码保留,不能把序列化输出默认视为无损原文。[S14][S15][S16] + +参考 MarkText 的 Markdown 编辑行为、ColaMD 的本地文件同步和内容保留规则,不复制其整个应用窗口,不把 Electron 作为 Ely 的桌面外壳。当前 ColaMD 仍包含 Milkdown 与 CodeMirror 相关依赖,但具体实现必须按选定提交核对,不凭包名推断编辑行为。[S17][S18] + +### 14.2 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| MARK-01 | 本地文件 | 新建、打开、保存、重命名和另存;支持直接打开文件或文件夹 | +| MARK-02 | 编辑模式 | 可视化、源码和只读预览可切换;切换本身不修改文件内容 | +| MARK-03 | Markdown | 支持 CommonMark 与 GFM 常用语法:标题、列表、任务、引用、链接、图片、表格和代码块 | +| MARK-04 | 扩展语法 | 支持脚注、数学公式、Mermaid 与 YAML front matter;资源随包提供 | +| MARK-05 | 导航 | 提供文件树、文档大纲、多标签、最近文件和按标题跳转 | +| MARK-06 | 编辑工具 | 支持查找替换、源码正则搜索、缩进、列表续写、表格行列操作和代码高亮 | +| MARK-07 | 附件 | 支持拖放和粘贴图片,默认复制到文档旁的资源目录并写入相对路径 | +| MARK-08 | 外部修改 | 监视文件内容、删除、重命名和原子替换;未保存内容参与冲突处理 | +| MARK-09 | 书写视图 | 支持专注模式、打字机滚动、正文宽度和字体设置;不改变 Markdown 语义 | +| MARK-10 | 搜索 | 提供当前文件与用户打开文件夹内的搜索;不默认扫描整盘 | +| MARK-11 | 输出 | 导出自包含或带资源目录的 HTML、PDF 和纯文本;支持打印及富文本复制 | +| MARK-12 | 会话恢复 | 恢复打开标签、未保存内容、光标、滚动和视图设置 | +| MARK-13 | Git 配合 | 在仓库内显示文件是否有改动,并可调用 ElyTree 查看;不内置第二套 Git 客户端 | +| MARK-14 | 大文件 | 大文件采用延迟渲染或源码视图,说明限制,不截断保存内容 | + +数学与 Mermaid 默认仅渲染文档中已保存的源代码,不执行任意本地命令。语法错误显示原文与错误位置,不清空代码块。 + +### 14.3 原文与保存规则 + +未发生编辑时,打开、切换视图和保存不得改写原文件。保留 BOM 状态、换行风格、front matter 原文、代码块内容和不认识的扩展语法。 + +可视化编辑允许规范化实际修改的语法块,但不得借机重写整篇文档。框架无法保留的结构应显示为原文块,或提示在源码模式编辑,不能删除后重建。 + +模式切换前提交当前编辑事务,保留内容历史与当前修订。撤销只作用于本文件;不能撤销另一个标签的编辑,也不能把视图切换变成一次内容重写。 + +换行解释采用明确规则,默认遵循所选 CommonMark/GFM 语义。提供单换行显示为换行的阅读选项时,不自动在源文件插入 `
` 或额外空格。 + +### 14.4 外部变更 + +文件未修改时,外部写入触发重载,并尽量保持滚动和光标。文件有未保存内容时,不自动覆盖;提供磁盘版本、本地版本与共同基线的比较。 + +文件被外部删除后,仍保留内存内容并提示另存。原子保存导致的重命名事件不能被误判为永久删除。网络目录和监听失效时提供手动刷新,不声称所有文件系统都支持相同监听能力。 + +AI 或脚本修改文件与其他外部修改使用同一套处理规则。ElyMark 不需要依赖 ElyAgent 才能响应文件变化。 + +知识图谱、双向链接数据库、笔记同步服务、多人协同和插件市场不在范围内。 + +## 15. ElyAgent + +### 15.1 定位与独立性 + +提供独立的 Agent 配置和会话管理界面。用户选择已安装的 Agent、工作目录、模型配置和执行参数,查看输入输出、工具调用、文件改动和运行状态。 + +推理循环来自嵌入的 Kigi,不另写一套;不成为其他 Ely 应用的内置 AI 功能,也不自动读取邮件或办公文档。用户通过工作目录或选定文件向后端提供内容。 + +### 15.2 接入基线 + +后端以 Kigi CLI(https://github.com/ZacharyZhang-NY/Kigi-CLI,Rust)为基础,以 Rust 依赖直接嵌入,不用 ACP(用户 2026-10-05 定)。提供商登录(订阅账号或 API 密钥)、模型与端点配置沿用 Kigi 的实现,不另写一套。不解析终端装饰文本来伪造结构化事件。 + +会话恢复只在 Kigi 的会话存储支持并通过验证时启用。不能把本地历史回放当成后端会话已经恢复。 + +### 15.3 功能要求 + +| 编号 | 功能 | 完成要求 | +| --- | --- | --- | +| AGENT-01 | 工具管理 | 检测、添加和选择本地 Agent 路径,显示版本、启动参数与可用状态 | +| AGENT-02 | 配置管理 | 按后端支持配置提供商、端点、模型及参数;密钥引用系统凭据库 | +| AGENT-03 | 配置保护 | 修改已有工具配置前备份;只修改明确字段,保留未知字段和用户注释 | +| AGENT-04 | 会话 | 按工作目录分组,支持新建、命名、搜索、归档和删除本地记录 | +| AGENT-05 | 输入输出 | 支持流式输出、停止生成、复制、日志展开和发送失败重试 | +| AGENT-06 | 并行运行 | 支持多个本机会话并行;显示各自目录、后端、模型和状态 | +| AGENT-07 | 工具与授权 | 展示后端提供的工具调用和授权请求;用户决定允许或拒绝 | +| AGENT-08 | 文件与产物 | 展示后端声明的改动、差异和产物;可打开文件或交给 ElyTree 查看 | +| AGENT-09 | 会话恢复 | 后端支持时恢复会话;否则只能查看历史或新建,不提供虚假的恢复按钮 | +| AGENT-10 | 执行控制 | 支持取消请求、停止进程和清理已退出会话;强制终止前说明影响 | +| AGENT-11 | 历史 | 本地保存可用的消息、工具事件、时间、后端会话标识和错误;可导出文本或 JSON | +| AGENT-12 | 用量 | 仅显示后端返回的 token、费用或上下文信息;缺失时显示未知 | +| AGENT-13 | 本地模型 | 允许连接后端本身支持的本地兼容端点,显示连通性;不内置模型下载与推理引擎 | +| AGENT-14 | 托盘 | 提供菜单栏或托盘入口与运行会话数量;可关闭,不依赖托盘完成操作 | + +### 15.4 会话状态与生命周期 + +会话状态包括未启动、启动中、空闲、运行中、等待输入、等待授权、已完成、已取消、失败和连接中断。只有收到后端证据才能进入相应状态。未提供进度百分比时不估造百分比。 + +界面关闭窗口时,如有运行会话,提示继续在应用后台运行或停止后退出。继续运行指 ElyAgent 进程仍在,不额外安装系统级守护服务。系统无托盘时仍需保留可重新打开的应用入口。 + +明确选择退出应用时,说明哪些任务会终止并等待用户决定。崩溃或系统重启后只展示持久化历史及可验证的恢复选项,不承诺进程继续运行,也不自动重放可能改写文件的请求。 + +同一工作目录启动多个可写会话时提示可能发生冲突,提供使用独立目录或 worktree 的入口,但不强制建设自动工作树编排系统。 + +切换模型或提供商只按后端支持的机制执行。不宣称所有历史会话可以无损迁移到不同后端。不内置跨协议转换网关、账户轮换或额度规避功能。 + +独立 Agent 的安装、授权和模型供应商条件不应影响其他七个应用的本地功能。 + +## 16. 应用之间的连接 + +应用间只提供明确、可取消的文件操作,不建设跨应用事务系统。 + +| 入口 | 行为 | 边界 | +| --- | --- | --- | +| 邮件附件用 Ely 打开 | 将附件复制为工作副本,再交给对应应用 | 不修改原始邮件 | +| 文档导出后查看 | 在 ElyPDF 或系统默认 PDF 应用打开输出文件 | 不把导出文件当成原编辑会话 | +| Markdown 查看版本 | 向 ElyTree 传递仓库路径和文件路径 | 不自动提交、不自动创建仓库 | +| Agent 查看产物 | 按用户选择调用已安装的 Ely 应用 | 不静默打开敏感目录或执行文件 | +| 文件作为邮件附件 | 创建草稿并附加指定文件 | 不自动发送 | +| 应用切换 | 显示已安装应用并启动 | 不要求先打开统一启动器 | + +目标应用未安装时,提供系统默认应用或安装说明,不阻塞原工作。应用间不读取彼此的内部数据库。 + +## 17. 工程结构与接入边界 + +### 17.1 项目组织 + +使用一个 Rust workspace。各应用独立编译,共享 UI、平台适配和文件处理代码。内核依赖按应用引入,不因 workspace 共存而全部链接。 + +```text +ely-office/ + apps/ + ely-doc/ + ely-table/ + ely-show/ + ely-pdf/ + ely-mail/ + ely-tree/ + ely-mark/ + ely-agent/ + crates/ + ely-ui/ # 组件库的应用级组合与共用交互 + ely-platform/ # 文件对话框、打印、凭据、系统集成 + ely-files/ # 文件会话、保存、恢复、监听 + ely-lok/ # LOK 接入;仅 Doc/Table/Show 使用 + ely-pdf-engine/ # MuPDF 与 OCR 接入 + ely-mail-engine/ # 邮件协议、同步和本地存储 + ely-web-content/ # 本地 HTML 与 Markdown 内容宿主 + assets/ + tests/ + packaging/ +``` + +Ely GPUI Components 保持独立仓库依赖。ElyTree 的 Git 调用与 ElyAgent 的适配器先放在各自应用内;没有重复需求时不抽出通用框架。 + +### 17.2 进程与状态 + +GPUI 主进程负责界面和用户输入。LOK 和 MuPDF 放在本地工作进程中,避免解析或原生库故障直接终止整个界面。默认按应用实例启动所需工作进程,不预建全套进程池。 + +工作进程使用本地管道传递控制消息和结果。每条请求包含会话标识、请求标识及所需的修订信息。只在进程边界使用通信协议;进程内使用普通 Rust 类型和函数,不把所有组件都包装成消息服务。 + +文档会话、编辑状态和内核对象的责任必须明确。内核持有文档的可写状态,UI 保存可展示的状态,不双向同步两份完整文档模型。调用顺序和线程规则按内核要求执行。 + +C/C++ 异常、原生错误与资源释放在 FFI 边界处理,不能跨 Rust 调用栈传播未处理的原生错误。原生库版本和绑定一起锁定。 + +邮件同步、Git 命令、OCR、缩略图与索引构建在后台任务中执行,不阻塞 UI 事件循环。后台指应用运行期间的本地任务,不表示默认安装常驻系统服务。 + +### 17.3 本地 Web 内容宿主 + +Mail 的 HTML 正文和富文本撰写、Mark 的编辑与预览需要成熟 Web 内容渲染。本 PRD 的默认实现采用 CEF 离屏内容区域,只在这两个应用引入。CEF 提供离屏画面和输入事件接口,适合接入自绘界面,但 GPUI 的实际接入仍须三端验证。[S22] + +CEF 只处理内容区域,不绘制应用标题栏、菜单、文件树和设置页面。不引入 Electron 应用外壳,不运行面向浏览器的本地文档服务器。CEF 必需的辅助进程随应用发行。 + +Ely 主题 token 同步到内容区样式。键盘、焦点、选择、缩放、右键菜单和输入法进入统一适配规则。对话框和应用菜单由 GPUI 展示,不出现框架默认主题。 + +邮件阅读环境禁用脚本及文件桥接。Markdown 编辑环境只运行随包代码,通过受限桥接访问当前用户打开的文件与资源。外部文档内容不能调用任意本地命令。 + +不同时维护 CEF、系统 WebView 和全原生 Markdown 三种路线。若 CEF 原型不能满足三端要求,先修订该接入决策并更新 PRD,不能在应用内部临时混用多套未验收宿主。 + +### 17.4 不建设的技术设施 + +不引入 Kubernetes、Docker 运行依赖、远程数据库、消息中间件、事件溯源系统、通用插件市场、统一知识图谱、分布式搜索或独立账号中心。 + +应用间不做 RPC 服务发现。Agent 不以独立后台服务器为前提。Git 不同时接入多个 Git 内核。Markdown 不叠加第三个编辑内核。新抽象必须服务于已有的重复需求。 + +## 18. 数据、配置与隐私 + +### 18.1 存储边界 + +| 数据 | 存储位置与规则 | +| --- | --- | +| 办公文件、PDF、Markdown | 用户指定目录;不强制复制到应用资料库 | +| Git 数据 | 仓库自身的 Git 目录及工作区;ElyTree 只缓存视图状态 | +| 邮件与附件 | ElyMail 独立 SQLite 索引和本地内容目录;可导出和设置容量 | +| Agent 会话 | ElyAgent 独立本地记录;区分应用记录与后端自身记录 | +| 通用偏好 | 版本化的本地配置文件;仅保存主题、语言及共用交互设置 | +| 应用偏好 | 各应用独立保存窗口、最近文件、编辑和业务设置 | +| 恢复副本 | 各应用恢复目录;受时间和容量限制 | +| 密钥与令牌 | 系统凭据库;不写入普通配置、诊断日志或同步索引 | + +数据库不作为通用文档格式。全文索引是可重建数据,索引损坏不能导致邮件原文、文档或会话原始记录丢失。 + +配置修改采用原子写入与必要的文件锁。多个应用同时修改共用偏好时合并已知字段,不互相覆盖其他字段。不为此启动常驻配置服务。 + +### 18.2 凭据与私密内容 + +Windows 使用系统凭据能力,macOS 使用 Keychain,Linux 使用可用的 Secret Service。系统凭据库不可用时,允许凭据只保留到本次退出,不能静默退回明文保存。 + +加密文档的密码只在需要期间保留。其恢复副本不得以明文落盘;使用成熟加密库和系统密钥保护。用户不能解锁恢复密钥时,提示无法恢复,不导出不受保护副本。 + +第三方模型、邮箱和 Git 服务的连接由用户配置或授权。文档编辑不向这些服务发送内容。打开文件不会自动产生 AI 请求。 + +诊断日志默认不写正文、附件、密钥和提示词。Agent 的本地会话历史是用户数据,不属于可自动上传的诊断日志。导出诊断包前显示内容范围,并允许移除路径和个人信息。 + +不内置行为广告和自动上传分析。崩溃报告由用户触发提交。更新检查与远程内容加载遵守离线及隐私设置。 + +### 18.3 数据管理 + +设置中提供缓存容量、清理、恢复副本管理、邮件导出和 Agent 历史导出。删除账号先说明会删除哪些本地缓存、草稿和凭据,不影响未授权删除的服务端数据。 + +数据库升级前创建备份。升级失败保留旧数据,并阻止用不兼容结构继续写入。应用降级不尝试猜测转换;允许恢复备份或只读导出。 + +## 19. 文件兼容与输出 + +### 19.1 格式范围 + +| 应用 | 正式编辑与保存范围 | 导入或受限范围 | 输出 | +| --- | --- | --- | --- | +| Doc | DOCX、ODT | DOC、RTF、TXT,以及可识别的模板;按兼容提示转换 | DOCX、ODT、PDF、文本 | +| Table | XLSX、ODS | XLS;CSV/TSV 按导入设置转换;宏文件只读或另存 | XLSX、ODS、CSV、TSV、PDF | +| Show | PPTX、ODP | PPT、模板及含专有对象的文件按兼容提示处理 | PPTX、ODP、PDF、PNG/JPEG | +| PDF | 本文定义的 PDF 对象、批注、表单和页面功能 | XFA、嵌入程序或无法编辑的对象只读或提示 | PDF、页面图片、文本 | +| Mail | 本地消息、草稿与服务端协议操作 | EML、MBOX、vCard | EML、MBOX、附件、vCard | +| Tree | 标准本地 Git 仓库 | 浅克隆、LFS、submodule 等显示其限制 | Git 原生提交与引用 | +| Mark | MD、Markdown 文本与本地资源 | 未支持语法保留原文;编码转换需确认 | Markdown、HTML、PDF、文本 | +| Agent | 已验证后端的配置与会话记录 | 外部会话按适配器能力读取,不作通用格式承诺 | 文本、JSON、后端支持的格式 | + +支持文件格式不等于支持该格式所有功能。发行说明必须提供版本化兼容清单,注明已验证的内容、限制和保存行为。 + +### 19.2 往返验证 + +每个办公测试文件依次执行:在 Ely 打开、编辑指定内容、保存、关闭、再次打开,再由独立目标应用打开。检查结构、内容、公式、修订、批注、对象可编辑性和输出画面。 + +不只与同一个内核比较。DOCX/XLSX/PPTX 应包含与 Microsoft Office 的交叉检查;测试机和目标 Office 版本写入报告。用于测试的第三方软件由团队合法取得,不要求终端用户安装。 + +字体一致的环境与字体缺失环境分别测试。字体缺失时出现分页差异不应被隐瞒,替换规则和缺失字体列表必须可见。产品不捆绑无再分发许可的微软字体。 + +数值计算使用独立预期结果。精确整数、日期、错误值和文本严格比较;浮点数按测试用例声明的容差比较,不设一个掩盖所有误差的全局容差。 + +已知高风险文件默认不自动覆盖。无法完整识别所有未知对象时,不宣称“已经检测全部兼容风险”。首次修改此类文件应提示另存副本。 + +### 19.3 打印与 PDF 输出 + +打印预览与实际输出使用同一文档修订、纸张、页边距和缩放设置。显示页数和范围。打印取消不改变文档内容。 + +Doc、Table、Show 的 PDF 输出由 LOK 完成,不要求安装 ElyPDF。ElyMark 的 PDF 输出由本地渲染与打印能力完成,不要求安装 LOK。ElyPDF 用于后续编辑和查看,而不是全套应用的强制导出服务。 + +## 20. 质量与性能要求 + +### 20.1 验收环境 + +基准使用 16 GB 内存、NVMe 存储、至少 6 个 CPU 核心或同档 Apple Silicon,以及满足所选图形后端要求的设备。三端记录具体 CPU、GPU、驱动、系统、字体、编译版本和样本哈希。 + +以下指标按发行构建、每项 30 次测试的 P95 统计。首次安装初始化与日常冷启动分开记录。网络、远程模型速度及打印机出纸时间不计入本地响应指标。指标是项目目标,调整需要在发布前更新文档和理由。 + +| 编号 | 项目 | 目标与样本 | +| --- | --- | --- | +| PERF-01 | 启动 | 无文件冷启动至窗口可操作不超过 3 秒;不预加载未使用内核 | +| PERF-02 | 交互 | 普通点击与按键反馈不超过 100 毫秒;长任务不连续阻塞主线程 100 毫秒以上 | +| PERF-03 | 文档打开 | 100 页、20 张图片、10 MB 内 DOCX,5 秒内显示首屏并接受操作 | +| PERF-04 | 表格打开 | 10 万非空单元格、1 万个简单公式的工作簿,5 秒内接受操作;重算另记录 | +| PERF-05 | PDF 浏览 | 本地 1000 页、200 MB 内 PDF,3 秒内显示第一页;不先渲染全部页面 | +| PERF-06 | 本地搜索 | 已完成索引的 10 万封邮件或 10 万提交仓库,常见查询首屏结果不超过 1 秒 | +| PERF-07 | Markdown | 1 MB 文档 2 秒内进入可编辑模式;超大文件允许进入明确的源码模式 | +| PERF-08 | 资源释放 | 连续打开关闭 20 次后,活动文档对象与句柄回到基线,内存无持续单向增长 | +| PERF-09 | 多会话 | 4 个 Agent 会话同时流式输出时,其他会话选择和日志滚动仍满足交互目标 | + +不提前宣传固定内存占用或比其他 Office 更快。发布报告列出单应用、内核子进程及内容宿主的总内存,不能只测 GPUI 主进程。 + +### 20.2 稳定性 + +保存失败、内核崩溃、强制结束、磁盘不足、目录只读、临时文件损坏、失去网络、OAuth 失效、外部文件替换和系统休眠必须有测试。 + +主要应用完成连续 8 小时编辑与切换测试。测试中不得出现源文件损坏、跨文档撤销、无提示覆盖、邮件重复发送或 Git 状态伪报。 + +不能取消的操作应明确说明所处步骤,并允许用户等待或安全关闭界面。不得为了满足“有取消按钮”而在破坏性写入中任意杀进程。 + +### 20.3 无障碍与国际化 + +键盘能完成打开、编辑、保存、搜索和主要工具操作。焦点框不依赖鼠标悬停。内容区和 GPUI 区域间的焦点切换可预测。正文及常规界面文字的对比度目标至少为 4.5:1。 + +覆盖简体中文与英文输入、混合字体、标点、组合输入和非 ASCII 路径。日期、数字与纸张设置使用显式区域配置。修改界面语言不得重解释已存储的单元格数值或文档日期。 + +不因内核渲染成图块就把整个文档对辅助技术隐藏。文档内容、表格单元格和编辑状态的可访问性属于正式验收。 + +## 21. 开源与许可证 + +### 21.1 自有代码默认许可 + +本 PRD 对新增应用代码采用 AGPL-3.0-or-later 作为发行默认,便于与 MuPDF 的开源使用路线保持一致。Ely GPUI Components 继续保留现有 MIT/Apache-2.0 许可,不因被应用使用而改写其许可证。[S1] + +第三方代码保持原许可证。不得把 LibreOffice、Git 或其他依赖的版权声明统一替换成 Ely 的声明。最终发行须按实际链接、进程调用、修改和打包方式审查,进程隔离不作为免除许可义务的依据。 + +### 21.2 第三方发行要求 + +| 依赖 | 发行要求 | +| --- | --- | +| MuPDF | 按锁定版本的 AGPL 条款提供对应源码、修改、集成和构建资料;不把公开 UI 仓库当成已完成合规 [S8][S9] | +| LibreOffice/LOK | 保留 MPL 2.0 及附带依赖的相应声明;发布对受约束文件的修改与所需资料 [S5] | +| MailCore 2 | 保留其许可、版权和免责条款,并检查其传递依赖 [S24] | +| Git | 作为独立程序分发并遵守其 GPLv2 等实际适用条款;提供所发行版本的源码取得方式 [S23] | +| Markdown 与内容宿主 | 保留 Milkdown、CodeMirror、CEF 及其依赖的通知;逐项检查实际发行包 [S14][S16][S22] | +| OCR、字体、词典和媒体组件 | 记录每个资源的来源、版本和再分发条件,不从系统目录随意拷贝受限资源 | + +MuPDF 的 AGPL 路线允许符合条件的开源集成;“项目开源”本身不足以证明整个发行包已经符合条件。是否收费与源码义务分开处理。[S8][S9] + +普通用户文档不因经过应用编辑而自动成为应用源码。不要在产品协议中要求用户公开文档或邮件。许可证审查涉及应用与依赖,不把用户内容列入发布源码包。[S9] + +### 21.3 发布资料 + +每个发行版本必须附带自有源码、锁定依赖清单、上游补丁、构建步骤、第三方通知和许可证文本。二进制下载页提供对应版本源码入口。不得只链接到会变化的上游默认分支。 + +公开构建说明必须足以让贡献者重建应用,不依赖未公开私有脚本。签名密钥和用户凭据不属于公开构建资料。 + +本节作为发布检查要求,不替代针对最终发行包的法律审查。 + +## 22. 测试与验收 + +### 22.1 测试资料 + +使用可再分发、自行制作或已获得测试授权的样本。真实客户资料须先取得授权并去除不应公开的信息,不放入公共仓库。 + +每个功能编号至少关联一个测试用例。每项用例记录输入样本、操作、预期结果、平台、内核版本和实际结果。测试未运行、失败与不适用分开记录。不能用“接口存在”替代操作验证。 + +UI 自动测试、内核测试和手工交互测试互相补充。三端的输入法、打印、屏幕阅读器、外接显示器和系统凭据流程需要真实环境检查。 + +### 22.2 跨应用验收用例 + +| 用例 | 覆盖范围 | 通过条件 | +| --- | --- | --- | +| QA-01 | 安装与离线启动 | 干净测试机断网安装;八个应用均可打开;没有登录、CDN 或外部文档服务器依赖 | +| QA-02 | 独立安装 | 只安装 Mark、Tree 或 Agent 时不安装或启动 LOK;只安装 Doc 时可独立导出 PDF | +| QA-03 | 统一界面 | 菜单、属性、错误、快捷键和主题符合第 5 节;没有上游产品界面泄露 | +| QA-04 | 输入与焦点 | 三端输入中文、选择文本、打开工具栏、提交组合输入和撤销,结果正确 | +| QA-05 | 多屏 | 缩放和屏幕变化后,选区、点击、浮层及演示画面不偏移 | +| QA-06 | 保存失败 | 断开目标盘、设置只读或写满磁盘,原文件保留,未保存状态和恢复内容正确 | +| QA-07 | 外部冲突 | 外部程序在本地未保存时覆盖文件,Ely 保留两份内容并要求处理 | +| QA-08 | 并发保存 | 在旧修订保存期间继续编辑,完成后仍显示新修订未保存 | +| QA-09 | 崩溃恢复 | 终止内核或应用,重新启动能定位恢复副本;不覆盖原件,不声称恢复不存在的历史 | +| QA-10 | Doc | 合同、长报告、跨页表格、目录和修订文件完成编辑、保存及独立应用往返验证 | +| QA-11 | Table | 函数、引用、日期、透视表、图表和 CSV 样本与预期一致;未知公式不显示伪结果 | +| QA-12 | Show | 母版、图表、备注、动画和本地媒体可编辑;多屏放映不暴露备注;PPTX 对象可继续编辑 | +| QA-13 | PDF 编辑 | 修改文字、图像、表单和页序后,在独立阅读器检查;失败不破坏原对象 | +| QA-14 | PDF 删除 | 应用删除后通过对象、文本和图像检查确认输出文件中无被删内容及旧修订残留 | +| QA-15 | PDF OCR 与签名 | 扫描件可本地搜索;签名显示完整性和信任边界;离线不虚报吊销核验 | +| QA-16 | Mail 离线 | 无网络可读取已缓存内容、搜索、写草稿和入队;未缓存附件不显示成已下载 | +| QA-17 | Mail 发送 | 模拟发送前失败、接受后断连和已发送副本失败,不重复发送,不误报最终状态 | +| QA-18 | Mail 同步 | 文件夹变更、UIDVALIDITY 变化、服务端删除和令牌失效后不丢本地草稿,不发生错误关联 | +| QA-19 | Git 结果 | GUI 操作后用独立 Git 命令检查索引、提交、引用和工作区,结果一致 | +| QA-20 | Git 异常 | 测试冲突、hook 失败、凭据失败、锁竞争、变基中断和取消;状态与实际仓库一致 | +| QA-21 | Mark 原文 | 无编辑保存逐字节一致;局部编辑不重写无关段落、front matter 和未知语法 | +| QA-22 | Mark 外部变更 | 测试原子替换、删除、重命名和并行编辑;不覆盖未保存内容,不截断导出 | +| QA-23 | Agent 能力 | 测试结构化和普通命令后端;不支持恢复、费用或权限事件时显示真实限制 | +| QA-24 | Agent 生命周期 | 并行会话、窗口关闭、停止、完整退出和重启后的状态与进程事实一致 | +| QA-25 | 隐私与断网 | 抓取测试机流量,未授权时无文档、正文、日志、CDN 或自动更新请求 | +| QA-26 | 升级与卸载 | 升级失败保留原版本和数据;卸载不删除用户目录;剩余应用继续启动 | +| QA-27 | 许可证 | 安装包、源码包、通知和构建资料相互对应,所打包资源均有来源与许可记录 | +| QA-28 | 无障碍与性能 | 完成第 20 节指标和键盘、辅助技术检查,报告记录全部子进程资源 | + +### 22.3 不得带入正式版的问题 + +以下任一问题阻止正式发布:源文件损坏;静默丢失受支持内容;覆盖未保存文件;跨标签撤销;公式伪结果;PDF 删除后仍可恢复敏感内容;无法确认的邮件被自动重发;Git 危险操作未说明;Agent 会话被伪报为可恢复;三端核心编辑不能完成;发行包缺少必要许可或对应源码。 + +其他问题必须写入已知问题说明,并且不破坏本文定义的必需功能。不能通过把必需功能改成隐藏、只读或占位来规避发布要求。 + +## 23. 必须验证的工程风险 + +这些检查属于实现和发布流程,不构成分阶段交付。当前文档未包含三端集成程序的实测结果。 + +| 检查项 | 需要提交的证据 | 不通过时的处理 | +| --- | --- | --- | +| LOK 三端嵌入 | Doc/Table/Show 的编辑、保存、中文输入与窗口操作录像及日志 | 修复适配或更新设计决策;不能只交付查看器 | +| LOK 高级功能 | 母版、放映、图表、修订、输入法、打印和无障碍的接口映射及用例 | 补齐桥接或维护必要上游补丁,不跳回上游原生 UI | +| MuPDF 编辑 | 字体、对象修改、表单、删除及保存的样本结果 | 修复对象编辑与输出;不得改为视觉覆盖冒充编辑 | +| MailCore 2 | 三端可重复构建、TLS、OAuth、同步和 MIME 测试 | 处理依赖及适配问题;替换后端必须显式修订选型 | +| 本地 Web 内容 | CEF 与 GPUI 的输入、焦点、缩放、弹层和辅助技术测试 | 接入未通过不能以独立 Electron 窗口代替 | +| Markdown 原文保留 | 无编辑保存、模式切换、局部修改和外部冲突的字节级差异 | 修复映射与保存;未知语法回到原文模式,不删除 | +| 邮箱服务注册 | 正式 OAuth 配置、适用审核状态与用户流程 | 不将未获许可的内置登录描述为可用 | +| Agent 后端差异 | 每个预设后端的版本、启动、控制、日志和恢复能力表 | 按真实能力显示,禁止模拟未提供的接口 | +| 打包与许可 | 三端依赖清单、源码对应关系、资源再分发检查 | 发行前解决,不以“整个项目开源”代替检查 | + +选择版本时记录源码提交、编译选项和补丁。升级 LOK、MuPDF、GPUI、CEF 或编辑框架后运行相关回归测试。不要跟随默认分支自动升级正式构建。 + +## 24. 交付物与完成定义 + +### 24.1 工程交付物 + +交付八个应用的三端发行包、对应源码、构建与打包脚本、依赖与许可证清单、格式兼容清单、测试样本和测试报告。帮助内容包括打开保存、恢复、离线边界、账号授权、仓库操作及 Agent 后端限制。 + +不为满足交付清单制造独立文档系统。构建说明、已知限制和测试报告可放在仓库现有文档目录。需求以本文件为准,代码中的功能状态不得与文档相反。 + +### 24.2 首个正式版完成定义 + +八个应用可以独立安装和运行。本文功能表均有实现与测试。三端核心流程通过。文件保存、计算、发送和 Git 操作的结果可检查。所有应用遵守同一套界面规范,用户不需要学习不同的保存和错误处理方式。 + +离线安装不缺资源。没有强制 Ely 账号、云服务、在线 Office 服务或跨应用 Agent 依赖。发行包与公开源码匹配,已知兼容限制写明。未验证事项不能写成已完成能力。 + +## 25. 资料来源 + +核对日期:2026-10-01。来源用于确认接口、项目结构和许可条件,不证明 Ely 已完成接入。产品功能、默认设置与性能目标由本文定义。正式实现应记录所采用版本的固定提交和许可证,而不是只引用默认分支。 + +[S1] Ely GPUI Components,README,组件库使用方式、主题及许可证。 +`https://github.com/ZacharyZhang-NY/Ely-GPUI-Components/blob/main/README.md` + +[S2] Ely GPUI Components,TASKS,组件分类与 host/prove 等状态定义。 +`https://github.com/ZacharyZhang-NY/Ely-GPUI-Components/blob/main/TASKS.md` + +[S3] LibreOfficeKit 官方文档,C/C++ 接口、分块渲染和输入回调。 +`https://docs.libreoffice.org/libreofficekit.html` + +[S4] LibreOfficeKit 官方头文件,实际接口及不稳定 API 条件。 +`https://raw.githubusercontent.com/LibreOffice/core/master/include/LibreOfficeKit/LibreOfficeKit.h` + +[S5] LibreOffice 官方许可证说明。 +`https://www.libreoffice.org/licenses/` + +[S6] MuPDF 官方 C Library 文档。 +`https://mupdf.readthedocs.io/en/latest/reference/c/index.html` + +[S7] MuPDF 官方能力说明。 +`https://mupdf.com/` + +[S8] MuPDF 官方许可证文档;本次读取页面版本为 1.28.5。 +`https://mupdf.readthedocs.io/en/1.28.5/license.html` + +[S9] GNU Affero General Public License 第 3 版原文。 +`https://www.gnu.org/licenses/agpl-3.0.en.html` + +[S10] MailCore 2 官方介绍,协议、API 与平台范围。 +`https://etpan.org/mailcore2.html` + +[S11] Google,Gmail IMAP/POP/SMTP OAuth 2.0 与权限要求。 +`https://developers.google.com/workspace/gmail/imap/xoauth2-protocol` + +[S12] Microsoft,IMAP/POP/SMTP OAuth 身份验证。 +`https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth` + +[S13] Git 官方文档,git-status 与 porcelain 输出格式。 +`https://git-scm.com/docs/git-status` + +[S14] Milkdown 官方仓库,框架结构与许可证。 +`https://github.com/Milkdown/milkdown` + +[S15] Milkdown 官方 Transformer 文档,编辑模型与 Markdown AST 转换。 +`https://milkdown.dev/docs/api/transformer` + +[S16] CodeMirror 官方开发入口与仓库迁移说明;应用依赖按实际发布包锁定。 +`https://github.com/codemirror/dev` + +[S17] MarkText 官方仓库,编辑模式、格式能力与许可证。 +`https://github.com/marktext/marktext` + +[S18] ColaMD 官方仓库及 package.json,本地文件行为、架构参考与依赖。 +`https://github.com/marswaveai/ColaMD` +`https://github.com/marswaveai/ColaMD/blob/main/package.json` + +[S19] Magpie 官方仓库,独立 Agent 工具定位参考。 +`https://github.com/yetone/magpie` + +[S20] Agent Client Protocol,Session Setup 与能力协商。 +`https://agentclientprotocol.com/protocol/v1/session-setup` + +[S21] Tesseract 官方 API 示例与本地 OCR 接入。 +`https://tesseract-ocr.github.io/tessdoc/APIExample.html` + +[S22] Chromium Embedded Framework 官方说明与离屏渲染接入。 +`https://github.com/chromiumembedded/cef` +`https://chromiumembedded.github.io/cef/general_usage.html` + +[S23] Git 官方 COPYING 文件。 +`https://raw.githubusercontent.com/git/git/master/COPYING` + +[S24] MailCore 2 官方 LICENSE 文件。 +`https://raw.githubusercontent.com/MailCore/mailcore2/master/LICENSE` diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..45b10ef --- /dev/null +++ b/agents.md @@ -0,0 +1,256 @@ +# Ely Office + +单一事实来源。技术栈或产品方向变更时,同步改本文件。需求以 `PRD.md` 为准。进度以 `tasks/TASKS.md` 为准。 + +## 产品 + +八个桌面应用:ElyDoc、ElyTable、ElyShow、ElyPDF、ElyMail、ElyTree、ElyMark、ElyAgent。离线优先,文件在用户目录。 + +## 技术栈 + +| 层 | 选型 | +| --- | --- | +| 界面 | Rust + GPUI + Ely-GPUI-Components | +| Doc / Table / Show | LibreOfficeKit,独立工作进程 | +| Show 媒体 | 各平台原生:macOS AVFoundation,Windows Media Foundation,Linux GStreamer(LOK 不放媒体) | +| PDF | MuPDF(C 接口),Tesseract OCR,独立工作进程 | +| Mail | MailCore 2 + SQLite;正文用 CEF 离屏内容区 | +| Tree | Git CLI,参数数组启动 | +| Mark | Milkdown + CodeMirror 6,CEF 离屏内容区 | +| Agent | Kigi CLI 的 Rust 代码直接嵌入(不用 ACP),沿用其提供商登录与模型配置 | + +## 目录 + +```text +apps/ 八个应用,各自一个二进制;ely-mark/web 是其网页源码(锁定版本,esbuild 构建) +crates/ ely-ui ely-platform ely-ipc(应用与引擎 worker 之间的帧);ely-files ely-lok ely-office(文档应用共用的外壳:标签、会话、保存、关闭询问、冲突、密码;引擎为 LOK 或 MuPDF)ely-pdf-engine(MuPDF worker 与客户端)ely-web-content(CEF 离屏页面,ElyMark 与 ElyMail 共用);ely-mail-engine 随其任务建 +reference/ 上游源码,只读,不属于本项目,不审查 +engines/ 下载的二进制与引擎构建产物,不入库,不审查;mupdf-src 是 reference/mupdf 加 patches/mupdf 的构建副本;content/<应用> 是网页内容的构建产物 +patches/ 对上游的补丁,随源码发行(libreoffice、mupdf) +packaging/ 三端打包脚本,apps.tsv 是八个应用的二进制名、名字、桌面分类与发布批次;icons/ 由图标母版渲染各端格式;libreoffice/ely.xcd 是内核的默认配置层 +assets/ icons/ 是八个应用的图标母版(SVG)与渲染出的 icns、png、ico,规则见其 README +dist/ 打包产物,不入库 +docs/ 工程风险与已知限制 +scripts/ fetch-references.sh 取上游 doctor.sh 查工具 review.sh 开分栏审查 build-*.sh 构建引擎 +tests/ scripts 与 engines 下是脚本测试;engines 缺二进制时报 SKIP;fixtures 是测试共用的样本(media/clip.mp4:1 秒 H.264/AAC;pdf/aes128.pdf:AES-128 加密,打开密码 user、所有者密码 owner;pdf/signer.p12 与 signer.der:自签名测试证书,密码 ely) +tasks/ TASKS.md 进度真相 +``` + +## 组件映射 + +依赖 `ely-gpui-component`(pin 69265b4,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。 + +| 需要 | 用 | +| --- | --- | +| 窗口外壳 | `shell::{TitleBar, TabBar, StatusBar, Toolbar, ToolbarGroup, WindowSwitcher, ZoomControl}` `layout::{AppShell, SplitPane, Sidebar, Dock}` | +| 命令与菜单 | `menus::{MenuBar, ContextMenu, DropdownMenu}` `navigation::{CommandPalette, QuickOpen}` | +| 导航 | `navigation::{EditorTabs, Tabs, Breadcrumb, TableOfContents}` `lists::{Tree, Outline, FileTree, SelectableList}` | +| 属性与表单 | `data_display::{PropertyGrid, DescriptionList}` `forms::{Select, FontPicker, ColorPicker, NumberInput, ScrubInput, Switch, SearchInput, TextInput}` `buttons::{Button, IconButton, ToggleButton, SegmentedControl}` | +| 对话框 | `overlays::{Dialog, ConfirmDialog, AlertDialog, PromptDialog, Popover}` | +| 反馈 | `feedback::{Toaster, ToastViewport, SaveState, SavingIndicator, SyncStatus, Alert, Banner, EmptyState, ErrorView}` `shell::{OfflineIndicator, CrashReporter, UpdateBanner, UpdateDialog}` | +| 设置 | `settings::{SettingsLayout, SettingsSection, SettingsRow, ThemeSelector, KeyboardShortcutsList, StorageSettings, PrivacySettings}` | +| Doc | `documents::{PrintPreview, TemplatePicker, WordCount, PageThumbnailList}` `editor::{FindWidget}` `collab::{CommentSidebar, Thread, Comment, Peer}` `lists::{List, ListItem}` | +| Table | `tables::{PivotTable, FilterBuilder, SortBuilder}` `misc::{CsvImporter, ExportDialog}` `editor::{CompletionMenu, SignatureHelp, FindWidget}` `forms::{CheckboxGroup, ColorPalette}` | +| Mail | `mail::{MailboxList, MailList, MailThreadView, MailReader, MailComposer, SignatureEditor, ScheduleSend}` | +| Tree | `git::{ChangesList, CommitInput, CommitList, lanes, DiffViewer, ConflictResolver, BlameView, BranchList, TagList, StashList, FileHistory}` | +| Mark | `editor::{CodeEditor, FindWidget}` `documents::{MarkdownRenderer, ZenMode}` | +| Agent | `agent::{ToolCallCard, PermissionPrompt, ToolApprovalDialog, AgentStepList, FileChangeCard, MultiFileDiffReview, ArtifactPanel, CostBreakdown}` `chat::{MessageList, PromptInput, StreamingMarkdown, ConversationList}` | +| 动效 | `motion::{duration, Flip, Reorder, Transition, Stagger, ProgressBar, Spinner, Skeleton}` | +| 主题 | `theme::{Theme, Mode}`;Omarchy 色板由 `ely-ui` 映射成组件库的调色板 | + +## 缺口 + +组件库没有的,在本项目内建: + +| 缺口 | 位置 | +| --- | --- | +| LOK 图块画布与输入桥 | `crates/ely-lok` | +| 富剪贴板:gpui 剪贴板只有文本与图片,无 HTML(富文本、表格) | `ely_platform::clipboard`,用 arboard(三端 HTML、PNG) | +| gpui 无障碍只有 `aria_value` 等,无文本选区与插入点:LOK 文档的光标与选区到不了辅助技术 | `LokView` 只桥接当前段落文本(`Role::MultilineTextInput`)。上游待办:gpui 补文本选区 AX | +| 选中对象的框与手柄:LOK 不画进图块 | `LokView` 按图形选区画框与 8 个手柄;按住手柄拖动经 `setGraphicSelection` 交内核(缩放;裁剪模式下裁剪) | +| 虚拟化 PDF 页面栈(`DocumentViewer` 一次铺全部页,不合 PERF-05) | `crates/ely-pdf-engine`、`apps/ely-pdf` | +| CEF 离屏内容视图 | `crates/ely-web-content` | +| CEF 页面的无障碍:离屏渲染的页面不进系统 AX 树(CEF 另给 AX 事件,GPUI 无从挂接) | 未桥接;页面文字在辅助技术里不可见。上游待办:gpui 可挂外部 AX 子树 | +| 组件库的大纲项(`lists::Outline`)与提示(`feedback::Alert`)无无障碍角色:屏幕阅读器读不到 | 上游待办;ElyMark 的搜索失败改为结果列表里的行。`feedback::Banner` 已修(fac20ab:危险与警告为 alert,其余为 status,名为其文字) | +| `documents::ZenMode` 把正文夹进 Sm 宽的列,与 ElyMark 的正文宽度设置冲突 | ElyMark 专注模式自己收起标签条、左栏与状态栏,正文宽度由页面设置 | +| CEF 页面的弹出层(`select` 下拉、日期框)在离屏模式另画一层 | 不画;Ely 的页面不用原生 `select`,用自绘菜单 | +| macOS 的 CEF 离屏输入不带 AppKit 编辑命令:Blink 只凭它们处理 Cmd+方向键与 Cmd+Backspace | `ely-web-content` 的 `edit.rs`:页面没处理这些键时照 AppKit 标准键位执行 | +| 表格公式栏与名称框(网格是 LOK 图块,`tables::Spreadsheet` 不用) | `apps/ely-table` | +| Doc/Table/Show 工具栏(`documents::FixedFormatToolbar` 绑 `TextInput`,不接 LOK) | 各 app,用 `shell::Toolbar` 组合 | +| CSV 导入的编码、分隔符、引号、列类型(`misc::CsvImporter` 只读逗号 CSV、映射到属主字段,文案写死英文) | `apps/ely-table` 的 `text.rs`,内核按选项导入 | +| 导出格式与说明(`misc::ExportDialog` 无 TSV,文案写死英文,不能附说明) | 各 app,用 `overlays::Dialog` 组合 | +| 幻灯片缩略图条(`documents::PageThumbnailList` 只收图片路径,无隐藏标记、右键菜单与重排) | `apps/ely-show` 的 `slides.rs`,内核按页绘制到内存图 | +| PDF 页面缩略图(同上,且一次铺全部页,不合 PERF-05) | `apps/ely-pdf` 的 `side.rs`,`uniform_list` 只画可见行,worker 按需渲染 | +| 演示的参考线:LOK 不把参考线画进图块,也无参考线接口 | 补丁 0027 读写参考线;`apps/ely-show` 的 `guides.rs` 画在画布上,拖动移动,拖出幻灯片即删除 | +| `A11yChecker` 的 `audit` 与对照表是私有的;Omarchy 色板要照同一套规则 | `ely-ui` 的 `omarchy/palette.rs` 抄了对照表。上游待办:公开 `audit` | +| 组件无 role/aria 语义:菜单、列表等控件在 AX 树仍不可见 | 上游已修核心控件(7380afb:Button、TextInput、Tabs、面板行、对话框等);其余上游待办。项目自绘控件已自带 | +| gpui 无 aria-disabled API(`AriaProperties` 无 disabled 字段) | 组件 7380afb 起以本地化 aria_description 表达;上游待办:gpui 补 API | +| 组件内部文案未接 i18n(面板族之外:欢迎页标题、菜单项、`SortBuilder` 与 `FilterBuilder` 等) | 面板族文案已接 i18n(7380afb,内建 en/zh-CN),`CommentSidebar` 与 `RelativeTime` 同(408f2b0);其余上游待办 | +| LOK 的 `.uno:StyleApply` 状态给界面名(`Default Paragraph Style`),样式列表给程序名(`Standard`),两者对不上 | ElyDoc 属性栏:列表外的当前样式显示为占位文字;更新样式用状态里的界面名。上游待办:状态带程序名 | + +## gpui 规则 + +取自组件库的 `AGENTS.md`、`src/lib.rs` 与 `examples/docs/hello.rs`,应用必须遵守。 + +- 启动:`gpui_platform::application().with_assets(Assets).run(..)`,闭包里先调 `ely_gpui_component::init(cx)`。没接 `Assets` 会 panic。 +- 首帧用 `Theme::set_mode_now`。之后 `Theme::set_mode` 渐变。 +- 根元素包进 `FocusScope`,调 `.root()`,聚焦它的句柄。 +- 渲染之外改了状态,调 `cx.notify()`。绘制中改的,再调 `window.request_animation_frame()`。不用 `request_animation_frame` 轮询布局。 +- 颜色、控件尺寸、圆角、阴影取自 `cx.theme()`。间距用 gpui 的 rem 阶梯。边框用 `border_1`。 +- 盖在页面上的东西走 `primitives::raise`。对话框走 `overlays::Dialog`。每个窗口一个 `Toaster`,一个 `ToastViewport`。 +- 可按压的元素都是 Tab 停靠点:`tab_index(0)` 加 `focus_ring`。`hover()` 需要元素 id。 +- 动效时长取 `motion::duration()`,它遵守减少动效。重复动画在减少动效下静止。动画只包住移动的部分:动画 id 传给子孙,换 id 会重建它们的焦点与拖动状态。 +- 文本框是 `forms::TextInput` 实体。文案走 `i18n::I18n`,缺键即失败。 +- 可复用组件内的 id 带上属主的 id。`uniform_list` 的行设 `w_full`。盖在内容上的层按需用 `block_mouse_except_scroll`。 + +## 规则 + +1. 界面优先用 Ely-GPUI-Components。通用交互回组件库,不在应用里复制。 +2. 出错就暴露。不吞错,不写兜底,不用默认值遮挡缺失数据。 +3. 修根因。不打补丁式小修。 +4. 关键路径有日志(`log`,与 gpui 一致)。日志不含正文、附件、密钥、提示词。 +5. 文件不超过 500 行。注释一行,十字以内,只在必要时写。 +6. 无 dead code,无 v1/v2 接口,无过期代码。 +7. 禁止大框套小框。禁止无用说明文字和小标题。 +8. 不过度设计。不加预留扩展,不加无第二个使用者的抽象。 +9. 动效只在交互时运行,每段不超过 220 毫秒,遵守系统“减少动效”。空闲时不重绘。目标设备含树莓派。 +10. 未验证的能力不写成已完成。 + +## 命令 + +```text +scripts/doctor.sh 工具链版本 +scripts/fetch-references.sh 取上游源码到 reference/ +scripts/fetch-tessdata.sh 取 OCR 语言数据到 engines/tessdata(钉提交,核对散列) +scripts/fetch-cef.sh 取 CEF minimal 包到 engines/cef(随 cef-rs 钉的版本,核对 SHA-1) +scripts/build-git.sh 构建钉住的 Git 到 engines/git(ElyTree 随包) +scripts/fetch-git-lfs.sh 取 git-lfs 放进 engines/git(核对散列) +scripts/review.sh ID 轮次 路径 审查一条任务 +scripts/apply-lo-patches.sh engines/lo-src 找出源码已到补丁序列第几个,补上其余(构建脚本调用) +tests/scripts/lo-patches.sh 补丁序列从中途接着打、再打一次为空操作 +tests/scripts/media-targets.sh 媒体播放接口在 Linux、Windows 目标可编译(缺目标时 SKIP) +cargo test -p ely-show --test compose 切换半程帧经 Metal 取像素(主线程,仅 macOS) +tests/scripts/*.sh *.py 脚本测试(缺工具时报 SKIP) +packaging/macos/bundle.sh ely-doc debug 出 dist/ElyDoc.app(带图标、文档类型与该应用的引擎) +packaging/macos/release.sh [批次] 该批各应用的 release 包与 dmg;ELY_SIGN_IDENTITY 加 ELY_NOTARY_PROFILE 即公证 +packaging/icons/masters.py 写出 assets/icons 的全部 SVG 母版(图标设计只改这里) +packaging/icons/build.sh 由 assets/icons 的母版渲染 icns、png、ico(只在 macOS) +tests/scripts/icons.sh 八个图标齐全、未过期,macOS 为 squircle,其余无底形 +tests/scripts/release-macos.sh dist 里首批六个 .app:以访达的方式打开文件(启动与运行中),引擎都取自包内,带齐许可,不含系统会登记的内嵌包,每个二进制要求的系统不高于应用所写;源码包齐全 +tests/scripts/hardened-macos.sh ELY_SIGN_IDENTITY 签名(hardened runtime)的 ElyMark 照样画出页面 +tests/engines/*.sh 引擎探测(LOK、MuPDF、libetpan) +tests/scripts/lokview.sh 真窗口里的 LokView:输入法、键入、点击、拖动,AX 树里的段落文本;ELY_CLIPBOARD_TEST=1 再测复制粘贴(会覆盖系统剪贴板) +tests/scripts/doc-shell.sh 真窗口 ElyDoc:空白标签进 AX 树,启动后 Cmd+N 打开新建对话框(需辅助功能权限) +tests/scripts/doc-props.sh 真窗口 ElyDoc 属性栏:段前段后连续键入、回车、失焦,读 AX 值与命令数(需辅助功能权限) +tests/scripts/theme.sh 真窗口 ElyTable 以深、浅两种外观启动,截窗口量网格亮度(需屏幕录制权限) +tests/scripts/lag.sh ely-doc 真窗口键入,从调试日志算输入落地、图块、按键到新图块的分位数(需辅助功能权限);ELY_LAG_LOG=路径 留下日志 +tests/scripts/pdf-idle.sh 真窗口 ElyPDF 无文件启动时无 worker 子进程 +scripts/build-mark-content.sh 按锁文件构建 ElyMark 页面到 engines/content/ely-mark(不跑安装脚本) +tests/scripts/mark-content.sh 真窗口:构建的页面离线渲染各包(可视、预览、源码) +tests/scripts/mark-session.sh 真窗口 ElyMark:不改保存逐字节相同,键入一字只改该块(需辅助功能权限) +tests/scripts/mark-modes.sh 真窗口 ElyMark:文档旁图片、三种模式往返、预览只读、跨模式撤销、改名、新建(需辅助功能与屏幕录制权限) +tests/scripts/mark-places.sh 真窗口 ElyMark:文件夹树、大纲跳转、文件夹搜索、最近列表(需辅助功能权限) +tests/scripts/mark-output.sh 真窗口 ElyMark:专注模式与外观持久、四种导出、打印、Git 状态与打开 ElyTree、大文件(需辅助功能权限与 pdftotext;ELY_CLIPBOARD_TEST=1 再测富文本复制) +tests/scripts/mark-outside.sh 真窗口 ElyMark:外部替换、改名、删除、冲突比较、自动保存、退出与崩溃后的恢复(需辅助功能权限) +tests/scripts/mark-tools.sh 真窗口 ElyMark:查找替换、正则、Tab、列表、表格、拖放与粘贴图片(需辅助功能权限;ELY_CLIPBOARD_TEST=1 再测粘贴) +tests/scripts/comments-rules.sh comments.py 对长注释与跨行注释报错 +tests/scripts/settings.sh 真窗口:ElyPDF 设置里选深色,ElyTree 即刻变深;作者名写入、不丢外观;ElyTree 拒绝非 Git 程序;设置文件读不出时提示且不覆盖;主窗口重开回到原处(需辅助功能与屏幕录制权限) +tests/scripts/privacy.sh 真窗口 ElyDoc:键入的文字不进日志;worker 的日志进应用日志;设置里的诊断先列出所含内容,导出的文件去掉家目录与姓名、不含正文(需辅助功能权限) +tests/scripts/panels.sh 真窗口 ElyDoc:左侧栏默认不宽于 230、拖动内侧边缘变宽、宽度存入设置、重启后照旧(需辅助功能权限) +tests/scripts/storage.sh 真窗口 ElyMark:设置里的存储列出各类大小,日志当场清空,缓存下次启动前清掉,恢复点数写入设置(需辅助功能权限) +tests/scripts/recovery.sh 真窗口 ElyDoc:恢复点、崩溃后副本、连续两次崩溃、只杀引擎后重新打开(需辅助功能权限) +tests/scripts/pdf-recovery.sh 真窗口 ElyPDF:加锁文件的封存恢复点、空闲引擎退出后重开时再问密码(需辅助功能权限) +tests/scripts/autosave.sh 真窗口 ElyDoc:自动保存开关、停手即写、外部改动不覆盖(需辅助功能权限) +tests/scripts/print.sh 真窗口 ElyDoc:打印用文档自己的纸张,取消不动文档(需辅助功能权限) +tests/scripts/types.sh packaging/types.tsv 的文件类型在 Linux 桌面项、Windows 安装程序与 macOS 文档类型里一致,从不设为默认 +tests/scripts/layout.sh 真窗口:六个应用在 640、800、1100 宽下无元素越出窗口左右,留截图(需辅助功能与屏幕录制权限) +tests/scripts/ooxml.py <用例> <文件> 不经 LibreOffice 读 DOCX、XLSX、PPTX 并按自算的预期核对(往返用例调用) +tests/scripts/webview.sh 真窗口 CEF 页面:输入法组字与提交、键入、焦点、主题变量、缩放、不可离开所在文件夹 +cargo check --workspace +cargo test --workspace +rm -rf target/debug/incremental 每次构建或测试后都删(用户定:增量缓存会占满磁盘) +cargo build -p ely-lok --bins LOK 单测前先建 worker;单测不会自动重建它 +cargo build -p ely-pdf-engine --bins ElyPDF 的 worker(链接 engines/mupdf-build,ELY_MUPDF_DIR 可改;运行时 ELY_PDF_WORKER 可指定) +cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely-lok --bins,LOK 取 ELY_LOK_DIR 或 engines/ 下的构建);ELY_LOG 设日志级别;ELY_SMOKE=1 开窗口、画一帧即退出,ELY_SMOKE=hold 开着不抢焦点;ELY_NO_DIALOG=1 启动失败不弹对话框 +``` + +## 决策 + +- 2026-10-01:上游版本以 `scripts/fetch-references.sh` 为准。LibreOffice 26.2.6(8221e31)、MuPDF 1.28.5(8ad45e9)、gpui 为 `gpui-pre` =0.3.8 与 `gpui-pre-platform` =0.3.8(crates.io,2026-10-07 起,随组件库 K01)、组件库 69265b4(2026-10-08 起)。 +- 2026-10-02:组件库 7380afb 起:对话框与面板的 Enter 提交改走字段的 key-down 动作(macOS IME 天然守卫,不再提前提交);面板族文案经组件 `i18n::text`(内建 en/zh-CN,应用目录可覆盖);核心控件带 role/aria;对话框带角色、名称与说明,Cancel/Close 与禁用说明本地化。同仓库 `~/Documents/GitHub/Ely-GPUI-Components` 修复并经 SSH 推送(本机 HTTPS keychain 令牌失效)。2026-10-04 组件库 b6cb94c:`NumberInput::on_commit` 只在 Enter、失焦、步进、拖动时触发,键入不触发;改文档的数值框用它,否则每个键入数字都成一条内核命令与一步撤销。同批带入上游的 Checkbox、Switch、Select 等 role/aria 与 RenderSurface。c0f17ee:`ToggleButton::disabled`(不可按、无 Tab 停靠、说明不可用,提示可写原因)。967d1e4:`ContextMenu::manual`(按属主编号的请求在某点打开,右键不开),供行在按下之后才到的菜单,如内核给的拼写建议。 +- 2026-10-06:审查者为探针复制的源码树须用自己的 `CARGO_TARGET_DIR`(`scripts/review.sh` 写明);写完结论前删掉这些副本与其构建目录(曾积到 /tmp 近 900 GB,磁盘写满、链接失败):曾编进本仓库 `target/`,ely-lok 里烘焙的源码路径指向已删的副本,单测找不到 worker。 +- 2026-10-03:每条任务由 `scripts/review.sh` 在 herdr 分栏里交给 codex(gpt-6.1-sol,effort high;本机 `codex` 别名已带 `--yolo`,重复传参会报错)审查,最多三轮。此前各轮用 gpt-6-astra xhigh。 +- 2026-10-01:macOS 的 LOK 用自建 LibreOffice 26.2.6.3 加 `patches/libreoffice/0001-lok-macos-tile-painting.patch`(已验证出图块)。2026-10-03 加 `0002-lok-dispatch-to-document-frame.patch`:`postUnoCommand` 原发往桌面的活动框架,另一文档关闭后命令落空或落到别的文档;改发本文档的框架。2026-10-04 加 `0003-lok-char-style-and-spacing-states.patch`:LOK 原不发当前字符样式与段前段后,属性栏无从同步;补发 `.uno:CharStyle`(界面名,无字符样式为空)与 `.uno:ULSpacing`(`上,下`,1/100 mm);混合选区的状态发 `{"commandName","dontcare":true}`,`ely_lok::MIXED` 表示,段落样式跨段不一致也算混合(原样报首段),字符样式带来的字体、字号、粗斜体等在选区内不一致也算混合(原样只看直接格式)。`.uno:ULSpacing` 缺的成员归零,故两值同发;属性栏记住已发未回显的两值状态(段距、页边距),连续编辑以它为底。同日加 `0004-lok-panel-states.patch`:补发页面尺寸、页面项(横向、版式、页码格式)、页边距、分栏、页眉页脚开关与版式、四种锚点与六种环绕(真假或禁用)、标题行重复;页面项缺的成员同样归零,故转向时页面项与尺寸都发。`0005-jsdialog-skip-disposed-dialogs.patch`:对话框守卫关闭对话框后,JSDialog 空闲任务仍为它生成整窗更新,读已释放窗口崩溃(实测崩溃报告),补丁让已释放的对话框不再生成消息。`0006-page-size-takes-the-page-item.patch`:`.uno:AttributePageSize` 也收页面项,转向是一次改动、一步撤销。`0007-lok-aqua-anyinput-without-peeking.patch`:worker 的请求经主 GCD 队列派发,而 Aqua 的 `AnyInput` 用 `nextEventMatchingMask` 窥探时会转动 run loop、在内核空闲任务中途执行请求(实测:表格删列与排版空闲任务交错,`SwLayIdle::DoIdleJob_` 段错误,约三成全量测试复现);补丁让 LOK 下的 `AnyInput` 改问客户端,worker 以 `registerAnyInputCallback` 报告是否有待处理请求,请求只在顶层让出时执行(补丁后连续十次全绿)。`0008-lok-toc-caption-formula-without-dialogs.patch`:目录、题注、公式在内核只有对话框入口;补丁给 `.uno:InsertMultiIndex` 加 `Title`(默认目录)、`.uno:InsertCaptionDialog` 加 `Category`(程序名)与 `Text`(照内核题注对话框的选项)、`.uno:InsertObjectStarMath` 加 `Formula`(文字转公式,选中公式时整体替换,一步撤销),并加 `getCommandValues(".uno:Formula")` 读选中公式。插入目录后光标落在目录内(内核原样)。`0009-lok-word-count-values.patch`:`getCommandValues(".uno:WordCount")` 给文档与选区的字数、字符数;内核的 `.uno:StateWordCount` 状态是英文串,只作重读的信号。构建加 `--with-myspell-dicts`,随包词典取自 LibreOffice 的 dictionaries 子模块(core 钉的 9925407,来源与许可见各目录 README 与 license),装在 `Resources/extensions`。构建后只留 `dict-en`(25 MB,英语拼写、断词、同义词):界面语言是英文与简体中文,中文无 Hunspell 词典;全部 65 个(473 MB)时 LOK 首启 5.4 秒、再启 2.3 秒,只留英文为 1.1 秒与 0.7 秒(PERF-01 要求 3 秒内可操作)。词典语种已由用户定:英文。`0010-lok-outline-values.patch`:`getCommandValues(".uno:Outline")` 给各标题的文字、级别、位置(twips)与是否当前(光标所在处按文档顺序之前最近的标题,`GetOutlinePos`;分栏时位置比不出先后),`.uno:Bookmarks` 的每项加位置(版面不显示的书签无位置);导航栏的跳转都按位置。`0011-lok-font-lists-present-when-a-member-is-used.patch`:LOK 把以 `;` 连写的字体列表整串当作字体名,列表中有字体已用上也报缺失(实测报出界面字体的回退列表);补丁让列表中任一成员用上即不算缺失,文档里写的列表全缺时照报。`0012-lok-calc-cursor-references-merge.patch`:Calc 在回调注册后补发单元格光标;`.uno:SheetReferences?sheet=N` 给引用某表的公式格数、命名区域数、第一个公式格,以及用 INDIRECT 的公式与名称数(这类引用运行时才知指向,删除说明里单独列出);合并带 `MoveContents` 时不弹对话框,并修上游“多个数据格的 API 合并什么也不做”。worker 以 `SAL_LOK_OPTIONS=sc_print_twips_msgs`(macOS 另加 `unipoll`)运行,Calc 的光标、选区与 `.uno:SheetGeometryData` 同为文档 twips;视图把可见区经 `setClientVisibleArea` 报给内核。`0013-lok-author-option.patch`:`lo_setOption("author", 名)` 设内核用户全名,新批注与修订以此署名。`0014-lok-calc-formula-entry-and-names.patch`:公式语法错误不弹询问、补全不切走编辑、提交清引用标记、名称的可撤销定义删除与列表判定、自动计算状态、xlsx/xls 的手动计算往返。`0015-aqua-no-wait-with-a-due-timer.patch`:Aqua 在直接超时待处理时不阻塞等原生事件(请求块里起的 0 毫秒定时器原会让调度器停到下一个请求)。`0005` 另加存活标记:发送消息的客户端关掉对话框后不再读已释放成员。worker 记下每个文档载入时的内核视图,每次请求先切到该视图(Calc 按当前视图读写,多工作簿时否则会错表)。切表经视图的有序输入队列。视图记录跳转所需的最远点,滚动与图块以内核文档尺寸和它的较大者为界(Calc 空白区随可见区扩展)。视图按内核报的冻结行列(`.uno:FreezePanesColumn/Row`)分格:冻结部分不随滚动,其余照滚,图块按格裁剪,命中与表头同一换算(`view/panes.rs`)。文档占用 Tab 与 Shift-Tab(在 LokView 键位上下文里优先于焦点切换):Writer 插制表符或跳表格,Calc 跳单元格。worker 崩溃时打印本机栈(`worker/fatal.rs`),客户端报出退出信号。官方 macOS 包的 `paintTile` 不出图,上游 master 的修复也不够。Skia 必须开。工作进程在主线程以 unipoll 模式运行,请求经主 GCD 队列派发,环境设 `SAL_FORCE_HIDPI_SCALING=1` 与 `SAL_SKIA=raster`。证据见 `docs/engineering-risks.md` R-01。 +- 2026-10-04:LOK 命令与对话框。界面只经 `ely_lok::commands::COMMANDS` 发 UNO 命令,worker 拒绝表外命令;内核弹出的对话框(`LOK_CALLBACK_WINDOW` created 或 JSDialog)在回调里即刻关闭并经 `LokError` 报给应用,需要的对话框由 Ely 自建。起因:Calc 的 Backspace 弹同步模态“删除内容”,曾卡死 worker。 +- 2026-10-04:ElyDoc 查找替换、拼写、比较、邮件合并(DOC-12 至 14)。查找经 `.uno:ExecuteSearch`:查全部计数并高亮,下一处按全部的矩形定当前序号;替换一处后重新计数,无当前项时“替换”先选下一处。拼写建议来自右键时内核给的上下文菜单,只取拼写部分,`ContextMenu::manual` 在插入点下打开。比较把当前文档存成工作副本、开成新标签、对它发 `.uno:CompareDocuments`,两个源文件都不动。邮件合并的字段在正文里写成 `«列名»`(同 Word 的显示,已定),预览与生成都是对模板副本整体替换;生成逐行写 DOCX 或 PDF 到所选文件夹,任一目标已存在即整体拒绝,从不发邮件。 +- 2026-10-04:ElyDoc 输出与导航(DOC-15、16)。导出 PDF 经 worker 的 `EXPORT_PDF` 请求,滤镜选项为 JSON(页码范围、批注印在页边、标出修订);打印先导出同一 PDF,macOS 用 PDFKit 的 `NSPrintOperation` 弹系统打印面板(`ely_platform::print`),其他平台报错,未实现。加密文档经 LOK 的 `DOCUMENT_PASSWORD` 回调询问密码,错的密码再问一次,取消即不打开。左栏导航默认打开(大纲、页面、书签),跳转前记下所在处,“返回”逐级回到原处;上一跳的输入未落到内核、或落下后光标尚未移动时,以其目标为所在处,连续跳转不丢位置;落下之后即按实际光标。当前标题取内核的标记,当前页按光标所在页。导出 PDF 与另存为的目标按文件身份(符号链接与硬链接)比对打开着的文档:导出遇之即拒绝,另存为遇本文件时只许原格式。缺字体时页面上方给出提示条。QA-10 与 Microsoft Office 互开未验证:本机无 Office。 +- 2026-10-04:ElyDoc 的标签、会话、打开、保存、另存为(按文件身份比对)、关闭询问、冲突、密码与标签条移入 `crates/ely-office`:应用实现 `Host`(格式表 `Kind`、每标签附加数据、内核回报钩子),共用逻辑以扩展 trait 提供(`use ely_office::prelude::*`)。ElyTable 用同一外壳。共用文案在 `office.*`。 +- 2026-10-04:ElyTable(C09.1)。新建为空白 XLSX(首个工作表名随界面语言),保存保留 xlsx/ods/xls/fods,另存为 XLSX(默认)或 ODS,xlsm/xltm 进风险模式。上方名称框与公式栏(回车分别发 `.uno:GoToCell` 与 `.uno:EnterString`,Esc 交回网格),中间行列表头按内核几何自绘(点选整行整列,拖边改尺寸),下方工作表标签(右键与“工作表”菜单:插入、重命名、复制、左右移、隐藏、显示、删除;删除前列出引用该表的公式数与命名区域数)与状态栏(保存状态、内核给的选区统计)。“单元格”菜单:插删行列与单元格、合并拆分(多个数据格时先问移入或保留隐藏)、行高列宽(厘米)与最适、隐藏显示行列、冻结。填充柄拖到目标格发 `.uno:AutoFill`。选区统计文字由内核给出,当前为英文(内核界面语言未随 Ely 切换),待处理。 +- 2026-10-04:ElyTable 公式、计算、名称(C09.2,TABLE-03 至 06)。公式语法为 Excel A1(用户定):`Sheet2!A1`、逗号分隔参数,由安装层 `packaging/libreoffice/ely.xcd` 设定;载入选项 `Language=en-US,DeviceFormFactor=desktop`,内核界面与数字区域都是 en-US(小数点、月/日/年输入)。单元格内编辑由内核完成:键入时内核给出匹配的函数(`editor::CompletionMenu` 显示在单元格下,上下键选,Tab 补全,经 `completeFunction` 入有序队列),参数提示(`editor::SignatureHelp`,取内核的用法文字),点选单元格插入引用并按内核颜色画框;内核只为点选的引用与再次编辑(F2)的已有公式画框,新键入的引用不画(同桌面版)。第一下 Esc 关提示,第二下取消(同内核)。语法错误的公式照原样输入,状态栏按界面语言解释当前格的错误(内核英文错误串换成界面文案,同统计);循环引用同此提示。“公式”菜单:自动计算开关(关时状态栏显示手动计算)、重新计算、全部重新计算、定义名称(名称、引用位置默认为所选区域的绝对引用、范围为工作簿或某表;名称先经内核判定)、名称列表(改引用、转到所命名的绝对区域、删除,失效引用标出;内核给出每个名称的区域,`.uno:SelectedRange` 给所选区域)、追踪错误、追踪引用与从属单元格、移去箭头。名称框可直接输入名称跳转,跳转后显示该名称。手动计算在 ods、xlsx、xls、fods 存后重开都保持。 +- 2026-10-04:ElyTable 格式、数据整理、规则(C09.3,TABLE-07 至 09)。补丁 `0016-lok-calc-formats-and-conditions.patch`(数字格式、条件格式、两处 sdi 参数)、`0017-lok-calc-filter-validation-columns.patch`(筛选、验证、分列)、`0018-lok-calc-reads-requests-search.patch`(读值、请求、查找全部):数字格式按代码设(`.uno:NumberFormat` 的 `FormatCode`),`.uno:NumberFormatCode` 读当前格代码与类别(常规按区域块首判定)或校验代码;标准筛选收 JSON 条件(`.uno:DataFilterStandardFilter` 的 `Query`,含按列取值),`.uno:AutoFilterValues` 照内核自己的筛选菜单给出条目、他列隐藏与本列勾选;数据验证收 JSON 规则(`.uno:Validation` 的 `Rule`),`.uno:Validation` 读当前格规则,`.uno:DataSelectValues` 给序列;条件格式收 JSON(单元格规则配内核单元格样式、色阶、数据条、按键删除,`cellsh.sdi` 的同名槽也加参数),`.uno:ConditionalFormats` 列出本表;分列收 `Options`(ScAsciiOptions 串);`.uno:SelectedRange` 另给所选区域内数据区的四角、首行文字与内核的标题判定。LOK 下内核不再弹自己的筛选菜单与序列下拉,改发 `LOK_CALLBACK_TOOLTIP`(`autofilter`、`datalist`,带列行),无效输入发 `validityerror`(标题、内容、样式、是否接受;接受与否取内核对话框的默认:信息接受,警告与停止拒绝),Ely 以 `ely_lok::Request` 接收后自建菜单与提示。查找全部按格报矩形(内核原把相邻命中并成一块,计数偏少)。查找替换移入 `ely-office`(`Finding`,ElyDoc 与 ElyTable 共用,`Host::search_leaves` 供 ElyDoc 记返回位置)。“粘贴为值/公式”只在系统剪贴板仍是本视图上次的复制时进行(内核自己的单元格剪贴板;Ely 复制的 HTML 末尾带一个进程内唯一的标记注释,以标记而不是文字识别,同样文字的外来复制不算);其他内容(含别的工作簿复制的单元格)明确拒绝并提示改用粘贴:内核无法从外来内容单取值或公式。排序最多三个关键字(内核限制)。右侧格式面板(数字、对齐、边框、填充)、“数据”菜单(排序、筛选、删除重复项、分列、查找替换、粘贴为值或公式、数据验证)、“格式”菜单(格式面板开关、条件格式)。组件库 `SortBuilder`、`FilterBuilder` 的文案未接 i18n,中文界面下仍为英文(上游待办)。 +- 2026-10-04:右侧属性栏宽取 `theme.sheet_size()`(380),左栏仍是 `sidebar_width`。`PropertyGrid` 名称列固定 120,240 宽时编辑器只剩约 76。颜色经色块弹出 `ColorPalette`,一次点击一条命令。 +- 2026-10-01:MailCore 2 的子模块 libetpan 过旧,改钉 libetpan master `4d47862`。 +- 2026-10-01:每个应用的全部数据在 `<本机数据目录>/ElyOffice//`,路径由 `ely_platform::data_dir` 给出。卸载只动这里,且先问。用户文档永不碰。 +- 2026-10-04(用户定):macOS 包标识前缀 `ely.zacharyzhang.office`(`packaging/macos/bundle.sh`);发布者 ZacharyZhang,网址 zacharyzhang.com(macOS `NSHumanReadableCopyright`,Windows 卸载项 `Publisher` 与 `URLInfoAbout`;Linux 桌面文件无此字段)。界面语言英文与简体中文,以后再加。拼写词典只随英文:LibreOffice 的词典库没有中文(中文不用 Hunspell 拼写)。内核界面语言固定英文,函数名照英文;ElyTable 把内核英文的选区统计(Sum、Average 等七个固定名)换成界面语言,数值照内核。批注与修订的作者取各用户的账户全名(无全名用登录名,与内核自身次序一致),由补丁 0013 的 `setOption("author")` 在 worker 启动时设定;作者设置页随设置任务接入。邮件合并字段写法 `«列名»` 由我定(用户交我决定)。 +- 2026-10-01:更新清单是 JSON:`{"version", "assets": {"-": {"url", "sha256"}}}`,只走 https(本机回环除外)。检查、下载、校验在 `ely_platform::update`,从不静默安装。包管理器安装的 Linux 版本不自更新。清单地址由设置给出,尚未配置。 +- 2026-10-05(用户定):ElyAgent 的后端与内置模型的登录配置以 Kigi CLI(https://github.com/ZacharyZhang-NY/Kigi-CLI,Rust)为基础,以 Rust 依赖直接嵌入,不用 ACP;沿用其提供商登录(订阅账号或 API 密钥)与模型配置。 +- 2026-10-05(用户定):八个应用都不许卡顿:键入、选择、滚动、文档渲染都要即时(GPUI 的 120 FPS 手感),树莓派 3 上也要流畅。输入延迟与帧时间是验收项,要实测;界面线程不等内核、不持 worker 锁做同步请求,空闲不做事。 +- 2026-10-05:LOK 的输入与事件路径(实测后定)。原样:视图的事件监听长轮询 worker(持 worker 锁最多 50 毫秒,worker 读线程也随之阻塞最多 250 毫秒),按键排在锁后,键入锁等待中位 290 毫秒、最长 9 秒;输入队列在界面执行器上逐条跳回界面线程,每键约一帧,连续键入积压到 1 至 2 秒。现在:worker 有新事件即主动推送(`tag::EVENTS`,推送线程在输出锁下取批,批次即发送时状态),客户端读线程把回复与事件分开,事件存入 `client::Feed`,视图以异步方式等它(`Feed::next`,有新批次时唤醒,不占线程、不碰 worker 锁;原先每个打开的文档阻塞一个后台线程最多一秒,文档多于核数时会饿死输入与图块;监听任务归视图所有,视图释放即停);每个视图一条后台通道(`view/lane.rs`),输入优先、图块其次,连续执行,界面线程只收落地结果;文档尺寸与页框只在内核报尺寸变化时在后台读;批次弄脏的已显示图块即刻重绘,不等下一帧;视图的 `Reported` 事件只在内核回报或输入落地时发出,滚动与图块到达不触发应用的观察钩子;编辑与光标移动后应用要读的内核列表(ElyDoc 导航、字数、审阅,ElyTable 工作表与几何、单元格规则、格式面板)经 `ely_lok::read_off` 在后台读,一次一个,结果按发起者核对。实测(macOS,`tests/scripts/lag.sh`):按打字速度键入时输入落地 0 至 1 毫秒,图块 1 至 3 毫秒,按键到新图块 ElyDoc 中位 19 毫秒、ElyTable 3 毫秒;连续快速键入(脚本一次送出近百字)时,各次运行在 3 至 225 毫秒的中位之间摆动:新文档首次键入时内核有一次 150 至 230 毫秒的冷启动(首块图块与拼写等初始化),同一批字排在它之后;原为中位 1 秒、p95 1.9 秒。树莓派 3 未实测(本机无设备)。界面线程不碰 worker:应用对内核的读取、保存与另存为的导出、导出 PDF、比较、邮件合并都经 `Tabs::read_in`(或 `ely_lok::read_off`)在后台执行,回到窗口时按发起的标签与读取时的状态核对;保存导出期间标签状态为“正在保存”,同一标签的后续保存排队到它结束;另存为在写入前按文件身份再查一次目标是否已被别的标签持有;导出期间又有编辑时,“保存后关闭”不关闭,回到关闭询问;关闭标签时的撤销历史也在后台读;邮件合并预览与比较在发起时就保留自己的标签号与工作目录;读取失败不立即重试,等下一次状态变化;载入时一并在后台读版面与撤销历史(`ely_lok::layout`,`LokView::open` 接收版面)。窗口关闭回调先不放行,在后台逐个读标签的撤销历史并关闭(有改动的照常询问),最后一个标签关闭后移除窗口;内核文档的关闭也在后台(2026-10-06 起,原先同步读历史、在界面线程持 worker 锁关闭)。 +- 2026-10-04(用户定):八个应用的内容区一律随系统深浅色:Doc 页面、Table 网格、Show 幻灯片、PDF 页面、邮件正文、Mark 编辑区、Agent 输出。自动颜色随之翻转,文件里写明的颜色照原样;打印与导出 PDF 仍为浅色。各引擎(LOK、MuPDF、CEF)接入应用主题,不留引擎默认色。LOK:`ely.xcd` 定义 `COLOR_SCHEME_ELY_DARK` 与 `COLOR_SCHEME_ELY_LIGHT`(文档底色、自动文字色、网格、链接等取 Ely 默认色板;内核自带的深色方案颜色留空、按应用外观取默认值,传方案名不改外观,所以不用它),`LokView` 在主题深浅变化时经有序队列发 `.uno:ChangeTheme`(不算编辑),页面底色深色取 `surface`、浅色取 `paper`;Omarchy 色板与这两套固定色可能略有差别。导出 PDF 实测仍为白底。图块作废时旧图仍显示,新图到达后替换,编辑与主题切换都不闪。 +- 2026-10-01:主题。`Appearance` 为系统、浅色、深色。系统档在有 Omarchy 时跟随 Omarchy(`ThemeSettings::omarchy_sync`)。读 Omarchy 的 `$HOME/.local/state/omarchy/current/theme/colors.toml`(Omarchy 写死此路径,不随 `XDG_STATE_HOME`),目录由 `ELY_OMARCHY_DIR` 可改。读法与回退链照 `reference/omarchy/bin/omarchy-theme-color`:松散引号、`colorN`、`purple`、`selection_background`、`theme_type`、`light.mode`、亮度和 382 比。缺的色相用 Ely 自己的色并警告。色板照组件库 `A11yChecker` 的规则收口:正文、状态色、代码色在各自的表面上 4.5:1,焦点与图表色在页面上 3:1;先拉近前景再拉到纯黑白,最后逐项核对,核对不过的主题整套忽略。切换时 Omarchy 先暂存 `next-theme` 再整体替换 `theme`,用文件监视跟随,不用它的钩子。新主题无 `colors.toml` 时退回 Ely 色板。`ELY_APPEARANCE`、`ELY_OMARCHY_SYNC`、`ELY_REDUCE_MOTION` 是覆盖开关,设置页接入前先用环境变量。 +- 2026-10-05:组件库 8658999(组件库重写历史后的 main,加本项目三个提交):属主不经 `Close` 移除的浮层(一起移除的嵌套浮层同样,回到最外层打开前之处)(对话框动作只清自身状态、异步校验通过后才关)照样把焦点交回打开前之处;原先焦点落在根上,键入无处可去,须先点一下网格。同批带入上游的 ColumnShell、输入法组字时光标重新闪烁、`Icon::from_path`、MIT 许可。旧 pin 2f02c82 已不在 main 上,只能按完整哈希取得。`Select::label` 与 `TextInput::label` 给控件无障碍名称而不绘制;对话框里描述列表旁的控件以此命名,开关用自身的 `label`。7186179 加 `NumberInput::label`(经内部文本框的 `TextInput::set_label`),同批带入上游的 rustfmt 修正。80c46b1:`TemplatePicker` 的列表是一个 Tab 停靠点,上下、Home、End 移动,回车使用,行带列表项语义,搜索提示、全部、使用、无匹配的文案进内建中英目录。7df3045:`SelectableList` 在 AX 树里是列表(`label` 命名),`ListItem` 是列表项(标题为名,带说明、选中、在可选列表中的序号与总数);原先行无角色,不在 AX 树里。af54877:`NumberInput::blank` 显示占位文字而非数字(混合选区),空着回车或失焦不提交,键入后照常。408f2b0:`CommentSidebar` 与 `RelativeTime` 的文案进内建中英目录;属主不处理“解决”时(无 `on_resolve`)不分未解决与已解决,一列显示全部。bcf0378:`CommentSidebar` 的批注列是列表,每条是 Tab 停靠点(列表项语义,名为作者与正文,带选中、序号与总数),回车或空格选中。71bfc93:`init_for_tests`(`test-support` 下)装主题与键位、不要资源,应用单测可渲染 Ely 组件。617a8d6:`FindWidget` 在查询框里按 Esc 即关闭(调 `on_close`)。2ce3368:`ContextMenu` 可定宿主大小。3d08744:`DescriptionList` 的值占满所在列,其中的文本框随之全宽;同批带入上游的图表、分页改动。a6b1bb0:`SignaturePad` 的“在此签名”与“清除”进内建中英目录;同批带入上游 P01 至 P15(各组件的越界与失效状态记日志而不 panic)。490b022:`ChangesList` 分列冲突、已暂存、改动、未跟踪、已忽略(各有其动作:冲突与已忽略只可打开),标题与提示进内建中英目录;同批带入上游 P16 至 P20。7bb53f4(ElyTree 用):`ChangesList` 行数可缺、按区报按下、当前行;`DiffViewer` 外算的段、块按钮、可选的变动行、逐行读给辅助技术;`CommitList`、`FileHistory`、`BlameView`、`ThreeWayMerge` 只画可见行,`CommitList` 近尾加载;`DescriptionList::text`;`SegmentedControl` 各段为单选;`BranchList`、`TagList` 可选中、只画属主处理的行内动作;`marked`、`conflicts`、`resolve` 按标记宽度(`MARKER` 为默认 7),a284c71 起只按 LF 断行;c5799cc:`StashList` 文案进内建中英目录,行可选(`selected`、`on_pick`),只画属主处理的行内动作;7fab072 至 3741d5d:分支、标签、储藏的行分两行(上行名称与行内动作,下行说明与行尾文字),窄时名称与说明先让;行内动作一律显示(原先只在悬停时显示,键盘停在其上时看不见),改动列表同;各 git 行带透明的 1 像素边框,焦点框才画得出(原先 `focus_ring` 无边框宽度,键盘焦点不可见);同批带入上游 P21 至 P29。4f8f3bd:`Changed::lines` 可缺(二进制文件、文件夹不计行数,不显示);37c5187 缩短其注释。19b0b29:`DiffViewer::from_stretches`(别处算好的差异,如 Git 自己的)、`hunk_action`(每块标题上一个按钮)、`selected` 与 `on_line`(改动行可按,带 Shift),`diff::mark_words` 给外来的行标出改动的词,“行内”“并排”“N 行未改动”进内建中英目录;30fea9e:有块按钮时各行同高,底色相接。edd2c12:`ChangesList` 每次按下带分区(`ChangeSection`),行是 Tab 停靠点(列表项语义),`selected` 标出当前文件。2e080b8:`SegmentedControl` 每段是单选按钮(名为其文字,带选中状态),原先不在 AX 树里。e226d79:差异查看器的行模型与配色移入 `git/rows.rs`(文件不超过 500 行);7b807a9 缩短其注释。51d2aea:`CommitList`、`FileHistory`、`BlameView` 只画视口内的行(`uniform_list`,填满所在的框;`CommitList` 收 `Rc` 的提交与 `lanes` 算好的图,`on_end` 在接近末尾时要下一页),各行是 Tab 停靠点(列表项语义,选中状态;Blame 每段首行可按,名为作者、时间与摘要),`FileHistory` 的行数可缺;`CommitInput` 的“提交到”“修改最近提交”进内建中英目录。e6220ad 与 3fc5a46:提交行窄时作者、提交号与时间先让,摘要至少 6rem。d9cbe99:`DescriptionList::text`(标签与文字值,行的无障碍名为“标签: 值”),提交行的无障碍名带上引用。fb7ee42 与 fcc45cc:`DiffViewer` 只读时每行也是标签(“行号 + 文字”),块头为标题,折叠行有名;`BlameView` 每行为“行号 文字”,段首按钮的名略去空的部分。3d0a3de 至 949d776:差异与内建目录的注释缩短。829ef68:`ThreeWayMerge` 只画视口内的行(填满所在的框;每行三侧各一行,冲突处一行带三个选择按钮,各行同高;不再附结果与统计,结果由属主显示),文案进内建中英目录,每行与冲突行在 AX 树里有文字;`merging::conflicts` 认 diff3 的 `|||||||` 段,`resolve` 取当前侧时不带基底段;`BranchList` 与 `TagList` 的行可选(`selected`、`on_pick`,Tab 停靠点、列表项语义)。63d4a45 至 922cd53:`BranchList` 只画属主处理的行内动作;`merging::marked` 按标记文字里 Git 的分组得出各段(diff3 段为基底);窄时行尾文字先让,行内动作不被挤出。 2ce3368:`ContextMenu` 实现 `Styled`,样式定其宿主大小,其中的虚拟列表才能撑满(原宿主无尺寸,`uniform_list` 高度为零)。 +- 2026-10-05:ElyTable 透视表与图表(C09.4,TABLE-10、11)。补丁 `0019-lok-calc-pivot-tables.patch` 与 `0020-lok-calc-charts.patch`:`.uno:DataPilotExec` 收 `Pivot`(JSON:源区域、行、列、筛选、值字段与函数 sum/count/average/max/min),光标在透视表上即原地修改,否则从源区域在新工作表创建;行、列、筛选里的空名表示内核的数据布局字段(多个值字段横排或竖排的位置),读出与写回都保留,已在表中的行、列、筛选字段保留各自的小计函数,读出的布局原样写回不改表;照内核布局对话框的步骤(`ConvertOrientation`,保留各字段的成员、排序与版式),新表的筛选按钮、钻取、展开收起都关(同对话框初值);放到新工作表时插表与透视表是两步撤销(同桌面版)。刷新 `.uno:RecalcPivotTable` 在内核里本就不进撤销(它改缓存)。`.uno:InsertObjectChart` 收 `Chart`(JSON:类型 column/bar/line/scatter/pie、标题、图例、X/Y 轴标题;`other` 保持原类型),LOK 下不开图表向导、插入为一步撤销、插入后图表选中;给出的区域每段都要解析得了、所指工作表都要存在,否则插入与修改都拒绝、文档不动(内核原会改用示例数据,修改时 `ChartModel::setArguments` 抛出未捕获的异常使 worker 退出);`.uno:ChangeChartData` 也收 `Chart`,区域与外观合成一步撤销(撤销保存整个图表的图示与主标题的副本,堆积等外观以外的设置一并恢复)(列表动作带当前视图号,否则 LOK 拒绝撤销别的视图的动作)。读取:`.uno:Chart`(选中图表的对象名、区域、表头、外观)、`.uno:PivotTable`(光标处透视表的名称、位置、源区域、字段与布局),`.uno:SelectedRange` 加 `data`(选区内数据的绝对区域)。界面在“插入”菜单:数据透视表、刷新数据透视表、图表;对话框在光标处有透视表、或有选中图表时为修改,否则以选中的数据(单格扩到数据区)新建,源与图表区域取 `data`,不含空行。为对话框发起的读取(`Tabs::ask_in`)回来时,发起的工作簿已不是当前标签即丢弃,不为隐藏的工作簿开对话框(ElyTable 全部对话框、合并询问)。没有数据时恢复原光标(`select.data` 会把单格标成区域,回车随之只在格内打转)。公式语法是 Excel A1,区域写 `Sheet1!$A$1:$C$5`。 +- 2026-10-05:ElyTable 文本文件、导出、打印、保护、大表(C09.5,TABLE-12 至 16,§9.3)。补丁 `0021-lok-calc-page-setup-protection.patch`:`.uno:PageFormatDialog` 收 `Page`(JSON:横向,百分比、宽高页数或总页数之一),一步撤销;`.uno:Protect` 收 `Protection`(JSON:带密码与八项许可保护,或凭密码取消,密码错不改动);`.uno:CellProtection` 带值时按值设锁定(原样只切换);读 `.uno:PrintLayout`(打印区域、重复行列、方向、缩放、共用页面样式的工作表、光标处手动分页)与 `.uno:SheetProtection`;修上游:缩放改动后 `InvalidateTextWidth` 按整表列数遍历未分配的列,越界崩溃。`0022-lok-calc-text-files-and-links.patch`:LOK 下 CSV 带选项载入不再弹导入对话框(原样照弹,模态挂死 worker);`saveAs` 的过滤器选项原样交给过滤器,只去掉 LOK 自己的关键字(原样丢空项、插空格,CSV 选项错位);读 `.uno:ExternalLinks`(外部引用、区域链接、表链接、DDE、WEBSERVICE 的来源)。LOK 载入选项可带 `FilterName`(原样只按扩展名猜,`.txt` 落进 Writer)。worker 的 CSV 导入与导出只收类型化字段(`IMPORT`、`EXPORT_TEXT`),选项串与 Calc 文本过滤器名在 worker 内生成,载入后不是表格即关闭并报错。CSV 规则由我定(用户未定):打开 csv、tsv、tab、txt 先出导入对话框,选编码(UTF-8、UTF-16、GB18030、Big5、Shift_JIS、EUC-KR、Windows-1252、Windows-1251、ISO-8859-1;有 BOM 按 BOM,合法 UTF-8 即 UTF-8,否则中文界面 GB18030、英文界面 Windows-1252)、分隔符(tsv、tab 为制表符,否则取首行最多的逗号、分号、制表符、竖线,都没有为逗号)、引号、逐列类型(常规、文本、三种日期顺序、跳过),预览与类型推断按 Calc 文本导入的规则拆字段(`impex.cxx`:引号前的空格跳过、引号后的文字并入、双写引号转义、空行算一行),后台读整个文件,严格解码(有字节读不通即提示并禁用打开,不以替换字符静默改写),预览前 8 行、每格 200 字,每列都有类型选择;打开时导入的就是检查过的那份字节(对话框期间源文件再变也不读);整个文件里出现前导零、加号开头、超过 15 位数字(可带负号)的列默认为文本;数字按 en-US 读,日期只在标为日期的列按所选顺序读,不识别特殊数字,`=` 开头的文字不作公式。导入得到未命名的新工作簿(标签为原文件名),保存走另存为 XLSX,原文件不动。导出(Cmd+E):PDF(全部工作表,按页面设置)、CSV、TSV;文本导出只写当前工作表的显示值(UTF-8,无 BOM),对话框写明导出哪张表、公式格式与其他表不保留;内核“只存当前表”的警告框由 `ely.xcd` 关掉。打印(Cmd+P)先导出同一 PDF 再交系统打印面板。“页面”菜单:页面设置(打印区域写作 A1:D20,多个用逗号;重复行 1:2;重复列 A:B;方向;缩放),插入、删除行列分页符(光标处没有即提示);页面样式由多张表共用时对话框写明作用于哪些表。“工作表保护…”在工作表菜单,“锁定单元格”“解锁单元格”在格式菜单。§9.3:`ely.xcd` 设 XLSX 与非 LibreOffice 生成的 ODF 载入时总是重算(原样信任文件缓存,实测 Excel 留下的 `=1+1` 过期缓存 7 与未知函数缓存 42 都当结果显示);不支持的函数显示 `#NAME?`,原公式保留(函数名存成小写),状态栏解释错误;其缓存值不保留,另存写成错误值(已知限制)。外部链接照内核“按需更新”不刷新,显示保存时的值,工作簿有链接时上方提示条列出来源(每次编辑后在后台重读)。NOW、TODAY 用系统时区;1904 日期系统随文件,未专门验证。视图只请求屏上的图块,离屏超过一屏的图块丢弃(原样只增不减,滚动长表内存随之涨);行列表头只画可见的。 +- 2026-10-05:ElyShow 应用壳、幻灯片管理、模板与母版(C10.1,SHOW-01、02)。补丁 `0023-lok-impress-slide-design.patch`:`.uno:PageSize` 改走页面对话框的 `SetPageSizeAndBorder`(原样走 `SetupPage`,不进撤销);`.uno:PresentationLayout` 收 `Master`(母版名,套到当前幻灯片,一步撤销;不存在即拒绝);`.uno:ThemeDialog` 收 `ColorSet`(内核配色名,套到当前母版,一步撤销);读 `.uno:SlideDesign`(母版列表、当前母版、是否在母版视图、当前版式、配色列表与当前配色)。内核参数类型:`SfxUInt32Item` 取 `long`(传 `unsigned long` 时参数被丢弃,命令仍跑出一步错误的撤销)。缩略图经 worker 的 `PAINT_PART`(`paintPartTile`,第二参是编辑模式:0 幻灯片,1 母版),按名缓存,编辑时只重画当前页,结构、尺寸、配色变动时全部重画,内核换深浅色方案后全部重画(首帧先于方案落地)。母版视图的进出内核不发重绘,按设计读出的编辑模式判定,进出后整屏作废重画(`LokView::invalidate`)。演示的画布按视口缩放整页并居中(`LokView::fit_page`)。新建:内核自己的空白演示(`private:factory/simpress`,16:9,标题版式)每次运行写一次到工作目录,再按副本打开;或内核随带的 Impress 模板(`template/common/presnt`)。版式取内核版式面板的 12 种;尺寸预设宽屏 16:9、16:9、4:3、A4、Letter,另可输入厘米。章节(用户定为外部阻塞):内核无幻灯片章节,PPTX 导入丢弃 `p14:sectionLst`,ODF 无此结构。补丁 `0024-lok-math-tiles-keep-the-painted-tile.patch`:图块收尾时公式对象助手以默认擦除重设设备尺寸,文字编辑中的 Impress 图块整块被擦(macOS 上为黑);改为不擦,同图表助手。离开文字编辑(内核收起插入点,`LokView::caret`)时重画当前页缩略图,编辑中画的缩略图带编辑框。`0025-lok-impress-paints-the-current-view.patch`:Impress 的 `GetViewShell` 原取文档外壳所连的视图,外壳栈重排(`ViewShellManager::MoveToTop`)时内核先断开再接回,其间到达的绘制得到空白图块(macOS 上为黑),按键丢失;LOK 下改取当前视图的主外壳。`0026-lok-impress-settles-panes-before-commands.patch`:Impress 的窗格重配排在用户事件里,先到的命令找不到幻灯片排序外壳,隐藏、显示等槽被丢弃;`ITiledRenderable::settleViews` 在 `postUnoCommand` 派发前执行,Impress 在此同步处理排队的配置。两者在八路并行下实测(各 96 次全绿,修前约四十次一败)。缩略图失效按撤销与重做列表的条目判定(`ely_lok::history::steps`):撤销列表有条目消失即撤销,重做列表的条目进了撤销列表即重做,撤销后立即编辑也能识别。 +- 2026-10-05:ElyShow 文字、图形、图片、对象操作(C10.2,SHOW-03 至 06)。补丁 `0027-lok-impress-objects-guides.patch`:读 `.uno:SlideObjects`(当前页各对象的种类、外框、占位符角色、锁定、是否选中,选区的种类、填充、轮廓、透明度、阴影、自动适配、外框、旋转与中心、锁定、能否组合与取消组合,网格与参考线吸附,参考线);`.uno:SelectObject` 收 `Index`、`Add`(按层次选中);`.uno:CapturePoint` 收 `Kind`、`X`、`Y` 添加参考线,收 `Index` 加坐标或 `Delete` 移动或删除(LOK 无参考线对话框);`.uno:TransformDialog` 收 `ProtectPos`、`ProtectSize`;`.uno:TextAutoFitToSize` 收 `Fit`(off、shrink、grow,对所有选中对象一步撤销,横向增长一律关,撤销连同文字恢复;原样是只对单个对象的开关);`.uno:ChangePicture` 收 `FileName` 原位替换;LOK 下插入图片放在幻灯片中央(原用窗口可见区中心,LOK 下无意义)。LOK 把 `TransformDialog` 的位置、尺寸与旋转中心当 twips 读入,读出是 1/100 mm,面板发送前换算;线宽与参考线不换算。插入菜单:文本框、矩形、椭圆、三角形、直线、箭头、连接线(内核直接在幻灯片中央创建)、图片;视图菜单:吸附到网格、吸附到参考线(勾选随内核)、添加水平或垂直参考线。右侧面板选中对象时为对象属性(位置、大小、旋转、锁定;填充、透明度、阴影、轮廓与线宽;文字的字体、字号、样式、颜色、对齐、列表、自动适配;图片的替换与裁剪;层次、对齐、分布、组合),否则为设计;两者下方是对象列表(可多选,按层次选中)。数值框在回车与随后的失焦各提交一次时只发一次:按字段记住已发值与发出时读到的值,到下一次读取(按标签与读取次数)为止;混合值的数值框为空并显示“混合”(`NumberInput::blank`),空着回车或失焦不发送,不作去重的依据。色块的清除按钮发各自的命令(无填充、无轮廓)。直线、箭头、连接线与图片只显示轮廓与线宽。对象列表取消全部选中即清除内核选区(`.uno:SelectObject` 只带 `Add` 时不选任何对象)。补丁按序列打:`scripts/apply-lo-patches.sh` 找出源码已等于“原版加前 N 个补丁”的 N,再打其余;原先逐个反向检测,补丁重叠后重跑构建失败。面板与菜单的命令不经视图输入,缩略图改按撤销列表的变化判定当前页作废。ElyDoc 属性栏的内核控件(开关、按钮、色块、字体、字号)移入 `ely_office::panel`,ElyShow 共用。连接线接到图形靠在画布上拖动端点,未单独验证粘附。 +- 2026-10-05(用户定):ElyShow 的音视频由各平台原生播放:macOS AVFoundation,Windows Media Foundation,Linux GStreamer(同 LibreOffice 自己的 avmedia 后端),H.264/AAC 解码与许可随系统;Ely 一个接口,三份实现。LOK 不放媒体。 +- 2026-10-05:ElyShow 动画、切换、媒体(C10.4,SHOW-09、10)。补丁 `0033-lok-impress-slide-layers-on-macos.patch`:放映图层渲染(`SlideshowLayerRenderer`)把内核画进客户缓冲区,Aqua 设备画不进,macOS 上每层都空;改为画进自带透明度的设备再拷出(同图块)。`0034-lok-impress-transitions-and-animations.patch`:`.uno:SetSlideTransition`(JSON:预设、时长、单击或自动及秒数、全部幻灯片)、`.uno:AddAnimation`(给选中对象加预设)、`.uno:ChangeAnimation`(开始方式、时长、延迟、前后移、删除),各一步撤销;类型不合或内核无此预设即不改;读 `.uno:SlideTransition` 与 `.uno:SlideAnimations`(每个效果的目标按 `.uno:SlideObjects` 的序号,并给放映图层的散列,同 `anim::convertTarget`)。`0035-lok-impress-media.patch`:`.uno:InsertAVMedia` 收 `Width`、`Height`(1/100 mm)时不弹媒体对话框、不经内核播放器量尺寸,放在幻灯片中央;读 `.uno:SlideMedia`(序号、URL、是否链接、实际播放的文件:链接即原文件,嵌入即内核的临时副本、文件是否缺失)。Ely 只给基础一组(切换:无、淡化、经黑色淡化、向左推入、向上推入、向左覆盖、向左揭开、向右擦除、向外分割、溶解;动画:出现、淡入、飞入、擦入、放大进入、浮入、放大缩小、闪烁及对应的退出),文案由 Ely 给出;文件自带的其他效果显示内核名称。预览不阻塞编辑:worker 的 `SLIDE_LAYERS` 在后台取图层(静止层、动画对象各一层、母版字段占位),Ely 按效果排时(单击接在前面全部之后,与上一同时,在上一之后)在画布上合成:淡入淡出改透明度,飞入飞出与浮入浮出移位,擦除裁剪,放大缩小与强调缩放,闪烁隐显;预览里不画日期、页脚、页码字段(内核留给客户端画)。切换预览用两页的图层:新页按初始可见,旧页对象全显示(只读当前页的效果,旧页的退出不知道);首页从黑开始;无填充的页铺页面底色(`ely_lok::paper`,同编辑区)。媒体(用户定各平台原生):`ely_platform::media::Player`,macOS 以 AVFoundation(`AVPlayer` 加 `AVPlayerItemVideoOutput`,帧为 420f 的 `CVPixelBuffer`,经 GPUI 的 `surface` 画在对象上),状态有载入、可播(时长、画面尺寸)、失败(系统给的原因);Windows 与 Linux 尚未构建,打开即报“此平台未构建”。插入:选文件后先由系统播放器打开,可播才插入(视频按 96 dpi 换算并缩进幻灯片八成之内,声音 2×2 厘米),不能播即提示原因、不插入;默认嵌入,随文档保存。选中媒体对象时属性栏给播放、暂停与时间;链接文件缺失或系统不能播放时写明。正在播放的媒体按标签、幻灯片名与媒体 URL 认定,不符即停;播完再播从头开始。预览按标签、幻灯片名与撤销列表认定,读取回来、每帧绘制时不符即弃;停止令在途读取作废。向左揭开时旧页叠在新页上。面板的秒数字段经 `object::commit` 去重(回车与随后的失焦只发一次);切换的各字段以已发未读回的切换为底(`Extra::transition_asked`,带命令的输入序号;命令落地后开始的读取回来或换页即清,撤销也随之生效);秒数去重的键带幻灯片,动画的还带效果序号与图层散列(`motion::effect_key`)。预览另按视图的已落地与待落地编辑认定。播放实例在文档每次通知时核对(`observe_self`),标签关闭即停。 +- 2026-10-05:ElyShow 表格与图表、备注与审阅(C10.3,SHOW-07、08)。补丁 `0028-svx-chart-look-helpers.patch` 与 `0029-svx-chart-data-helpers.patch`:图表的外观与数据读写移入 svx 的 `ChartHelper`(Calc 与 Impress 共用;JSON 经 boost.json 保留类型,类型不对即拒绝,改动之前判定);`0030-sc-chart-looks-from-svx.patch`:Calc 改用它,原代码删去;`0031-lok-impress-charts-notes-comments.patch`:`.uno:InsertObjectChart` 收 `Chart`(外观与数据的 JSON),不开图表界面,落在幻灯片中央,选中一个图表时就地修改(复制后替换,一步撤销),JSON 不合即拒绝;先设外观再设数据(数据按图表类型写);读 `.uno:Chart`。缺失的点读写为 null(图表自己的非数标记),对话框里是空格;散点图的类别即 X 值:须为数字,按数字写入(原样写成文字,PPTX 导出为文字引用,再打开 X 变成 1、2、3);PPTX 打开的散点图按系列角色读出,各系列共用一组 X 时以 X 为类别,各系列 X 不同时不是一张表,读出不带 `data`,对话框只改外观并说明。`0032-oox-scatter-x-values-as-numbers.patch`:散点图的 X 全是数字时导出 PPTX 为数字引用。只在图表变成散点图时设为只画点,原是散点图的保留连接线;应用对散点图先查 X 是否数字,指明哪一格。原有的 `attachData` 去掉:各写入已自行生效,它反把数字类别写回文字;新命令 `.uno:SlideNotes` 以纯文字设 `Part` 页的备注,一步撤销(备注随所属页提交,切页排在失焦提交之前也不错页);读出当前页序号、文字与是否“带格式”。PowerPoint 给每段文字都写全字符格式,所以“带格式”指段落格式或字符格式在文中不一致:一致时新文字沿用它,不一致时拒绝(纯文字会丢格式,界面只读并说明)。批注带所在幻灯片序号;视图无作者时新批注取用户名(原样留空,补丁 0013 设的用户名到不了 Impress)。0027 另加:`.uno:SlideObjects` 不列批注(26.2 的批注是绘图对象),`.uno:SelectObject` 的序号同样不数批注。共用:`ely_office::chart`(图表对话框的外观字段,ElyTable 与 ElyShow),`ely_office::panel`(内核状态驱动的面板控件,ElyDoc 属性栏与 ElyShow 对象面板)。界面:“插入”菜单的表格(列 1–20、行 1–50)、图表(外观与图表自带的数据表,类别乘系列,可增删;非数字的格拒绝并指明哪一格;选中图表时为修改)、批注;表格对话框的行列数键入即生效(不等失焦);读选中图表时在同一次 worker 调用里读出当时选中的对象(页、在列表中的序号、外框),与发起时不符即丢弃,建对话框前再按界面所见核对一次;对象面板的表格组(行列插删、合并、删除表格,可否由内核状态定,光标进单元格后可用);画布下方备注栏(失焦提交);“视图”菜单的批注栏(`collab::CommentSidebar`:选中即转到该页并点亮画布上的标记,编辑、删除;只在所在页显示为选中,离开后可再点回)。不做回复:Impress 无回复结构,内核的回复把原批注改写成英文引语加回答;PRD 未要求。备注不上观众屏随放映(C10.5)。 +- 2026-10-06:ElyShow 放映与演讲者视图(C10.5,SHOW-11、12)。补丁 `0036-lok-impress-slide-show.patch`:读 `.uno:SlideShow`(每张幻灯片的隐藏、备注文字、切换与动画,同 `.uno:SlideTransition`、`.uno:SlideAnimations` 的形状,媒体带外框);`.uno:SlideMedia` 每项加外框(1/100 mm)。放映用内核的放映图层(`SLIDE_LAYERS`),不是截图轮播:母版的日期、页脚、页码由内核另画成文字层,`ely_lok::show::layers` 把它放到母版占位处的层次上(该页不显示的字段无文字层,即不画)。“放映”菜单:从头开始(F5,从第一张未隐藏的)、从当前幻灯片开始(Shift+F5,隐藏的也从它开始);开始时等该标签已发的输入落地,在同一次 worker 调用里读当前页、把文档另存为工作目录里的 ODP 副本(每次放映一个文件名)、载入副本并按应用深浅色设方案、读全部幻灯片;放映的图层、动画与嵌入媒体都来自副本,编辑不进入本次放映,放映结束时关闭并删除副本。启动中与放映中再按开始不响应。单击组从此前全部已触发动画与入页切换都结束后才开始。单击、右、下、空格、PageDown、N 下一步(先放本页的单击动画组,再到下一张未隐藏的幻灯片,最后是黑色结束屏,再一下退出);左、上、PageUp、Backspace、P 回到上一张并显示为全部动画已完成;数字加回车跳到第几张(含隐藏的);Home、End;B 或句号黑屏;L 激光笔;Esc 退出(有已键入的数字时先清掉)。与“上一个”同时、在其后开始的动画并入前一单击组,第一组在进入本页(切换结束)时开始;自动换片在本页动画全部完成后再等所设秒数。切换按进入方式:下一步有切换,跳转(含跳到本页)与后退无;离开的页按离开那一刻的动画状态画在切换里。媒体载入或播放失败即停,演讲者视图(单屏时观众屏)写明原因,放映继续。后退显示该页全部动画已完成。本页图层到达后才开始入页切换与动画计时;切换结束即释放离开页的图层。`Player::tick` 判定循环重播与失败,`Player::ended` 判定播完(人按的暂停不算播完;编辑区与放映共用);定位为精确定位(容差为零)。单击媒体对象播放、暂停或播完重播,离开本页即停;视频帧画在对象上。多屏:屏幕多于一块时先问观众屏与是否开演讲者视图(演讲者视图开在另一块屏上),两窗口都全屏;演讲者视图有当前页(鼠标在其上即为观众屏的激光点)、下一页(初始状态)、备注、计时(自开始起)、时钟、页码、上一步、下一步、黑屏、激光笔、结束放映。备注只在演讲者视图,观众屏从不画备注。图层宽度取观众屏上幻灯片的两倍像素,不超过 1920;只缓存当前页与下一页。GPUI 无隐藏光标的样式:单屏放映开激光笔时系统光标仍在。应用单测以组件库 `init_for_tests` 装主题,可渲染放映窗口。 +- 2026-10-06:ElyShow 输出与演示检查(C10.6,SHOW-13、14,QA-12)。补丁 `0037-pdf-impress-handouts.patch`:PDF 导出的 `ExportHandouts`(每页张数 1、2、3、4、6、9)照打印的讲义版式(`SdPage::CalculateHandoutAreas`,3 张带笔记线)把 `PageRange` 内未隐藏的幻灯片排到讲义页上;其他张数不出页,导出失败。讲义版式会改文档的讲义页,所以讲义与图片都在另存的 ODP 副本上做(`output::on_copy`,做完关闭并删除)。`Pdf::handouts` 经 worker 的 `EXPORT_PDF` 传入。导出对话框:内容 PDF、图片、讲义,范围全部、当前、从–到,讲义每页张数(默认 3);打印对话框:幻灯片或讲义,生成同一 PDF 后交系统打印面板。图片每张未隐藏的幻灯片一个 PNG,宽 1920,副本设浅色方案,文件名 `名称-序号.png`,所选文件夹里任一目标已存在即整体不写。PPTX/ODP 由保存与另存为负责。补丁 `0038-lok-impress-slide-check.patch`:读 `.uno:SlideCheck`(每张幻灯片与母版:文字所用字体族、链接图片与 OLE 链接及是否缺失、内核不能显示的对象:读不出的图片、无数据的 OLE;缺失的链接图片只作为链接报一次)。演示检查以内核报的缺失字体(`FONTS_MISSING`)为准,按所用字体定位到幻灯片或母版;媒体缺失与媒体链接取自 `.uno:SlideShow`;点一行转到所在第一张幻灯片。系统打印面板只经 `ely_office::print_later` 打开:它在前台任务里、GPUI 放开应用之后运行(面板的模态循环会回调 GPUI,在实体更新里打开即借用冲突崩溃),三个应用共用。图片输出先以 `create_new` 占下全部目标(不跟随符号链接),任一冲突或失败都删去本次占下的文件。输出与检查的对话框由后台读取打开,回来时须仍是最近一次请求(`Deck::ask_dialog`、`dialog_due`)且没有别的对话框;对话框种类或当前标签一有变化,此前的请求即作废(`watch_dialogs`,随文档通知)。图片的张数与隐藏取自写出的副本本身;PNG 写完显式 flush。读不出的图片按载入后的状态判定(`NONE` 或解码失败的 `Default` 占位);OLE 链接检查源文件是否在。 +- 2026-10-06(用户定):ElyPDF 签名与权限。验证签名时证书信任取操作系统的信任库(macOS 钥匙串的信任设置;Windows、Linux 随其构建)。可视签名三种来源:手写、键入姓名、图片文件;可视签名不是数字签名。只凭用户密码打开、带所有者限制的文件照限制办(打印、复制、编辑等被拦并说明原因),输入所有者密码可解除;改保护须所有者权限。 +- 2026-10-06(用户定):OCR 随包语言数据用 tessdata_fast,英文、简体中文、繁体中文(eng、chi_sim、chi_tra)。 +- 2026-10-06(用户定):ElyMail 在八个应用里最后做:任务表中 C12 移到 C15 之后、C16 之前;CEF 内容宿主随 ElyMark 先建(C14.0),邮件编号依次前移。 +- 2026-10-06(用户定):`ely-office` 的外壳不再只服务 LOK:`Host` 带关联类型 `Engine`(`ely_office::Engine`:后台载入、视图、撤销历史的标记、写出、关闭,视图的落定、编辑计数、待落地、焦点与事件),`Tab` 与 `Core` 按引擎取文档、视图与 worker 类型。LOK 的实现在 `ely_office::lok`(`Lok`、`start`、只对 LOK 的 `Commands` 即 `run_active`/`run_in`;查找替换、面板、图表仍只限 LOK);MuPDF 的在 `apps/ely-pdf/src/engine.rs`(`Pdf`;worker 首次载入才起,`Mupdf::get`)。`Kind::macros` 为 `None` 表示该格式无宏,不扫文件。ElyPDF 每个标签一个 `PdfView` 实体(页面、图块、文字、选择、找到的词都在里面,自己绘制),`Viewer` 只管标签条、书签栏、查找框与状态栏;打开、密码、关闭询问与保存走外壳。外壳的密码框:对话框开着时后到的加锁文件排队(`Core::locked`),对话框关闭(含取消、提交)后下一个再问;每次询问一个身份(按输入框实体),连续询问的 Esc 各归各的;提交时对话框先关、再载入。ElyDoc 的加锁测试原先清的是应用自己的对话框字段,改为清外壳的。 +- 2026-10-07:本地 Web 内容宿主(C14.0,§17.3)。`crates/ely-web-content`:CEF 154.0.32(Chromium 154.0.8037.58,cef-rs a712ad9,`default-features = false`),`scripts/fetch-cef.sh` 取 minimal 包到 `engines/cef`,`.cargo/config.toml` 的 `CEF_PATH` 让构建用它。进程:应用的 `main` 先调 `ely_web_content::subprocess()`(参数里有 `--type=` 即为 CEF 子进程,载入框架后 `execute_process`,不碰 GPUI);开发构建子进程即应用本身的二进制;macOS 包里是五个辅助应用(`<二进制名> Helper`、`(GPU)`、`(Renderer)`、`(Plugin)`、`(Alerts)`,`Contents/Frameworks`,`LSUIElement`,可执行文件为 `ely-web-helper`),框架在 `Contents/Frameworks`,`packaging/macos/bundle.sh` 对依赖 ely-web-content 的应用自动放入并先签内层;查找次序 `ELY_CEF`、包内、辅助应用所在、构建时的 `engines/cef`。启动:`start` 在 GPUI 里只做准备(载入框架、给 NSApp 的类补上 Chromium 要的 `isHandlingSendEvent`、`setHandlingSendEvent:` 与包住 `sendEvent:`,并加 `CrAppProtocol`、`CrAppControlProtocol`),`initialize` 与建浏览器都在消息泵任务里、GPUI 的更新之外进行(实测:在更新里同步调 CEF,CEF 转动 AppKit 的循环,GPUI 报 RefCell 已借用、整窗不画);浏览器异步建(`on_after_created` 交回)。凡在 GPUI 里对 CEF 的调用(输入、焦点、尺寸、脚本、缩放、重新载入、关闭)都排进主线程的队列,由泵在下一轮、更新之外依次执行(实测:在实体释放时同步关浏览器,CEF 转动 AppKit 的循环去校验菜单,GPUI 的菜单回调遇已借用的应用状态而中止,约三成复现)。泵:外部消息泵,按 `on_schedule_message_pump_work` 的时间,另照 cefclient 每轮后以 1/30 秒兜底(实测:只按请求时第一轮后 CEF 再不请求,页面不载入);空闲时 CPU 约 1.5%。离屏渲染须用 Alloy 风格(`runtime_style`;Chrome 风格不出画面)。同一缓存目录只允许一个实例,第二个启动时报出占用它的进程号。画面:`on_paint` 的 BGRA 直接成 GPUI 图像,上一帧在下一次绘制时从图集释放(`drop_image`);光标随页面。输入:鼠标点击、移动、滚轮(行按 40 点),键照 cefclient 的 macOS 做法:键盘按下带字符与系统键码发 `KEYDOWN` 再发 `CHAR`(退格、回车、方向等用 AppKit 的字符),可打出字的键等 GPUI 的输入法回调后以所打的字发同样两个事件,多字或组字经 `ime_commit_text`/`ime_set_composition`(无替换范围时用 CEF 的“无范围”,两端为最大值;传空指针 CEF 不收);松开带同一字符(不带时 Chromium 当作又按一次,实测退格删两个字);拷贝、剪切、粘贴、全选走框架命令。焦点随 GPUI 的焦点给页面。页面与应用之间:CEF 的消息路由,页面调 `window.elyQuery({request, onSuccess, onFailure})`(`onSuccess` 必须有,缺了路由不发),应用收到 `Event::Query(请求, 回复)`;应用以 `run_js` 调页面。主题:每次载入与主题变化时把调色板、代码色、字体与字号比例、深浅写成 `:root` 的 `--ely-*` 变量与 `color-scheme`。缩放 `set_zoom(倍数)`。页面只许载入所在内容文件夹里的文件:按真实路径判定(`..`、百分号转义、指向外面的符号链接都算外面,文件须存在),导航与每个资源请求(脚本、图片、样式、请求等)都过这道门,资源另许 `data:` 与本页的 `blob:`,其余一律取消;另在每个 CEF 进程以 `--proxy-server=127.0.0.1:9` 与 `--proxy-bypass-list=<-loopback>` 把一切网络(含本机回环与 WebSocket,后者不经资源回调)送往不通的代理;WebRTC 走 UDP 不经代理,故另以 `--force-webrtc-ip-handling-policy=disable_non_proxied_udp` 只许它经代理,并在每个页面上下文建立时、页面脚本之前删去 `RTCPeerConnection` 等构造(渲染进程的 `on_context_created`);页面的地址由真实路径逐字节转义生成(文件名含 `#`、`?` 照样载入);用户点出的链接以 `Event::Leave` 交给应用;页面的警告与错误写入日志。载入失败(主框架,停止不算)、渲染进程退出、CEF 起不来都报 `Event::Failed`,页面区改显示组件库的错误视图与“重新载入”(浏览器在即重载,否则重建;CEF 起不来时只说明),载入成功才报 `Event::Loaded`。浏览器关闭时断开它与表面的互相持有,页面的内存随之释放(释放时写日志)。真窗口(macOS,开发构建与 `dist/ElyMark.app`):页面以应用深色主题画出,点选、键入中英文、回车、退格、方向键、拷贝粘贴、`elyQuery` 往返;`tests/scripts/webview.sh` 以输入法回调组“你好”、键入、读回焦点、主题变量与缩放 1.25,核对页面跳不出所在文件夹、外面的脚本不执行、本机 HTTP 收不到请求、`#` 名的页面载入、载入前被删的页面报失败、开关 20 个页面都释放内存、杀掉渲染进程后页面区不再显示旧画面、经辅助功能按下“Reload”后页面重新画出(截窗口量标记色),WebRTC 不存在且本机 UDP 收不到 STUN,并截窗口量页面画出的标记色(去掉绘制、去掉断开持有、去掉代理或不显示故障时该脚本都失败);需屏幕录制与辅助功能权限,缺即报失败。未验证:Windows 与 Linux;辅助技术(见缺口);树莓派(R-06)。 +- 2026-10-07:ElyTree 恢复与清理、外部工具、操作规则(C13.6,TREE-13、14,§13.3,QA-19、20)。仓库页第二行:在终端中打开(macOS `open -a Terminal`,其他平台报未构建)、在访达中显示(`reveal_path`)、清理未跟踪的文件…;页底“引用日志”列出 HEAD 最近 200 次移动(`reflog show -z`,位置、Git 写的原因、时间),选中即中间显示该提交(同提交详情,`Whose::Reflog`),上方“在此新建分支…”(找回删掉的分支)与“把当前分支移到此…”:有已暂存的改动即拒绝(`reset --keep` 会丢掉它们,请先提交或取消暂存);否则在后台算出离开该分支的提交,对话框写明分支、从哪到哪、离开的提交(引用日志仍可找回)与“工作区改动保留”,确认后 `reset --keep`(Git 在改动冲突时拒绝;没有任何按钮做硬重置),执行前在写队列里核对 HEAD 仍在预览时的分支(完整引用名,或仍游离)与提交、仍无暂存,任一变了即什么也不做。引用日志的时间是该次移动的时间(`--date=unix` 下的 `%gd`),不是提交时间;选中按事件本身(提交、原因、时间与它在全部引用日志中自最早起的序号,后者新移动不改,同一秒同原因的两次也分得开),同一提交的多条不会一起选中;之后有新的移动时该事件的位置后移,选中与详情跟着它,事件已不在引用日志里即不显示。清理:对话框先由 `git clean -n -d`(勾“包括被忽略的文件”另加 `-x`)得出 Git 会删除的路径(`core.quotePath=false`,其余 C 引号照 Git 规则还原),其中的文件夹当场展开为其中的文件(`ls-files --others`,同一忽略规则,另一仓库不入),对话框逐个列出文件与文件夹;确认后先逐段查每个所列路径:仍在仓库里、途中没有符号链接、文件仍是文件、文件夹仍是文件夹(任一变了即整体拒绝,什么也不删),再以 `git clean -f [-x] -- 所列文件` 删去(只给文件,Git 不递归;此后被跟踪了的文件 Git 留下,也不穿过链接),最后自深而浅删去所列文件夹中已空的(预览后在其中新出现的文件不动,文件夹随之留下),每次都问。文件标题栏多出“在编辑器中打开”(用户在“仓库›选择编辑器…”选的程序,存在数据目录 `tools.json`,字段类型不对与读不出同样报错且不覆盖;macOS 的 .app 经 `open -a`,在后台等它结束,失败即显示其原话;未选时先问)与 Markdown 的“在 ElyMark 中打开”(`ely_platform::apps::open`:按安装布局找同级的应用,macOS 的 `名称.app/Contents/MacOS`,Windows 的 `Ely Office\名称`,开发构建的同一目录;找不到即报错)。锁(§13.3):任何写操作因 Git 的锁文件失败(`….lock': File exists`,或配置文件的 `could not lock config file …: File exists`)时,对话框说明另一个 Git 正在工作、Ely 从不删除锁文件,“重试”把同一写操作重新排队。QA-19:测试都以独立的 Git 命令核对索引、提交、引用与工作区。QA-20:冲突、钩子失败、凭据被拒(本机 401 服务器,显示 Git 的 “terminal prompts disabled” 原话)、锁、变基中断后中止、取消(停止取回)都有窗口测试,结束时仓库状态以 Git 读回核对。切换到系统 Git 目前经 `ELY_GIT`(设置页随 C18.1);状态栏显示所用 Git 的路径与版本。真窗口(macOS):仓库页工具行、清理预览对话框、文件标题栏的编辑器与 ElyMark 按钮,按下后同级的 ely-mark 以该文件启动。未验证:Windows 与 Linux 的终端;ElyMark 打开文件的内容(C14)。 +- 2026-10-07:ElyTree 储藏、远程、工作树、子模块、LFS(C13.5,TREE-10 至 12,§13.3)。左栏第四页“仓库”:上方取回(`fetch --progress --all`)、拉取(`pull --progress`,照用户的 pull 设置)、推送、强制推送…;储藏(`stash list -z`,新的在前;“储藏改动…”问说明与是否包括未跟踪的文件,`stash push`),远程(名称与地址,`remote` 加 `config --get-regexp` 读出取回与推送地址),工作树(`worktree list --porcelain -z`,行名为文件夹名,有失效的可“清理”即 `worktree prune`),子模块(索引里的 gitlink 与文件夹自身 `.git` 的 HEAD:未检出、在记录的提交上、在别的提交上、冲突),LFS 文件(`git lfs ls-files --long --json`:工作树里是内容、对象在本机而工作树是指针、只有指针而对象不在本机);没有子模块或 LFS 文件时不列。各列表分别读出,一个读不出只在该处显示 Git 原话;切到此页时读,此后每次状态重读时重读,只取最新一次。选中一项,中间显示:储藏为提交详情的同一套(`hist::Picked`,历史与储藏共用读取与面板):改动的文件(对第一个父提交)与储藏的未跟踪文件(第三个父提交,各自对空树),选文件看差异,上方应用、弹出、删除…;远程为地址与推送地址,取回、修改…(改名 `remote rename`、改地址 `remote set-url`)、移除…(先问);工作树为路径与分支或提交、锁定与失效原因,打开(作为标签)、移除…(先问,主工作树与当前的不给);子模块为记录的与检出的提交,“检出记录的提交”(`submodule update --init`,经远程运行)与打开。用户输入的名称、地址、路径前一律加 `--`(推送的远程名同样)。表单与选择对话框(新建分支、标签、改名、身份、储藏、远程、工作树、选远程与上游)记下打开时的仓库,提交时写进那个仓库,不随当前标签变;储藏列表按每条三个字段读(说明可为空)。储藏的应用、弹出、删除按 `stash@{n}` 执行,执行前在写队列里核对它仍是所见的那个储藏(另有储藏插入即拒绝,什么也不做);弹出遇冲突时 Git 保留储藏,对话框显示 Git 原话。删除储藏、移除远程与工作树每次都问。分支页的本地分支多出“设置上游…”(列出远程分支,`branch --set-upstream-to=refs/remotes/名`)与“取消上游”。推送:有上游即 `push --progress`;没有上游时后台读出远程,问推到哪个远程(`push --set-upstream -- 远程 refs/heads/分支:refs/heads/分支`),没有远程或 HEAD 游离即说明。强制推送(§13.3):后台读出当前分支、上游的远程与远端分支、上次取回时远端的提交(租约)、此刻 HEAD 的提交(源)与远端有而源没有的提交,对话框写明远程、分支与将被覆盖的提交(或写明没有),确认后推送的就是所示的源提交(`push --force-with-lease=远端分支:租约提交 -- 远程 源提交:远端分支`),确认前本地分支再动也不改变推送内容;远端自上次取回后变了即被拒绝;没有上游或游离即说明不能。远程命令(取回、拉取、推送、子模块更新)是写队列里的一种(`Write::Remote`),经 `running::Running` 运行:Git 在自己的会话里(`setsid`,SSH 找不到终端,不会挂在终端提示上;凭据照用 credential helper 与 ssh-agent),进度逐行显示在中间上方的提示条,可停止(向整个进程组发 SIGTERM,Git 自行清理;已结束的不再发);用户停止时提示“已停止”、清掉其后的排队写,不当作失败;失败时对话框显示 Git 的 stdout 与 stderr(以回车结尾的进度行被下一行改写,只留最后一次;以换行结尾的行全部保留,钩子的每一行都在);拉取遇冲突照合并类命令判定为停下。关窗与退出时停下所有远程命令与克隆。克隆改用同一运行器。真窗口(macOS):仓库页五类列表、储藏详情与文件、工作树详情、分支行。未验证:Windows 与 Linux(停止远程命令在非 Unix 上报未构建);真实网络与 HTTPS、SSH 凭据;LFS 对象的下载(只显示状态)。 +- 2026-10-07(用户定):跟随组件库最新的 main。组件库 K01 把 gpui 从 zed 仓库的提交换成 crates.io 的 `gpui-pre` =0.3.8(`gpui-pre-platform` 同版本),K02 让 Tab 绑在 FocusScope 的 ElyFocus 上下文下、更深的上下文先得;本项目工作区的 `gpui`、`gpui_platform` 随之改为这两个包(以 `package =` 保留原名,源码不变),组件库 pin 到 dabc1eb(其上加本项目一个提交:树与树行的无障碍角色)。升级后全工作区编译无改动。 +- 2026-10-08:首版打包(R1.1)。图标(用户定:logo-design 技能出方案,选 C“实心几何”;macOS 标准 squircle,Linux 与 Windows 无底形,八个同一套设计):母版在 `assets/icons`(我按圆角矩形、圆与 30°/45° 斜线在 256 网格上画;每个颜色对白与 `#1E1E1E` 都不低于 3:1,ElyMark 原定的石板灰在深色上 1.75:1,改为琥珀 `#B87A00`;ElyTable 在 48 像素以下用简化的一版),`packaging/icons/build.sh` 以 AppKit 渲染出 icns(macOS)、512 像素 png(Linux 的 AppImage 与桌面文件)、16 至 256 像素 ico(Windows 安装程序、开始菜单快捷方式与卸载项);生成物入库,Linux 与 Windows 构建不需要 macOS。系统交给应用的文件:`ely_ui::run` 收下启动参数与 `on_open_urls`(访达的打开请求,启动前到达的也在内),应用先取 `launch_files`(有则不开空白文档;ElyMark 有则不恢复上次的标签),再以 `on_open` 处理运行中的请求;六个应用都走这一条(ElyDoc、ElyTable、ElyShow 原先不收启动参数)。macOS 包:LOK 应用只带内核,按 LibreOffice 自己的布局并入本应用:内核的库进 `Contents/Frameworks`(合并库构建留下的“invalid - merged lib”占位文件不拷,其余非代码文件即报错),内核的资料进 `Contents/Resources`,另有 `MacOS/ely-lok-worker`;`install_dir` 先找包内(`Frameworks/libmergedlo.dylib`)。不带 `LibreOffice.app` 本身:原先整份嵌入时,其快速查看与聚焦插件、`soffice` 等启动程序与文档类型被系统登记,系统设置里出现“LibreOffice 扩展”(用户发现;已注销本机七处登记);测试也不再运行 LibreOffice 应用(样本改为 `tests/fixtures/office` 的固定文件,运行开发构建的 `soffice` 会让系统重新登记它)。ElyPDF 带 `MacOS/ely-pdf-worker` 与 `Resources/tessdata`(三种语言),OCR 数据先找应用旁再找包内;ElyTree 带 `Resources/git`,其中每个 Mach-O 先签(Git 以 `INSTALL_SYMLINKS` 构建,内建命令是指向同一程序的相对链接;原先是硬链接,拷进包后变成约 150 份副本、689 MB;以 `MACOSX_DEPLOYMENT_TARGET=14.0` 构建,原先随本机 SDK 要求 macOS 27);ElyMark 用同级 ElyTree 包里的 Git。文档类型(`packaging/macos/documents.sh`)一律 `Alternate`,不抢系统默认:Doc 为 docx、odt、doc、rtf;Table 为 xlsx、ods、xls、csv、tsv;Show 为 pptx、odp、ppt;PDF;Markdown;Tree 为文件夹(查看者)。worker、内核、OCR 数据、CEF 与页面的所在路径都写日志,`tests/scripts/release-macos.sh` 据此核对都在包内。许可(§21.2、§21.3):每个包的 `Resources/Licenses` 有 Ely 的 AGPL 全文(仓库根 `LICENSE`)、`rust-crates.txt`(`packaging/crates.py`:本机目标下链进各二进制的全部 crate 与其许可文件,无许可文件的取 `packaging/licenses` 里该表达式的标准文本并写明作者,都没有即失败)与所带引擎的许可:LibreOffice 的 LICENSE、NOTICE、CREDITS;MuPDF 的 COPYING 与其编进去的每个第三方及每组内嵌字体(按构建实际嵌入的)的许可、OpenSSL、tessdata(Apache-2.0,按提交与散列取)、Great Vibes 的 OFL;Git 的 COPYING 与 LGPL-2.1、git-lfs 的 LICENSE.md(发布包里没有,按标签与散列取);CEF 的 LICENSE 与 Chromium 的 CREDITS、页面的第三方通知;缺任何一份即打包失败(`packaging/licenses.sh`)。`release.sh` 另出 `ElyOffice-<版本>-source.tar.gz`:自有源码、锁文件、上游补丁与构建脚本(上游各自钉在提交,取得方式即其中的 fetch 脚本)。正式签名(hardened runtime)时 CEF 的渲染与 GPU 辅助程序带 `allow-jit`,插件辅助程序带 `allow-unsigned-executable-memory` 与 `disable-library-validation`(`packaging/macos/entitlements`;原先不带,V8 保留不到代码区,页面载入失败);`tests/scripts/hardened-macos.sh` 以 `ELY_SIGN_IDENTITY` 签 ElyMark 并核对页面画出。ElyTable 同时收到多个文本文件(访达一次交来多份 CSV)时,读完的导入排队,没有对话框开着才逐个询问(原先后到的一份替换前一份,前一份静默丢失)。`packaging/macos/release.sh` 按 `apps.tsv` 的批次出各应用的 dmg;本机只有 Apple Development 证书,无 Developer ID,未公证。未验证:Intel 包(只建了 arm64)、公证、Linux 与 Windows 的包(本机不能建)。 +- 2026-10-08(用户定):深色主题不用纯黑或近黑,底色取柔和的灰 `#252525`。组件库 fd0efc7:深色色板 bg `#252525`、surface `#2c2c2c`、sunken `#202020`、overlay `#323232`、hover `#383838`、active 与 border `#3f3f3f`、border_strong `#555555`;为让说明文字在较亮的 hover 与 active 上仍有 4.5:1,muted 提到 `#c4c2c0`、subtle 与代码注释提到 `#acaaa8`;色盲友好图表的紫与橙改为 `#9d8dec`、`#ffd93d`(3:1 且各色两两分得开)。`ely.xcd` 的深色方案随之:页面 `#2c2c2c`、页外 `#252525`、边界与网格 `#555555`、域底纹 `#3f3f3f`。跨应用同步外观、设置里的浅色/深色/跟随系统、Omarchy 下随 Omarchy 主题,本来已有(共用 `settings.json` 加文件监视;`settings.sh` 核对 ElyPDF 选深色后 ElyTree 即刻变深);Omarchy 的读法与 OmaPhoto 相同(`~/.local/state/omarchy/current/theme/colors.toml`、`mode` 与具名颜色)。 +- 2026-10-08:图像的显存与内存(C20.2 长时测试查出)。GPUI 把每张画过的 `RenderImage` 留在窗口的图集里,直到调 `window.drop_image`;LOK 的图块、PDF 的图块与缩略图、演示的缩略图、动画预览与放映图层原先都不释放,ElyDoc 编辑保存 60 轮后应用进程涨到约 2 GB(内核不涨)。组件库 `primitives::Retired`:不再画的图先交给它,下一次绘制时释放(`release` 只放别处已不持有的,`release_all` 给自知不再画的属主,`append` 接过关闭者的);LokView、PdfView 在替换、挤出、清空图块时交出旧图,绘制前释放;ElyShow 每个标签一份,关闭的标签与结束的预览交给 Deck;放映按窗口各一份(观众屏、演讲者视图),离场中的幻灯片还在画时其图层等它结束。修后同样 100 轮在 340 至 440 MB 之间起落。 +- 2026-10-08:性能基准(C20.1,§20.1)。`tests/perf/bench.py` 在发行构建上测 PERF-01 至 08(每项 30 次,P50、P95,含全部子进程的内存),样本由 `tests/perf/samples.py` 按固定种子生成到 `engines/bench`,记录写到 `docs/perf/<日期>-<机型>.md`(格式见其 README);`ELY_STALLS=1` 开主线程忙碌记录(只供测量,空闲的应用照样不做事)。实测改动:Doc、Table、Show 原先要等内核启动完(约 2.5 至 3.9 秒)才开窗口,ElyDoc 日常启动 P95 3.9 秒不达标;现 `Worker::launch` 只起进程,内核的版本与图块核对在第一个请求前做(`Worker::ready`,任何请求都先过它,未核对的 worker 收不到请求),`ely_office::start` 在后台核对并设作者名,失败即 `startup::fail`;窗口约 0.15 秒可操作,空白文档在内核首次载入后出现(中位约 2.7 秒,其中约 2.2 秒是内核首次载入文档)。启动参数改为按当前目录取绝对路径(原相对路径成了 `file://相对` 而打不开)。补丁 `0039-skia-macos-no-unused-font-manager.patch`:macOS Skia 首次画字时以全部系统字体再建一个无人使用的 `SkFontMgr`(约 370 个字体枚举加排序,主线程约 175 毫秒),新文档的第一个字与打开文档的首块带字图块因此迟到;去掉后 PERF-03 的 10 次对比 P95 由 2.6 秒降到 1.5 秒。PERF-02 的计时原以输入落地时的代际为基准,内核的作废事件先于落地回报到达时,该次编辑的图块不计,钟一直走到下一次按键(记下的是按键间隔约 250 毫秒);现按编辑的修订认定(`view/clock.rs`:修订不低于该编辑的批次所升的代际,其图块即为所求),实测中位 2 毫秒、P95 5 毫秒。PERF-08:关闭的标签原把其图块留在 GPUI 的图集里(每个文档约 20 MB 显存,40 次新建关闭后应用涨约 250 MB);`Engine::retire` 交出关闭视图的全部图像(LOK 图块、PDF 图块与缩略图、ElyMark 页面的画面),外壳在窗口的下一帧释放,修后 40 次在 454 至 462 MB 之间。历史首屏改为从按下“历史”计(原从进程启动计,含打开仓库)。R1 后基准更严:PERF-03、04 到视图收下的第一块图块(`lok: tile (x, y) shown`),其间有图块画失败即整次作废;DOCX 样本原以单独一段的分页符分页,内核只排出 60 余页(`Heading1` 段上的 `pageBreakBefore` 同样丢页),改为分页符放在标题段内,PERF-03 之前先核对恰为 100 页(视图记录页数);PERF-02 每键须有自己的计时,缺即失败;PERF-08 循环前记基线,记内核中的文档数(worker 每次载入关闭写 `N documents open`),文档数须回到基线,文件数自第一次之后不变(第一次打开模板对话框时进程一次性载入两个系统字体与两个 AppKit 本地化表)。worker 的启动核对失败后保持失败,此后一切请求被拒(原核对失败的状态与成功相同,第二次即放行)。空白文档就绪由约 2.5 秒降到约 1.0 秒,发生在本次重建内核之后,与 0039 无关(对比实测),原因未查明。 +- 2026-10-08:打印输出(C19.2,§19.3)。`ely_platform::print::print_pdf` 每次取系统打印设置的副本,纸张取 PDF 中最大的一页的尺寸与方向(一次打印只有一种纸张,较小的页按原大印在其上),四边距为零(文档的页边距已在 PDF 里),缩放为 1;`ELY_PRINT_TO=路径` 让作业写成文件、不弹面板(测试用);原先用共用设置,打印机默认纸张与边距使 A4 以外的文档被裁切或偏移。Doc、Table、Show、PDF、Mark 的打印都先等输入落地、在后台写出同一份 PDF(同一修订、页面设置与缩放),对话框给出页数与范围,再交系统面板(`ely_office::print_later`);取消不动文档,副本随即删去。`tests/scripts/print.sh` 真窗口核对。 +- 2026-10-08:兼容清单与往返验证(C19.1,§19.1、§19.2)。`docs/compatibility.md` 是随版本发布的兼容清单(版本、内核与补丁、验证机,各应用的编辑保存、导入受限与输出,已验证与限制),版本或格式行为变了即同步改它。往返用例在 `crates/ely-lok/src/client/tests/independent.rs`:Ely 编辑、保存、关闭、再打开、再编辑、再保存,再由 `tests/scripts/ooxml.py` 检查;它不用 LibreOffice,自己解 OOXML,预期值自己算(整数、日期序号、错误值、文本严格相等,浮点按用例声明的容差),DOCX 另交 macOS 的 `textutil`;没有 python3 时报 SKIP。与 Microsoft Office 的交叉检查未做(本机没有 Office)。 +- 2026-10-08:恢复副本、引擎停止与自动保存(C06.5,§6.2、§6.3)。Doc、Table、Show、PDF 共用 `ely_office::Recovering`:每 10 秒为有改动、已落定、不在导出的标签经 `Engine::snapshot`(写副本,文档不动)写一个恢复点(`ely_files::Recovery`,限额取存储设置;加密文档封存),索引在数据目录 `recovery/index.json`;崩溃标记照 ElyMark 的 `Crash`,下次启动把崩溃留下的最新点开成未命名副本并标为未保存,提示写明只恢复了内容,光标与撤销历史没有。引擎进程退出(`Engine::ended`,客户端 `try_wait`)时 `ely_office::Stopping` 在上方显示提示条(原因、最近一次恢复点的时间)与“重新打开文档”:`Engine::restart` 换上新 worker(LOK 照原配置与作者名重起,PDF 下次打开再起),有改动的标签从最新点开成副本,无改动的从文件重开,有改动却没有恢复点的列出;被放下的标签迟到的失败不再提示,指向它们的关闭询问一并撤下。重开的副本接过原恢复点的文件夹(封存随之),旧点留到新标签写下新点为止;载入或解封失败的条目留在索引里,下次崩溃后的启动再给。需要密码的副本(加密 PDF 的恢复点照原加密写出)照样询问,答后仍是未命名副本(`Source::Locked`)。引擎是否退出在出错时与每次恢复计时时查,空闲的引擎退出也会显示。恢复索引经 `ely_files::save_atomic` 暂存后替换,写不进时上一份照旧可读;写不进即提示一次、每次计时重试。干净结束后的下次启动只删已接过或正常的条目,没能重开的留着再试。自动保存(设置“文档”页,每个应用一个开关,存在应用的 `settings.json` 的 `autosave`,默认关):只对本应用自有格式(`Kind::save_as`)、无风险(宏、签名、修复)、状态为“已编辑”的标签,编辑计数停 2 秒不变即走普通保存;保存失败、外部改动后状态不再是“已编辑”,不再自动写,等用户处理。Cmd+W 关闭标签是共用命令(`ely_ui::commands::CloseTab`,原来只有 ElyPDF 绑了;文档应用里 Cmd+W 落进 LOK,内核按自己的 Ctrl+W 关掉框架,worker 下一次绘制或读取即在已释放的文档上段错误);worker 另拒绝内核关闭框架与退出的快捷键(Mod1 加 W、F4、Q)。真窗口(macOS):`recovery.sh`(崩溃后副本回来、关闭询问;只杀引擎后提示条、重新打开、文字回来、关闭询问),`autosave.sh`(关时不写,开关存下,开后停手即写,外部改动后不覆盖)。未验证:Table、Show、PDF 的真窗口恢复(同一外壳);Windows 与 Linux。 +- 2026-10-08(用户定):首版发布六个应用(ElyDoc、ElyTable、ElyShow、ElyPDF、ElyTree、ElyMark);ElyMail 与 ElyAgent 第二批发布。ElyMark(C14)做完即开始首版的完整测试与打包,C12、C15 留到第二批。 +- 2026-10-08:数据管理(C18.3,§18.3)。设置的“存储”页(每个应用):本应用数据目录里的日志(`ely.log`)、网页内容缓存(`web`,只 ElyMark 显示)、恢复副本(`recovery`)各自的大小与清除(`ely_platform::storage`);日志当场清空(日志器以追加打开,截短不坏句柄),缓存与恢复副本在用着,写下记号(`.clear-web`、`.clear-recovery`),下次启动在设置与窗口之前删去(删不掉即启动失败、写明原因,记号留着下次再试,不打开没删掉的数据);每次清除先问,写明删什么、何时删;每个文档保留的恢复点(10、20、50)与空间(32、64、256 MB)存在应用设置的 `recovery`(严格读,不合即报错),ElyMark 写恢复点时照用(C06.5 的文档应用同)。日志超过 8 MB 时,启动时只留后 4 MB、从整行起。数据库的升级前备份与失败只读只属 ElyMail(第二批),首版的六个应用没有数据库;邮件与 Agent 历史导出同属第二批。组件库 ea4ed4c:`SettingsRow` 是以标题命名、以说明描述的组(原先行标题不进 AX 树,两个同名按钮分不出);待清除的状态以禁用的按钮写明。 +- 2026-10-08(用户定:图标照 Papirus 重画):每个应用是 Papirus 192 网格上的一个大的扁平物件(页、讲台、信封、终端),主体一种饱和色,折角为其浅色,内容为白色、至多一个点缀色,主体与内容带 Papirus 的柔和投影(下移 4 或 2、模糊、20% 黑)。macOS 为浅色 squircle 上的同一物件(占主体 86%),Linux 与 Windows 只有物件。48 像素以下用无投影、无小细节的简化版。母版由 `packaging/icons/masters.py` 写出,配色见 `assets/icons/README.md`。Papirus 图标包(`assets/papirus_icons-master.zip`,用户放入)只作参照,不随包。 +- 2026-10-08(用户要求核对各系统的文件关联):文件类型只写在 `packaging/types.tsv`(应用、扩展名、MIME;`/` 为文件夹)。Linux 的桌面项由它写 `MimeType=`(原先没有,桌面环境从不提供 Ely 打开文件);Windows 安装程序由 `packaging/windows/types.sh` 生成的 NSIS 宏注册(原先没有):HKCU 下每个扩展名的 `OpenWithProgids` 与 `Applications\<程序>\SupportedTypes`,ProgID `ElyOffice.<名称>` 带图标与打开命令,ElyTree 在文件夹右键菜单加“Open in ElyTree”;只进“打开方式”,从不改默认(同 macOS 的 Alternate),卸载时删去。makensis 在本机编过 ElyDoc 与 ElyTree 的安装程序;Windows 与 Linux 上实际打开文件未验证。 +- 2026-10-08(用户定:窗口缩窄时控件挤成一团、越出边框,不可接受):窄窗口的版面。外壳一次排两侧栏(`shell/panel.rs` 的 `room`,有单测):内容区至少留 360,放不下时两侧先缩到各自最窄,再先藏右栏、后藏左栏,窗口变宽即回来,用户拖的宽度不变。组件库 6d95b24 至 69265b4:`ScrollArea::vertical` 只纵向滚动、内容不得宽于区域(侧栏一律用它,原先横向不受限,整栏按自然宽度排后被裁);`PropertyGrid` 每行可折行,放不下时控件到标签下方,标签列 104;全部图标的 `SegmentedControl` 与 `Tabs` 放不下全部文字时只显示图标,文字作提示与无障碍名(宽度取上一帧量得的条宽与文字排版宽度);`StatusBar` 两侧与各项可缩,文字以省略号结尾;`Banner` 文字折行;`Button` 不再拒绝收缩,只在无法折行时缩并以省略号结尾;未聚焦的单行文本框显示文字开头(原插入点在末尾,看到的是尾部);`Switch::name` 只给无障碍名(行已有标签时不再重复画字);`CommitInput` 的勾选与按钮一行可折。各应用固定宽度的控件框(`w_32` 等)改为占满、以原宽为上限。`tests/scripts/layout.sh`:六个应用各开样本,640、800、1100 宽下读 AX 树,任何元素越出窗口左右即失败,并留截图供目视(栏内裁切 AX 看不出)。缩放(用户问为何 Doc、Table、Show 不能缩放;视图早有 `set_zoom`,界面从未接上):`ely_office::Zooming`,Cmd+= / Cmd+- / Cmd+0,“视图”菜单三项,状态栏右侧 `ZoomControl` 的属主模式(组件 69265b4:显示属主给的百分比、按下交属主;原只缩放整个界面);Cmd+滚轮与触控板捏合以指针处为中心缩放(`view/zoom.rs`,档位 25% 至 400%);ElyShow 的 Cmd+0 回到适合幻灯片,手动缩放即停止适合。缩放只在内存里随标签,不跨重启。 +- 2026-10-08(用户定:侧栏太占地方,要可调):侧栏宽度(C05.6)。外壳的左右侧栏(`shell/panel.rs`,八个应用共用)默认左 220、右 320(原取 `sidebar_width` 240 与 `sheet_size` 380;右 320 是 ElyDoc 属性栏不截断控件的最窄宽度,实测 280 截断数值框);内侧边缘是组件库的 `layout::resize_edge`,拖动改宽,左至少 160、右至少 280(原 220,属性栏放不下)、至多窗口的 45%;拖动停 0.5 秒后存入应用设置的 `panels`(`settings::panels`),退出时还没存的当场写入(窗口位置同),启动时严格读回(不是正数即在设置窗口写明并用默认),写入失败也在设置窗口写明;开合动画期间不显示拖动边。 +- 2026-10-08:凭据与私密内容(C18.2,§18.2)。恢复密钥(加密文档的恢复副本用)存在系统凭据库(keyring:钥匙串、Windows 凭据、Secret Service);凭据库不可用或拒绝时改用本次运行的随机密钥,只在内存(`RecoveryKey { key, lasting }`,`lasting` 为假即提示恢复不跨重启),从不写成普通文件;用旧密钥封存的恢复点读不开即报“无法恢复”,不导出明文。PDF 打开密码与证书密码只在 worker 内存里、文档关闭即弃。日志:worker 的 stderr 经 `ely_ipc::relay` 按其级别并入应用日志(原先从访达启动时 worker 的日志丢失),两个 worker 与应用一样读 `ELY_LOG`、默认 info(LOK worker 原读 `RUST_LOG`,平时只出错误);日志调用不打印正文、密码、密钥;引擎(worker、内核、MuPDF)给的错误串可能引用文档,日志只记请求号与错误类别,全文只经回复交给界面;worker 的 stderr 只放行其结构化日志记录与崩溃栈帧,引擎自己打印的文字不进日志(只计数)。worker 启动失败也写成结构化记录,最后一条错误由 `Relay::last_error` 带进客户端的报错。诊断导出时姓名来源(账户名、设置)读不出即不导出并写明原因;日志尾部从整行开始。诊断(设置里的“诊断”,每个应用):先列出要导出的内容(本应用日志的最新 4 MB、macOS 上 7 天内本应用与其 worker 的崩溃报告,列表在 AX 树里),开关“去掉路径与个人姓名”默认开(凡路径与 URL 整段换成 ``:有引号到引号为止,否则到行末;分段标题只写文件名;账户全名、登录名与作者名按从长到短换成 ``),导出为一个文本文件(`ely_platform::diagnostics`),不上传。Git 服务的凭据照用用户自己的 credential helper 与 ssh-agent;打开文件不发任何请求。未验证:Windows 与 Linux 的凭据库与崩溃报告位置(那两端诊断只含日志)。 +- 2026-10-08:配置(C18.1,§18.1)。通用偏好在 `<本机数据目录>/ElyOffice/settings.json`,各应用的在其数据目录的 `settings.json`;都经 `ely_platform::settings::Store`:带 `version`(本构建为 1;更新的版本或读不通即报错,不覆盖),写入在同目录的 `.settings.lock`(`ely_platform::lock::FileLock`,flock 或 LockFileEx,由 ely-files 移来共用)下先重读、只改所写的键、未知字段原样留下,临时文件 fsync 后原子替换;多个应用同时写不丢字段(单测八路并发)。通用字段(`settings::Shared`,类型不对即报错):外观(跟随系统、浅色、深色)、语言(跟随系统、English、简体中文;下次启动生效,设置里写明)、动效(跟随系统、减少、完整)、跟随 Omarchy(只在有 Omarchy 时显示)、作者名(空即账户名;`user::name` 先取它,ElyPDF 的批注、LOK 的批注与修订都用它)。环境变量 `ELY_APPEARANCE`、`ELY_OMARCHY_SYNC`、`ELY_REDUCE_MOTION`、`ELY_LOCALE` 仍优先。`ely_ui::settings` 启动时读入(读不出即用默认值、记下原因),以 `notify` 监视文件夹,别的应用写入后即刻重读:外观、Omarchy、动效当场换,作者名经 `observe_global` 在后台交给 LOK worker(`set_author`)。设置窗口(应用菜单“设置…”,Cmd+,,每个应用一个)用组件库 `SettingsLayout`、`SettingsSection`、`SettingsRow`、`ThemeSelector`(组件 489ef31 起三项文字进内建中英目录)、`SegmentedControl`、`Switch`、`forms::Input`:通用;Doc、Table、Show、PDF 另有“文档”(作者名,失焦或回车写入);ElyTree 另有“Git”(Git 程序,空即随带的;写入前在后台核对 2.40 或更新,不是即在框下以 Banner 说明、不写;`ely_platform::git::locate` 次序为 `ELY_GIT`、此设置、随带的,版本核对移入 `ely_platform::git::system_version`,下次启动 ElyTree 生效)。文件读不出时窗口上方以 Banner 写明原因,改动不保存。作者名与 Git 程序每次提交都编号,写入经 `Store::set_if` 在文件锁下核对仍是最新的提交才落盘(等锁期间改了主意,旧写入作废);写入失败另以 Banner 写明,键入的值留着可重试;作者框在未编辑时跟随别的应用写入的名字。版本核对先看 `--version` 的退出状态(`git::version_of`)。各应用主窗口的位置、大小与最大化、全屏存在应用的设置里(`window`,停止移动 0.5 秒后写),启动时所记之处的中心仍在某块显示器上才用。另:ElyMark(CEF)直接启动的应用继承了 Chromium 忽略或屏蔽的 SIGTERM,测试的 pkill 杀不掉、留下进程(实测数个钟头的残留);`apps::open` 在 exec 前把 SIGTERM、SIGINT、SIGHUP、SIGQUIT 复原并清空信号屏蔽(单测:启动者忽略并屏蔽 SIGTERM 时子进程照样被终止)。未验证:Windows 与 Linux 的锁与窗口位置。 +- 2026-10-08:进程与状态核对(C17.1,§17.2、§17.4)。帧(`ely-ipc`)由长度、标签、请求号与内容组成:客户端每个请求一个递增的请求号,worker 的回复带回同一个,不符即报错(`check_reply`;原先只靠一问一答的次序配对,丢一个回复即整串错位而不自知);LOK 主动推送的事件请求号为 0(`UNASKED`);PDF worker 的版本戳包含 `ely-ipc` 的源码。帧超过上限(64 MB)发送前即报错,什么也不写;worker 的结果超限时回一个错误,worker 不退出。LOK 的输出读不下去(坏帧、提前结束)时,原因经回复通道交给等待的请求,客户端停下并回收 worker 再报出(原先请求一直等进程结束)。会话:LOK 每次载入一个会话号(递增,原先用内核文档指针,释放后的地址被下一份文档复用,旧句柄读到新文档、事件被当作已关的丢掉);PDF 的 `Doc` 带打开它的 worker 会话,客户端发送前核对,别的 worker 的旧句柄被拒;worker 的文档号带进程前缀(进程号与启动时刻),别的进程的号在此不存在。修订(PDF):`Doc` 记着读到时的历史标记(标记相同即文档相同,撤销回到原标记),指向页、对象、位置的请求带上它,worker 在动内核之前核对,不符即以 `stale:` 拒绝;整份文档的请求(页数、保存、撤销、元数据、附件列表与添加、保护等)带“任意”;按键读取或移除附件带标记。ElyPDF 的编辑在排队时记下当时的句柄,落地后按新标记换句柄,过期的读取照旧按版本丢弃(画不出的块不因过期记为失败),过期的编辑提示“文档已改动,未执行”:排在别的编辑后面的编辑因此被拒(它对着的是前一个编辑落地之前的页面),再按一次即可。LOK 的修订(R2):worker 给每份文档记一个修订,会改文档的请求(键、鼠标按下与松开、拖动、文字、命令、粘贴、剪切、函数补全;不按键的鼠标移动不算)在执行前加一;按编号指向工作表、幻灯片、对象、参考线、制表位、行列的命令(参数里有 `Index`、`nTabNumber`、`TabNo`、`Part`、`Row`、`Col`、`Column` 或区域的四角,值不是 -1;-1 是新建)先发 `EXPECT` 带读到时的修订,不符即以 `stale:` 拒绝,内核不动;同一次读取得出的一组命令(对象列表的多选)只核对一次,在一次 worker 调用里依次执行(`Worker::run_all`、`LokView::run_all_at`、`Commands::run_all_in`)。`LokView::run` 拒绝带编号的命令,须经 `run_at`;外壳的 `run_in` 对带编号的命令用 `Host::read_at`(ElyTable、ElyShow 为其读取时同一次 worker 调用里取的 `Worker::now`,ElyDoc 为视图所见:推送的事件批次带修订,每个输入落地后视图另取一次修订);`Tabs::read_in` 也给读取闭包这个句柄(ElyPDF 为视图当前的句柄)。ElyTable、ElyShow 在视图所见修订新于其读取时重读,所以键入之后按编号的命令不会一直过期;记着编号的对话框(删除与重命名工作表、删除重复项、幻灯片备注栏键入期间)在读取时记下修订,确认按它执行(`Commands::run_on`),其间文档结构变了即拒绝;ElyDoc 属性栏按视图所见的修订发(删除所示的制表位)。输入失败时视图照样取回修订(请求在执行前已计数),之后的编号命令不会一直过期。过期的拒绝提示“文档在此间已改动,请重试”(`office.error.changed`)。MuPDF 打开中途(加锁检查、日志、修复)失败即释放已打开的文档与修复副本(原先每次失败留下整个文档;MuPDF 的存储另有 256 MB 上限的缓存,满后不再增长)。FFI:MuPDF 每个调用在垫片的 `fz_try`/`fz_catch` 里,返回码加错误信息;LibreOffice 只经 LOK 的 C 接口;worker 崩溃时客户端报出退出状态。工作进程:LOK 随应用窗口启动(启动即开空白文档),MuPDF 在打开第一个 PDF 时启动;不建进程池。后台:Git、OCR、缩略图、文件夹搜索、导出与系统打开都在后台任务里。不建设 §17.4 所列设施(无 RPC 服务发现、无常驻服务、Git 只有一个内核、Markdown 只有 Milkdown 与 CodeMirror)。 +- 2026-10-08:应用之间的连接(C16.1,§16;首版只做六个应用之间的三项,邮件与 Agent 的三项随第二批)。启动别的 Ely 应用(`ely_platform::apps`):先找与本应用同一安装或构建的同级(`.app` 旁、Windows 的 `Ely Office\名称`、开发构建同一目录),再找 macOS 登记过的(按包标识 `AppId::bundle_id`,`NSWorkspace`);`.app` 一律经 `open -a` 交给 Launch Services,正在运行的应用经其打开请求收下文件(不另起一个),其余直接启动。应用切换:每个应用的应用菜单有“打开 Ely 应用”子菜单,只列已安装的其他 Ely 应用(`commands::switch_to`,八个无参数动作),启动失败即提示原因。导出后查看:导出、提取、拆分、删除标记内容、清理、保护、签名与附件另存成功后,提示带“打开”:PDF 在已安装的 ElyPDF 打开(作为新标签,不当作原编辑会话),否则与其他文件、文件夹一样交系统默认程序(gpui `open_with_system`);ElyPDF 打开失败即提示原因,不改交系统。ElyMark 导出缺图、图或公式时的警告同样带“打开”。Markdown 查看版本:ElyMark 状态栏的 Git 项把文件本身交给 ElyTree,ElyTree 收到文件即打开其所在仓库并显示该文件的历史(`Tree::open_path` 按文件所在文件夹开仓库,再以仓库内路径 `file_log`);不自动提交,不建仓库;未安装 ElyTree 时提示从发行包安装。真窗口(macOS):`mark-output.sh` 核对 ElyTree 打开该文件的历史、导出警告的“打开”在 ElyPDF 打开 PDF;`release-macos.sh` 从 ElyDoc 的应用菜单启动 ElyMark。AppKit 自己的窗口恢复关掉(`startup::no_window_restore`,`ApplePersistenceIgnoreState`):Ely 自己恢复会话;原先应用被强退后再启动,AppKit 先弹模态“是否重新打开窗口”,访达交来的文件被挡在后面(打包测试实测)。窗口测试的 `seen` 不看系统菜单(苹果菜单的“最近使用的项目”会列出别处打开过的同名文件)。未验证:Windows 与 Linux 的同级查找与系统打开;系统默认程序打开(会在本机弹出别的应用,未自动测)。 +- 2026-10-08:ElyMark 的单行 `$$…$$`(用户以 Pandoc 示例发现):remark-math 把它读成行内公式,序列化只写一个 `$`,与原文不同,整段退成原文块。现按原文记下每个行内公式的 `$` 数(`math.js`),两个及以上以显示模式排出(同 GitHub),写回照原数;导出同样以显示模式。Pandoc 专有的表格、定义列表、行块不是 CommonMark/GFM,照 §14.3 以原文块保留(`---- ----` 在 CommonMark 里是分隔线)。 +- 2026-10-08:ElyMark 书写视图、输出、Git 配合、大文件(C14.7,MARK-09、11、13、14,PERF-07)。书写视图:“视图”菜单的专注模式(⇧⌘F,收起标签条、左栏与状态栏;可视模式里光标所在的顶层块之外淡到 0.35,源码里当前行之外同),打字机滚动(插入点所在行保持在视口中部;上下各留半屏,首尾行也能居中),正文宽度窄、中、宽(36、48、64rem),字体无衬线、衬线、等宽,文字大小(⌘= ⌘- ⌘0,70% 至 200%,每档 10%)。全部在页面里以 CSS 实现(`look.js`:根元素的 data 属性与 `--ely-text-scale`),Markdown 不变;设置存在数据目录的 `look.json`(读不出即报错并用默认值),菜单随之打勾,启动时恢复;淡入淡出遵守系统的减少动效。组件库的 `ZenMode` 不用(见缺口)。输出(“文件›导出…”⌘E 一个对话框选 HTML、HTML 与资源文件夹、PDF、纯文本;“打印…”⌘P):页面以 unified(remark-parse、remark-gfm、remark-math、remark-rehype、rehype-katex、rehype-stringify,版本写死)把编辑器此刻的全文(与懒挂载、大文件无关,不截断)渲染成 HTML,Mermaid 用浅色主题画成 SVG(画不出的照原代码块并在导出后提示);front matter 不进输出。HTML 内嵌导出样式表(构建时单独产出 `export.css`:KaTeX 样式与 woff2 字体内嵌、浅色排版)与图片(文档相对路径、百分号转义、file URL 读成 data URL;网址照原样;读不到的图片照原链接并提示);HTML 与资源文件夹写 `名称.html` 与 `名称_files/`(样式表与图片副本,同名图片按序号区分;文件夹已在即拒绝,写坏即删去);纯文本以 hast-util-to-text 按块换行,公式写 TeX;PDF 先把同一份内嵌 HTML 暂存到工作目录,在不显示的 CEF 视图里由 Chromium 打印(`WebView::print_pdf`,结束经 `Event::Printed` 报回),再交到所选位置;打印同一份 PDF 交系统打印面板(`ely_office::print_later`)。导出都经 `ely_files::export`(不覆盖打开着的文件)。富文本复制用 Chromium 自己的复制(剪贴板带 HTML)。Git(MARK-13,不做第二套客户端):Git 的查找与运行环境移入 `ely_platform::git`(`locate`:`ELY_GIT`,应用旁,ElyTree 包内的 Git,本构建的 `engines/git`;`command`:只读不抢锁等同一套环境变量),ElyTree 照用;ElyMark 在后台对当前文件问 `rev-parse --show-toplevel` 与 `status --porcelain=v2 --ignored`,状态栏显示未改动、有改动、未跟踪、已忽略、有冲突(问不出即“未知”,提示里是原因;不在仓库里不显示),按一下以仓库根打开 ElyTree(`ely_platform::apps::open`);切换标签、每次保存(指纹变化)、外部改动与窗口回到前台时重问。大文件(MARK-14):超过 2 MiB 的文本以源码打开,上方提示条写明大小与“可视与预览已关闭,保存保留全部内容”,切到可视或预览即提示不切;保存与导出都是全文。PERF-07 实测(macOS,release):1 MB 文档自启动到可视可编辑 1.4 秒(首次冷启动 2.0 秒);4 MB 在可视下约 5 秒,故按源码打开。真窗口(macOS,`tests/scripts/mark-output.sh`):Git 状态未改动→键入自动保存后有改动→按下打开 ElyTree 于该仓库;专注模式收起标签、存入 `look.json`、重启仍在,文件不变;四种导出的内容(KaTeX、SVG、内嵌图片、全文;资源文件夹的样式与图片;PDF 以 pdftotext 读出文字;纯文本含 TeX);打印生成 PDF;3.3 MB 文件以源码打开、⌘1 不切、末尾编辑后全文保存、纯文本导出含最后一行;`ELY_CLIPBOARD_TEST=1` 时可视全选复制带 HTML。`mark-content.sh` 的 look 步核对淡出、窄列、衬线与插入点居中。去掉专注收起、图片内嵌、大文件阈值、Git 状态或截断文本导出时各自失败。窗口测试须在英文输入法下运行(中文输入法会把模拟键入收进组字)。导出时每张图定为一个 `src`(去掉 `srcset`、`sizes` 与 `picture` 的 `source`,无 `src` 时取 `srcset` 的首个地址);公式的 KaTeX 错误随导出提示(行号与 KaTeX 的说明),不当作完整导出。引用正文的提示走外壳的 `toast_unlogged`,日志只记计数与行号;组件库 28a6bba 起 `Toaster` 只记提示的编号与级别,不记标题。未验证:Windows 与 Linux;Windows 的 Git 路径在 ElyTree 包内的位置。 +- 2026-10-07:ElyMark 外部变更、自动保存与会话恢复(C14.6,MARK-08、12,§6.2、§6.3、§14.4,QA-22)。监视:一个 `notify` 监视器盖住各标签文件所在的文件夹(不递归),200 毫秒内的一串改动合成一次查看;按会话指纹(长度与修改时间)比对,自己的保存不触发。文件改了:读到稳定的字节(读前读后指纹相同,最多五次),无未保存内容(含页面还没送回的编辑)即原地换成磁盘的文字(`ely.refresh`:插入点所在的文件行与滚动尽量保持,时间线从这份文字重新开始),会话重载指纹并以新文件身份登记;有未保存内容即不动磁盘与编辑器,状态为“外部已改”,自动保存停止,上方提示条“比较…”。比较:组件库 `git::regions` 对“上次载入或保存时”的文字、你的与磁盘上的三方合并,`ThreeWayMerge` 逐个冲突选边(编辑器的文字先落定;比较期间磁盘再改即重读重比,选定前核对所读内容的指纹,不符即提示再比较一次、不写入),可“用磁盘上的”“保留你的”(立即覆盖写入)或冲突都选完后“用合并的”(磁盘的版本成为新的基底,合并结果作为一步可撤销的编辑,随后自动保存)。路径不见了:等 300 毫秒(原子替换的空档)仍不在,即在原文件夹里按文件身份(设备与 inode,不认符号链接)找改名后的文件(`Sessions::renamed_in`),找到即标签跟过去(与 F2 改名同一条路);找不到即为已删:内容留着,提示条“另存为…”,不再自动保存、不重建文件。读不出(权限等)或监视器出错:提示条写明原因与“再查一次”;“视图›检查磁盘上的文件”随时手动查看(网络目录等不保证有事件)。自动保存(§6.2):有路径的文件停手 1 秒后保存(`ely_files::PolicyEngine`),外部改动或删除后停止,选定处理后重新开始;失败照常留为未保存。会话(MARK-12):数据目录的 `session.json` 记各标签的路径(未命名为空)、名称、模式、插入点所在的文件行与滚动;每 10 秒有未保存内容且有变化的标签写一个恢复点(`ely_files::Recovery`,每标签一个文件夹,20 点 7 天);退出(Cmd+Q 或关窗)开始时先记下当时的标签;`session.lock` 写进程号,正常退出删去它与全部恢复点;下次启动锁还在且进程已不在即为崩溃(`ely_files::alive`)。启动时打开记下的文件,标签显示后换成记下的模式与位置;崩溃后的未保存内容从最新恢复点放回,一律开成未命名副本(有路径的文件照磁盘打开,从不先覆盖原件,§6.3;原件已不在也照样开副本并提示哪个文件没能打开)、标为未保存,并提示“恢复了某时未保存的内容;撤销历史未恢复”(§6.3 区分内容与历史)。显式“不保存”后正常退出,下次不当作崩溃,未命名的内容不回来。真窗口(macOS,`tests/scripts/mark-outside.sh`):原子替换后干净标签换成新文字、键入后自动保存;未保存时外部写入不被覆盖,比较三栏、保留你的写回;互不冲突的两处改动合并后都在;外部改名后标签名跟随、保存写到新名、旧名不重现;改成无权限读即提示、恢复权限后“再查一次”重载;删除后不再自动保存、不重建;退出后重开回到源码模式与原来的行;未命名文档写过恢复点后 `kill -9`,重开内容回来且关闭时问是否保存,选不保存后再开不回来。去掉干净重载、改名跟随、删除时停自动保存、读错提示、崩溃内容、退出时记下标签或恢复的位置时各自失败。启动时先显示可视再换到记下的模式(一次多余的绘制,已知)。启动参数给了文件时只开它们与崩溃恢复的副本,不连带上次的标签。“保留你的”以所比较的指纹为基线(`Session::rebase`)走普通保存,之后的外部改动照样冲突;自动保存撞上外部改动时交给比较(`Host::conflicted`),不弹外壳的冲突框;外壳在询问取消、保存面板取消、保存失败、冲突询问取消时都调 `Host::close_cancelled`,关闭标签时调 `Host::tab_closed`(ElyMark 即刻删其恢复点并更新清单);每个标签只在首次显示时恢复一次;缺列的位置读作 0。组件库 f756043:脏标签与非当前标签的关闭按钮透明而非隐藏,辅助技术可按。外壳冲突询问的“保留我的”以此刻磁盘的指纹为基线走普通保存(导出期间再改照样冲突;文件已不在时直接写),“从磁盘重新载入”先结束挂起的关闭;退出途中首次保存的标签以新路径记入清单。取消退出(未保存询问点取消)后清单照常跟随(`Host::close_cancelled`);关闭或放弃的标签的恢复点在下一次保存会话时删去。位置记文件行与行内列。“不截断导出”随导出在 C14.7。未验证:Windows 与 Linux(`alive` 的 Windows 实现未构建);网络目录。 +- 2026-10-07:ElyMark 编辑工具与附件(C14.5,MARK-06、07)。查找替换(Cmd+F):组件库 `FindWidget` 盖在页面右上,查询与替换是两个 `TextInput`,带区分大小写、全词、正则三个开关;可视用 `prosemirror-search`,源码用 `@codemirror/search`;总数与当前序号由页面按不重叠的命中报回;正则写错时框内显示 JS 的原话(按两个编辑器所用的 `gmu` 标志编译);空的或无效的查询不执行查找命令(CodeMirror 对它会打开自带的面板);零宽命中按码点前进(落在代理对中间时 Unicode 正则会往回答,原先反复收集同一处直至页面崩溃);预览不替换;换模式或换标签后按同一查询重查。查找框整窗一个,各标签的页面各记其查询:下一处、替换每次都带上框里此刻的查询与替换文字,标签一换即按框里的重查。查询变了从当前命中的起点往后找,键入替换文字不移动命中。替换框回车替换一处。缩进:源码 Tab 与 Shift-Tab 为 `indentWithTab`;可视在列表里 Tab 下沉、Shift-Tab 上提,不在列表时吞掉;代码块同源码。WebView 在自己的键位上下文绑定 Tab 与 Shift-Tab 交给页面(组件库 K02 起更深的上下文先得),焦点不离开编辑器。列表续写:可视为 Milkdown 自带,源码为 lang-markdown 的回车续写。“表格”菜单:插入表格(3×3)、上下加行、左右加列、删行、删列(prosemirror-tables);插入后插入点落在新表第一格(Milkdown 在段末插入时把插入点留在段里,之后的行列操作落空);插入点不在表格里、或在源码与预览里即提示。代码高亮见 C14.3。附件:拖放图片文件或粘贴剪贴板里的 PNG,复制到文档旁的 `assets/`:拖放保留原名、重名加序号,粘贴名为 `pasted-毫秒.png`;都以 `create_new` 占名,从不覆盖已有文件,写坏即删去半成品(`ely_files::import_attachment` 原先先查后拷,其间出现的文件会被覆盖);在插入点写入相对链接,名称按百分号转义,源码里替代文字的方括号与反斜杠加转义;复制期间文档另存到别的文件夹即不写链接,提示图片留在原文件夹的 assets;未命名文档、预览与非图片文件拒绝并说明。粘贴经 `WebView::intercept_paste`:宿主先看剪贴板,图片自己处理,其余照常交给页面。macOS:CEF 离屏输入不带 AppKit 编辑命令,而 Blink 在 macOS 上只凭这些命令处理 Cmd+方向键与 Cmd+Backspace(Option 组合 Blink 自己处理),可视编辑器里这些键原来不动;`ely-web-content` 的键盘处理器(`edit.rs`)在页面没处理这些键时照 AppKit 标准键位以 `Selection.modify` 移动或扩选到行首行尾、文首文末,Cmd+Backspace 与 Cmd+Delete 删到行首、行尾;CodeMirror 自己处理的键不经此。组件库 8f4729c 与 aa0c99d:开关按钮(`ToggleButton`、`ToggleGroup` 的各项)在 AX 树里是按钮,名为其文字或提示,带按下状态。真窗口(macOS,`tests/scripts/mark-tools.sh`):可视全部替换,源码正则替换,Tab 与 Shift-Tab,列表续写,Cmd+Down 后插入表格、加行加列、删列,拖放图片(自带的拖动源)复制到 assets 并写入转义的链接;`ELY_CLIPBOARD_TEST=1` 时粘贴图片同样落地;复制被拖住(FIFO)时经菜单另存到别处,不写链接;两个标签各设查询后回到前一个全部替换按框里的;键入替换文字后替换一处落在原命中;源码拖入 `a]b.png` 写成 `![a\]b](…)`。去掉 AppKit 键、正则、Tab 绑定、插入点修正、文件夹核对、起点查找、标签跟随或转义时各自失败。`tests/scripts/comments.py` 另查 Swift 测试助手与页面的 JS 源码。本机有全局快捷键占用 Cmd+Shift+S 与 Cmd+Shift+Z(空白 AppKit 窗口也收不到),所以测试经菜单另存。未验证:Windows 与 Linux。 +- 2026-10-07:ElyMark 导航与搜索(C14.4,MARK-05、MARK-10,MARK-01 的打开文件夹)。左栏三页(分段控件):文件、大纲、搜索。打开(面板可选文件与文件夹,启动参数同)文件夹即在后台按其真实路径列出其下的 Markdown 文件(`folder.rs`:`.md`、`.markdown`,不进点开头的文件与文件夹,不跟随链接,只在所开的文件夹里,不扫整盘),以组件库 `lists::FileTree` 显示(只画可见行,可过滤),选中按回车或双击打开;窗口回到前台、改名之后即重列,列表变了即重搜文件夹。没开文件夹时文件页是最近列表(`ely_files::recent`:数据目录的 `recent.json`,新的在前,留 20 个,读不出即报错且不覆盖;ElyTree 的同一套移入此处共用),打开成功的文件与列出的文件夹记入,按真实路径(同一文件只一条);标签的路径变了(改名、另存为)即记入新路径,旧路径已不存在即移出。大纲:页面在显示后与每次送出全文时报出当前编辑器的标题(可视与预览取文档里的标题节点,源码取 CodeMirror 的语法树,ATX 与 Setext),以 `lists::Outline` 显示,原文块里的标题也在内(以 remark 读其源码,引用、列表中的标题同样列出,跳转落在标题文字上),选一项即把插入点放到该标题并把键盘焦点交给文档(预览滚到该处)。搜索:本文件或文件夹(打开了文件夹才有),按字面、不分大小写逐行找,打开着的文件按其当前文字(含未保存的),其余读盘,读不出或不是 UTF-8 的文件以“某文件未能搜索”的行列在结果最前(带原因),最多 500 条;本文件的结果随当前标签:切换标签即重搜,较早的搜索迟到即弃;结果列出行文字与“文件:行”,选中回车即打开(已开即转到,按真实路径认标签)并把插入点放到该行的那一处:源码定位到该行的命中,可视按写出时每个块在全文里的位置(`preserve.js` 的写出同时给出各块的区间)找到该行所在的块,再按块内此前命中的个数落到块中对应的命中(此前的行先经 Milkdown 解析,按编辑器所见的文字计数,链接地址等不算,与块内查找同按不重叠计;原文块照源码计数;该行所见文字没有查询词时——命中只在链接地址等处——落在该行开头);行号自文件开头算,可视与预览先减去 front matter 的行数,落在 front matter 里即滚到它。页面未显示完就要求的定位等显示后再做。多标签由外壳。真窗口(macOS,`tests/scripts/mark-places.sh`):文件树不列 .txt、点选回车打开,大纲跳到二级标题后键入落在标题里,文件夹搜索找到两个文件的行、不搜 .txt、选中结果打开并在该行键入,最近列表重启后还在、从中打开文件夹。组件库 dabc1eb 起树与树行有无障碍角色(树、树项,名称、层级、选中、展开);大纲项仍没有(测试按位置点选)。未验证:Windows 与 Linux。 +- 2026-10-07:ElyMark 编辑模式、文件与语法(C14.3,MARK-01 至 04,§14.3)。模式:可视、源码、预览,“视图”菜单与 Cmd+1/2/3,状态栏右侧分段控件;每个标签各记其模式,页面回落到源码时标签随之。切换在页面里进行(`ely.mode`):先取当前编辑器的全文并立即送出待送的全文,再以同一文字显示新模式;每次切换都新建目标模式的编辑器(不再保留隐藏的编辑器)。本文件只有一条时间线:带着编辑离开一个模式时,把离开时的全文记入时间线;编辑器自己的撤销用尽(各看其撤销深度,Milkdown 的历史键位排在我们的之后)时再按撤销,回到时间线上一条全文、以当前模式重建(另一段的编辑作为一步撤销),重做用尽时向后走;只有真正的编辑会截掉时间线上其后的部分,撤销、重做的跳转不算编辑;离开时编辑器还有可重做的步骤,就把全部重做后的文字记在时间线前方,回来后重做即到那里。模式请求按序号只认最新的:编辑器构建期间再要的模式记下,构建完且仍是最新请求时显示它,此后的新请求清掉它;较旧的显示回报回来时序号不符即退出,不碰排队的请求。预览每次新建、只读(正文与代码块)。切换本身不改文件。文件:新建(空的未命名 `untitled.md`,保存走另存为)、打开、保存、另存为(外壳)、重命名(F2,“文件”菜单):`commands::RENAME` 与 `commands::RENAMING`(含重命名的文件命令表),`document_root`/`set_document_menus` 改收文件命令表;ElyMark 的 `rename.rs` 在原文件夹里改名(只有它用,不放外壳),`ely_files::rename_new` 不覆盖已有文件(macOS `renamex_np` 加 `RENAME_EXCL`,Linux `renameat2` 加 `RENAME_NOREPLACE`,Windows `MoveFileExW` 不带替换;后两者未构建验证),名称须不含斜杠且扩展名为可保存的格式,未命名或正在保存的标签不改名,改名完成时标签须仍开着且仍是原路径,否则只记日志、不动登记;同文件登记记下打开时的文件身份,原路径已不存在而身份相同即为同一文件(改名间隙里打开新名字会转到原标签),改名后以 `Sessions::track` 迁移登记(不关再开);外壳每次保存成功后也以它更新登记的路径与身份(原子保存换了文件);标签名与 `Session::renamed` 的路径随之,内容与指纹不变。文件 URL:Windows 的驱动器路径写成 `file:///C:/…`、UNC 写成 `file://主机/…`,去掉 `\\?\` 前缀,围栏反向换回 Windows 路径再取真实路径(字符串转换有单测,Windows 上未运行)。CEF 的请求处理移入 `fence.rs`(`handlers.rs` 原超过 500 行)。文档旁的图片:页面 `` 设为文件所在文件夹,`WebView::allow_images` 让该文件夹只放行图片请求(资源类型为图片;导航、脚本仍只限内容文件夹),另存为或改名后随文件移动(已显示的相对图片不重载,已知限制);Mermaid 的包按脚本自身位置载入,不受 `` 影响。Milkdown 7.22.2 的图片解析把无标题图片的 `null` 标题交给要求字符串的属性,ProseMirror 拒绝、Milkdown 记错误后丢掉整张图;`image.js` 改为空字符串。实测(macOS,`tests/scripts/mark-modes.sh`):文档旁的图片画出(截窗口量色),三种模式往返不改文件,预览不收键入,可视里键入后经预览回来撤销得回原样,源码里的改动回到可视并保存,改名到已有文件被拒、已有文件不动,改名到新名后文件移动、继续编辑保存到新文件,新建打开空的可视文档(读外壳写下的 `untitled.md` 须为空),可视键入、源码键入、回可视后两次撤销回到两者之前、重做回来(撤销跳转当作编辑、丢掉时间线时失败)(去掉预览只读、去掉保留编辑器、去掉旧编辑器的链接时测试失败);`mark-content.sh` 在同一段脚本里连要两种模式,最后显示后者(去掉记下的模式时失败);可视已显示、其回报未答时再要源码,最后为源码(不核对最新请求时失败)。“直接打开文件夹”随文件树在 C14.4。未验证:Windows 与 Linux。 +- 2026-10-07:ElyMark 文件会话与原文保留(C14.2,§14.3,QA-21)。ElyMark 用 `ely-office` 的外壳(标签、打开、保存、另存为、关闭询问、冲突),引擎 `Mark`(`apps/ely-mark/src/engine.rs`):载入读字节,UTF-8 带不带 BOM 都可,UTF-16 与非 UTF-8 即报错并指出字节位置(`text.rs`);编辑器拿到去掉 BOM、CRLF 换成 LF 的文本;写出时文本与载入时相同即原样写回原字节,否则补回 BOM 并按载入时多数的换行(CRLF 多于单独 LF 即 CRLF;混用换行的文件编辑后统一成多数的那种,已知限制)。外壳的 `Engine::open` 改为收窗口、返回 `io::Result`(网页视图要窗口,页面开不了即为打开失败)。每个标签一个页面;页面每次改动立即报 `{"kind":"changed","version"}`,停手 300 毫秒后报该版本的全文 `{"kind":"text"}`;外壳的“已落定”即最新改动的全文已到,保存等它;历史标记为全文的长度与散列,撤销回原样即为未修改;外壳保存时先写出再读标记,两者之间页面可能送来新全文,所以写出时记下所写文字的标记,紧接的读标记交出它,之后的编辑仍算未保存。可视模式保留原文(`preserve.js`):照 Milkdown 自己的 remark 流程(`parse` 加 `runSync`)读出原文各顶层块的位置,与编辑器的顶层块一一对应(数目不同或有块无位置即不能对应;空文件 Milkdown 仍有一个空段落,照空处理)。每个顶层块单独序列化,与原文逐字相同的才是可视块,否则换成原文块(`ely_raw`:等宽、可编辑的纯文字,写出即其文字,不进撤销历史的一次替换);块间 Milkdown 不显示的原文(如链接定义:Milkdown 把引用链接内联、删去定义)也成原文块,所以没有藏起来的原文。序列化的标记照该文档自己多数的写法(`style.js`:无序与有序列表标记、强调、加粗、代码围栏、分隔线、Setext;以 remark-parse 统计后设 `remarkStringifyOptionsCtx`),写法一致的文档几乎全为可视块,与多数写法不同的块成原文块。写出时按块做 Myers 差异(`diff.js`;ProseMirror 未改的块是同一对象,否则按内容比较),未改的块与块间原文逐字照抄,只有改动的块用 Milkdown 序列化。YAML front matter 不进可视编辑器,以原文显示在上方,写出时原样接回。不能对应的文档直接以源码模式打开(可编辑,无损),并报 `fallback`。打开后插入点在文档开头。页面每次载入是一代(应用计数,随 `ely.show` 传入),回报带代数,别的代的回报丢弃;载入时版本计数归零,重载后显示最后收到的全文。故障视图按“重新载入”后焦点回到页面(原先留在消失的按钮上,键入无处可去)。普通 Markdown 读文件的权限错误不交外壳当作加密文件(外壳把 PermissionDenied 当作要密码),照系统原话报出;外壳打开失败另写日志。实测(macOS,`tests/scripts/mark-session.sh`):BOM、CRLF、front matter、Setext 标题、`1)` 列表、HTML 块、`~`删除线、硬换行、链接定义与 `[[...]]` 的文件,不改保存逐字节相同;在首段键入一字保存,除该段多一个字外逐字节相同(整篇序列化、去掉 BOM 时该测试失败);段落里的 `[[...]]`、引用里的 Setext 与 `[[...]]`、只有链接定义的文件都以原文块显示,键入一字只多一字;在链接定义前向前删除不移动、不修剪它;渲染进程被杀、按重新载入后再键入的字照样保存;无读权限的文件报“Permission denied”、不问密码(不用原文块、不按代丢弃回报时测试失败)。未验证:Windows 与 Linux;模式切换与撤销(C14.3)。 +- 2026-10-07:ElyMark 内容包(C14.1,§14、§17.3)。网页源码在 `apps/ely-mark/web`:版本全部写死,`package-lock.json` 锁定;`scripts/build-mark-content.sh` 以 `npm ci --ignore-scripts` 安装、esbuild 打成经典脚本(file:// 下模块脚本被 Chromium 拦下)到 `engines/content/ely-mark`,附 `THIRD-PARTY-NOTICES.txt`(按打进包的每个包取其许可文件;没有许可文件的只许 MIT,以清单的作者与 MIT 原文写出,构建时列出)。包:Milkdown 7.22.2(commonmark、gfm 含脚注与任务、history、代码块组件)、CodeMirror 6(代码块与源码模式,语言取 language-data,全部打进包)、KaTeX 0.19.0(字体随包)、Mermaid 12.1.0(单独的 `diagram.js`,首次遇到图时才载入;`securityLevel: strict`,不在页面上留错误图)。数学按 Crepe 的做法(remark-math,块公式为 LaTeX 代码块)。只渲染文档里保存的源码;公式与图出错时显示错误(公式带位置,图带行号)与原文,不清空代码块。页面接口 `ely.show(markdown, mode, texts)`,mode 为 visual、source、preview;每次显示一个新容器与序号,较早的显示若后完成即销毁其编辑器、不报告,页面上始终只有最后一次的编辑器;预览时正文与代码块都只读;显示后经 `elyQuery` 报 `{"kind":"shown"}`。颜色全部取 `--ely-*`(CodeMirror 的高亮、Mermaid 的主题变量),随系统深浅色:Milkdown 把预览净化后拷进页面(不保留我们给的元素),且每次调用给的回调都是新函数,所以 Mermaid 预览先返回带编号的空占位,绘制结果只在该占位仍在页面上时经回调落下(改语言、改文字、卸载都换掉占位,旧结果即弃);绘制在全页一条队列里依次进行,每次绘制带代次,调用 Mermaid 前与落下前都核对属主、占位与代次,失效即跳过(编辑器销毁后排着的绘制不再执行);`:root` 的样式一变,凡占位仍在页面上的图(含正在绘制的)都以新颜色重画,此前的结果作废,首次落下的就是新颜色。记录归各编辑器所有,编辑器销毁即释放,占位已离开页面的记录在下一次绘制或换主题时清掉。Milkdown 把视口外的代码块换成占位(懒挂载),滚入视口时再建。界面文字由应用给(`mark.*`)。打包时 `bundle.sh` 把 `engines/content/<应用>` 放进 `Resources/content`,缺即失败;应用先找包内,再找本构建的 `engines/content`,都没有即报错。ElyMark 启动后显示一个空的可视文档(文件会话在 C14.2)。实测(macOS,`tests/scripts/mark-content.sh`):三种模式下标题、任务、表格、脚注、行内与块公式、代码块(逐块滚入视口,须可见、未被遮挡)、Mermaid 图(不小于 100×80)与两种错误,切换深浅色后图的颜色随之变,连续两次显示只留并只报后一次,截窗口背景色占比低于 0.97,KaTeX 字体已载入,无越界请求;`.app` 包内页面显示。未验证:Windows 与 Linux;树莓派上 2.5 MB 主脚本的载入时间。 +- 2026-10-07:ElyTree 分支、标签、合并、变基、冲突(C13.4,TREE-07 至 09,§13.3)。Git 一律以 `GIT_EDITOR=true`、`GIT_SEQUENCE_EDITOR=true` 运行(没有终端可开编辑器,合并与撤销照 Git 自己拟的信息);失败时 stderr 为空即报 stdout(合并的冲突写在 stdout)。左栏第三页“分支”:本地分支(当前在前)、远程分支、标签(新的在前),`for-each-ref` 读出(附注标签取所指提交与标签自身的说明,`origin/HEAD` 不算分支,上游已删即不显示领先落后),上方“新建分支…”“新建标签…”;选中一个,中间列出提交、日期、上游与说明,以及适用的动作:切换(远程分支为 `switch --track refs/remotes/名`,不被同名标签劫走)、合并到当前分支、把当前分支变基到此、在此新建分支、重命名、删除(`branch -d`,Git 拒绝时报原话)、强制删除(`-D`)、删除标签。历史页提交详情多出拣选、撤销提交(合并提交以 `-m 1`)、在此新建分支、在此新建标签。新建分支可选“并切换过去”(`switch -c`);标签有说明即附注标签(`tag -a -m`,签名照用户的 `tag.gpgSign`)。变基、删除、强制删除、删除标签、中止、跳过每次都先问(每个确认只对这一次,§13.3),合并、拣选、撤销、继续不问。进行中的操作按 Git 目录里的 `rebase-merge`/`rebase-apply`、`MERGE_HEAD`、`CHERRY_PICK_HEAD`、`REVERT_HEAD` 判定(每次状态重读时读,只取最新一次),中间上方的提示写明正在把谁并到谁(变基时“当前侧”是新基底、“另一侧”是正在重放的提交),给继续(`<操作> --continue`)、跳过(合并无此项)、中止。命令失败时,只有合并、变基、拣选、撤销,且 Git 的输出有以 `CONFLICT (` 起头的行、失败后索引仍有未合并条目(`ls-files --unmerged`),才提示“因冲突停下”;其余(如钩子拒绝、冲突中 `checkout --ours` 的钩子失败)对话框显示 Git 的原话。失败的报错带上 stdout 与 stderr 两者(拣选的 CONFLICT 行在 stdout,钩子多写 stderr)。分支与标签的名称取自完整引用名(`refs/heads/`、`refs/remotes/`、`refs/tags/` 之后),同名的分支与标签各归各的;合并与变基用完整引用。分支行内的切换与删除接到同样的入口(删除照样先问)。冲突文件:中间上方是三侧与 `ThreeWayMerge` 的块级选择,各块照工作树文件里 Git 写下的标记分组(不另算一套合并;标记宽度取 `check-attr conflict-marker-size`,照 Git 的 `strtol_i` 读,不是正整数即 7;行须恰为该宽度,长短不同的同形行照正文;只按 LF 断行,同 Git,正文里的独立 CR 不算行界),基底取自 diff3 标记,没有时取 Git 自己 `merge-file --zdiff3 --marker-size` 对三个索引段的结果(块数相同时,只填没有基底的块,diff3 里空的基底照空);下方是可编辑的结果(`CodeEditor`,初为工作树里的带标记文件);选一侧即在结果里解决对应的那一块标记(其余手改照留),结果里的标记数与未选的冲突数对不上时(已手改过)不再按块选,提示在结果里改;“保存”只写工作树文件(经同一仓库的写队列,与 Git 的写操作不重叠;落地时按仓库与路径记下;路径已成符号链接即拒绝,不顺着链接写),“标为已解决”只做 `git add`(须先保存;结果里还有标记时先问),两者分开(§13.3);另有“用当前侧的文件”“用传入侧的文件”(`checkout --ours/--theirs`),一侧没有该文件时还有“以删除解决…”(`git rm`,先问)。符号链接、子模块、任一侧含 NUL(Git 视为二进制)、不是 UTF-8 或缺少的冲突整份解决:写明情形,给“用当前侧的文件”“用传入侧的文件”(只给有该文件的一侧)、“标为已解决”与“以删除解决…”。重读时结果有未保存的手改即保留并提示。真窗口(macOS):合并冲突的提示条、三侧与按块取另一侧后结果更新。未验证:Windows 与 Linux;交互式变基(不提供)。 +- 2026-10-07:ElyTree 提交、历史、Blame(C13.3,TREE-05、06)。左栏分“改动”与“历史”两页(`navigation::Tabs`)。提交:改动页下方是 `git::CommitInput`(摘要与说明两个框,摘要满 50 变黄、72 变红),上面一行是 Git 设置里的身份(每次状态重读与窗口回到前台时重读,只取最新一次;`user.name `,未设即写明,点它开身份对话框:姓名、邮箱(两个输入框以其标签为无障碍名),可勾“用于所有仓库”写 `--global`,否则写本仓库;空着即提示不写)与签名(`commit.gpgsign` 为真时写明以 `gpg.format` 签名,默认 openpgp),签名照用户自己的 Git 与工具,Ely 不另设。提交框按仓库各存一份(换标签时存下当前的、换上另一个的:摘要、说明、修改最近提交与其草稿),提交为 `commit --cleanup=whitespace -F -`(信息经标准输入,`#` 开头的行照留,用户的 `commit.cleanup` 不删它们,不经 shell),“修改最近一次提交”勾上即显示为勾选、读出上次的信息填入(读回前不能提交;先存下已键入的,取消勾选时放回;每次勾选或取消、换仓库都使在途的读取作废,读回时须仍是发起的仓库与那一次),提交成功后只在提交框仍是所提交的内容时清空(提交期间又键入的留着,别的仓库的提交框不动);钩子或签名失败时以对话框显示 Git 的全部原话,不只说失败。提交同其他写操作排队。历史:`log --topo-order --exclude=refs/stash --all`,每页 2000 个,滚到距末尾 20 行内读下一页;每次状态重读后比对 `show-ref --head`,引用变了(含外部提交、切分支、取回)即从头重读;读取期间来的比对记下,读完再比;引用读不出时历史栏显示 Git 的原话,之后读通即重读并清掉;提交图由 `git::lanes` 按已读的全部提交算出;时间按本机时区写到分钟(不用相对时间,长列表不必随时刷新)。搜索:在“提交信息”(`--grep`)、“作者”(`--author`)、“改动的文字”(`-S`)中按字面、不分大小写找,或按提交号(`rev-parse --verify`,前缀可);搜索时不画图。选中一个提交:中间上方是摘要、说明、作者与提交者(不同时才列)、完整提交号、父提交(可点,转到父提交)、签名状态(`%G?` 按字母译出,带签名者)、引用;下方左列是相对第一个父提交改动的文件(`diff --name-status -M` 与 `--numstat`,根提交对空树,空树由 `hash-object -t tree` 算,不写死 SHA-1),右边是所选文件的差异(与改动区同一读法:Git 自己的差异,图片、二进制、非 UTF-8 同样处理,无块的纯重命名显示为无改动)。改动区差异与历史页所选文件的标题栏都有“文件历史”与“逐行追溯”(改动区限已跟踪的文件):文件历史为 `log --follow --numstat -z`,跨重命名,每个提交带当时的路径与行数,选中即显示该提交里这个文件的差异(状态按两侧是否存在判定);逐行追溯为工作树文件的 `blame --porcelain`,未提交的行写“尚未提交”,每段首行可按,转到历史页的该提交;条色按提交时间新旧。读取都在后台,按发起时的仓库、提交与文件核对;文件历史与逐行追溯每次打开有窗口内唯一的序号,迟到的结果即弃。提交详情的作者、提交者、提交号、签名、引用、摘要与说明在 AX 树里有文字。真窗口(macOS)经 AX:历史页、提交图、引用、提交详情、文件列表与差异。未验证:Windows 与 Linux;GPG 与 SSH 实际签名(只读设置,签名由用户的 Git 完成)。 +- 2026-10-07:ElyTree 差异与暂存(C13.2,TREE-03、04)。差异一律取 Git 自己的输出(`-c diff.suppressBlankEmpty=false diff --no-color --no-ext-diff --no-textconv -U3`,前缀写明 `a/`、`b/`,覆盖用户的 noprefix 等设置;空白的上下文行照样带前导空格):已暂存为 `--cached -M`(HEAD 对索引,即提交前的实际索引内容),改动为索引对工作树,未跟踪为 `--no-index` 对 `/dev/null`(退出码 1 即有差异);Git 的清理过滤(换行转换等)由它自己做,按块、按行暂存的补丁对得上索引。块之间未改动的行从旧侧的 blob 取(`cat-file blob`,已暂存为 `HEAD:旧路径`,改动为 `:路径`),可展开;行号按块头推算,旧文件比差异短即报错。改动的词按每对删去与加入的行标出(Unicode 词)。空白:精确、忽略空白变化(`-b`)、忽略全部空白(`-w`);行内与并排。按块或按行:只在精确比较、且是普通内容改动时(无新建、删除、重命名、复制、权限变化的头),已暂存区为取消暂存(`apply --cached --reverse`),改动区为暂存(`apply --cached`);未跟踪、重命名等整份暂存,界面写明。补丁由所选行切出:暂存时未选的删去行留作上下文、未选的加入行不写;取消暂存时反之;块头按“范围前的行数”换算,空范围照 Git 的写法;“无换行结尾”按切出后两侧的实际行尾:无换行的行在哪一侧后面还有行,那一侧就带上换行(所选的删去或加入行同样),两侧不同的上下文行拆成一删一加;`apply` 带 `--whitespace=nowarn`,用户的 `apply.whitespace` 不改所选的字节。写入前在后台重读同一条差异,与所选时读到的字节不同即拒绝(“文件在显示后改了,未暂存”),索引不动;同一仓库的写仍排队。所选行按住 Shift 取同一块内的范围;差异一变(任何重读得到不同的输出)所选即清空;每次读取有窗口内唯一的序号,换文件后旧文件迟到的读取即弃。图片(png、jpg、gif、webp、bmp、tiff、ico)以 `BeforeAfter` 左右拖动对比,一侧缺失时只显示另一侧;其他二进制文件,以及两侧任一侧不全是 UTF-8 的文件(差异以外的字节也算),显示两侧大小,整份暂存;文件与符号链接互换时 Git 为同一路径打出两段(删去与新增),两段都显示(不折叠:两段没有共同的旧文),整份暂存;工作树侧照 Git 读:符号链接读其自身的路径,不跟随;未跟踪的文件夹与冲突文件(冲突在 C13.4 处理)只说明。单击文件即在中间显示其差异(已忽略的文件以系统程序打开),标题栏另有“打开文件”。外部改动后状态重读,正显示的差异随之重读;文件不再列在原分区即收起。真窗口(macOS):改动区选两行暂存,索引恰为所选,已暂存区显示实际索引内容,并排视图;AX 树中各行、块按钮与分段可按。 +- 2026-10-07(用户定):ElyTree 的 Git。随包自建钉住的 Git v2.56.0(a018953)加 git-lfs v3.8.0(各平台散列钉在 `scripts/fetch-git-lfs.sh`),`scripts/build-git.sh` 以 RUNTIME_PREFIX 构建到 `engines/git`,LFS 放在 `libexec/git-core`。查找次序:`ELY_GIT`(系统 Git,低于 2.40 即拒绝),否则应用旁或 `Resources/git`,否则本构建的 `engines/git`;没有即报错,不退回别的 Git。每次运行都是参数数组,带 `GIT_OPTIONAL_LOCKS=0`(读取不抢索引锁)、`GIT_TERMINAL_PROMPT=0`(从不在终端问凭据)、`GIT_LITERAL_PATHSPECS=1`(路径不当通配)。 +- 2026-10-07:ElyTree 仓库与工作区(C13.1,TREE-01、02)。窗口经 `ely_ui::tool_root`:“文件”只有新建(选文件夹 `git init`)与打开(可多选文件夹),`commands::menus` 改收各应用自己的文件命令(文档应用照旧六项),保存、另存、导出、打印的动作在工具窗口里不挂;“仓库”菜单:克隆…、关闭仓库(Cmd+W)。每个仓库一个标签,同一工作树(按 `--show-toplevel` 的真实路径)只开一个,再开即切过去;裸仓库拒绝。最近列表在数据目录的 `recent.json`(新的在前,留 20 个),读不出即报错且不覆盖;最近项打不开即移出并说明。克隆:先问地址,再在最近仓库旁问新文件夹(名取地址末段去 `.git`),文件夹非空即拒绝;Git 的进度逐行显示在正中(回车与换行都断行,读到即显示),可停止(SIGTERM,Git 自己删去半成品;已结束的进程不再发信号,免误伤重用的进程号);关窗或退出时停下进行中的克隆。状态读 `status --porcelain=v2 -z --branch --untracked-files=normal --ignored`,未知字母即报错;行数取 `diff --numstat -z`(已暂存与未暂存各一次,二进制不计),未跟踪、冲突、忽略的不计行数。左栏 `git::ChangesList` 分冲突、已暂存、改动、未跟踪、已忽略;单击文件以系统程序打开;暂存(`add --all`)、取消暂存(有提交时 `restore --staged`,首个提交前 `rm --cached -f`:索引与文件不同也照取消,文件不动)、全部暂存(`add --update`)、全部取消(`reset -q`);取消暂存重命名连旧路径,暂存只用新路径(已暂存的重命名再改时旧路径已不在);放弃改动每次都问(已跟踪的 `restore --worktree`,未跟踪的 `clean -f -d` 并写明会删除)。同一仓库的写操作排队、一次一条,失败即清掉其后的排队并报出 Git 原话,关闭标签要等排队写完。外部改动:`notify` 监视工作树、Git 目录与共用目录(`--git-common-dir`;链接的 worktree 的分支与引用在主仓库里),250 毫秒内的一串改动合成一次重读,读取中再来的合成一次;空闲不读。状态栏:分支(游离显示短提交号,无提交写明)、上游与领先落后(上游引用已删即写明不存在,不显示为 0)、真实路径、Git 版本、随附或系统与所用 Git 的路径。中间区在 C13.2 前只显示改动数或“没有要提交的改动”。组件库 4f8f3bd 与 37c5187:`Changed::lines` 可缺。真窗口(macOS):五个分区、行数、状态栏。未验证:Windows 与 Linux(停止克隆在非 Unix 上报未构建);LFS 仓库的克隆。 +- 2026-10-07:ElyPDF 输出与文件恢复(C11.8,PDF-13、14,QA-13)。“文件›导出…”(Cmd+E)一个对话框三种:页面图片(PNG,72、150、300 dpi,全部或所选页,总是浅色,选文件夹,`名称-序号.png` 按页数补零,先以 `ely_files::claim_all` 占下全部目标,任一失败即删去本次全部);文字(UTF-8,各页以换页符分开,MuPDF 的 stext);优化的 PDF(可看可改的设置:压缩数据流、字体与图片;图片高于某 dpi 降到某 dpi,双三次重采样到确切目标,重压缩:无损的仍无损、有损的保持原格式,默认 300 降到 150;降到的 dpi 须低于阈值,否则不可导出;完成提示写明原文件与新文件大小)。导出图片与文字要“复制”许可,优化副本要“修改”许可。“打印…”(Cmd+P)把文档此刻写成副本(要“打印”许可,worker 的 `PRINT_COPY`)再交系统打印面板(`ely_office::print_later`)。文件恢复:MuPDF 打开时修复了文件结构(`pdf_was_repaired`),worker 即把修复后的文档整份写成临时副本、改开这份副本(日志与快照从此正常;原先修复过的文件不能保存),关闭文档时删去;打开时 MuPDF 的警告记下作修复报告(`ely_open` 期间接管警告回调,照样转给原回调)。外壳的 `Engine::signed` 改为 `Engine::risk`(宏由外壳判,签名、修复由引擎答),加 `Risk::Repaired`:提示条说明打开的是修复的副本、“另存为”写新文件、原件不动,带“报告…”按钮显示 MuPDF 的原话;保存一律另存,另存到原件被拒;签名或修复的文件另存成功后标签记下“待重开”,到内存与文件一致(已保存、无待落地编辑、不在导出)时才按新文件重开;另存期间又有编辑则留着编辑,下一次保存后再重开。MuPDF 有时读到坏对象才修复(尾部完好、某对象偏移坏):打开与解锁后先把每个对象读一遍,并解码图片以外的每个数据流(`ely_scan`,读不出的对象记入报告);MuPDF 修复了结构或读的过程中有任何警告(如内容流解压失败),都当作损坏文件,改开整份写出的副本、报告列出警告、保存一律另存。图片(用户定)在画出时检查:画图块或缩略图时 MuPDF 有警告即为损坏,报告补上警告,标签转为修复风险(保存一律另存);没画过的图片在原位保存(覆盖文件)之前统一解码检查(外壳的 `Engine::unsound`,有问题即转为另存为)。全部解码放在打开时的代价实测:100 张 2000×2000 的 JPEG(281 MB)打开到首块 2.6 秒(release,本机)。导出图片与文字期间 MuPDF 有警告即报错不写(读不全的内容不当作导出)。打印副本不带加密(已核过打印许可;否则系统打印面板打不开加了打开密码的副本),`print_later` 在面板关闭后删去它。导出对话框的降采样数值随键入生效(不等回车或失焦),直接点导出用的是所见的数。补丁 `0005` 见上(保存打不开输出时的保存中标志)。QA-13:worker 改字、加并填文本字段、移页后保存,以 PDFKit(另一阅读器)读回:页序、新字取代旧字、字段值都对(`tests/independent.rs`,只在 macOS);图片编辑在 PDFKit 里未单独核对(PDFKit 无图片清单接口)。组件库 a6b1bb0 的 `init` 改为返回错误(缺资源时),启动时出错即以 `startup::fail` 报出。真窗口(macOS):损坏文件的提示条与“修复的副本”状态、导出对话框。未验证:Windows 与 Linux 的打印(打印面板只在 macOS 实现);真窗口里实际打印。 +- 2026-10-06:ElyPDF 密码与签名(C11.7,PDF-11、12,QA-15,§11.4)。权限:worker 记下每个文档的访问(MuPDF 的认证结果,4 为所有者;未加密即全权),`PERMISSIONS` 按 Ely 的位报出(打印、复制、修改、批注、填表、页面与书签、所有者),每个改动请求先按表核对(`secure::needs`:页面增删移转与书签要“页面与书签”或“修改”,内容、链接、属性、附件、OCR 层、删除与清理要“修改”,批注要“批注”,建改字段要修改加批注,填表要填表或批注,提取要复制,改保护要所有者),不许即以 `restricted:名` 报错,客户端转成 `PermissionDenied`,界面按名说明并提示输入所有者密码;复制文字在应用里按位拦(读文字本身不拦)。“文档”菜单“输入所有者密码…”:对时解除限制;错时 MuPDF 已丢了原密钥(RC4 与 AES-128 实测文字读不出),worker 以原密码重新认证。“密码与权限…”只在所有者权限下:写新文件(AES-256,`ely_protect` 经 `ely_save_copy` 带写出选项),打开密码可空,所有者密码必填且须与打开密码不同,六项许可勾选;或不保护(去掉加密)。只凭用户密码打开的文件改保护即拒绝。签名:MuPDF 以 `HAVE_LIBCRYPTO` 构建,worker 另链 `libmupdf-pkcs7.a` 与 OpenSSL 的静态 `libcrypto.a`(pkg-config 给目录)。`SIGNATURES` 按表单字段树列出全部签名字段(不画在页面上的也列,页与框可无;控件无 /P 时从各页 /Annots 找页):已签的给出摘要核对(`pdf_check_widget_digest`:未改、已改、读不出)、其后有无更新与更新是否越出签名所许(`pdf_signature_incremental_change_since_signing`、`pdf_validate_signature`)、签名者(MuPDF 的识别名与通用名)、签名者自称的时间、原因、地点,以及签名里的证书(签名者在前,DER);单个签名读不出只报该项的原因。证书信任由应用交系统判定(`ely_platform::trust`:macOS 用 `SecTrust`、`SecPolicy::create_x509`、禁网,故从不核验吊销;Windows 与 Linux 未构建,报“此平台未构建”)。界面“文档›签名…”对话框逐个列出完整性、证书、吊销(一律“未核验吊销状态”)、时间(带时区)、原因、地点与所在页,不给笼统的“有效”。新签:“以证书签名…”工具,拖出框或点空的签名字段,对话框选 .p12/.pfx、填证书密码、原因、地点,另存:快照副本上建字段(名 `SignatureN` 避开表单已有名)并签,以增量更新写出(旧签名覆盖的字节不动),写完重开核对新签名的摘要,不过即报错删文件;原文档不动;发起时绑定视图版本与所选位置。补丁 `patches/mupdf/0004-signature-reason-and-location.patch`:原因与地点写进签名字典(MuPDF 原只画进外观;签后再改会落到第二个更新段,字节范围算成负数)。外观只画标签、名字与时间(不画空的识别名各栏,不画 MuPDF 标志)。已签文件(`ely_signed` 数表单里有值的签名字段)打开为 `Risk::Signed`:上方提示条说明修改会使签名失效、“另存为”写出未签名副本,带“签名…”按钮;保存不覆盖原文件。外壳的 `risk` 由布尔改为 `Option`(宏或签名),`Engine::signed` 由引擎回答。Ely 一切整份写出(保存、另存、提取、删除、清理、保护)在 `ely_save_copy` 里去掉签名值与其外观(字段留下、空着,去掉 AppendOnly 标志):整份重写本就使签名失效,不留假签名。签名核对的是文件,不是编辑中的文档:`SIGNATURES` 带路径,worker 以文档的密码另开该文件读出再关闭;签名列表按标签当前的文件路径读、按路径缓存,保存后重读。已签文件另存为成功后,标签按新文件重新打开(外壳的 `reload`,同冲突时;撤销历史随之重新开始),打开着的文档、签名列表与以后的签名都对应这份未签名的副本;另存失败时标签与签名都仍是原文件。签名对话框每次打开都重读文件,磁盘上被改动即显示已改。去掉签名值时一并去掉该签名字段自身的只读位(签名时由它的锁设下),以便再签;它锁住的其他字段的只读位留着(分不出原本是否只读,已知限制)。补丁 `patches/mupdf/0005-failed-save-clears-save-in-progress.patch`:MuPDF 的 `prepare_for_save` 置起保存中标志、只有写出过程清除它;打不开输出文件时标志留着,此后一切改动绕过日志,编辑没有撤销步(实测:存到不存在的目录后旋转,历史标记仍为 0);补丁让打开输出或预排签名失败时清除它。可视签名(用户定三种来源):“添加可视签名…”对话框,手写(组件库 `SignaturePad`,笔画按较长边归一)、键入(姓名加字体:手写体为随包的 Great Vibes,OFL,`crates/ely-pdf-engine/fonts`,取自 google/fonts c1eda92,嵌入 worker 二进制,以 CID 字体写入;或已有的拉丁与中日韩字体;缺字即报出是哪个字)、图片(按原比例);随后单击放下(宽 160 点)或拖框(按比例放进框内),各为一步:手写成墨迹批注,键入成文字,图片成图片内容;对话框写明它不是数字签名。真窗口(macOS):已签文件打开有提示条,状态为已签名,签名对话框三项分列;可视签名对话框。未验证:Windows 与 Linux 的证书信任;时间戳与吊销;带图片外观的数字签名。 +- 2026-10-06:ElyPDF 内容删除与文档清理(C11.6,PDF-09、10,§11.4,QA-14)。标记只在视图里(“标记文字以删除”按所选文字每行一框,“标记区域以删除”拖框,单击已有标记取消;预览为页面上的深色框;任何编辑落地即清空,页码可能已变),文档本身不动。“删除标记的内容…”与“清理文档…”都写新文件:先把文档快照写出、重开副本(`ely_save_copy`,提取与保存同用),在副本上改,再以 `garbage=2` 整份写出(不增量,旧修订不留),写到工作目录、核对通过后经 `ely_files::export` 交到所选位置,工作副本普通删除(对话框写明:原件、旧备份、同步历史与已发出的副本仍可能含有被删内容;不是磁盘级擦除)。删除:先删去碰到区域的批注与字段(MuPDF 的删除只管链接与文本框批注,其他批注的内容、字段的值会留下),字段失去最后一个控件即连同空了的上级一起从表单去掉(否则值留在字段树里);全部区域的删除做完后才建 Redact 批注(否则后一区域会删掉前一区域的标记)并 `pdf_redact_page`(黑框;图片在区域内的像素由 MuPDF 改成白色;碰到区域的线条整条删去,只删被盖满的会留下可复原的路径;碰到的文字删去,含隐藏文字)。有区域的页再过一遍内容(含表单):标记内容的 /ActualText、/Alt、/E 一律去掉,结构树里属于该页的元素(自身或继承的 `Pg`,或子项 MCR、OBJR 的 `Pg` 指向该页,连同其上级)去掉这三项与 /T;整页去掉而不只区域内的,宁多勿漏。页面缩略图 /Thumb 删去(它可引用原图),核对时区域所在页仍有缩略图即失败。写出后重开核对:区域内的字(与删除同一判法:字框各边缩一成后相交)为零;区域内画出来不是黑色的像素为零;逐页以设备重放,落进区域的路径、渐变、图像蒙版除我们的黑框外为零,图片单独画出在区域内只剩白或黑;文件只有一个版本;任一不过即报错并删去输出。确认时绑定标记与文档版本,选完路径后标记或版本变了即不写。清理:先扫出数量(属性与元数据条数、批注、附件含批注里的文件、有隐藏文字的页、脚本入口),勾选后写出:删 Info;其余按一遍遍历全部对象及其中的直接字典与数组:/Metadata 与 /PieceInfo,文件说明的 /EF 与 /RF 及关联文件 /AF,脚本;删批注(链接与字段不算批注,保留),删附件(名称树与文件批注;附件按带 /EF 的文件说明计数,每个一次),隐藏内容为:内容过滤(包住 MuPDF 的 sanitize 过滤器,用途设为 View 才认关着的图层)删去关着的图层里的一切、3 与 7 渲染模式及零透明度的文字,脚本(名称树 JavaScript;任何对象的 /A、/OpenAction、/Next 与 /AA 各项里 S 为 JavaScript 或带 /JS 的动作,从链中摘除,其后的 /Next 链接回原处,非脚本动作保留),带隐藏或不可见标志的批注(含链接与字段控件:链接不在 MuPDF 的批注表里,按页面 /Annots 删;控件删去时字段连同值从表单去掉);有隐藏文字被删的页,标记内容的替代文字照删除的做法去掉。扫描与写出后的核对都在快照副本上跑同一遍清理来计数,所以计数与执行一致;所选类别核对不为零即报错。扫描回来时须仍是当前标签且版本未变,对话框写明文件名。未验证:Windows 与 Linux。 +- 2026-10-06:ElyPDF 文档结构与 OCR(C11.5,PDF-07、08)。MuPDF 以 `tesseract=yes` 构建(自带的 Tesseract 与 Leptonica,C++,worker 另链 libc++/libstdc++);构建脚本记下所用参数,参数一变即整个重建(make 不跟踪参数,曾留下未开 OCR 的旧目标文件)。语言数据(用户定 tessdata_fast:eng、chi_sim、chi_tra)由 `scripts/fetch-tessdata.sh` 按钉住的提交取到 `engines/tessdata` 并核对散列;运行时取 `ELY_TESSDATA`、应用旁的 `tessdata`、或本构建的目录,缺 eng 即报错。OCR:worker 把页面按 300 dpi 画成灰度图,只让这张图经 MuPDF 的 OCR 设备进 stext(不把页面原有内容交给它),按行回报页面坐标的外框与文字,不改文档;应用逐页在后台读,可取消,每页回来时核对视图版本,读完以一步写入隐藏文字层:无字形的 Identity-H 字体(ToUnicode 恒等映射),每行一个 `3 Tr` 的文字对象按外框横向拉伸,写在单独的内容流里(自带 `q … Q`,3 Tr 不漏到后来写的字)并以 `/ElyOCR` 标记;“重新识别”先删去这些流再写,原有文字不动;“跳过”不读已有文字的页。BMP 以外的字拒绝整层(已知限制)。补丁 `0003-ocr-needs-every-language.patch`:Tesseract 只载入能载入的语言、静默丢掉其余,补丁让载入数少于所请求时报错。每次识别有自己的编号,取消后的页回来即弃(不再按版本与页码认);页码范围随键入更新。补丁 `patches/mupdf/0002-embedded-file-and-link-uri.patch`:`pdf_add_embedded_file` 成功时未结束它开的操作,之后撤销与状态读取都抛错;`pdf_set_link_uri` 写了 `/A` 却留着旧 `/Dest`(读取先看 `/Dest`),改链接目标无效。书签:添加(在选中项之后,否则末尾)、改名、改目标、删除(连子项),按自顶的路径定位并核对标题,经 MuPDF 的大纲迭代器在一步内完成。链接:读取每页的外框、地址与内部目标;阅读时单击即跳转(内部)或打开(只 http、https、mailto,其他拒绝并告知);“添加链接”工具拖出外框,目标为页码或网址;单击已有链接改目标或删除,按序号并核对地址。文档属性:标题、作者、主题、关键词可改(只写改过的,一步),创建与生成程序及时间只读,文件没有的显示“无”。附件:EmbeddedFiles 名称树按名列出,再列各页 FileAttachment 批注的文件(标出页码),以键区分(`n:名称`、`a:页:对象号`,重名不混);大小取 `/Params /Size`,缺即按实际字节(不把缺失显示成 0);类型、说明,另存为(经 `ely_files::export`)、添加(文件名为名,重名拒绝)、移除;改动时名称树重写为一层有序的 `/Names`。左栏分段为页面、书签、批注、附件,书签与附件为空时显示空状态;“文档”菜单:文档属性…、识别文字…、添加书签、添加链接、添加附件…。真窗口:扫描页识别后可查到文字、页面观感不变。移除批注里的文件即删去该批注。未验证:XMP 元数据不随属性修改;Windows 与 Linux 上的 OCR。 +- 2026-10-06:ElyPDF 批注与表单(C11.4,PDF-05、06)。垫片 `ely_annot.c`、`ely_form.c`:读一页的批注与字段(JSON:对象号、种类、外框、颜色、内容、作者、印章名;字段的全名、值、开状态、是否选中、只读、选项与显示文字);批注与字段按对象号认定,撤销后不变,页上没有即拒绝。添加批注收 JSON 列表(可跨页,一步):高亮、下划线、删除线(按所选文字每行一框)、便签(内容)、矩形、椭圆、直线、手绘、印章(MuPDF 的 14 种标准印章);全部检查在开步之前,不合即不改、重做不丢。作者取账户全名(`ely_platform::user::name`)。改批注的颜色与内容、删除批注各一步。字段(MuPDF 无建字段接口,垫片自建):文本、复选、单选、选择框(组合框);名字不可含点、不可重名;同名单选并入同一组(各按钮的开状态为 ChoiceN,开状态先画外观再设回 Off);改名、改选项(原值不在新选项里即清空)、删除(组里最后一个按钮删去即删组)各一步。填写:文本与选择框经 `pdf_set_field_value`(不跑脚本),复选与单选设组头 /V 与各按钮 /AS;只读字段拒绝。外观在同一步里生成。界面:“批注”菜单(高亮、下划线、删除线、便签、印章、矩形、椭圆、直线、手绘)与“表单”菜单(文本字段、复选框、单选按钮、选择框)各为工具,Esc 退出。阅读时单击复选与单选即填写,单击文本与选择框在右栏填写,单击批注在右栏改颜色、内容或删除;表单工具下单击已有字段改名、选项或删除,拖出新字段(单击给默认大小)后在右栏取名再添加;新单选默认取本页最后一组的名字。按下即改的操作须视图无排队编辑,右栏的改动按发起的标签与版本核对。右栏的开合在拖动与绘制期间保持手势开始时的样子,手势按开始时该页在窗口中的位置与比例换算(之后页面因右栏开合或滚动移动也不影响);单击按窗口像素判定(移动不足 3 像素)。文字未读到时拖出的标记等文字到后再加。新字段的默认名避开表单里的全部名称(worker 读整棵 `/AcroForm/Fields`,含没有页面控件的节点与父名),右栏等它读完再给;改名只改名字末段,按父路径下的全名查重。左栏“批注”列出全文批注(每批 20 页在后台读),点一行转到所在页。组件库 3d08744:`DescriptionList` 的值占满所在列。未验证:已有文件里的签名字段与按钮只读不改;多选列表框只当单选填;Windows 与 Linux。 +- 2026-10-06:ElyPDF 文字、图片与对象编辑(C11.3,PDF-03、04,§11.3)。字体(用户定):文档自带字体(能写新字时)或 MuPDF 内置的 Helvetica、Times、Courier(不嵌入,WinAnsi)与 Noto 中日韩(不嵌入,UTF-16 的 Adobe CJK 字体),不扫描系统字体;自带字体以 CID 字体(Identity-H,按字形号写)写入。对象(用户定):图片与表单 XObject。文字块以 MuPDF 的 stext 为准(同一读法,块序号稳定);可编辑的块须横排,且每个字形都在页面自身的内容流里(以不落盘的过滤试跑核对,表单里的字不可改)。改字是一步:用内容过滤的 `text_filter` 去掉该块的字形(按字形原点在用户空间 0.5 点内且字符相符,不靠白块遮盖),再在原首行基线处按框宽排版写新字(空格处折行,太长的词断开,行距沿用原块);所有检查(块文字未变、横排、字形都在页面内容里、字体画得出每个字、排版)都在开步之前,拒绝时文档与重做都不动;画不出某字即告知是哪个字,界面请用户换字体;换了字体、字号改变或新文字越出原块的框时先出预览(在快照副本上改并画出该区域,文档与撤销历史都不动),确认后才写。行距取首行与下一条不同基线的距离(同一基线的几段不算行),没有即字号的 1.2 倍。新文字在点击处起框。新增内容第一次写入时把页面原内容包进 `q … Q`(以 `%Ely` 标记,只包一次)。图片与表单:清点处理器按 `q`、`Q`、`cm` 跟踪,列出页面顶层的每次 `Do` 与其页面框;移动、缩放(右下角柄,保持比例、左上不动)以 `X = C·T·C⁻¹` 包住该次绘制重写内容流,删除即不写该次 `Do`,替换图片是新图片资源、按比例放进原框中央;添加图片落在当前页中央(像素按点、缩到页面一半以内)。MuPDF 的字体与图片资源缓存不随撤销与放弃回退(实测:预览后再写,字体引用指向已不存在的对象),每次放弃、撤销、重做后清空。编辑工具在“编辑”菜单(编辑文字和图片、添加文字、添加图片…),Esc 退出(键位上下文在窗口根上,页面与面板里都管用);替换、添加与删除图片和表单、拖动移动与缩放、写字与预览的确认都绑定发起的标签与视图版本,执行前视图须未变且无排队的编辑,否则提示不执行;任何编辑落地即关掉此前的预览;图层读取在回来时总清掉进行中的标记,编辑之前发出的读取作废后重读;右侧编辑面板:文字、字体、字号、颜色、框宽,应用、删除文字;对象:替换图片…、删除。真窗口(macOS)改过一次字:同字体同位置替换,标签为已编辑。 +- 2026-10-06:ElyPDF 页面组织(C11.2,PDF-02)。worker 打开即开 MuPDF journal;插入空白页、从文件插入(整份 PDF 嫁接)、删除、移动、旋转(90 度倍数,写页面的 `/Rotate`)、裁剪(按所见的上右下左边距设 `/CropBox`,全零即去掉)、撤销与重做都是一步,回复带历史标记(worker 按 MuPDF 的撤销位置记每步是编辑还是保存,标记是最后一个已做的编辑;撤销重做跳过保存步)。删除时指向已删页的书签保留但失去跳转,链接移除,表单字段去掉已删页上的控件;页码标签由 MuPDF 的删除与插入随之平移。坏的编辑(删光、页码乱序、非 90 度、边距超出页面)拒绝并回滚整步。保存(Save、Save As 走外壳)先 `pdf_save_snapshot` 写出(不动内存中的文档与撤销历史,靠补丁 0001),再打开该副本以 `garbage=2` 紧缩写到工作目录(已删页的对象不留在文件里),加密文件以用户输入的密码重开副本、照原加密写出(密码只在 worker 内存);外壳读回字节、原子写入。提取(选中页写新文件,经 `ely_files::export`,不覆盖打开着的文件,被别的标签持有即拒绝)在快照副本上删去其余页再紧缩写出,原文档的撤销与重做都不动;拆分按每份页数写到所选文件夹(`名称-序号.pdf`,先以 `ely_files::claim_all` 占下全部目标,任一已存在即一个不写;原 ElyShow 的同名函数移入 ely-files 共用)。合并即从文件插入到末尾再另存;插入的页带着自己的批注、链接与表单字段(指向源文件自身页的链接改指插入后的页,字段并入表单,重名的加序号,如 `field0 2`(名称按需分配,长名也终止),源表单的默认外观 `/DA` 与默认对齐 `/Q` 给到没有自己值的字段树根,源表单的字体资源并入目标 `/DR`,同名而不同的字体改名为 `名称_2` 并改写字段外观里的引用(按 PDF 名称语法解 `#xx` 转义,写出时照样转义);源文件的书签不并入)。删除时表单控件按被删页的 `/Annots` 认定,不只看可省略的 `/P`。裁剪边距以所见为准:先按视图旋转、再按页面 `/Rotate` 换到页面自身方向,并按 `/UserUnit` 换算。编辑失败也通知外壳队列已动,等着的保存、关闭不会挂住。结构编辑后区间点选的起点作废。应用:每个视图一条编辑队列,一次一条、按序执行,落地后重读页数、尺寸与书签,清掉图块、文字、缩略图、找到的词与选择,视图版本加一,此前发出的读取回来即丢;查找在编辑后重查。修改标记是当前历史与载入或上次保存时的历史不同,撤销回到保存点即为已保存(外壳新增 `Engine::saved` 与 `Session::reverted`:引擎回报未修改时 Dirty 回到 Saved,保存中、冲突、失败不变)。左栏是页面缩略图(`side.rs`;有书签时上方分段切换到书签):点选、Shift 连选、Cmd 增减,右键菜单,拖到某页之前即移动(移动后仍选中),Delete 删除;无选中时作用于当前页。“页面”菜单与“编辑”菜单的撤销(Cmd+Z)、重做(Shift+Cmd+Z)。裁剪对话框以毫米输入。MuPDF 的构建改为在 `engines/mupdf-src`(reference 只读)打 `patches/mupdf` 后编译。 +- 2026-10-06:ElyPDF 阅读(C11.1,PDF-01)。MuPDF 只链进 `ely-pdf-worker`(`crates/ely-pdf-engine`,build.rs 把 C 垫片 `shim/ely_mupdf.c` 与 `libmupdf.a`、`libmupdf-third.a` 只链给这个二进制),应用不链 MuPDF。build.rs 以协议、垫片与 worker 源码的散列为版本戳,应用启动 worker 先握手(`HELLO`),戳不同即拒绝(单测不重建 worker,旧二进制曾被误用)。worker 在打开第一个文件时才启动(PERF-01),`tests/scripts/pdf-idle.sh` 查空白窗口无子进程。垫片每个调用都在 `fz_try`/`fz_catch` 里,返回码加最后一条错误信息,MuPDF 的跳转从不越过 Rust。worker 与应用之间是 `ely-ipc` 的帧(从 ely-lok 抽出,两者共用):打开(锁定即报)、解锁、页数、页框、渲染一块(设备像素、缩放、旋转、深色)、按阅读顺序的字符与外框、页内搜索(每处命中及其全部四边形,跨行短语算一处,数量不设上限)、书签(JSON)、关闭;文件读不出只报错,worker 照常工作。查看器:打开时只读页数、前 100 页尺寸与书签,其余尺寸后台每批 100 页补齐;视图只排屏上的页(连续、单页、双页;适合宽度、适合页面、实际大小与缩放档;视图旋转 90°),按 512 设备像素一块请求,一次一块、由近及远,只留屏上的块(横纵两向),后台回来的块不在屏上即弃;画不出的块记下不再请求,换文档或重开前不重试;深色外观下页面按亮度反相(MuPDF `fz_invert_pixmap_luminance`,色相保留)。文字选择按字符、可跨页,复制按行换行,未读的页先读;查找每批 20 页在后台进行,命中逐个跳转并标出,Esc 关闭(组件库 617a8d6);书签栏用 `lists::Outline`,点即跳到页与位置。切换视图、缩放、旋转后仍停在正在读的那页。加密文件打开时问密码(外壳的密码框,见上一条),错了再问,取消即不打开。没有页的文件报错并关闭,不开标签。双页时同一行在视口内的两页都可为当前页;当前页只在横纵两向都可见的页里定,跳转在目标页左缘不在视口时横向移过去。图块按视口半开区间取。首次按下时文字未读,读到后照原按下处起选;查找结果按查找实例与所属标签核对,只在该标签为当前时跳转。书签为空时不留左栏。启动参数里的文件直接打开。 diff --git a/scripts/review.sh b/scripts/review.sh new file mode 100755 index 0000000..265e60f --- /dev/null +++ b/scripts/review.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Usage: review.sh [path...] — codex high review. +set -euo pipefail + +[ "${HERDR_ENV:-}" = 1 ] || { echo "review.sh: not inside herdr" >&2; exit 2; } +id=${1:?item id} +round=${2:?round} +shift 2 +scope=${*:-"the files this item touched; find them from the item line and the tree"} + +cd "$(dirname "$0")/.." +name="rv-$(echo "$id" | tr 'A-Z.' 'a-z-')-r$round" +verdict="tasks/reviews/$id-r$round.md" +mkdir -p tasks/reviews +rm -f "$verdict" + +prompt="You review item $id of Ely Office, round $round. Work dir is the project root; agents.md holds the rules. +Read the line for $id in tasks/TASKS.md and the PRD.md sections it cites. +Scope: $scope +Find defects only: behavior that breaks the PRD, errors swallowed, silent fallbacks, dead code, files over 500 lines, comments over one line or ten words, boxes inside boxes or useless subtitles in UI, abstractions with one user, missing or weak tests, claims no evidence backs. Build and run the tests yourself. +Copies of the tree you build for probes use their own CARGO_TARGET_DIR, never ./target: crates bake their source path in. Delete those copies and their target dirs before you write the verdict; keep only small logs. +Do not suggest enhancements. Do not reject for taste. Edit no file except the verdict. +Round 2 and later: read the previous verdict tasks/reviews/$id-r$((round - 1)).md, confirm each defect is fixed, then look for new defects in changed code. +Write the verdict to $verdict. Line one is PASS or FAIL. After that, one entry per defect: file:line, what is wrong, a concrete failing scenario, the direction of the fix. +When done, reply with the single word PASS or FAIL." + +# The last review's pane stays open until the next starts. +last=tasks/reviews/.pane +if [ -f "$last" ]; then + herdr pane close "$(cat "$last")" >/dev/null 2>&1 || true +fi +pane=$(herdr pane split --current --direction right --cwd "$PWD" --no-focus | jq -r '.result.pane.pane_id') +echo "$pane" >"$last" +started= +for _ in 1 2 3 4 5; do + if err=$(herdr agent start "$name" --kind codex --pane "$pane" --timeout 120000 -- --model gpt-6.1-sol -c 'model_reasoning_effort="high"' -c check_for_update_on_startup=false 2>&1); then + started=1 + break + fi + sleep 3 +done +[ -n "$started" ] || { echo "review.sh: agent did not start in $pane: $err" >&2; exit 1; } +herdr agent prompt "$name" "$prompt" --wait --timeout 3000000 >/dev/null +for _ in $(seq 360); do + [ -f "$verdict" ] && break + sleep 5 +done + +[ -f "$verdict" ] || { echo "review.sh: no verdict at $verdict; pane $pane left open" >&2; exit 1; } +cat "$verdict" diff --git a/scripts/test-map.py b/scripts/test-map.py new file mode 100644 index 0000000..ff43a0d --- /dev/null +++ b/scripts/test-map.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Writes docs/test-map.md: each PRD id, its tasks, their tests.""" + +import os +import re +import subprocess + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +LATER = ("MAIL", "AGENT") +# Where each area's tests live, named or not. +WHERE = { + "UX": "crates/ely-ui/tests、shell.sh、doc-shell.sh、settings.sh", + "FILE": "crates/ely-files、crates/ely-office、recovery.sh、autosave.sh", + "LOK": "crates/ely-lok/src/client/tests、crates/ely-lok/src/view/tests、lokview.sh", + "DOC": "apps/ely-doc/src/tests、doc-props.sh", + "TABLE": "apps/ely-table/src/tests、theme.sh", + "SHOW": "apps/ely-show/src/tests", + "PDF": "apps/ely-pdf/src/tests、crates/ely-pdf-engine/tests、pdf-recovery.sh", + "TREE": "apps/ely-tree/src/tests", + "MARK": "apps/ely-mark 单测、mark-*.sh", + "PERF": "tests/perf/bench.py、lag.sh", + "QA": "见 docs/qa.md", +} + + +def read(name): + with open(os.path.join(ROOT, name)) as text: + return text.read() + + +def ids(): + """PRD table rows: id and its requirement.""" + for line in read("PRD.md").splitlines(): + if m := re.match(r"\| ([A-Z]+-\d+) \| (.+?) \|", line): + yield m.group(1), m.group(2).split("|")[0].strip() + + +def cites(line): + """PRD ids a line names: single, lists, ranges.""" + cited = set() + for prefix, rest in re.findall(r"([A-Z]+)-(\d+(?:(?: ?至 ?|、)(?:[A-Z]+-)?\d+)*)", line): + numbers = re.split(r"(?: ?至 ?|、)(?:[A-Z]+-)?", rest) + ranges = re.findall(r"(?: ?至 ?|、)", rest) + n = [int(x) for x in numbers] + cited.add(n[0]) + for kind, (a, b) in zip(ranges, zip(n, n[1:])): + cited.update(range(a, b + 1) if "至" in kind else [b]) + yield from (f"{prefix}-{k:02d}" for k in sorted(cited)) + cited = set() + + +def tasks(): + """Task lines and decisions: a label, the PRD ids, the line.""" + for line in read("tasks/TASKS.md").splitlines(): + if m := re.match(r"- \[(.)\] (C[\d.]+[a-z]?) ", line): + yield m.group(2) + ("" if m.group(1) == "x" else f"({m.group(1)})"), set(cites(line)), line + for line in read("agents.md").splitlines(): + if m := re.match(r"- (\d{4}-\d\d-\d\d)[^:]*:.*?((C[\d.]+)", line): + yield f"决策 {m.group(2)}", set(cites(line)), line + + +def known(): + """Test function and script names present in the tree.""" + found = subprocess.run(["grep", "-rhoE", r"fn [a-z0-9_]+\(", "crates", "apps", "--include=*.rs"], + cwd=ROOT, capture_output=True, text=True).stdout + names = {name[3:-1] for name in found.split()} + names |= {f for f in os.listdir(os.path.join(ROOT, "tests", "scripts"))} + return names + + +def main(): + present = known() + rows = ["# 功能编号与测试对照", "", "对应 PRD §22。由 `scripts/test-map.py` 从 PRD、`tasks/TASKS.md` 与 `agents.md` 生成:每个编号列出引用它的任务与决策,其中写明、且在代码里确有其名的测试(单测函数名或 `tests/scripts` 的脚本),以及该领域测试所在之处。邮件与 Agent 属第二批,不列。", "", + "| 编号 | 要求 | 任务 | 记下的测试 | 测试所在 |", "| --- | --- | --- | --- | --- |"] + listed = list(tasks()) + for id_, need in ids(): + if id_.startswith(LATER): + continue + mine = [(label, line) for label, cited, line in listed if id_ in cited] + named = [] + for _, line in mine: + for name in re.findall(r"`([a-z0-9_.-]+)`|([a-z0-9-]+\.sh)", line): + name = name[0] or name[1] + if name in present and name not in named: + named.append(name) + labels = list(dict.fromkeys(label for label, _ in mine)) + where = WHERE[id_.split("-")[0]] + rows.append(f"| {id_} | {need} | {'、'.join(labels) or '无'} | {'、'.join(f'`{n}`' for n in named) or '—'} | {where} |") + with open(os.path.join(ROOT, "docs", "test-map.md"), "w") as out: + out.write("\n".join(rows) + "\n") + print(f"docs/test-map.md: {len(rows) - 6} ids") + + +if __name__ == "__main__": + main() diff --git a/tasks/TASKS.md b/tasks/TASKS.md new file mode 100644 index 0000000..c4ce822 --- /dev/null +++ b/tasks/TASKS.md @@ -0,0 +1,177 @@ +# Tasks + +进度真相。自上而下,逐条完成,不跳项。编号前缀是 PRD 章节号,`C00` 是用户指令的前置项。 + +每条完成后:`scripts/review.sh ID 轮次` 在 herdr 开新分栏,codex --yolo(gpt-6.1-sol,effort high)审查,最多三轮。有问题先修,复审通过才勾选。审查只看缺陷,不收“建议增强”。每轮结论写入 `tasks/reviews/<编号>-r<轮>.md`。 + +状态:`[ ]` 未做 `[x]` 完成且审查通过 `[~]` 三轮用尽,末轮缺陷已修并有回归测试,未经复审 `[!]` 外部阻塞,写明原因与证据,不算完成。 + +## C00 前置 + +- [x] C00.1 工具链:cmake、ninja、pkg-config、git-lfs 就位;`scripts/doctor.sh` 打印版本,缺项以非零码退出 — R3 ok +- [x] C00.2 上游源码:`scripts/fetch-references.sh` 全部 pin 检出,`reference/*` 的 HEAD 等于 pin — R2 ok +- [x] C00.3 组件库研读:可复用组件、缺口、gpui 规则写入 `agents.md` — R3 ok +- [x] C00.4 引擎前置验证(§23):LOK 动态库加载与转换,MuPDF 构建并渲染一页,CEF 分发包可取,MailCore 2 依赖可构建。结果与阻塞项写入 `docs/engineering-risks.md`;官方 macOS 包出不了图块(R-01),出图块的证明改在 C07.1 — R5 ok(codex 复审) + +## C01 产品定义与平台(§1–§4) + +- [x] C01.1 workspace 骨架(§17.1):八个 app,加当前用到的 crate(`ely-ui`、`ely-platform`;其余在各自任务建,不留空 crate);AGPL 许可字段;`cargo check --workspace` 通过;每个 app 开出一个 Ely 主题窗口;`ELY_SMOKE=1` 启动即退出的冒烟脚本 — R2 ok +- [x] C01.2a 打包(§4.1、§4.2):macOS `.app` 脚本,Linux AppImage 配方,Windows 安装脚本;卸载保留用户目录。本机验证:macOS 包构建、签名、启动;Windows 脚本经 makensis 编译;Linux 桌面文件经校验。未验证:AppImage 构建、Windows 安装与卸载的运行、正式签名与公证 — R4 ok(codex 复审) +- [x] C01.2b 手动检查更新、下载、离线校验;包管理器安装的版本不自行更新(§4.2)。本条只做 `ely_platform::update`;更新前保存编辑的提示与界面在 C05.4、C06.4 — R3 ok +- [x] C01.2c 图形后端初始化失败时显示原因,不留空白窗口(§4.1)。已验证(macOS):失败信息写日志、打到标准错误、弹系统对话框,以 1 退出;GPUI 启动回调里的 panic(macOS 上会 abort)经 panic 钩子走同一条路,首帧之后的 panic 仍是普通 panic;启动前探测 Metal 设备。未验证:没有 Metal 设备的机器(探测失败分支)、`open_window` 返回错误的分支(macOS 只对弹出窗口返回错误)、Linux 与 Windows 的后端失败(未探测,本机无 Linux C 交叉工具链) — R3 ok + +## C05 统一 UI/UX(§5) + +- [x] C05.1 主题:浅色、深色、跟随系统,Omarchy Theme Sync,动效 token 与减少动效。已验证(macOS):Omarchy 自带的 22 套色板(`reference/omarchy`,pin f45461a)映射后满足组件库 A11yChecker 的全部规则(正文与状态色 4.5:1,焦点与图表色 3:1);`colorN` 形式的生成主题经回退链得到色相;运行中的应用按 Omarchy 的替换顺序跟随,无 `colors.toml` 时退回;覆盖开关与减少动效进入应用。Linux 与 Windows 的减少动效读取通过了目标编译(x86_64-unknown-linux-gnu、x86_64-pc-windows-msvc),未运行。未验证:Linux 上 Omarchy 默认目录的探测、gsettings 与 Windows 调用的实际结果、真实 Omarchy 机器 — R3 ok +- [x] C05.2 外壳:标题与标签区、左导航、内容区、右属性区、底部状态区;空侧栏不占位;窗口内转场。已验证(macOS):八应用共用 `ely_ui::Shell`;画廊冒烟与探针(`tests/scripts/shell.sh`)断言左右栏渲染与收起、one→two→one 转场、转场后键盘焦点保留、侧栏与内容区宽度(页面键不再重建内容子树);空栏占位与删栏两种变异实现均触发失败 — R3 ok +- [x] C05.3 命令:统一文件菜单命名、Cmd/Ctrl 语义、命令面板(UX-01、UX-02)。已验证(macOS):应用菜单在前使 File 成为独立菜单;菜单标签、菜单到动作的映射、六个命令经快捷键与菜单动作逐条触发且结果一致;面板经真实按键打开、输入查询、Enter 执行;toggle 关闭走面板自身的焦点归还。键位错接、菜单错接、绑定缺失、on_run 置空、派发丢弃五种变异均触发失败 — R5 ok +- [x] C05.4 交互规范:焦点与 Esc、禁用原因与混合态、长任务、失败保留输入、危险确认(UX-03 至 UX-07)。已验证(macOS):Tab 经 `tab_stop` 句柄按 nav→content→props→chip 顺序行进,Esc 只关当前浮层;阻塞命令菜单置灰、面板执行给出原因并 toast;混合选择显示 Mixed;长任务工作在后台执行器(范围/状态/错误/取消/重试,取消在检查点生效且错误优先);PromptDialog 校验失败保留输入与原因;ConfirmDialog 命名对象与影响并键盘可达;探针覆盖纯键盘流程,截图逐像素比对固化渲染 — R4 ok +- [x] C05.5 国际化与无障碍:简中与英文、非颜色表达、角色与名称、输入法与缩放(UX-08 至 UX-12)。已验证(macOS):语言探测重写(macOS preferredLanguages、Linux 首个非空优先、C/POSIX→en,单测);组件库 7380afb 起面板族文案经 i18n(内建 en/zh-CN)、对话框带角色/名称/说明、正文与校验错误可读、禁用带说明;对话框与面板 Enter 改走 key-down 动作,IME 候选确认不再提前提交,key-up-only 回归锁死且旧实现变异触发崩溃;resize 800×600 往返布局与点击断言;100%/150%/200% 与跨屏缩放需非 Retina 及多屏硬件,本机未验证(UX-10 部分) — R6 ok +- [~] C05.6 侧栏可调宽度(用户 2026-10-08 提出:左右侧栏太占地方):八个应用共用的外壳侧栏默认变窄(左 220、右 320,原 240、380),内侧边缘可拖动(左至少 160、右至少 220,至多窗口的 45%),宽度按应用记在其设置里、重启后照旧;`panels.sh` 真窗口核对默认宽、拖宽、记住、重启恢复 — R1 FAIL 三项已修:拖动后立即退出丢了宽度(退出时写入未存的宽度,窗口位置同);存着的无效宽度静默当作默认(严格解析,读不出即在设置窗口写明并用默认);写入失败只进日志(设置窗口以 Banner 写明,下次拖动或退出时重试);`panels.sh` 加三个场景(去掉退出时写入即失败) — R2 FAIL 两项已修:一个“未保存”提示供所有保存共用,通用设置保存成功即清掉侧栏的失败(按保存种类各记各的:通用、侧栏、窗口位置,各自成功才清);侧栏重试成功后旧提示仍在(成功即清其提示);`panels.sh` 加交错保存与修好后重试(全部清除即失败) — R3 FAIL 两项已修(不再送审):启动时记下的读取错误在拖动写入有效宽度后仍显示(读取错误也按种类记,该种类写入成功即一并清掉);测试在设置页内容进 AX 树之前就断言提示已消失(先等页面控件出现);回归:无效宽度经拖动修好后提示消失,去掉清除即失败 +- [ ] C05.7 窄窗口版面(用户 2026-10-08 提出:缩窄窗口时控件挤成一团、越出边框):外壳一次排两侧栏,内容区至少 360,侧栏先缩后藏(`room`,单测);组件库 6d95b24 至 69265b4 的纵向滚动区、可折行属性行、只显示图标的分段与标签、可缩的状态栏与按钮、折行的提示条、未聚焦文本框显示开头;应用里的固定宽度改为占满且有上限;重复标签的开关只给无障碍名;`tests/scripts/layout.sh` 六个应用三种宽度无元素越出窗口左右并留截图 — 未送审:用户 2026-10-09 定性能已够、先发首版 +- [ ] C05.8 文档缩放(用户 2026-10-08 问 Doc、Table、Show 为何不能缩放):`ely_office::Zooming`,Cmd+= / Cmd+- / Cmd+0、“视图”菜单、状态栏缩放控件(组件 `ZoomControl` 的属主模式),Cmd+滚轮与捏合以指针为中心,25% 至 400%,ElyShow 回到适合幻灯片;单测 `zooming_keeps_the_pointed_spot_and_walks_the_steps`,真窗口按键记过 125%、150%、125% — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C06 文件、保存与恢复(§6) + +- [x] C06.1 文件会话:位置、指纹、修订号、状态机、同文件聚焦(FILE-01、02、03,§6.1)。已验证(macOS):新建 `crates/ely-files`;新建文件首存前无路径;加载/已保存/未保存/保存中/只读/外部已修改/加载失败/保存失败八种状态;保存票据带任务序号与加载代次,旧修订完成不清新编辑状态,重载使旧票据失效,重复与旧回调不释放他人槽位;同文件(含硬链接、符号链接)再开聚焦或只读视图,原子替换后身份实时重取,路径解析错误全部传播且登记原子;32 测试,Windows/Linux 目标编译过 — R10 ok +- [x] C06.2 保存:原子替换、外部修改检查、冲突流、导出独立、路径与错误(FILE-04 至 FILE-10)。已验证(macOS):暂存写入+校验+替换,失败留原文件与未保存内容;统一 `save` 流程先查外部修改(冲突保留两版,重载/保留本地/比较三条出路);替换保留原权限与 macOS ACL;非原子卷先排他备份(禁截断禁跟链、源权限继承、名称无界生成),探测可注入并经跨目录符号链接锁死单行回退;导出按解析后路径与 inode 拒同文件;附件导入只复制且重名候选计数器必保;空格/中文/大小写/满长文件名全过;58 测试,Windows/Linux 目标编译过。FILE-10 的解码域错误(损坏/加密/缺字体/不支持)随各引擎任务落地 — R5 ok +- [x] C06.3 恢复副本与崩溃恢复:10 秒、20 点、7 天、容量;内容、光标、历史分开说明;加密文档不落明文(§6.2、§6.3、§18.2)。已验证(macOS):`Recovery` 每点暂存发布、单任务占用、20 点/7 天/容量(超限点提交前拒绝、计量含光标)、`tick` 节律;`RecoveryTask` 后台线程每 10 秒快照、condvar 即时取消、stop/drop 都 join、状态格不被磁盘 I/O 阻塞;内容/光标/历史三恢复能力分开报告;恢复排他落盘不跟链接;`discard` 清恢复点并留标记;加密恢复经 AES-256-GCM(RustCrypto,可交叉编译),密钥由 keyring 入系统凭据库(macOS Keychain/Windows 凭据/Linux Secret Service),OS 级排他锁(flock/LockFileEx)保护初始化,锁目录用 getpwuid_r 账户家目录不受 HOME/XDG 影响,加载错误明示不可用不导出明文;89 测试;Linux 的 Secret Service C 链本机交叉缺 dbus 头文件,未验证;Windows 目标编译过 — R11 ok +- [x] C06.4 保存策略:显式保存、可选自动保存、外部变更停止覆盖;Mark 1 秒自动保存。已验证(macOS):`PolicyEngine` 三策略(Explicit 默认显式、AutoSave 可选、MarkAutoSave);去抖用单调 Instant(校时不推迟);修订号跟踪——保存期间继续输入的较新编辑保留自己的 1 秒到期,无路径期编辑只记不调度、首保存得路径后续排;外部变更或兼容风险永久停止自动覆盖并一次性提示;自动保存失败保持未保存状态(下拍重试);通用可选自动保存复用 Mark 的 1 秒去抖(PRD 未定值,记此决策);99 测试 — R2 ok +- [~] C06.5 文档应用接入恢复副本与崩溃恢复:Doc、Table、Show、PDF 每 10 秒后台恢复点(加密文档封存),崩溃后以新副本恢复并分说内容、光标、历史;普通未签名文件可在设置里开自动保存(§6.2、§6.3;2026-10-08 发现只有 ElyMark 接了 `ely_files::Recovery`) — 已做:`ely_office::Recovering`(每 10 秒为有改动、已落定的标签写恢复点,进程崩溃后下次启动以未命名副本打开最新点并提示只恢复了内容);`ely_office::Stopping`(引擎进程退出时上方提示条写明原因与最近恢复点时间,“重新打开文档”起新引擎,有改动的标签从最新点、无改动的从文件重开,没有恢复点的列出);`ely_office::Autosaving`(设置“文档”页的开关,本应用自有格式、无风险、状态为已编辑的文件停手 2 秒后保存,失败或外部改动后不再自动写);Cmd+W 关闭标签移入共用命令(原来只有 ElyPDF 绑定,文档应用里 Cmd+W 落进内核,内核按自己的快捷键关掉文档,worker 随即在已释放的文档上崩溃),worker 拒绝内核关闭框架与退出的快捷键(Mod1+W、F4、Q);`recovery.sh`、`autosave.sh` 真窗口核对 — R1 FAIL 四项已修:恢复出的加密文档丢了封存(重开的副本接过原恢复点的槽,封存随之);恢复副本一载入就删了唯一的旧恢复点(改为新标签接过旧的恢复点文件夹,旧点留到新点写下,载入或解封失败的条目留在索引里);加密 PDF 的恢复副本要密码而重开不问(加密的副本照样询问,答后仍是未命名副本);空闲的 ElyPDF 引擎退出无人察觉(每次恢复计时都查引擎是否已退出);`pdf-recovery.sh` 真窗口核对加锁 PDF 的封存点、两次崩溃、空闲引擎退出与重开 — R2 FAIL 三项已修:打不开的恢复点在正常退出后的下一次启动被删(索引里未接过的条目标为保留,干净结束后只删其余的,下次再试);索引写不进只进日志(提示一次,之后每次计时重试,写成即止);接过旧恢复点后“最近的恢复点”仍为“尚无”(接过时取其时间);另:崩溃后的重开只在确有条目时写索引 — R3 FAIL 一项已修并加回归,三轮用尽:恢复索引原地覆写,写到一半失败即坏掉上一份(改为暂存后原子替换,`a_failed_index_write_keeps_the_last_one`) + +## C07 LOK 共用接入(§7) + +- [!] C07.1 LOK 固定构建与加载:自建 26.2.6.3 并打 `patches/libreoffice` 补丁,出图块;隔离用户配置,工作进程,打开、关闭、保存、转换,错误码不当成功,不执行宏(LOK-01、09、10、11)。已验证(macOS):补丁构建加载、未打补丁拒绝(官方 dmg 对照)、严格 JSON 版本握手、打开/保存/转换往返、错误码报出不成功、关闭清理(文档释放、joinThreads、kit 销毁、陈锁与 lu*.tmp 清、失败可见);生产 worker 无宏执行入口(dev argv 门 + debug_assertions,release 带 --dev 也拒绝,实测);6 测试。阻塞(不算完成):审查要求「禁用/放开宏两种配置下同一份自动执行文档出标记对照」,但本后端(LOK Aqua/unipoll)任何配置下文档事件都不派发给 Basic(OnLoad/表单按钮经鼠标键盘、温/冷 profile、两种 URL 形式、官方 AcceleratorKeyChecker.fodt 对照、放开宏变异共 10 轮证伪);属本机无法实测的运行验证,审查者与我们在 Linux/LO 7.4.7 上才见到事件触发 — R10 该项仍 FAIL,证据 tasks/reviews/C07.1-r1..r10.md +- [~] C07.2 图块渲染:失效通知、修订防旧覆盖、坐标、缩放、滚动、DPI;GPUI `LokView`(LOK-03、04)。已验证(macOS):编辑与 UNO 删除带精确矩形抬代际,只动光标不抬;视图只丢相交图块,旧代际图块不落;文档尺寸随批次刷新;滚动在增删页、缩放、视口变化后都夹回文档内;逻辑像素布局、DPI 只进纹理(2x 下一块 256 逻辑像素、512 纹理像素);BGRA 原样进 RenderImage;释放视图即停事件轮询;坏几何报错且 worker 存活。查出并修复 `postUnoCommand` 发往桌面活动框架(补丁 0002,补丁前 3/3 失败、后 15/15 通过)。23 测试。未验证:Linux/Windows TDF 构建(无补丁 0002) — R3 FAIL 一项(滚动夹定)已修并加回归,三轮用尽 +- [~] C07.3 输入:键盘、鼠标、中文输入法组合、剪贴板文本、富文本、图片、表格(LOK-06、07)。已验证(macOS):键盘(vcl 键码与修饰位,文本走输入法)、鼠标(按下/拖动/松开、点击计数、按钮,坐标与选区同一规则)、输入法组合开始/更新/提交/取消经 gpui `ElementInputHandler` 按 UTF-16 处理组合内选区与替换范围;光标、选区、光标可见性经回调画在视图上;系统剪贴板(`ely_platform::clipboard`,arboard)收发纯文本、HTML(富文本、表格)、PNG;剪切先写系统剪贴板再 `.uno:Cut`(Calc 不弹对话框);粘贴先验载荷(PNG 与 HTML 内嵌图片须解码),以撤销栈顶条目识别新步骤(满 100 步仍成立),失败回滚并报错;输入失败经 `LokError` 事件给应用;真窗口探针 `tests/scripts/lokview.sh` 断言输入法、点击、拖动、复制粘贴的文档结果,去掉鼠标处理或输入法变异均失败;49 测试。未验证:系统级拼音输入法实机、Linux/Windows 剪贴板运行(仅目标编译)、粘贴回滚分支无真实内核失败用例 — R3 FAIL 两项(HTML 内嵌坏图、撤销满)已修并加回归,三轮用尽 +- [~] C07.4 命令与状态:功能到接口映射表,状态回调,字体样式以内核为准,对话框由 Ely 实现,无障碍文本桥(LOK-02、05、08、12,§7 尾段)。已验证(macOS):`ely_lok::commands::COMMANDS` 为映射表(界面 id→UNO 名、带类型参数、确认方式),worker 只跑表内命令;参数按类型校验(字体须在内核字体表,字号 1–999 且 0.05 步,纯十进制);确认方式按实测:完成结果(SelectAll/粗斜体/下划线/删除线/样式)、同步撤销计数(Calc 另认单元格编辑文本变化)、状态回读(字体/字号),失败经 Undo 回滚并报错;状态回调(含持久已知状态)、选中对象框、字体与样式表来自内核;内核对话框(WINDOW created/JSDialog)在回调里即关并经 `LokError` 报出,Calc Backspace 不再卡死;无障碍桥:打开即取当前段落,段落/插入点/选区(-1 为无)、Calc 单元格与输入行文本,`Role::MultilineTextInput` + `aria_value`,真窗口 AX 树读到打开时与编辑后的段落;空闲时 worker 退出经 `LokError` 报出;70 测试。未验证:Linux/Windows 运行、Calc 打开时单元格文本(内核快照为空)、Calc 撤销按单元格编辑文本变化确认(非计数)— R3 FAIL 两项(Calc 单元格编辑撤销、字号容差)已修并加回归,三轮用尽 + +## C08 ElyDoc(§8) + +- [~] C08.1 应用壳、新建、模板、打开、保存、格式规则(DOC-01)。已验证(macOS):ElyDoc 文档标签共用一个 LOK worker,启动即开空白文档,焦点在文档,Cmd+N 立即可用(真窗口 AX 探针);新建:空白/报告/信函模板与本地模板文件(复制后无标题打开,原件不动,含宏的模板进入风险模式);打开同一文件聚焦已有标签;保存保留原格式(kernel 以 TakeOwnership 写副本,`ely_files::save` 原子替换),另存为只写 DOCX(默认)或 ODT,转换写新文件(别名指向原文件时按原格式、拒绝转换);外部修改走冲突对话框(从磁盘重载/保留我的/另存副本);含宏文件(docm/dotm、OOXML/ODF/OLE 宏结构、绑定命名空间的嵌入 Basic)进风险模式,保存只能另存副本;状态栏显示已保存/已编辑/正在保存/磁盘已改动/保存失败/风险模式,载入中显示占位标签;未保存关闭与退出询问(等待输入落地,并以内核撤销历史核对);保存等待排队输入;文件面板错误报出;页面居中、页外为主题底色;17 个应用测试、ely-files 104、ely-lok 72。未验证:原生打开/保存面板的自动化(测试直接给路径)、状态栏文字的 AX 暴露(组件缺口)、Linux/Windows、LOK 随包打包;左栏导航与右栏属性分属 C08.6 与 C08.2 — R3 FAIL 三项(关闭确认时序、只读会话失败、XML 宏识别)已修并加回归,三轮用尽 +- [~] C08.2 文字、段落、样式(DOC-02、03、04)。已验证(macOS,带补丁 0003 的 LOK):右侧属性栏(文字、段落、样式三组)跟随活动标签,选中对象时让位;字体取内核字体表,字号、粗斜体、下划线、删除线、上下标、颜色与高亮(色块弹出色板,“无”清除高亮);对齐、缩进、行距 1/1.5/2、段前与段后分设(磅,内核回显,另一项保持)、项目符号与编号、升降级、制表位(厘米,添加与逐个删除);段落与字符样式的应用、新建(名称校验,列表随即重读)、按所选更新,当前段落样式与字符样式都取内核状态(光标移动、撤销、重新显示后同步);混合选区各控件显示“混合”,不冒用首项,样式不唯一时“更新”禁用并说明原因;段距连续快速编辑不互相覆盖,数值框只在回车、失焦、步进时提交(组件库 `NumberInput::on_commit`,真窗口 `doc-props.sh` 断言两值与两条命令,恢复旧回调即失败);字符样式带来的属性不一致也显示“混合”(补丁 0003 扩到 `SwTextNode::GetParaAttr`);每条命令经 `COMMANDS` 白名单与内核确认,参数错误报出;真窗口点击加粗、红色与段后,文字与控件同步;落盘 DOCX 含颜色、高亮、上标、制表位、列表、新样式及其父样式与更新后的对齐,更新字符样式只改光标处的样式。21 个应用测试、ely-lok 84。未验证:自定义字号(菜单为常用字号加当前值)、内核界面名与程序名不同的内置样式在列表中的选中、Linux/Windows(需带补丁 0002、0003 的 LOK);“标题样式驱动大纲和目录”分属 C08.6 与 C08.4 — R3 FAIL 两项(字符样式属性的混合状态、段距回归未经真控件)已修并加回归,三轮用尽 +- [~] C08.3 页面、分节、页眉页脚、表格、图片与对象(DOC-05、06、07、08)。已验证(macOS,带补丁 0003–0007 的 LOK):插入菜单(表格对话框、图片经系统文件面板、分页符、分节符(确认后从当前页复制出新页面样式“第 N 节”再分页,前后两节各自分栏与页眉页脚;内核复制页面样式会清空撤销记录,故先确认)、页码、总页数);属性栏页面组(纸张 A3/A4/A5/B5/Letter/Legal 与当前尺寸、纵横向连同尺寸互换、四边页边距厘米连续编辑不互相覆盖、分栏 1–3、本节页面样式、页眉与页脚各自的开关及首页不同与奇偶页不同,未开启时禁用并说明;转向连同尺寸一步撤销);表格组(光标在表内出现:行列插删、合并拆分、平均列宽与行高、适合行高、全部或无边框、底色、标题行每页重复、行跨页允许或保持整行、删除表格,内核禁用的操作灰显并说明);选中图片时只显示图片组(宽高厘米保持比例、裁剪、替换、四种锚点、六种环绕、上下层次);视图画出选中对象的框与 8 个手柄,拖动手柄经 `setGraphicSelection` 缩放,裁剪模式下裁剪(真窗口实测);落盘含页面尺寸与方向、边距、分栏、页眉页脚与首页/偶数页、分节页面样式、页码与总页数、表格与表头重复、边框底色、行保持、合并单元格、图片锚点与环绕、裁剪、层次。组件库 `NumberInput::on_commit`、`ToggleButton::disabled`。26 个应用测试、ely-lok 94;修复 worker 请求在内核空闲任务中途重入的崩溃(补丁 0007,崩溃栈由 worker 信号处理打印)。未验证:Linux/Windows(需补丁 0002–0007 的 LOK)、表格行高列宽的数值设定(以平均与适合代替)、对象的精确位置、JSDialog 崩溃修复的稳定复现、C07.4 的无障碍选区测试约十余次全量运行失败一次(内核未发事件,原因未明) — R3 FAIL 一项(页面回归测试只等页面项、未等尺寸的竞态)已修,两测试改等完整页面状态,串行全量连跑三次 26/26,三轮用尽 +- [~] C08.4 引用结构、公式、审阅(DOC-09、10、11)。已验证(macOS,带补丁 0003–0008 的 LOK):插入菜单加目录(默认目录,标题随界面语言)、更新目录、脚注、尾注、书签(名称不可空、不可重名)、超链接(无文字时以地址为文字)、交叉引用(指向书签,显示文字、页码、章节号或上下方;无书签时说明)、题注(选中图片或公式为“图”,表内为“表”,编号与“: ”分隔照内核题注对话框;无对象时说明)、公式(文字转内核公式对象;选中公式时预填原式并整体替换,一步撤销)、批注;审阅菜单加批注、批注与修订面板、修订开关、全部接受、全部拒绝。右栏可切到审阅面板:修订开关随内核状态(未知时禁用)、全部接受或拒绝,批注按线程列出(作者、时间、正文),选中线程即滚到批注处并放置光标、可回复;修订逐条列出,可逐条接受或拒绝,点条目定位。列表随内核批注与修订回调刷新。补丁 0008 让目录、题注、公式不经内核对话框。真窗口实测:批注、回复、修订开关、键入被标记、逐条接受、公式插入与渲染。落盘含目录、脚注、尾注、书签、书签引用、超链接、题注序列、公式对象与原式、批注与回复、接受后的文字、拒绝后删去的文字。31 个应用测试、ely-lok 78;公式含补充平面与组合字符时不吞前文,替换选中公式留在原处并保留锚点、间距与对齐(浮动与行内),一步撤销复原;批注正文保留首尾与中间空段落未验证:Linux/Windows(需补丁 0002–0008 的 LOK)、作者名(内核显示“Unknown Author”,作者设置待定)、批注组件内部文案(Open、Resolved、Reply、相对时间)为英文(组件 i18n 上游待办)、交叉引用只指向书签(不含标题与题注)、公式导出 DOCX 后的可编辑性、插入目录后光标落在目录内(内核原样) — R3 FAIL 一项(替换浮动公式时把手动纵坐标当作基线自动定位清掉)已修:只对行内公式清除自动纵向定位,回归断言页锚点公式的 svg:x、svg:y 不变,三轮用尽 +- [~] C08.5 查找替换、拼写、字数、文档比较、邮件合并(DOC-12、13、14)。已验证(macOS,带补丁 0003–0009 与英文词典的 LOK):工具菜单“查找和替换”(Cmd+F)在页面右上打开查找框:键入即计数并高亮全部,“n/共 m”随上下一处走,区分大小写、全词、正则(内核 ICU)三个开关,替换一处后重新计数,无当前项时“替换”先选下一处,全部替换;查询可为空白;上下一处滚到匹配处;查找后文档被改动时计数标“已改动”,回车或下一次查找重新计数(边打字边计数会选中全部匹配,故不自动);比较、预览、生成都先等源标签排队的输入落到内核,且始终用发起时的标签;计数中按回车或下一处会等计数落地再定位;字段值里的 `«列名»` 原样保留(先换成一次性记号再填值);生成先写暂存文件再以独占新建发布,检查之后才出现的同名文件不被覆盖;字数取内核(补丁 0009),状态栏显示全文或选区字数;拼写用随包 Hunspell 英文词典,错词红色波浪线由内核画,右键在插入点下给建议与“全部忽略”(组件库 `ContextMenu::manual`,967d1e4,注释在 aaaf0d5);审阅菜单“与其他版本比较…”把当前文档作副本开新标签并标出对较旧文件的修订,两份源文件不动,审阅面板随即打开;工具菜单“邮件合并…”选 UTF-8 CSV(首行为列名,空名、重名、无数据行、列数不齐、列名含 « 或 »、非 UTF-8 均报错),字段按钮在光标处写入 `«列名»`,按记录号预览为新标签,生成按 DOCX 或 PDF 每行一个文件写到所选文件夹,任一目标已存在则整体拒绝,不发邮件。真窗口实测:查找计数、下一处、替换一处、拼写右键建议与应用、合并面板、插入字段、预览。40 个应用测试、ely-lok 81。未验证:Linux/Windows(需补丁 0002–0009 的 LOK 与词典)、写错的正则只显示“无结果”(内核不报原因)、查找框与批注面板的组件内部文案为英文(组件 i18n 上游待办)、英文以外的拼写词典(只随包英文,待用户确认语种)、`«列名»` 字段写法待用户确认 — R3 FAIL 三项已修:连续两次生成各自占用独立模板与暂存目录;新查询或清空查询使在途结果作废;计数中的多次定位按序排队执行;各加回归,三轮用尽 +- [~] C08.6 打印、导出、长文档导航、兼容风险提示、往返测试(DOC-15、16,§8.3,QA-10)。已验证(macOS,带补丁 0003–0011 的 LOK):文件菜单“导出为 PDF…”(Cmd+E)与“打印…”(Cmd+P)先开同一对话框:页面全部或范围(起止页受页数约束)、批注印在页边、标出修订;导出经系统存储面板写 PDF,完成后提示,目标是打开着的文档(符号链接或硬链接也算)即拒绝;打印把同一 PDF 交给系统打印面板(PDFKit),取消或打印都记日志;加密文档询问密码(遮蔽输入),错的密码再问,对的打开;左栏导航(工具菜单与 Cmd+[ 返回)分大纲、页面、书签三页,大纲、当前标题与书签位置取内核(补丁 0010;当前标题按文档顺序,分栏也对),当前页按光标所在页,点标题、页、书签即按位置跳转,跳前记下所在处(上一跳未落下或光标未跟上时以其目标为准,落下后按实际光标),“返回”逐级回原处(关闭的标签提示);缺字体时页面上方提示条,文档写的字体列表全缺照报,有成员可用的列表不报(补丁 0011)。往返:合同(编号列表、批注、修订)与长报告(标题、目录、跨页且表头重复的 80 行表格)经 DOCX 往返,结构与文字保留。真窗口实测:导航、当前项随跳转(含同页两标题)、返回、缺字体提示、导出对话框与范围、系统打印面板的三页预览与取消。43 个应用测试、ely-lok 110。未验证:Linux/Windows(需补丁 0002–0011 的 LOK;打印在其他平台报错未实现)、QA-10 与 Microsoft Office 互开(本机无 Office)、真实打印机出纸 — R1 FAIL 四项已修:导出可覆盖打开着的源文件;连续跳转时“返回”记成旧光标;文档写的字体列表被整串滤掉;同页标题的当前项不随跳转;各加回归 — R2 FAIL 两项已修:导出与另存为按文件身份比对(硬链接也认出),另存为到本文件的硬链接按本文件格式判定转换;上一跳落下后不再当作在途;各加回归 — R3 FAIL 一项(分栏时当前标题只比纵坐标,落到右栏标题)已修:当前标题改由内核按文档顺序给出(补丁 0010 加 current),加两栏回归,三轮用尽 + +## C09 ElyTable(§9) + +- [~] C09.1 应用壳、工作簿、区域编辑(TABLE-01、02)。已验证(macOS,带补丁 0003–0013 的 LOK):ElyDoc 的文档外壳移入 `crates/ely-office` 后两应用共用(ElyDoc 41+2 个测试照过,真窗口脚本 doc-shell、doc-props 照过);ElyTable 启动即开空白工作簿,名称框与公式栏随单元格(回车跳转或写入,Esc 交回网格),行列表头按内核几何对齐图块(实测拖宽 A 列后表头随之重排),点表头选整行整列,拖表头边改行高列宽,填充柄拖到 A6 续出等差序列,选区统计取内核数值、名称换成界面语言(平均值 21 求和 42);工作表标签切换、加号插入、右键菜单与“工作表”菜单的插入、重命名(名称校验)、复制、左右移、隐藏、显示、删除(列出引用该表的公式数、第一个公式格与命名区域数);“单元格”菜单插删行列与单元格、合并拆分(两个以上数据格先问移入或保留隐藏)、行高列宽(厘米,作用于整个选区)与最适、隐藏显示行列、冻结(真窗口 B2 冻结后上下左右滚动,首行首列不动,表头同步);Tab 与 Shift-Tab 在格间移动;名称框跳到远处时画布随单元格光标滚动;右键工作表标签先选中该表再出菜单;表头可 Tab 到达、带列头/行头角色与名称、回车或空格选整列整行;新建工作簿另存 XLSX 后重开工作表齐全。内核测试 ely-lok 119(两工作簿各自切表与写入、工作簿与区域编辑各一组、冻结状态、INDIRECT 计数、作者署名),应用测试 10(删除与合并的确认绑定发起的工作簿、含切表前后输入顺序、切表等待期间换工作簿、名称框随活动工作簿);组件库加 `Tabs::on_context`(2f02c82)。未验证:Linux/Windows(需补丁 0002–0012 的 LOK)、`ely_platform::user` 的 Linux、Windows 分支未经编译器(本机无交叉工具链)、行列分组与筛选隐藏的表头、触控板以外的大表滚动性能(C09.5 的 TABLE-16)、`a11y_follows_the_focused_paragraph` 间歇(单跑 8 败 1,全量 4 败 2,原因未明) — R1 FAIL 九项已修:首帧工作表标签崩溃;切表越过输入队列;单元格光标不带动滚动;冻结不分格;尺寸对话框只改一行一列;右键菜单丢失被点的表;INDIRECT 引用未计入删除说明;平面 ODF 宏检测漏子元素命名空间(改用 quick-xml 按命名空间作用域解析);表头无角色与键盘操作;各加回归 — R2 FAIL 五项已修:多工作簿时 worker 未切到文档自己的内核视图(现每次请求先 setView);切表未进有序输入队列;延迟切表丢失发起的工作簿;远处跳转被缓存的文档尺寸截住(真窗口 A100 与连续下移到 A130 实测可见);切换工作簿后名称框与公式栏不刷新;各加回归 — R3 FAIL 两项已修,三轮用尽:删除工作表与合并的等待回调重读活动工作簿(现请求与确认框带发起标签的 id,经 `run_in` 执行;回归在去掉修复时失败);水平滚动只比左边缘(现比右边缘,回归在去掉修复时失败;真窗口名称框跳 M1,整格与填充柄可见) +- [~] C09.2 公式输入、计算、计算控制、名称与引用(TABLE-03、04、05、06)。已验证(macOS,带补丁 0003–0015 的 LOK):公式为 Excel A1 语法与逗号分隔(用户定,安装层 ely.xcd;`=SUM(1,2)`、跨表 `'S'!D1`、绝对与混合引用实测);单元格内键入时函数列表在格下(真窗口:`=SU` 列出 SUM 等与签名、说明,下键选 SUMIF、Tab 补全为 `=SUMIF()`),参数提示取内核用法文字,点选单元格插入引用并画框(真窗口点 C3 得 `=SUMIF(C3)` 与品红框),点选后继续键入、F2 再编辑已有公式两引用各画框、提交与取消都清框;语法错误的公式照原样输入不弹询问;状态栏按界面语言解释当前格错误(25 条,含循环引用 Err:522);算术、逻辑、条件统计、文本、日期、查找、动态数组(FILTER、SORT、UNIQUE、SEQUENCE、XLOOKUP)取内核结果;自动计算关闭后改值不重算、重新计算后更新,ods、xlsx、xls、fods 存后重开仍为手动(补丁前 xlsx、xls 变回自动);名称:工作簿与工作表范围定义、改引用、撤销、删除,像单元格地址或含符号的名称由内核判定后拒绝,名称框跳转名称,删去目标行后名称标为失效;“公式”菜单(自动计算、重算、名称、追踪错误与引用从属、移去箭头)。同轮修内核:JSDialog 发送者在发送中被关闭的释放后使用(0005 增补)、Aqua 直接超时丢失唤醒致新载入 XLSX 无初始状态(0015,约四分之三复现到 5/5 正常)、载入选项未被 LOK 解析。测试:ely-lok 公式 7 项、ely-table 17 项(含定义名称取所选区域) — R1 FAIL 五项已修:名称列表“转到”按名称跳转,跨表的工作表名称失败、同名工作簿名称被当前表遮蔽(现由内核列出每个名称的绝对区域,转到即跳该区域;非区域名称不可转到);定义名称在排队输入落地前读选区(现等发起工作簿的输入落地,并经新加的 `.uno:SelectedRange` 同步向内核要所选区域,选区显示为名称时也得到其区域,多个区域时提示后留空);等待后的名称列表改用当前工作簿(现带发起的工作簿 id);七处注释超长。各加回归(跨表名称与遮蔽、排队后定义与名称选区、等待中换工作簿),转到的回归在旧实现下失败 — R2 FAIL 一项已修:定义名称的“范围”列表取自当前工作簿,等待中换工作簿后列出别的工作簿的表(现取自对话框所属工作簿);同类问题一并收口:重命名、显示工作表、行高列宽对话框都带发起的工作簿 id,确认时只作用于它。回归:等待中换工作簿后范围列表与定义落点、重命名中换工作簿,均在旧实现下失败 — R3 FAIL 一项已修,三轮用尽:名称列表“转到”只移动发起工作簿的选区而不切到它(现先切到该工作簿再跳转,名称框随之显示目标);回归断言跳转后活动工作簿与名称框,在旧实现下失败 +- [~] C09.2a 内容区随系统深浅色,重绘不闪(用户 2026-10-04 提出;LokView、ElyDoc、ElyTable)。已验证(macOS):内核以 Ely 的深浅两套颜色方案绘制,Writer 页面与 Calc 网格深色时平均亮度约 26–35、浅色约 240–254,切回浅色复原(`the_page_follows_a_dark_theme_and_back`);深色下导出 PDF 仍白底(Quick Look 渲染平均约 255);真窗口系统深色下 ElyDoc 页面深底浅字(含中文)、ElyTable 深色网格,`ELY_APPEARANCE=light` 下为白底;主题切换不把文档标为已改;作废的图块继续显示到新图到达,仍按作废请求重绘(视图测试) — R1 FAIL 两项已修:图块重绘失败只记日志,界面一直显示旧图且同一代际不再重试(现报给应用的错误通路,可视区移动或下次作废时重试;回归注入失败断言报错与移动后重试);主题回归绕过视图(现经 `LokView::follow_theme` 与有序队列驱动,断言内核实际绘出的深浅;把深色方案名改成浅色时该测试失败) — R2 FAIL 一项已修:单元测试无法初始化应用主题,`render` 取主题这一步无回归(把它固定为浅色时单测全过);新加真窗口脚本 `tests/scripts/theme.sh`,以 `ELY_APPEARANCE=dark/light` 启动 ElyTable、截窗口量网格亮度(实测深 26、浅 252),上述变异下脚本报“the dark grid paints at 252” — R3 FAIL 两项已修,三轮用尽:脚本只量平均亮度,不画图块时底色也能过(现另量网格线像素占比,只有内核图块画网格线;去掉图块绘制的变异下报“no grid lines (0 per mille)”);缺屏幕录制权限时 SKIP 被当亮度比较而报 FAIL(现判断在顶层,权限拒绝时 SKIP 退出 0,已模拟验证) +- [~] C09.3 格式、数据整理、规则(TABLE-07、08、09) — R1 FAIL 九项已修:分列、数据验证、条件格式列表的等待回调重读活动工作簿(现带发起的工作簿 id);粘贴为公式跨工作簿时按纯文本粘贴丢公式、粘贴为值会执行 `=` 开头的文字(现只用本视图复制的内核单元格,其余明确拒绝并提示);单元格规则缓存只按地址,原地设规则或清除后不刷新、同地址切表复用(现按工作表计数、地址、编辑数);清除数据验证发空的运算符与样式被内核拒绝(现发完整的“任何值”);“任何值”带输入提示时被当作无规则丢掉提示(现以有无运算符区分);格式面板同地址切表不重读(同上,键含工作表计数);日志记下文档自己的验证标题与内容(现只记种类,内容只进提示);边框验收只看命令成功(现核对存档单元格属性,全部线关的变异下失败);补丁超 500 行(拆为 0016–0018)。各加回归(ely-table 23 项,含三条等待路径、原地设规则与清除、同地址切表;ely-lok rules 7 项)。同轮按用户要求修卡顿,见 agents.md 2026-10-05 决策 — R2 FAIL 五项已修:粘贴为值/公式只比文字,另一工作簿同样文字的复制可通过(现以 Ely 复制写入 HTML 的唯一标记识别,经真实剪贴板验证);数据验证、条件格式列表、数据区、筛选按钮、序列、格式代码、名称、删除工作表、合并等仍在界面线程读内核(现全部经后台读取;保存导出、导出 PDF、比较、邮件合并同改,保存期间状态为正在保存,同标签的保存排队);切换文档时审阅与导航仍显示上一文档的列表(现切换即清空);读取失败后立即重读成环(现记下已问的状态,等下一次变化);按键到新图块的计时可被旧图块结束(现只在编辑落地后的新代际图块到达时结束,回归覆盖)。另修异步后暴露的两处:同一标签的两次保存并发(现排队)、替换一处后的过期标记晚到使“全部替换”只重新计数(现计数结果即刻清除过期并作为基准) — R3 FAIL 五项已修,三轮用尽:导出期间又有编辑时“保存后关闭”仍关闭并丢失这些编辑(现回到关闭询问;回归 `typing_during_a_closing_save_is_never_lost`);格式代码校验返回后发给当时的活动工作簿(现发给发起的工作簿;回归 `a_format_code_lands_in_the_workbook_that_asked`);两个标签同时另存为同一路径时后者覆盖前者(现写入前按文件身份再查持有者;回归 `two_tabs_saving_as_one_path_keep_one_owner`);邮件合并预览在模板导出完成前未保留标签号,交错的预览串用模板(现发起时保留;回归 `interleaved_previews_keep_their_sources`);关闭干净的标签仍在界面线程读撤销历史(现后台读;回归 `closing_a_clean_tab_never_waits_for_the_worker`,worker 被占 400 毫秒时关闭调用不足 100 毫秒)。同轮查出事件等待阻塞后台线程(每个文档一个,最多一秒)的根因并改为异步唤醒,测试由约 80 秒降到约 15 秒 +- [~] C09.4 透视表与图表(TABLE-10、11) — 补丁 0019(透视表)、0020(图表);ElyTable“插入”菜单:数据透视表(新建/原地修改,字段放行、列、筛选、值,值函数求和、计数、平均、最大、最小,对话框显示源区域)、刷新数据透视表、图表(柱、条、线、散点、饼;区域、标题、图例、X/Y 轴标题;选中图表时为修改,一步撤销)。顺带修根因:无数据时 `select.data` 留下单格标记使回车不下移;组件库 e21336f 让属主直接移除的对话框(含一起移除的嵌套浮层)交回焦点。R1 FAIL 六项已修:图表区域解析不了即拒绝;对话框的读取回来时发起的工作簿已非当前标签即丢弃(`Tabs::ask_in`,ElyTable 全部对话框与合并询问);透视表数据布局字段读写保留(空名),读出原样写回不改表;嵌套浮层一起移除时焦点回到最外层之前;补丁拆为 0019、0020;注释压到十字内。R2 FAIL 三项已修:图表区域另查所指工作表存在(`sctools::chartRanges`,插入与修改共用;原样“bad range”修改使 worker 因未捕获异常退出,现拒绝且撤销步数不变);已在表中的字段写回时保留小计函数(含小计的 FODS 往返测试,去掉修复时失败);对话框控件经组件库 8658999 的 `Select::label`、`TextInput::label` 与开关自身的 `label` 命名(真窗口 AX 树英文与中文核对:图表对话框六项、透视表各字段、公式栏两项与格式面板两项原先也无名,一并修)。R3 FAIL 一项已修,三轮用尽:图表修改的撤销原只存外观 JSON,换类型后撤销丢掉堆积等设置;现撤销存整个图示与主标题的副本,外观先改、区域后改,撤销先回区域再回图示(`undoing_a_new_kind_brings_the_whole_chart_back`:堆积柱形图改饼图后撤销,导出仍带 `chart:stacked="true"`;换回旧做法时失败)。测试:ely-lok `client::tests::analysis`、`enter_and_tab_move_the_cell_cursor`,ElyTable `tests::analysis`,组件库 `a_dialog_its_owner_drops_hands_focus_back`、`nested_dialogs_dropped_together_hand_focus_back`,ely-lok `a_read_layout_applies_back_unchanged`,ElyTable `a_dialog_asked_from_a_workbook_left_behind_is_dropped`;真窗口看过两个对话框与插入的图表。 +- [~] C09.5 CSV/TSV、导出、打印、保护、大数据浏览(TABLE-12、13、14、15、16,§9.3,QA-11)。已验证(macOS,带补丁 0003–0022 的 LOK):打开 csv/tsv/tab/txt 先出导入对话框(编码、分隔符、引号、逐列类型,预览按所选读),GB18030 与 UTF-8 样本、引号内含分隔符、前导零与 20 位编号保持文本、`=1+1` 文字不执行、年月日列读成日期;导出 PDF/CSV/TSV(文本只写当前表的显示值,对话框写明损失),打印经同一 PDF 交系统面板;页面设置(打印区域、重复行列、方向、三种缩放)与行列分页符,XLSX 往返保留区域、标题行列、横向与按页缩放;工作表保护(密码、八项许可)、锁定与解锁单元格,锁定格不可改、错误密码仍受保护;XLSX 载入总是重算,未知函数显示 #NAME? 且公式保留,过期缓存不冒充结果;外部链接显示保存值并在提示条列出来源;10 万行表滚到底只留屏边图块(去掉淘汰时该测试失败)。真窗口(macOS,中文界面,深色)看过导出(切到 CSV 时的损失说明)、页面设置、保护工作表三个对话框。未验证:1904 日期系统、Linux 与 Windows — R1 FAIL 六项已修:`.txt` 按扩展名落进 Writer(载入选项加 `FilterName`,补丁 0022;worker 查文档类型,去掉过滤器名时回归失败);列类型只看前 64 KiB(后台读整个文件,18,000 行后的 `007` 仍为文本,存 XLSX 重开照旧);解码静默替换(严格解码,读不通即提示且不能打开);预览行宽不含全部列(行宽按列数,真窗口横向滚到后面的列);预览文字未取主题色(取前景色,真窗口深色可读);缩放模式与数值框挤在一列(拆成两行,真窗口三种模式与宽高两框都在可见区)。分隔符改为下拉(分段控件在中文下被裁) — R2 FAIL 四项已修:提交时重新复制源文件,校验与导入不是同一份字节(导入改为写入检查过的字节,回归在对话框期间改写源文件);旧对话框的扫描回调落进新对话框(按字节快照身份与扫描序号核对,两种先后顺序都测,去掉身份核对时失败);第 65 列起无类型选择(去掉列数上限,真窗口 AX 树有第 65 列);链接只在首次读取(随编辑计数重读,回归在空白工作簿里新加外部引用) — R3 FAIL 三项已修,未经复审:预览拆字段与内核不一致(引号前空格、空行;改按 Calc `impex.cxx` 的规则拆,去掉 csv 依赖;应用回归逐格比对预览与内核导入结果);负号开头的长编号与前导零未判为文本(按数字主体判定,回归中去掉负号处理时失败);补丁 0021 把透视表函数的注释留在新函数上方(移回原处) + +## C10 ElyShow(§10) + +- [~] C10.1 应用壳、幻灯片管理、模板与母版(SHOW-01、02)。已验证(macOS,带补丁 0003–0026 的 LOK):左侧缩略图(深色随主题,隐藏页半透明带标记,点选与右键)、中间按视口缩放居中的画布、右侧版式(12 种)、母版、配色(内核 7 套)、尺寸(预设与厘米);幻灯片菜单与右键:新建、复制、删除、隐藏、显示、上下移、移到开头末尾,各为一步撤销;新建对话框:内核空白演示与随带模板;编辑母版:缩略图与画布换成母版,新建母版,粘贴到母版的文字出现在用它的幻灯片上并存入母版;关闭母版回到幻灯片。真窗口看过新建、母版进出、新增幻灯片。[!] 章节:内核无章节(PPTX 导入丢弃 `p14:sectionLst`,ODF 无此结构),用户定为外部阻塞 — R1 FAIL 七项已修:尺寸框在回显前按旧值重建(发出的尺寸保留到读回,连续改宽高以它为底,回归先后两次改尺寸);尺寸改动与撤销不通知 LOK,视图尺寸停在旧值(补丁 0023 在 `SetPageSizeAndBorder` 与页面格式撤销重做里通知,视图回归读到新尺寸);适配缩放下限 25%(只留上限,3 米页面回归);撤销绕过缩略图重画(读取时比对尺寸、配色、母版表,变了就全部重画;撤销回归逐页比对内核新画像素,去掉比对时失败);缩略图失败后立即重试成环(失败页等下次读取,回归在内核丢文档后只失败一次);缩略图行无 AX 角色(列表与列表项,当前、序号、总数、隐藏说明,真窗口 AX 树核对);宽高框无名称(组件库 7186179 加 `NumberInput::label`,真窗口 AXTitle 为宽(厘米)、高(厘米)) — R2 FAIL 四项已修:同一尺寸重复提交(按显示尺寸去重,回归回车加失焦只一步撤销);整页适配下光标扩大滚动范围、页面跳到左上(适配时范围取文档尺寸,视图回归把滚动范围扩到三倍后页框不动);母版内容撤销不重画引用页(读取时比对撤销与重做步数,撤销后全部重画,回归逐页比像素);模板行无 AX 与键盘(组件库 80c46b1:模板行为列表项,可 Tab 与方向键选取,真窗口 AX 核对)。另修真窗口发现的文字编辑图块全黑(补丁 0024,内核回归撤补丁即失败)与离开编辑后缩略图留编辑框(`LokView::caret`,回归去掉即失败) — R3 FAIL 一项已修:撤销后立即编辑时重做数不变,母版内容的撤销漏画(改按撤销、重做列表的条目判定,回归“撤销后立即隐藏”与“重做后立即隐藏”,改回按数即失败)。另修压测发现的偶发全黑图块与命令丢失(补丁 0025、0026,八路并行各 96 次全绿)。三轮审查已满 +- [~] C10.2 文字、图形、图片、对象操作(SHOW-03、04、05、06)。已验证(macOS,带补丁 0003–0027 的 LOK):插入文本框(键入)、矩形、椭圆、三角形、直线、箭头、连接线、图片(落在幻灯片中央,替换原位且一步撤销,裁剪进入裁剪模式);对象面板改位置(3 厘米落在 3 厘米)、大小、旋转、锁定、填充与无填充、透明度、阴影、轮廓、线宽、字体、字号、加粗、项目符号、自动适配三档;两对象左对齐、组合、取消组合;参考线添加、移动、删除与吸附;对象列表在 AX 树里是列表,行名为种类、说明为占位符角色;缩略图随面板命令重画。内核回归:读取、按层次选中、twips 换算、参考线、图片居中与替换。应用回归:七项(含回车加失焦只发一次,去掉判定即失败)。组件库 7df3045:`SelectableList` 与 `ListItem` 的 AX 语义。连接线粘附到图形未单独验证 — R1 FAIL 六项已修:清除按钮把 0 当黑色发(清除各发自己的命令,回归走面板常量);已发记录不随读取失效(改按标签与读取次数,撤销后再发与两个同框对象各发各的回归);线类对象无轮廓控件(直线、箭头、连接线、图片显示轮廓与线宽,回归改直线颜色与线宽);列表清空不取消选中(补丁 0027 加只带 `Add` 即全不选,内核与应用回归);混合值显示为 0 且挡住设 0(混合为空、显示“混合”,回归两对象统一设 0 为一步);补丁重叠使重跑构建失败(`scripts/apply-lo-patches.sh` 按序列定位,`tests/scripts/lo-patches.sh` 从第 13 个接着打、再打为空操作,完整构建脚本重跑通过) — R2 FAIL 三项已修:混合数值框里是真的 0,直接回车即发送(组件库 af54877 加 `NumberInput::blank`,空着不提交,组件回归去掉判定即失败);混合选区的自动适配选“不适配”不发命令(补丁 0027 让 `.uno:TextAutoFitToSize` 收 `Fit`,对全部选中对象一步撤销,回归标题缩小、内容增长、合选后不适配为一步);`object.rs` 502 行(文字组移入 `text.rs`) — R3 FAIL 两项已修:“不适配”留着横向自动增长(三档都关横向增长);缩小后撤销改了原段落格式(撤销记录连同文字保存);内核回归导入横向增长文本框设不适配后保存无横向增长、20 磅小框缩小再撤销后绘制与保存的段落不变,去掉保存文字即失败。三轮审查已满 +- [~] C10.3 表格与图表、备注与审阅(SHOW-07、08)。已验证(macOS,带补丁 0003–0032 的 LOK):插入表格(4×3 落在中央,选中),表格组在光标进格后可用,下方插入行;插入图表(对话框的外观与数据表,非数字格拒绝并指明,选中图表再开为修改,改值一步撤销,读回为新值);备注栏键入后提交为一步撤销,撤销复原;PPTX 备注(每段带全字符格式)可编辑且沿用原格式,格式不一致的备注只读、命令拒绝;批注对话框打开即可键入,回车保存,批注栏中文、显示作者与“刚刚”,选中点亮画布标记,编辑、删除;批注不进对象列表,对象序号也不数批注。R1 FAIL 十项已修,R2 FAIL 三项已修,均带回归(见下)。R1 修后:备注随所属页提交(切页排在提交前也不错页),段落格式一致时沿用、不一致时只读;缺失的点读写为空;类型错的图表 JSON 拒绝;读图表回来时已换标签即丢弃。R2 修后:图表 JSON 经 boost.json 保留类型;散点图 X 按数字写入并导出,PPTX 存后重开可改数据,再存再开值不变;读图表以读取时内核的选中对象核对(同外框的另一图表、跨页都丢弃);表格对话框键入的行列数即生效(真窗口未复验:本机当时有人在用);批注列键盘可达。内核回归:图表插入、修改与撤销,备注读写与撤销,批注序号、作者、编辑、删除,表格插入与增行。 R3 FAIL 三项已修,带回归与变异检验,未复审:散点图改标题删掉原有连接线(只在图表变成散点图时设为只画点,`a_retitled_scatter_keeps_its_lines`);散点图的 X 是文字时应用不拦、内核拒绝后草稿丢失(应用按类型先查 X,指明哪一格,对话框保留,`a_scatter_with_words_for_x_keeps_its_draft_and_names_the_cell`);选中的批注离开所在页后不能再点回(只在所在页显示为选中,`a_comment_picked_on_another_slide_shows_its_slide_again`)。 +- [~] C10.4 动画、切换、媒体(SHOW-09、10)。已验证(macOS,带补丁 0003–0035 的 LOK):切换按预设设定、读回、一步撤销,Ely 给的十种都能设,全部幻灯片一次设定,类型不合拒绝;动画加入、改开始方式与时长延迟、前后移、删除各一步撤销,十四种都能加,效果的目标序号与放映图层散列相符;图层在 macOS 上有像素(补丁 0033 前全空);真窗口看过淡入预览(标题由暗渐亮,空占位符不显示)与向左推入预览;面板与菜单中文。媒体:嵌入插入在中央、存后重开仍可播放副本,链接文件删去即标为缺失,缺失文件插入被拒;系统播放器(macOS AVFoundation)打开 H.264/AAC 样本、给出 1 秒与 160×90、播放出帧,坏文件给出原因(主线程集成测试)。R1 修后:播放器接口在 Linux、Windows 目标可编译(`tests/scripts/media-targets.sh`,不可用的播放器为空类型),播放实例按标签、幻灯片名与媒体 URL 认定,换页、删除或替换即停;播完再按播放从头开始(平台测试覆盖);向左揭开时旧页在上;预览按标签、幻灯片名与撤销列表认定,读取回来或绘制时不符即弃,停止使在途读取作废(应用测试与变异检验);时长、等待、延迟的回车加失焦只发一次(应用测试与变异检验)。R2 修后:预览另按视图已落地与待落地的编辑认定,键入未成撤销条目也弃(`typing_ends_a_preview_before_any_undo_step`);切换各字段以该页已发未读回的切换为底合并,读回相符或换页即清(`two_fields_sent_before_a_read_both_land`);正在播放的媒体在每次文档状态通知时核对,关闭最后一个标签即停(主线程真窗口探针验证,去掉观察即仍在播;系统播放器只能在主线程打开,应用单测在工作线程,无法常驻测试);`media-targets.sh` 调用应用用到的全部播放器方法(删去 `pause` 即失败);切换合成移入 `preview/compose.rs`,`tests/compose.rs` 以 Metal 画半程帧取像素(向左揭开、向左覆盖、向左推入左旧右新;叠放顺序任一写死都失败)。R3 FAIL 两项已修,带回归与变异检验,未复审:已发未读回的切换按其命令的输入序号认定,命令落地后开始的读取一到即清(不再比值),发出后立即撤销也不会把撤销掉的时长再写回(`an_undone_duration_is_not_sent_again`);动画秒数的去重按幻灯片、效果序号与图层散列区分,回读前给两个效果提交同一值都生效,回车加失焦仍各一步撤销(`two_effects_take_the_same_value_before_a_read`)。未验证:真窗口里插入与播放媒体(本机当时有人在用);Windows 与 Linux 播放器未构建;ElyShow 自身未在非 macOS 目标编译(本机缺 ring 的交叉 C 工具链)。 +- [~] C10.5 放映与多屏、演讲者视图(SHOW-11、12)。已验证(macOS,带补丁 0003–0036 的 LOK):`.uno:SlideShow` 读出每张的隐藏、备注、切换、动画与媒体(内核测试);母版页脚与页码由内核画成文字层,落在占位处的层次上,字段关闭的幻灯片不画(内核测试,按像素行);放映从头开始与从当前页开始(当前页隐藏也从它开始),单击组逐步播放,跳过隐藏页到黑色结束屏再退出,后退为已完成状态,数字加回车跳页,黑屏、激光笔、Esc 退出,应用快捷键不被吞(应用测试,按键经放映窗口送入,变异检验:按键挂在焦点之下或同步关窗即失败);单击媒体对象按外框命中(应用测试);真窗口:F5 全屏放映、B 黑屏、右键到结束、Esc 结束(日志)。R1 FAIL 九项已修,带回归与变异检验:两窗口的根作用域各自取得焦点(演讲者视图与观众窗口都收按键);连按两次开始只开一次(启动中的状态,每次的副本文件名不同,旧放映结束不清新放映);“从当前幻灯片开始”等已发输入落地,并在同一次 worker 调用里读当前页;放映播的是开始时另存并载入的副本(编辑不进入放映,结束时关闭并删除,副本按应用深浅色设方案);单击组从此前全部动画与入页切换结束之后开始(`steps::click_at`);离开的页按离开时的动画状态画在切换里(完成的退出不再出现);迟到的图层若已不是当前或下一页即丢弃,同时最多读两页;跳转(含跳到本页、Home、End)无切换并重新开始本页;媒体载入失败与播放中失败都停下并在演讲者视图(单屏时在观众屏)写明原因,放映继续。R2 FAIL 六项已修,带回归与变异检验:后退显示本页全部动画已完成(含只有同时、之后开始的页);本页图层到达才开始入页切换与动画计时,读取期间的单击与自动换片同此时基(worker 被占 400 毫秒的测试);切换结束即释放离开页的图层,只留当前与下一页;离开页的画法移入 `compose::left`,Metal 像素测试画出完成退出的对象在覆盖切换中不再出现(改回全部显示即失败);循环与失败由 `Player::tick` 判定(编辑区与放映共用),平台主线程测试覆盖真实播放器的循环边界与损坏文件。放映里异步失败交给提示这一步(`media_tick` 的错误分支)只能在主线程真实播放器上走到,应用单测在工作线程,未单独测到。R3 FAIL 两项已修,带回归与变异检验,未复审:人按的暂停与播到末尾分开记(`Player` 记手动暂停,循环只在真正播完后重播;定位改为精确,平台主线程测试在末尾 50 毫秒内暂停并持续 tick 仍停着);播放器之后报出的失败经 `Show::media_seen` 写出原因、停下并继续放映(应用测试,改为不提示即失败)。未验证:两块屏幕与演讲者视图(本机只有一块屏);真窗口里关闭后回到编辑窗口(修正延迟关窗后,本机当时有人在用,未复看);真窗口里的切换与动画放映、激光点、媒体播放;单屏开激光笔时系统光标仍显示(GPUI 无隐藏光标样式);Windows 与 Linux。 +- [~] C10.6 输出、演示准备检查(SHOW-13、14,QA-12)。已验证(macOS,带补丁 0003–0038 的 LOK):讲义 PDF 由内核按打印的讲义版式排出(每页 1、2、3、4、6、9 张,3 张带笔记线;只排范围内未隐藏的幻灯片;其他张数导出失败;内核测试按 pdftotext 页与文字核对,看过版面);导出(Cmd+E)PDF、图片、讲义,范围全部、当前、从–到;打印(Cmd+P)幻灯片或讲义后交系统打印面板;讲义与图片都在另存的副本上做,原文档的撤销步数不变(应用测试);图片每张未隐藏的幻灯片一个 PNG(宽 1920,浅色方案),任一目标已存在即不写(应用测试);PPTX/ODP 走保存与另存为(外壳已有);PPTX 存后重开,备注、切换、动画、媒体仍在并可改(内核测试)。演示检查(放映菜单“检查演示…”):`.uno:SlideCheck` 给每张幻灯片与母版的所用字体、链接文件与是否缺失、内核不能显示的对象,结合内核报的缺失字体与 `.uno:SlideShow` 的媒体,列出缺失字体、缺失媒体、外部链接、无法显示的对象及所在幻灯片或母版,点一行转到该页(内核与应用测试,共用夹具 `tests/fixtures/show/trouble.fodp`)。真窗口看过导出对话框。R1 FAIL 六项已修,带回归与变异检验:系统打印面板改在 GPUI 放开应用之后打开(`ely_office::print_later`,ElyDoc、ElyTable 同用;模态循环里点菜单原会因借用冲突崩溃);图片先以排他方式占下全部目标文件再画,任一已存在(含失效的符号链接)即不写并删去本次占下的,失败同样删去;输出的张数、隐藏与当前页取自标准幻灯片(`.uno:SlideShow`),母版视图下没有“当前”;读不出的图片按载入后的实际状态判定(链接先载入,解码失败的占位也算),OLE 链接的源文件缺失即标缺失(删去源文件的回归);检查与输出对话框的读取回来时,只有仍是最近一次请求且没有别的对话框打开才显示。打印面板的修复未在真窗口复验(本机当时有人在用)。R2 FAIL 三项已修,带回归与变异检验:PNG 编码后显式 flush 并报错(最后一次写入失败原被缓冲区析构吞掉,留下截断的文件);对话框或当前标签有任何变化,此前发起的检查与输出读取一律作废(另一个对话框打开后又取消,旧检查也不再弹出);图片的张数、隐藏与范围取自实际写出的副本(对话框打开后改了显示也一致)。审查者已在真窗口复验打印面板(幻灯片与讲义,面板里操作菜单、取消)不再崩溃。R3 FAIL 两项已修,未复审:范围的起止框键入即记下(原只在提交时记,失焦或直接按导出会丢掉键入的值;审查者以同一改法在真窗口验证,本项无自动测试,组件键入在应用单测里不可驱动);对话框打开后再显示隐藏页、导出图片得到三张可读 PNG 的回归(改回用对话框时的隐藏标志即失败)。未验证:真窗口里打印面板与讲义打印;Windows 与 Linux 的打印(`ely_platform::print` 只实现 macOS)。 + +## C11 ElyPDF(§11) + +- [~] C11.1 PDF 工作进程与 MuPDF 绑定,C 垫片捕获异常;打开、渲染、阅读模式、搜索、书签(PDF-01)。已验证(macOS,MuPDF 1.28.5):worker 测试(渲染与深色、旋转、字符顺序与外框、搜索不分大小写、嵌套书签、坏文件报错后 worker 照常、关闭后文档不在、1000 页到第一块不到 3 秒);应用测试(打开带页数与书签、查找跨页逐个跳转与 Esc 关闭、跨页选择复制、切换单页仍在原页);布局、旋转换算、缩放档、选择与书签的纯函数测试;真窗口:打开 30 页文件、深色页面、放大、查找“2 of 30”并标出、双页。未验证:加密文件的密码流程(测试 PDF 待 C11.7 能写加密文件);200 MB 的真实大文件;Windows 与 Linux。 — R1 FAIL 十三项已修:测量层位置未绑视口(拖选无效);首次拖选丢起点;跳转后当前页回退;旧查找结果混入新查找;后台查找改动别的标签;画不出的块反复请求;单页命中超 512 被截;跨行短语算两处;第二个加密文件挤掉第一个;长页屏外块不丢;空书签仍占左栏;空白窗口即起 worker;截断的帧头当作流结束;另修垫片三处长注释并让注释检查覆盖项目 C 源码;各加回归,去掉修复时回归失败(左栏只测判定,渲染接线未单测) — R2 FAIL 四项已修:密码框里取消时下一个密码框建不出(窗口仍在事件中;现延后到事件结束,建不成即关闭该文档);双页时当前页被改回左页;横向屏外的块照请求照留;零页文件开出标签、单页视图越界崩溃(现报错并关闭,布局接受空页集);各加回归,去掉修复时回归失败 — R3 FAIL 五项已修,三轮用尽:提交密码时对话框的关闭回调放弃了文档(现关闭后对话框仍是这一份才放弃);连续密码框共用一个标识,第二个收不到 Esc(现按输入框实体区分);双页当前页不看横向(现只在两向可见的页里定);放大双页跳到右页不横移(现目标页左缘出视口即横移);视口右缘恰在图块边界时多取一列(现按半开区间);各加回归(真键入回车、两次 Esc、横滚、跳转、边界),去掉修复时回归失败;真窗口加密文件流程仍待 C11.7 +- [~] C11.2 页面组织(PDF-02)。已验证(macOS,MuPDF 1.28.5 加补丁 0001):外壳 `ely-office` 按引擎泛化(LOK 与 MuPDF),ElyDoc、ElyTable、ElyShow 全量照过(ElyDoc 加锁测试改为关外壳的密码框);worker 测试(删除后书签、链接、字段、页码标签,移动后书签跟随页,旋转、按所见边距裁剪与去裁剪,空白页与从文件插入,提取不改文档也不成重做,保存后撤销重做,坏编辑回滚);应用测试(删除后标签为未保存、撤销回到已保存,保存写入移动、保存后撤销为未保存、重做为已保存,编辑一次一条按序,编辑前的读取丢弃,关闭询问,提取与拆分写新文件、不覆盖打开的文件、目标已存在即一个不写,裁剪按毫米,点选、连选、增减);逐项变异验证;真窗口:缩略图栏、深色页面。组件库 2ce3368(`ContextMenu` 可定宿主大小)。未验证:拖动移动只经单测的同一入口(`move_to`),未在真窗口拖;修复后打开的文件不能保存(快照不支持,待 C11.8);200 MB 文件的保存内存峰值;Windows 与 Linux — R1 FAIL 八项已修:提取在原文档的撤销历史上做临时删除,冲掉重做(现在快照副本上做);结构编辑后区间点选的起点仍指旧页,插入空白页越界崩溃(现清掉起点);编辑失败不通知,等着的保存挂住(现失败也通知);窗口关闭在界面线程同步读历史、持锁关闭文档(现全在后台,关完再移除窗口;ElyDoc、ElyTable、ElyShow 同);从文件插入丢掉链接、批注与字段(现一并嫁接并改指新页,重名字段加序号);省略 /P 的字段控件不随页删除(现按被删页的 /Annots);裁剪不看视图旋转;裁剪不看 /UserUnit;各加回归,去掉修复时回归失败 — R2 FAIL 三项已修:重名字段的候选名用 256 字节定长缓冲,长名截断后永远同名,worker 卡死(现按需分配);插入的字段丢了源表单的默认外观与字体资源,合并后外观改变(现带过来,冲突的字体改名并改写引用;回归按红色像素比对渲染);一处注释超长;各加回归,去掉修复时回归失败 — R3 FAIL 两项已修,三轮用尽:改写字体引用不解 PDF 名称的 #xx 转义,转义写法的字段仍绑到目标的同名字体(现解码查表、转义写出);源表单的默认对齐 /Q 丢失(现与 /DA 一起给到字段树根);各加回归(保存后的字节),去掉修复时回归失败 +- [~] C11.3 文字对象编辑、图像与对象(PDF-03、04,§11.3)。已验证(macOS,MuPDF 1.28.5 加补丁 0001):worker 测试(块的文字、字号、可否编辑,表单里的字不可改;改字后旧字形不在、撤销复原;字体缺字时什么都不改、换中文字体写入;预览画出改动而文档不变;新文字落在点击处、颜色照设;图片与表单的移动、缩放、按比例替换、删除、添加与撤销;自带字体写入后再改;预览或撤销后再加同一字体仍正确);应用测试(点选块用自带字体改字不出预览;换字体先出预览、缺字提示;拖动图片与拉角缩放;新文字与删除对象;Esc 退出工具);去掉修复时回归失败(资源缓存);真窗口:编辑工具的轮廓、点选出面板、改字应用。用户定:字体用 MuPDF 内置,对象为图片与表单 XObject。未验证:页面内联图片(BI…EI)不可选;竖排与旋转的字不可改;Windows 与 Linux — R1 FAIL 七项已修:预览与缺字拒绝在开步后才查,冲掉重做(现检查都在开步前、预览在快照副本上);字号变大未出预览(现字号或范围变了即预览);同一基线的几段被当成两行,行距为零(现按不同基线);选文件期间换了标签,替换与添加图片落到别的文档(现绑定发起视图与版本);编辑落地时进行中的图层读取被丢弃,读取标记永远为真(现回来总清标记);文字框里按 Esc 不退出(上下文移到窗口根);注释超长,注释检查补上 .h 与多行 C 注释;各加回归,去掉修复时回归失败 — R2 FAIL 两项已修:预览确认不核对文档版本,删页后改写到别的页(现预览带版本,编辑落地即关,确认与执行前核对);选文件期间有排队的编辑,序号作废仍执行(现核对版本且须无排队编辑);各加回归(同文两页删首页后旧预览不写、排队时不执行),去掉修复时回归失败 — R3 FAIL 一项已修,三轮用尽:删除对象不核对版本与排队,连按两次删除删掉未选中的对象(现删除与拖动都须视图未变且无排队编辑);各加回归(连按两次删除只删一个、排队时拖动不入队),去掉修复时回归失败 +- [~] C11.4 批注、表单(PDF-05、06)。已验证(macOS,MuPDF 1.28.5 加补丁 0001):worker 测试(九种批注一步添加、读回种类颜色内容作者印章、高亮按颜色画出、撤销重做、存后另一 worker 读到;坏的批注连同好的都不加、重做不丢;按对象号改色改内容与删除、撤销回同一对象;新建文本、复选、两个同组单选、选择框,填写后保存重开值与选中保留、文本值画出;重名、点号、非组的单选名、空选项拒绝,改名撞名拒绝,组里最后一个按钮删去即删组;选择框换选项保留或清空原值;填写在撤销重做之间照样画出);应用测试(拖选高亮一步、作者为账户名、撤销;矩形与手绘按拖动落位、单击不画椭圆;便签与印章先问后加、单击选中改色删除;拖出复选与选择框后单击勾选与取消、右栏填选择框与原有文本字段;第二个单选默认并入本页组、组内只一个选中;排队编辑时单击复选框不入队;左栏批注读完全部页、点行转页;表单工具改名改选项、无改动不成一步);变异验证(排队守卫、开步前检查、删组);真窗口:高亮画出、左栏批注列表、右栏颜色与作者、表单菜单画复选框、阅读时单击勾选。组件库 3d08744(`DescriptionList` 的值占满所在列)。未验证:已有文件里的签名字段与按钮只读不改;多选列表框只当单选填;Windows 与 Linux — R1 FAIL 三项已修:子字段改名按父路径下的全名查重(原从根查,与兄弟字段同名),右栏只改名字末段;文字未读到时拖出的高亮等文字到后再加(原直接丢弃),换工具、新的按下或编辑即作废,读取失败即报错;新字段默认名按全文已有字段取(原只看本页),右栏等全部页读完再给;另修:右栏在拖动或绘制期间不打开(原按下即开,视图变窄,页面在手下移动,C11.3 的拖图测试约三成失败);单击按窗口像素判定(原按页面点,手下页面平移时误判为拖选);各加回归,去掉修复时回归失败 — R2 FAIL 两项已修:默认名也避开字段的父名(原只比完整名,有 `Text1.Child` 时仍给 `Text1`);已开的右栏在手势中保持开着(原手势中一律收起,页面在手下移动);另修根因:手势开始时冻结该页在窗口中的位置与比例,拖动与绘制都按它换算(右栏动画中开始的拖动也不再错位);各加回归,去掉修复时回归失败 — R3 FAIL 一项已修,三轮用尽:没有页面控件的字段(如只在字段树里的父字段)仍占名,默认名照样撞上(现 worker 读整棵 `/AcroForm/Fields` 的全部名称,右栏等它读完再给);加回归(worker 列出无控件的父与子、应用默认名避开它并能添加),去掉修复时回归失败 +- [x] C11.5 文档结构、OCR(PDF-07、08)。已验证(macOS,MuPDF 1.28.5 加补丁 0001、0002,tessdata_fast):worker 测试(扫描页按行识别出英文两行、位置为页面点;写入隐藏层后可查、可搜、画面逐像素不变,一步撤销重做,存后保留;重新识别替换旧层、不替换则并存;BMP 外字符、空行、越界页拒绝且不改;简体与繁体中文识别;坏语言报 Tesseract 错误后 worker 照常;书签加在末尾与子项之后、改名改目标、按标题核对、删除连子项、撤销、存后保留;链接列出内部目标、加网址、改到页、地址已变即拒、javascript 与 file 拒绝、删除;属性读写、生成程序不可改、存后保留;附件按名列出与读出字节、重名拒绝、移除、存后保留);应用测试(单击链接跳页;链接工具拖出后坏目标不写、网址写入、单击已有链接改到页与删除;书签以页名为默认标题添加、选中后改名与删除;属性只写改过的,无改动不成一步;附件栏列出、另存为写出原字节、移除;OCR 一页读完后一步写入可查,再识别时跳过已有文字的页;取消即不写);变异验证(OCR 跳过策略、属性只写改过的、补丁 0002 两处);真窗口:识别文字对话框、扫描页识别后查找命中。未验证:XMP 元数据不随属性改;Windows 与 Linux 上的 OCR — R1 FAIL 六项已修:OCR 层的 3 Tr 漏到之后写的字,新字不显示(现层内 q … Q);范围字段只在提交时更新,键入后直接点识别仍用旧值(现随键入更新,真窗口键入 2 后只读第 2 页);取消的识别回来的页会进入新一次识别(现每次识别带编号);多语言里有一种载入失败仍报成功(补丁 0003,载入数少于请求即报错);批注里的附件不在附件栏(现一并列出、读取、移除,以键区分);缺 /Size 的附件显示 0 B(现按实际字节);各加回归,去掉修复时回归失败 — R2 FAIL 一项已修:读附件时取走了文档自己持有的流缓冲区,再读为空、保存后附件内容丢失(现复制字节);加回归(连读两次、读取不改历史、存后重开字节完整),去掉修复时回归失败 — R3 PASS +- [~] C11.6 内容删除与文档清理(PDF-09、10,§11.4,QA-14)。已验证(macOS,MuPDF 1.28.5 加补丁 0001 至 0003):worker 测试(删去“Hello”与其上的隐藏文字后,输出文本不含二者、其余文字保留、文件字节里无该词、只有一个 startxref,打开着的文档历史不变;图片被区域盖住的一半全黑、另一半保留颜色;区域内的便签删去、远处的保留;空区域与越界页不写文件;清理前计数,全选清理后各类为零、属性无标题、无附件、无隐藏文字、可见文字保留、原文档计数不变;只清批注时属性保留;未勾选报错);应用测试(区域标记与单击取消;文字标记写出新文件后无该词、原文档无编辑;编辑后标记清空、无标记时不开对话框;清理先计数再写出、标题已去);变异验证(删去区域内批注、编辑后清空标记);核对本身抓出过两处:判字相交须与删除同一缩框规则(原把邻字算作残留),并证实隐藏文字随删除一并去掉;真窗口:区域标记的深色框与删除对话框。未验证:Windows 与 Linux — R1 FAIL 八项已修:部分盖住的路径留在文件里(现碰到即整条删,核对加设备重放,区域内除黑框外有画即失败;图片单独画出只许白或黑);字段删控件后空父字段带值留下(现一并去掉);关着的图层与零透明度文字未清(现过滤器设 View 用途,删图层内一切与看不见的字);页面上的 XMP 未清(现清全部对象的 /Metadata 与 /PieceInfo);无 /Names 时清理报错(现判空);隐藏批注两头不计(现计入隐藏内容);扫描回来不核对标签与版本(现用 ask_in 并核对版本,对话框写文件名);路径选择期间改了标记仍写(现确认时绑定标记与版本,变了不写);各加回归,去掉修复时回归失败 — R2 FAIL 六项已修:删字后标记内容的 /ActualText 留下(现有区域的页去掉标记内容与结构元素的替代文字);重叠区域后者删掉前者的 Redact 批注(现先删后建);/AF 关联文件、嵌套直接字典里的元数据、链接与父字段上的脚本、/Next 链里的脚本漏清(现一遍遍历全部对象与嵌套值,脚本从链中摘除、后续接回);各加回归,逐一变异验证失败 — R3 FAIL(三轮用完)四项已修:子项 MCR 指定页的结构元素替代文字留下(现按子项 Pg 判定并连同上级);页面 /Thumb 引用原图(现删去,核对查缩略图);隐藏文字的标记内容 /ActualText 留下(现有隐藏文字被删的页去掉替代文字);隐藏链接与隐藏字段漏清(现按 /Annots 删链接,控件连同字段值删);各加回归,逐一变异验证失败;未经第四轮审查 +- [~] C11.7 密码、签名(PDF-11、12,QA-15)。已验证(macOS,MuPDF 1.28.5 加补丁 0001 至 0004,OpenSSL 3.6 静态链接):worker 测试(AES-256 保护副本要密码、只许所给权限,越权请求以权限名拒绝;错的所有者密码不改访问,AES-128 样本上错一次后文字照读;对的解除限制;只凭用户密码不能改保护,所有者可去掉保护;所有者密码必填且不同于打开密码;无打开密码的文件照守限制;新签名摘要未改、签名者与原因地点读回、证书即所给证书;证书密码错不写文件;改一字节即为已改;二次签名后首个仍未改且标为其后有更新;空签名字段原位签;不许改页面时不能加新签名字段;普通保存的已签文件不带签名、字段留空;键入签名以嵌入的 Great Vibes 写入、缺字报出该字);系统信任测试(自签名证书不受信任、以其为锚即受信任);应用测试(限制在所有者密码前拦住旋转、错密码再问、对后可旋转;禁止复制时剪贴板不变;保护对话框要求不同的所有者密码并写出加锁副本;已签文件为签名风险、签名对话框读出未改与不受信任并画出;签名写出副本、原文档无编辑;拖框后有编辑即不签;手写、键入、图片三种可视签名各落为墨迹、文字、图片且按比例;对话框把手写交给放置工具);逐项变异验证(所有者错后重认证、权限核对、复制拦截、整份写出去签名、签名前核对版本);真窗口:已签文件的提示条与状态、签名对话框(曾抓出列表行含换行即崩溃,改为对话框分行)、可视签名对话框(组件库 a6b1bb0 让签名板文案进中英目录)。未验证:Windows 与 Linux 的证书信任;时间戳与吊销;在真窗口拖框签名与放置可视签名(同入口经单测) — R1 FAIL 五项已修:只按页面控件列签名,漏了不在页面上的签名字段(现按表单字段树列,页可无;无 /P 时从页面 /Annots 找);另存为已签文件后打开的文档仍带签名(现保存时同一步去掉内存里的签名值,不算编辑,列表重读);签名完成清掉当前标签的位置(现只清发起视图的同一位置);所有者密码回复关掉后开的对话框(现只关同一输入框的对话框);补丁注释超长且检查漏看补丁里的块注释(现缩短,检查覆盖);各加回归,逐项变异验证失败 — R2 FAIL 两项已修,根因同一:上轮在保存时去掉打开文档的签名,写入失败即坏了原签名、撤销重做又带回签名;现签名一律从文件读(`SIGNATURES` 带路径,另开文件核对),列表按标签当前路径读,内存去签名删去;加回归:另存失败后仍为已签与原签名、另存后撤销重做列表仍无签名,变异(改回读编辑中的文档)即失败 — R3 FAIL(三轮用完)三项已修:另存为后签名仍用内存里带旧签名的文档(现已签文件另存成功即按新文件重开标签;去签名时清掉该字段自身的只读位,副本里的空字段可再签);签名列表按路径缓存,文件被外部改动后仍显示旧结果(现每次打开对话框重读);保存打不开输出文件后 MuPDF 的保存中标志留着、此后编辑不进撤销(补丁 0005);各加回归,逐项变异验证失败;未经第四轮审查 +- [~] C11.8 输出、文件恢复(PDF-13、14,QA-13)。已验证(macOS,MuPDF 1.28.5 加补丁 0001 至 0005):worker 测试(页面按 144 dpi 导出为两倍点数的 PNG、离谱分辨率报错;文字按页以换页符分开;优化副本小于原件十分之一、432 dpi 的图片降到 150 dpi(宽 205 至 212)、降到高于阈值报错;无打印许可不能出打印副本、无复制许可不能导出文字;损坏文件打开得修复报告、两页照读、编辑后能保存、原件字节不变,完好文件无报告;保存打不开输出后历史仍完整);PDFKit 读回改字、填写的字段与页序;应用测试(导出两页图片与文字;损坏文件为修复风险、有报告、报告对话框画出,另存到原件被拒且原件不变,另存新文件后按新文件重开、无风险);变异验证(不换修复副本、修复风险不报、另存后不重开);真窗口:损坏文件的提示条与状态、导出对话框。未验证:Windows 与 Linux 打印;真窗口实际打印;图片编辑经独立阅读器核对 — R1 FAIL 三项已修:另存后立即重开丢掉快照之后的编辑(现标签记下待重开,内存与文件一致时才重开,期间的编辑留着、下一次保存后重开);尾部完好而对象偏移坏时 MuPDF 读到才修复,漏报且保存失败(现打开与解锁后逐个读对象,修复都在打开时发生);降采样数值只在回车或失焦时生效,键入后直接导出用旧值(现随键入生效);前两项各加回归,变异验证失败;第三项真窗口复测:键入 72 后直接点导出,输出图片宽 100(即 72 dpi) — R2 FAIL 两项已修:结构完好而内容流解压失败时当作正常文件、导出空文字、保存覆盖原件(现打开时解码图片以外的数据流,有警告即为损坏、改开副本;导出遇警告报错);打印副本照原加密、打印面板打不开(现打印副本不加密,打印后删去);各加回归,逐项变异验证失败;真窗口:带打开密码的文件打印面板打开,取消后副本已删 — R3 FAIL(三轮用完)两项已修:扫描跳过图片,坏图片照样可原位覆盖(用户定:图片在画出时检查,没画过的在原位保存前检查;画出遇警告即转修复风险,保存前检查有问题即转另存为);导出图片在等编辑与选路径前取页码,删页后导出另一页(现绑定发起时的视图版本,变了即拒绝);各加回归(画出的坏图、没画过的坏图、排队删页),变异验证失败;未经第四轮审查 + +## C13 ElyTree(§13) + +- [x] C13.1 Git 运行器(参数数组、porcelain v2)、仓库管理、工作区状态(TREE-01、02) +- [~] C13.2 差异、暂存(TREE-03、04)。已验证(macOS,随附 Git 2.56.0):Git 自己的差异解析(无换行结尾、二进制、非 UTF-8、空白上下文行);按块与按行暂存和取消暂存,索引字节逐一核对(两块只暂存一块;只选一行加入或删去;末行无换行时的追加与恢复;用户 `apply.whitespace=fix` 不改所选字节);显示后文件变了即拒绝、索引不动;空旧文件不折叠;文件与链接互换两段都显示、整份暂存、行与行号逐一核对;悬空链接与指向非 UTF-8 文件的链接按链接路径读;图片与二进制按大小或图片显示;忽略空白时空白改动不显示;窗口内选行(Shift 取范围)、暂存后所选清空、换文件后旧读取迟到即弃;真窗口经 AX:选两行暂存,索引恰为所选,已暂存区显示实际索引内容,并排视图,各行、块按钮与分段在 AX 树中。未验证:Windows 与 Linux — R1 FAIL 八项已修:换文件后旧读取覆盖新文件(读取序号窗口内唯一);未选删去行留作上下文时照搬无换行标记,追加行被并入(按两侧实际行尾生成);`apply.whitespace` 改写所选字节(`--whitespace=nowarn`);差异外的非 UTF-8 字节被替换成 U+FFFD 仍进文本视图(两侧整份核对);`diff.suppressBlankEmpty` 使差异读不出(命令里覆盖);空旧文件被算成一行(零行);组件 viewer.rs 超 500 行(拆出 rows.rs);三条组件注释超十词。R2 FAIL 三项已修:取消暂存时所选删去行的无换行标记照搬,与后行相接(统一规则:无换行的行在哪一侧后面还有行就带上换行);文件变成符号链接时同一路径两段输出读不出(解析多段);rows.rs 一条注释超十词。R3 FAIL 两项已修(不再送审):类型变化两段共用折叠游标,已删除内容又显示为未改动(多段时不折叠);工作树侧跟随符号链接读目标(按 Git 读链接自身的路径字节)。 +- [~] C13.3 提交、历史、Blame(TREE-05、06)。已验证(macOS,随附 Git 2.56.0):历史读分支、标签、合并,分页,按信息、作者、提交号、改动文字搜索;文件历史跨重命名;提交详情(根提交、重命名、新增)与其文件差异;Blame 含未提交行;提交与修改最近提交,`#` 行在用户 `commit.cleanup=strip` 下照留,钩子失败报出原话;身份与签名取自 Git 设置;窗口:提交框提交后历史自动重读、选提交与文件,提交框按仓库各存一份,连按两次修改最近提交读回作废,提交期间键入的保留,Blame 只取最新一次读取,历史读取期间的外部提交读完后补上,引用读不出时报错、修好后恢复,外部改身份后重读、身份对话框字段有无障碍名;真窗口经 AX:历史页、提交详情各字段、差异行与 Blame 行的文字、文件历史。未验证:Windows 与 Linux;实际 GPG/SSH 签名 — R1 FAIL 八项已修:提交框与修改最近提交各仓库共用(按仓库存);修改最近提交的读取迟到又打开(作废序号);提交完成无条件清空(只清仍是所提交内容的);文件历史与 Blame 回调只比路径(每次打开的序号);`commit.cleanup=strip` 删 `#` 行(`--cleanup=whitespace`);历史里的文件无文件历史与 Blame 入口(文件标题栏);提交详情不进 AX(`DescriptionList::text` 等);组件注释超十词。R2 FAIL 三项已修:勾选在读回前不显示,第二次点击仍是勾选(立即勾选、读回前不能提交);只读差异行不进 AX;Blame 行不进 AX。R3 FAIL 五项已修(不再送审):历史读取期间的外部更新漏掉(记下、读完再比);引用读不出只写日志(显示原话,读通即清);外部改身份与签名不重读(状态重读与窗口回前台时重读,取最新);身份对话框输入框无无障碍名(以标签命名);组件注释超十词。 +- [~] C13.4 分支、标签、合并、变基、冲突(TREE-07、08、09)。已验证(macOS,随附 Git 2.56.0):分支与标签的建、改名、删(每次先问)、切换(远程分支按完整引用跟踪,不被同名标签劫走);合并、拣选冲突停下不弹失败框,提示条写明两侧;按块取一侧、保存、标为已解决、继续得到合并提交;标记宽度照 `conflict-marker-size`(5、10,以及 0、-1、abc、10abc 按 Git 当作 7),正文里形似分隔的行照留,只按 LF 断行;diff3 空基底不被补填;含 NUL 的冲突整份解决;钩子拒绝(继续时的 pre-commit、取一侧时的 post-checkout,输出含 CONFLICT 字样也算)显示 Git 原话;真窗口:冲突提示条、三侧、按块取另一侧。未验证:Windows 与 Linux — R1 FAIL 七项已修:符号链接与二进制冲突按文本处理(整份解决);保存不经写队列、顺着符号链接写(入队、拒绝链接);钩子失败被当成冲突停下(按未合并条目判定);冲突分块另算一套合并(照 Git 的标记);同名分支与标签混淆(完整引用名);行内动作未接;窄时删除按钮被挤出。R2 FAIL 六项已修:标记宽度写死 7、`=======` 前缀行当分隔(按属性宽度、整行判定);仍有未合并条目时钩子失败被当成停下(按命令与 Git 的 CONFLICT 行判定);NUL 的 UTF-8 文件当文本(按 Git 视为二进制);zdiff3 基底覆盖 diff3 空基底(只填缺的);远程分支切换被同名标签劫走(`refs/remotes/`);组件注释超十词。R3 FAIL 三项已修(不再送审):`conflict-marker-size` 为 0、负数或非数时读错(照 Git 的 `strtol_i`,不合即 7);正文里的独立 CR 被当作行界(组件 a284c71 按 LF 断行);任意含 CONFLICT 子串的钩子输出被当成停下(须是合并类命令、输出有以 `CONFLICT (` 起头的行、且索引仍有未合并条目;Git 的报错带上 stdout 与 stderr 两者,拣选的 CONFLICT 行在 stdout)。 +- [x] C13.5 Stash、远程、worktree、submodule、LFS(TREE-10、11、12)。已验证(macOS,随附 Git 2.56.0 与 git-lfs 3.8.0):储藏的列表、说明与分支、未跟踪文件分开显示与其差异;弹出遇冲突保留储藏;储藏列表变了即拒绝;远程的地址与推送地址;强制推送列出将覆盖的提交,远端在计划之后变了即被租约拒绝,取回后再推成功;没有上游或游离不给强制推送;工作树的分支与失效、清理;子模块未检出与在记录的提交上;LFS 对象在本机与只有指针;窗口:储藏对话框、储藏详情、删除先问、弹出;取回带进度落地;停止取回不报失败;无上游推送先问远程并设上游;强制推送对话框;设置与取消上游;真窗口:仓库页各列表、储藏与工作树详情。未验证:Windows 与 Linux;真实网络凭据;LFS 对象下载 — R1 FAIL 六项已修:强制推送确认后本地分支再动会推出未预览的内容(推送所示的源提交);对话框提交时写进当前标签而非打开时的仓库(各表单带仓库号,含 C13.4 的分支、标签、改名与身份对话框);按冒号前缀合并输出删去钩子的报错行(只合并回车改写的行);推送的远程名前无 `--`(加上);空说明的储藏使整个列表读失败(按三字段解析);行的焦点框不画、行内动作只在悬停时显示(组件 bc6f1c2 与 3741d5d:透明边框、动作一律显示) +- [~] C13.6 恢复与清理、外部工具、操作规则、外部 Git 响应(TREE-13、14,§13.3,QA-19、20)。已验证(macOS,随附 Git 2.56.0):引用日志的位置、原因与先后;把分支移回保留工作区改动,HEAD 在预览后动过即拒绝;清理预览含制表符、引号、中文名与文件夹,只删所列,预览后新建的与被忽略的留下;锁文件在时对话框重试、锁文件不被删除、去掉后重试成功;凭据被拒显示 Git 原话;变基中断、中止后回到原处;窗口:清理对话框、引用日志选中与移回;编辑器设置的读写与损坏不覆盖;真窗口:工具行、清理预览、文件标题栏按钮与启动 ElyMark。未验证:Windows 与 Linux 的终端;ElyMark 打开文件的内容(C14) — R1 FAIL 八项已修:清理把预览的文件夹整个交给 Git,预览后其中新建的文件也被删(展开为文件,只删所列,空了的文件夹才删);`reset --keep` 丢掉独立暂存的内容(有暂存即拒绝,执行前再查);移回只核对提交,切到同一提交的另一分支也会被移(另核对完整分支引用与游离);引用日志时间取了提交时间(取该次移动的时间);同一提交的多条日志一起选中、详情取错条(按条目选);编辑器设置字段类型不对被当成未选并覆盖(报错不覆盖);`open -a` 的失败被丢掉(等它结束并显示);配置文件锁不认作锁(加上其原话)。R2 FAIL 两项已修:所列文件在确认前被换成同名文件夹时 `git clean` 递归删去其中新文件(先查类型、变了即整体拒绝,由 Ely 逐个删文件);刷新后按旧位置取引用日志条目,选中与详情漂到别的事件(按事件本身认定)。R3 FAIL 三项已修(不再送审):直接删文件时不管确认前已被跟踪的文件(改回 `git clean -f -- 所列文件`,Git 留下被跟踪的);父文件夹被换成指向仓库外的链接时删到外面(逐段核对无链接、类型不变,变了即拒绝);同一秒同原因的两条引用日志无从区分(加自最早起的序号) + +## C14 ElyMark(§14) + +- [~] C14.0 本地 Web 内容宿主 `ely-web-content`:CEF 离屏接入 GPUI,输入、焦点、缩放、主题 token(§17.3;ElyMark 先用,ElyMail 后用)。已验证(macOS arm64,CEF 154.0.32):开发构建与 `.app` 包(五个辅助应用)里页面画出、随应用深色;点选、中英文键入、回车、退格、方向、拷贝粘贴;输入法组字与提交、焦点、主题变量、缩放 1.25、`elyQuery` 往返、页面跳不出所在文件夹(`tests/scripts/webview.sh`);连续五次启动都画出。未验证:Windows 与 Linux;辅助技术读页面文字(缺口) — R1 FAIL 七项已修:资源请求不过门(每个请求都判定,外面的脚本与 HTTP 被取消);符号链接可出门(按真实路径);浏览器与表面互相持有,关闭后不释放(关闭时断开,20 个都释放);载入失败当作载入完成且显示 Chromium 的错误页(报失败,显示组件错误视图与重新载入);文件名里的 `#` 当作 URL 语法(逐字节转义);渲染进程退出与 CEF 起不来只写日志(视图内显示故障并可重新载入);测试不量像素(截窗口量标记色,去掉绘制即失败)。另修:在 GPUI 更新里同步调 CEF 偶发中止(全部经主线程队列在更新之外执行)。R2 FAIL 两项已修:WebSocket 不经资源回调可达本机(每个 CEF 进程把一切网络送往不通的代理,含回环);崩溃测试不看故障视图(截窗口须不见旧画面,经辅助功能按下 Reload 才恢复)。R3 FAIL 两项已修(不再送审):WebRTC 经 UDP 绕过代理(只许经代理,且页面里删去 WebRTC 构造;本机 UDP 收不到 STUN,去掉时测试失败);恢复只看载入事件(重新载入后截窗口须见页面标记,保留故障时测试失败) +- [~] C14.1 内容包:Milkdown、CodeMirror 6、Mermaid、数学公式,本地构建、随包、无 CDN。已验证(macOS):`tests/scripts/mark-content.sh` 构建后在真窗口里三种模式渲染标题、任务、表格、脚注、公式、代码块、Mermaid 与两种错误(带原文与位置),每个代码块滚入视口后读出、要求可见(不透明、未被遮挡),图不小于 100×80,预览只读,切换深浅色后图的颜色随之变(含首次绘制途中切换,且首次落下的就是新颜色),绘制途中把代码块改成 Rust 后旧图不落入,连续两次显示只留后一次且只报后一次,截窗口时背景色占比须低于 0.97(隐藏正文时为 0.999),KaTeX 字体载入,无越界请求;`.app` 包内页面显示。未验证:Windows 与 Linux;树莓派载入时间 — R1 FAIL 四项已修:按钮图标的尺寸规则也压扁了 Mermaid 图(限于工具栏与语言列表);主题变化后已显示的图不变色(图带编号,令牌一变即经代码块的回调重画,只取每块最新的一次);连续两次显示会留下两个编辑器(每次显示一个容器与序号,过期的创建即销毁、不报告);测试不看是否真的可见(逐块可见性、图的尺寸、换主题、竞争与截窗口,各以变异核对会失败) R2 FAIL 五项已修:Milkdown 每次预览给的回调都是新的函数,按回调认定的记录对不上,改语言后旧图仍落入(预览先返回带编号的占位,结果只在占位仍在页面上时落下;改语言、改文字、卸载都会换掉占位);首次绘制途中换主题时记录被当作已离开而删去(占位在页上即保留,换主题时连同待画的一并重画);记录由全局持有、编辑器销毁不释放(每个编辑器拥有自己的记录,销毁即释放;占位离开页面的记录在下一次绘制或换主题时清掉;实测 20 次显示与切回源码后为 0);测试的退出钩子在探针已回收后返回 1(只停仍在运行的探针,清理总执行);块公式只查节点存在(滚入视口查可见,隐藏公式的变异会失败)。重载页面使两项竞争发生在 Mermaid 包载入与绘制途中,去掉各项修复的变异都会失败 R3 FAIL 两项已修(不再送审):主题重画复用同一记录,旧主题的首次结果仍会先落下约一秒(每次绘制带代次,重画即作废此前的结果);编辑器销毁后队列里排着的绘制仍逐个执行(每个绘制在调用 Mermaid 前与落下前都核对属主、占位与代次,失效即跳过;测试在三张约 3 秒的图排队时切回源码,之后的主线程长停顿须不超过一次,去掉核对时为两到三次) +- [x] C14.2 文件会话与原文保留:BOM、换行、front matter、未知语法(§14.3,QA-21)。已验证(macOS):`tests/scripts/mark-session.sh` 在真窗口打开 BOM、CRLF、front matter、Setext 标题、`1)` 列表、HTML 块、`~`删除线、硬换行、链接定义与 `[[...]]` 的文件,不改保存逐字节相同,首段键入一字保存只改该段(整篇序列化与去掉 BOM 的变异都失败),不能对应的文档以源码模式打开并可保存;`text.rs` 单测(BOM、换行、混用、非 UTF-8)。未验证:Windows 与 Linux;混用换行的文件编辑后统一为多数的换行(已知限制) — R1 FAIL 六项已修:改动段落时其中的 `[[...]]` 被转义、引用与列表内的局部改动重写整个容器(Milkdown 不能逐字写回的顶层块一律以原文块显示与编辑,序列化照文档自己的写法);块间隐藏的链接定义被移动与修剪(块间原文也成原文块,无隐藏原文);页面重载后新编辑被当作旧版本丢弃(每次载入一代,回报带代数,计数归零);无读权限的文件被当作加密文件问密码(权限错误照原话报出);注释超过十词 — R2 FAIL 两项已修:保存写出的文字与随后读的修订标记取自两次快照,导出与读标记之间的编辑被记为已保存(写出时记下所写文字的标记,读修订时先交它;单测在两者之间改文字);链接定义的测试用多行 grep 恒真(改为逐字节比较) +- [~] C14.3 编辑模式、Markdown 与扩展语法(MARK-01、02、03、04)。已验证(macOS):`tests/scripts/mark-modes.sh` 真窗口里文档旁图片画出,可视、源码、预览往返不改文件,预览不收键入,经预览往返后撤销仍有效,源码改动回到可视并保存,重命名拒绝已有名称、改到新名后继续保存,新建为空可视文档;`mark-content.sh`、`mark-session.sh` 照过;`rename_new` 单测。未验证:Windows 与 Linux(改名的平台实现未构建);“直接打开文件夹”随文件树在 C14.4 — R1 FAIL 七项已修:另一模式改过文字后回来,原编辑器连同撤销历史被销毁(旧编辑器留在新编辑器之后,撤销用尽即回到它,重做用尽回到较新的);改名完成时标签已关仍去登记(须仍开着且仍是原路径);Windows 路径的 file URL 无效(驱动器与 UNC 分别处理,围栏反向转换,单测);构建中再要的模式被丢(记下,构建完显示最后一个);新建的测试不看内容(读外壳写下的文件须为空);`handlers.rs` 超过 500 行(请求处理移入 `fence.rs`);`Renaming` 只有一个使用者(移回 ElyMark) — R2 FAIL 四项已修:跨模式撤销被当作新编辑、丢了重做(跳转只报新文字,不截时间线);按模式分开的历史使撤销次序不对(改为本文件一条时间线,以当前模式重建);改名间隙里打开新名字得到第二个可写标签(登记记下文件身份,原路径不在而身份相同即同一文件,改名后迁移登记;单测);构建完成的回报回来后旧请求覆盖新的(请求按序号,只认最新) R3 FAIL 三项已修(不再送审):编辑器内撤销后切换模式,重做丢失(离开时把全部重做后的文字记在时间线前方);原子保存换了文件身份,保存后改名的间隙仍得到第二个会话(外壳每次保存成功后以 `Sessions::track` 更新登记的路径与身份;单测在原子替换后改名);旧回报清掉了较新构建排队的模式(代次不符即退出,不碰排队请求;探针按住两个回报复现) +- [~] C14.4 导航与搜索:文件树、大纲、多标签、最近文件、文件夹搜索(MARK-05、10)。已验证(macOS):`tests/scripts/mark-places.sh` 真窗口打开文件夹与文件,树只列 Markdown、回车打开,大纲跳转后键入落在标题,文件夹搜索找到两文件的行、不搜 .txt、结果打开到该行,最近列表重启后保留并可打开文件夹;`folder.rs` 与 `ely_files::recent` 单测;其他三个 ElyMark 门照过。未验证:Windows 与 Linux;大纲项的无障碍(组件库待办) — R1 FAIL 六项已修:搜索定位只到块首(带上查询,落在块内该行那一处);可视与预览的行号未计 front matter(按 front matter 行数换算,落在其中即滚到它);以原文块显示的标题不进大纲(原文块记下标题级别,大纲取其首行文字,跳转落在标题文字上);本文件的搜索结果不随标签切换(切换即重搜,迟到的结果按次序作废);读不出的文件只记日志(以“某文件未能搜索”的行列在结果里,带原因);改名后最近列表仍是旧名(最近列表按真实路径,改名即迁移,旧路径不在即移出)。各项去掉时 `mark-places.sh` 或 `mark-modes.sh` 失败 R2 FAIL 三项已修:块内命中按源码计数,链接地址里的词打乱次序(此前行先经 Milkdown 解析,按编辑器所见的文字计数;原文块照源码);引用、列表里的标题成了原文块后不进大纲(原文块的源码以 remark 读出其中全部标题与位置,不再只记顶层标题的级别);改名后文件树与文件夹搜索仍是旧名(改名即重列文件夹,列表变了即重搜文件夹)。各项去掉时 `mark-places.sh` 失败 R3 FAIL 两项已修(不再送审):此前命中的计数与块内查找的重叠规则不一(都按不重叠,`ana` 在 `banana` 里只算一次);命中只在链接地址里时落到下一处可见的命中(该行所见文字没有查询词即落在该行开头)。另修:已打开的文件因路径写法不同(`/var` 与 `/private/var`)不被认出,跳转落空(文件夹以真实路径列出,标签按真实路径认);块内位置把软换行算进与不算进两边不一(编辑器一侧也算入换行)。去掉各项时 `mark-places.sh` 失败 +- [~] C14.5 编辑工具与附件(MARK-06、07)。已验证(macOS):`tests/scripts/mark-tools.sh` 真窗口可视全部替换、源码正则替换、Tab 与 Shift-Tab、列表续写、Cmd+Down 后插入表格与行列操作、拖放图片落到 assets,`ELY_CLIPBOARD_TEST=1` 时粘贴图片;`edit.rs`、`attach.rs`、`import_attachment` 单测;其他 ElyMark 门与 `webview.sh` 照过。未验证:Windows 与 Linux — R1 FAIL 五项已修:查找框整窗一个,替换用了该标签页面里旧的查询(下一处与替换带上框里的查询,换标签即重查);键入替换文字使命中后移(查询变化从命中起点找);源码里替代文字的方括号未转义(加转义);复制期间另存到别处,链接指向新文件夹里不存在的图片(完成时核对文件夹,变了即不写链接并说明);四处注释超过十词(缩短,`comments.py` 另查 Swift 与页面 JS)。去掉各项时 `mark-tools.sh` 失败 R2 FAIL 三项已修:空查询仍调 CodeMirror 的查找命令,打开它自带的面板(各命令先核对查询有效);正则校验未带编辑器所用的 `u` 标志,`a{`、`\a` 报成无结果(按 `gmu` 校验);`comments.py` 漏过 JS 的跨行块注释(一并检查,`comments-rules.sh` 核对)。`mark-content.sh` 的 find 步核对两种模式都无 CodeMirror 面板、四个错误;去掉守卫或 `u` 时失败 R3 FAIL 两项已修(不再送审):可视模式零宽正则在表情字符处只进一个 UTF-16 单元,落在代理对中间时同一命中反复收集,页面进程耗尽内存崩溃(零宽命中按码点前进,命中不再往前即停);`comments.py` 漏过代码之后开始的跨行块注释(任意位置未闭合的 `/*` 都算)。`mark-content.sh` 在两种模式下对 `😀 alpha` 找 `(?=.)` 得 7 处(只进一个单元时页面崩溃、测试失败),`comments-rules.sh` 核对行内起头的跨行注释 +- [~] C14.6 外部变更与会话恢复(MARK-08、12,§14.4,QA-22)。已验证(macOS):`tests/scripts/mark-outside.sh` 真窗口原子替换后重载并保持位置、键入后 1 秒自动保存;未保存时外部写入不被覆盖,三方比较、保留你的、合并;外部改名跟随、删除后不自动保存不重建、读不出时提示并可再查;退出后恢复标签、模式与行;崩溃后恢复未命名文档的未保存内容并标为未保存,显式不保存后不回来;`Sessions::renamed_in`、`alive` 单测;其他 ElyMark 门与 `webview.sh` 照过。未验证:Windows 与 Linux;网络目录;导出(C14.7) — R1 FAIL 六项已修:比较期间磁盘再改,合并与保留按旧的磁盘指纹写入、覆盖新改动(比较记下所读内容的指纹,选定前核对,不符即重读重比;重载与比较都以所读字节的指纹为准,`Session::reload_at`);有路径文件的崩溃恢复内容直接进了原文件、下一次编辑自动保存覆盖原件(恢复内容一律开成未命名副本,原件照磁盘打开);原件已不在时恢复内容随标签被丢(照样开成副本,并提示哪个文件没能打开);取消退出后会话清单一直不更新(外壳的“未保存”询问取消时调 `Host::close_cancelled`,清单恢复跟随;已关闭或放弃的标签的恢复点随即删去);只恢复行、不恢复行内位置(记下并恢复列);“保留你的”之后自动保存一直停着(选定后重新开始)。另:启动参数给了文件时只开它(及崩溃恢复的副本),不再连带上次的标签(否则加载先后决定哪个标签在前,键入落到别的文件)。去掉各项时 `mark-outside.sh` 失败 R2 FAIL 五项已修:“保留你的”仍走无条件覆盖(改为以所比较的指纹为基线 `Session::rebase` 后走普通保存,之后的外部改动照样冲突);一个标签连续消费多份恢复文本(每个标签只在首次显示时恢复一次);询问里点保存、随后取消保存面板(或保存失败)时清单仍冻结(外壳在这些出口也调 `close_cancelled`,冲突询问取消同);放弃的标签要等下一次定时才删恢复点与清单(外壳关闭标签时调 `Host::tab_closed`,即刻删去其恢复点、在途的恢复点写完即删,并更新清单);旧会话文件缺列即启动失败(缺列读作 0)。另:自动保存撞上外部改动时不弹外壳的冲突框,交给比较(`Host::conflicted`);组件库 f756043:脏标签与非当前标签的关闭按钮原为隐藏(`invisible`),辅助技术按不动、无法关闭,改为透明仍可按;`ax-press` 另找应用的各窗口并可限定角色。去掉各项时 `mark-outside.sh` 或单测失败 R3 FAIL 三项已修(不再送审):外壳冲突询问的“保留我的”仍以 `save_overwrite` 无条件覆盖,导出期间的新外部写入被盖掉(改以此刻磁盘的指纹为基线 `rebase` 后走普通保存,之后的变化照样冲突;文件已不在时才直接写;ely-files 单测核对其后的写入不被覆盖,ElyDoc 的保留我的测试照过);退出时冲突询问的“从磁盘重新载入”丢了挂起的关闭、清单冻结(先调 `close_cancelled` 再重载);退出途中首次保存的未命名标签在清单里仍无路径,重启不恢复(关闭钩子按恢复槽把新路径与名称写回冻结的清单)。`mark-outside.sh` 覆盖退出时冲突→重新载入→新文档进清单、退出时首次保存→重启恢复该文件;去掉各项时失败 +- [~] C14.7 书写视图、输出、Git 配合、大文件(MARK-09、11、13、14)。已验证(macOS):`tests/scripts/mark-output.sh` 真窗口专注模式与外观持久、HTML/资源文件夹/PDF/纯文本导出全文、打印出 PDF、Git 状态随保存变化并打开 ElyTree、3.3 MB 文件只用源码且全文保存与导出、富文本复制带 HTML;`mark-content.sh` 的 look 步;`paper`、`look` 单测;PERF-07 release 1 MB 1.4 秒(冷启动 2.0 秒);其他 ElyMark 门与 `webview.sh` 照过。未验证:Windows 与 Linux R3 FAIL 两项已修(不再送审):导出只收 `img[src]`,`srcset` 与 `picture > source` 的资源留着相对路径(导出时每张图定为一个 `src`:去掉 `srcset`、`sizes` 与 `picture` 的 `source`,无 `src` 时取 `srcset` 的首个地址,随后照常内嵌或拷入资源文件夹);KaTeX 的语法错误被吞,导出照报完整(保留渲染的 VFile,公式错误带行号与 KaTeX 的说明进提示,日志只记行号)。另:组件库 28a6bba 的 `Toaster` 原把每条提示的标题写进日志(提示可引用正文与图片名),改为只记编号与级别;外壳加 `toast_unlogged`,ElyMark 的缺失提示用它。`mark-output.sh` 加响应式图片三种、错误公式、图片名与公式文字不进日志;去掉各项时失败 + +## C15 ElyAgent(§15) + +第二批发布(用户定,2026-10-08):首版不发布 ElyAgent,首版的完整测试与打包不等它。 + +- [ ] C15.1 嵌入 Kigi CLI 的 Rust 代码作后端,沿用其提供商登录与模型配置,不用 ACP(§15.2) +- [ ] C15.2 工具管理、配置管理、配置保护(AGENT-01、02、03) +- [ ] C15.3 会话、输入输出、并行运行(AGENT-04、05、06) +- [ ] C15.4 工具与授权、文件与产物(AGENT-07、08) +- [ ] C15.5 会话恢复、执行控制、历史、用量(AGENT-09、10、11、12) +- [ ] C15.6 本地模型、托盘、生命周期(AGENT-13、14,§15.4,QA-23、24) + +## C12 ElyMail(§12) + +第二批发布(用户定,2026-10-08):首版不发布 ElyMail,首版的完整测试与打包不等它。 + +- [ ] C12.1 MailCore 2 构建、C 垫片、Rust 绑定,三类协议连通与 MIME 解析 +- [ ] C12.2 本地存储:SQLite 索引、内容目录、消息身份(账号、邮箱、UIDVALIDITY、UID)、容量(§12.3、§18.1) +- [ ] C12.3 账号、TLS、OAuth 流程、应用密码(MAIL-01、02) +- [ ] C12.4 同步:头与 90 天正文、按需附件、队列、POP3、同步控制(MAIL-14,§12.3) +- [ ] C12.5 邮箱视图、阅读、会话线程(MAIL-03、04) +- [ ] C12.6 写信、草稿、发送队列与发送状态机(MAIL-05、06、07,§12.4,QA-17) +- [ ] C12.7 组织、搜索、附件(MAIL-08、09、10) +- [ ] C12.8 规则、联系人、迁移、通知(MAIL-11、12、13、15,QA-16、18) + +## R1 首版:六个应用的打包(§2.2、§21,用户 2026-10-08 定) + +- [~] R1.1 八个应用的图标(同一套设计:macOS 为标准 squircle,Linux 与 Windows 为无底形);系统交给应用的文件(启动参数与访达的打开请求)都经同一条路打开;macOS 包自带各自的引擎(LOK 与其 worker、PDF worker 与 OCR 数据、ElyTree 的 Git),带文档类型与图标,按批次出 dmg — R1 FAIL 五项已修:正式签名的 CEF 辅助程序缺 JIT 许可,页面载入失败(渲染与 GPU 辅助程序带 `allow-jit`,`hardened-macos.sh` 以开发证书签名后页面画出);随包 Git 随本机 SDK 要求 macOS 27(以 14.0 构建,`release-macos.sh` 核对每个二进制的最低系统);同时打开多个 CSV 时前一个导入询问被替换丢失(排队逐个询问,单测);发行无许可与源码资料(每个包的 Licenses、源码包,缺即失败);图标测试放过全透明图标(核对标记与颜色)。另:嵌入整个 LibreOffice.app 使系统登记其快速查看插件,设置里出现“LibreOffice 扩展”(用户发现;改为只带内核并注销本机登记,测试不再运行 LibreOffice);Git 的内建命令改为链接(ElyTree 689 MB 降到 63 MB)。去掉各项时相应测试失败 — R2 FAIL 两项已修:MuPDF 编进 worker 的五组字体(urw、sil、noto、droid、han)缺许可(按构建实际嵌入的字体族收齐,`release-macos.sh` 核对);图标测试只验 ico 的 256 层(逐层、逐个 icns 尺寸验标记,清空 16 像素层即失败) — R3 FAIL 五项已修(不再送审):字体许可把 README 当作许可(每族须有 LICENSE、COPYING、NOTICE 或 OFL,README 只作附加;`release-macos.sh` 同样要求,去掉 SIL 的 OFL 即失败);ICO 小尺寸层换成实心方块仍通过(每个 png、ico 层与 icns 尺寸都与母版重新渲染逐像素比对,按预乘比较,macOS 的 16、32 像素旧格式容差 8;实心与清空 16 像素层都失败);三处注释超过十词(改短,`comments.py` 另查 packaging 下的 Python 与 Swift) +- [ ] R1.2 各系统的文件关联(用户 2026-10-08 要求核对):`packaging/types.tsv` 一处定义;Linux 桌面项 `MimeType=`,Windows 安装程序“打开方式”注册与卸载(`packaging/windows/types.sh`,makensis 编过),macOS 照旧;都不设为默认;`tests/scripts/types.sh` 核对三端一致 — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C16 应用之间的连接(§16) + +- [x] C16.1 六个入口:邮件附件、导出查看、Markdown 版本、Agent 产物、文件作邮件附件、应用切换。首版做六个应用之间的三项(导出后查看、Markdown 查看版本、应用切换),邮件附件、文件作邮件附件与 Agent 产物随第二批 — R1 FAIL 三项已修:符号链接文件交给 ElyTree 时按目标文件取历史(只解析所在文件夹的真实路径,保留 Git 跟踪的名字,单测含仓库外的链接);系统默认程序打开的失败被吞(`apps::open_default` 核对退出状态并提示,单测);`mark-output.sh` 只按回车提交保存面板(改按其“存储”按钮,连跑两次通过)。另:系统打开、应用切换与 ElyMark 交 ElyTree 都改在后台运行,界面线程不等子进程 — R2 FAIL 一项已修:新加的符号链接单测未限 Unix,Windows 测试编译失败(加 `#[cfg(unix)]`;同类的 ElyDoc 导出、ElyMark 文件夹、ElyShow 图片三个旧单测一并加上);本机无 MSVC 工具链,Windows 测试编译未能本机核对 + +## C17 工程结构与接入边界(§17) + +- [~] C17.1 进程与状态规则核对:工作进程、请求带会话与修订、FFI 边界、后台任务(§17.2、§17.4) — R1 FAIL 六项已修:LOK 文档号取内核指针、释放后被复用(每次载入一个会话号;关后再开四次,旧句柄被拒、新文档事件照常);PDF 请求不带预期修订(`Doc` 带历史标记,指向页与对象的请求在动内核前核对,过期以 `stale:` 拒绝;删页后按旧页码转页被拒);PDF 句柄不分 worker(句柄带 worker 会话,文档号带进程前缀);发送端可写超限帧(发送前即报错,worker 的超限结果回错误);LOK 输出读错时请求一直等(错误经回复通道交回,停下并回收 worker);PDF 打开中途失败泄漏原生文档(失败即释放,超过 MuPDF 256 MB 缓存后不再增长)。另:对话框关闭原以文档号当指针(回调另带原生句柄)。各项都有回归测试,去掉修复即失败。R2 FAIL 三项已修:LOK 按编号的命令不带预期修订(worker 记修订,`EXPECT` 核对,删表后按旧编号改名被拒);LOK 会话号每个 worker 都从 1 起(会话号带进程前缀,句柄带 worker 会话,别的 worker 的句柄被拒);PDF 附件的读取与删除带“任意”(改为带标记,同名附件换过后旧读取与旧删除都被拒、新附件不动)。随之:一次读取得出的一组命令只核对一次;不按键的鼠标移动不算改动;ElyTable、ElyShow 落后于视图所见修订时重读;读取闭包取视图的句柄。R3 FAIL 四项(第三轮后修,各有回归测试,去掉修复即失败):幻灯片备注的 `Part` 等编号不核对(编号参数补齐 `Part`、`TabNo`、行列与区域四角;删去前一张幻灯片后旧备注请求被拒、余下幻灯片的备注不动);重命名对话框确认时取最新修订(对话框记下打开时的修订,经 `run_on` 执行;对话框开着时删去第一张表,确认后 C 不被改名);输入失败时视图丢了 worker 已加的修订(失败也取回修订;拆分失败后输入器报出的修订与 worker 一致);ElyDoc 删除所示制表位被拒(属性栏按视图所见修订发;添加后删除再保存) + +## C18 数据、配置与隐私(§18) + +- [~] C18.1 配置:版本化、原子写入、文件锁、合并已知字段;通用与应用偏好 — 通用与应用的 `settings.json`(版本、锁、只改所写的键、原子替换),设置窗口(Cmd+,:外观、语言、动效、Omarchy、作者名、ElyTree 的 Git),别的应用写入即刻跟随,主窗口位置;顺带修 ElyMark 启动的应用收不到 SIGTERM、组件 `ThemeSelector` 文案与 `Banner` 无障碍角色 — R1 FAIL 五项已修:作者框只在开窗时读一次,别的应用改名后失焦写回旧名(未编辑时跟随,只写真正的编辑);Git 校验迟到覆盖后一次选择(提交带序号,校验后在同一把锁下核对是最新的才写,过期的不回显);写入失败的提示被随后的重读清掉(另记保存错误,以 Banner 显示);ElyTree 自己的设置读不出时只进日志(Git 页显示原因);版本核对只看 stdout(先核对退出状态、带上 stderr);各加回归(`settings.sh` 四个场景,去掉修复各自失败;`version_of` 单测) — R2 FAIL 两项已修:作者名写入前就当作已存,写失败后被别处的名字顶掉、重按回车也不再写(已存与键入分开,写成功才更新已存,失败的输入留着可重试);提交与磁盘相同的 Git 时不作废在途校验,慢校验随后写回旧程序(每次提交先作废在途校验);`settings.sh` 各加场景,去掉修复各自失败 — R3 FAIL 两项已修(不再送审):作者名的在途写入在等锁期间被改回原值后照样落盘(每次提交都编号,含改回已存值);Git 的“是否最新”在拿到文件锁之前核对,等锁期间被作废的写入锁释放后照写(`Store::set_if` 在文件锁下核对,作者与 Git 共用);回归:另一进程持锁时改回原值、最后的选择留下(`settings.sh` 两个场景,去掉锁下核对即失败),`set_if` 单测 +- [~] C18.2 凭据:系统凭据库,不可用时只留本次会话;日志不含正文与密钥 — 恢复密钥在凭据库不可用时只留本次运行(内存);worker 日志并入应用日志、两端都读 `ELY_LOG`;设置里的诊断先列内容、默认去掉路径与姓名再导出;`privacy.sh` 真窗口核对日志与诊断不含正文 — R1 FAIL 六项已修:引擎原生 stderr(MuPDF 警告引出内容流里的词)原样进日志(relay 只放行 worker 自己的结构化记录与崩溃栈帧,其余只计数;worker 的失败日志把引号里的片段换成 …;崩溃提示改为结构化记录);脱敏只换家目录(凡含 / 或 \ 的词与 URL 都换成 ``,分段标题只写文件名);短名先换拆开长名(按长度从长到短);日志读不出被当作没有(只有不存在算没有,其余报错);relay 把 worker 的模块名改成 lok-worker 后按模块的 ELY_LOG 过滤失效(保留原模块名与级别);`privacy.sh` 清理时以 143 退出(保留自身状态);回归:真 worker 读坏内容流后日志无该词(放行原生行即失败)、脱敏与日志错误单测 — R2 FAIL 三项已修:引擎错误串经“去引号”仍漏出无引号的数据与字段内的引号(worker、ElyPDF 应用与 LOK 输入通道的失败日志只记请求号与错误类别,全文只经回复交给界面;去掉 `scrubbed`);带空格的路径只换掉第一段(从路径起点整段换掉:有引号到引号为止,否则到行末);单字姓名不换(凡非空姓名都换);回归:真 worker 拒绝带凭据的 ftp 地址后日志无该串(记错误全文即失败),脱敏单测含空格路径、Windows 路径与单字中文名 — R3 FAIL 四项已修(不再送审):双引号内的转义引号提前结束路径(双引号里反斜杠转义,按 JSON 找收尾);日志尾部从行中截断,半截路径的文件名漏出(截在行中即跳到下一整行);姓名来源读不出照样导出(`Redact::of_account` 返回错误,导出即失败并显示);LOK worker 启动失败用原生输出,原因被 relay 丢掉、客户端只见管道关闭(改为结构化记录,`Relay::last_error` 把最后一条错误带进客户端的报错);回归:各项单测,真 worker 装不上内核时报错带 worker 的原因(改回原生输出即失败) +- [~] C18.3 数据管理:缓存、清理、恢复副本、导出;数据库升级前备份,失败只读 — 设置里的“存储”:本应用的日志、网页内容缓存(ElyMark)、恢复副本各自大小与清除(日志当场清空;缓存与恢复副本用着,记下后下次启动、打开任何东西之前清掉),每个文档的恢复点数与空间可选(写进应用设置,ElyMark 的恢复点照用;C06.5 的文档应用同);日志过 8 MB 时启动即只留后一半(从整行起);数据库升级与备份只属 ElyMail(第二批),首版六个应用无数据库;邮件与 Agent 历史导出同属第二批;`storage.sh` 真窗口核对;组件库 ea4ed4c:`SettingsRow` 是以标题命名、带说明的组,辅助技术说得出各控件设的是什么 — R1 FAIL 四项已修:恢复点数与空间两个选择各自写回读取时的整份,连续修改互相覆盖(以界面此刻的值改一项,提交带序号,在文件锁下核对最新);清除不经确认(一律先问,写明删什么、何时删,确认才清);启动时清除失败只进日志(设置页写明,记号留着下次再试);测试只核对名称(已知大小的样本核对显示的大小,选 10 后连写 12 个恢复点只留 10 个,确认前不删,持锁时两次选择都留下) — R2 FAIL 四项已修:页面切换后的回读盖掉了未写入的选择(选过未写下的限额留在页面上,写成之后才按磁盘);启动时清除失败仍打开数据(改为启动失败并说明原因,记号留着下次再试);`ax-values` 新增的说明列挡住了其后的坐标(说明移到坐标之前);日志与恢复副本的大小未核对(日志不小于样本,恢复副本等于磁盘上按同法算出的大小);测试另加持锁时切页再选、清除失败时不启动 — R3 FAIL 三项已修并加回归,三轮用尽:设置窗口关掉再开丢了待写入的限额(待写入的限额、提交序号与写入失败移到应用级的全局,不随窗口);清除成功盖掉了限额写入的失败(两者各记各的,限额的失败留到限额写入成功);一条注释超过十词;`storage.sh` 加关窗重开两次持锁选择都留下、限额写入失败后清空日志仍提示、再写成功后提示消失 + +## C19 文件兼容与输出(§19) + +- [~] C19.1 版本化兼容清单、往返验证用例(独立于内核比较) — 已做:`docs/compatibility.md`(版本、内核与补丁、验证机,六个应用各自的编辑保存、导入受限、输出范围,已验证与限制);`independent.rs` 三个用例(DOCX、XLSX、PPTX:Ely 编辑、保存、关闭、再打开、再编辑、再保存),由 `tests/scripts/ooxml.py` 不经 LibreOffice 解 OOXML 检查,预期值由它自己算(整数、日期、错误值、文本严格,浮点按用例容差),DOCX 另交 macOS `textutil`。未做:与 Microsoft Office 的交叉检查(本机没有 Office);字体一致与缺失两种环境的对照 — R1 FAIL 六项已修:读取器改为按关系找部件(主文档、批注、设置、工作表、幻灯片顺序与各页备注,目标须在),批注须在其所在段落被引用,修订开关按 OOXML 的开关值读,表格另查输入格,删去无用函数;读取器另有自测(`ooxml.py selftest`:评审给的每种损坏都须被查出),往返用例在核对后跑它 — R2 FAIL 三项已修:批注须紧跟“Alpha 甲”(同段挪到别处即不过),幻灯片须各为其部件、按序为 First、空白、Second 且只有末张隐藏、备注只在首张;注释改为一行;三种损坏加入自测 — R3 FAIL 三项已修并加回归(各为一种自测损坏),三轮用尽:批注须仍是点(不带范围标记);第二次会话的 Gamma 也须是修订插入;备注须在备注页的正文占位符里(挪到页脚即不过) +- [x] C19.2 打印与 PDF 输出:同一修订、纸张、页边距、缩放 — 已做:打印原用系统共用的打印设置(打印机默认纸张与边距),不缩放时 A4 以外的文档会被裁切或偏移;改为每次打印取一份副本,纸张取 PDF 首页的尺寸与方向、边距为零(文档的页边距已在 PDF 里);导出与打印都在该标签的输入落地之后(同一修订),对话框给出页数与范围,系统面板另有预览;取消不动文档,打印副本删去。`print.sh` 真窗口:A5 横向 DOCX(不经 LibreOffice 写成)以 595×420 点打印,取消后文件不变、打印副本不在、关闭不问。未验证:Windows 与 Linux 的打印(未构建) — R1 FAIL 三项已修:打印设置的副本另把缩放设为 1(原沿用系统共用的缩放);纸张取最大的一页(一次打印只有一种纸张,较小的页按原大印在其上,不裁不缩;逐页纸张做不到,已知限制);测试改核实际输出:`ELY_PRINT_TO` 让打印作业写成文件而不弹面板,`print.sh` 以 pdfinfo 与 pdftotext 读出页面尺寸、页数与文字位置(A5 页文字距左 72 点;A5 加 A3 的文档两页都在 A3 纸上、右缘文字在) — R2 FAIL 两项已修:纸张按两轴取(最长边与各页短边的最大者,方向随最大的一页,各页按自身旋转计),宽高交叉的页也不裁(`paper_for` 单测;`print.sh` 加 1000×200 与 600×600 混排,两端文字都在);`ELY_PRINT_SHARED_SCALE` 让测试先把共用缩放设为一半,A5 文字仍距左 72 点才算过(去掉缩放复位即失败) — R3 PASS + +## C20 质量与性能(§20) + +- [~] C20.1 性能基准:PERF-01 至 PERF-09 的度量工具与记录格式 — 已做:`tests/perf/bench.py`、`samples.py`、`docs/perf/README.md`;办公三应用先开窗口、内核在后台启动(ElyDoc 日常启动 P95 由 3.9 秒降到 0.19 秒,空白文档仍随内核约 2.7 秒);PERF-08 跑 40 次,内存起落、文件数不变;PERF-09 属第二批。未达标照实记录:首次按键到图块的 P95 约 250 毫秒(内核首次输入的初始化)、空白文档就绪 P95 超 3 秒;2026-10-08 全量重测八项全部达标(补丁 0039、PERF-02 计时、关闭标签释放图像,见 agents.md) — R1 FAIL 六项已修:worker 启动核对失败后保持失败、一切请求被拒(单测);PERF-03、04 不再以缓存或请求日志为成功;DOCX 样本实为 62 页(分页符改放在标题段内,测前核对 100 页);PERF-02 每键须有计时;PERF-08 记基线与内核文档数;注释超长 — R2 FAIL 四项已修:首屏改为图块真正画到窗口上之后(`lok: first tile drawn`、`pdf: first tile drawn`,ElyPDF 的图块改由画布直接画以知成败),窗口画失败亦作废;PERF-08 按句柄身份(类型与名称)比对基线,多出的只许 `/System/Library/` 的只读资源且每次相同,首次关闭漏下 `/dev/null` 即失败;文档数日志缺失即失败;`tests/perf/test_bench.py` 以造出的日志与句柄核对这些判定;按修后的基准重测记录 — R3 FAIL 三项已修(不再送审):PERF-02 计时改在显示该编辑的图块真正画到窗口后结束(`EditClock::drawn`,单测),打字时有图块画失败即失败;PERF-08 每轮只认本轮写下的文档数(日志游标,单测);删去不再用的 `files()`;PERF-08 的 Cmd+N、Cmd+W 改以带 Command 标志的事件直接投给应用(`chord.swift`),原 osascript 偶尔丢掉 Command 打出 w +- [ ] C20.2 稳定性:保存失败、内核崩溃、磁盘不足、只读目录、休眠 — 已做:写满磁盘的保存(4 MB 磁盘映像,原文件不变、不留半成品);`tests/perf/soak.py` 长时编辑切换(两个文档轮流键入、保存、只在一个里撤销,每 10 轮以不经内核的读取器核对:内容在、撤销不跨文档、文件不坏);它查出图像不释放(见 agents.md),修后 40 分钟 673 轮全部核对通过,内存 359 至 468 MB(文档与撤销历史随之增长,约每轮 0.17 MB);内核崩溃与强制结束见 `recovery.sh`、`pdf-recovery.sh`。未做:8 小时整跑、系统休眠(测试不能让本机休眠)。2026-10-08 01:34 ElyMark 在 CEF 内一次 XPC 回复释放处 SIGTRAP(`~/Library/Logs/DiagnosticReports/ely-mark-2026-10-08-013420.ips`,CEF 帧无符号),未复现,信息不足 — 未送审:用户 2026-10-09 定性能已够、先发首版 +- [ ] C20.3 无障碍与国际化复核 — 已做:`docs/accessibility.md`(§20.3 逐条:依据与缺口);查出文档区与 ElyMark 页面把 Tab 留给自己、键盘无出口,组件库 029632b 加 F6 与 Shift+F6 照 Tab 次序移出移回(`f6_leaves_a_field_that_keeps_tab`);内核数字区域固定 en-US,改界面语言不重解释已存数值 — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C21 开源与许可证(§21) + +- [ ] C21.1 AGPL 许可文件、第三方通知、锁定依赖清单、源码对应 — 已做:`docs/licenses.md`;`release.sh` 另出上游源码包(实际编译的打过补丁的 MuPDF 与随包 Git),`release-macos.sh` 核对其中为打过补丁的 MuPDF。未做:法律审查;LibreOffice 外部库的源码随包与否 — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C22 测试与验收(§22) + +- [ ] C22.1 功能编号到测试用例的对照表 — 已做:`scripts/test-map.py` 生成 `docs/test-map.md`(首版 159 个编号各有任务,写明的测试与测试所在) — 未送审:用户 2026-10-09 定性能已够、先发首版 +- [ ] C22.2 QA-01 至 QA-28 的执行记录 — 已做:`docs/qa.md`;六个发行应用空闲时无网络套接字(`lsof -i`) — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C23 工程风险证据(§23) + +- [ ] C23.1 九项风险的证据与处理 — 已做:`docs/engineering-risks.md` 末尾 PRD §23 九项的证据与状态 — 未送审:用户 2026-10-09 定性能已够、先发首版 + +## C24 交付物与完成定义(§24) + +- [ ] C24.1 交付物清单、构建说明、已知限制、测试报告 — 已做:根目录 `README.md`(构建、测试、文档索引)、`docs/help.md`(打开保存、恢复、离线边界、仓库操作);已知限制与测试报告见 `docs/compatibility.md`、`docs/qa.md`、`docs/perf/` — 未送审:用户 2026-10-09 定性能已够、先发首版 +- [ ] C24.2 验收逐条核对表 — 已做:`docs/acceptance.md`;结论:macOS 首版六个应用可交付,PRD 的首个正式版(八个应用、三端)未达到 — 未送审:用户 2026-10-09 定性能已够、先发首版 diff --git a/tasks/reviews/C00.1-r1.md b/tasks/reviews/C00.1-r1.md new file mode 100644 index 0000000..70cc91d --- /dev/null +++ b/tasks/reviews/C00.1-r1.md @@ -0,0 +1,13 @@ +FAIL + +scripts/doctor.sh:14 — The version command's exit status is thrown away. Only `command -v` decides whether a tool is missing, so a tool that is on PATH but cannot run counts as present. Its error text is printed as if it were the version. +Scenario (reproduced): put a `cmake` on PATH that prints `dyld: Library not loaded: libcurl.4.dylib` and exits 134. doctor prints `cmake dyld: Library not loaded: libcurl.4.dylib` and exits 0. Same result with `RUSTUP_TOOLCHAIN=nonexistent-xyz`: rustc and cargo print `error: override toolchain ... is not installed` and doctor still exits 0. This breaks "缺项以非零码退出". +Fix: capture the output and exit status of `"$tool" "$@"`. If the status is non-zero, print the tool as failed with its error and set missing=1. + +scripts/doctor.sh:2 — The comment has 12 words (`Build tools Ely Office needs. Prints versions. Exits 1 on any miss.`). agents.md rule 5 caps comments at ten words. +Scenario: any rule check of comment length flags this line. +Fix: cut it to ten words or fewer, or remove it. + +scripts/doctor.sh — No test covers the non-zero exit, which is the item's acceptance criterion. Nothing in the repo runs doctor with a missing or broken tool and asserts exit 1. The fact that the line-14 defect went undetected shows this gap. +Scenario: a change that makes the script always `exit 0`, or that drops a tool from the list, passes unnoticed. +Fix: add a test that runs doctor.sh with a stub PATH. Cover (a) one required tool absent and (b) one tool that exits non-zero. Assert exit 1 and the matching report line in each case, and assert exit 0 when every tool is present and works. diff --git a/tasks/reviews/C00.1-r2.md b/tasks/reviews/C00.1-r2.md new file mode 100644 index 0000000..a9566b1 --- /dev/null +++ b/tasks/reviews/C00.1-r2.md @@ -0,0 +1,14 @@ +FAIL + +r1 check: +- doctor.sh:12 broken tools: fixed. With `RUSTUP_TOOLCHAIN=nonexistent-xyz`, doctor prints `BROKEN rustc: ...` and `BROKEN cargo: ...` and exits 1. +- doctor.sh:2 comment length: fixed. It is now nine words. +- missing exit-code test: partly fixed. tests/scripts/doctor.sh covers all-present, absent, and broken, and it passes locally. The r1 scenario "drops a tool from the list, passes unnoticed" still holds; see the first entry below. + +tests/scripts/doctor.sh:15 — The test builds its stub list from the `check` lines in doctor.sh, then asserts output only for cmake and ninja. If a required tool is removed from doctor.sh, it also disappears from the test's expectations. C00.1 names cmake, ninja, pkg-config, and git-lfs, but nothing pins pkg-config or git-lfs. +Scenario (reproduced): delete the `check pkg-config` and `check git-lfs` lines from a copy of doctor.sh and run the test against it. It prints `doctor.sh: 3 cases pass` and exits 0. +Fix: hard-code the required tool list in the test. In the all-present case, assert that a version line appears for each of those tools. + +tests/scripts/doctor.sh:13 — The test sets `PATH` to `$stubs:/usr/bin:/bin`, so in the "absent" case doctor still finds any real tool installed in a system directory. On Linux, `apt install cmake` puts it at /usr/bin/cmake. Removing the cmake stub then exposes the real cmake, doctor exits 0, and the test fails. Whether the test passes depends on the host, including the Linux and Raspberry Pi targets. +Scenario (reproduced): add a directory holding a real `cmake` to the test's PATH after the stubs, standing in for /usr/bin on Linux. The test prints `FAIL: cmake absent: doctor exited 0` and exits 1. +Fix: give doctor a PATH that contains only the stub directory. Start doctor with an absolute bash path so the `env bash` lookup is not needed, and either stub or link `head` in the stub directory or replace `head` with parameter expansion in doctor.sh. diff --git a/tasks/reviews/C00.1-r3.md b/tasks/reviews/C00.1-r3.md new file mode 100644 index 0000000..b88b704 --- /dev/null +++ b/tasks/reviews/C00.1-r3.md @@ -0,0 +1,13 @@ +PASS + +r2 check: +- tests/scripts/doctor.sh:15 test list tracked doctor's own list: fixed. The test now hard-codes the 11 tools on line 8 and asserts a version line for each one. Re-ran the r2 scenario by deleting `check pkg-config` and `check git-lfs` from a copy of doctor.sh. The test prints `FAIL: no version line for pkg-config` and exits 1. +- tests/scripts/doctor.sh:13 real system tools leaked into the absent case: fixed. Line 14 sets `PATH="$stubs"` only and starts doctor with `"$BASH"`. doctor.sh uses only builtins now, since `head` was replaced by `${out%%$'\n'*}`, so nothing outside the stub directory can be found. + +Verified this round: +- `bash tests/scripts/doctor.sh` prints `doctor.sh: present, absent x11, broken pass` and exits 0. It does the same under /bin/bash 3.2. +- `bash scripts/doctor.sh` on this host prints versions for all 11 tools, including cmake 4.4.3, ninja 1.13.2, pkg-config 3.0.7 and git-lfs 3.8.0, and exits 0. +- Mutant with `exit 0` in place of `exit "$failed"`: the test fails with `rustc absent: doctor exited 0`. +- A stub on PATH that is not executable is reported as `BROKEN cmake: ... Permission denied`, and doctor exits 1. + +No new defects in the changed code. diff --git a/tasks/reviews/C00.2-r1.md b/tasks/reviews/C00.2-r1.md new file mode 100644 index 0000000..416439b --- /dev/null +++ b/tasks/reviews/C00.2-r1.md @@ -0,0 +1,15 @@ +FAIL + +Ran `bash tests/scripts/references.sh`: exit 0, "14 pins match". All 14 `reference/*` HEADs equal their pins and every worktree is clean. The defects below are gaps that this run cannot catch. + +scripts/fetch-references.sh:12 — The "already done" check compares only HEAD against the pin. When HEAD matches, the function returns before lines 19–21, so a changed sparse path list is never applied. The script prints `ok` and exits 0 with sources missing. +Scenario (reproduced on a scratch copy of reference/libreoffice-core): add `vcl/inc` to the libreoffice-core sparse list and run the script. It prints `ok libreoffice-core 8221e31…` and exits 0. `reference/libreoffice-core/vcl` does not exist, and `sparse-checkout list` still shows the old five paths. tests/scripts/references.sh also passes, because it only probes `LibreOfficeKit.h`. +Fix: make the skip condition also require that the recorded sparse set equals the requested paths. For example, compare `git sparse-checkout list` with "$@". Otherwise re-apply `sparse-checkout set` on every run. + +scripts/fetch-references.sh:38 — mailcore2's pinned dependencies are never checked out. `deps/ctemplate`, `deps/libetpan` and `deps/tidy-html5` are submodules of the pin, but only mupdf gets `submodule update` (line 36). `git -C reference/mailcore2 submodule status` lists all three with `-` (uninitialized), and `deps/libetpan` is an empty directory. This breaks "全部 pin 检出". It also leaves C00.4 ("MailCore 2 依赖可构建") with no pinned libetpan, ctemplate or tidy sources: build-mac and build-android consume `deps/`. +Scenario: run the script on a fresh machine, then try to build MailCore 2 from `reference/mailcore2` following build-mac/README.md. The libetpan sources are absent, and the script and test both report success. +Fix: run `git -C "$ref/mailcore2" submodule update --init --depth 1 --recursive`, the same way as mupdf. Have the test assert this. + +tests/scripts/references.sh:25 — Submodule state is checked through a single file, `mupdf/thirdparty/freetype/.../freetype.h`. The other 17 mupdf submodules (harfbuzz, lcms2, jbig2dec, openjpeg, libjpeg, zlib, …) are never checked for being initialized or for being at the commit the mupdf pin records. +Scenario: `git -C reference/mupdf submodule deinit -f thirdparty/harfbuzz`, or a submodule left at another commit (`+` in `submodule status`). The test still prints "14 pins match" and exits 0, while C00.4's MuPDF build fails. +Fix: for each repo whose submodules the script fetches (mupdf, and mailcore2 after the fix above), fail when any line of `git submodule status --recursive` starts with `-` or `+`. diff --git a/tasks/reviews/C00.2-r2.md b/tasks/reviews/C00.2-r2.md new file mode 100644 index 0000000..22e0042 --- /dev/null +++ b/tasks/reviews/C00.2-r2.md @@ -0,0 +1,13 @@ +PASS + +Ran `bash scripts/fetch-references.sh`: exit 0, all 14 print `ok`. Ran `bash tests/scripts/references.sh`: exit 0, "14 pins match, sparse set and submodules in place, gpui rev agrees". Every `reference/*` worktree is clean. + +r1 defects, each confirmed fixed: + +scripts/fetch-references.sh:18 — The skip now also requires `sparse_ok`. Reproduced the r1 scenario on a scratch copy of reference/libreoffice-core with `vcl/inc` added to the list. The script printed `fetch`, `sparse-checkout list` showed six paths, and `vcl/` was present. A second run printed `ok`. + +scripts/fetch-references.sh:45 — mailcore2 submodules are now initialized. In reference/, ctemplate, libetpan and tidy-html5 show a leading space in `submodule status --recursive`, and `deps/libetpan` has sources. A fresh scratch fetch of mailcore2 checked out all three at their recorded commits. Deinit of `deps/tidy-html5` showed `-`. The next run restored it. + +tests/scripts/references.sh:39 — The test now fails on any `-`, `+` or `U` line from `submodule status --recursive`, or on an empty list, for mupdf and mailcore2. Confirmed that the grep catches a deinitialized submodule. + +No new defects in the changed code. diff --git a/tasks/reviews/C00.3-r1.md b/tasks/reviews/C00.3-r1.md new file mode 100644 index 0000000..e5e2732 --- /dev/null +++ b/tasks/reviews/C00.3-r1.md @@ -0,0 +1,11 @@ +FAIL + +Ran `bash tests/scripts/agents-md.sh`: exit 0, "agents.md: 115 component names exist". The component library is at b54500b. Every brace-listed name in the map is re-exported from its module's `mod.rs`, or for `theme::{Theme, Mode}`, defined in `theme/mod.rs`. `Theme::set_palette` exists at `theme/mod.rs:163`. The gap claims hold. `documents/viewer.rs:290` maps every page into the element tree with no culling, which breaks PERF-05 ("不先渲染全部页面"). `FixedFormatToolbar` holds an `Entity` (`documents/toolbar.rs:82`). `misc::WebView` is a native macOS overlay, not a CEF off-screen view. No formula bar or name box is exported from `tables`. + +tests/scripts/agents-md.sh:28 / agents.md:34 — agents.md says the script "核对下表的每个名字", but the script only extracts `module::{...}` brace groups. It does not check `editor::FindWidget` on the Doc row (agents.md:45) or `Theme::set_palette` (agents.md:52). Scenario: on a scratch copy, changing line 45 to `editor::NoSuchWidget` or line 52 to `Theme::no_such_fn` still gives exit 0 and "115 component names exist". Fix: make the script also extract the bare `module::Name` and `Type::method` forms in the map, or write every map entry in brace form and stop naming items the script cannot check. + +tests/scripts/agents-md.sh:21 — The script greps for `pub (struct|enum|fn|trait|type) Name` anywhere under the module directory. It does not check that `module::Name` resolves. This passes items in private submodules and methods inside `impl` blocks. Scenario: on a scratch copy, adding `documents::{..., Mark}` and `git::{new, ...}` gives exit 0 and "117 component names exist". But `documents::Mark` sits in private `mod markdown` and is not re-exported, and `git::new` is only a method (`git/review.rs:69`). `use ely_gpui_component::documents::Mark` would not compile. Fix: check each name against the module's public surface, meaning the `pub use` lists in `mod.rs` plus items defined in `mod.rs` itself. Better still, compile a generated `use ely_gpui_component::module::{...};` file against the pinned crate. + +tests/scripts/agents-md.sh:2 — The comment is eleven words, over the ten-word limit. It also says "is defined", which is the weak property the script checks, not the import path the map promises. Fix: shorten it to ten words or fewer, and state the property the corrected check verifies. + +agents.md:34 — TASKS.md C00.3 asks for the gpui rules to be "写入 `agents.md`". agents.md only says "gpui 用法规则见该仓库 `AGENTS.md`". That points into `reference/`, which agents.md:27 marks as not part of this project. The upstream file is 254 lines and mixes library-internal notes (gallery capture harness, wasm build, website) with the rules a consumer must follow. Examples of the latter: `ely_gpui_component::init` panics unless `Assets` is passed to `with_assets`; the root goes in a `FocusScope`; a state change outside render calls `cx.notify()` and, during paint, `request_animation_frame`; overlays go through `primitives::raise`; `uniform_list` rows set `w_full`. Scenario: an implementer of C01.1 reads only agents.md, as the project rules say to, and gets none of these. Fix: write the consumer-facing gpui rules into agents.md, or change the C00.3 line in TASKS.md if delegating to upstream is the decision. diff --git a/tasks/reviews/C00.3-r2.md b/tasks/reviews/C00.3-r2.md new file mode 100644 index 0000000..cadc3fe --- /dev/null +++ b/tasks/reviews/C00.3-r2.md @@ -0,0 +1,19 @@ +FAIL + +Ran `python3 tests/scripts/agents_md.py`: exit 0, "agents.md: 116 component names are public". The component library is at b54500b. + +Round 1 defects: +- The script missed bare names. Fixed. Every map entry is now in brace form: `editor::{FindWidget}`, and `Theme::set_palette` was removed. Changing the map to `editor::{NoSuchWidget}` now exits 1. +- The script grepped the whole module tree. Partly fixed. It now reads only `mod.rs`, so `git::new` and `documents::Mark` are rejected. Methods in `impl` blocks inside `mod.rs` still pass. See the first entry below. +- The script comment was too long. Fixed. The docstring is now nine words. +- The gpui rules were missing. Fixed. agents.md:66-79 holds the consumer rules. I checked each one against upstream `AGENTS.md`, `src/lib.rs:54-59` and `examples/docs/hello.rs:40-44`. `motion::duration`, `primitives::raise`, `FocusScope::root`, `i18n::I18n`, `Theme::set_mode` and `Theme::set_mode_now` all exist. + +The gap claims still hold. `tables::Spreadsheet`, `documents::FixedFormatToolbar` and `documents::DocumentViewer` are exported. No formula bar or name box exists in `src`. PERF-05 is at PRD.md:732. + +tests/scripts/agents_md.py:9 — The `ITEM` regex is `^\s*pub\s+(?:struct|enum|fn|...)` and allows leading whitespace. It therefore collects `pub fn` methods inside `impl` blocks in `mod.rs` as module items. Scenario: on a scratch copy, changing agents.md:52 to `theme::{Theme, Mode, set_mode_now, set_palette}` gives exit 0 and "118 component names are public". Both names are associated functions of `impl Theme` (`theme/mod.rs:140`, `:163`), so `use ely_gpui_component::theme::set_palette` does not compile. This is round 1's "methods inside `impl` blocks" scenario, still open. Fix: count only top-level items (no indentation, outside any `impl` or inline `mod`). Better: compile a generated `use ely_gpui_component::module::{...};` file against the pinned crate. + +tests/scripts/agents_md.py:26 — A glob re-export yields the literal name `*`. Names it brings in are then reported as not public. Scenario: `motion/mod.rs:18` has `pub use curve::*;`, which exports `motion::duration`, the function agents.md:77 tells apps to use. Adding `motion::{duration, ...}` to the map gives "FAIL: motion::duration is not public", which is false. Fix: the compile check above handles this too. Otherwise resolve `name::*` by reading that submodule's top-level `pub` items. + +agents.md:100 — The documented command for script tests is `tests/scripts/*.sh`. The new check is `agents_md.py` (renamed from `agents-md.sh` in round 1), so that glob no longer runs it. Scenario: `for t in tests/scripts/*.sh; do bash "$t"; done` runs only `doctor.sh` and `references.sh`. A map name that drifted from the library goes unnoticed. Fix: list `tests/scripts/agents_md.py` in the commands section, or make the documented glob cover it. + +agents.md:68 / agents.md:73 — The section says its rules are "取自组件库 `AGENTS.md`". Upstream `AGENTS.md` contains no rule "不用 `request_animation_frame` 轮询布局". Its own inspector requests a frame on each render while picking (upstream AGENTS.md:229). The startup chain and the `Assets` panic come from `src/lib.rs:57` and the examples, not from `AGENTS.md`. Scenario: someone checking the source of the polling ban finds nothing upstream that backs it. Fix: name the real source of each rule. If the polling ban is this project's own rule (it follows from rule 9), state it as that. diff --git a/tasks/reviews/C00.3-r3.md b/tasks/reviews/C00.3-r3.md new file mode 100644 index 0000000..7d4e658 --- /dev/null +++ b/tasks/reviews/C00.3-r3.md @@ -0,0 +1,17 @@ +PASS + +I ran `python3 tests/scripts/agents_md.py`. It exited 0 and printed "agents.md: 117 component names are public". The component library is at b54500b. + +Round 2 defects: +- Methods in `impl` blocks counted as module items. Fixed. `ITEM` is now anchored at column 0 (`agents_md.py:9`). On a scratch copy, `theme::{Theme, Mode, set_mode_now, set_palette}` exits 1 with "theme::set_mode_now is not public". +- Glob re-exports. Fixed. `glob_names` reads the top-level `pub` items of the submodule. `motion::{duration, ...}` is now in the map (agents.md:51) and passes. `lerp` and `spring` from `curve::*` also pass, which is correct. `NoSuch`, `curve`, `self`, an empty name and `{}` each exit 1. An unknown module also exits 1, and so does an unresolvable glob. +- Script tests missing from the commands. Fixed. agents.md:100 now lists `*.py`. +- Source of the gpui rules. Fixed. agents.md:68 now names `src/lib.rs` and `examples/docs/hello.rs` as well. The polling ban is in upstream `AGENTS.md:116` ("Never poll layout with `request_animation_frame`"), so the round 2 claim that it had no upstream source was wrong. + +Rechecked: +- agents.md:70-79. Each rule matches an upstream source: `AGENTS.md` lines 45-47, 51-52, 56, 60, 62, 75, 116, 133, 162, 188 and 218; `src/lib.rs:57`; `hello.rs:16-17` and `:42`. +- Every mapped module is `pub mod` in `src/lib.rs`. +- The gap claims hold. `DocumentViewer` maps every page (`documents/viewer.rs:290`). `FixedFormatToolbar` takes an `Entity`. `tables::Spreadsheet` is exported. `src` has no formula bar and no name box. PERF-05 is at PRD.md:732. +- File sizes are within the limit: agents.md has 108 lines and agents_md.py has 48. The only comment in the script is a nine-word docstring. + +No defects found. diff --git a/tasks/reviews/C00.4-r1.md b/tasks/reviews/C00.4-r1.md new file mode 100644 index 0000000..ad0d943 --- /dev/null +++ b/tasks/reviews/C00.4-r1.md @@ -0,0 +1,29 @@ +FAIL + +What I ran and confirmed: + +- I mounted `engines/LibreOffice_26.2.6_MacOS_aarch64.dmg` (297 798 926 bytes). `nm -gU` shows `libreofficekit_hook`, `libreofficekit_hook_2` and `lok_preinit`. The only VCL plugin is `libvclplug_osxlo.dylib`. +- I rebuilt the three LOK spikes against `reference/libreoffice-core/include` and ran them on a fresh profile: + - Threaded mode dies with `NSWindow should only be instantiated on the main thread!`. + - Unipoll with runLoop never calls the poll callback. + - Unipoll with GCD prints "polls so far: 0". It then gives BuildId 8221e31, a size of 12808 × 16408, an all-zero tile and a PDF. +- `init.cxx:4277/4348` and `configure.ac:5950-5952` match the doc. +- The patch is byte-identical to the `engines/lo-src` working-tree diff. It applies cleanly to the pinned `init.cxx`. The LO build is still running, so the patch has not yet been shown to compile; the doc defers that to C07.1. +- MuPDF: the lib sizes match. The rebuilt `mupdf_spike` on the LOK PDF gives 1 page, 612x792, 1224x1584, 8241 dark pixels, and the CJK text. +- `libmupdf-pkcs7.a` imports OpenSSL EVP symbols. +- CEF: the index sizes match, and the macosarm64 URL answers 200 with 132224904 bytes. +- The tessdata sizes match. +- libetpan 6dc099a fails on `md5.c` with default flags and builds with `CFLAGS=-std=gnu17`. +- `tests/scripts/*.sh` and `agents_md.py` exit 0. + +docs/engineering-risks.md:22 — The doc says upstream master's `init.cxx` has "没有出图块的支持". That is false. Master contains bd2d0bec67, "coda-m: Don't skip the tile painting on macOS" (committed 2025-12-10). It changes the exact guard the doc cites, from `defined(UNX) && !defined(MACOSX)` to `defined(UNX)`. That is the same change as the patch's second hunk. +Scenario: someone checks upstream before C07.1 and finds the doc wrong. Nothing in the doc says why a backport is not enough. With that commit, macOS takes the `#else` branch, which uses `SetOutputSizePixelScaleOffsetAndLOKBuffer`. `AquaSalVirtualDevice` does not override `SetSizeUsingBuffer`, in either the pin or master. So upstream's path still ignores `pBuffer` under Aqua, and that is the real reason the CoreGraphics hunk is needed. The doc never states this. +Fix: cite bd2d0bec67, say what it fixes and what it does not, and base the need for the patch on the Aqua `SetSizeUsingBuffer` fact. + +docs/engineering-risks.md:38 and :42 — R-03 does not record a blocker. The locked MailCore 2 (1b48bab) includes `` and ``. Neither the pinned submodule libetpan 6dc099a (2022-06-27) nor Homebrew's libetpan 1.10.1 has them. Point 2 blames the gap only on the prebuilt libetpan-osx-21. +Scenario: I built and installed 6dc099a with `-std=gnu17`. `inst/include/libetpan` has no `mailgmail.h` and no `mailactivesync.h`. The conclusion's plan ("用 `cc` 构建、固定源文件清单,对 ICU 头文件打补丁") would fail on `MCGmailSession.cpp` and `MCActiveSyncSession.cpp` in C12.1, just as the CMake attempt did. "锁定的 libetpan 可构建" (line 9) proves only that the wrong version of the dependency builds. That does not meet the C00.4 requirement "MailCore 2 依赖可构建". +Fix: state that the pinned submodule is too old for the pinned MailCore 2. Upstream libetpan master has `src/low-level/gmail/mailgmail.h` and `src/low-level/activesync/mailactivesync.h`. Pin a libetpan commit that has these APIs and show that it builds, or record the gap as a blocker and update the status row and the conclusion. + +docs/engineering-risks.md:21, :31 — The evidence for R-01 facts 3–5 and all of R-02 is spike programs and a sample `.docx`. They exist only in another session's `/private/tmp/.../scratchpad/spikes`. The repo does not contain them, and the doc names no source or command except the `nm` check in fact 1. agents.md's 2026-10-01 decision says "证据见 R-01", and PRD §23 asks for submitted evidence. +Scenario: once the temp directory is purged, no one can re-run the threaded crash, the unipoll/GCD dispatch, the zero tile or the MuPDF render. Also, the spikes pre-zero the tile buffer with `calloc`, so "返回全零缓冲" cannot tell "wrote zeros" apart from "never wrote". +Fix: commit the spike sources and the sample document with their run commands, for example under `tests/scripts` with a check that skips when `engines/` is absent and says so. Fill the tile buffer with a sentinel before `paintTile`. diff --git a/tasks/reviews/C00.4-r2.md b/tasks/reviews/C00.4-r2.md new file mode 100644 index 0000000..ccb0803 --- /dev/null +++ b/tasks/reviews/C00.4-r2.md @@ -0,0 +1,52 @@ +FAIL + +Round-1 defects: + +- R-01 upstream claim: fixed. Line 22 cites bd2d0bec67. At the pin, `vcl/quartz/salvd.cxx` has no `SetSizeUsingBuffer` override; only the base in `vcl/inc/salvd.hxx:46` exists. +- R-03 libetpan blocker: fixed. libetpan is now pinned at 4d47862. I built it from scratch in a scratch directory and it builds and installs both headers. All 76 `mailgmail_*`/`mailactivesync_*` functions that MailCore 2 calls are defined in `libetpan.a`. +- Spikes not in the repo: fixed. `tests/engines/lok_probe.c` and `mupdf_probe.c` are in the repo, the sample documents are generated, and the tile is pre-filled with 0xAB. + +What I ran: + +- `tests/engines/lok.sh` exits 1 twice in a row. See the first entry. +- `tests/engines/mupdf.sh` passes. The library sizes match, and the pkcs7 archive imports 17 EVP/PKCS7 symbols. +- `tests/engines/libetpan.sh` passes. +- `tests/scripts/*.sh` and `agents_md.py` exit 0. +- The patch is byte-identical to the `engines/lo-src` diff. It applies with `git apply --check` to the pin. +- The `init.cxx:4277/4348` and `configure.ac:5950-5952` references match. + +tests/engines/lok.sh:12 — The cleanup runs `hdiutil detach "$mount" -quiet || true`. The detach fails with "Resource busy", and `|| true` swallows the error. `rm -rf "$work"` then walks into the still-mounted read-only dmg. +Scenario: on this machine, two runs in a row printed "lok: load, PDF export, thread rule pass; tile untouched". Then came about 3 MB of `rm: ... Read-only file system` lines, and the script exited 1. The dmg stayed mounted under `/var/folders/.../tmp.*/mnt` until I detached it by hand a few seconds later. The evidence script for R-01 never exits 0, and every run leaks a mount. +Fix: make cleanup wait until the image is released, for example by retrying the detach until it succeeds or a deadline passes. A detach failure must fail the script. Never run `rm -rf` over a path that is still mounted. + +docs/engineering-risks.md:25 (status row :7) — The doc says "构建关闭 Skia" and "构建已在 `engines/lo-src` 进行". Neither is true. No build process is running, and `engines/lo-build.log` (17:34) ends in `make: *** [Makefile:302: build] Error 2`. The cause is six errors in `vcl/osx/salframeview.mm:802-847` (`SkiaHelper` and `CAMetalLayer` undeclared). That code is not guarded by `HAVE_FEATURE_SKIA` and asserts `"macos requires skia"`. `config_host.mk` shows `ENABLE_SKIA=` because `--disable-skia` is in `autogen.lastrun`. +Scenario: C07.1 follows the documented plan of a self-build with Skia off, and it cannot compile LibreOffice 26.2.6.3 on macOS. With Skia on (which is required), the patch's reasoning about the Quartz `AquaSalGraphics` drawing into a foreign `CGContext` has not been examined. C00.4 requires blockers to be written down; this one is missing. The agents.md decision depends on it too. +Fix: record the failed Skia-off build as a blocker, citing the log and `salframeview.mm:802`. Correct the build status. Either show that a Skia-on build still sends the patched `paintTile` path through Quartz into `pBuffer`, or record that as unverified and change the R-01 row. + +docs/engineering-risks.md:42; scripts/build-libetpan.sh:21 — The doc says `md5.c` fails to compile under the default C standard because of K&R definitions. At the pinned 4d47862, `md5.c` uses ANSI prototypes (`MD5Init(MD5_CTX * context)` at :92). A clean `./autogen.sh` with no `CFLAGS`, run with Apple clang 21, builds and installs with no errors. Setting `CFLAGS="-std=gnu17"` replaces autoconf's default `-g -O2`, so the generated Makefiles have `CFLAGS = -std=gnu17 -W -Wall`. +Scenario: the shipped libetpan is built at -O0 with no debug info, to work around a failure that does not happen at this pin. The doc records a false reason. +Fix: drop the `CFLAGS` override, or keep the default optimization flags. Delete the `md5.c` sentence, or confine it to 6dc099a. + +scripts/build-libetpan.sh:16, :21-22 — These lines are silent fallbacks. +- `brew --prefix openssl@3 ... || echo /usr` points OpenSSL at `/usr`, which has no OpenSSL headers on macOS. libetpan's `configure.ac:227-243` then sets `with_openssl=no` without an error. +- json-c is auto-detected (`configure.ac:841-852`, `with_json=auto`). Without json-c ≥ 0.14, the `gmail` subdirectory and `mailgmail.h` are left out (`src/low-level/Makefile.am:40-46`). This build found Homebrew json-c 0.19. +The doc's dependency list (:42) does not mention json-c. +Scenario: on a machine without Homebrew or with no `OPENSSL_DIR`, libetpan builds without TLS. `tests/engines/libetpan.sh` only checks headers, so it still passes, and PRD §23 requires TLS for MailCore 2. On a machine without json-c, the test fails with "pinned libetpan lacks libetpan/mailgmail.h", which hides the real cause. +Fix: require `OPENSSL_DIR` or the brew prefix and fail if it is missing. Pass `--with-json`, so configure errors out when json-c is absent. Assert `HAVE_OPENSSL` in the generated `config.h` in the test. List json-c in R-03. + +tests/engines/mupdf.sh:44 — The only check on the signing helper is `[ -s libmupdf-pkcs7.a ]`. When `pkg-config libcrypto` fails, MuPDF's `Makerules:238` leaves `HAVE_LIBCRYPTO` unset. `pkcs7-openssl.c:28` then compiles stub versions of the same three symbols, and the archive is still non-empty. +Scenario: on a machine without OpenSSL's pkg-config file, the test prints "signing helper pass". The R-02 claim "用 OpenSSL 3.6.4 构建成功" is false there. +Fix: pass `HAVE_LIBCRYPTO=yes` in `scripts/build-mupdf.sh`, and assert that `nm -u libmupdf-pkcs7.a` lists EVP/PKCS7 symbols. + +docs/engineering-risks.md:16 — The doc says `lok.sh` asserts each fact listed below ("逐条断言下列事实"). It does not. The script never checks: +- the dmg size or the three exported symbols (fact 1); +- that no `svp` plugin is present (fact 2); +- the `init.cxx` line numbers (fact 4); +- bd2d0bec67 or `salvd.cxx` (fact 5); +- `configure.ac` (fact 6). +Scenario: a different dmg that drops `lok_preinit`, or ships an svp plugin, still passes `lok.sh`, and the doc claims those facts were asserted. +Fix: assert the binary facts (`nm -gU`, the plugin list, the dmg size) in `lok.sh`, and give the source facts as plain citations. + +docs/engineering-risks.md:22 — "与补丁第二处相同" and "需要补丁第三处" point to the wrong hunks. The patch's second hunk (`@@ -174`) is the `vcl/sysdata.hxx` include. The guard change is hunk 3 (`@@ -4274`), and the CoreGraphics path is hunk 4 (`@@ -4283`). +Scenario: a reader checking the claim against the patch compares the wrong hunk. +Fix: cite the hunks by their header lines, or by the code they change. diff --git a/tasks/reviews/C00.4-r3.md b/tasks/reviews/C00.4-r3.md new file mode 100644 index 0000000..e09f7ed --- /dev/null +++ b/tasks/reviews/C00.4-r3.md @@ -0,0 +1,37 @@ +FAIL + +Round-2 defects: + +- lok.sh detach: fixed. Cleanup retries `hdiutil detach` for up to ten seconds and fails the script if the detach never succeeds. It skips `rm -rf` while the image is still mounted. Two runs in a row both exited 0 in about 10 s. `hdiutil info` showed no leftover LibreOffice image afterwards. +- Skia-off build and build status: fixed. `engines/lo-src` is at 8221e31, with `ENABLE_SKIA=TRUE` and `SKIA_GPU=METAL` in `config_host.mk`. The build is running (about 38 clang processes). Fact 7 and the status paragraph now say this. +- md5.c and CFLAGS: fixed. The CFLAGS override is gone. A fresh build has `CFLAGS = -g -O2 -W -Wall`. The md5.c sentence is now limited to 6dc099a. +- libetpan fallbacks: partly fixed. The script passes `--with-json`, the test asserts `HAVE_OPENSSL` and `HAVE_JSON`, and json-c is listed. The OpenSSL fallback is still open; see the first entry. +- mupdf.sh pkcs7 stub: fixed. The script passes `HAVE_LIBCRYPTO=yes`, and the test runs `nm -u` and looks for `PKCS7_`. A fresh build in a scratch tree passed: library sizes 53 940 840 and 9 905 040 bytes, and 17 PKCS7/EVP imports. +- The doc's "逐条断言" claim: fixed. lok.sh now asserts the dmg size, the three exports and the plugin list. Facts 5 to 8 are given as citations. The cited lines match the source: `init.cxx:4277/4348`, `salvd.hxx:46-50`, `configure.ac:5950-5952`, `gdiimpl.cxx:1390-1393`, `salgdiutils.cxx:112` and `SkiaHelper.cxx:256-261`. +- Hunk citations: fixed. The patch is described by the code it changes. It is byte-identical to the diff in `engines/lo-src`. + +What I ran: + +- `tests/engines/lok.sh` twice: exit 0 both times. +- `tests/engines/mupdf.sh`: exit 0, both cached and from a fresh build. +- `tests/engines/libetpan.sh`: exit 0, both cached and from a fresh build (86 s). +- `tests/scripts/*.sh` and `agents_md.py`: exit 0. + +scripts/build-libetpan.sh:16, :11, :29 — The OpenSSL fallback is still silent, and a TLS-less build gets cached as good. +- `brew --prefix openssl@3` exits 0 and prints `/opt/homebrew/opt/openssl@3` even when that formula is not installed. I checked this with `brew --prefix libressl`, which returned a path that does not exist. So the `|| { ... exit 1; }` branch only runs when Homebrew itself is missing. +- When `OPENSSL_DIR` or the brew prefix points at a directory with no OpenSSL, libetpan's `configure.ac:227-237` sets `with_openssl=no`. The build then succeeds, writes `PIN`, and prints "built libetpan". +- Line 11 keys the cache on the pin alone, so every later run reuses that build. +Scenario: I ran `OPENSSL_DIR=/nonexistent tests/engines/libetpan.sh` in a scratch tree. The build succeeded, and the test printed "FAIL: libetpan built without TLS". I reran the test with the working Homebrew OpenSSL. The build script printed "ok" from the cache, and the test failed with the same message again. It keeps failing until someone deletes `engines/libetpan` by hand. The doc's claim at `docs/engineering-risks.md:47` that the script fails outright ("缺了脚本直接失败") is false for this case. +Fix: before building, require `$ssl/include/openssl/ssl.h`. After configure, fail if `config.h` lacks `HAVE_OPENSSL` or `HAVE_JSON`, and do so before `PIN` is written. + +docs/engineering-risks.md:27-29 — PRD §23 requires recording the source commit, compile options and patches when a version is chosen ("选择版本时记录源码提交、编译选项和补丁"). R-01 picks a self-built LibreOffice, and the build is already running. Its configure options are recorded nowhere in the repo: +- They exist only in `engines/lo-src/autogen.lastrun`, and `engines/` is not tracked. +- There are 32 options, among them `--enable-mergelibs`, `--disable-python`, `--without-java`, `--with-lang=` and `--with-fonts`. +- There is no `scripts/build-libreoffice.sh`, and the doc does not list the options. +- How `engines/lo-src` was created (the clone, the tag, then `git apply` of the patch) is also unrecorded. +Scenario: C07.1 runs on another machine, or `engines/` is cleared. Nobody can rebuild the binary that `ELY_LOK_EXPECT_TILE=written` is meant to verify. lok.sh's plugin-list assertion depends on `--enable-mergelibs` in a way nobody can trace. +Fix: add a `scripts/build-libreoffice.sh` that clones the pin, applies the patch and runs autogen with the explicit option list. Alternatively, record the exact option list in R-01. Either way, `engines/lo-src` must be reproducible from the repo. + +docs/engineering-risks.md:24 — The doc says the six compile errors come from "该块(`:794-810`)". In a release build the `assert` is compiled out. The block at 794-810 accounts for three of the errors: two `SkiaHelper` uses at :802 and `CAMetalLayer` at :807. The other three come from a second unguarded block in `windowWillExitFullScreen`, at :841 (two `SkiaHelper` uses) and :846 (`CAMetalLayer`). +Scenario: a reader who patches or guards only lines 794-810 still gets three errors. +Fix: cite both blocks (:794-810 and :833-849), or cite the error lines themselves. diff --git a/tasks/reviews/C00.4-r4.md b/tasks/reviews/C00.4-r4.md new file mode 100644 index 0000000..fa3ec6d --- /dev/null +++ b/tasks/reviews/C00.4-r4.md @@ -0,0 +1,5 @@ +FAIL + +scripts/build-libetpan.sh:11 — R3 的无 TLS 缓存问题仍可复现。缓存命中只检查 PIN 和 libetpan.a,直接跳过新增的 OpenSSL 与配置检查。实测在隔离目录用锁定源码构建 `--without-openssl --with-json` 的静态库,保留旧脚本会写入的 PIN 和 BUILD-config.h;随后使用正常 OpenSSL 连续执行两次当前构建脚本,均退出 0 并打印 `ok libetpan`,两次执行测试均退出 1,报 `libetpan built without TLS`。因此升级脚本后,R3 已产生的坏缓存仍会永久复用。修复方向:缓存命中前核对 BUILD-config.h 中 HAVE_OPENSSL、HAVE_JSON 均为 1;缺失或不符时使缓存失效并重新构建,加入旧坏缓存的恢复测试。 + +tests/engines/libetpan.sh:14 — 构建失败的具体诊断被重定向进临时 build.log,退出时第 8 行的 trap 随即删除该文件。实测在无缓存的隔离目录执行 `OPENSSL_DIR=/nonexistent tests/engines/libetpan.sh`,只收到 `FAIL: build failed, see .../build.log`,该路径在命令返回后已不存在;直接运行构建脚本才可看到 `no OpenSSL headers under /nonexistent`。新增前置检查失败以及 configure、编译失败时,指定的复核入口都会丢失错误原因,违反出错即暴露的规则。修复方向:失败时先将构建日志输出到 stderr,再清理临时目录;增加失败路径测试,断言原始诊断可见。 diff --git a/tasks/reviews/C00.4-r5.md b/tasks/reviews/C00.4-r5.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C00.4-r5.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C01.1-r1.md b/tasks/reviews/C01.1-r1.md new file mode 100644 index 0000000..4bb3740 --- /dev/null +++ b/tasks/reviews/C01.1-r1.md @@ -0,0 +1,28 @@ +FAIL + +Checked: `cargo check --workspace`, `cargo clippy --workspace --all-targets` (clean), `cargo test --workspace` (3 passed), `cargo fmt --check` (clean), `tests/scripts/apps-start.sh` (8 of 8 pass), `tests/scripts/agents_md.py` (pass). Every file is under 500 lines and every comment is within limits. All eight app manifests match and inherit the AGPL license from the workspace. + +1. crates/ely-platform/src/logging.rs:10 (and Cargo.toml:21,25, crates/ely-platform/Cargo.toml:12-13) + What is wrong: logging is built on `log` + `env_logger`. agents.md:90 (rule 4) requires `tracing`. + Failing scenario: a later task follows rule 4 and writes `tracing::info!` or a `tracing` span on a critical path, such as a LOK worker start. No `tracing` subscriber is installed, so the record is dropped without any error. It reaches neither stderr nor `ely-.log`. + Direction: install a `tracing-subscriber` filtered by `ELY_LOG` that writes to the same file and stderr, and bridge gpui's `log` records into it (`tracing-log`). The other option is to change rule 4 in agents.md to `log`. Code and rule must agree. + +2. crates/ely-ui/src/shell.rs:25 + What is wrong: the outer `div()` exists only to carry `debug_selector("shell")`. That call does nothing unless gpui is built with `test`/`test-support` (gpui div.rs:887-894), and nothing in the repo queries "shell". The result is dead code plus a box inside a box around the `FocusScope` root. + Failing scenario: a release or normal debug build gets an extra full-size layout node that does nothing. Searching `crates apps tests scripts` for "shell" turns up no reader of the selector. + Direction: remove the wrapper and the selector, and return the `FocusScope` root directly. + +3. crates/ely-ui/src/shell.rs:34 + What is wrong: the only content is an `h1` with the app name, which already appears in the window title (run.rs:28). agents.md rule 7 forbids useless titles and text. + Failing scenario: opening ElyDoc shows "ElyDoc" in the title bar and again as a large heading in the middle of the window. The heading adds no information. + Direction: drop the heading and leave the themed content area empty until a later task gives it real content. + +4. Cargo.toml:28-29 + What is wrong: `unexpected_cfgs` declares `check-cfg` for `feature = "cargo-clippy"`, but no crate in the workspace uses `cfg(feature = "cargo-clippy")`. This is dead configuration. + Failing scenario: removing the two lines leaves `cargo check` and `cargo clippy` output unchanged. + Direction: delete the `[workspace.lints.rust]` entry. + +5. tests/scripts/apps-start.sh:17 + What is wrong: the script sandboxes only `HOME`. `directories::BaseDirs::data_local_dir` uses `$XDG_DATA_HOME` first on Linux, which PRD §4.1 lists as a target. + Failing scenario: on a Linux machine or CI runner with `XDG_DATA_HOME` set, the eight apps append to the developer's real `$XDG_DATA_HOME/ElyOffice/logs`. `find "$work"` at line 20 finds nothing, so the script reports "wrote no log file" even though logging works. + Direction: point `XDG_DATA_HOME` at the temp dir (or unset it) for each app run, as is done for `HOME`. diff --git a/tasks/reviews/C01.1-r2.md b/tasks/reviews/C01.1-r2.md new file mode 100644 index 0000000..cfe825b --- /dev/null +++ b/tasks/reviews/C01.1-r2.md @@ -0,0 +1,13 @@ +PASS + +Checks run: `cargo check --workspace` (clean), `cargo clippy --workspace --all-targets` (clean), `cargo fmt --check` (clean), `cargo test --workspace` (3 passed), `tests/scripts/apps-start.sh` (8 of 8 open a window, draw, and quit), `tests/scripts/agents_md.py` (117 names are public). Every file is under 500 lines, and every comment fits on one line. + +The five round-1 defects: + +1. Fixed. Logging still uses `log` + `env_logger`. agents.md rule 4 now says `log`, matching gpui. Neither the code nor the rules mention `tracing` anywhere. +2. Fixed. crates/ely-ui/src/shell.rs:20 returns the `FocusScope` root directly. The wrapper and `debug_selector` are gone. +3. Fixed. The repeated `h1` app name is removed. The window shows only the themed content area. +4. Fixed. The `[workspace.lints.rust]` `check-cfg` entry is removed from Cargo.toml. +5. Fixed. tests/scripts/apps-start.sh:17 sets `XDG_DATA_HOME` to a path inside the temp dir. + +The changed code (Cargo.toml, agents.md, crates/ely-ui/src/run.rs, crates/ely-ui/src/shell.rs, tests/scripts/apps-start.sh) has no new defects. Showing a reason when the graphics backend fails to start (PRD §4.1) belongs to C01.2, so it is out of scope here. diff --git a/tasks/reviews/C01.2a-r1.md b/tasks/reviews/C01.2a-r1.md new file mode 100644 index 0000000..49c53e3 --- /dev/null +++ b/tasks/reviews/C01.2a-r1.md @@ -0,0 +1,7 @@ +FAIL + +Checks run: `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo fmt --check` (all clean); `cargo test --workspace` (5 passed); `tests/scripts/bundle-macos.sh`, `desktop-files.sh`, `installer-windows.sh`, `apps-start.sh` (all pass, none skipped). Release bundle `packaging/macos/bundle.sh ely-pdf` built, verified with `codesign --verify --deep --strict`, and printed `ready ElyPDF` under `ELY_SMOKE=1`. `otool -L` shows only system libraries, and the binary's minos is 11.0, below the plist's 14.0. Both appimagetool 1.9.1 SHA-256 values in appimage.sh match the downloaded release assets. Every file is under 500 lines, and every comment fits on one line in ten words or fewer. + +1. packaging/windows/installer.nsi:41 — The installer ships only `${APP_SLUG}.exe`. The repo has no `.cargo/config.toml` setting `+crt-static`, so a `x86_64-pc-windows-msvc` build links `VCRUNTIME140.dll` dynamically. That DLL is not part of Windows 11. This breaks PRD §4.2: "独立安装包自带所需运行库". Failing scenario: install on a clean Windows 11 machine (for example Windows Sandbox) and start the app. Windows reports "VCRUNTIME140.dll was not found" and the app never opens. Fix: link the CRT statically for the windows-msvc target (`-C target-feature=+crt-static` in `.cargo/config.toml`), or have the installer carry the VC runtime DLLs next to the exe. Add a check that the shipped exe imports no `VCRUNTIME*.dll`. + +2. packaging/linux/appimage.sh:59-65 — The AppDir holds only the binary, the desktop file and the icon. No shared library is bundled. gpui_linux links `xkbcommon` with the `x11` and `wayland` features, so the binary needs `libxkbcommon.so.0` and `libxkbcommon-x11.so.0` at load time. Neither is on the AppImage excludelist of libraries the host is expected to provide. This breaks PRD §4.2: "每个应用也有独立发行包,并附带其所需运行库". Failing scenario: on the Arch/Omarchy test snapshot (§4.1), where `libxkbcommon-x11` is a separate package that is not installed, `ElyDoc-x86_64.AppImage` exits with "error while loading shared libraries: libxkbcommon-x11.so.0". Fix: deploy the binary's non-excluded dependencies into `usr/lib` of the AppDir (linuxdeploy, or ldd filtered by the excludelist) with an `$ORIGIN` rpath. Fail the build if a needed library cannot be resolved. diff --git a/tasks/reviews/C01.2a-r2.md b/tasks/reviews/C01.2a-r2.md new file mode 100644 index 0000000..e7ff551 --- /dev/null +++ b/tasks/reviews/C01.2a-r2.md @@ -0,0 +1,16 @@ +FAIL + +Checks run: `cargo check --workspace`, `cargo clippy --workspace --all-targets` and `cargo fmt --check` are all clean. `cargo test --workspace` passes 5 tests. `tests/scripts/bundle-macos.sh`, `desktop-files.sh`, `installer-windows.sh`, `appimage-pins.sh` and `apps-start.sh` all pass, and none is skipped. Every file is under 500 lines. + +Round 1 defects: + +1. installer.nsi, VCRUNTIME140.dll: fixed. `.cargo/config.toml` sets `+crt-static` for `cfg(all(windows, target_env = "msvc"))`. I ran `cargo check --target x86_64-pc-windows-msvc -p ely-platform -v`, and every target `rustc` call it prints carries `-C target-feature=+crt-static`. gpui_windows loads no other non-system DLL at link time. `nvapi`/`amd_ags` are optional `LoadLibrary` calls, and `D3DCompileFromFile` exists only under `debug_assertions`. +2. appimage.sh, missing shared libraries: fixed. linuxdeploy 1-alpha-20251107-1 is pinned for both architectures, and both hashes match the downloaded assets (`appimage-pins.sh`). Its source (`src/core/elf_file.cpp:244-250`, `src/core/appdir.cpp:372-374`) throws `DependencyNotFoundError` on an ldd "not found" line and fails the deploy, so the comment at line 78 is accurate. + +New defects: + +1. packaging/linux/appimage.sh:70-76 — Lines 72, 74, 75 and 76, and the two `usr/share` directories created at line 70, do work that linuxdeploy redoes on line 79, so their output is never used. That is dead code (rule 6). Per the linuxdeploy source: `deployDesktopFile` copies the `--desktop-file` into `usr/share/applications/`; `deployIcon` copies the `--icon-file` into `usr/share/icons/hicolor//apps/`; `symlinkFile` removes the root `.desktop`/`.png` and replaces them with symlinks; and the `AppRun` symlink is created the same way when none exists. The hand-made copy at line 74 is also wrong whenever the icon is not 256×256. Failing scenario: `assets/icons/ely-doc.png` is delivered at 512×512. Line 74 puts it under `hicolor/256x256/apps`, and linuxdeploy adds a second copy under `hicolor/512x512/apps`. The AppImage then ships a 512 px image declared as 256 px, and desktop integration installs that copy into the user's 256×256 icon theme directory. Fix: keep only the desktop entry that linuxdeploy reads (line 73, or write it to `$work`). Delete the other manual copies and the `AppRun` link, and let linuxdeploy place them. + +2. tests/scripts/installer-windows.sh:2 — The comment has eleven words ("The Windows installer builds, keeps user data, needs no runtime DLL."). Rule 5 caps comments at ten words. Failing scenario: `review.sh` and the rule-5 check reject the file as written. Fix: shorten it to ten words or fewer. + +3. tests/scripts/installer-windows.sh:32 — The check that the C runtime is linked statically is a plain substring grep over the whole file. It cannot tell whether the flag applies to the msvc target. Failing scenario: someone comments the line out (`# rustflags = ["-C", "target-feature=+crt-static"]`) or moves it under `[target.x86_64-pc-windows-gnu]`. The test still prints "needs no runtime DLL", while an msvc build again imports `VCRUNTIME140.dll` and fails to start on a clean Windows 11 machine. Fix: parse `.cargo/config.toml` (for example with `python3 -c 'import tomllib…'`) and assert that `target."cfg(all(windows, target_env = \"msvc\"))".rustflags` contains `target-feature=+crt-static`. An alternative is to assert that the flag appears in `cargo check --target x86_64-pc-windows-msvc -v` output. diff --git a/tasks/reviews/C01.2a-r3.md b/tasks/reviews/C01.2a-r3.md new file mode 100644 index 0000000..88b636a --- /dev/null +++ b/tasks/reviews/C01.2a-r3.md @@ -0,0 +1,15 @@ +FAIL + +Checks run: `cargo check --workspace`, `cargo clippy --workspace --all-targets` and `cargo fmt --check` are all clean, and `cargo test --workspace` passes 5 tests. `tests/scripts/bundle-macos.sh`, `desktop-files.sh`, `installer-windows.sh`, `appimage-pins.sh` and `apps-start.sh` all pass, and none is skipped. Every file is under 500 lines. + +Round 2 defects: + +1. appimage.sh, the hand-made AppDir copies: fixed. Only the desktop entry is written, into `$work`. linuxdeploy places the binary, the desktop file, the icon and `AppRun`. +2. installer-windows.sh:2, the comment length: fixed. It is now ten words. +3. installer-windows.sh, the substring grep for crt-static: fixed. The test now reads the effective cfg for the msvc target. I checked that it can fail: `RUSTFLAGS="" cargo rustc -p ely-platform --lib --target x86_64-pc-windows-msvc -- --print cfg` drops `target_feature="crt-static"`, so grep exits 1. Without the override, the line is present. + +New defects: + +1. packaging/linux/appimage.sh:76 — appimagetool is called without `--runtime-file`. In appimagetool 1.9.1 (`src/appimagetool.c`, `src/appimagetool_fetch_runtime.cpp`), a missing runtime file makes it download `https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-$arch` at build time. That URL is a moving tag, and the tool checks only HTTP 200 and the content length, with no hash. This runtime becomes the executable header of every AppImage we ship. It is the one binary the recipe pulls without a pin, and `tests/scripts/appimage-pins.sh:2` ("Every AppImage tool the recipe downloads matches its pinned hash") states the opposite, with no evidence. Failing scenario: upstream pushes a new `continuous` runtime, whether broken or compromised. The next `appimage.sh ely-doc` embeds it silently, so two builds of the same commit produce different AppImages, and nothing fails. Fix: pin a dated type2-runtime release per architecture and check it with `fetch_tool` against a sha256. Pass it with `--runtime-file`. Add the runtime URLs to `appimage-pins.sh`. + +2. tests/scripts/installer-windows.sh:33-34 — `cfg=$(... cargo rustc ... 2>&1)` drops cargo's exit status. Any cargo failure then falls through to the grep and is reported as "the msvc build links the C runtime dynamically". Cargo's real error is swallowed and a wrong cause is shown (rule 2). Failing scenario: a dependency added later fails to compile for `x86_64-pc-windows-msvc`, or the registry cannot be reached. The test fails with the crt-static message, and the developer hunts in `.cargo/config.toml` for a problem that is not there. Fix: make a non-zero cargo exit fail the test with cargo's own output (for example `cfg=$(...) || fail "cargo rustc for msvc failed: $cfg"`), and grep only after it succeeds. diff --git a/tasks/reviews/C01.2a-r4.md b/tasks/reviews/C01.2a-r4.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C01.2a-r4.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C01.2b-r1.md b/tasks/reviews/C01.2b-r1.md new file mode 100644 index 0000000..142d6f7 --- /dev/null +++ b/tasks/reviews/C01.2b-r1.md @@ -0,0 +1,27 @@ +FAIL + +Ran `cargo test -p ely-platform` (all pass) and `cargo clippy -p ely-platform --all-targets` (clean). Probes A–C ran against a local server in a scratch crate that depends on `ely-platform`. + +1. crates/ely-platform/src/update.rs:221-230: The rule "https only, loopback excepted" can be bypassed with userinfo. The code checks the URL by string prefix, and `"http://127.0.0.1:" + anything` passes. ureq treats `127.0.0.1:1@` as userinfo and connects to the host after `@`. + Scenario (probe B): `check(.., "http://127.0.0.1:1@192.168.31.221:PORT/latest.json")` returned `Ok(Available(..))`. The server logged `host: 192.168.31.221`, so the manifest came over plain http from a non-loopback host. Whoever controls the network then picks the URL and the sha256, and the downloaded file passes verification. `plain_http_is_for_this_machine_only` does not cover this case. + Fix: parse the URL with `http::Uri` or `ureq::http::Uri`, reject userinfo, and decide by scheme plus the parsed host. Add the userinfo case to the test. + +2. crates/ely-platform/src/update.rs:77, 232-246: `ensure_secure` checks only the first URL. Both agents use ureq's defaults (`max_redirects` 10, `https_only` false), so redirects to `http://` are followed for the manifest and for the download. + Scenario (probe A): `http://127.0.0.1:P/redirect` answered 302 to `http://192.168.31.221:P/latest.json`. `check` followed it and returned `Available`. An `https://` manifest URL that redirects to `http://` is followed the same way, so the manifest arrives over plain http. + Fix: check every hop. Either turn redirects off (`max_redirects(0)`) and follow `Location` manually through `ensure_secure`, or use `https_only(true)` for non-loopback URLs. Add a test where a redirect leaves loopback. + +3. crates/ely-platform/src/update.rs:175-180, 239-246: The comment "the user can cancel" is not true. The download agent has no read timeout, and the cancel flag is checked only between `read` calls. A stalled connection blocks `read` forever: cancel does nothing, and no error is ever returned. + Scenario (probe C): the server sent the headers and 1000 of 1000000 bytes, then went silent. I set `cancel` to true 1 s in. `download` was still blocked 19 s later. In the field, a dropped Wi-Fi link or a hung CDN leaves the download hung for good. + Fix: put an idle-read timeout on the socket (for example a custom connector or transport with `set_read_timeout`) so that `read` returns, `cancel` is honoured, and a stall becomes an error. Add a test with a stalled server. + +4. crates/ely-platform/src/update.rs:62, 66-73: `APPIMAGE` is an inherited environment variable, and it overrides the package-manager prefixes. A distro-installed binary launched from inside any AppImage is treated as `SelfManaged`. + Scenario: the user starts a terminal emulator or editor that ships as an AppImage, then runs `/usr/bin/ely-doc` (installed by pacman) from its terminal. `APPIMAGE` is set, so `install_policy()` returns `SelfManaged` and the app would update itself over files the package manager owns. This breaks PRD §4.2 ("Linux 包管理器安装的版本不绕过包管理器自行覆盖文件"). + Fix: count it as an AppImage only when `current_exe` lies under `$APPDIR` (the mount of this AppImage), not just because `APPIMAGE` exists. Cover the inherited-variable case in the unit test. + +5. crates/ely-platform/src/update.rs:142-147: The download's own error is lost when removing the `.part` file fails, because the `?` on `remove_file` returns the removal error instead. + Scenario: the hash does not match, and on Windows an antivirus scanner still holds `ElyDoc.exe.part`. The caller sees "cannot remove …part", and "the file has sha256 X, the release says Y" never reaches the user. + Fix: keep the original error and attach the removal failure to it, or log the removal failure and still return the original error. + +6. crates/ely-platform/src/update.rs:76-102, 138-141: Rule 4 (key paths are logged) is not met. `check` makes a network request and logs neither the request nor the outcome (current, available, or which failure). A cancelled download is not logged either. Only a successful download logs anything. + Scenario: QA-25 asks for an audit of outgoing update requests, and a user reports "check for updates does nothing". The log has no line showing a check was made or what it returned. + Fix: log the manifest host and the result in `check`, and log a cancelled download (version only, no secrets). diff --git a/tasks/reviews/C01.2b-r2.md b/tasks/reviews/C01.2b-r2.md new file mode 100644 index 0000000..3694911 --- /dev/null +++ b/tasks/reviews/C01.2b-r2.md @@ -0,0 +1,17 @@ +FAIL + +Ran `cargo test -p ely-platform` (11 + unit tests pass) and `cargo clippy -p ely-platform --all-targets` (clean). The probe ran in a scratch crate that depends on `ely-platform` and talks to a local server. + +Round 1 defects: +1. Userinfo bypass: fixed. `secure_host` parses with `Uri`, refuses `@` in the authority, and decides by parsed scheme and host. Covered by unit and integration tests. +2. Redirects not checked: fixed. Both agents use `max_redirects(0)`, and `fetch` runs every hop through `secure_host`. Covered by `a_redirect_is_followed_on_this_machine_and_refused_when_it_leaves_for_plain_http`. +3. Stall blocks cancel forever: the caller-facing part is fixed (cancel and the idle error both return). The fix leaves the blocked read behind, see new defect 1. +4. Inherited `APPIMAGE`: fixed. The policy now depends only on the executable path. +5. Download error lost on cleanup failure: fixed, with a test that makes the cleanup fail. +6. Missing logs: fixed. `check` logs host and outcome, and `download` logs cancel and failure. Covered by a test. + +New defects: + +1. crates/ely-platform/src/update.rs:182-200, 208-228: A stalled or cancelled download leaks its reader thread and its socket. The socket still has no read timeout. `stream` stops waiting on the channel, but the spawned thread stays blocked in `reader.read` until the peer closes or sends data. A half-open connection (Wi-Fi dropped, CDN hung, no TCP keepalive) never does either, so the thread, the 64 KiB buffer and the connection stay alive for as long as the process runs. + Scenario (probe): a local server sends the headers and 1000 of 1000000 bytes, then keeps the connection open and silent. Ten `download` calls with `idle` = 200 ms each returned "the download stalled" as expected. 3 s later the process had 13 threads instead of 3, and 20 more TCP descriptors than before (10 client, 10 server). Each "retry" after a stall adds one more. The current test can't see this because its server closes after 60 s and the test process exits first. + Fix: put the idle timeout on the socket itself, for example a ureq `Transport`/connector that sets `set_read_timeout(idle)` on the `TcpStream`. Then `read` returns, the thread (or the plain loop that can replace it) ends, and the connection drops. Add a test that keeps the connection open, then asserts that the thread or connection is gone after the stall error, for example that the server sees EOF or reset. diff --git a/tasks/reviews/C01.2b-r3.md b/tasks/reviews/C01.2b-r3.md new file mode 100644 index 0000000..0f311d7 --- /dev/null +++ b/tasks/reviews/C01.2b-r3.md @@ -0,0 +1,8 @@ +PASS + +Ran `cargo test -p ely-platform` (12 integration tests and the unit tests pass; the integration suite passed 5 runs in a row), `cargo clippy -p ely-platform --all-targets` (clean), `cargo fmt -p ely-platform --check` (clean), `cargo check --workspace` (ok), `tests/scripts/comments.py` and `tests/scripts/agents_md.py` (both pass). + +Round 2 defect: +1. Stalled or cancelled download leaks its reader thread and socket: fixed. `IdleConnector`/`Idle` caps every transport wait at `idle`, so ureq's `TcpTransport` sets a socket read timeout. The blocked `read` returns, the thread ends, and the connection drops. Probe (scratch crate, same silent server as round 2): ten stalls at `idle` = 200 ms each failed with "the download stalled" in about 204 ms. 2 s later the process had 2 threads and 1 TCP descriptor, the same as before the stalls. Five instant cancels returned `None` in about 1 ms each. Right after them there were 12 threads and 11 TCP descriptors, and 2 s later they were back to 2 and 1. `a_stalled_download_fails_and_frees_its_socket` and `a_cancel_is_answered_at_once_and_the_socket_is_freed_within_the_idle_time` check that the server sees EOF or reset. + +No new defects found in the changed code (`update.rs` idle transport, the stall and cancel tests, and the `///`/`//!` handling in `comments.py`). diff --git a/tasks/reviews/C01.2c-r1.md b/tasks/reviews/C01.2c-r1.md new file mode 100644 index 0000000..5897957 --- /dev/null +++ b/tasks/reviews/C01.2c-r1.md @@ -0,0 +1,11 @@ +FAIL + +Ran: `cargo test -p ely-platform` (all pass, including `tests/startup.rs`), `cargo clippy -p ely-platform -p ely-ui --all-targets` (clean), `ELY_NO_DIALOG=1 tests/scripts/apps-start.sh` (8 of 8). Also built a scratch probe against the pinned gpui 1a28cff with `runtime_shaders`, the same features the apps use: `catch_unwind(|| gpui_platform::application().run(|_| panic!(..)))`. + +1. crates/ely-ui/src/run.rs:22-35 — on macOS the `catch_unwind` never catches a startup panic. gpui runs the `run` callback from `did_finish_launching`, an `extern "C" fn` (gpui_macos/src/platform.rs:1317). A panic there cannot unwind, so the process aborts. Scenario: the probe printed `panic in a function that cannot unwind … thread caused non-unwinding panic. aborting.` and exited 134, without printing `caught`. So `open`, `ely_gpui_component::init`, `Theme::set_mode_now`, `Shell::new` and Metal renderer creation (`expect("error building metal library")`, gpui_apple/src/metal_renderer.rs:219-224) all end in an abort with no dialog and no "could not start" text. Windows behaves the same way inside `extern "system" window_procedure`. Fix direction: install the failure path in a panic hook (`logging::init_in` already sets one) so it runs before the abort, or have it call `startup::fail` from the hook during startup. Do not rely on unwinding out of `run`. + +2. crates/ely-ui/src/run.rs:57-68 — the "window did not open" branch cannot catch the case §4.1 is about on macOS. `MacPlatform::open_window` returns `Err` only for `AnchoredPopup` (gpui_macos/src/platform.rs:693). The renderer is built without returning a `Result` (gpui_macos/src/window.rs:1101). When no Metal device is found, gpui logs and calls `std::process::exit(1)` (gpui_apple/src/metal_renderer.rs:205-208). Scenario: on a Mac or VM without a usable Metal device, ElyDoc exits 1 with only gpui's log line. No dialog appears and `startup::fail` never runs, which breaks "无法初始化图形后端时显示原因". Fix direction: check for a Metal device yourself before `run` (macOS), or do the equivalent wgpu adapter probe (Linux/Windows), and call `startup::fail` with the reason. Alternatively, patch gpui so renderer creation returns `Err` through `open_window`, and record it in `patches/`. + +3. crates/ely-ui/src/run.rs:22-35 — on Linux, where the event loop is Rust and panics do unwind, `catch_unwind` covers the app's whole lifetime, not just startup. Scenario: a panic an hour into editing unwinds out of `run`, and the user sees "ElyDoc could not start … the graphics backend did not start: ". That is a false reason in the log and the dialog (rule 2: no masking or mislabeling). Fix direction: limit this path to startup. For example, set a "started" flag after the first window opens and stop treating later panics as startup failures. The real panic text should go through the normal panic path. + +4. tasks/TASKS.md:21 — "本机验证:…窗口打不开与启动期 panic 都走这条路" has no evidence. No test or script makes a window fail or makes a startup panic go through `run`. `tests/startup.rs` calls `startup::fail` directly, and `apps-start.sh` covers only the success path. Findings 1 and 2 show the claim is false on macOS, the only machine it was "verified" on. Fix direction: add a test or smoke hook that triggers a startup panic and a window-open failure through `ely_ui::run` (for example an env-gated fault in a test binary) and asserts exit 1, the stderr text and the log line. Until that passes, move these two paths under 未验证. diff --git a/tasks/reviews/C01.2c-r2.md b/tasks/reviews/C01.2c-r2.md new file mode 100644 index 0000000..eb8b974 --- /dev/null +++ b/tasks/reviews/C01.2c-r2.md @@ -0,0 +1,14 @@ +FAIL + +Ran: `cargo test -p ely-platform -p ely-ui` (all pass, including `tests/startup.rs` and the harness-less `tests/startup_panic.rs`: "2 cases pass"), `cargo clippy -p ely-platform -p ely-ui --all-targets` (clean). I also ran the `startup_panic` "launch" child without `ELY_NO_DIALOG`. A system alert came up that named ElyDoc, the reason and a log path, and it blocked until the process was killed. Read gpui 1a28cff `gpui_apple/src/metal_renderer.rs:190-210`: `graphics::check` uses the same `Device::all()` / `system_default()` test that gpui uses before its `process::exit(1)`. + +Round 1: + +1. Startup panic aborting on macOS: fixed. `startup::guard` (startup.rs:36-48) calls `fail` from the panic hook. That runs before the non-unwinding abort. `startup_panic.rs` "launch" proves it inside gpui's launch callback: exit 1, stderr text and log line. +2. Metal device missing, so gpui exits silently: fixed. `graphics::check` runs before `run` (run.rs:17-19), and its probe matches gpui's own. The failure branch is honestly listed under 未验证. +3. Late panics labelled as a failed start: fixed. The `STARTED` flag is set on the first frame (run.rs:48). `startup_panic.rs` "late" shows a later panic exits 101 with the ordinary panic text and no "could not start". +4. Unbacked claims in TASKS: fixed. The window-open branch and the probe-failure branch moved to 未验证. The panic path has a test. + +New: + +1. crates/ely-platform/src/startup.rs:19-22 — `fail` names a log file it has not checked exists. It rebuilds the path from `data_dir(app)` and does not ask where logging actually writes, or whether logging started at all. Scenario A: `~/Library/Application Support/ElyOffice/ely-doc` is not writable. `logging::init` fails and run.rs:15 calls `fail`. The dialog and stderr then say "日志 / Log: …/ely-doc/ely.log", but that file was never created, and the `log::error!` on line 18 goes nowhere because no logger is set. The user is sent to a log that does not exist (rule 2: no default standing in for missing data). Scenario B, seen in this review: the `startup_panic` child logs to its temp dir, yet the dialog points at the real user data dir. Neither test checks the path, so this went unnoticed. Fix direction: have logging record the path it actually opened (for example `init` returns it or stores it), and have `fail` print that path. When logging did not start, say there is no log. Add an assertion on the printed log path to `tests/startup.rs`. diff --git a/tasks/reviews/C01.2c-r3.md b/tasks/reviews/C01.2c-r3.md new file mode 100644 index 0000000..0921bc4 --- /dev/null +++ b/tasks/reviews/C01.2c-r3.md @@ -0,0 +1,9 @@ +PASS + +Ran: `cargo test -p ely-platform -p ely-ui`: all pass, including `tests/startup.rs` (2), `tests/logging.rs` and the harness-less `startup_panic` ("2 cases pass"). `cargo clippy -p ely-platform -p ely-ui --all-targets`: clean. `ELY_SMOKE=1 ELY_NO_DIALOG=1 ely-doc`: "ready ElyDoc", exit 0. `ely-doc` with a read-only `HOME`: exit 1, stderr gives the real reason ("cannot create …/ElyOffice/ely-doc: Permission denied") and "日志 / Log: none, logging did not start". + +Round 2: + +1. `fail` named a log file it had not checked: fixed. `logging::init_in` stores the path it actually opened in `LOG_PATH`, and only after `try_init` succeeds (logging.rs:53). `fail` prints `logging::path()`, or says there is no log (startup.rs:19-22). `tests/startup.rs` asserts that stderr names the temp-dir log. It also asserts that without a logger the text says "none" and names no `ely.log`. I confirmed the read-only-home case by hand on the real binary. + +New: none found. Each file is under 500 lines. Each comment is one line of ten words or fewer. Every public item has a caller. The 未验证 list in TASKS.md still matches what was not exercised. diff --git a/tasks/reviews/C05.1-r1.md b/tasks/reviews/C05.1-r1.md new file mode 100644 index 0000000..1bdd779 --- /dev/null +++ b/tasks/reviews/C05.1-r1.md @@ -0,0 +1,28 @@ +FAIL + +Checked: `cargo test -p ely-ui -p ely-platform` passes (ely-ui 6 unit tests, ely-platform 10 + 12 + startup_panic). `tests/scripts/theme.sh` passes on macOS. `cargo clippy --all-targets` on both crates is clean. The three fixtures match `themes/{tokyo-night,catppuccin-latte,white}/colors.toml` in basecamp/omarchy at f45461a, so the "real palettes" claim holds. Contrast numbers below come from a probe crate outside the repo that `#[path]`-includes `omarchy.rs` and calls `parse` on the fixtures. + +1. crates/ely-ui/src/theme/omarchy.rs:73 (and the test at :244) + What is wrong: `readable` and `toward_bg` lift text only against `bg`. The test also checks only against `p.bg`. The components put this text on `surface`, `sunken`, `hover`, `active` and `selection`, which are mixed toward `fg`, so the lifted colors drop below 4.5 there. Ely's own palettes stay at or above 4.5 on every one of those surfaces except `selection`. The TASKS line ("全部正文色达 4.5:1") and agents.md ("每个正文色拉到 4.5:1") claim more than the code does. + Failing scenario: catppuccin-latte gives `fg_muted` on `surface` 4.35, `fg_subtle` on `surface` 4.30 and on `active` 3.70. tokyo-night gives `fg_subtle` on `surface` 4.20 and on `active` 3.44. white gives `fg_subtle` on `sunken` 3.98. `shell::OfflineIndicator` (status.rs:128,133) draws `fg_muted` on `surface`, so under catppuccin-latte its label reads 4.35:1. + Fix direction: lift each text token against the darkest or lightest surface it is drawn on (bg, surface, sunken, hover, active), not just bg. Extend the test to assert every text token against each of those surfaces. + +2. crates/ely-ui/src/theme/omarchy.rs:75-84 + What is wrong: only the named keys (`red`, `green`, `blue`, …, `selection`) are read. Omarchy v4's own `colors.toml` format also carries `color0`..`color15`, `purple`/`bright_purple` and `selection_background`. Omarchy's resolver (`bin/omarchy-theme-color`) aliases red→color1, green→color2, yellow→color3, blue→color4, magenta→color5/purple, cyan→color6, and selection→selection_background. `omarchy-theme-colors-from-alacritty` writes only `accent`, `selection`, `background`, `foreground` and `color0`..`color15`. Every theme without its own `colors.toml` (for example a user or git-installed theme that ships `alacritty.toml`) therefore reaches Ely in that form. Each missing hue then silently becomes Ely's default through `unwrap_or(p.danger)` and the like, with no log line. That is a default value hiding missing data (rule 2), and the theme is not synced. + Failing scenario: parse a generated file (`accent`, `selection`, `background`, `foreground`, `color0`..`color7` from Tokyo Night). The probe shows `palette.danger == Palette::dark(false).danger` and `success` equal to Ely's default. Syntax `keyword` comes from Ely's chart, not the theme's `color5`. Danger, success, warning, info, chart, ANSI and syntax colors all show Ely's palette under an Omarchy theme. + Fix direction: resolve keys with Omarchy's alias cascade (named key, then `colorN`, then `purple`/`selection_background`). Where a key is still missing, log it rather than substituting silently, and add a fixture in the generated `colorN` form. + +3. crates/ely-ui/src/theme/mod.rs:37 + What is wrong: `ELY_OMARCHY_SYNC` treats every value except `"0"` as on, without a warning. `ELY_APPEARANCE` (:32) and `ELY_REDUCE_MOTION` (accessibility.rs:20) both warn on an invalid value. This is a silent fallback on a documented 0/1 switch. + Failing scenario: `ELY_OMARCHY_SYNC=false` or `off` leaves Omarchy sync on, and the log does not say the value was ignored. + Fix direction: match `"0"`, `"1"` and unset explicitly, and warn on anything else, as the other two switches do. + +4. crates/ely-ui/src/theme/watch.rs:40 + What is wrong: when the live theme directory has no `colors.toml`, `reload` logs at debug and keeps the previous `OmarchyState`. The app keeps showing the theme the user just left. + Failing scenario: the app runs under tokyo-night, and the user runs `omarchy-theme-set` with a user theme that has no `colors.toml` and no `alacritty.toml`, so Omarchy writes none. `theme/` is swapped and `theme.name` changes, but Ely stays on the tokyo-night palette. Nothing above debug level records this. + Fix direction: on `Ok(None)` after a change, clear `OmarchyState`, re-apply (Ely's palette or the chosen appearance), and log at info. + +5. crates/ely-platform/src/dirs.rs:19-21 + What is wrong: the default Omarchy directory comes from `BaseDirs::state_dir()`, which honours `$XDG_STATE_HOME`. Omarchy hardcodes `$HOME/.local/state/omarchy/current` (`bin/omarchy-theme-set`: `CURRENT_THEME_PATH="$HOME/.local/state/omarchy/current/theme"`). + Failing scenario: on an Omarchy machine with `XDG_STATE_HOME=$HOME/.state`, Ely looks in `~/.state/omarchy/current`, logs "no Omarchy on this machine", and never syncs. + Fix direction: build the path from the home directory as `.local/state/omarchy/current` to match Omarchy, with `ELY_OMARCHY_DIR` still taking precedence. diff --git a/tasks/reviews/C05.1-r2.md b/tasks/reviews/C05.1-r2.md new file mode 100644 index 0000000..a1eb327 --- /dev/null +++ b/tasks/reviews/C05.1-r2.md @@ -0,0 +1,32 @@ +FAIL + +Checked: `cargo test -p ely-ui -p ely-platform` passes (ely-ui 10 omarchy tests plus startup_panic, ely-platform 12 + 10 + 2 + 1). `cargo clippy -p ely-ui -p ely-platform --all-targets` is clean. `tests/scripts/theme.sh` passes on macOS, including the live swap, the empty theme and the return. `tests/scripts/references.sh`, `agents_md.py` and `comments.py` pass. All 22 fixtures are byte-identical to `reference/omarchy/themes/*/colors.toml` at f45461a. `accessibility.rs` and `env.rs`, built alone in a scratch crate, `cargo check` cleanly for x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc. That backs the cross-compile claim; `cargo check -p ely-platform --target …` itself cannot run here because ring has no C cross compiler. Contrast figures below are WCAG ratios worked out from the fixture hex values, where `danger`, `warning`, `success` and `info` equal the raw `red`, `yellow`, `green` and `blue`. + +Round 1: +1. Fixed. `palette.rs:28-48` lifts against bg, surface, overlay, sunken, hover and active. `tests.rs:65` checks every listed token on all six for all 22 themes. +2. Fixed. `colors.rs:11-27,111-133` follows Omarchy's alias cascade, the `colorN` fixture checks it, and hues that are still missing are logged in `watch.rs:38`. +3. Fixed. `env::flag` warns on any value other than 0 or 1, and `theme.sh:64` covers `ELY_OMARCHY_SYNC=false`. +4. Fixed. `watch.rs:47-51` clears the state, re-applies and logs at info. `theme.sh:99-100` checks this live. +5. Fixed. `dirs.rs:21-27` builds the path from home and ignores `XDG_STATE_HOME`. + +New defects: + +1. crates/ely-ui/src/theme/omarchy/palette.rs:102 (and tests.rs:42) + What is wrong: `success`, `warning`, `danger` and `info` are the theme's raw hues and are never lifted. The component library draws text in them: `overlays::PromptDialog`'s validation reason (dialogs.rs:299), the required-field star (forms/structure.rs:156), git badges, the merge marker and others. Ely's own palettes keep all four at or above 4.5 on bg (light 4.82–5.33, dark 6.37–9.97). The test's "every text token" list leaves them out. So the TASKS line ("全部正文色在六种表面上达 4.5:1") and agents.md ("每个正文色…都拉到 4.5:1") claim more than the code does. + Failing scenario: 11 of the 22 bundled themes put a status hue under 4.5:1 on bg alone. Some examples: nord red 3.05, solitude red 2.78, miasma red 2.30, catppuccin-latte yellow 2.31 and green 2.96, rose-pine yellow 2.05, matte-black yellow 2.90. Under nord, a PromptDialog error reads at 3.05:1 on the plain background and lower on the dialog's surface. + Fix direction: lift each status color for text the way the syntax colors are lifted, or keep the hue for fills and add a lifted text variant if the component library allows it. Add the four tokens to `text_on_surfaces`. If that is not done, narrow the claim in TASKS.md and agents.md. + +2. crates/ely-ui/src/run.rs:33-37 + What is wrong: `ELY_SMOKE` treats every value except `hold` as one-frame smoke and treats an unset or non-UTF-8 value as off. Neither case gives a warning. That is the same silent fallback round 1 flagged for `ELY_OMARCHY_SYNC`. The new `hold` mode is also missing from agents.md:111, which documents only `ELY_SMOKE=1`. + Failing scenario: `ELY_SMOKE=0 cargo run -p ely-doc` opens a window, draws one frame and quits, and the log does not say why. + Fix direction: match `"1"`, `"hold"` and unset explicitly, warn on anything else (through `env::value`), and document `hold` in agents.md. + +3. crates/ely-platform/src/accessibility.rs:11-13 + What is wrong: when the OS gives no answer, it logs "using full motion" before it looks at `ELY_REDUCE_MOTION`. The log then contradicts the decision that follows. + Failing scenario: on Linux without gsettings, with `ELY_REDUCE_MOTION=1`, the log reads "the OS gives no reduce-motion answer, using full motion" and then "theme: reduced motion on". + Fix direction: log the fallback only when both the environment variable and the OS give no answer, or log the source the decision came from. + +4. crates/ely-platform/src/accessibility.rs:4-6 + What is wrong: `pub fn system_reduce_motion` only forwards to `os_reduce_motion`. Its sole caller is `reduce_motion` in the same file, so it is a public layer with one user (rule 8). + Failing scenario: no behavior breaks. It is an extra public entry point with no caller outside the file. + Fix direction: call `os_reduce_motion` from `reduce_motion` and delete the wrapper. diff --git a/tasks/reviews/C05.1-r3.md b/tasks/reviews/C05.1-r3.md new file mode 100644 index 0000000..b1e88b2 --- /dev/null +++ b/tasks/reviews/C05.1-r3.md @@ -0,0 +1,11 @@ +PASS + +Checked: `cargo test -p ely-ui -p ely-platform` passes (ely-ui 11 unit tests including `run::tests` and startup_panic, ely-platform 12 update tests and the rest). `cargo clippy -p ely-ui -p ely-platform --all-targets` is clean. `tests/scripts/theme.sh` passes on macOS, including the live swap, the empty theme and the return. `references.sh`, `agents_md.py` and `comments.py` pass. All 22 fixtures are byte-identical to `reference/omarchy/themes/*/colors.toml` at f45461a. I built `env.rs` and `accessibility.rs` alone in a scratch crate with the project toolchain (1.96.0), and `cargo check` passes for x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc, so the cross-compile claim still holds after the wrapper was removed. Every file is under 500 lines. + +Round 2: +1. Fixed. `palette.rs:130-133` lifts `success`, `warning`, `danger` and `info` against bg and their `*_subtle`. `TEXT_PAIRS` (`palette.rs:32-35`) and `verify` check them. These pairs match the library's `tooling/a11y.rs:35-38` exactly. `tests.rs:73-80` asserts all four on all 22 themes, and `tests.rs:123-130` checks that a red which already reads is kept. The claims in TASKS.md ("正文与状态色 4.5:1") and agents.md ("各自的表面") now match the code. +2. Fixed. `run.rs:36-46` matches unset, `1` and `hold` explicitly and warns on anything else through `env::value`. `run.rs:93-99` tests this, and agents.md:112 documents `hold`. +3. Fixed. `accessibility.rs:6-8` logs the full-motion fallback only when neither the variable nor the OS gives an answer. +4. Fixed. The wrapper is gone, and `reduce_motion` calls `os_reduce_motion` directly. + +New defects: none found in the changed code. diff --git a/tasks/reviews/C05.2-r1.md b/tasks/reviews/C05.2-r1.md new file mode 100644 index 0000000..8a39d59 --- /dev/null +++ b/tasks/reviews/C05.2-r1.md @@ -0,0 +1,7 @@ +FAIL + +1. `crates/ely-ui/src/shell/mod.rs:95`:页面键作为整个内容区动画祖先的 ID,切页会重建持续显示控件的元素状态,违反 agents.md 的动画与焦点规则。已用公开 Shell API 在临时副本构建并运行探针:内容区始终显示同一个 `Button::new("next-page", ...)`,按钮回调增加页码并通知重绘,`Shell::page(("page", page))` 跟随页码。按 Tab 聚焦,再连续按两次 Return,实际只到第 1 页;第一次转场后焦点环消失,日志出现 `focus: its element left the tree; the root takes it`。保持动画键的对照组可连续触发同一按钮。修复方向:保持内容树及控件的祖先 ID 稳定,用独立转场状态记录页面键变化和起始时间,并补充持续显示控件的键盘焦点回归测试。 + +2. `tests/scripts/shell.sh:14`:测试仅检查启动器输出 `ready ElyDoc`,没有验证 C05.2 的布局或切页行为;现有 Slide 单测也只验证插值计算。已实跑原实现的工作区构建、测试、八应用启动及 shell 冒烟;随后仅在临时副本中将 `Shell::render` 传给左右 SidePanel 的视图都过滤为 None,彻底移除有内容的侧栏,重跑该脚本仍以退出码 0 输出成功。这使任务的核心布局回归无法被测试发现。修复方向:加入实际布局和交互断言,覆盖左右侧栏有/无内容、one → two → one 的展开与收起,以及转场后的焦点;让上述删除侧栏的变异实现触发失败。 + +3. `crates/ely-ui/src/shell/mod.rs:20`:新增注释违反单行、最多十词的明确规则。这里的文档注释跨两行,第 21 行有 16 词;`crates/ely-ui/src/run.rs:13` 同样跨两行,第 14 行有 12 词。另外 `crates/ely-ui/examples/shell.rs:1`、`crates/ely-ui/src/shell/blank.rs:6`、`crates/ely-ui/src/shell/panel.rs:15`、`:55`、`:61` 分别为 12、14、13、14、15 词。运行 `python3 tests/scripts/comments.py` 已返回退出码 1 并列出这些范围内的位置。修复方向:将必要注释压缩到一行且最多十词,删除冗余说明,并重新运行注释检查。 diff --git a/tasks/reviews/C05.2-r2.md b/tasks/reviews/C05.2-r2.md new file mode 100644 index 0000000..45ed6be --- /dev/null +++ b/tasks/reviews/C05.2-r2.md @@ -0,0 +1,5 @@ +FAIL + +tests/scripts/shell.sh:24:上一轮第 2 项的布局验证仍有缺口。新增探针用 `is_focused` 判断区域显隐,缺少侧栏和内容区的实际宽度断言。已在临时副本验证:删除左右侧栏的变异会使脚本失败;将 `crates/ely-ui/src/shell/panel.rs:100` 的空视图分支改为 `return div().flex_none().w(cx.theme().sidebar_width(false)).h_full().into_any_element();`,使 page two 收起属性内容后仍保留完整空栏,重跑脚本却以退出码 0 输出成功。这使 PRD §5.2“无内容的侧栏不占位”的核心回归继续漏检。修复方向:断言左右侧栏有/无内容时的实际布局边界,以及 one → two → one 转场结束后的侧栏宽度和内容区宽度,确保空栏占位变异触发失败。 + +crates/ely-ui/src/shell/mod.rs:24:上一轮第 3 项的多行注释仍保留。第 24–25 行组成两行文档注释,`crates/ely-ui/src/run.rs:13–14` 同样如此,违反 agents.md 的单行注释规则。当前 `python3 tests/scripts/comments.py` 返回退出码 0,因为它只统计每行词数,漏掉连续文档注释。修复方向:将这两处必要注释各压缩为一行且最多十词,并补充多行文档注释的检查。 diff --git a/tasks/reviews/C05.2-r3.md b/tasks/reviews/C05.2-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C05.2-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C05.3-r1.md b/tasks/reviews/C05.3-r1.md new file mode 100644 index 0000000..d5e4c2e --- /dev/null +++ b/tasks/reviews/C05.3-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-ui/src/commands.rs:51 — macOS 将唯一的 File 菜单作为首个应用菜单处理,实际界面缺少 File 菜单,违反 UX-01 的文件菜单入口要求。复现:构建并启动 ElyDoc.app,原生 accessibility 菜单栏仅有 ElyDoc;展开 ElyDoc 后才出现 New、Open…、Save、Save As…、Export…、Print…。八个应用共用此菜单定义。修复方向:macOS 在 File 前提供应用菜单,使 File 成为独立的文件菜单,并核对实际原生菜单栏。 + +crates/ely-ui/src/shell/root.rs:86 — ToggleCommandPalette 直接翻转布尔值,关闭路径跳过组件的焦点归还,丢失打开面板前的控件焦点。复现:在 shell gallery 聚焦右栏属性控件,连续两次发送 Command+Shift+P;面板关闭后 props.is_focused(window) 为 false,日志显示焦点落到根作用域。相同起点使用 Esc 或选择命令关闭时均为 true。该路径违反组件库要求的每个浮层关闭路径归还焦点,也使后续键盘操作离开原控件。修复方向:让快捷键关闭与面板自身关闭使用一致的焦点恢复流程,并覆盖重复切换后的焦点断言。 + +tests/scripts/shell.sh:35 — 命令验收绕过快捷键、菜单点击和命令面板选择,无法检出 C05.3 的关键接线失败。crates/ely-ui/examples/shell.rs:186 直接调用 commands::run 并 dispatch_action 打开面板,191–193 行直接执行命令并关闭面板。实测临时副本中分别删除 run.rs 的 commands::bind_keys(cx),以及将 Root 的 CommandPalette.on_run 改为空回调,两次 shell.sh 都以 0 退出并报告 commands、palette pass;原版 workspace check、workspace test、shell.sh 和八应用启动测试也均通过。修复方向:通过实际按键、菜单选择及面板输入和选择触发命令,断言执行结果与关闭状态,使这两种断线变异均触发测试失败。 diff --git a/tasks/reviews/C05.3-r2.md b/tasks/reviews/C05.3-r2.md new file mode 100644 index 0000000..6b4ed29 --- /dev/null +++ b/tasks/reviews/C05.3-r2.md @@ -0,0 +1,3 @@ +FAIL + +tests/scripts/shell.sh:34 — 上一轮第 3 项的菜单验收仍有缺口:探针只读取菜单标签,命令执行来自快捷键和面板,菜单点击及其动作映射均未验证。复现:在临时副本中将 crates/ely-ui/src/commands.rs:65 的 `MenuItem::action(SAVE.label, Save)` 改成 `MenuItem::action(SAVE.label, Open)`,运行 shell.sh 仍以 0 退出并报告 menus、commands、palette pass;此时菜单 Save 绑定 Open,Cmd+S 绑定 Save,UX-02 要求的相同结果已被破坏。修复方向:实际选择文件菜单项并断言执行的命令,逐项与对应快捷键结果比较,使该错接变异触发失败。 diff --git a/tasks/reviews/C05.3-r3.md b/tasks/reviews/C05.3-r3.md new file mode 100644 index 0000000..56cc13c --- /dev/null +++ b/tasks/reviews/C05.3-r3.md @@ -0,0 +1,3 @@ +FAIL + +tests/scripts/shell.sh:37 — 本轮新增的菜单动作批量执行掩盖了快捷键错误:crates/ely-ui/examples/shell.rs:223–239 在发送 Cmd+S 后执行全部文件菜单动作,脚本仅检查整份日志是否出现 Save,最终 recent 也被后续动作覆盖。复现:在临时副本中将 crates/ely-ui/src/commands.rs:77 的 `KeyBinding::new(SAVE.keys, Save, Some(CONTEXT))` 改成 `KeyBinding::new(SAVE.keys, Open, Some(CONTEXT))`,运行 `bash tests/scripts/shell.sh` 仍以 0 退出并报告全部通过;此时 Cmd+S 执行 Open,文件菜单 Save 执行 Save,违反 UX-02 的相同结果要求。上一轮菜单 Save→Open 变异已被新增映射断言检出,但快捷键验收出现回归。修复方向:逐项独立触发菜单选择和对应快捷键,在每次触发后立即断言实际执行的命令,再比较两条路径的结果,使快捷键错接或缺失触发测试失败。 diff --git a/tasks/reviews/C05.3-r4.md b/tasks/reviews/C05.3-r4.md new file mode 100644 index 0000000..51e229f --- /dev/null +++ b/tasks/reviews/C05.3-r4.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-ui/examples/shell.rs:283 — 新增的菜单结果断言读取刚由同一命令的快捷键写入的 `recent()[0]`,菜单零次执行也会被判为成功。上一轮 Cmd+S→Open 变异已被本轮测试检出,但菜单验收仍受前一次执行结果污染。复现:在临时副本中将本文件 `Probe::Actions` 分支内的 `window.dispatch_action(action, cx)` 替换为 `drop(action)`,停用全部菜单动作派发后运行 `bash tests/scripts/shell.sh`,仍以 0 退出并报告 menus、commands、palette pass。修复方向:每次触发前记录执行日志位置,触发后断言新增且仅新增一次对应命令,再比较菜单与快捷键的结果,确保漏执行触发失败。 + +crates/ely-ui/examples/shell.rs:501 — 本轮扩充后的文件共 508 行,违反 agents.md 第 5 条的 500 行上限。复现:`wc -l crates/ely-ui/examples/shell.rs` 输出 508,第 501 至 508 行已越界。修复方向:整理重复的探针流程,将该文件控制在 500 行以内。 diff --git a/tasks/reviews/C05.3-r5.md b/tasks/reviews/C05.3-r5.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C05.3-r5.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C05.4-r1.md b/tasks/reviews/C05.4-r1.md new file mode 100644 index 0000000..ee2a02b --- /dev/null +++ b/tasks/reviews/C05.4-r1.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-ui/examples/shell/main.rs:316 — 删除对话框把任意未修饰的 Enter 松键都送到 `delete_confirmed`,忽略当前焦点。复现:打开 Page One 的删除确认,按 Tab 让 Cancel 显示焦点环,再按 Enter;实际删除 Page One,只剩 Page Two,违反 UX-03、UX-07。隔离探针与截图均确认此路径。修复方向:让 Cancel 和 Delete Page 各自响应键盘激活,通过组件的确认及关闭流程执行操作并归还焦点;补 Cancel 上 Enter/Space 保留对象的测试。 + +crates/ely-ui/src/tasks.rs:65 — `tick(done)` 在前台实体更新闭包内同步执行,长任务会阻塞窗口和取消操作,违反 UX-05。复现:以 600 毫秒的同步工作模拟一次导出步骤;探针确认回调与 UI 位于同一线程,原定 50 毫秒执行的 UI 定时器延迟至约 628 毫秒。现有任务测试的回调立即返回,覆盖不到此路径。修复方向:将实际工作放入后台执行器或工作进程,向 UI 传回进度和结果,并使工作接受取消信号;测试耗时工作期间的窗口响应及取消。 + +crates/ely-ui/examples/shell/main.rs:171 — 删除最后一页后仍索引 `self.pages[0]`,并把选择设为已失效的 `[0]`。复现:沿现有探针先删除 Page Two,再确认删除剩余 Page One;进程因 `index out of bounds: the len is 0 but the index is 0` 中止。修复方向:为删除后的空页面集合清空当前页与选择,渲染空状态,让属性计算及重命名、删除入口处理空选择;补连续删除到零页的测试。 + +crates/ely-ui/examples/shell/main.rs:258 — 任务状态的唯一操作始终调用 `cancel_task`;`Failed` 状态下该调用直接结束,界面缺少 UX-06 要求的重试或另存路径。复现:运行已有磁盘满失败任务,界面停在 `Failed(The disk is full)`;调用状态项绑定的操作后仍保持同一失败状态。修复方向:保留失败任务的输入,在错误状态提供可执行的重试或另存入口,并验证恢复后使用原输入重新执行成功。 + +tests/scripts/shell.sh:57 — 混合态与长任务断言仅检查探针从模型生成的日志,无法验证 UX-04、UX-05 对应界面。实测两次独立变异:移除 `PropertyGrid` 的 `.group(group)` 后,脚本仍通过;移除任务状态栏的整个 `.when_some(task_label, ...)`,连同取消入口一起删除后,脚本仍以 0 退出并报告任务通过。修复方向:断言实际渲染的混合属性、任务范围、状态和错误,通过界面操作触发取消与恢复;让上述两种变异分别触发失败。 diff --git a/tasks/reviews/C05.4-r2.md b/tasks/reviews/C05.4-r2.md new file mode 100644 index 0000000..340bdd1 --- /dev/null +++ b/tasks/reviews/C05.4-r2.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-ui/src/tasks.rs:51 — `cancel()` 立即报告 `Cancelled`,后台工作仍可启动或继续,工作返回的错误也会在第 86 行被丢弃,违反 UX-05 和错误必须暴露的规则。隔离探针中,创建任务后同步取消,工作回调仍执行一次;另一任务在约 62 毫秒取消时写入计数为 0,约 795 毫秒时计数变为 1,状态仍为 `Cancelled`,回调返回的模拟写入错误消失。修复方向:派发工作前检查取消,把取消信号传给正在执行的工作,并在实际停止后报告取消完成;保留停止前产生的错误,补取消前后副作用与错误的断言。 + +crates/ely-ui/examples/shell/main.rs:274 — Cancel / Retry 的唯一入口是只绑定 `on_click` 的 `StatusBarItem`;当前锁定组件的该项没有 Tab 停靠点、焦点环或键盘激活处理,违反 UX-03。复现:运行磁盘满失败任务,从导航开始逐帧按 Tab 十二次再按 Enter,任务仍为 `Failed(The disk is full)`;原脚本通过鼠标点击同一项可以重试成功。修复方向:使用支持焦点与 Enter / Space 激活的组件提供任务操作,通用状态栏交互修复回组件库,并补仅用键盘取消和重试的测试。 + +tests/scripts/shell.sh:87 — 亮像素数量无法确认混合属性、任务范围和错误详情已渲染,上一轮的测试缺陷仍存在。两次独立变异均实测以 0 退出:移除 `PropertyGrid` 的 `.group(group)` 后运行 `ELY_APPEARANCE=light bash tests/scripts/shell.sh`;把任务文案改为省略进度范围的 `Export · Cancel` 和省略错误详情的 `Export failed · Retry` 后,在深色主题运行同一脚本。修复方向:固定截图主题,核对实际渲染的 Mixed、进度范围及错误文本,并让上述变异分别触发失败。 + +tests/scripts/ink.py:2 — 模块说明共有 14 个词,超过项目规定的十词上限。复现:按空白拆分该行说明得到 14,现有 `comments.py` 检查仍通过。修复方向:将这条说明缩短到十词以内,并让注释检查覆盖 Python 文档字符串。 diff --git a/tasks/reviews/C05.4-r3.md b/tasks/reviews/C05.4-r3.md new file mode 100644 index 0000000..c88bea4 --- /dev/null +++ b/tasks/reviews/C05.4-r3.md @@ -0,0 +1,3 @@ +FAIL + +tests/scripts/shell.sh:98 — 混合属性的截图断言仍会放过违反 UX-04 的渲染,第二轮的测试缺陷尚存。复现:在临时副本中,仅将 `examples/shell/main.rs` 的属性行渲染改为 `group.row(name.clone(), if name == "Format" { "Text".to_string() } else { value.clone() })`,保留混合选择及模型日志;运行 `bash tests/scripts/shell.sh`,实测退出码为 0。截图明确显示两页被选中且 `Format` 为首个对象的 `Text`,区域哈希仍与 `Mixed` 基准完全一致,距离为 0。当前 16×16 平均亮度哈希无法验证属性值,调低第 104 行的容差也无法捕获此回归。修复方向:为实际渲染的属性值采用能区分 `Mixed` 与 `Text` 的断言,并将此变异加入验证,要求脚本失败。 diff --git a/tasks/reviews/C05.4-r4.md b/tasks/reviews/C05.4-r4.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C05.4-r4.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C05.5-r1.md b/tasks/reviews/C05.5-r1.md new file mode 100644 index 0000000..97b80cd --- /dev/null +++ b/tasks/reviews/C05.5-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-ui/src/shell/root.rs:133 — 命令面板及窗口内容缺少 UX-12 要求的辅助技术名称、角色、状态和文本。实测打开中文画廊的命令面板,macOS AX 树只有窗口、系统窗口按钮和菜单,搜索框、命令结果、Rename/Delete 按钮与属性全部缺席;屏幕阅读器无法读取或操作这些内容。在临时副本为三个焦点锚点添加 role 和 aria_label 后,同一 AX 客户端立即读到 Inbox、Content、Properties,确认采集链正常。修复方向:在组件库补齐实际使用组件的可访问语义并更新依赖,为项目自绘控件补齐语义;增加原生辅助技术树及读写、选择、禁用状态的验收。 + +crates/ely-ui/examples/shell/dialogs.rs:27 — 此处接入的 PromptDialog 将输入法候选确认的 Enter key-up 当作重命名提交,违反 UX-09。按 macOS IME 回调顺序在临时探针中设置 marked text 为 ye,再提交“页”,随后仅派发 Enter key-up,实际得到 dialog_after_candidate_enter=false、标题变为“页”;用户确认候选时,对话框已经提交关闭。现有 probe.rs:283 直接注入最终汉字,shell.sh 的 CJK/撤销断言通过时仍遗漏此错误。修复方向:在组件库区分 IME 已消费的确认键和明确的表单提交,再更新依赖;回归覆盖组合、候选确认、继续输入、撤销及跨区域焦点。 + +crates/ely-ui/src/shell/root.rs:133 — 中文目录只覆盖命令名称和分组,命令面板自身文案仍为英文,违反 UX-08。设置 ELY_LOCALE=zh-CN 后打开面板,实测仍显示 Type a command、Recent,查询无结果时显示 No matching commands;画廊 main.rs:269、dialogs.rs:14 的状态、操作和错误正文同样硬编码英文。shell.sh:107 的中文流程停在菜单检查,因而全部通过。修复方向:让这些可见文案与错误正文统一读取双语目录,组件固定文案在组件库接入国际化;中文验收继续覆盖面板、空结果、状态、对话框和错误。 + +crates/ely-platform/src/locale.rs:13 — macOS 使用区域格式 localeIdentifier 选择界面语言,遗漏系统首选语言。以 -AppleLanguages '(zh-Hans)' -AppleLocale en_US 启动且清除 ELY_LOCALE,原生探针读到 preferred=["zh-Hans"]、system=en-US;实际 ely-doc 冒烟日志也是 locale en-US -> en-US。选择中文界面并保留美国区域格式的用户因此得到英文界面,违反 UX-08。修复方向:按系统首选界面语言匹配受支持语言,将区域格式与界面语言分别处理,并增加二者不同的回归案例。 + +crates/ely-platform/src/locale.rs:28 — Linux 将高优先级的 C/POSIX 语言设置跳过,继续采用低优先级中文设置。将该 Linux 函数原样编译成临时探针,LC_ALL=C.UTF-8、LC_MESSAGES=zh_CN.UTF-8、LANG=zh_CN.UTF-8 返回 zh-CN;LC_MESSAGES=C、LANG=zh_CN.UTF-8 也返回 zh-CN。有效的英文覆盖因此失效。修复方向:采用首个非空的高优先级设置,遇到 C/POSIX 时解析为英文并结束查找;补上这两种优先级组合的测试。 + +tests/scripts/shell.sh:80 — 缺少 UX-10 指定的多屏缩放验收。当前 resized layout 断言仅检查逻辑窗口宽度;对应 probe.rs:159 改变窗口尺寸。本轮在临时副本记录 scale_factor,1100×720 → 800×600 → 1100×720 全程为 2,断言仍通过;测试也没有点击命中、文本选区或浮层位置的检查,无法检测跨屏 DPI 改变后的坐标错误。修复方向:增加 100%、150%、200% 以及跨屏变化的验收,分别核对点击目标、选区和浮层位置,明确记录三端实际运行结果。 diff --git a/tasks/reviews/C05.5-r2.md b/tasks/reviews/C05.5-r2.md new file mode 100644 index 0000000..4c9547f --- /dev/null +++ b/tasks/reviews/C05.5-r2.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-ui/src/shell/root.rs:133 — 上轮 UX-12 缺陷仍在。新增 role/aria 仅覆盖自绘锚点和任务按钮。本轮打开中文命令面板并检查原生 macOS AX 树,内容节点只有“收件箱”“Content”“属性”三个按钮;画面中的搜索框、命令结果、标签页和属性文本均缺席。关闭面板并点击“重命名…”后,AX 树仍无对话框、输入框和确认/取消按钮,屏幕阅读器无法获取这些控件的名称、角色、状态和文本。修复方向:在组件库补齐实际使用组件的辅助技术语义并更新依赖,增加原生 AX 树及输入、选择、禁用状态的回归验收。 + +crates/ely-ui/examples/shell/dialogs.rs:27 — 上轮 UX-09 缺陷仍在:PromptDialog 将输入法候选确认的 Enter key-up 当作重命名提交。本轮临时探针按组合输入回调设置 marked text 为 ye、提交“页”,随后只派发 Enter key-up,实测 dialog_after_candidate_enter=false、titles=页|Page Two。用户确认候选时,名称即被提交且对话框关闭。现有 shell.sh 全部通过,其 CJK 流程仍直接注入最终汉字。修复方向:在组件库区分输入法已消费的确认键与表单提交并更新依赖;回归覆盖组合、候选确认后继续输入、撤销和跨区域焦点。 + +crates/ely-ui/src/shell/root.rs:133 — 上轮 UX-08 缺陷仍在:新增双语目录未覆盖组件内部文案及全部画廊文案。本轮 ELY_LOCALE=zh-CN 的实际画面仍显示 Type a command、Recent、No matching commands;重命名对话框仍显示 Cancel,main.rs:408 的 Content、main.rs:133 的 Mixed 也继续硬编码英文。中文用户在主要操作流程中得到混合语言界面,shell.sh:107 的中文验收仍在菜单阶段停止。修复方向:将剩余固定文案接入双语目录,组件文案在组件库修复后更新依赖,并检查中文面板、空结果、混合态和对话框。 + +tests/scripts/shell.sh:80 — 上轮 UX-10 的缩放验收缺口仍在。新增 resized click hits 日志未被脚本断言,probe.rs:170 唯一的 resize 仍设回启动尺寸 1100×720。本轮记录步骤 1、11、13,三次 viewport 均为 1100×720、scale_factor 均为 2,shell.sh 仍全部通过;100%、150%、200% 及跨屏变化下的点击、文本选区和浮层位置均无验收,相关坐标回归可通过现有测试。修复方向:实际改变缩放并跨屏运行,断言点击目标、选区和浮层位置,记录三端实际验证结果。 + +crates/ely-ui/examples/shell/main.rs:501 — 本轮文件已增长至 538 行,违反 agents.md 的单文件最多 500 行规则;按当前文件直接统计即可复现。修复方向:按现有职责拆分画廊区域渲染或状态处理,使每个文件保持在 500 行以内。 diff --git a/tasks/reviews/C05.5-r3.md b/tasks/reviews/C05.5-r3.md new file mode 100644 index 0000000..6ae81fb --- /dev/null +++ b/tasks/reviews/C05.5-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-ui/src/shell/root.rs:133 — 上轮 UX-12 缺陷仍在。本轮运行中文画廊并读取原生 macOS AX 树,命令面板打开时内容节点仅有“收件箱”“内容”“属性”三个按钮,画面中的搜索框、命令结果、标签页和属性文本均缺席;关闭面板、点击“重命名…”后,AX 树仍缺少对话框、输入框和确认/取消按钮。屏幕阅读器无法获取主要交互控件的名称、角色、状态和文本。修复方向:在组件库补齐实际使用组件的辅助技术语义并更新依赖,增加原生 AX 树及输入、选择、禁用状态的回归验收。 + +crates/ely-ui/examples/shell/dialogs.rs:27 — 上轮 UX-09 缺陷仍在:PromptDialog 将输入法候选确认的 Enter key-up 当作重命名提交。本轮临时副本按输入法回调设置 marked text 为 ye、提交“页”,随后仅派发 Enter key-up,实测 dialog_after_candidate_enter=false、titles=页|Page Two。用户确认候选时,名称即被提交且对话框关闭。现有 shell.sh 全部通过,其 CJK 流程仍直接注入最终汉字。修复方向:在组件库区分输入法已消费的确认键与表单提交并更新依赖;回归覆盖组合、候选确认后继续输入、撤销和跨区域焦点。 + +crates/ely-ui/src/shell/root.rs:133 — 上轮 UX-08 缺陷仍在:组件内部固定文案尚未接入双语目录。本轮 ELY_LOCALE=zh-CN 的实际命令面板仍显示 Type a command、Recent,输入无匹配查询后显示 No matching commands;重命名对话框仍显示 Cancel。中文用户在主要操作流程中得到混合语言界面,shell.sh:108 的中文验收仍停在菜单阶段。修复方向:在组件库将面板、空结果及对话框固定文案接入国际化并更新依赖,补齐中文流程回归。 + +tests/scripts/shell.sh:82 — 上轮 UX-10 的缩放验收缺口仍在。新增断言仅要求 scale factor 含数字;probe.rs:170 唯一的 resize 仍设回启动尺寸 1100×720。本轮临时探针记录步骤 1、11、13,三次 viewport 均为 1100×720、scale_factor 均为 2,resized layout 与 resized click hits 均为 true,原始 shell.sh 全部通过。100%、150%、200% 及跨屏变化下的点击、文本选区和浮层位置仍无验收,相关坐标回归可通过现有测试。修复方向:实际改变缩放并跨屏运行,逐项断言点击目标、选区和浮层位置,记录三端实际验证结果。 diff --git a/tasks/reviews/C05.5-r4.md b/tasks/reviews/C05.5-r4.md new file mode 100644 index 0000000..c0f4436 --- /dev/null +++ b/tasks/reviews/C05.5-r4.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-ui/examples/shell/dialogs.rs:50 — UX-12 的对话框语义仍缺失。本轮在 macOS 打开中文删除对话框,原生 AX 树只新增 Cancel 和“删除页面”两个按钮,画面中的“删除 Page One?”及永久删除说明均缺席;重命名输入 fail 后,“磁盘已满,名称未保存”也只出现在画面。屏幕阅读器无法获知删除对象、影响及提交失败原因。上游 Dialog 容器与标题、正文、校验错误仍缺少可访问语义。修复方向:在组件库补齐对话框角色、名称、说明和错误文本,更新依赖,并在原生 AX 树上验收这些状态。 + +reference/Ely-GPUI-Components/src/buttons/button.rs:293 — 新增 Button 角色后,disabled 状态仍只改变透明度和鼠标样式。本轮重命名输入 fail,画面的“重命名”按钮置灰,AX 树仍将其暴露为普通按钮,缺少禁用状态。辅助技术会把当前无法执行的提交读作可用操作。修复方向:将实际 disabled 状态同步到辅助技术属性,并覆盖可用到禁用及恢复的原生 AX 回归。 + +crates/ely-ui/examples/shell/main.rs:157 — UX-08 仍有主要流程固定英文。ELY_LOCALE=zh-CN 时,重命名输入框的占位符及 AX 名称仍为 Page name;重命名和删除对话框仍显示上游 PromptDialog/ConfirmDialog 写死的 Cancel。现有中文脚本在菜单阶段退出,这些混合语言界面仍使测试通过。修复方向:将输入框占位符和组件取消文案接入双语目录,并检查中文对话框及其辅助技术名称。 + +tests/scripts/shell.sh:82 — 上轮 UX-10 验收缺口仍在。断言仅要求 scale factor 含数字;probe.rs:170 的唯一 resize 设回启动尺寸 1100×720。本轮临时探针记录步骤 1、11、13,viewport 均为 1100×720、scale_factor 均为 2,resized layout 和 resized click hits 均通过。100%、150%、200% 及跨屏变化的点击、文本选区和浮层位置均未得到验证,缩放回归可通过现有测试。修复方向:实际覆盖指定缩放及跨屏变化,逐项核对点击目标、选区和浮层位置,并记录三端验收结果。 + +tests/scripts/shell.sh:83 — UX-09 的现有回归仍直接注入最终汉字,覆盖不到本轮修复的候选确认事件。本轮将临时上游副本的 Dialog Enter 处理恢复为旧 key-up 实现,完整 shell.sh 仍退出 0;同一副本经 marked text=ye、提交“页”、仅派发 Enter key-up 后,实测 dialog=false、titles=页|Page Two。当前 key-down 实现通过该重放,现有测试却无法区分两者。修复方向:把组合输入、候选确认的事件顺序纳入探针,断言对话框持续打开、标题保持原值,并覆盖继续输入、撤销和焦点切换。 diff --git a/tasks/reviews/C05.5-r5.md b/tasks/reviews/C05.5-r5.md new file mode 100644 index 0000000..9d0c045 --- /dev/null +++ b/tasks/reviews/C05.5-r5.md @@ -0,0 +1,5 @@ +FAIL + +reference/Ely-GPUI-Components/src/overlays/dialogs.rs:162 — r4 的 UX-12 对话框正文缺陷仍在。ConfirmDialog 把 message 交给无辅助技术语义的 said() 子节点;本轮新增的 Dialog aria_description 只读取 detail,此调用没有设置 detail。本轮 macOS 中文删除对话框的画面显示“Page One及其内容将被永久删除。”,原生 AX 树中的 AXDialog 只有“删除Page One?”标题及“取消”“删除页面”按钮,正文、AXDescription 和 AXHelp 均缺失。屏幕阅读器仍无法获取永久删除的影响。修复方向:在组件库为确认正文提供可访问文本或对话框说明,更新依赖,并在原生 AX 树验证完整删除影响。 + +crates/ely-ui/examples/shell/probe.rs:321 — r4 的 UX-09 回归测试缺陷仍在。新增 key-up-only Enter 在撤销已清空输入框后执行,空名称校验会阻止提交,掩盖旧 key-up 处理器的提前提交。本轮仅在临时组件副本恢复旧 Dialog on_key_up 实现,完整 tests/scripts/shell.sh 仍退出 0;随后重放 marked text=ye、提交“页”、仅派发 Enter key-up,旧实现得到 dialog_after_candidate_enter=false、titles=页|Page Two,当前实现得到 true、titles=Appendix|Page Two。修复方向:把有效名称的组合输入和候选确认顺序纳入正式探针,在 key-up 后断言对话框保持打开、页面标题保持原值,并要求旧实现触发测试失败。 diff --git a/tasks/reviews/C05.5-r6.md b/tasks/reviews/C05.5-r6.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C05.5-r6.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C05.6-r1.md b/tasks/reviews/C05.6-r1.md new file mode 100644 index 0000000..748bf50 --- /dev/null +++ b/tasks/reviews/C05.6-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-ui/src/settings/panels.rs:64 — 最新宽度仅由延迟 500 ms 的 detached task 保存,正常退出会丢弃尚在等待的改动,违反 C05.6 的重启恢复要求。真实 ElyDoc 窗口中从默认宽度向右拖动 80 点,松手后立即 Cmd+Q,再启动:侧栏回到默认 220 点,settings.json 只有 window/version,缺少 panels;独立重复三次均复现。方向:正常退出前提交并等待最新宽度写入,补上拖动后立即正常退出、重启检查宽度的回归测试;当前 panels.sh:51 先等待写入再终止,覆盖不到此路径。 + +crates/ely-ui/src/settings/panels.rs:34 — 已存在的无效宽度通过 as_f64()/and_then 转为 None,随后被 fit 当作缺省值,构成静默回退。复现:应用设置写入 {"version":1,"panels":{"left":"broken","right":320}} 后启动 ElyDoc,左栏恢复默认 220 点,日志没有宽度解析错误;真实窗口探针已确认。方向:区分缺省/null 与无效配置,对 panels 结构及已填写宽度的类型、有效数值做校验,传播具体错误并显示。 + +crates/ely-ui/src/settings/panels.rs:69 — 宽度写入失败只写日志,未接入界面错误状态或刷新错误提示,违反 PRD UX-06。复现:正常启动后把该应用的 settings.json 文件替换为同名目录,再拖宽左栏;窗口显示约 296 点的新宽度,日志记录 “panel widths not saved: Is a directory (os error 21)”,应用与设置窗口均没有保存失败提示,改动只留在内存。读取失败在本文件:28 也只有日志与默认宽度。方向:将应用偏好的读取、保存错误接入界面可见的错误状态,保留本次输入并提供重试,补上失败路径的真窗口断言。 diff --git a/tasks/reviews/C05.6-r2.md b/tasks/reviews/C05.6-r2.md new file mode 100644 index 0000000..15f3e19 --- /dev/null +++ b/tasks/reviews/C05.6-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-ui/src/settings/mod.rs:133 — 通用偏好保存成功会清空 Current.unsaved,吞掉仍未解决的侧栏保存错误,违反 PRD UX-06。真实 ElyDoc 窗口中,把应用 settings.json 替换为同名目录后拖宽左栏,设置窗口显示保存失败;点击 Dark 成功保存通用外观后,该提示消失,应用 settings.json 仍为目录,侧栏宽度仍待保存。AX 与磁盘状态已验证,证据在 /tmp/c056-r2-evidence.log。方向:按保存操作记录错误,通用偏好的成功仅清除对应错误,侧栏错误保留到其保存成功;补上交错保存的真窗口断言。 + +crates/ely-ui/src/settings/panels.rs:102 — 侧栏成功重试只更新 saved,保留旧的保存失败提示,导致界面报告与实际保存结果冲突。真实窗口中先以同名目录触发宽度保存失败,再移除目录并再次拖动;settings.json 已成功写入 panels.left=349,重新打开设置窗口仍显示 “The last change was not saved: Is a directory (os error 21)”。AX 与保存字节已验证,证据在 /tmp/c056-r2-evidence.log;现有 panels.sh 在首次失败提示出现后即退出。方向:最新宽度保存成功时清除该操作的错误并刷新界面,保留其他操作的错误;补上修复保存路径、再次拖动、检查保存字节与提示恢复的回归测试。 diff --git a/tasks/reviews/C05.6-r3.md b/tasks/reviews/C05.6-r3.md new file mode 100644 index 0000000..8823e87 --- /dev/null +++ b/tasks/reviews/C05.6-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-ui/src/settings/panels.rs:102 — 宽度保存成功只清除 unsaved[PANELS],启动时记录的 Current.app_error 持续保留,设置窗口继续报告已经修复的读取错误。真实 ElyDoc 窗口中,以 {"version":1,"panels":{"left":"broken","right":320}} 启动,再拖宽左栏;磁盘已保存有效的 panels.left=296、right=null,重新打开设置并等待控件进入 AX 树后,仍显示 “This app's settings cannot be read, so defaults are in use: settings: panels left cannot be "broken"”。重复复现,证据在 /tmp/c056-r3-evidence.log。方向:保存或重试成功后重新校验应用配置,清除已经恢复的对应读取错误,保留其他读取错误;补上无效配置经拖动修复后的真实窗口断言。 + +tests/scripts/panels.sh:114 — 重试恢复断言把重新打开设置时暂时为空的 AX 内容当作错误提示已清除,覆盖不到第二轮的提示残留缺陷。在使用独立 CARGO_TARGET_DIR 的隔离副本中,仅移除 panels.rs:104 的成功清错调用,完整 panels.sh 仍以退出码 0 输出 “ok: panels”。独立真实窗口探针确认:该断言执行时设置页的 Dark 控件尚未进入 AX 树;移入鼠标、等待控件加载后,仍可读到 “The last change was not saved: Is a directory (os error 21)”,磁盘宽度已保存。原始实现经相同探针确认提示成功清除,证据在 /tmp/c056-r3-evidence.log。方向:先确认设置页实际内容进入 AX 树,再检查提示状态,并要求移除成功清错调用的变异使测试失败。 diff --git a/tasks/reviews/C06.1-r1.md b/tasks/reviews/C06.1-r1.md new file mode 100644 index 0000000..02ee286 --- /dev/null +++ b/tasks/reviews/C06.1-r1.md @@ -0,0 +1,15 @@ +FAIL + +crates/ely-files/src/session.rs:192 — `reload()` 把修订号重置为 0,保存票据仅携带修订号,旧任务因此能够匹配重新加载后的新编辑,违反 FILE-03。隔离测试复现:编辑到 revision=1,取得保存票据并写盘;保存完成回调到达前调用 `reload()`,再编辑一次;随后 `finish_save(old_ticket, path)` 返回成功,状态实际为 `Saved`,revision 和 saved_revision 都是 1,此时重新加载后的编辑尚未写盘。修复方向:保证重新加载前后的修订身份唯一,使重新加载失效旧保存票据,并补充这个回调顺序的回归测试。 + +crates/ely-files/src/session.rs:194 — `reload()` 无条件设为 `Saved`,使只读视图获得保存权限,违反 §6.1 的只读副本约束。隔离测试中,`Session::read_only(path)` 经外部更新后调用 `reload()`,随后 `state()` 返回 `Saved`,`begin_save()` 返回成功;与原可写会话同时存在的视图因此可以写入同一文件。修复方向:重新加载保留访问模式,所有状态转换持续维护只读约束,并覆盖只读视图重新加载后保存被拒绝的测试。 + +crates/ely-files/src/session.rs:165 — 保存完成无条件覆盖较新的外部修改状态,同时把完成时读取到的磁盘指纹记成已保存指纹,违反 FILE-02。隔离测试复现:取得票据并写入本次保存内容,另一程序随后改写文件,调用 `external_modified()`,最后收到本次保存的完成回调并调用 `finish_save()`;实际状态变成 `Saved`,记录的指纹属于外部内容,会话内容仍属于本次保存修订。修复方向:保存完成只确认对应任务写入的版本,保留其后发生的外部修改通知及冲突状态,并增加乱序完成回调测试。 + +crates/ely-files/src/session.rs:398 — 去重仅比较规范化路径,硬链接会产生两个可写会话,违反 §6.1 的同文件聚焦要求。隔离测试通过 `std::fs::hard_link(doc, alias)` 创建同一文件的两个名字,依次执行 `open(doc, false)` 和 `open(alias, false)`,实际返回 `Fresh(0)`、`Fresh(1)`,第二次应聚焦已有会话。修复方向:结合文件系统文件身份识别硬链接别名,保留路径用于显示,并加入真实硬链接去重测试。 + +crates/ely-files/src/session.rs:392 — `canonicalize()` 的所有错误被 `unwrap_or_else` 吞掉并静默改用原路径,违反错误暴露规则,也会改变已打开文件的去重键。隔离测试先通过符号链接打开文件,删除其目标后再次以同一符号链接调用 `open()`,实际返回 `Fresh(1)`;底层 `canonicalize()` 已明确失败。对原本缺失的路径,`open()` 同样直接登记为新会话。修复方向:向调用方传播路径解析错误,失败时保持会话登记不变;为 `open()` 和 `is_open()` 补充解析失败测试。 + +crates/ely-files/src/session.rs:92 — FILE-02 要求的 `Loading` 状态不可达。`load()` 只读取元数据便构造 `Saved`,全部构造及转换方法均未设置 `Loading`,`state` 又是私有字段;当文档内核仍在异步加载大文件时,调用方无法用此会话表示加载中。源码检索确认 `SaveState::Loading` 唯一用处是显示字符串分支。修复方向:提供实际的加载开始、成功及失败转换,并测试加载完成前的状态及允许操作。 + +crates/ely-files/src/session.rs:202 — `SaveError` 是闲置的公开错误类型,违反无 dead code 规则。全项目检索仅找到定义和再导出;实际只读保存失败由 `begin_save()` 返回 `Err(SaveState::ReadOnly)`,既有只读测试也只验证该返回值,`SaveError::ReadOnly` 从未构造或消费。修复方向:删除闲置类型及导出,或让现有保存错误路径实际使用该类型,并同步测试。 diff --git a/tasks/reviews/C06.1-r10.md b/tasks/reviews/C06.1-r10.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C06.1-r10.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C06.1-r2.md b/tasks/reviews/C06.1-r2.md new file mode 100644 index 0000000..b1a3261 --- /dev/null +++ b/tasks/reviews/C06.1-r2.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-files/src/session.rs:328 — 上轮路径解析错误问题仍有遗漏:`NotFound` 被转换为路径键并登记成功。隔离测试中,打开缺失文件得到 `Ok(Fresh(0))`;打开真实文件后删除它,再检查和打开,分别得到 `Ok(false)`、`Ok(Fresh(1))`,原登记仍存在。悬空符号链接测试已通过,但普通缺失路径的错误仍被吞掉;现有 `a_missing_path_registers_as_a_new_file` 还把这一行为写成预期。修复方向:向 `open()`、`is_open()` 的调用方传播解析失败,失败时保持登记不变;新建文件走无隐含目标路径的流程,并补充缺失、删除后的回归测试。 + +crates/ely-files/src/session.rs:320 — 上轮硬链接去重只修复了 Unix 分支,Windows 仍以规范化路径作为身份。同一文件的两个硬链接具有不同路径,依次打开会登记两个可写会话,违反 §6.1。抽取当前非 Unix `identity()` 原样在本机运行硬链接测试,两个键确实不同;Windows 目标的交叉检查通过,但报告 `Key::File` 从未构造,现有硬链接测试也被 `#[cfg(unix)]` 排除。修复方向:Windows 分支使用卷与文件身份去重,并让硬链接回归测试覆盖 Windows。 + +crates/ely-files/src/session.rs:315 — Unix 去重键只保存首次打开时的设备号和 inode,原子替换后同一路径失去已有会话关联,违反 §6.1。隔离测试先打开 `doc.md`,再通过临时文件加 `rename()` 替换它;随后 `is_open(doc.md)` 返回 `false`,再次打开得到 `Fresh(1)`,预期为 `Focus(0)`。正常原子保存或外部编辑器的原子替换都会触发两个可写会话。修复方向:保留规范化路径与文件身份的关联,在保存或替换后刷新身份,同时保留硬链接去重,并加入真实原子替换测试。 + +crates/ely-files/src/session.rs:208 — `begin_save_overwrite()` 直接生成票据,绕过只读、加载中及保存中的限制。隔离测试对 `Session::read_only(path)` 调用此方法,取得票据并完成保存后,状态变成 `Saved`,后续普通 `begin_save()` 也返回成功;§6.1 的只读视图因此获得持续写权限。上轮只读重载复现已通过,当前测试只检查普通保存入口。修复方向:所有保存入口共同维护访问模式与任务状态,覆盖确认仅解除外部冲突限制,并覆盖只读、加载中和已有保存任务的拒绝路径。 + +crates/ely-files/src/session.rs:183 — 未解决的外部修改会被普通状态转换清除,破坏 FILE-02 的状态表示与冲突保存守卫。隔离测试执行外部写入、`external_modified()`、`edit()` 后,状态变成 `Dirty`,普通 `begin_save()` 返回成功;保存期间收到外部修改后再调用 `fail_save()`,第 254 行同样把状态改成 `SaveFailed`,重试保存获准。两条路径都绕过了明确处理外部冲突的步骤。修复方向:独立保留未解决的外部修改事实,使编辑和保存失败持续维护冲突约束,覆盖这两种事件顺序。 + +crates/ely-files/src/session.rs:165 — 新增的 `finish_load()` 在完成回调时重新读取磁盘指纹,并无条件设为 `Saved`,会把加载之后的外部版本当作内存内容的基线。隔离测试让后台读取 14 字节内容,外部程序随后改写成 39 字节并调用 `external_modified()`,最后调用 `finish_load()`;实际记录的指纹长度为 39,状态为 `Saved`,内存仍是先前的 14 字节,违反 §6.1 的最后加载指纹要求及 FILE-02。修复方向:使用本次实际加载内容对应的指纹确认完成,保留其后发生的外部修改状态,并补充延迟完成回调的测试。 diff --git a/tasks/reviews/C06.1-r3.md b/tasks/reviews/C06.1-r3.md new file mode 100644 index 0000000..cc18798 --- /dev/null +++ b/tasks/reviews/C06.1-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-files/src/session.rs:277 — 外部变更将 Loading/Saving 直接改成 ExternalModified,导致第 213 行新增的覆盖保存守卫失效。隔离测试中,加载期间收到 external_modified() 后,begin_save_overwrite() 返回成功;已有修订 1 保存任务时收到外部变更、编辑到修订 2,也能启动第二个保存任务。进一步按“修订 2 写盘、修订 1 写盘、修订 1 完成回调、修订 2 完成回调”重放,实际得到 state=Saved、revision=2、saved_revision=2,磁盘内容仍为修订 1,违反 FILE-03。修复方向:独立维护加载与进行中的保存任务,外部冲突事件持续保留任务限制,覆盖确认仅解除冲突限制;补充加载中变更和保存交错完成的回归测试。 + +crates/ely-files/src/session.rs:389 — 文件身份只在已命中登记后的第 392 行刷新,原子替换后仍可能使用过期 inode 去重。隔离测试打开 doc.md 为 Fresh(0),通过临时文件 rename 完成原子保存并调用 finish_save(),再给新文件建立 alias.md 硬链接;is_open(doc.md) 为 true,is_open(alias.md) 为 false,打开 alias.md 得到 Fresh(1),产生两个可写会话,违反 §6.1。另一测试保留旧文件的硬链接后替换 doc.md,打开旧链接反而得到 Focus(0),聚焦了内容与目标均已不同的会话。修复方向:替换后同步更新登记身份,并在硬链接匹配前核实缓存身份与登记路径的当前文件一致;覆盖替换后的新旧硬链接场景。 + +crates/ely-files/src/session.rs:187 — 上轮外部冲突修复保住了普通保存守卫,但编辑仍将 ExternalModified 改成 Dirty,隐藏尚未解决的外部修改。隔离测试执行外部写入、external_modified()、edit() 后,begin_save() 返回 ExternalModified,state() 却返回 Dirty;冲突标志为私有字段,显示层无法通过会话状态持续显示外部已修改,违反 FILE-02。现有 tests.rs:159 还将 Dirty 写成预期。修复方向:编辑后继续向显示层暴露未解决的外部修改状态,并让回归测试断言冲突提示持续存在。 + +crates/ely-files/src/session/tests.rs:265 — 上轮要求的 Windows 硬链接回归仍被 #[cfg(unix)] 排除,新增 Windows 文件身份分支缺少硬链接去重的行为测试。Windows 编译会直接删除 hard_links_share_one_session;若该平台重新退化为按路径去重,同一文件两个硬链接生成两个可写会话的缺陷仍缺少检测。本轮 18 项原有单测及 Windows --tests 编译检查均通过,硬链接行为仅在本机 Unix 执行。修复方向:让现有硬链接回归覆盖 Windows,并在 Windows 上运行该测试验证卷与文件身份去重。 diff --git a/tasks/reviews/C06.1-r4.md b/tasks/reviews/C06.1-r4.md new file mode 100644 index 0000000..6ee649f --- /dev/null +++ b/tasks/reviews/C06.1-r4.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/session.rs:249 — 重载后的旧保存回调直接返回 StaleTicket,新增的 saving 标志持续为 true。隔离测试执行 begin_save()、写盘、外部修改、reload()、旧任务 finish_save()、edit();旧回调返回 StaleTicket,随后 state() 为 Dirty,begin_save() 与 begin_save_overwrite() 均持续返回 Saving,已结束的任务永久阻塞后续保存。现有 a_reload_kills_old_tickets 只断言修订与显示状态,遗漏恢复保存。修复方向:独立跟踪进行中的任务身份,在旧任务结束时释放对应槽位,同时继续拒绝其过期内容确认;补充重载、旧任务结束、再次保存的回归测试。 + +crates/ely-files/src/session.rs:226 — 加载失败限制仍依赖可被外部事件覆盖的显示状态。隔离测试执行 Session::loading(path)、fail_load()、外部写入、external_modified()、begin_save_overwrite(),实际得到 fingerprint=None 与 Ok(SaveTicket { generation: 0, revision: 0 })。文件内容尚未成功加载,覆盖保存已获准,可将空白或残缺的内存内容写回文件;上一轮任务守卫修复仍遗漏失败加载后的变更事件。修复方向:独立保留加载成功与编辑可用性,所有保存入口持续检查该条件,覆盖确认只解除外部冲突;补充加载失败、外部变更、覆盖保存的拒绝测试。 + +crates/ely-files/src/session.rs:399 — 实时身份匹配将 file_id(have) 的所有错误转换为 false,静默失去同文件去重。macOS 隔离测试先打开 restricted/doc.md 为 Fresh(0),给它建立可访问的 alias.md 硬链接,再撤销 restricted 目录的访问权限;已登记路径的 metadata 返回 PermissionDenied,is_open(alias.md) 却返回 Ok(false),open(alias.md, false) 返回 Ok(Fresh(1))。同一 inode 形成两个可写会话,违反 §6.1,并吞掉实际文件系统错误。修复方向:匹配流程返回可传播的错误,权限等身份查询失败通过 open()/is_open() 暴露且保持登记不变;补充已登记路径失去访问权限后的硬链接回归测试。 diff --git a/tasks/reviews/C06.1-r5.md b/tasks/reviews/C06.1-r5.md new file mode 100644 index 0000000..37537a4 --- /dev/null +++ b/tasks/reviews/C06.1-r5.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/session.rs:262 — 旧票据在身份校验前无条件清空 saving,能够释放另一个正在运行的保存任务。上一轮的单次旧回调释放场景已通过;隔离测试继续执行“旧任务开始、reload、旧任务完成被拒绝、新任务开始、旧完成回调重复到达、新任务写盘并完成”,实际新任务的 finish_save() 返回 StaleTicket,state() 持续为 Saving;重复旧回调之后也已解除并发保存守卫。现有回归仅检查空闲时重复完成,遗漏新任务进行中的重复回调。修复方向:记录可核对的进行中任务身份,仅由对应任务释放槽位;过期内容确认继续拒绝,并覆盖旧回调与新任务重叠的测试。 + +crates/ely-files/src/session.rs:307 — reload() 成功后遗漏更新新增的 loaded 标志,加载失败后的恢复仍持续拒绝保存。隔离测试执行 Session::loading(path)、fail_load(),成功读取文件并调用 reload(),得到 Saved;随后 edit() 得到 Dirty,begin_save() 却返回 LoadFailed。重新加载成功的会话因此无法通过保存流程持久化后续编辑,显示状态与保存能力冲突。上一轮“加载失败后外部变更仍拒绝覆盖”已通过,测试遗漏成功重载后的恢复。修复方向:成功重载同步更新加载生命周期与内容可用性,失败时保持原状态,并补充加载失败、成功重载、编辑及保存的回归。 + +crates/ely-files/src/session.rs:443 — 身份查询错误在登记新会话之后传播,open() 返回失败时遗留虚假登记。上一轮权限错误暴露测试已通过;macOS 隔离测试依次打开 first.md 为 Fresh(0)、restricted/second.md 为 Fresh(1),撤销 restricted 的访问权限,再执行 open(first.md, true),得到 PermissionDenied。恢复权限并关闭会话 0 后,is_open(first.md) 仍为 true,再次打开得到 Focus(2),该会话从未成功创建,无法聚焦有效窗口。修复方向:全部可能失败的身份查询在提交登记前完成,失败时保持登记与编号原状;补充已匹配文件后仍有权限失败条目的只读打开回归。 diff --git a/tasks/reviews/C06.1-r6.md b/tasks/reviews/C06.1-r6.md new file mode 100644 index 0000000..14c7c26 --- /dev/null +++ b/tasks/reviews/C06.1-r6.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/session.rs:263 — 保存任务身份仅由 generation 与 revision 组成,同一修订的两次保存会得到相同身份。隔离测试先保存到 first.md,随后保持修订号启动另存为 second.md;第一项任务的重复完成回调实际返回 Ok(()),将状态提前改为 Saved 并释放第二项任务的槽位。second.md 写入成功后的真实完成回调返回 StaleTicket,会话路径仍停留在 first.md,后续保存会继续指向旧文件。上一轮跨 reload 的重复回调场景已通过,同一 generation/revision 的重叠场景仍失败。修复方向:每次保存分配独立任务身份,完成回调按任务身份核对槽位,保留修订号与加载代次的内容有效性检查;补充同修订连续另存为及旧回调重放的回归。 + +crates/ely-files/src/session.rs:296 — fail_save() 缺少票据参数,仍无条件清空当前保存槽位,成功回调新增的身份守卫未覆盖失败回调。隔离测试执行“任务 A 开始、A 失败、继续编辑、任务 B 开始、A 的失败回调重复到达、B 写入成功并完成”,重复失败回调将状态改为 SaveFailed,B 的 finish_save() 返回 StaleTicket;B 进行期间的并发保存守卫也被解除。修复方向:失败回调携带独立任务身份,仅由匹配任务释放槽位并更新状态;补充旧失败回调与新任务重叠的回归。 + +crates/ely-files/src/session.rs:436 — 已找到需要聚焦的会话后仍继续查询其他登记路径,后续无关路径的权限错误会阻断同文件聚焦,违反 §6.1。macOS 隔离测试依次打开 first.md 为 Fresh(0)、restricted/second.md 为 Fresh(1),撤销 restricted 目录访问权限,再打开可正常访问的 first.md;is_open(first.md) 返回 Ok(true),open(first.md, false) 却返回 PermissionDenied。上一轮只读打开失败后保持登记与编号不变的回归已通过。修复方向:普通重复打开确认匹配后立即返回 Focus;新建登记继续保持失败时无副作用,并增加已匹配文件后存在无关权限错误条目的聚焦回归。 diff --git a/tasks/reviews/C06.1-r7.md b/tasks/reviews/C06.1-r7.md new file mode 100644 index 0000000..709f88c --- /dev/null +++ b/tasks/reviews/C06.1-r7.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-files/src/session.rs:299 — fail_save() 新增的身份校验仅比较 task,遗漏重新加载后的 generation 校验,旧内容的保存失败会污染当前会话状态。隔离测试执行“编辑、开始保存 A、外部修改、成功 reload、A 的失败回调到达”,reload 后状态为 Saved、修订号为 0,fail_save(A) 实际返回 Ok(()) 并将状态改成 SaveFailed,违反 FILE-02 的当前文件状态要求。上轮旧失败回调与新任务重叠的回归已通过;本轮跨 reload 的失败回调探针失败。修复方向:匹配任务释放槽位后核对加载代次,过期失败返回 StaleTicket 并保留当前状态;补充重新加载后旧任务失败及后续保存恢复的回归。 + +crates/ely-files/src/session.rs:445 — 同文件聚焦仍受登记顺序影响:匹配目标前遇到无关路径的权限错误就提前返回,违反 §6.1。macOS 隔离测试先打开 restricted/first.md 为 Fresh(0),再打开 second.md 为 Fresh(1),撤销 restricted 目录访问权限;second.md 仍能正常读取,open(second.md, false) 与 is_open(second.md) 却均返回 PermissionDenied。上轮目标排在错误条目前的场景已通过,调换登记顺序后复现失败。修复方向:在传播其他条目的身份查询错误前完成已有会话匹配,保留确实无法判定时的错误与登记原子性;补充无关权限错误条目排在目标前后的聚焦回归。 diff --git a/tasks/reviews/C06.1-r8.md b/tasks/reviews/C06.1-r8.md new file mode 100644 index 0000000..773473b --- /dev/null +++ b/tasks/reviews/C06.1-r8.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/session.rs:172 — finish_load() 与第 189 行 fail_load() 缺少加载任务有效性校验,旧加载回调会污染成功 reload 后的当前会话,违反 FILE-02 与 §6.1。两项隔离测试分别执行 Session::loading(path)、外部修改、reload() 成功、edit(),再投递初始加载的延迟成功或失败回调:成功回调将 Dirty 改成 Saved,此时 revision=1、saved_revision=0,记录的指纹长度从新版本的 20 字节退回旧版本的 7 字节;失败回调将状态改成 LoadFailed,begin_save() 返回 LoadFailed,阻止当前有效内容保存。现有 31 项测试通过,两项延迟加载探针均失败。修复方向:加载成功与失败回调只允许更新仍有效的加载任务,reload 使旧加载失效;补充重载并编辑后旧加载成功、失败回调的回归,验证状态、指纹和后续保存能力。 diff --git a/tasks/reviews/C06.1-r9.md b/tasks/reviews/C06.1-r9.md new file mode 100644 index 0000000..66f47dd --- /dev/null +++ b/tasks/reviews/C06.1-r9.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/session.rs:501 — 文件共 502 行,违反 agents.md 第 5 条“文件不超过 500 行”。在项目根目录运行 `wc -l crates/ely-files/src/session.rs` 即返回 502。修复方向:清理文件内已无调用的代码(例如第 374–379 行的私有 `Key` 类型 `PartialEq` 实现),将文件控制在 500 行以内。 diff --git a/tasks/reviews/C06.2-r1.md b/tasks/reviews/C06.2-r1.md new file mode 100644 index 0000000..ace39db --- /dev/null +++ b/tasks/reviews/C06.2-r1.md @@ -0,0 +1,17 @@ +FAIL + +crates/ely-files/src/save.rs:40 — [P1] 固定 `.bak` 路径直接交给 `fs::copy`,会截断已有目标并跟随链接,违反 FILE-04 与 §6.1 的旧文件保留要求。macOS 实测:创建 `report.md.bak` 为 `report.md` 的硬链接,把目录设为 0555,调用 `save_with_backup`;复制先将原文件清空,随后创建临时文件返回 PermissionDenied,保存失败后原文件已变成零字节。目录可写时函数返回成功,但备份仍是零字节;`.bak` 指向其他文档的符号链接也会导致该文档被覆盖。修复方向:以排他创建方式取得独立备份文件,处理已有名称和链接冲突,验证备份完成后再替换目标;增加上述失败路径回归测试。 + +crates/ely-files/src/save.rs:17 — [P1] 保存路径没有执行 FILE-05 要求的外部修改检查。`Session::external_changed` 只有测试调用,`begin_save` 仅检查手动设置的冲突标记,`save_atomic` 与 `save_with_backup` 直接写入。实测:加载文件、产生本地编辑、由外部改写文件,在文件监听通知到达前执行 `begin_save → save_atomic → finish_save`,外部内容被覆盖,会话进入 Saved。修复方向:将加载指纹与保存目标绑定,在保存流程内检查并传播检查错误和冲突,冲突时保留两个版本供重新加载、保留本地副本或比较;回归测试须覆盖尚未收到外部通知的保存。 + +crates/ely-files/src/save.rs:61 — [P1] 导出防覆盖只比较路径文本,同一文件的路径别名可以绕过检查,违反 FILE-06。实测:正在编辑 `/tmp/.../report.md`,创建 `child` 目录,将导出目标设为 `/tmp/.../child/../report.md`;`export` 返回成功,源文件内容变成 `%PDF-export`。修复方向:按实际解析后的目标路径和文件身份校验是否与编辑源文件重合,保留路径解析错误,并增加父目录别名、符号链接目录及大小写别名的覆盖测试。 + +crates/ely-files/src/save.rs:19 — [P2] 原子替换直接使用临时文件的权限,保存会改变已有文档的访问权限。macOS 实测:将已有文件权限设为 0640,执行 `save_atomic` 成功后权限变成 0600,原来通过组权限读取该文档的用户失去访问权。修复方向:替换前保留原目标的适用权限和访问控制属性,将复制失败作为保存错误处理;增加保存前后访问权限一致的回归测试。 + +crates/ely-files/src/save.rs:48 — [P2] 备份名称无条件在完整文件名后追加 `.bak`,使合法长文件名无法保存,违反 FILE-09。实测:先创建文件名为 250 个 `x` 加 `.md` 的文件,普通文件写入成功,`save_with_backup` 因生成 257 字节的文件名返回 InvalidFilename / File name too long。修复方向:为内部备份生成符合目标文件系统长度限制的独立名称,覆盖接近文件名上限的保存测试。 + +crates/ely-files/src/save.rs:89 — [P2] 附件重名处理直接追加数字后缀,也会把合法文件名扩展到文件系统上限之外,违反 FILE-09。实测:文件名为 251 个 `x` 加 `.png`,首次导入成功,第二次导入生成 257 字节的 `...-1.png`,返回 InvalidFilename。修复方向:在生成重名目标时预留后缀空间并安全处理非 ASCII 文件名,增加长文件名重复导入测试。 + +crates/ely-files/src/save.rs:108 — [P2] 单独文件名的 `Path::parent()` 返回空路径,`unwrap_or(".")` 保留了这个空值,随后 `statfs("")` 失败。macOS 实测:在有效工作目录调用 `atomic_capable(Path::new("report.md"))`,返回 NotFound / No such file or directory,导致合法相对目标无法完成保存能力检查。修复方向:将无目录分量的目标解析到当前目录,再探测对应存储介质;增加单独相对文件名测试。 + +crates/ely-files/src/session.rs:501 — [P2] 文件实测为 501 行,超过 agents.md 规则 5 的 500 行上限。修复方向:将该文件收至 500 行以内,并在检查中覆盖此限制。 diff --git a/tasks/reviews/C06.2-r2.md b/tasks/reviews/C06.2-r2.md new file mode 100644 index 0000000..5403bbc --- /dev/null +++ b/tasks/reviews/C06.2-r2.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-files/src/save.rs:253 — [P1] 新增的统一保存入口始终调用 `save_atomic`,跳过 `atomic_capable` 和 `save_with_backup`,违反 §6.1 对非本地目标保留上一份文件并提示限制的要求。隔离验证中,通过 macOS `statfs` 拦截清除 `MNT_LOCAL`,确认 `atomic_capable(target)` 返回 false;随后调用 `save`,结果仍为成功、会话进入 Saved,目录仅留下新内容的目标文件,旧版本已消失。修复方向:在统一保存流程内探测目标能力,为受限目标执行保留旧版本的保存路径,并向调用者返回限制与备份信息;覆盖能力为 false 的分支。 + +crates/ely-files/src/save.rs:73 — [P1] 上轮权限保留问题仍存在:`COPYFILE_ACL` 被写成 `1 << 2`,macOS 系统头文件中该值实际为 `COPYFILE_XATTR`,ACL 的值是 `1 << 0`。实测给 0644 文档添加 `user:nobody deny read` ACL,调用 `save_atomic` 返回成功后,该 ACL 条目消失,文件访问限制被放宽。现有测试仅核对 mode 位。修复方向:使用正确的 ACL 标志并传播复制失败,增加保存前后 ACL 一致的回归测试。 + +crates/ely-files/src/save.rs:133 — [P1] 新备份按默认创建权限落盘,遗漏源文件的访问限制。实测在 `/private/tmp` 下的 0755 目录创建 0600 文档,在 umask 022 下执行 `save_with_backup`,得到的备份权限为 0644,其他本机用户因此可以读取原本受保护的文档内容。修复方向:排他创建备份时先限制访问,在复制内容前应用源文件适用的权限和 ACL,任何失败均传播;增加共享目录中的私有文件备份回归测试。 + +crates/ely-files/src/save.rs:63 — [P1] 保存通过符号链接加载的文档时,`persist(path)` 替换链接本身,使会话与实际加载的文件分离。实测创建 `alias.md -> report.md`,通过 `Session::load(alias.md)` 加载并编辑,再执行 `save`;函数成功且状态为 Saved,`alias.md` 已变为普通文件,`report.md` 仍为旧内容。后续从原文件路径打开时看不到已保存的编辑。修复方向:将保存目标解析到实际加载文件,在该目标目录暂存并替换,保留链接关系,同时让外部修改检查针对同一目标;补充经文件符号链接保存的回归测试。 + +crates/ely-files/src/save.rs:126 — [P2] 备份名称只有 100 个候选,而每次成功保存都会永久占用一个,导致正常重复保存达到固定上限后持续失败。实测对同一 `report.md` 连续调用 `save_with_backup`,前 100 次成功,第 101 次返回 AlreadyExists / `no free backup name`;目录仍可写且有可用空间。修复方向:使用能够持续生成排他备份名称的策略,保持已有文件保护,并增加超过 100 次连续保存的回归测试。 + +crates/ely-files/src/save.rs:30 — [P2] 上轮附件长文件名问题仍有遗漏:长度预算只截断 stem,suffix 超过 NAME_MAX 时仍被完整追加。实测源文件名为 `a.` 加 253 个 `x`,共 255 字节;首次导入成功,第二次生成 `-1.` 加 253 个 `x`,共 256 字节,返回 InvalidFilename / File name too long。修复方向:对完整候选名统一分配长度预算,覆盖扩展名本身接近上限的情况,并增加长扩展名重复导入测试。 diff --git a/tasks/reviews/C06.2-r3.md b/tasks/reviews/C06.2-r3.md new file mode 100644 index 0000000..0f64cb5 --- /dev/null +++ b/tasks/reviews/C06.2-r3.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/save.rs:307 — [P1] 能力探测使用传入路径的父目录,实际保存却在 `resolved(path)` 指向的目录替换文件;经本地符号链接保存网络盘文件会跳过旧版本备份,违反 §6.1。macOS 隔离探针仅对目标目录的 `statfs` 清除 `MNT_LOCAL`:直接保存目标返回备份且保留旧内容;经本地目录中的符号链接保存同类目标时,`atomic_capable(alias)` 返回 true,`save` 返回 `backup=None`,会话进入 Saved,目标旧内容消失。修复方向:能力探测、备份与替换统一使用解析后的实际目标,并增加本地链接指向受限卷的回归测试。 + +crates/ely-files/src/save.rs:130 — [P2] 备份名在追加唯一后缀后整体截断,截掉区分候选名的部分,导致合法长文件名的保存卡在碰撞循环,违反 FILE-09。实测 255 个 `x` 的文件首次调用 `save_with_backup` 就持续尝试原文件名,3 秒超时后终止;250 个 `x` 加 `.md` 的文件前十次保存成功,第十一次生成的名称又与 `-1` 候选相同,同样超时。现有长文件名测试仅保存一次,105 次测试仅使用短文件名。修复方向:按完整唯一后缀预留长度,再截断原名称,保证每次碰撞产生新候选;补充满长名称首次保存和长名称连续保存超过十次的回归测试。 + +crates/ely-files/src/save.rs:219 — [P2] 附件重名候选也在追加计数器后整体截断,长 stem 会吞掉计数器,使重复导入持续检查同一已存在路径,违反 FILE-09。实测无扩展名、名称为 255 个 `x` 的附件首次导入成功,第二次导入卡在 `while target.try_exists()`,3 秒超时后终止;源文件和目录均可正常读写。现有长扩展名测试通过,但没有覆盖满长 stem。修复方向:为计数器保留长度预算,使截断后的候选仍随计数器变化,并补充满长无扩展名附件的重复导入测试。 diff --git a/tasks/reviews/C06.2-r4.md b/tasks/reviews/C06.2-r4.md new file mode 100644 index 0000000..0c1e7cf --- /dev/null +++ b/tasks/reviews/C06.2-r4.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/save/tests.rs:364 — [P2] 新增符号链接回归直接向 `save_mode` 传入真实路径和 `backup=true`,且链接与目标位于同一目录,覆盖不到上一轮出错的 `save → resolved → atomic_capable` 流程。上一轮三个行为缺陷已分别复验通过,但在临时副本中仅将 `save` 的 `atomic_capable(&target)` 回退为 `atomic_capable(&path)`,全部 58 项测试仍通过;独立 macOS 探针只对另一目录的真实目标清除 `MNT_LOCAL`,经本地链接保存时随即复现 `backup=None`、状态为 Saved、旧版本丢失。修复方向:通过公开 `save` 入口测试跨目录链接,并让能力探测仅对真实目标目录返回受限;断言备份的位置及旧内容,确保该单行回退会使回归测试失败。 diff --git a/tasks/reviews/C06.2-r5.md b/tasks/reviews/C06.2-r5.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C06.2-r5.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C06.3-r1.md b/tasks/reviews/C06.3-r1.md new file mode 100644 index 0000000..55f3007 --- /dev/null +++ b/tasks/reviews/C06.3-r1.md @@ -0,0 +1,17 @@ +FAIL + +crates/ely-files/src/recovery.rs:207 — [P1] `restore_copy` 使用 `std::fs::copy`,会截断已有目标并跟随目标符号链接,违反 §6.3 的恢复生成新副本要求。实测将包含 `recovered` 的恢复点复制到已有 `source.md`,或指向该文件的 `restored.md` 符号链接,均返回 `Ok(())`,原内容 `original` 被覆盖。修复方向:排他创建恢复目标,目标已存在时返回错误或选择新名称,保证路径别名也无法覆盖源文件;补已有文件、符号链接与硬链接回归测试。 + +crates/ely-files/src/recovery.rs:111 — [P1] 恢复内容直接写入正式 `.bin`,枚举时缺少完整提交校验,失败写入会成为可恢复内容。子进程设置 `RLIMIT_FSIZE=1024` 后写入 8192 字节,`write_once` 返回错误、`last_ok` 为 `None`,重建 `Recovery` 后却列出长度仅 1024 的恢复点。清理也有同类中断问题:在删除 `.bin` 后留下 `.cursor`,`points()` 会返回不存在的内容路径,后续写入在 `bytes()` 中持续报 `NotFound`。修复方向:暂存并校验、同步完整恢复点后原子发布,枚举只认可已提交内容,并让中断清理可恢复;补部分写入及清理中断测试。 + +crates/ely-files/src/recovery.rs:92 — [P1] `last_ok` 在清理前更新,清理能够删除全部恢复点,随后仍返回成功及已删除路径,违反 §6.2 对最后成功恢复时间和持久化状态的要求。实测容量为 4 字节,先写入 `old!`,再写入 `large`:两份内容全部被删除,第二次结果仍为 `Ok(Some(path))`,`last_ok` 变成第二次时间,`points()` 为空;默认容量下单个文档超过 64 MiB 同样触发。修复方向:提交前核算单点容量,超限明确失败并保留最近有效恢复点,最终确认恢复点仍可用后再更新成功状态;补超大单点测试。 + +crates/ely-files/src/recovery.rs:267 — [P1] 放弃状态只记录在随后会被 `Crash::open` 覆盖的 `session.lock` 中,恢复点没有会话归属或持久放弃状态。实测先生成 `explicitly discarded` 并调用 `discard()`,再由新子进程打开同一文档目录并在首次恢复写入前遭到 SIGKILL;下次检查返回 `Crashed`,恢复列表重新包含已放弃内容,违反 §6.2。修复方向:将放弃结果与对应会话或恢复点持久关联,保证下一次会话开启及崩溃后仍排除该内容;补真实进程终止回归测试。 + +crates/ely-files/src/recovery.rs:75 — [P1] 加密文档恢复被无条件拒绝,§18.2 要求的加密恢复与系统密钥保护路径缺失。`Recovery::sensitive(...).write_once(...)` 对所有输入直接返回 `PermissionDenied`,代码和依赖均没有获取恢复密钥、加密写入或解密恢复的入口,因此已解锁的加密文档也会永久失去崩溃恢复能力。现有测试仅验证拒绝写入。修复方向:接入成熟加密库及系统密钥保护,验证加密恢复往返、磁盘内容保密和密钥无法解锁时的明确失败。 + +crates/ely-files/src/recovery.rs:31 — [P2] 10 秒间隔仅为未被执行路径使用的常量,模块提供的写入入口同步完成全部 I/O,缺少 §6.2 要求的后台定时恢复调度。源码检索确认没有调度调用方;实测在时间 100 秒和 101 秒连续调用会立即产生两个恢复点。`running` 仅在同一个持有独占 `&mut self` 的同步调用内设为真再清零,现有并发测试直接篡改私有字段,未验证实际后台任务重叠。修复方向:实现可执行的 10 秒后台调度及同文档任务占用管理,以可控时钟和延迟写入验证周期、跳过重叠任务及失败后继续调度。 + +crates/ely-files/src/recovery.rs:120 — [P2] 七天清理仅由新恢复点写入触发,重启与读取恢复列表均保留过期副本,违反 §6.2 的最长保留时间。实测写入时间为当前时间减八天的恢复点,随后销毁并重新创建 `Recovery`;`points()` 仍返回该内容,磁盘文件仍存在。用户只查看恢复列表或文档之后没有新编辑时,副本会无限期保留。修复方向:在恢复目录打开、扫描或维护流程中按当前时间执行过期清理,并测试重启后没有新写入的情况。 + +crates/ely-files/src/recovery.rs:182 — [P2] 容量统计只累计 `.bin`,光标与历史文件完全漏算,实际恢复目录可突破用户设置的上限。实测容量为 4 字节,写入 1 字节内容及 4096 字节光标字符串后返回成功,目录实际占用 4097 字节。修复方向:容量核算覆盖每个恢复点的全部文件,以带元数据的恢复点验证容量清理和超限失败。 diff --git a/tasks/reviews/C06.3-r10.md b/tasks/reviews/C06.3-r10.md new file mode 100644 index 0000000..6c6c27d --- /dev/null +++ b/tasks/reviews/C06.3-r10.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/key.rs:61 — [P2] r9 的账户查询并发缺陷仍存在:`getpwuid` 返回共享静态存储,随后读取 `pw_dir` 时另一线程可覆盖它。macOS 构建及 89 项测试通过;Linux 隔离副本使用项目锁文件中的同版本依赖,普通账户 `reviewer` 下完整测试首次通过,重复运行现有 `key::tests::the_account_home_holds_under_concurrent_queries` 时第 8 次退出 101,`key/tests.rs:251` 实际目录为 `/root`,期望为 `/home/reviewer`。这会把 `RecoveryKey::load()` 的锁目录切到 `/root/.local/share/ElyOffice`,普通账户随即因权限错误无法加载恢复密钥,阻断加密文档恢复(PRD §18.2)。修复方向:使用调用者独占缓冲区的 `getpwuid_r`,显式处理缓冲区不足、查询失败和账户缺失,并在 Linux 普通账户下验证并发查询回归。r9 的用户名硬编码问题已修复,修改 HOME 的子进程测试在两平台均通过。 diff --git a/tasks/reviews/C06.3-r11.md b/tasks/reviews/C06.3-r11.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C06.3-r11.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C06.3-r2.md b/tasks/reviews/C06.3-r2.md new file mode 100644 index 0000000..9eeb2ce --- /dev/null +++ b/tasks/reviews/C06.3-r2.md @@ -0,0 +1,17 @@ +FAIL + +crates/ely-files/src/recovery.rs:145 — [P1] 加密恢复点分两次发布 `.enc` 和 `.nonce`,枚举却只凭 `.enc` 认定恢复点存在,上一轮的完整提交缺陷仍存在于加密路径。隔离探针让 nonce 写入失败,留下已发布密文;移除故障后,`points()` 与后续 `write_once()` 均持续返回 `NotFound`,连已有有效恢复点也无法列出。第 258 行先删 nonce 再删密文,清理中断同样复现。修复方向:以完整恢复点为单位原子提交,枚举识别提交状态,清理中断保持可恢复,并覆盖发布与删除各阶段的故障测试。 + +crates/ely-files/src/recovery.rs:107 — [P1] 容量预检遗漏加密包的 4 字节长度头、16 字节认证标签和 12 字节 nonce,上一轮“清空全部恢复点后仍报告成功”仍可复现。容量设为 40 字节,先写入 `old!`,再写入 `123456789`:第二点实际占 41 字节,清理删除两点,结果却为 `Ok(Some(path))`,`last_ok` 更新且 `points()` 为空。修复方向:按实际完整落盘大小做提交前容量校验,超限保留旧点,确认新点仍可恢复后才更新成功状态;补加密容量边界回归。 + +crates/ely-files/src/recovery.rs:53 — [P1] `open()` 使用默认 64 MiB 容量先清理,再允许调用方通过 `with_limits` 应用用户设置,会删除用户容量范围内的有效恢复点。实测以 70 MiB 上限写入 65 MiB 内容,随后执行 `Recovery::open(dir, now)?.with_limits(20, 70 MiB)`,恢复列表变空、文件已删除。修复方向:打开和清理前接收用户容量设置,保证重启前后的保留策略一致,并补自定义容量重开测试。 + +crates/ely-files/src/recovery.rs:419 — [P1] 系统恢复密钥的读取与创建存在覆盖竞态,且所有读取错误都进入生成新密钥的分支。两个应用首次同时读取同一 `SERVICE/ACCOUNT`,均读到缺失后各自生成密钥;所用 `set_generic_password` 会更新已有项,因此最后只保留一把密钥。对原样抽取的 `RecoveryKey::load` 使用隔离 Keychain 替身和屏障实测,两次调用均成功、返回密钥不同,其中一个应用生成的恢复点在使用持久密钥重开时返回 `InvalidData`。另注入读取 `PermissionDenied`,后续写入可成功时会直接替换旧密钥。修复方向:只对明确的条目缺失创建密钥,采用排他创建,竞态失败后读取已建立的密钥;其他读取错误原样暴露。补并发初始化及拒绝访问回归。 + +crates/ely-files/src/recovery.rs:441 — [P1] 上一轮要求的系统密钥保护路径在 Windows、Linux 仍为空实现,`RecoveryKey::load()` 无条件返回 `Unsupported`。两平台即使系统凭据库可用、文档已解锁,也无法通过当前接口建立可跨崩溃恢复的受保护密钥,违反 §18.2。Windows、Linux 编译检查通过仅证明该拒绝分支可编译。修复方向:接入 Windows 系统凭据能力和 Linux Secret Service,并验证跨进程密钥读取、加密恢复往返及凭据库不可用时的明确失败。 + +crates/ely-files/src/recovery.rs:80 — [P2] 上一轮的后台调度缺陷仍存在:新增 `tick()` 仅返回到期布尔值,全项目只有测试调用;全部写入和清理仍同步完成,缺少实际执行的 10 秒后台恢复任务。重放时 `tick(100)` 本身没有写入,时间 100 秒与 101 秒的两次 `write_once` 仍立即产生两点;`running` 仍只在持有独占 `&mut self` 的同步调用内变化,并发测试继续直接修改私有字段。修复方向:落实后台周期调度及同文档任务占用管理,以可控时钟和延迟写入验证真实调度、重叠跳过及失败后的继续执行。 + +crates/ely-files/src/recovery.rs:60 — [P2] 七天保留修复仅覆盖普通 `open()`;加密入口 `sensitive()` 和恢复列表读取均跳过过期清理,上一轮的超期保留场景仍成立。实测写入时间为当前时间减八天的加密恢复点,销毁后以同一密钥重新创建 `Recovery::sensitive`,`points()` 仍列出该点且文件仍在;已打开后长期没有新写入的目录也缺少维护路径。修复方向:在加密与普通恢复的打开、扫描或维护流程统一执行年龄限制,并补无新写入的加密重启与持续打开测试。 + +crates/ely-files/src/recovery.rs:194 — [P2] 加密点把缺失光标编码为空串,解密枚举又无条件包装为 `Some`,丢失 §6.3 要求的恢复能力区分。实测 `Recovery::sensitive(...).write_once(content, None, time)` 后,`points()[0].cursor` 为 `Some("")`;上层据此判断有光标恢复数据时会得到虚假状态。修复方向:在加密包中保留光标是否存在的信息,完整往返 `None` 与 `Some`,补未提供光标时的回归。 diff --git a/tasks/reviews/C06.3-r3.md b/tasks/reviews/C06.3-r3.md new file mode 100644 index 0000000..2f32658 --- /dev/null +++ b/tasks/reviews/C06.3-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-files/src/recovery.rs:107 — [P1] 加密容量预检遗漏第 143 行新增的 1 字节光标存在标志,上轮容量缺陷仍可导致全部恢复点丢失。隔离实测:容量设为 40 字节,先写入 `old!`,再写入 `12345678`,第二点实际占 41 字节;清理删除两点,`write_once` 却返回 `Ok(Some(path))`,`last_ok` 更新到第二次时间,`points()` 为空。现有 76 项测试全部通过,新增容量边界断言失败。修复方向:统一按完整序列化结果核算容量,提交前拒绝超限点并保留旧点,确认新点仍存在后更新成功状态;补加密容量边界回归。 + +crates/ely-files/src/recovery.rs:436 — [P1] 密钥创建仍使用会覆盖已有凭据的 `set_secret`,随后重读无法消除上轮初始化竞态。通过 keyring 可注入凭据后端与屏障复验:两个调用先同时读到 `NoEntry`,A 写入并重读返回后,B 再写入并重读;两次均成功,返回密钥不同,持久凭据只保留 B 的密钥。A 生成的恢复点使用持久密钥重开时返回 `InvalidData`。macOS 后端明确采用覆盖已有项的语义;拒绝读取时覆盖旧密钥的分支已修复。修复方向:对跨进程初始化采用排他创建或锁内重查,保证成功返回的密钥持续有效;补上述交错顺序与跨实例解密回归。 + +crates/ely-files/src/task.rs:69 — [P1] `RecoveryTask::drop` 只记录日志,后台线程被分离后继续持有恢复状态和快照闭包,能够撤销用户的显式放弃。隔离实测:首点写入后暂停下一次快照,销毁任务句柄,再对同一目录调用 `Recovery::discard()`;此时标记存在、恢复列表为空。释放快照后,遗留线程重新生成恢复点并清除 `.discarded`,实测得到 `discarded_marker=false`、`points_after_discard=1`,违反 §6.2。修复方向:任务销毁时可靠取消并结束工作线程,正在生成快照的任务在提交前复查取消状态,保证销毁完成后再无写入;补销毁、显式放弃和延迟快照的回归。 + +crates/ely-files/src/task.rs:57 — [P2] `last_ok()` 与整次同步写入、清理共用同一互斥锁;后台 I/O 阻塞期间,调用方也无法取得上一份成功恢复时间,违反 §6.2 对大文件未完成时显示该时间的要求。临时副本仅在 `save_atomic` 注入 800 毫秒 I/O 延迟,已有成功恢复点时查询 `last_ok()` 仍阻塞约 817 毫秒,直到新写入完成才返回;真实慢盘或挂起写入会同样阻塞状态查询线程。修复方向:让已完成状态的读取独立于进行中的磁盘 I/O,写入完成后发布新状态;补延迟写入期间可立即读取旧成功时间的回归。 diff --git a/tasks/reviews/C06.3-r4.md b/tasks/reviews/C06.3-r4.md new file mode 100644 index 0000000..fab2fdf --- /dev/null +++ b/tasks/reviews/C06.3-r4.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/key.rs:71 — [P1] 取得初始化锁后直接调用 `init`,缺少锁内重读,上轮密钥覆盖竞态仍成立。隔离后端实测:B 首次读取已取得 `None` 后暂停,A 完成初始化并返回,随后 B 继续、取得已释放的锁并覆盖凭据;两次调用均成功且密钥不同,A 的恢复点用最终持久密钥恢复时返回 `InvalidData`。原有 81 项测试全部通过,受控交错回归失败。修复方向:在取得排他锁后重新读取凭据,仅在锁内确认缺失时生成密钥,并覆盖该交错顺序与跨实例解密回归。 + +crates/ely-files/src/key.rs:76 — [P1] 等待初始化超过 1 秒即删除锁,仍在运行的持有者会失去排他保护。隔离实测:让 A 的凭据写入暂停超过 1 秒,B 删除 A 的锁、写入并成功返回密钥;恢复 A 后,A 再次覆盖凭据并成功返回另一密钥,B 已生成的恢复点随即无法解密。系统凭据解锁或慢响应可触发同一顺序;取得锁后重读也无法修复这个场景。修复方向:采用随持有进程退出而释放的系统锁,存活持有者等待超时应返回明确错误;分别传播凭据读取与锁检查错误,补慢初始化并发回归。 + +crates/ely-files/src/task.rs:27 — [P2] 独立状态锁初始化为 `None`,丢失传入 `Recovery` 已有的成功时间。隔离实测:先成功 `write_once`,再以 10 秒间隔启动同一对象的后台任务,立即调用 `task.last_ok()` 得到 `None`;该状态持续到下一次写入尝试结束,大文件写入阻塞时也无法显示已有成功时间,违反 §6.2。修复方向:启动时用传入对象的 `last_ok()` 初始化发布状态,并补首次后台写入尚未完成时保留已有成功时间的回归。 diff --git a/tasks/reviews/C06.3-r5.md b/tasks/reviews/C06.3-r5.md new file mode 100644 index 0000000..3557712 --- /dev/null +++ b/tasks/reviews/C06.3-r5.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/key.rs:114 — [P1] 空锁文件以及读取、解析失败均被当作持有者死亡,随后删除锁;创建锁到写入 PID 之间仍存在丢失排他保护的窗口。隔离副本仅插入调度屏障复现:A 创建锁后暂停,B 读取空锁并接管、暂停在凭据写入前;A 恢复并成功生成密钥及恢复点,B 随后覆盖凭据并成功返回另一密钥。最终持久密钥恢复 A 的副本时报 `InvalidData`。现有 85 项测试全部通过,该受控交错回归失败。修复方向:使用随持有者退出释放的系统排他锁,传播锁操作错误,并覆盖尚未写入 PID 时的并发初始化与最终密钥解密回归。 + +crates/ely-files/src/key.rs:73 — [P1] 取得锁后的错误通过 `?` 提前返回,锁文件只在成功路径清理;新存活检测随后把已结束的初始化视为仍在运行。隔离实测:后端首次写入返回 `PermissionDenied`,模拟取消系统凭据解锁;后端恢复可用后,同一进程再次调用仍返回 `TimedOut`,锁文件保留,后续尝试持续失败。加密文档的恢复初始化因此要等持有进程退出才能恢复。修复方向:用作用域锁守卫覆盖所有成功与错误出口,补暂时读取或写入失败后原进程重试成功的回归。 + +crates/ely-files/src/key.rs:136 — [P1] `#[cfg(unix)]` 分支使用 macOS 的 `libc::__error()`,Linux 的 libc 接口没有该函数,导致 Linux 编译失败。将该函数原样提取,使用项目锁定的 libc 0.2.189 和 Rust 1.96.0 对 `x86_64-unknown-linux-gnu` 编译,实际报 `E0425: cannot find function __error in crate libc`;整 crate 的 Linux 检查另受本机缺少 libdbus 交叉编译环境阻挡。修复方向:使用标准库读取系统错误,或按操作系统选择 errno 接口,并加入 Linux 目标编译检查。 diff --git a/tasks/reviews/C06.3-r6.md b/tasks/reviews/C06.3-r6.md new file mode 100644 index 0000000..5e233fe --- /dev/null +++ b/tasks/reviews/C06.3-r6.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/key.rs:60 — [P1] 初始化锁放在调用方传入的目录,系统凭据却固定为第 43 行的全局 `ElyOffice Recovery/recovery-key`;不同目录的锁无法互斥保护同一凭据。两个应用使用各自数据目录首次初始化时,可各自取得锁并在锁内读到缺失,随后依次覆盖全局密钥。隔离后端加屏障实测:A、B 两次初始化均成功且返回不同密钥;A 返回后生成加密恢复点,B 随后完成初始化,最终持久密钥恢复 A 的副本返回 `InvalidData: the sealed point is unreadable`。原有 85 项测试全部通过,跨应用目录的受控交错回归失败,违反 §18.2 的加密崩溃恢复要求。修复方向:使锁与凭据采用一致的共享范围,为同一全局凭据固定唯一跨进程锁位置,或将凭据按稳定身份隔离;补不同应用目录并发初始化及最终持久密钥解密既有恢复点的回归。 diff --git a/tasks/reviews/C06.3-r7.md b/tasks/reviews/C06.3-r7.md new file mode 100644 index 0000000..2362bbe --- /dev/null +++ b/tasks/reviews/C06.3-r7.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-files/src/key.rs:55 — [P1] 第 6 轮的锁与凭据作用域缺陷在不同数据根目录下仍成立:`BaseDirs::data_local_dir()` 在 Linux 随进程的 `XDG_DATA_HOME` 改变,第 43 行的凭据身份始终是全局 `ElyOffice Recovery/recovery-key`。同一用户在同一桌面会话中用不同 `XDG_DATA_HOME` 启动两个应用并首次初始化时,两者取得不同锁,能够覆盖同一系统凭据。Linux 容器构建后,以共享的隔离凭据后端调用未修改的 `RecoveryKey::load()`,受控交错实测两次加载均成功、返回密钥不同;A 先生成的加密恢复点用最终持久密钥枚举时报 `InvalidData: the sealed point is unreadable`。相同数据根目录的对照通过。修复方向:将锁和凭据绑定到同一稳定身份,使凭据命名空间随数据根目录隔离,或为同一系统凭据选择独立于进程数据目录配置的共同锁;补不同 `XDG_DATA_HOME` 的跨进程初始化和最终密钥解密回归。 + +crates/ely-files/src/key.rs:65 — [P1] 锁前读取可将尚未持久化的密钥成功返回给另一个应用。当前 Linux 配置 `linux-native-sync-persistent` 的 keyring 3.6.3 后端先写共享 keyutils 缓存,再写 Secret Service;后一步失败时会删除刚建的缓存。A 持锁初始化、密钥已进入缓存而 Secret Service 尚未完成时,B 在这里读取缓存并直接返回,绕过初始化锁;A 随后失败回滚,B 已生成的恢复点失去可重新加载的密钥。按该依赖的实际发布与回滚顺序构造隔离后端,通过公开入口实测:B 成功返回并写入加密恢复点,A 初始化失败后密钥消失,重试产生的新持久密钥读取该点报 `InvalidData`。临时副本将读取移入锁内后,同一探针通过;原有 85 项测试全部通过。修复方向:让读取等待初始化事务结束,并以持久凭据后端的完成结果判定密钥可用;补初始化中途可见、持久化失败回滚及随后重试的并发回归。 diff --git a/tasks/reviews/C06.3-r8.md b/tasks/reviews/C06.3-r8.md new file mode 100644 index 0000000..8853cb9 --- /dev/null +++ b/tasks/reviews/C06.3-r8.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-files/src/key.rs:55 — [P1] 共同锁仍依赖进程环境中的 `HOME`,凭据身份则固定为 `ElyOffice Recovery/recovery-key`;`directories` 6.0.0 在 macOS/Linux 的 `home_dir()` 优先读取 `HOME`,同一系统用户、同一凭据服务下的两个应用使用不同 `HOME` 时会取得不同锁,并发覆盖同一恢复密钥。隔离双进程探针调用原样的 `RecoveryKey::load()`,仅以共享测试凭据后端控制首次读取交错:A、B 都读到缺失,A 完成初始化并生成加密恢复点,B 随后覆盖凭据;两次加载均成功且密钥不同,最终持久密钥读取 A 的恢复点返回 `InvalidData: the sealed point is unreadable`。macOS 与 Linux 均复现;相同 `HOME`、不同 `XDG_DATA_HOME` 的对照及 r7 的失败回滚回放均通过。现有 macOS 87 项、Linux 86 项测试及 Windows 目标编译检查通过,新增路径单测只检查传入常量的拼接,未覆盖实际账户与环境变量的作用域差异。修复方向:用稳定的操作系统账户身份定位共同锁,或将锁与凭据绑定到同一规范化命名空间;补同一账户、不同 `HOME` 的跨进程初始化及最终持久密钥解密既有恢复点的回归。 diff --git a/tasks/reviews/C06.3-r9.md b/tasks/reviews/C06.3-r9.md new file mode 100644 index 0000000..ded7670 --- /dev/null +++ b/tasks/reviews/C06.3-r9.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-files/src/key.rs:61 — [P2] 新增账户查询通过 `getpwuid` 取得共享静态存储,随后在同步保护之外读取 `pw_dir`;Linux 上另一线程的账户查询可改写该存储,使恢复密钥锁目录漂移。普通账户 `/home/reviewer` 下,一线程执行原样的 `account_home()`,另一线程调用 `getpwuid(0)`,第 6 次查询即返回 `/root`;通过原样公开入口 `RecoveryKey::load()` 与隔离凭据后端回放,第 9627 次加载出现 `PermissionDenied`,已有恢复密钥因此加载失败。临时副本改用调用者持有缓冲区的 `getpwuid_r` 后,同一并发探针 20000 次加载通过。修复方向:采用可重入账户查询,处理缓冲区不足、查询失败与账户缺失,并补并发查询其他账户时锁目录始终属于当前账户的回归。r8 的不同 `HOME`/`XDG_DATA_HOME` 双进程初始化回放已通过,两份恢复点均可用最终持久密钥解密。 + +crates/ely-files/src/key/tests.rs:241 — [P2] 新增 Unix 回归测试将系统账户目录硬编码为 `/Users/zacharyzhang`,使其他账户及 Linux 的正确实现必然测试失败。macOS 本机 88 项测试通过;Linux 同版本依赖构建后,以普通账户运行得到 86 项通过、1 项失败,此处实际值为 `/home/reviewer`,期望值为 `/Users/zacharyzhang`。该测试也没有改变 `HOME`,无法锁住 r8 的环境覆盖场景。修复方向:从当前测试账户取得基准,在独立子进程中分别设置不同 `HOME`,验证账户目录和共同锁保持一致,并验证最终持久密钥能解密各进程的恢复点。 diff --git a/tasks/reviews/C06.4-r1.md b/tasks/reviews/C06.4-r1.md new file mode 100644 index 0000000..1095b2f --- /dev/null +++ b/tasks/reviews/C06.4-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-files/src/policy.rs:102 — 保存完成只接收 `ok: bool`,成功时无条件清除最新编辑的计时,丢失保存期间新增修订的自动保存任务。复现:已有路径的 Mark 文件在 t=100 编辑 revision 1,t=101 返回 `SaveDue` 并取得保存票据;t=101.2 编辑 revision 2;随后 revision 1 落盘,`Session::finish_save` 正确保留 `Dirty`,但 `PolicyEngine::save_finished(true)` 清空计时,t=102.2 及之后持续返回 `Nothing`,磁盘停留在 revision 1。临时测试结合 `Session` 与 `save_atomic` 实测失败,违反 §6.2 的停输入 1 秒自动保存要求。修复方向:保存完成关联提交时的修订或编辑代次,仅清除该任务已经持久化的编辑;保留较新编辑的截止时间,并加入保存期间继续输入的回归测试。 + +crates/ely-files/src/policy.rs:64 — 无路径期间的编辑被直接丢弃,首次保存成功后 `set_has_path()` 也没有恢复待保存编辑的计时。复现:新建 Mark 文件,revision 1 开始首次显式保存;保存期间输入 revision 2,`edit()` 因 `has_path=false` 跳过记录;revision 1 落盘后设置路径,此时会话仍为 `Dirty`,后续 `tick()` 永远返回 `Nothing`。临时落盘测试实测失败;即使上一项修复为保留较新的计时,此处仍没有可保留的记录。修复方向:无路径期间也记录最新编辑和修订,仅阻止自动提交;首次保存取得路径后,为尚未持久化的编辑继续调度,并覆盖首次保存期间输入的测试。 + +crates/ely-files/src/policy.rs:74 — 防抖使用可回拨的 `SystemTime`,且把负时间差静默替换为零,导致 Mark 的 1 秒自动保存被系统校时推迟。复现:`edit(UNIX_EPOCH + 100s)` 后系统时钟回拨 60 秒,实际已经停止输入 1 秒时调用 `tick(UNIX_EPOCH + 41s)`,实测返回 `Nothing`;直到墙上时间追到 101 秒才返回 `SaveDue`,实际等待约 61 秒。修复方向:用 `Instant` 或可注入的单调时钟计算防抖期限,移除吞掉时间错误的默认值,并增加校时场景的回归覆盖。 diff --git a/tasks/reviews/C06.4-r2.md b/tasks/reviews/C06.4-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C06.4-r2.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C06.5-r1.md b/tasks/reviews/C06.5-r1.md new file mode 100644 index 0000000..d8d181d --- /dev/null +++ b/tasks/reviews/C06.5-r1.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-office/src/open.rs:229 — 恢复出的敏感文档丢失封存属性。`Source::Copy` 在第 163 行把 `tried` 设为 false,`reopening` 也只携带路径、时间和名称,因此新标签以 `sealed:false` 登记。实测:用固定测试密钥替代钥匙串读取,给原生 ElyDoc 提供含 `C065_SENSITIVE_SECRET` 的有效 `.enc` 恢复点;恢复后的下一轮生成 `.bin`,新 index 全为 `sealed:false`,直接解压该文件即可读取正文(/tmp/c065-sensitive-mock-result.log)。违反 C06.5 的加密文档封存要求。修复方向:把原恢复点的敏感属性传到新标签,在进程恢复和引擎重开后持续使用封存恢复点。 + +crates/ely-office/src/recover.rs:232 — 恢复交接提前删除唯一持久副本。发起异步 `load` 后立即删除旧恢复目录,第 224、244 行同时从持久索引移除旧条目;新标签的恢复点要等下一轮计时才生成,载入失败也失去原索引。实测:第一次启动从恢复点显示 `C065_UNSAVED`,此时恢复点数量为 0;立即再次 SIGKILL 并完整等待下一次启动,原内容消失,只剩新的空白标签(/tmp/c065-double-crash2-result.log、/tmp/c065-double-crash2.log)。违反 §6.2、§6.3 的持久恢复要求。修复方向:在新标签已有持久恢复点或用户明确处理该副本之后交接旧目录和索引,载入及解封失败保留原恢复条目;引擎重开的释放路径应用同一交接规则。 + +apps/ely-pdf/src/engine.rs:135 — 加密 PDF 的恢复快照沿用普通保存,快照仍需原 PDF 密码;恢复却通过 `Source::Copy` 使用空密码,`open.rs:190` 又仅为带 registry 的文件询问密码,导致恢复副本载入失败。实测:用 `aes128.pdf` 的 owner 密码打开、旋转页面、写快照,重新打开快照得到 `locked:true`;将此快照封存为有效 `.enc` 并交给原生 ElyPDF 恢复,日志报告 `did not open: a password is needed`,最终 index 为空(固定测试密钥仅替代钥匙串,/tmp/c065-encrypted-recovery2.log、/tmp/c065-encrypted-mock-result.log)。违反 C06.5 的加密文档崩溃恢复要求。修复方向:为封存恢复生成解封后可加载的 PDF 内容,并持续携带敏感属性;覆盖加密 PDF 的进程恢复和引擎重开。 + +crates/ely-office/src/stopped.rs:18 — worker 退出检测依赖视图先发出引擎错误,空闲 ElyPDF 缺少退出通知。实测:打开 `tests/fixtures/office/t.pdf`,等待页面绘制完成,单独 SIGKILL 其 `ely-pdf-worker`,再等待 13 秒;AX 树仍保留文件标签,故障提示和 `Reopen documents` 按钮均缺失,日志也未记录引擎退出(/tmp/c065-pdf-stopped-result.log、/tmp/c065-pdf-stopped-ax.log、/tmp/c065-pdf-stopped.log)。用户无法从窗口重新打开文档,违反 §6.3 及任务所声称的引擎退出提示。修复方向:通过 worker 退出通知更新 `Stopped` 并显示最近恢复时间和重开入口,覆盖空闲 PDF 的退出场景。 diff --git a/tasks/reviews/C06.5-r2.md b/tasks/reviews/C06.5-r2.md new file mode 100644 index 0000000..9e3b3e7 --- /dev/null +++ b/tasks/reviews/C06.5-r2.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-office/src/recover.rs:66 — 解封或载入失败的恢复点会在正常退出后的下一次启动被删除。`held`、`reopening` 虽保留在索引中,`end()` 仍清掉崩溃标记,随后 `start()` 第 57 行按 Clean 删除整个恢复目录。实测:提供有效封存 PDF 恢复点,钥匙串暂不可用时窗口提示恢复失败,索引和恢复点仍在;直接 Cmd+Q 正常退出,再启动时恢复点从 1 个变为 0(/tmp/c065-r2-held-result.log、/tmp/c065-r2-held-first-ax.log)。用户尚未保存或明确放弃该副本,唯一持久内容已丢失,R1 的失败保留修复仍存在此缺口。修复方向:持久记录尚待处理的恢复条目,正常退出后继续保留并重试,直到成功交接、保存或用户明确放弃。 + +crates/ely-office/src/recover.rs:216 — 恢复索引写入失败只进日志,恢复任务继续成功写点,界面隐去影响崩溃恢复的错误。实测:以空崩溃索引启动,将 `index.json` 设为 0444,打开 PDF 并旋转页面;生成 1 个 `.bin`,索引仍为 `[]`,日志报告 Permission denied,AX 树没有恢复失败提示;杀掉应用、恢复索引权限后重启,恢复点文件仍在,窗口没有恢复标签(/tmp/c065-r2-index-result.log、/tmp/c065-r2-index-first-app.log、/tmp/c065-r2-index-first-ax.log)。违反 §6.2 的恢复要求及暴露错误规则。修复方向:将索引持久化失败传回界面,保留待写条目并重试,确保报告成功的恢复点具有可供下次启动读取的持久索引。 + +crates/ely-office/src/recover.rs:291 — 接过旧恢复点时仅用 `at` 显示恢复提示,`Keeping.last` 仍为 None,故障条只读取这个值。实测:ElyPDF 从已有恢复点打开未命名副本,在下一轮快照前 SIGKILL worker;AX 树同时显示副本的恢复时间和 `Last recovery point: none yet`(/tmp/c065-r2-recovered-time-ax.log)。恢复点实际存在,§6.3 要求的最近恢复时间显示错误。修复方向:恢复交接时同步已有持久恢复点的时间,让故障条读取当前保留恢复点的实际最近成功时间,并覆盖首次恢复后立即停止引擎的场景。 diff --git a/tasks/reviews/C06.5-r3.md b/tasks/reviews/C06.5-r3.md new file mode 100644 index 0000000..9bb344f --- /dev/null +++ b/tasks/reviews/C06.5-r3.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-office/src/recover.rs:96 — 恢复索引直接覆写原文件,短写失败会破坏上一份有效清单,使已成功持久化的恢复点在下一次启动无法恢复,违反 §6.2、§6.3。具体复现:已有一个恢复点及有效索引,调用生产 `Keeping::save_index()`,用 `RLIMIT_FSIZE=16` 注入文件写入中途失败;调用返回 `FileTooLarge`,原索引只剩 `[{"slot":"old","`,恢复点仍有 1 个。恢复写入条件并正常退出后,`Keeping::start_in()` 返回 `InvalidData: EOF while parsing a string`;应用因第 138 行的启动错误关闭本次恢复功能,索引重试也无法在下次运行执行(/tmp/c065-r3-index-probe.log)。磁盘耗尽或进程在索引覆写期间退出同样会留下不完整索引。修复方向:先完整写入并校验暂存索引,再原子替换,失败时保留上一份有效索引,并覆盖短写与重启场景;临时副本采用暂存后替换,在同一故障下注入失败后原索引仍完整、再次启动成功(/tmp/c065-r3-index-atomic-control.log)。 diff --git a/tasks/reviews/C07.1-r1.md b/tasks/reviews/C07.1-r1.md new file mode 100644 index 0000000..214daab --- /dev/null +++ b/tasks/reviews/C07.1-r1.md @@ -0,0 +1,21 @@ +FAIL + +crates/ely-lok/Cargo.toml:17 — Windows 工作进程编译失败。实跑 `cargo check -p ely-lok --all-targets --locked --target x86_64-pc-windows-msvc`,`ffi.rs:283/314` 因缺少 `Win32_System_LibraryLoader` feature 报 E0432;`worker.rs:161/170` 无条件调用仅在 Unix 依赖中声明的 `libc::_exit`,报 E0433。补齐动态加载 feature,并为进程退出提供正确的 Windows 实现,再运行该目标检查。 + +crates/ely-lok/src/client.rs:32 — 所有平台都开启 `unipoll`,但 `worker.rs:117` 的非 macOS 分支直接读取 IPC,从未调用 `Lok::run_loop()`,内核主循环初始化缺失。Linux x86_64 隔离容器中,原代码构建成功;使用可用的 LibreOffice 7.4.7 内核核验启动模式,同一二进制在 `SAL_LOK_OPTIONS=unipoll` 下返回版本后加载 FODT 以 139 退出,清空该选项后加载成功并以 0 退出。锁定的 26.2 源码也在 unipoll 分支只调用 `InitVCL()`,将 `soffice_main()` 留给 `runLoop`。按平台接通内核循环,或在非 macOS 同步服务分支使用内核线程模式,并添加 Linux 文档加载回归测试。 + +crates/ely-lok/src/client.rs:48 — 启动握手仅记录版本,任何返回版本的内核都被接受,固定构建要求没有落实到加载边界。实测官方未打补丁的 26.2.6.3 dmg 被 `Worker::spawn` 接受;该构建与补丁版拥有相同 BuildId,却缺少本任务要求的 macOS 图块绘制能力。Linux 探针中的 7.4.7 也通过握手。加载前校验随应用发行的构建身份及补丁产物,握手核对完整版本;增加错误版本和未打补丁构建的拒绝测试。 + +crates/ely-lok/src/worker.rs:161 — 正常 SHUTDOWN 直接 `_exit(0)`,跳过文档注册表和内核清理;EOF 路径的 `quit` 同样直接退出。真实补丁内核打开 FODT 后调用 `Worker::shutdown()`,退出码为 0,但源文件旁的 `.~lock.source.fodt#`、profile 的 `.lock` 和隔离 TMPDIR 内的 `lu*.tmp` 仍存在;预先 `close_doc` 后仍留下内核临时目录。违反 LOK-10。关闭时先释放全部文档,再完成内核及其拥有的临时资源清理,覆盖显式 shutdown、Drop 和管道 EOF,并断言锁及临时文件恢复基线。 + +crates/ely-lok/src/client.rs:95 — `shutdown()` 丢弃发送与应答错误,并把任意已退出状态都返回为 `Ok(())`。注入能完成版本握手、收到 SHUTDOWN 后以 23 退出的工作进程,实测结果为 `shutdown=Ok(())`;上述真实 Linux 加载崩溃后也返回成功。校验应答和 `ExitStatus::success()`,将异常退出、协议错误及等待失败传给调用方,并覆盖这些失败路径。 + +crates/ely-lok/src/client.rs:56 — 字符串应答的解码与工作进程编码不匹配。服务端使用 `Pack::str` 写入四字节长度,客户端在此处和 `:78` 将整个 payload 转成文本。真实 BuildId 返回值以 `�\0\0\0` 开头,缺失文档的错误文本以 `N\0\0\0Unsupported URL` 开头,破坏版本解析及错误展示。使用 `Unpack::str` 解码版本和错误应答,并添加精确文本与无效载荷断言;现有 `contains(PINNED_BUILD)` 掩盖了该问题。 + +crates/ely-lok/src/worker.rs:227 — profile URL 由未经绝对化的路径直接拼接,隔离目录可能被静默忽略。实测以 `../real-profile` 调用 `Worker::spawn` 返回成功,但指定目录始终为空;同一内核使用该目录的绝对路径时创建了 `user/`。前者被编码为 `file://../real-profile`,无法代表调用方创建的本地目录,违反 LOK-01。创建目录后规范化为绝对路径,再按平台正确构造文件 URL;增加相对路径测试,核对实际配置落点。 + +crates/ely-lok/src/client.rs:140 — 显式指定的工作进程路径失效时,会静默改用 sibling 或源码目录中的 debug 二进制。实测设置 `ELY_LOK_WORKER=/no/such/ely-lok-worker`,`Worker::spawn` 仍成功运行开发构建,配置错误被隐藏,也可能运行过期协议实现。环境变量已设置时严格校验并返回对应错误;只在变量缺省时执行默认定位,并测试不存在路径和空值。 + +crates/ely-lok/src/client/tests.rs:93 — 宏测试无法证明 LOK-11:fixture 中的 `Sneaky` 是空过程,没有自动执行事件绑定;测试只检查文档尺寸和关闭结果。临时将工作进程加载选项改成 `EnableMacrosExecution=true,MacroSecurityLevel=0` 后,`a_macro_document_loads_without_running_anything` 仍通过。使用带真实自动执行绑定、能在隔离目录写入标记的宏样本,断言标记缺失;通过允许执行的对照验证样本确实能触发,并要求放开宏的变异使安全测试失败。 + +patches/libreoffice/0001-lok-macos-tile-painting.patch:28 — 新增注释 `Aqua virtual devices cannot paint into a client buffer: paint, then copy.` 共 12 个英文词,违反 agents.md 的十词上限。`tests/scripts/comments.py` 当前未扫描补丁,现有检查仍报告通过。将该新增注释缩至十词以内,并让注释检查覆盖项目维护补丁的新增注释。 diff --git a/tasks/reviews/C07.1-r10.md b/tasks/reviews/C07.1-r10.md new file mode 100644 index 0000000..e8adbcc --- /dev/null +++ b/tasks/reviews/C07.1-r10.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:166 — R9 的宏安全回归测试缺陷仍在。样本文档只绑定按钮事件,加载测试未触发该事件;第 170 行的正向对照执行用户配置中的另一份库宏。隔离副本将 worker.rs 的 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,执行 `cargo build -p ely-lok --bin ely-lok-worker --locked` 后,以实际固定 LOK 构建运行 `cargo test -p ely-lok --locked a_macro_never_writes_its_marker -- --nocapture --test-threads=1`,实测仍通过,无法检测 LOK-11 的加载时放开宏回归。修复方向:使用同一份带有效自动执行事件的文档验证禁用和放开两种配置,要求放开宏的变异使安全测试失败;正向执行对照放入独立测试程序。 diff --git a/tasks/reviews/C07.1-r2.md b/tasks/reviews/C07.1-r2.md new file mode 100644 index 0000000..fca7522 --- /dev/null +++ b/tasks/reviews/C07.1-r2.md @@ -0,0 +1,15 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:35 — R1 的宏回归测试缺陷仍在。fixture 把 Basic 源码直接放进 `office:script`,并使用无效的 `office:events/office:event-listener` 绑定,内核无法据此执行 `Sneaky`。隔离副本把加载选项改成 `EnableMacrosExecution=true,MacroSecurityLevel=0` 后,`a_macro_never_writes_its_marker` 仍通过。另用含嵌入 Basic 库、模块及 `script:event-listener` 的有效样本作对照,同一补丁内核在放开权限后写出了标记,原实现阻止了标记。替换成经正向对照验证的自动执行样本,并要求放开宏的变异使测试失败。 + +crates/ely-lok/src/worker.rs:117 — 关闭清理吞掉目录读取失败及枚举错误,其他删除失败也只记录日志,随后仍以 0 退出。真实补丁内核打开并关闭文档后,将隔离 `profile/tmp` 权限设为 000,再调用 `shutdown()`,实测返回 `Ok(())`;恢复权限后仍有两个 `lu*.tmp` 文件。SHUTDOWN 的成功应答还在清理前发送。将清理失败纳入关闭结果,在清理完成后发送应答,并覆盖目录读取、文件删除失败,保证 LOK-10 的失败对调用方可见。 + +crates/ely-lok/src/client.rs:147 — shutdown 在应答缺失或协议错误时直接返回,跳过 `child.wait()`;Drop 再次发送失败后也只写日志。注入完成版本及图块握手、收到 SHUTDOWN 后无应答退出 23 的工作进程,shutdown 已正确返回错误,但释放 Worker 后,存活父进程下的子进程仍被 `ps` 标为 `Z `。工作进程崩溃会持续积累僵尸进程。保留原始错误,并让发送、接收和协议失败路径均完成子进程终止及回收;增加释放 Worker 后确认子进程已回收的断言。 + +crates/ely-lok/src/client.rs:97 — 新增图块自检直接把文件路径拼进 URL,遗漏百分号编码。真实补丁内核使用名为 `profile#hash` 或 `profile%20literal` 的合法配置目录时,`Worker::spawn` 均返回自检文件的 `Unsupported URL ... type detection failed`;普通目录及相对目录成功。`#` 被解释为片段,`%20` 被解码为空格,导致 LOK-01 的加载流程失败。将自检文件通过正确的本地路径到文件 URL 转换传入内核,覆盖保留字符路径。 + +crates/ely-lok/src/worker.rs:300 — R1 的配置路径修复仍未处理 Windows 路径。`canonicalize` 产生的盘符路径及扩展前缀被整体百分号编码;隔离探针把 `\\?\C:\Users\test\profile` 传给实际 helper,得到 `file://%5C%5C%3F%5CC%3A%5CUsers%5Ctest%5Cprofile`,预期本地路径断言失败。该结果没有合法的本地文件 URL 路径,无法指向要求隔离的配置目录。按平台转换盘符、分隔符及 UNC 路径,再编码路径内容;为 Windows 路径增加精确 URL 和实际配置落点验证。 + +crates/ely-lok/src/client.rs:71 — R1 的固定版本校验仍只检查全文是否包含提交哈希。注入成功返回图块计数、版本中 `ProductVersion` 为 `99.0` 的工作进程,`Worker::spawn` 返回成功;仅包含该哈希的非 JSON 文本也被接受。完整版本和 BuildId 字段的契约因此没有落实。解析版本结构并逐项核对固定版本及 BuildId,拒绝缺字段、错误字段及畸形载荷,补充这些握手失败测试。 + +scripts/build-libreoffice.sh:57 — R2 缩短补丁注释后,脚本无法继续使用已经应用 R1 补丁的源码缓存。将当前固定提交及已打旧补丁的 `init.cxx` 放入隔离缓存,实跑脚本以 1 退出并报 `patch does not apply`:新补丁的反向检查因注释变化失败,随后正向应用又遇到已经修改的代码。现有构建目录可直接复现。为已知旧补丁提供受校验的迁移或缓存重建流程,保留对未知本地改动的保护,并覆盖旧补丁缓存升级及升级后重复运行。 diff --git a/tasks/reviews/C07.1-r3.md b/tasks/reviews/C07.1-r3.md new file mode 100644 index 0000000..45dd482 --- /dev/null +++ b/tasks/reviews/C07.1-r3.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:169 — R2 的宏回归测试缺陷仍在。正向对照调用的是预先写入用户配置的库宏;文档样本仅绑定按钮事件,加载流程没有触发该事件。隔离副本将 LOAD_OPTIONS 改成 `EnableMacrosExecution=true,MacroSecurityLevel=0` 后,`a_macro_never_writes_its_marker` 仍通过。测试无法检测加载时放开宏的回归。使用同一份带有效自动执行事件的文档分别验证禁用与放开两种加载配置,并要求放开宏的变异使安全测试失败。 + +crates/ely-lok/src/worker.rs:249 — 新增 RUN_MACRO 通道在正式构建中直接执行宏,绕过加载选项,违反 LOK-11。实测以 `cargo build -p ely-lok --release --locked` 生成的工作进程运行现有宏测试,`Worker::run_macro("macro:///Standard.Module1.Sneaky")` 成功创建标记文件;该接口与 IPC 分支均无测试配置限制。将允许执行宏的正向对照放入独立测试程序,移除产品工作进程中的宏执行入口,并验证正式二进制拒绝该请求。 + +crates/ely-lok/src/ipc.rs:15 — R2 的 Windows 配置路径修复仍生成错误的文件 URL。抽取实际 Windows 分支运行,`\\?\C:\Users\test\profile` 得到 `file://C:/Users/test/profile`,预期 `file:///C:/Users/test/profile` 的断言失败;盘符被放进 authority,指定配置目录无法正确表示。UNC 输入还得到 `file:////server/share/profile`。分别处理本地盘符与 UNC 的 authority/path,再编码路径内容;补充精确 URL 及 Windows 实际配置落点测试。 + +crates/ely-lok/src/client.rs:253 — R2 的版本校验仍通过文本搜索提取字段,接受畸形文本及错误层级的字段。注入工作进程返回顶层 `ProductName=OtherOffice`、`ProductVersion=99.0`、`BuildId=wrong`,同时在前置 metadata 对象内放入期望值,`Worker::spawn` 实测成功;去掉 JSON 大括号也被接受。固定构建契约仍可被错误版本响应绕过。解析完整 JSON,严格核对顶层版本结构及字段,并覆盖畸形载荷和嵌套同名字段。 + +crates/ely-lok/src/client.rs:182 — R2 的回收修复在关闭协议失败后直接等待子进程,遗漏终止仍存活的工作进程。注入完成版本及图块握手、收到 SHUTDOWN 后回复非法 tag 99 并持续存活的进程,`shutdown()` 超过 4 秒仍卡在等待,只有外部终止才能结束;Drop 走同一路径。保留关闭协议错误,在失败路径终止并回收子进程,再返回原始错误;增加非法应答且子进程继续存活的回归测试。 + +scripts/build-libreoffice.sh:58 — R2 的旧补丁缓存兼容改成单行 marker 检查,丢失完整补丁与未知改动校验。隔离缓存保留 `SetOutputSizePixel(...)`,将绘制条件恢复成排除 MACOSX 的旧条件,实际脚本仍跳过补丁并进入 make;使用受控构建工具桩实测退出 0。在真实构建中,该条件会编掉 macOS 图块绘制,产物失去本任务要求的出图能力。校验完整补丁状态,对已知旧注释版本做受校验迁移,遇到缺失 hunk 或未知代码修改时明确失败;覆盖旧缓存重复运行及残缺补丁场景。 diff --git a/tasks/reviews/C07.1-r4.md b/tasks/reviews/C07.1-r4.md new file mode 100644 index 0000000..6636031 --- /dev/null +++ b/tasks/reviews/C07.1-r4.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:169 — R3 的宏回归测试缺陷仍在。正向对照运行预置用户库中的宏,加载样本只绑定按钮事件,测试没有触发该事件。隔离副本将 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,重新构建工作进程后,`a_macro_never_writes_its_marker` 仍通过,无法检出加载时放开宏的回归。使用同一份含有效自动执行事件的文档验证禁用与放开两种配置,并要求放开宏的变异使安全测试失败。 + +crates/ely-lok/src/worker.rs:250 — R3 的产品宏执行入口仍存在,新增保护仅检查运行时环境变量 `ELY_LOK_DEV=1`,公开的 `Worker::spawn_dev` 也会设置它。实测新编译的 release 工作进程运行宏测试时成功写出标记;再用该 release 二进制、设置 `ELY_LOK_DEV=1` 运行 `a_production_worker_refuses_run_macro`,普通 `Worker::spawn` 继承此变量,拒绝断言失败,违反 LOK-11。把宏正向对照移到独立测试程序,移除产品工作进程中的宏执行分支,并验证 release 二进制在该环境变量存在时仍拒绝请求。 + +crates/ely-lok/src/client.rs:275 — R3 的版本校验修复仍以字符计数代替 JSON 解析,字符串内的花括号会改变嵌套深度。注入有效 JSON:metadata 内依次放 `"pad": "}"`、三项期望版本字段、`"pad2": "{"`,顶层则放 `ProductName=OtherOffice`、`ProductVersion=99.0`、`BuildId=wrong`;实测 `Worker::spawn` 仍成功。另在正确字段后添加无值的 `"invalid": }`,畸形 JSON 也被接受,固定构建校验继续失效。解析完整 JSON 并严格核对顶层字段,覆盖字符串花括号、嵌套同名字段及畸形载荷。 + +scripts/build-libreoffice.sh:57 — R3 的残缺补丁检查缺陷仍在。新增的 `defined(MACOSX)` marker 可由其他条件行满足,无法证明绘制条件已修复。隔离缓存应用当前补丁后,仅把绘制条件恢复为 `#if defined(UNX) && !defined(MACOSX) || defined(_WIN32)`,保留两个 marker;实跑原脚本仍进入受控 make 桩并以 0 退出。真实构建会编掉 macOS 图块绘制,违反 C07.1 的出图要求。校验完整补丁状态,对已知旧注释版本做受校验迁移,缺失 hunk 或未知代码修改时明确失败,并覆盖旧缓存重复运行与残缺补丁场景。 diff --git a/tasks/reviews/C07.1-r5.md b/tasks/reviews/C07.1-r5.md new file mode 100644 index 0000000..3ae7515 --- /dev/null +++ b/tasks/reviews/C07.1-r5.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:169 — R4 的宏回归测试缺陷仍在。样本仅绑定按钮事件,加载测试没有触发该事件;正向对照执行的是用户配置中的另一份库宏。隔离副本把 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,重新构建工作进程后,`a_macro_never_writes_its_marker` 仍通过,无法检测加载时放开宏的回归。使用同一份带有效自动执行事件的文档验证禁用和放开两种配置,要求放开宏的变异使安全测试失败。 + +crates/ely-lok/src/worker.rs:250 — R4 的产品宏执行入口仍存在。运行时开关由环境变量换成了 `--dev`,公开的 `Worker::spawn_dev` 会传入该参数,release 构建也包含执行分支。实测新编译的 release 工作进程运行现有宏测试,`run_macro` 成功创建标记文件,违反 LOK-11;环境变量继承的旧路径已修复。将宏正向对照移到独立测试程序,移除产品工作进程中的宏执行入口,并验证 release 二进制在携带 `--dev` 时仍拒绝请求。 + +crates/ely-lok/src/client.rs:313 — R4 的完整 JSON 校验要求仍未满足。新解析器接受任意反斜杠转义及由数字、减号、小数点任意组合的值。通过 IPC 注入正确版本字段并追加 `"invalid": --`,或把 ProductName 写成非法 JSON 字符串 `"Libre\Office"`,实测 `Worker::spawn` 均成功;有效的 `"\u004cibreOffice"` 又被误拒绝。R4 的嵌套花括号和缺值载荷已被拒绝,但畸形版本响应仍可通过固定构建校验。使用完整 JSON 解析核对顶层字段,覆盖非法数字、非法转义及合法 Unicode 转义。 + +scripts/build-libreoffice.sh:59 — 新增检查阻断全新构建。固定提交的原始 `init.cxx` 本身含有 old_line,脚本在第 64 行应用补丁前就以 `has a reverted tile condition` 退出。以该提交的原始文件搭建隔离缓存,实跑脚本退出码为 1,补丁与构建均未执行。先识别完整原始状态并应用补丁,再校验完整结果;覆盖空缓存首次构建路径。 + +scripts/build-libreoffice.sh:63 — R4 的残缺补丁校验缺陷仍在。原先恢复绘制条件的变异现已被拒绝,但两个 marker 仍无法证明全部修改已应用。隔离缓存应用完整补丁后,仅把像素复制分支恢复为 `#ifdef _WIN32`,保留设备分配与绘制条件修改;实跑脚本仍进入受控 make 桩并以 0 退出。该状态会在 macOS 编译掉向 pBuffer 复制像素的代码,使图块保持未写入。校验补丁全部修改及已知旧注释版本,残缺或未知状态明确失败,并覆盖逐项撤销补丁修改的回归测试。 diff --git a/tasks/reviews/C07.1-r6.md b/tasks/reviews/C07.1-r6.md new file mode 100644 index 0000000..4502f61 --- /dev/null +++ b/tasks/reviews/C07.1-r6.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:169 — R5 的宏安全回归测试缺陷仍在。文档样本绑定按钮事件,加载测试没有触发该事件;正向对照执行的是用户配置中的另一份库宏。隔离副本把 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,重新构建工作进程并运行 `a_macro_never_writes_its_marker`,测试仍通过,无法检测加载时放开宏的回归。使用同一份带有效自动执行事件的文档验证禁用和放开两种配置,并要求放开宏的变异使安全测试失败;正向执行对照放入独立测试程序。 + +crates/ely-lok/src/client.rs:372 — R5 的完整 JSON 校验缺陷仍在。数字检查接受前导零,字符串解析器在第 319 行直接接收未转义控制字符。通过 IPC 返回正确的三个版本字段,再追加 `"invalid":01`,或追加含实际换行字符的字符串字段,实测 `Worker::spawn` 均成功;追加合法的 `"extra":1e2` 或 `"extra":"\ud83d\ude00"` 又被误拒绝。R5 的 `--`、非法反斜杠转义与 `\u004c` 用例已修复,但畸形响应仍可通过固定构建校验。使用完整 JSON 解析器核对顶层版本字段,并覆盖前导零、控制字符、指数数字和代理对转义。 + +scripts/build-libreoffice.sh:58 — R5 的残缺补丁校验缺陷仍在,绘制条件校验也再次缺失。`patched()` 只搜索三个全文件 marker,头文件和像素复制分支共用同一 marker。以固定提交的原始 init.cxx 应用完整补丁后,分别仅恢复旧绘制条件、仅恢复像素复制分支的 `#ifdef _WIN32`、仅恢复 BitmapTools 头文件条件,实跑脚本三次都进入受控 make 桩并以 0 退出。前两种状态会在 macOS 编译掉绘制或像素复制,破坏出图块要求。空缓存首次应用补丁已通过;应校验完整补丁结果,对残缺和未知状态明确报错,并覆盖逐项撤销补丁修改的回归测试。 diff --git a/tasks/reviews/C07.1-r7.md b/tasks/reviews/C07.1-r7.md new file mode 100644 index 0000000..95ee5d4 --- /dev/null +++ b/tasks/reviews/C07.1-r7.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:169 — R6 的宏安全回归测试缺陷仍在。样本文档只绑定按钮事件,加载测试没有触发该事件;正向对照执行的是用户配置中的另一份库宏。隔离副本将 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,重新构建工作进程后运行 `a_macro_never_writes_its_marker`,实测仍通过,无法检测 LOK-11 的加载时放开宏回归。修复方向:以同一份带有效自动执行事件的文档验证禁用和放开两种配置,要求放开宏的变异使安全测试失败;正向执行对照放入独立测试程序。 + +crates/ely-lok/src/client.rs:318 — R6 的完整 JSON 校验缺陷仍在。新增控制字符搜索只匹配 U+0000 和 U+001F,原始换行、制表符和 U+0001 仍直接进入字符串;第 363 行的数字扫描仍排除 `e`、`E` 和 `+`,新增指数检查分支实际不可达。通过 IPC 返回正确的三个版本字段,再追加含上述任一原始控制字符的字符串字段,实测 `Worker::spawn` 均成功;追加合法的 `"extra":1e2` 则被拒绝。前导零已拒绝,合法代理对附加字段已接受,但畸形响应仍能通过固定构建校验。修复方向:使用完整 JSON 解析器校验顶层版本字段,并加入覆盖控制字符、指数、前导零及 Unicode 转义的回归测试。 + +scripts/build-libreoffice.sh:64 — R6 的残缺补丁校验缺陷仍在。逆向完整性检查失败后,仍以全文件 marker 将残缺补丁认作旧注释版本。对固定提交的原始 init.cxx 应用完整补丁,分别仅将 BitmapTools 头文件条件、像素复制分支条件恢复为 `#ifdef _WIN32`,实跑脚本均打印 `tile patch already applied (older comment)`、进入受控 make 桩并以 0 退出;像素复制分支会在 macOS 被编译掉,破坏出图块要求。旧绘制条件回退已被拒绝,首次应用完整补丁也通过。修复方向:校验所有补丁修改的完整结果,明确拒绝残缺或未知状态,并加入逐项撤销修改的回归测试。 diff --git a/tasks/reviews/C07.1-r8.md b/tasks/reviews/C07.1-r8.md new file mode 100644 index 0000000..1fe9696 --- /dev/null +++ b/tasks/reviews/C07.1-r8.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:166 — R7 的宏安全回归测试缺陷仍在。样本文档只绑定按钮事件,加载测试没有触发该事件;正向对照执行的是用户配置中的另一份库宏。在隔离副本中将 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,重新构建工作进程并运行 `a_macro_never_writes_its_marker`,实测仍通过,无法检测 LOK-11 的加载时放开宏回归。修复方向:用同一份带有效自动执行事件的文档验证禁用和放开两种配置,要求放开宏的变异使安全测试失败;正向执行对照放入独立测试程序。 + +crates/ely-lok/src/client.rs:324 — R7 的完整 JSON 校验缺陷仍在。第 318 行找出原始控制字符后,第 324 行仍把它当作反斜杠处理,按后一个字符解码。通过 IPC 返回正确的三个版本字段,并追加值为 `a`、实际换行(或制表符、U+0001)、`b` 的字符串字段,实测 Worker::spawn 均成功;ProductName 使用实际 U+0001 后接 `u004cibreOffice` 也被解码为 LibreOffice 并通过校验。新增复验还发现字段间 U+00A0 被当成合法 JSON 空白。合法指数与代理对附加字段、前导零拒绝已通过复验,畸形响应仍能通过固定构建校验。修复方向:使用完整 JSON 解析器校验顶层版本字段,并加入覆盖原始控制字符、JSON 空白边界、指数、前导零及 Unicode 转义的回归测试。 + +scripts/build-libreoffice.sh:65 — R7 的残缺补丁校验缺陷仍在。完整补丁逆向检查失败后,全文件 marker 仍将残缺补丁认作旧注释版本。对固定提交的原始 init.cxx 应用完整补丁,再分别仅将 BitmapTools 头文件条件、像素复制分支条件恢复为 `#ifdef _WIN32`,隔离环境实跑脚本均打印 `tile patch already applied (older comment)`、进入受控 make 桩并以 0 退出,逆向完整性检查仍失败;像素复制分支会在 macOS 被编译掉,破坏出图块要求。首次应用完整补丁、拒绝旧绘制条件回退已通过复验。修复方向:校验所有补丁修改的完整结果,对残缺或未知状态报错,并加入逐项撤销补丁修改的回归测试。 diff --git a/tasks/reviews/C07.1-r9.md b/tasks/reviews/C07.1-r9.md new file mode 100644 index 0000000..a5b988a --- /dev/null +++ b/tasks/reviews/C07.1-r9.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-lok/src/client/tests.rs:166 — R8 的宏安全回归测试缺陷仍在。样本文档只绑定按钮事件,加载测试没有触发该事件;正向对照执行的是用户配置中的另一份库宏。隔离副本将 LOAD_OPTIONS 改为 `EnableMacrosExecution=true,MacroSecurityLevel=0`,执行 `cargo build -p ely-lok --bin ely-lok-worker --locked` 后,以实际固定 LOK 构建运行 `cargo test -p ely-lok --locked a_macro_never_writes_its_marker -- --nocapture --test-threads=1`,实测仍通过。新增“未验证”注释后,测试仍无法检测 LOK-11 的加载时放开宏回归。修复方向:使用同一份带有效自动执行事件的文档验证禁用和放开两种配置,要求放开宏的变异使安全测试失败;正向执行对照放入独立测试程序。 + +crates/ely-lok/src/client.rs:319 — R8 的完整 JSON 校验缺陷仍在。控制字符检查只拒绝 NUL,其余原始控制字符仍在第 327 行被当作反斜杠处理。通过 IPC 返回正确的三个版本字段,并追加值为 `a`、实际换行(或制表符、U+0001)、`b` 的字符串字段,实测 Worker::spawn 均成功;ProductName 使用实际 U+0001 后接 `u004cibreOffice` 也被解码为 LibreOffice 并通过校验。U+00A0 空白拒绝、合法指数与代理对附加字段接受、前导零拒绝均已通过复验,畸形响应仍可通过固定构建校验。修复方向:使用完整 JSON 解析器校验顶层版本字段,并加入覆盖全部原始控制字符、JSON 空白边界、指数、前导零及 Unicode 转义的回归测试。 diff --git a/tasks/reviews/C07.2-r1.md b/tasks/reviews/C07.2-r1.md new file mode 100644 index 0000000..79af297 --- /dev/null +++ b/tasks/reviews/C07.2-r1.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-lok/src/lib.rs:10 — [P1] C07.2 要求的 GPUI `LokView` 及视图渲染链缺失。当前仅导出 `Worker`、`Doc`、`Tile`、`Invalidations`;源码没有图块缓存、旧代际响应丢弃、页面裁剪或统一的缩放/滚动/DPI 坐标转换。临时消费程序 `use ely_lok::LokView;` 实测编译报 E0432;同一 960×960 twips 区域在 `worker.rs:269` 始终按 64×64 像素渲染,接口无法请求 200% DPI 所需的 128×128 像素。`docs/engineering-risks.md:29` 所称“客户端按页矩形裁剪”“旧代际图块不覆盖新画面”也缺少实现与验收依据。修复方向:完成可消费的 GPUI 视图、目标像素尺寸与共享坐标转换、按代际接收/丢弃图块,并以延迟旧响应及缩放、滚动、DPI 场景验证后更新证据。 + +crates/ely-lok/src/events.rs:122 — [P1] 每次按键或点击均通过 `worker.rs:308、321` 无条件递增代际并设置 `all=true`,覆盖精确失效矩形的作用,违反 LOK-03“只更新必要图块”。实测打开含两段文字的 FODT,读取 512×512 图块与失效批次,再点击 `(150,150)`:点击前后像素完全相同,事件却从 `(generation=0, all=false, rects=[])` 变为 `(1, true, [])`。局部编辑同样只能触发整区重绘;即使后端产生矩形回调,这个无条件全量标志仍然存在。修复方向:恢复可靠的内容区域失效来源,以实际脏区驱动重绘;将纯光标/选择事件与内容变更分开,补充局部编辑仅更新相交图块的测试。 + +crates/ely-lok/src/worker.rs:66 — [P1] `WAIT_EVENTS` 同步阻塞唯一的 IPC 读取线程,等待期间后续编辑、关闭和退出请求均无法被读取;非 macOS 的 `serve` 在第 214 行存在同一路径。客户端 `poll_events` 同时占用 `&mut Worker` 并同步等待回复。实测文档空闲、已知代际为 0 时发送文档允许的 `WAIT_EVENTS(id, 0, 0)`,随后发送 `POST_KEY` 和 `SHUTDOWN`,1.5 秒后三个请求均无回复且进程仍存活;当前后端没有自发图块回调,只有被堵住的编辑请求才能推进代际,因而形成永久等待。修复方向:让事件订阅/等待期间继续处理控制请求,提供可完成的取消与关闭路径,并验证空闲订阅后编辑和退出均可完成。 + +crates/ely-lok/src/worker.rs:269 — [P1] 图块尺寸直接做有符号除法、`usize` 强转与未检查乘法,错误参数进入分配和 FFI。通过与公开 `Doc::paint_tile` 相同的请求格式实测:`(w=0,h=960)` 和 `(w=14,h=960)` 都返回 `OK`、零像素;`(w=-15,h=960)` 在第 270 行触发 `attempt to multiply with overflow`,worker 中止,客户端收到 EOF,进程内全部会话随之丢失。修复方向:在分配及调用 LOK 前校验文档区域和目标像素尺寸,以 checked arithmetic 核对缓冲区及 IPC 上限,非法请求返回明确错误并保持 worker 可用;覆盖零值、负值、极小值及超大值。 + +crates/ely-lok/src/client/tests.rs:210 — [P2] 像素测试把全零缓冲区计为有效深色像素;第 221 行的编辑失效测试只核对代际与标志,未验证更新后的画面。本轮在临时副本中仅将 `Document::paint_rect` 的函数体改为空操作,保留独立的启动绘制自检,重新构建 worker 后,`a_tile_paints_real_pixels` 与 `an_edit_bumps_the_generation_and_invalidates` 仍全部通过;此时实际图块始终为全零。这使当前测试无法守住 C07.2 的基本绘制与更新行为。修复方向:针对包含已知正文的区域验证有效页面/文字像素以及编辑造成的预期画面变化,并要求该空绘制变异触发失败。基线已亲自完成 `cargo build -p ely-lok --bins --locked`、`cargo check -p ely-lok --all-targets --locked`、固定补丁引擎下的全部 10 项测试与注释检查,均通过。 diff --git a/tasks/reviews/C07.2-r2.md b/tasks/reviews/C07.2-r2.md new file mode 100644 index 0000000..a439a2b --- /dev/null +++ b/tasks/reviews/C07.2-r2.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-lok/src/worker.rs:286 — [P1] 上轮局部失效缺陷仍存在:编辑键继续调用 `events::bump`,在 `events.rs:127` 无条件设置 `all=true`,精确矩形仍会被整区失效覆盖,违反 LOK-03。固定补丁引擎实测:两段正文开头按 Return,批次为 `(generation=1, all=true, rects=[])`;远处 `(0,7680,3840,3840)` 图块编辑前后逐字节相同,`LokView::apply_batch` 仍清空全部缓存。纯点击及方向键已保持代际稳定,局部编辑的验收仍失败。修复方向:接通可靠的内容脏区来源,以相交矩形驱动重绘,并验证局部编辑保留未受影响的缓存图块。 + +crates/ely-lok/src/worker.rs:299 — [P1] 新增 `POST_UNO` 路径执行内容变更后没有产生失效或推进修订;当前固定 macOS 后端也没有图块失效回调。实测先编辑到 generation 1,再依次发送 `.uno:SelectAll`、`.uno:Delete`,重新绘制的像素已经改变,响应仍为 generation 1,事件批次为 `(1,false,[])`。`LokView::watch` 因代际相同直接继续等待,缓存中的旧正文会一直显示,违反 LOK-03。修复方向:将 UNO 内容变更纳入可靠的修订与脏区通知链,补充删除、撤销等命令执行后视图刷新的回归测试。 + +crates/ely-lok/src/view.rs:266 — [P1] `Geom::px_of` 已乘 DPI,`prepare` 又把其结果作为 GPUI `Pixels` 布局与绘制尺寸;GPUI 在 `paint_image` 内继续按窗口比例转换到设备像素,造成重复缩放。临时 GPUI 测试实测:zoom=1、DPI=2 时,3840 twips 图块生成 512 像素纹理,却绘制为 512 个逻辑像素;正确逻辑宽度应为 256。此时 `doc_point(256,0)` 返回 `(1920,0)`,正确文档位置应为 `(3840,0)`。因此跨 1x/2x 显示器会改变页面逻辑尺寸、可见范围及滚动距离,违反 LOK-04;现有几何测试仅验证公式往返,未覆盖 GPUI 单位。修复方向:用 zoom 转换文档与 GPUI 逻辑坐标,仅在纹理分辨率转换中应用 DPI,并验证实际绘制尺寸、命中位置及滚动转换。 + +crates/ely-lok/src/view.rs:204 — [P1] 图块送入 `RenderImage` 前交换红蓝通道,导致文档颜色错误。锁定版本的 GPUI `RenderImage` 要求 BGRA,`new` 原样保存帧字节;当前 LOK 同样返回 BGRA。固定引擎绘制红色正文实测得到 1182 个红色像素,样本为 `[0,0,255,255]`;将该像素通过实际 `finish_tile` 后,`RenderImage::as_bytes(0)` 变成 `[255,0,0,255]`,GPUI 会显示蓝色。现有黑白图块测试无法捕获此错误。修复方向:按 GPUI 的 BGRA 契约传递图块,添加红蓝可区分的渲染回归断言。 + +crates/ely-lok/src/view.rs:155 — [P1] 文档尺寸只在构造时读取,失效处理仅更新代际与缓存;滚动上限、图块请求范围和页面裁剪持续使用旧 `doc_size`。固定引擎实测:初始文档为 `(12808,16408)` twips,输入 80 次 Return 后内核尺寸增至 `(12808,32532)`;调用实际 `apply_batch` 后,视图尺寸仍为 `(12808,16408)`。新页面被第 282 行的范围检查及页面 mask 排除,滚动也无法到达新增内容,违反 LOK-04。修复方向:在文档尺寸变化时同步视图尺寸与滚动边界,覆盖增页、删页后的可见范围和裁剪。 + +crates/ely-lok/src/view.rs:139 — [P2] 事件任务被 `detach`,只有代际变化后才检查弱视图句柄;空闲批次始终走 `continue`。释放空闲 `LokView` 后,该任务仍每 50 毫秒轮询并强引用 worker,阻止其释放。临时 GPUI 测试已确认视图弱句柄失效,继续执行 20 个调度步骤后 worker 弱引用仍可升级;显式关闭文档使轮询返回错误后,任务才结束。关闭视图会留下持续工作的事件任务和引擎资源。修复方向:让订阅任务随视图释放而取消,或在每次等待前检查视图存活状态;补充空闲状态关闭视图后的任务与 worker 释放测试。 diff --git a/tasks/reviews/C07.2-r3.md b/tasks/reviews/C07.2-r3.md new file mode 100644 index 0000000..d756fb3 --- /dev/null +++ b/tasks/reviews/C07.2-r3.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-lok/src/view.rs:168 — [P1] 滚动边界在文档缩短、缩放减小和视口扩大后失效,违反 LOK-04。`apply_batch` 将滚动位置夹到文档宽高,正确上限应扣除当前视口对应的文档尺寸;`set_zoom`(第 95 行)与 `prepare`(第 274 行)改变这一上限后也保留原位置。固定补丁引擎与实际 GPUI 视图方法实测:600 逻辑像素高的视口,80 次 Return 将文档高度从 16408 增至 32532 twips,滚到底部为 23532;SelectAll/Delete 后文档恢复 16408,滚动位置却变为 16408,正确上限为 7408。此时页面裁剪区域的底边落在视口顶部,正文完全消失,直到再次滚动。同样,200% 缩放下滚到底部后改为 25%,滚动位置仍为 11908,正确上限为 0;将视口高度从 300 扩至 1200 逻辑像素也复现 11908 对 0。三项临时回归断言均失败,现有 22 项测试全部通过。修复方向:在文档尺寸、缩放及视口尺寸变化时,按同一坐标转换重新计算水平与垂直滚动上限并夹定当前位置,确保图块位置与页面裁剪使用更新后的值;补充删页、缩小缩放及扩大窗口后的滚动边界回归。 diff --git a/tasks/reviews/C07.3-r1.md b/tasks/reviews/C07.3-r1.md new file mode 100644 index 0000000..e23d33c --- /dev/null +++ b/tasks/reviews/C07.3-r1.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-lok/src/view/input.rs:166 — 剪切先通过 worker 执行 `.uno:Cut`(worker/input.rs:104),再写系统剪贴板;写入失败时,正文已经被删除。复现:选择 `Hello 世界`,仅向 `clipboard::write` 注入写失败,再执行 `Input::Copy { cut: true }`;返回错误,保存后的段落为 `[""]`。失败时用户内容丢失,重试也失去原选区。修复方向:先取得复制数据并成功写入系统剪贴板,再删除选区;为写失败时正文和选区保留增加回归。 + +crates/ely-lok/src/view/input.rs:428 — IME 更新丢弃平台给出的组合内选区,`selected_text_range` 固定返回组合尾部(:397),`text_for_range` 固定返回 `None`(:383)。复现:在 GPUI 空窗口中调用 `EntityInputHandler::replace_and_mark_text_in_range(None, "nihao", Some(2..2), ...)`,再查询选区及 `0..2` 的组合文本,实际得到 `5..5` 和 `None`。输入法移动组合内光标后,平台与编辑器的范围失同步。修复方向:保存并按 UTF-16 更新组合选区、处理替换范围、提供组合文本查询;通过这些平台回调验证组合内编辑。 + +crates/ely-lok/src/worker/input.rs:134 — 粘贴只检查 `setClipboard`,然后发送未请求结果通知的 `.uno:Paste`(ffi/document.rs:85)并回复成功;实际导入失败被吞掉。复现:使用已构建的固定引擎,向打开的 FODT 粘贴 MIME 为 `image/png`、数据为 `b"this is not a png"` 的载荷,worker 返回 `OK`;随后保存,文档保持 `Hello 世界`,``(图片数据为 `this is not a png`)。worker 回复 `OK`,保存后的段落为 `[""]`、`` 的 xml-example.txt,实际 LOK 加载成功,risk=true,普通 Save 被导向 DOCX/ODT 转换,破坏 §8.1 原格式保存。复现二:将真实宏 FODT 的库元素及属性改用绑定同一命名空间的 basic 前缀,LOK 加载与往返导出仍保留 library-embedded,应用 risk=false,遗漏 §8.3 风险状态。修复方向:按输入格式解析实际 XML 元素,以命名空间 URI 和元素名识别嵌入库;普通 TXT 保留普通保存语义,并覆盖正文标签字符串与合法前缀别名。 diff --git a/tasks/reviews/C08.2-r1.md b/tasks/reviews/C08.2-r1.md new file mode 100644 index 0000000..b3e15dd --- /dev/null +++ b/tasks/reviews/C08.2-r1.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-doc/src/props/styles.rs:54 — 字符样式的选中值和“更新”目标来自 `Props.character`,该字段只记录在菜单中最后应用的样式;光标、选区、撤销和标签切换均无法把它同步为内核当前样式,违反 DOC-04、LOK-05。复现:在含 Emphasis 与 Strong Emphasis 两段文字的文档中,先在第一段应用 Emphasis,再将光标移到 Strong Emphasis,设为红色并点击字符样式“更新”;实际更新 Emphasis,其他引用 Emphasis 的文字随之变红。临时副本探针运行后,保存 XML 中 Emphasis 的定义出现 `fo:color="#ff0000"`,保护该样式的断言失败。另一个探针将已应用 Emphasis 的属性栏隐藏后重新显示,内核与保存 XML 保持 Emphasis,`Props.character` 却为 None,“更新”随之禁用(props.rs:66)。修复方向:从内核当前字符样式同步显示与更新目标,处理光标移动、混合选区、撤销和重新显示,并增加上述时序的回归测试。 + +apps/ely-doc/src/props/groups.rs:301 — 段前、段后只有共享的“减少/增加”操作,commands.rs:120、128 将其映射为同时调整两者的 ParaspaceIncrease/Decrease,无法分别设置 DOC-03 要求的段前与段后间距。复现:空白段落从两者为零开始,连续增加五次,落盘的段落属性为 `fo:margin-top="0.198in" fo:margin-bottom="0.198in"`;需要段前 0、段后 6 pt 时,现有两个按钮始终同步调整两个值,无法完成该操作。已有格式测试只确认命令返回成功,未断言段前、段后结果。修复方向:提供分别设置段前、段后的 Ely 控件与白名单参数映射,以内核状态回显,并用保存结果验证单独修改一项时另一项保持原值。 diff --git a/tasks/reviews/C08.2-r2.md b/tasks/reviews/C08.2-r2.md new file mode 100644 index 0000000..de6a38e --- /dev/null +++ b/tasks/reviews/C08.2-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-doc/src/props/groups.rs:326 — 段距回调捕获绘制时的 upper/lower,每次提交都把两项一起入队;另一项刚提交且内核状态尚未回显时,下一项会携带旧值覆盖它,违反 DOC-03。真窗口复现:段前 6 pt、段后 10 pt,选中段前输入 8 并按 Enter,立即选中段后输入 12 并按 Enter,随后保存。临时副本仅增加参数日志,实际依次发送 ["282","353"]、["212","35"]、["212","423"](1/100 mm);后两条恢复旧段前 212。保存后当前段落的 XML 为 fo:margin-top="0.0835in"、fo:margin-bottom="0.1665in",即段前仍为 6 pt、段后变成 12 pt。初始两项均为零时连续输入 6、12 也将段前恢复为零。已有 spacing_above_and_below_set_apart 在两次命令间等待回显,应用测试直接调用 run,均覆盖不到此时序。修复方向:协调待提交段距与内核回显,让单项修改保留另一项最新已提交的值,处理数值控件的重复提交及失焦;增加真控件连续编辑、保存与 XML 断言。 + +apps/ely-doc/src/props/groups.rs:178 — 属性栏把混合颜色归为 Automatic,其余文字属性同样缺少混合表示(groups.rs:47、91,props/styles.rs:33),违反 UX-04、LOK-05;上轮要求的混合选区处理仍有缺口。真窗口复现:同段中 Alpha 使用 Emphasis(红色、斜体、12 pt),Beta 使用另一个字符样式(蓝色、粗体、18 pt),点击正文后 Cmd+A。选区稳定后截图显示字号 12、粗斜体均关闭、颜色为透明色块,字符样式为 No Character Style,字符样式 Update 仍启用,全部字段均未显示“混合”。独立内核探针选中 Emphasis 与 Strong Emphasis 时也得到 CharFontName=""、FontHeight="0"、Bold="false"、CharStyle="No Character Style",当前字号菜单还会把该 0 加为普通可选值。修复方向:从内核取得可区分一致值、混合值与无样式的状态,必要时补齐补丁 0003 的状态表示;相关控件显示本地化“混合”,并根据可确定的当前样式控制更新目标;增加混合文字选区的实际控件回显测试。 diff --git a/tasks/reviews/C08.2-r3.md b/tasks/reviews/C08.2-r3.md new file mode 100644 index 0000000..f639935 --- /dev/null +++ b/tasks/reviews/C08.2-r3.md @@ -0,0 +1,5 @@ +FAIL + +patches/libreoffice/0003-lok-char-style-and-spacing-states.patch:66 — 第 2 轮的混合文字属性缺陷仍存在,新增 ItemStatus/DONT_CARE 分支仅覆盖部分状态,违反 UX-04、LOK-05。具体复现:同段 Alpha 使用字符样式 ElyA(Liberation Serif、12 pt、斜体、红色),Beta 使用 ElyB(Liberation Sans、18 pt、粗体、蓝色);真窗口打开该 FODT,点击正文并 Cmd+A,等待选区稳定。字符样式已显示 Mixed、字符样式 Update 已禁用,但字体仍显示 Liberation Serif、字号仍为 12、粗斜体均关闭、颜色仍为 Automatic,相关字段均缺少 Mixed。独立内核探针全选同一文档后得到 CharFontName="Liberation Serif"、FontHeight="12"、Bold="false"、Italic="false"、FontColor="-1",仅 CharStyle 为 MIXED;逐字段断言混合状态的测试失败。现有 crates/ely-lok/src/client/tests/format.rs:253 仅断言 Italic、CharStyle、StyleApply、ULSpacing,覆盖不到此场景。修复方向:补齐同段不同字符样式所形成的字体、字号、效果、颜色等实际混合属性状态,并增加逐字段内核断言与真实控件回显断言。 + +apps/ely-doc/src/props/tests.rs:266 — quick_spacing_edits_keep_each_other 直接调用 set_spacing,绕过 NumberInput 的 Changed、Submit、Blur 与绘制时回调,仍缺少第 2 轮要求的真实连续编辑回归。当前实现的真窗口复验已通过:6/10 pt 连续改为 8/12 pt,保存 DOCX 得到 before="160"、after="240" twips;零段距连续输入 6/12 pt 也保持新值。覆盖缺口的具体复现:临时副本仅把 paragraph.rs:125 的 on_change 恢复为捕获绘制时 upper/lower 并直接 run 两项段距,保留 set_spacing 与其他代码;21 个 ElyDoc 测试仍全部通过,包括该回归测试。该副本真窗口从 0/0 pt 连续输入段前 6、段后 12,按 Enter 并点击正文失焦,最终两项均恢复为 0;保存 DOCX 的 w:spacing 为 before="0"、after="0"。tests/scripts/shell.sh 使用 gallery,也覆盖不到 ElyDoc 的段距回调。修复方向:通过实际数值控件执行连续输入、Enter 和失焦,保存后断言两个段距,并确认恢复旧回调会使该测试失败。 diff --git a/tasks/reviews/C08.3-r1.md b/tasks/reviews/C08.3-r1.md new file mode 100644 index 0000000..a76394b --- /dev/null +++ b/tasks/reviews/C08.3-r1.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-doc/src/insert.rs:92 — “分节符”继续使用当前页面样式,生成的只是共享页面样式的分页,DOC-05 的独立分节及 DOC-06 的分节关系缺失。应用层复现:新建文档输入 First,调用 on_section_break,输入 Second,在第二部分设置两栏并保存 DOCX;word/document.xml 只有一个 w:sectPr,整篇都为两栏。相同流程的 FODT 只有一个 Standard master-page。修复方向:建立可独立配置的节边界及页面/页眉页脚关系,保存后保留两节;回归断言前节保持一栏、后节为两栏。现有插入测试仅检查 fo:break-before="page",会放过此缺陷。 + +apps/ely-doc/src/props/page.rs:270 — “页眉页脚”的版式控件只读取 SwPageHeaderLayout,并只发送 header.layout,页脚的奇偶页版式无法设置,违反 DOC-06。复现:仅启用页脚,SwPageHeaderLayout 回显为空,两项控件都禁用并提示先打开页眉;同时启用页眉页脚并设置 layout=3,落盘含 header-left,页脚仍共享奇偶页且没有 footer-left。应用层 footer-only 回归失败;现有内核测试仅断言普通 footer。修复方向:接入页脚自己的版式命令与状态,并按实际启用的页眉/页脚提供有效的版式控制;验证仅页脚和两者同时启用的落盘结果。 + +apps/ely-doc/src/props/page.rs:176 — 一次方向切换分别提交方向标志和纸张尺寸,形成两个撤销步骤,撤销一次会留下矛盾的页面属性。复现:Letter 纵向页调用 set_orientation(true),等待横向状态与尺寸回显,再执行一次 edit.undo;FODT 恢复为 8.5in × 11in,style:print-orientation 仍为 landscape。应用层与 worker 探针均复现,破坏 DOC-05 的方向状态及撤销行为。修复方向:将方向和尺寸作为一个完整的内核操作及撤销步骤提交,覆盖一次撤销、重做与失败时整体恢复。 + +apps/ely-doc/src/props/object.rs:130 — 图片尺寸去重只比较换算后的宽度,高度编辑会被静默丢弃,违反 DOC-08。复现:插入图片,设 image.size=[58,5670],等待 Size="102 x 10001";调用 set_extent(true,10.1),宽度换算仍为 58 twips,函数直接返回,高度保持约 10.00 cm,落盘仍为 svg:height="3.9374in"。应用层回归断言 10.10 cm 落盘失败;现有尺寸测试只改宽度。修复方向:同时比较宽高,任一尺寸变化都提交;覆盖窄长图片的高度编辑。 diff --git a/tasks/reviews/C08.3-r2.md b/tasks/reviews/C08.3-r2.md new file mode 100644 index 0000000..2144dcb --- /dev/null +++ b/tasks/reviews/C08.3-r2.md @@ -0,0 +1,7 @@ +FAIL + +patches/libreoffice/0004-lok-panel-states.patch:84 — r1 的页脚版式缺陷仍可从实际控件复现:补丁注册了 SwPageHeaderLayout,遗漏 SwPageFooterLayout 的状态广播。新建文档仅打开页脚,SwPageFooterItem 回显 true,SwPageFooterLayout 仍为 None,“首页不同”和“奇偶页不同”持续禁用;真窗口中页脚已显示 On,点击版式控件仍无法发送命令,违反 DOC-06。独立应用探针 review_footer_on_makes_layout_available 失败。现有 sections_footers_and_tall_images_hold_up 直接发送 footer.layout,绕过控件的启用条件。修复方向:接入页脚版式的初始及变更状态广播,覆盖仅开启页脚后通过实际控件修改首页与奇偶页版式的回归。 + +apps/ely-doc/src/insert.rs:114 — 插入分节创建新页面样式后,属性栏的页面样式列表保持旧快照;Props::show 对同一视图直接返回,reload 仅由属性栏创建文字样式设置。复现:输入 First,插入分节并确认,当前节显示 Section 2/第 2 节;展开“本节样式”,选项仍为 Standard、First Page 等十个旧样式。切回第一节后无法从该控件选择新节样式,阻断 DOC-06 的分节关系修改。临时探针 review_section_list_refreshes_after_insert 失败,真窗口菜单确认相同结果。修复方向:在新页面样式创建完成后刷新该文档属性栏的页面样式列表,回归验证新节出现在下拉选项中并可重新选择。 + +apps/ely-doc/src/props/page.rs:180 — 方向切换读取最近回显的纸张尺寸,覆盖已排队且尚未回显的纸张更改。复现:Letter 纵向页依次提交 set_paper("A5") 与 set_orientation(true),两次提交均在首个回显前排入队列;完成后 AttributePageSize 为 27940 x 21590,页面成为 Letter 横向,A5 设置静默丢失,违反 DOC-05。临时探针 review_paper_and_orientation_keep_each_other 失败;现有页面测试等待纸张或方向各自回显后再继续,放过这个顺序。修复方向:纸张和方向编辑共同基于已提交待回显的完整页面状态,覆盖连续更改时尺寸与方向共同保留的回归。 diff --git a/tasks/reviews/C08.3-r3.md b/tasks/reviews/C08.3-r3.md new file mode 100644 index 0000000..bab7f29 --- /dev/null +++ b/tasks/reviews/C08.3-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-doc/src/props/layout_tests.rs:261 — 页面回归的初始化条件存在竞态,同文件第 30 行也有相同问题:仅等待 AttributePage,随后调用的 set_paper / set_orientation 已改为依赖 AttributePage 与 AttributePageSize 共同组成的 Sheet。实际回调可先送达 AttributePage,此时 Sheet 为 None,两次编辑直接返回,测试随后超时。按要求构建 worker 后,ely-doc 串行全量测试首次 24/26 通过(两个页面测试失败),再次 25/26 通过(queued_page_edits_footers_and_new_sections_show_up 失败);独立临时探针捕获 page=Some("false,0,4")、size=None、sheet=None,证明该回归会跳过待验证的页面命令,也阻断其后的页脚与新节列表断言。临时副本改为等待完整 Sheet 后,两轮串行页面测试各 11/11 通过。修复方向:两个测试共同等待可解析的完整页面状态,再执行纸张与方向编辑,保留连续提交及后续页脚、分节断言。 diff --git a/tasks/reviews/C08.4-r1.md b/tasks/reviews/C08.4-r1.md new file mode 100644 index 0000000..cf92e4f --- /dev/null +++ b/tasks/reviews/C08.4-r1.md @@ -0,0 +1,11 @@ +FAIL + +patches/libreoffice/0008-lok-toc-caption-formula-without-dialogs.patch:240 — [P1] 公式转换用 UTF-16 长度作为向左选择的字符数;补充平面字符会使选区越过刚插入的公式,吞入前面的正文并改变公式内容,违反 DOC-10。实测:在正文先键入 `PREFIX`,随后执行 `formula.insert(["𝑥 + 1"])`;命令返回成功,`.uno:Formula` 读回 `X𝑥 + 1`,落盘正文只剩 `PREFI`。现有公式测试仅覆盖 ASCII。修复方向:按实际插入范围选中文字,统一字符串长度与内核光标移动的计数单位,并验证正文及原式在补充平面字符和组合字符下完整保留。 + +apps/ely-doc/src/refs.rs:103 — [P1] 所有提示框统一设置 `max_len(1000)`,公式编辑预填时静默截断已有原式,违反 DOC-10 和出错即暴露规则。临时副本中先通过内核插入 `"x + ".repeat(300) + "1"`(1201 字符),选中公式调用 `on_formula`;内核原式完整,编辑框实际只剩前 1000 字符,末尾的 `1` 已丢失。用户直接点击 Apply 会以截断值替换原公式。修复方向:完整保留已加载公式的预填内容;任何长度限制均在提交前明确校验,加入超过 1000 字符的预填与保存回归。 + +patches/libreoffice/0008-lok-toc-caption-formula-without-dialogs.patch:238 — [P2] 修改选中公式先删除整个对象,再以默认属性创建新对象,导致原有锚点和布局属性丢失,破坏 DOC-10 的公式修改及对象定位。实测:插入 `a over b`,执行 `image.anchor_page`,落盘有 `text:anchor-type="page"` 和 `text:anchor-page-number="1"`;保持该公式选中,修改为 `c over d` 后保存,锚点变成 `text:anchor-type="as-char"`,页锚点消失,公式回到正文流中。现有回归只检查对象数量和公式文字。修复方向:在选中公式的嵌入对象上更新原式,保留框架、锚点及布局属性,并验证一次撤销恢复原式和对象属性。 + +crates/ely-lok/src/commands/refs.rs:47 — [P2] 题注对话框允许空正文提交(`apps/ely-doc/src/refs.rs:246`),命令表却将 Text 作为必填文本;可提交的编号题注操作必然失败,违反 DOC-09。实测:选中公式,以空题注提交,`caption.insert(["Figure", ""])` 返回 `caption.insert needs Text`,对话框已经关闭,文档中没有题注。补丁中的题注入口支持空正文,现有测试只提交 `ratio`。修复方向:统一题注表单与命令参数的空正文约束,让编号题注的有效提交走通,并增加实际提交路径回归。 + +crates/ely-lok/src/review.rs:140 — [P2] 批注 HTML 转正文时同时把 `
` 与其外层空段落的 `` 计作换行,重复增加空行,审阅面板显示的正文偏离原批注(DOC-11)。实测:通过内核插入含 `a\n\nNext` 的批注,`.uno:ViewAnnotations` 给出 `
a

Next
`;`review::notes` 返回 `a\n\n\nNext`。每个空段落都多出一次换行,当前单测仅覆盖连续非空段落。修复方向:按段落边界转换正文,正确处理空段落内的占位 br,并用内核实际返回的多段批注验证空行数量。 diff --git a/tasks/reviews/C08.4-r2.md b/tasks/reviews/C08.4-r2.md new file mode 100644 index 0000000..32f1e44 --- /dev/null +++ b/tasks/reviews/C08.4-r2.md @@ -0,0 +1,5 @@ +FAIL + +patches/libreoffice/0008-lok-toc-caption-formula-without-dialogs.patch:253 — [P2] 第一轮公式布局缺陷仅修复了浮动公式:遇到 FLY_AS_CHAR 时主动丢弃已读取的框架属性,随后删除对象并创建默认公式,行内公式的间距与垂直对齐仍会丢失,违反 DOC-10。实际内核探针:加载含 `a over b` 的 FODT,公式为 `text:anchor-type="as-char"`,其局部框架属性为 `fo:margin-left="0.25in"`、`fo:margin-right="0.5in"`、`style:vertical-pos="top"`、`style:vertical-rel="line"`;点击选中后 `.uno:Formula` 完整读回原式,执行 `formula.insert(["c over d"])` 成功,保存后的上述间距与对齐消失,局部属性只剩默认 `style:vertical-pos="from-top"`。当前回归只检查页锚点,遗漏行内公式。修复方向:在原嵌入公式对象上更新原式并保留框架与布局属性,覆盖行内公式的间距、垂直对齐及一步撤销回归。 + +crates/ely-lok/src/review.rs:150 — [P2] 批注转换仍用 `trim_end()` 删除真实正文末尾的空段落,审阅面板展示偏离内核正文,违反 DOC-11;第一轮新增的中间空段落回归覆盖不到此路径。实际内核探针:执行 `comment.insert(["a\n\n"])`,`.uno:ViewAnnotations` 返回 `
a


`,`review::notes` 却返回 `"a"`,两个末尾空段落被静默删除;打开带同样批注的文档也走此转换。修复方向:仅消除 HTML 段落转换产生的末尾分隔符,完整保留正文自身的空段落与空白,并增加内核返回值的末尾连续空段落回归。 diff --git a/tasks/reviews/C08.4-r3.md b/tasks/reviews/C08.4-r3.md new file mode 100644 index 0000000..28249ab --- /dev/null +++ b/tasks/reviews/C08.4-r3.md @@ -0,0 +1,3 @@ +FAIL + +patches/libreoffice/0008-lok-toc-caption-formula-without-dialogs.patch:264 — [P2] 公式替换把所有 VertOrientation::NONE 当作自动基线定位并清除 RES_VERT_ORIENT,浮动公式的手动纵坐标随之丢失,违反 DOC-10 的公式修改及对象布局保持。实际内核回归:加载含 `a over b` 的 FODT,框架为页锚点第 1 页,`style:vertical-pos="from-top"`、`style:vertical-rel="page"`、`svg:x="2in"`、`svg:y="3in"`;点击选中后执行 `formula.insert(["c over d"])` 返回成功,保存后的页锚点与横坐标保持,纵坐标却变成 `svg:y="-0.2445in"`,公式移到原位置之外;一次撤销恢复原式及 `3in`。隔离副本中的新增回归在纵坐标保留断言处失败;现有页锚点回归仅断言 anchor-type,覆盖不到手动坐标。修复方向:保留显式垂直位置及其参照关系,区分自动基线调整与浮动对象的手动定位,优先在原嵌入对象上更新原式,并加入页锚点手动坐标及一次撤销的回归。 diff --git a/tasks/reviews/C08.5-r1.md b/tasks/reviews/C08.5-r1.md new file mode 100644 index 0000000..bc085ec --- /dev/null +++ b/tasks/reviews/C08.5-r1.md @@ -0,0 +1,17 @@ +FAIL + +apps/ely-doc/src/compare.rs:34 — 比较副本在输入队列完成前保存,审阅结果遗漏刚提交的编辑,违反 DOC-13。复现:正文为 `Initial`,通过 `LokView::insert("PendingText")` 排队输入,随即调用 `compare`;等待比较标签最终名称和输入队列完成后保存并读回,正文仍为 `Initial`,原标签随后包含 `PendingText`。临时探针 `review_compare_includes_queued_text` 已失败。修复方向:沿用保存流程的 `settled` / `when_settled` 守卫,待源标签排队输入完成后创建比较副本,并补充排队输入回归测试。 + +apps/ely-doc/src/merge/run.rs:83 — 合并模板在源标签输入队列完成前保存,预览及生成会遗漏待处理文字或字段,违反 DOC-14。复现:正文为 `Dear `,在一次调度中调用 `insert_field("Name")` 后立即预览 `Name=Ada` 的记录;等待最终预览名称和输入队列完成后读回,预览正文为 `Dear `,预期为 `Dear Ada`。临时探针 `review_preview_includes_queued_field` 已失败。修复方向:预览和生成统一等待源标签输入完成,再取得模板快照;测试覆盖字段排队期间发起预览和生成。 + +apps/ely-doc/src/merge/run.rs:68 — 逐列对已经填充过的正文执行全局替换,后续列会改写先前列的 CSV 值,违反 DOC-14 的记录内容保真。复现:模板为 `Dear «Name» of «City»`,列顺序为 `Name,City`,记录为 `«City»,London`;等待填充完成后读回正文得到 `Dear London of London`,预期为 `Dear «City» of London`。临时探针 `review_merge_preserves_literal_field_markers_in_data` 已失败。修复方向:基于原模板的字段位置进行一次性填充,保证 CSV 值作为字面数据保留,并补充值含其他字段标记的回归测试。 + +apps/ely-doc/src/merge/run.rs:159 — 输出文件只在启动后台任务前通过 `exists()` 检查,随后内核直接保存到目标路径;检查与写入之间出现的文件会被覆盖,违反本任务承诺的拒绝覆盖行为。复现:调用 `generate` 后、推进后台调度前,在首条目标路径写入 `EXTERNAL CONTENT`;完成生成后该文件变成 DOCX ZIP。临时探针 `review_generate_preserves_target_created_after_preflight` 已失败,现有测试仅覆盖调用前已存在的目标。修复方向:以排他创建或排他发布保证最终目标的所有权,将内核输出先写入独立暂存路径;遇到占用时暴露错误并保留已有文件,补充检查后目标被占用的测试。 + +apps/ely-doc/src/find.rs:143 — 查找缓存只随新的搜索回调更新,正文编辑完成后保留过期的匹配数量和矩形,违反 DOC-12 的查找结果正确性。复现:正文为 `cat dog`,查找 `cat` 并定位第一项,通过输入 `cow` 替换所选文字;待编辑和回调完成,查找框的总数仍为 1,实际匹配数为 0。临时探针 `review_find_updates_after_edit` 已失败。修复方向:观察文档编辑代际,在编辑完成后重新执行当前查询并更新匹配缓存,同时覆盖删除匹配、添加匹配及匹配位置变化的回归场景。 + +apps/ely-doc/src/find.rs:151 — 上下一处仅更新当前序号,结果落在视口外时页面保持原滚动位置,DOC-12 的定位操作无法展示所选结果。复现:100 个 `filler` 段落后放一个 `needle`,视口为 800×600 且滚动位置为 `(0,0)`;查找下一处后结果矩形为 `(1418,34218,626,275)` twips,滚动位置仍为 `(0,0)`,视口高度为 9000 twips。临时 LOK 视图探针 `review_search_reveals_match_below_viewport` 已失败。修复方向:选中搜索结果时调整视口以展示结果,并保留内核搜索选区;补充跨页上下一处和替换后的定位测试。 + +crates/ely-lok/src/commands/tools.rs:7 — 查找及替换的 `SearchString` 使用 `Arg::Text`,其 `trim().is_empty()` 校验拒绝合法的纯空白查询,违反 DOC-12。复现:正文为 `a b c`,在查找框输入一个空格;参数校验报 `find.run needs SearchItem.SearchString`,匹配总数保持 0,预期为 2,替换纯空白同样受影响。临时探针 `review_find_accepts_a_space_query` 已失败。修复方向:给搜索字符串使用保留空白的参数校验,空查询由查找流程明确处理,并测试纯空格的查找与替换。 + +../Ely-GPUI-Components/src/menus/hosts.rs:243 — commit `967d1e4` 新增的 `ContextMenu::manual` 文档注释包含 18 个英文词,超过本轮要求的十词上限。复现:对该新增注释去掉 `///` 后按空白分词得到 18;项目内的 `tests/scripts/comments.py` 扫描范围覆盖项目目录,组件仓库的这一行需单独核对。修复方向:将该注释缩为单行十词以内。 diff --git a/tasks/reviews/C08.5-r2.md b/tasks/reviews/C08.5-r2.md new file mode 100644 index 0000000..269777d --- /dev/null +++ b/tasks/reviews/C08.5-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-doc/src/merge/run.rs:116 — 预览等待源标签输入完成后重新读取当前活动标签;生成的等待回调(160–162 行)存在同一问题,违反 DOC-14 的模板内容保真。复现:标签 A 正文为 `SourceA `,标签 B 为 `SourceB `;在 A 排队插入 `«Name»` 并立即预览或生成 `Name=Ada`,等待期间切换到 B。预览正文得到 `SourceB `;生成文件名取自 B,预期正文为 `SourceA Ada` 且文件名取自 A。隔离目录中的 `review_preview_keeps_source_when_active_tab_changes` 和 `review_generate_keeps_source_when_active_tab_changes` 均失败。修复方向:发起操作时捕获源标签 id,等待、模板快照和文件命名始终使用该 id;补充等待期间切换标签的预览及生成回归测试。 + +apps/ely-doc/src/merge/run.rs:75 — 第一阶段按 CSV 列顺序全局替换标记,较短字段标记会改写较长字段标记内部的文字,使后者失去匹配,违反 DOC-14。复现:合法 CSV 为 `Name,«Name»` / `Ada,Bob`,通过两个字段按钮形成模板 `Dear «Name» and ««Name»»`;预览正文得到 `Dear Ada and «Ada»`,预期为 `Dear Ada and Bob`。隔离探针 `review_merge_fills_overlapping_header_markers` 已失败。修复方向:从原模板识别完整字段及其位置,处理重叠标记时保留完整字段边界,再填入字面值;补充包含字段分隔符的列名回归测试。 + +apps/ely-doc/src/find.rs:105 — 每次排队搜索都覆盖共享的 `Finder.asked`,回调处理按最后一次请求的类型解释全部结果(192 行),连续请求会丢失完整匹配缓存,违反 DOC-12。复现:正文为 `cat cat cat`,键入查询 `cat` 后,在全部计数结果到达前按 Enter;等待输入队列完成且内核搜索计数达到 2,查找缓存得到 `(0, None)`,预期总数为 3 并定位当前项。隔离探针 `review_find_count_and_step_queued_together` 连续三次失败;现有测试先等待计数完成再定位,遗漏此顺序。修复方向:串行处理计数与定位,完整计数结果落地后执行排队的定位请求,并将回调与对应查询及请求类型关联;补充计数期间按 Enter 或下一处的回归测试。 diff --git a/tasks/reviews/C08.5-r3.md b/tasks/reviews/C08.5-r3.md new file mode 100644 index 0000000..5a6978d --- /dev/null +++ b/tasks/reviews/C08.5-r3.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-doc/src/merge/run.rs:175 — 每次生成用未递增的 `self.next` 保存到同一个 `doc-{next}/merge.odt`,后台读取前的另一轮生成会覆盖首轮模板,违反 DOC-14 的记录内容保真。复现:已完成输入的标签 A、B 分别含 `SourceA «Name»`、`SourceB «Name»`,CSV 为 `Name` / `Ada`;在首轮后台任务读取模板前,对 A、B 连续发起生成并选择两个输出目录。A 命名的 DOCX 正文得到 `SourceB Ada`,预期为 `SourceA Ada`。隔离探针 `review_two_generations_keep_separate_templates` 重复失败;仅在隔离副本为每轮生成分配独立编号后,该探针通过。修复方向:每轮生成拥有独立的模板快照及暂存路径,保持其生命周期到后台任务结束,并补充连续生成的内容回归测试。 + +apps/ely-doc/src/find.rs:220 — 搜索请求队列只记录请求类型,清空查询后仍将旧查询的在途结果写回匹配缓存,违反 DOC-12。复现:正文为 `cat cat cat`,输入查询 `cat`,在全部计数回调到达前清空查询;待输入队列与搜索回调完成,空查询的匹配总数从 0 恢复为 3。隔离探针 `review_clear_query_while_count_is_pending` 重复得到实际 3、预期 0。修复方向:将结果关联到对应查询及选项的代际,清空或变更查询时使旧结果失效,补充清空查询期间仍有搜索在途的回归测试。 + +apps/ely-doc/src/find.rs:148 — 计数期间的定位请求保存在单个 `Option` 中,后一次请求覆盖前一次,导致用户导航动作丢失,违反 DOC-12。复现:正文为 `cat cat cat`,输入查询 `cat`,在全部计数结果到达前连续按两次 Enter 或点击两次“下一处”;计数与定位完成后当前项为第一项,预期为第二项。隔离探针 `review_two_steps_during_a_count_are_both_run` 重复得到实际 `Some(0)`、预期 `Some(1)`;已有测试仅覆盖等待期间的一次定位。修复方向:按顺序保留并执行每个待处理定位请求,补充计数期间连续导航及前后方向组合的回归测试。 diff --git a/tasks/reviews/C08.6-r1.md b/tasks/reviews/C08.6-r1.md new file mode 100644 index 0000000..ba26a80 --- /dev/null +++ b/tasks/reviews/C08.6-r1.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-doc/src/output.rs:126 — PDF export writes directly to the selected destination without checking whether it is an open document's source. Reproducer: open a FODT, choose Export as PDF, enter that FODT's filename in the native save panel, and confirm Replace. The real window reports successful export while the source becomes `%PDF-1.7`; the isolated application probe likewise replaced a 1,243-byte FODT with a 26,002-byte PDF. The tab still identifies the file as its editable source. Fix direction: check destination identity against open source files before exporting, including aliases, and refuse source replacement by a different output format; add a source-preservation regression through the export path. + +apps/ely-doc/src/navigate.rs:148 — Navigation history reads the cached cursor before preceding queued jumps have updated it. Reproducer: from page one, jump to page two and then page three before the first cursor callback arrives, wait for page three, and invoke Back. Both history entries contain the original cursor `(1418, 1658, 0, 414)`; Back returns to page one (`y=1658`) instead of page two (`page top=16408`). This breaks DOC-16's return-to-previous-position behavior. Fix direction: serialize navigation/history capture with completion of the preceding jump and its cursor update, and cover consecutive jumps without settling between them. + +crates/ely-lok/src/events/parse.rs:56 — Treating every semicolon-containing font name as an internal fallback silently removes document-declared missing font lists. Reproducer: open a FODT whose font declaration names `Nowhere Sans;Nowhere Serif`, with both families absent. The real worker logs that exact list in FONTS_MISSING, but `Events.fonts` becomes empty and ElyDoc suppresses its missing-font banner. The isolated engine assertion fails with `fonts=[]`. This hides the font replacement and pagination risk required by §8.3. Fix direction: preserve document-declared missing families/lists and distinguish internal fallback candidates using their provenance; add coverage for an explicit missing multi-family declaration. + +crates/ely-lok/src/view/scroll.rs:59 — Explicit navigation uses `show`, which leaves an already visible target at its existing position, while Navigator computes the current item from the viewport's one-third reading line. Reproducer in the real window: put First part and Second part on the same page, then click First part in the outline. The cursor is at First part, but Second part retains the current-item marker; repeated clicks keep the mismatch. The isolated geometry probe also shows a visible target at `y=6000` leaving the reading line at `3000`. This contradicts C08.6's verified claim that the current item follows navigation. Fix direction: make explicit navigation and current-item calculation agree for visible targets and scroll-boundary clamping, and add a same-page rendered-current-item regression. diff --git a/tasks/reviews/C08.6-r2.md b/tasks/reviews/C08.6-r2.md new file mode 100644 index 0000000..58db8e0 --- /dev/null +++ b/tasks/reviews/C08.6-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-doc/src/output.rs:114 — The open-source export guard compares canonical paths through `save::holder`; different hard-link paths to the same file pass the guard. Reproducer on macOS with the real worker: open the 1,243-byte `long.fodt` fixture, create `alias.pdf` with `std::fs::hard_link(&source, &alias)`, and call `export_to` with that alias. Export succeeds and the still-open source becomes a 26,002-byte file starting with `%PDF-1.7`. This reproduces in two isolated runs and leaves R1's source-preservation defect open for hard-link aliases. Fix direction: compare filesystem file identities against open sources, propagate identity-read failures, and add a hard-link source-preservation regression through `export_to`. + +apps/ely-doc/src/navigate.rs:160 — `going` survives completion of its jump, so a later manual return to its original cursor is mistaken for a jump still in flight. Reproducer with the real worker: start at `(1418, 1658, 0, 414)`, jump to the second heading and wait for completion, manually click back at the original cursor and wait, then jump to page three and invoke Back. History stores the second heading `(1418, 17542, 9971, 275)` and Back actually lands at `y=17542`, although this jump left from `y=1658`. Both isolated runs fail; DOC-16's return-to-original-position contract breaks. Fix direction: retire pending-jump state when its input and cursor update complete, invalidate it after manual movement, and cover a completed jump followed by manual return, another jump, and Back. diff --git a/tasks/reviews/C08.6-r3.md b/tasks/reviews/C08.6-r3.md new file mode 100644 index 0000000..6b5f2d4 --- /dev/null +++ b/tasks/reviews/C08.6-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-doc/src/navigate.rs:139 — The current outline item compares only vertical coordinates, so headings in adjacent columns are attributed to the last column even when the cursor is in the first. Reproducer with the real macOS worker: use the navigation LONG fixture with column breaks instead of page breaks, apply `page.columns` with `2`, and jump to the first heading. The heading rectangles are `(1418, 1418, 4985, 653)` and `(6404, 1418, 4985, 275)`; the cursor lands at `(1418, 1658, 0, 414)`, but `current` returns `Some(1)` instead of `Some(0)`. Two isolated runs fail. A real window independently shows the caret and AX paragraph at “First part” while the outline marker and emphasis remain at “Second part”, contradicting C08.6's cursor-following current item. Fix direction: determine the current heading using document order and its position in the page/column, accounting for the cursor's horizontal position; add a two-column regression that checks the rendered current item after jumping between columns. diff --git a/tasks/reviews/C09.1-r1.md b/tasks/reviews/C09.1-r1.md new file mode 100644 index 0000000..7365c28 --- /dev/null +++ b/tasks/reviews/C09.1-r1.md @@ -0,0 +1,19 @@ +FAIL + +apps/ely-table/src/sheets.rs:130 — 工作簿加载与工作表状态回显之间存在整窗崩溃窗口。`loaded` 先把 `extra: Default::default()` 的标签设为活动标签,`sheet_strip` 随即用空 choices 和 selected="0" 构造 Tabs;真实窗口启动时及 Cmd+N 新建第二个工作簿时均复现 `tabs Name("table-sheets") have no tab 0`,应用 abort,已有编辑随进程退出。现有三个应用测试只挂载返回 Empty 的 Blank 窗口,绕过了 Book 渲染;本轮 worker 构建、cargo check/test --workspace、doc-shell.sh、doc-props.sh 全通过仍漏过此故障。修复方向:让工作表列表与有效当前项就绪后再渲染工作表 Tabs,覆盖真实启动、打开及连续新建的首帧回归。 + +apps/ely-table/src/sheets.rs:151 — 工作表切换直接调用 worker,越过 LokView 的有序输入队列,待处理编辑会写到另一张表。具体复现:两张空表,显示第一张,在同一调度轮调用 `run_active("cell.enter", &["42"])` 后立即走 `show_sheet(1)`;队列完成后第一张 A1 为空,第二张 A1 为 42,保存 FODS 也确认了错误归属。临时副本中让切换等待原表输入落下后,同一回归通过。修复方向:把切表纳入同一输入顺序,或等已排队输入完成后切换,并加入保存后逐表核对内容的竞态回归。 + +crates/ely-lok/src/view.rs:273 — 自动滚动只响应文本光标 `batch.cursor`,Calc 的 `batch.cell.cursor` 只被赋值。真实窗口在名称框输入 A100 并回车,内核和名称框已经到 A100,画布仍显示第 1–32 行,活动单元格与填充柄都在可视区外;方向键移出可视区也走相同路径。修复方向:按文档类型把单元格光标变化接到可见区更新,覆盖远处跳转与键盘连续移动的画布、表头和选区断言。 + +crates/ely-lok/src/view.rs:401 — 冻结命令只改变内核状态,画布、表头、光标与输入仍共用一个随 scroll 平移的原点,冻结区域未参与视图变换。真实窗口在 B2 执行 Cells → Freeze or Unfreeze Panes 后向下滚动一屏,顶部变成第 32 行,首行随画布消失,违反 TABLE-02;现有 workbook 测试只发冻结命令。修复方向:读取内核冻结位置,并让冻结区与滚动区的图块、表头、选区及命中坐标使用对应变换;增加冻结后双向滚动及点击编辑回归。 + +apps/ely-table/src/cells.rs:197 — 行高/列宽对话框把选区操作缩成当前光标的一行或一列。真实窗口选 A1:C1,在 Column Width 输入 3 厘米并提交,只有 A 列变宽,B、C 保持原宽;多行选区的 Row Height 同样只传当前 Row。修复方向:菜单尺寸操作针对内核当前选区内的全部行列,保留拖动单个表头边界的明确单行/列目标,并分别验证多行、多列和不连续选区的保存结果。 + +apps/ely-table/src/sheets.rs:139 — 整个标签条共用一个作用于活动表的右键菜单,丢失右键点击的工作表身份。真实窗口创建 Sheet1、Sheet2,保持 Sheet2 活动,右键 Sheet1 并选 Rename Sheet…,弹出的标题和输入框均为 Sheet2;复制、隐藏、删除也使用同一活动表路由。修复方向:菜单请求携带被点击表的身份,让各命令及影响确认绑定该目标,并加入非活动标签的右键回归。 + +patches/libreoffice/0012-lok-calc-cursor-references-merge.patch:42 — 删除影响查询只检查静态引用 token,把动态跨表引用误报为零影响。真实内核复现:S.A1 为 `Hello 世界`,U.A1 为 `=INDIRECT("S.A1")`;查询 S 得到 `formulas=0,names=0,first=""`,界面据此声称没有其他表或名称引用它。删除 S 后保存的 U.A1 从 `Hello 世界` 变成 `#REF!`。修复方向:将动态依赖纳入删除影响说明,对无法确定的引用显示明确限制,并加入删除前后值与错误状态的回归。 + +crates/ely-office/src/files.rs:83 — 平面 ODF 的宏检测只读取根标签上的命名空间绑定,漏掉子元素合法声明的 Basic 命名空间。具体样本:正常 `office:document` 根,在 `office:script` 上声明 `xmlns:ooo="http://openoffice.org/2004/office"`,其下含 `ooo:library-embedded` 与 Basic 模块。该 FODS 经真实 LOK 成功加载为工作表 S,保存后 Basic 库仍存在,`macro_risk` 却返回 false,应用省略风险状态和宏处理提示,违反 LOK-11。修复方向:按 XML 命名空间及元素局部名解析完整作用域,加入嵌套声明、前缀替换及命名空间遮蔽回归。 + +apps/ely-table/src/grid.rs:172 — 自绘行列表头只有 mouse-down 回调,缺少角色、可访问名称、Tab 停靠、焦点环和键盘激活。真实窗口 AX 树包含工作簿标签、两个输入框、网格文本区和工作表标签,所有可点击行列表头均缺席;键盘和辅助技术无法定位并激活 A 列或第 1 行表头,违反 agents.md 的可按压元素规则及 UX-03、UX-12。修复方向:为表头提供对应语义、可见焦点与键盘选择操作,并以真实 AX 树和键盘流程验证整行、整列及扩展选择。 diff --git a/tasks/reviews/C09.1-r2.md b/tasks/reviews/C09.1-r2.md new file mode 100644 index 0000000..71ac66d --- /dev/null +++ b/tasks/reviews/C09.1-r2.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-lok/src/worker.rs:243 — SHEETS 与 SHOW_SHEET 直接调用当前内核视图的 getPart/setPart,未切到请求对应文档的 LOK view;同时打开多个工作簿后,切表会落空并返回成功,当前表读取也会混入另一工作簿的状态。真实 worker 复现:打开 S/T/U 三表工作簿,先显示 U,再打开只有 G 的第二个工作簿;对第一份请求显示 S,随后在第一份 C1 输入 123456 并保存 FODS,123456 仍落在 U,S 为空;第一份的 sheets.current 同时读成第二份的 0。隔离副本在加载时保存各自 view id、请求前 setView 后,逐表保存断言与双文档 current 断言均通过。修复方向:所有相关读写绑定对应文档的内核视图,校验目标表及切换结果,并补双工作簿往返切表与保存内容回归。 + +apps/ely-table/src/sheets.rs:161 — 等待旧输入完成的切表请求会被随后输入超越,仍未保持完整输入顺序。原 R1 的“输入后切表”回归已通过;扩展复现为两张空表、显示第一张,同一调度轮依次 run_active("cell.enter", &["42"])、show_sheet(1)、run_active("cell.enter", &["99"]),完成后保存 FODS:第一张 A1 为 99,第二张为空,42 被覆盖。隔离副本将切表请求排进 LokView 输入队列后,第一张保留 42、第二张得到 99。修复方向:切表进入与键入、命令共用的有序队列,覆盖切表前后均有输入的保存回归。 + +apps/ely-table/src/sheets.rs:161 — 延迟切表回调重新读取 active_tab,丢失发起请求的工作簿身份。具体复现:两份各有两张表的工作簿都显示第一张;在第一份排队输入 42 后请求显示第二张,立即选择第二份工作簿;等待完成后,第一份 extra.sheets.current 为 0,第二份变成 1。原样隔离回归失败,将切表绑定原文档并排入其输入队列后得到预期 (1, 0)。修复方向:请求保存原工作簿 id 和文档身份,完成与失败回报均作用于该工作簿,并加入等待期间切换或关闭工作簿的回归。 + +crates/ely-lok/src/view/scroll.rs:24 — 单元格自动滚动仍被缓存的 doc_size 截断,R1 的远处跳转缺陷继续存在。真实 ElyTable 空白工作簿在名称框输入 A100 并回车,名称框已显示 A100,画布与 AX 表头停在第 20–51 行,活动格与填充柄均在视口外;再跳 A101 仍停在同一范围。新回归 a_moved_cursor_scrolls_into_view_and_a_still_one_does_not 在 Writer fixture 上注入位于现有 doc_size 内的假 Cell,绕过了 Calc 空白区域扩展及真实跳转路径。修复方向:让 Calc 的滚动边界与工作表几何及可见区扩展一致,更新尺寸后完成目标格可见性校验;以新建空白表的真实远处跳转和连续方向键验证画布、表头与填充柄。 + +apps/ely-table/src/book.rs:63 — 名称框与公式栏仅在活动 LokView 发观察通知时同步,工作簿标签切换只改变 core.active,静止的目标文档留下上一工作簿的值。真实窗口复现:第一份空白工作簿光标在 A101,第二份 A1 输入 second;点击第一份工作簿标签后,画布和内容区切回第一份空表,名称框仍为 A1、公式栏仍为 second,持续保持到该视图再次产生通知。修复方向:活动工作簿变化时立即从其 LokView 同步两个字段,并覆盖两份静止工作簿反复切换及关闭当前标签后的可见值回归。 diff --git a/tasks/reviews/C09.1-r3.md b/tasks/reviews/C09.1-r3.md new file mode 100644 index 0000000..0173fcb --- /dev/null +++ b/tasks/reviews/C09.1-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-table/src/sheets.rs:257 — 删除工作表的等待回调重新读取活动工作簿,丢失发起删除的文档身份。真实 worker 的隔离应用回归:两份工作簿各有两张表,第一份当前为 OriginTwo,第二份当前为 OtherTwo;在第一份同一调度轮排队输入 42 并调用 ask_delete,立即切到第二份。等待完成后确认框显示 OtherTwo,确认删除后两份的工作表数量为 (2, 1),第一份的删除请求实际删除了第二份的表,违反 TABLE-01 与 UX-07。隔离副本绑定原工作簿后,同一回归得到 OriginTwo 和 (1, 2)。修复方向:等待请求及确认框携带原工作簿和目标工作表身份,引用查询与确认删除都按该身份执行;目标关闭或变化时重新校验,补等待期间切换工作簿和确认删除后的逐工作簿断言。 + +crates/ely-lok/src/view/scroll.rs:54 — 水平自动滚动只比较单元格左边缘,遗漏右边缘越出视口的情况。真实 ElyTable 默认 1100×720 窗口、新建空白表,在名称框输入 M1 并回车:名称框显示 M1,画布保持 A 列起始位置,M1 只露出左侧,右边框与填充柄均被窗口裁掉;从 L1 按右方向键进入 M1 同样走此判断,影响 TABLE-02 的填充柄操作。隔离断言以 1052 像素网格视口和 M1 的 (15360, 0, 1280, 256) twips 验证右下角可见性,原样失败,改为比较 x + w 后通过。修复方向:水平滚动按完整目标矩形及冻结区可用宽度判断,覆盖部分可见的右边缘单元格、键盘进入及填充柄可见性。 diff --git a/tasks/reviews/C09.2-r1.md b/tasks/reviews/C09.2-r1.md new file mode 100644 index 0000000..4c08c54 --- /dev/null +++ b/tasks/reviews/C09.2-r1.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-table/src/formulas.rs:243 — Name management discards the selected name's scope when jumping (TABLE-06). Define Local as $B$2 in worksheet Other, switch to the first worksheet, select Local (Other) in Names, and press Go To. The handler sends cell.goto("Local"); the patched engine returns `cell.goto: the kernel reported Some((".uno:GoToCell", false))`, and the target remains unselected. A workbook name shadowed by a same-named local name also resolves according to the current sheet instead of the selected entry. The existing name test only jumps to the global Rate. Fix: resolve and navigate the exact selected scope through the kernel; add cross-sheet local-name and shadowed-global-name regressions. + +apps/ely-table/src/formulas.rs:91 — Define Name snapshots the selection before queued input and its callbacks settle. From A1, queue cell.goto("B2:C3") and immediately invoke Define Name. After the view settles on B2:C3, the dialog still contains '工作表1'!$A$1; submitting defines the name on the old cell. An isolated GPUI/real-worker regression reproduced this stale range. This contradicts the documented default of the selected area's absolute reference. Fix: retain the originating tab ID, wait for its input and selection state, then populate the dialog from that tab; cover pending cell and worksheet changes. + +apps/ely-table/src/formulas.rs:104 — A named selection silently becomes an empty default reference. Define Area on B2:C3, move to A1, jump to Area through the name box, then invoke Define Name again. CELL_ADDRESS now contains Area; refers_to accepts only A1 addresses, so unwrap_or_default clears the Refers to field and disables Define despite a valid selection. Both a real-worker regression and the native AX tree reproduced the empty field while the name box showed Area. This violates the selected-range default and the rule against silent fallbacks. Fix: obtain the actual selected range, resolving named selections through the kernel, and surface an actual resolution failure; add this selection path to the tests. + +apps/ely-table/src/formulas.rs:188 — The deferred Names action changes its target workbook. With two named workbooks open, queue an edit in workbook A, invoke Names before it settles, and switch to B. The continuation calls ask_names again using core.active, so the resulting dialog lists B's names and its Apply/Delete actions operate on B. An isolated GPUI/real-worker test captured originating tab ID 1 and dialog tab ID 0. Fix: pass the captured tab ID through the continuation and read names from that workbook; test a tab switch during the wait and verify subsequent mutations stay on the originating workbook. + +patches/libreoffice/0015-aqua-no-wait-with-a-due-timer.patch:8 — Added comments violate the one-line, ten-word rule. `python3 tests/scripts/comments.py` exits 1: lines 8–9 form a two-line comment and each exceeds ten words. The same check reports overlong scoped comments at crates/ely-lok/src/client/tests/formula.rs:7, crates/ely-lok/src/sheet.rs:234, crates/ely-lok/src/view/formula.rs:102, apps/ely-table/src/sheets.rs:127, and patches/libreoffice/0014-lok-calc-formula-entry-and-names.patch:117 and :228. Fix: shorten each comment to a single line of at most ten words and rerun the comment check. diff --git a/tasks/reviews/C09.2-r2.md b/tasks/reviews/C09.2-r2.md new file mode 100644 index 0000000..d4801a4 --- /dev/null +++ b/tasks/reviews/C09.2-r2.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-table/src/sheets.rs:331 — Define Name builds its scope choices from the active workbook while the repaired deferred action retains the originating workbook (TABLE-06). Open workbook A with sheet Origin and workbook B with sheet Destination. In A, queue a move to B2:C3, invoke Define Name, and switch to B before the dialog appears. The dialog correctly retains A's tab and Origin!$B$2:$C$3, but its Scope list offers Workbook and Destination; Origin is absent. Selecting Destination and submitting Area closes the dialog and reports `names.define: the undo history did not move`, because A has no such sheet. An isolated GPUI/real-worker probe captured scope choices ["Destination"]; the native window reproduced the field, popup choices, and failed submission. The existing queued-definition regression checks the reference text and omits the rendered scope choices. Fix: obtain Define Name's sheet choices from its stored Dialog::Define.tab, and cover a workbook switch during the wait through scope selection and successful definition in the originating sheet. diff --git a/tasks/reviews/C09.2-r3.md b/tasks/reviews/C09.2-r3.md new file mode 100644 index 0000000..9239fa1 --- /dev/null +++ b/tasks/reviews/C09.2-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-table/src/formulas.rs:266 — Go To moves the named selection in a hidden workbook while leaving another workbook visible (TABLE-06). Open A with Spot referring to Origin!$C$7 and B with Destination selected at A1. Queue input in A, invoke Names, and switch to B before the deferred dialog opens. The dialog correctly lists A's Spot. Clicking Go To closes it and moves A's selection, while B remains active and its name box still shows A1; manually switching back to A reveals Spot. An isolated GPUI/real-worker assertion on core.active failed, and the native window reproduced the unchanged visible workbook and address. The existing regression checks A's hidden selection and omits the active workbook. Fix: activate the dialog's originating tab as part of Go To, synchronize the visible bars and grid, and cover the displayed workbook and target selection after a switch during the wait. diff --git a/tasks/reviews/C09.2a-r1.md b/tasks/reviews/C09.2a-r1.md new file mode 100644 index 0000000..5c711b5 --- /dev/null +++ b/tasks/reviews/C09.2a-r1.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-lok/src/view.rs:389 — 图块重绘失败被留在日志里,界面持续显示旧内容且同一代际的重绘被禁止,违反 agents.md 规则 2 与 PRD UX-06。复现:先让 (0, 0) 的 generation=0 图块落地,再应用 generation=1、all=true 的作废批次,对替换请求注入可恢复的 paint_tile 错误;finish_tile 只记录警告并写入 failed,下一次 prepare 继续返回旧图像,request_tile 因 failed 提前返回,LokError 订阅者收到零条错误。隔离副本的失败探针已复现;加入 LokError 发射后同一探针通过,订阅通路另以控制事件验证。修复方向:将绘制失败传到应用的错误通路,提供明确的失败状态及重试路径,并覆盖保留旧图块时的失败与恢复。 + +crates/ely-lok/src/client/tests/paint.rs:353 — C09.2a 的主题回归测试直接调用 Worker::run,并自行传入深浅方案名,绕过 LokView::render 的主题选择与有序队列,应用内容区随主题变化的接线缺少回归保护。复现:在隔离副本把 view/render.rs:19 的 DARK 值改为 COLOR_SCHEME_ELY_LIGHT,the_page_follows_a_dark_theme_and_back 与全部 19 个 view::tests 仍通过;以 ELY_APPEARANCE=dark 启动真窗口 lokview,截图出现白色图块,正确实现的同一窗口为深底浅字。修复方向:从真实应用主题变化驱动 LokView,断言显示出的图块颜色及切回结果,并使上述错误方案参数的变异触发测试失败。 diff --git a/tasks/reviews/C09.2a-r2.md b/tasks/reviews/C09.2a-r2.md new file mode 100644 index 0000000..7718ab8 --- /dev/null +++ b/tasks/reviews/C09.2a-r2.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-lok/src/view/tests/theme.rs:38 — R1 第二项仍有回归缺口:测试直接调用 follow_theme(dark),再向 worker 取图块,跳过真实应用主题到 LokView::render 的接线与视图显示。复现:隔离副本把 view/render.rs:41 改为 self.follow_theme(false),全部 21 项 view::tests 仍通过,包含新增的 the_view_paints_the_app_theme_and_back;同一变异以 ELY_APPEARANCE=dark 启动真窗口,页面图块平均亮度为 253,深色断言失败。正确实现的同一真窗口探针通过,切换应用主题得到 27→253。修复方向:由真实应用主题变化驱动 LokView 的渲染,断言视图实际显示的深浅图块与切回结果,并使固定浅色参数的变异触发回归失败。 diff --git a/tasks/reviews/C09.2a-r3.md b/tasks/reviews/C09.2a-r3.md new file mode 100644 index 0000000..37d762a --- /dev/null +++ b/tasks/reviews/C09.2a-r3.md @@ -0,0 +1,5 @@ +FAIL + +tests/scripts/theme.sh:40 — 真窗口回归仅断言中央区域的平均亮度,页面背景可独立满足深浅色阈值,实际图块显示缺少回归保护。R2 的固定浅色变异现已触发失败(252);新复现:隔离副本把 view/render.rs:92 的图块绘制循环改为 painted.tiles.iter().take(0),截图中的整个网格消失,只剩背景和自绘表头,全部 21 项 view::tests 仍通过,theme.sh 仍返回 0 并输出 ok: theme (dark 25, light 255)。正确实现输出 dark 26、light 252。修复方向:让真窗口断言同时核对实际内核图块内容与主题颜色,使用可识别的单元格内容或网格特征,使省略图块绘制的变异触发失败。 + +tests/scripts/theme.sh:34 — 权限拒绝分支的 exit 0 只退出 shade_of 的命令替换子进程,SKIP 文案随后被当作亮度做整数比较,脚本最终返回 FAIL。复现:隔离副本让 window-shade.swift 返回其截图失败分支的 denied;运行原样 theme.sh,输出 line 40: integer expression expected 和 FAIL: the dark grid paints at SKIP: needs Screen Recording permission,退出码 1。修复方向:把截图不可用状态传到顶层统一处理并退出 SKIP,亮度变量只接收数值。 diff --git a/tasks/reviews/C09.3-r1.md b/tasks/reviews/C09.3-r1.md new file mode 100644 index 0000000..6a7b0da --- /dev/null +++ b/tasks/reviews/C09.3-r1.md @@ -0,0 +1,21 @@ +FAIL + +apps/ely-table/src/rules.rs:96 — 等待输入落地后重新调用 `ask_rule`,它重新读取活动工作簿,丢失发起工作簿的身份;`data.rs:288` 的分列和本文件 `428` 的条件格式列表使用同一重入方式。复现:工作簿 A 排队跳到 A2,立即打开数据验证,再切到 B;等待结束后 `Dialog::Validation.tab` 为 B,确认将修改 B。隔离探针得到 `Some(1)`,期望 `Some(0)`。修复方向:等待回调捕获发起的 tab id,后续读取、对话框和提交全程使用该 id,并为三条等待路径补回归。 + +crates/ely-lok/src/view/clip.rs:89 — 内核剪贴板的可用性绑定到目标视图的 `copied`,跨工作簿粘贴公式会进入纯文本分支并丢失公式。复现:A 的 B1 为 `=A1*3`、显示 6,复制 B1;切到从未复制过的 B,在 C1 执行“粘贴为公式”;保存 FODS 后 C1 是数值 6,公式数保持 1,期望新增公式后为 2。同视图现有回归通过,跨工作簿探针失败。修复方向:在共用 worker 的剪贴板状态中记录复制来源,验证系统剪贴板仍对应该来源后,允许目标工作簿使用内核的公式粘贴;增加跨工作簿保存结果断言。 + +crates/ely-lok/src/view/clip.rs:92 — 外部剪贴板分支对两个选择性粘贴操作均调用普通文本粘贴,“粘贴为值”会执行以 `=` 开头的文本。复现:系统剪贴板写入 `=1+2`,在空格执行“粘贴为值”;保存 FODS 后出现新增的 `table:formula`,实际公式数 2,期望保持 1。修复方向:根据粘贴操作设置内核的导入与选择性粘贴方式,使值模式保留值或字面文本,并用保存文件检查公式属性。 + +apps/ely-table/src/rules.rs:240 — 验证规则缓存仅以地址判定是否重读,同格应用或撤销规则后继续使用旧规则,同地址切换工作表也会复用它。复现:停在 E2,初始缓存为 `any`,应用带下拉列表和输入提示的 `list` 规则;等待队列落地后内核返回 `list`,界面缓存仍为 `any`,`cell_aids` 因此继续隐藏下拉按钮和提示。修复方向:缓存键包含工作表身份与规则变更状态,在应用、清除、撤销和切表后重读,并验证当前格的缓存及可见控件。 + +apps/ely-table/src/rules.rs:31 — “清除数据验证”用 `Validation::default()` 构造 `any`,序列化得到空 `operator` 和 `errorStyle`;补丁第 455 行校验这两项后拒绝执行。复现:E2 已有 `list` 验证,发送菜单生成的清除参数,worker 返回 `rules.validation: the undo history did not move`,原验证规则保留。修复方向:为清除发送合法的 `any` 参数,并在内核按该模式处理无关字段;增加从实际菜单参数到规则消失的回归。 + +crates/ely-lok/src/sheet/data.rs:157 — `type == "any"` 时直接返回默认字段,丢弃已存在的输入提示;`apps/ely-table/src/rules.rs:126` 又将此类规则重建为默认配置。复现:数据验证选择“任何值”,开启输入提示并填写 `Enter an identifier`;内核实际返回 `showInput:true` 和完整消息,解析结果却为 `show_input:false`、空消息,重新选择该格或重开验证对话框后提示消失。修复方向:区分“没有规则”和“已有 unrestricted 规则”,完整保留后者的提示字段,并覆盖应用、重读及再次编辑。 + +apps/ely-table/src/format.rs:132 — 数字格式缓存键只有地址与编辑计数,工作表切换保持同地址时跳过重读。复现:第一表 A1 为百分比,第二表 A1 为货币;面板显示第二表后切回第一表,内核返回 `percent`,面板仍为 `currency`。隔离探针确认该不一致。修复方向:将工作表身份加入缓存键,在切表完成后刷新格式类别和代码,并增加同地址切表回归。 + +crates/ely-lok/src/events.rs:263 — 将完整 `Request::Invalid` 以 Debug 写入日志,包含文档自定义的验证标题和消息;`apps/ely-table/src/rules.rs:223` 还把同一消息交给会记录全文的 `Tabs::toast`。复现:验证消息为 `PrivateWorkbookMessage`,触发无效输入;捕获的日志含 `Invalid { title: "PrivateTitle", message: "PrivateWorkbookMessage", ... }`,违反 agents.md 规则 4 和 PRD §18.2 的日志约束。修复方向:日志仅记录请求种类、样式和接受状态,消息内容留在界面反馈通路;对日志中用户文档内容缺失作断言。 + +crates/ely-lok/src/client/tests/rules.rs:118 — 边框验收只检查 `worker.run` 成功,无法发现已撤销记录掩盖的实际格式缺失。复现:在隔离副本中把边框命令的全部 `Arg::Line(..., true)` 改为 `false`,构建独立 mutant worker;现有 `number_formats_alignment_and_fills_apply` 仍通过,而保存 FODS 的探针确认所选 B2:B5 的边框样式缺失。同一保存探针在原 worker 下通过。修复方向:分别验证全部、外框和清除操作后所选单元格实际保存的外边框与内边框属性,使该变异触发失败。 + +patches/libreoffice/0016-lok-calc-formats-data-rules.patch:501 — 文件共 919 行,超过 agents.md 规则 5 的 500 行上限。复现:`wc -l` 返回 919。修复方向:按格式、数据整理和规则拆分补丁,使每份符合行数限制,并同步构建脚本中的补丁顺序与重复应用检查。 diff --git a/tasks/reviews/C09.3-r2.md b/tasks/reviews/C09.3-r2.md new file mode 100644 index 0000000..2008a1d --- /dev/null +++ b/tasks/reviews/C09.3-r2.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-table/src/data.rs:355 — 选择性粘贴仅以纯文字相等判断复制来源,相同显示值会让另一工作簿的复制通过检查。复现:A 的 E2 为 `=B2*2`、显示 6,复制 E2;B 的 A1 为 `=2+4`、同样显示 6,复制 A1;返回 A,在 F2 执行“粘贴为公式”。实际选择性粘贴处理器的隔离探针保存 FODS 后公式数从 1 增到 2,写入 A 保留的旧内核剪贴板公式;当前约定要求拒绝 B 的复制。修复方向:跟踪系统剪贴板的复制身份与变更版本,将其关联到持有内核单元格剪贴板的视图,覆盖相同文字的跨工作簿与外部复制场景。 + +apps/ely-table/src/rules.rs:143 — 打开数据验证仍在界面处理器中获取 worker 锁并同步读取内核,违反 PRD PERF-02 和 2026-10-05 的界面线程约束。复现:另一后台操作持有 worker 锁 300 毫秒时打开数据验证,隔离探针测得处理器阻塞 304.7 毫秒。同类同步路径仍位于 `rules.rs:332`、`data.rs:174`、`data.rs:371`、`rules/aids.rs:37`、`format.rs:184`;`crates/ely-office/src/find.rs:225` 在查找期间收到编辑回报时同步读取撤销历史。修复方向:将这些读取放入后台,在回调中核对发起文档与读取版本,并补充 worker 延迟时界面处理器及时返回的回归。 + +apps/ely-doc/src/review.rs:115 — 切换文档时立即替换 `shown`,同时保留上一文档的修订、批注与线程列表,后台读取期间这些旧内容仍可操作。复现:A、B 各有一条修订,面板显示 A 的 `TrackedAlpha`;将 B 的读取延迟 300 毫秒后切到 B,面板仍显示 A 的修订,此时点击“全部拒绝”。隔离探针确认 B 的修订数从 1 变为 0。修复方向:文档切换时清除旧列表或阻止其操作,让显示内容、操作目标和异步读取版本保持一致,并覆盖延迟读取期间切换与操作的场景。 + +apps/ely-table/src/rules.rs:261 — 规则读取失败后仍无条件调用 `follow_rule`,失败路径保持缓存缺失,随即重新提交相同后台读取;`book.rs:197` 的工作表与几何读取存在相同循环。复现:规则缓存需要刷新时终止该测试的 worker,200 次调度产生 66 条引擎错误反馈,读取任务持续自行重排,造成错误提示与日志刷屏。修复方向:记录失败对应的读取状态并结束当前请求链,后续重试由新的状态变化或明确恢复操作触发;增加 worker 崩溃后请求及反馈次数有界的回归。 + +crates/ely-lok/src/view.rs:415 — 任意被接纳的图块都会结束 `edit_at`,计时与对应输入落地、编辑后的图块版本及实际显示脱节。复现:已有旧图块结果等待界面接收,此时提交新文本,再接收旧图块;隔离探针确认 `edit_pending()` 仍为 true,计时已被清除。`tests/scripts/lag.sh:42` 用这些记录计算“edit to tile”分位数,旧画面可以贡献很短的样本,当前结果无法证明所声明的按键到新画面延迟。修复方向:将计时关联到输入序号及其编辑后的图块版本,在对应可见画面到达时记录,并覆盖旧图块先到、连续输入与滚动图块交错的场景。 diff --git a/tasks/reviews/C09.3-r3.md b/tasks/reviews/C09.3-r3.md new file mode 100644 index 0000000..2812242 --- /dev/null +++ b/tasks/reviews/C09.3-r3.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-office/src/save.rs:334 — 异步导出完成后,保存成功分支直接执行关闭,遗漏导出期间新增的编辑,违反 FILE-01、FILE-03 与 QA-08。复现:文档输入 `BeforeExport`,执行关闭提示中的保存;在内核导出完成、完成回调尚待处理时输入 `AfterExport`。隔离探针延迟完成回调并通过真实输入桥提交新文字,确认标签被关闭,存档包含 `BeforeExport`、缺失 `AfterExport`。修复方向:保存任务携带实际导出修订,完成时核对当前修订及排队输入;后续编辑保留未保存状态,关闭流程继续处理这些编辑。补充导出期间继续编辑并保存关闭的回归。 + +apps/ely-table/src/format.rs:192 — 自定义数字格式在后台校验成功后,通过 `run_active` 发送给回调完成时的活动工作簿,丢失发起目标,违反 TABLE-07。复现:A、B 的 A1 均为常规格式;在 A 提交 `0.000`,校验回调返回前切换到 B。真实内核探针读得 A/B 的格式分别为 `General`、`0.000`,A 的操作修改了 B。修复方向:捕获发起视图与选区版本,校验完成后向对应目标提交,处理等待期间选区变化;增加延迟校验期间切换工作簿的回归。 + +crates/ely-office/src/save.rs:230 — 另存为只在后台导出前检查目标占用,写入回调直接保存,遗漏等待期间新增的文件持有者,破坏 §6.1 的独立会话约束。复现:A、B 各含 `FirstOwner`、`SecondOwner`,在两次导出完成前均另存为同一新路径 `same.odt`。真实内核探针确认两个标签的会话指向同一文件,最终文件仅含 `SecondOwner`;第二次保存覆盖了第一标签的文件。修复方向:提交前按文件身份重新检查目标持有者,并覆盖并发另存为相同路径的回归;同标签保存队列继续按现有顺序执行。 + +apps/ely-doc/src/merge/run.rs:149 — 预览发起后台模板导出时直接使用 `core.next`,直到后续载入才分配编号;等待期间另一预览复用并重建同一临时目录,导致模板串用,违反 DOC-14 与 FILE-08。复现:A 正文为 `SourceAlpha «Name»`,B 为 `SourceBeta «Name»`;延迟 A 模板导出的完成回调,先生成 B 的 Grace 预览,再放行 A 的 Ada 预览。探针保存并读取两个预览,标题属于 A 的 Ada 预览实际包含 `SourceBeta` 和 `Ada`,缺失 `SourceAlpha`。修复方向:每次预览在异步工作发起前保留独立编号与临时目录,后台导出、载入和填充沿用该身份;覆盖两个来源交错预览的回归。 + +crates/ely-office/src/save.rs:39 — 标签栏关闭调用 `request_close` 后,干净标签仍在界面线程通过 `dirty_now` 获取 worker 锁并同步读取撤销历史,违反 PERF-02 与 agents.md 的界面线程约束。复现:后台操作持有 worker 锁 350 毫秒时关闭干净的 ElyTable 标签;直接调用标签栏对应处理路径的隔离探针测得 370.8 毫秒。修复方向:标签关闭与菜单退出的读取、文档释放移到后台,返回时核对发起标签和编辑版本;窗口关闭回调保留约定的同步判断。增加 worker 延迟期间普通标签关闭及时返回的回归。 diff --git a/tasks/reviews/C09.4-r1.md b/tasks/reviews/C09.4-r1.md new file mode 100644 index 0000000..4ca1c86 --- /dev/null +++ b/tasks/reviews/C09.4-r1.md @@ -0,0 +1,13 @@ +FAIL + +patches/libreoffice/0019-lok-calc-pivot-tables-and-charts.patch:257 — 图表插入只校验 Chart 外观,错误的 RangeList 会沿用内核示例数据并报告成功。复现:选中有数据的区域,打开 Insert → Chart,将 Data range 改为 `bad range` 后插入;真实窗口显示 Row 1–4、Column 1–3 的示例数值,且没有错误提示。真实 worker 的 `chart.insert` 返回 `Ok(())`,回读区域为空。修复方向:在创建图表和写入撤销历史前严格解析并校验整个数据范围,失败时返回错误并保持文档原状;补充错误范围的状态及画面断言。 + +apps/ely-table/src/analysis.rs:79 — 异步回调直接打开对话框,缺少发起标签仍存在且仍为当前标签的核对,后续 data_of 回调也有相同问题。复现:工作簿 A 有数据,B 为空;暂时占住 worker 锁,在 A 发起 Chart 后立即切到 B,再释放锁。隔离测试实测 `active=1`,显示的 Chart 对话框却持有 `tab=0`;点击 Insert 会修改隐藏的 A。修复方向:在每次异步返回及继续读取前核对发起标签、文档和当前上下文,丢弃过期结果;补充延迟读取期间切换或关闭标签的测试。 + +patches/libreoffice/0019-lok-calc-pivot-tables-and-charts.patch:632 — 读取布局时丢弃 PIVOT_DATA_FIELD,重建 ScDPSaveData 时也丢失其位置与方向,原样应用会改变已有透视表布局。复现:打开行字段为 Region、值字段为 Sales/Sum 与 Year/Max、Data 布局字段置于列上的 FODS;读取字段后原样调用 pivot.make。真实内核输出从 `East\t7\t2026`、`West\t3\t2026` 的横向值列变成 `East\tSum - Sales\t7`、下一行 `Max - Year\t2026` 的纵向布局。修复方向:在现有透视表的读取和修改中保留 Data 布局字段的位置、方向与顺序;补充横向多值布局原样应用的往返测试。 + +/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/primitives/focus.rs:142 — dropped_overlay 按最新已移除浮层覆盖返回焦点,两层浮层一起消失时返回的是已移除外层的焦点。复现:先聚焦一个存活控件,依次打开外层与内层 Dialog,再由属主一次移除两层;隔离的 GPUI 渲染测试中原控件的 is_focused 断言失败,单层的既有测试通过。修复方向:沿移除浮层的 previous 链恢复到存活的原始焦点;补充同时移除嵌套浮层的测试。 + +patches/libreoffice/0019-lok-calc-pivot-tables-and-charts.patch:501 — 该文件共 706 行,超过 agents.md 规则 5 的 500 行上限。复现:`wc -l patches/libreoffice/0019-lok-calc-pivot-tables-and-charts.patch` 输出 706。修复方向:按透视表与图表拆分补丁并同步构建脚本的补丁顺序,使每个文件符合行数上限。 + +/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/primitives/focus.rs:129 — commit 38deadd 新增的注释 `Open overlays holding focus, oldest first, with what they took it from.` 共 13 个单词,超过本轮要求的十词上限。复现:按空白分词得到 13。修复方向:将该注释压缩到十词以内。 diff --git a/tasks/reviews/C09.4-r2.md b/tasks/reviews/C09.4-r2.md new file mode 100644 index 0000000..6ce78f9 --- /dev/null +++ b/tasks/reviews/C09.4-r2.md @@ -0,0 +1,7 @@ +FAIL + +patches/libreoffice/0020-lok-calc-charts.patch:260 — 图表区域校验放过不存在的工作表,首轮错误区域缺陷仍有漏网输入,修改入口(同文件第 350 行)也缺少有效区域校验。复现:只有工作表 S 的工作簿,在“插入 → 图表”中输入 `NoSheet!A1:B3`;真实窗口仍插入 Row 1–4、Column 1–3 的示例图,真实 worker 返回 `Ok(())`,回读 `range` 为空。选中图表后将数据区域改为同一字符串并应用,worker 在 `ChartModel::setArguments` 抛出 `IllegalArgumentException` 后以 SIGABRT 退出,窗口显示引擎停止与 Broken pipe;独立的有效图表修改探针也复现同一崩溃。修复方向:插入和修改都在创建对象、写入撤销历史或改变数据之前完整校验区域及其工作表引用,失败时保持文档原状并返回错误;覆盖不存在工作表的插入与修改、worker 存活和文档状态断言。 + +patches/libreoffice/0019-lok-calc-pivot-tables.patch:137 — 重建已有透视表维度时遗漏原有小计函数,原样应用会静默删除小计行。复现:FODS 透视表的行字段为 Region、Year,值字段为 Sales/Sum,Region 配置求和小计;真实内核刷新后有 `East Sum - Sales\t\t7` 与 `West Sum - Sales\t\t3`。读取 `.uno:PivotTable` 后按返回布局原样执行 `pivot.make`,两条小计行消失,其余明细与总计保留;隔离探针的前后单元格文本相等断言失败。修复方向:修改布局时保留原有维度的小计函数及此次未编辑的设置,补充含小计的已有透视表读写往返测试,并核对实际输出单元格。 + +apps/ely-table/src/analysis/dialogs.rs:188 — 新对话框的可见字段名仅用于 DescriptionList 展示,输入、开关及字段选择控件缺少对应 AX 名称,违反 PRD UX-12。复现:真实 macOS“插入图表”AX 树含一个值为源区域的文本框和三个空白文本框,标题、X 轴标题、Y 轴标题的名称均缺失,图例开关只显示 `on`;“数据透视表”中 Name、Qty、City 三个字段在 AX 树里都只显示“不使用”选择框(第 69 行),辅助技术无法区分要配置的字段。修复方向:将可见字段名关联到对应控件的无障碍名称;在两种界面语言下通过真实 AX 树验证每个字段、聚合函数和图例开关的名称。 diff --git a/tasks/reviews/C09.4-r3.md b/tasks/reviews/C09.4-r3.md new file mode 100644 index 0000000..b00be33 --- /dev/null +++ b/tasks/reviews/C09.4-r3.md @@ -0,0 +1,3 @@ +FAIL + +patches/libreoffice/0020-lok-calc-charts.patch:323 — ScUndoChartLook 仅保存类型、标题、图例及轴标题的 JSON;切换类型创建新 diagram 后,撤销丢失原图表的堆积设置,违反图表修改的一步撤销约定。复现:打开包含两个数值序列的堆积柱形图,在“插入 → 图表”的修改对话框中选 Pie、Apply,再按 Cmd+Z;真实 macOS 窗口显示并排柱形图,原来的堆积柱形图未恢复。真实 worker 的隔离探针同样失败:修改前导出的 FODS 含 `chart:stacked="true"`,修改并撤销后导出的 FODS 已丢失该属性。现有 `charts_insert_and_change_in_one_step` 只核对 Look 和数据区域,漏掉这类原有图表设置。修复方向:撤销保存并恢复完整图表模型或完整的原 diagram 状态,覆盖带堆积设置的既有图表“修改类型 → 一次撤销 → 保存”的真实内核回归,并核对输出属性与实际图形。 diff --git a/tasks/reviews/C09.5-r1.md b/tasks/reviews/C09.5-r1.md new file mode 100644 index 0000000..34b8246 --- /dev/null +++ b/tasks/reviews/C09.5-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-lok/src/worker.rs:398 — 文本导入沿用文件扩展名自动识别,未指定 Calc 的文本过滤器。实测相同内容 `Account,Value\n00123,1.5\n` 保存为 `.csv`、`.tsv`、`.tab` 时得到工作表,保存为 `.txt` 时得到 Writer 的 `Page 1`,SHEETS 返回的 part info 只有 `mode`,随后保存为 FODS 失败;应用的工作表与几何读取也失败。这破坏了 agents.md 声明的 TXT 导入流程。修复方向:文本导入显式选定电子表格文本过滤器,并检查载入文档类型;加入 TXT 经过应用导入、保存 XLSX、重开的回归。 + +apps/ely-table/src/text.rs:70 — 默认列类型仅由前 64 KiB 推断,后续行直接按常规数值导入,违反 TABLE-12 和 §9.3 的编号保留要求。实测 `Account\n` 加 18,000 行 `123\n`,再加 `00123\n12345678901234567890\n`,按默认选择导入后 A18002 为 `123`,A18003 为 `1.23456789012346E+019`,前导零与精度已丢失。首条记录超过 64 KiB 时,截断逻辑还会把整个预览清空,列类型选择器数量为零。修复方向:把受限预览与完整文件的数据安全判定分开,后台检查完整列并保留危险编号;超长记录也应提供可用的列类型选择。现有短样本测试覆盖不到这些失败。 + +apps/ely-table/src/text.rs:119 — 解码时丢弃 `encoding_rs` 的错误标记,错误编码通过替换字符静默改写数据。实测文件字节 `Account\nA\xffB\n` 选择 UTF-8 后,预览显示 `A�B` 且仍可提交;IMPORT 返回成功,实际保存的 FODS 正文也是 `A�B`,整个过程没有解码错误提示。修复方向:传播解码失败,在完整导入前验证所选编码,向用户暴露错误并阻止带静默替换的数据进入工作簿;保留采样末端截断与实际非法字节的区别。 + +apps/ely-table/src/text/dialogs.rs:147 — 预览行的布局宽度未体现各列的总宽度,水平滚动容器无法到达被裁掉的列。macOS 真窗口导入八列 CSV 时仅显示前三列,连续向左右水平滚动仍停在 Column1–Column3,Column4–Column8 的预览与类型控件处于裁剪区外。AX 树里存在这些控件,现有模型测试因此照样通过。修复方向:让预览内容的实际布局宽度包含全部列,并验证滚动后后续列能显示、点击和修改类型。 + +apps/ely-table/src/text/dialogs.rs:80 — 预览单元格直接使用 div 的默认黑色文字,未采用主题前景色。macOS 深色真窗口中,八列 CSV 的 `Column1`、`00123` 等预览值绘制为黑色,背景为深色对话框表面,常规文字对比度远低于 PRD §20.3 的 4.5:1,影响导入规则检查。修复方向:为预览文本应用主题前景色,并用实际深浅色渲染验证文字和背景的对比度。 + +apps/ely-table/src/page/dialogs.rs:93 — 三个缩放模式与固定宽度的数值框挤在 DescriptionList 的同一值列。macOS 默认真窗口中,百分比模式下第三个模式位于裁剪区外;点击第二个模式切到宽×高页数后,两个数值框把模式选择器整体挤出可见区,只剩 `1 × 1`,无法通过该控件切回百分比或选择总页数。TABLE-14 的缩放操作因此受阻,现有测试直接赋值 Scale 绕过了实际控件。修复方向:给模式选择器与数值输入分配足够且稳定的布局空间,并在真实渲染中逐项切换三个模式验证。 diff --git a/tasks/reviews/C09.5-r2.md b/tasks/reviews/C09.5-r2.md new file mode 100644 index 0000000..2fab417 --- /dev/null +++ b/tasks/reviews/C09.5-r2.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-table/src/text.rs:261 — 校验与导入使用了两个文件版本:预览和严格解码读取 Pending.bytes,提交时却重新从 pending.path 复制文件。实测先打开 `Account\n123\n`,等扫描结束,再由外部程序将源文件改成字节 `Account\n00123\nA\xffB\n`,点击打开仍成功;A2 变成 `123`,A3 变成 `A�B`,数据损失与解码错误均进入工作簿。R1 的整文件扫描和严格解码在此路径失效,违反 TABLE-12 与 §9.3。修复方向:导入经过校验的同一份字节快照,或在后台对实际工作副本重新完整校验、推断并要求用户确认变化;加入源文件在对话框期间改变的回归。 + +apps/ely-table/src/text.rs:231 — 扫描回调只核对每个 Pending 自己的 asked 计数,新导入对话框重新从 0 开始,因此两个文件的首次扫描都为 1。延迟旧扫描回调的探针实测:打开 `old.csv`(`Old\n00123\n`),取消后打开 `next.csv`(`Next,Amount\n123,1.5\n`),等新文件预览完成,再释放旧回调;标题与 path 仍为 next.csv,预览却变成 `["Old"]`,列类型从两个 Standard 变成一个 Text,reading 也被旧结果清除。慢扫描期间取消并打开另一文件即可触发,违反 TABLE-12 的按当前选择预览和列类型约定。修复方向:回调同时核对导入请求或字节快照的身份与扫描版本,丢弃已取消对话框的结果;加入跨导入对话框的延迟回调回归。 + +apps/ely-table/src/text/dialogs.rs:144 — PREVIEW_COLUMNS 同时截断了列类型控件,导致第 65 列起无法选择任何导入类型。真窗口打开 65 列 CSV(第 65 列为 `2026-03-04`)时,AX 树只有“第 1 列”至“第 64 列”的类型选择器;第 65 列始终使用默认 Standard,用户无法选择日期顺序、文本或跳过。TABLE-12 要求导入前逐列选择类型,预览采样上限在这里成为永久操作上限。修复方向:保留预览采样限制时,为全部实际列提供类型选择入口,并验证第 65 列及后续列的选择能传入内核。 + +apps/ely-table/src/book.rs:213 — 外部链接列表只在标签首次读取时填充,后续编辑始终命中 links.is_none() 的缓存门禁。实测空白工作簿初次读得 `Some([])` 后,在 B1 输入引用 source.ods 的公式;内核 `.uno:ExternalLinks` 已返回该文件 URL,界面的 extra.links 仍为 `[]`,links_banner 因此持续隐藏。用户在已打开工作簿中新增外部引用后,来源与未刷新状态没有显示,违反 §9.3 及 agents.md 的链接提示约定。修复方向:让链接列表随相关文档修改失效并在后台重读,按发起标签与状态核对回报;加入在空白工作簿中新增外部引用后的提示回归。 diff --git a/tasks/reviews/C09.5-r3.md b/tasks/reviews/C09.5-r3.md new file mode 100644 index 0000000..8eff772 --- /dev/null +++ b/tasks/reviews/C09.5-r3.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-table/src/text.rs:158 — 预览与列类型推断使用的 CSV 解析语义与实际导入的 Calc 语义存在差异,导致确认过的内容在导入时改变。实测打开 UTF-8 文件 `Id,Note\n "00123",x\n`,预览第二行是 `[" \"00123\"", "x"]`,第一列判为 Standard;提交后 A2 实际为 `123`,前导零丢失,临时应用回归 `r3_probe_code_quote_after_space` 断言失败。普通空行同样存在差异:`Id\n\n00123\n` 的预览省略空行,内核保留空行。违反 TABLE-12 的导入前可检查性与编号保留要求。修复方向:使预览、整文件类型推断与内核实际导入采用一致的字段和记录解析规则;无法保证一致的输入显式阻止,并加入引号前空格及空记录的应用回归。 + +apps/ely-table/src/text.rs:180 — 编号识别只移除加号,负号使后续纯数字判定失败,因此负号开头的超长编号和带前导零编号始终默认 Standard。实测导入 `Id\n-12345678901234567890\n`,预览保留原字符串,提交后 A2 变为 `-1.23456789012346E+019`,临时应用回归 `r3_probe_code_signed_long_number` 断言失败。违反 TABLE-12、§9.3 的长编号保留与禁止自动科学计数法转换要求,以及 agents.md 的超过 15 位数字列默认文本约定。修复方向:按数字主体检查符号、前导零与位数,对会损失编号的列推断为 Text;加入负号长编号与负号前导零的实际导入回归。 + +patches/libreoffice/0021-lok-calc-page-setup-protection.patch:201 — 新增 `lcl_PutSpan` 时把原有透视表函数的注释留在插入点,补丁应用后形成两行相邻 `///`,且第一行把该辅助函数描述为透视表。按补丁新侧含上下文重建注释检查可直接复现,违反 agents.md 的注释一行规则;现有 comments.py 只匹配新增行,漏过这处结果源码中的连续注释。修复方向:将透视表说明移回 `lcl_PivotTable` 前,让每个函数保持独立的一行说明,并按补丁应用后的上下文核验注释规则。 diff --git a/tasks/reviews/C10.1-r1.md b/tasks/reviews/C10.1-r1.md new file mode 100644 index 0000000..f2aacfd --- /dev/null +++ b/tasks/reviews/C10.1-r1.md @@ -0,0 +1,15 @@ +FAIL + +apps/ely-show/src/design.rs:238 — 页面尺寸框在异步命令回显前继续由旧的 extra.size 重建,on_commit 又捕获旧的另一边尺寸,连续编辑会覆盖已提交的值。真窗口从 28×15.75 cm 开始,在宽框输入 30 并回车,立即在高框输入 20 并回车,最终 AX 值为 28.00、20.00;日志依次出现提交 30、额外提交 28、提交 20,产生三条 slide.size 命令。修复方向:按文档保留已提交待回显的宽高,以这对值渲染和提交下一次修改,回显按对应请求确认;增加回车、失焦及快速连续编辑的真窗口回归。 + +apps/ely-show/src/deck.rs:168 — 页面尺寸读取只更新 Extra,LokView 的 doc_size 持续使用载入时的尺寸;此次 PageSize 补丁路径没有触发所需的尺寸重读。补充实核测试从空白演示改为 4:3,输入和后台读取均已落地后,内核与 Extra 为 (14401, 10801) twips,LokView 仍为 (15875, 8930),导致画布缩放、页框和可绘制范围错误;真窗口修改尺寸也出现截断和旧页框。修复方向:让尺寸修改及撤销、重做发出有效的版面变化通知,使 LokView 在后台重读完整布局、同步尺寸并重新适配;回归同时断言内核尺寸与实际画布几何。 + +crates/ely-lok/src/view/render.rs:193 — 自动适配强制最低 25% 缩放,页面大于视口四倍时无法整页适配。尺寸框允许 300 cm;对约 (170079, 170079) twips 的页面使用 800×600 px 视口,补充 fit 测试得到 zoom=0.25,右下角为 (2834.65, 2834.65) px,整页适配断言失败。修复方向:自动适配采用由页面和视口计算出的缩放比例,覆盖允许的大尺寸页面和窄视口;手动缩放的限值独立处理。 + +apps/ely-show/src/deck.rs:132 — 普通编辑回报只使当前页缩略图失效,全局尺寸变化的刷新依赖 design_command 的主动调用,撤销、重做绕过这条路径。实核回归新建并复制为两页,改为 4:3,等待两页重绘,再执行 edit.undo;尺寸已恢复且所有异步任务落地后,第 0 页缓存像素与 paint_part 的新鲜输出仍不相等。修复方向:按读取结果中的尺寸、共享母版配色等实际变化使受影响的全部缩略图失效,覆盖撤销与重做;测试逐页比较渲染像素。 + +apps/ely-show/src/slides.rs:143 — 缩略图读取失败后无条件再次调用 paint_next,首次尚未生成图片的页面始终被判为 missing,形成立即重试循环。补充失败路径测试先打开并绘制一页,再停止 worker、清除该页缓存并请求绘制;一次失败在没有新输入的情况下连续调度了 10 次绘制,计数断言失败。实际 worker 退出时会持续发请求、唤醒 UI 并重复报错。修复方向:记录此次绘制失败的页面与状态,在状态变化或明确重试前停止调度,保持错误可见;增加 worker 退出后的有界请求断言。 + +apps/ely-show/src/slides.rs:184 — 缩略图行只有 id、Tab 停靠和 aria_label,缺少 AX role,页面导航及隐藏状态无法进入辅助技术树,违反 UX-12。真窗口复制为两页并隐藏第二页,截图显示两张缩略图和隐藏标记,原生 AX 树仍没有任何幻灯片行;Tab 聚焦时 GPUI 明确记录 has no accessibility node,并提示辅助技术将读出整个窗口。修复方向:为幻灯片和母版行提供可操作角色、名称、当前项及隐藏状态,增加包含具体行名称与状态的原生 AX 回归。 + +apps/ely-show/src/design.rs:238 — 宽、高 NumberInput 的内部文本框缺少可访问名称,旁边 DescriptionList 的视觉文字没有与输入框关联,违反 UX-12。真窗口 AX 树中的两项只有 AXTextField value=28.00、value=15.75,title 和 label 均为空,屏幕阅读器无法识别所编辑的尺寸方向与单位。修复方向:把本地化的宽、高及单位绑定到实际输入控件的可访问名称,必要的组件接口同步补齐,并用原生 AX 值和名称核验两种语言。 diff --git a/tasks/reviews/C10.1-r2.md b/tasks/reviews/C10.1-r2.md new file mode 100644 index 0000000..9568776 --- /dev/null +++ b/tasks/reviews/C10.1-r2.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-show/src/design.rs:53 — 尺寸提交缺少变化判定,回车及随后的失焦会把同一尺寸提交两次,产生额外撤销步骤。真窗口从 28×15.75 cm 开始,只将宽改为 30、回车,再点击画布;日志出现两次 slide.size,第一次 Cmd+Z 后宽仍为 30,第二次才恢复 28。连续改宽高时仍有重复命令,R1 此项的撤销问题持续存在。修复方向:按当前及待回显尺寸消除重复提交;增加真实回车、失焦及连续编辑的命令数和逐步撤销回归。 + +crates/ely-lok/src/view/scroll.rs:52 — 光标滚入视口时无条件扩大 reach,整页适配又用这个范围计算居中和页面遮罩,文字编辑会破坏画布适配。真窗口新建空白演示,双击标题占位符输入 Title,页面立即从中央跳到左上角,页外区域变黑,Esc 结束文字编辑后仍持续显示。实核视图补充回归在 800×600 视口移动页内光标后,页面框由 (24, 88.49, 752, 423.01) 变成 (0, 0, 895.20, 704.20)。修复方向:整页适配的范围、滚动及遮罩依据实际页面尺寸,保持页内光标移动后的整页居中;覆盖真实输入和光标移动后的画布几何。 + +apps/ely-show/src/deck.rs:180 — 全局缩略图失效判定只比较尺寸、当前配色及母版名称列表,母版内容的撤销、重做会留下引用页的旧缩略图。实核补充回归新建并复制为两页,在 Default 母版粘贴 MasterBrand,退出母版并确认两页缓存均匹配内核新画,再执行 edit.undo;所有异步任务落地后仍处于幻灯片视图,第 0 页缓存与内核新画像素不相等。临时将此次编辑影响的全部缩略图失效后,同一回归通过。修复方向:把母版内容变动及其撤销、重做纳入依赖页的失效判定;逐页比较引用页缓存与新画像素。 + +apps/ely-show/src/new.rs:86 — 新建对话框中的 TemplatePicker 没有提供模板行的可访问名称、角色和选中状态,模板行也被 Tab 顺序跳过,违反 UX-03、UX-12。真窗口搜索 Blue,画面显示 Blue Curve、Blueprint Plans、Midnightblue 三项及当前模板,原生 AX 树中的对话框只有搜索框、Use template 和 From a File 按钮;从搜索框按 Tab 经过两个分类按钮后直接到 Use template,方向键保持原选择。键盘及屏幕阅读器无法浏览选择这些结果。修复方向:在组件库补齐模板行的键盘选择和 AX 语义,并接入相应版本;回归具体模板名称、当前选择与键盘选取结果。 diff --git a/tasks/reviews/C10.1-r3.md b/tasks/reviews/C10.1-r3.md new file mode 100644 index 0000000..958a520 --- /dev/null +++ b/tasks/reviews/C10.1-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-show/src/deck.rs:195 — 全局缩略图失效仅比较重做步数,撤销后紧接新编辑会把重做栈清回原长度,遗漏母版内容变更,违反 SHOW-02。实核补充回归新建并复制为两页,在 Default 母版粘贴 MasterBrand,退出母版并逐页确认缓存匹配内核;随后在同一次界面更新中经正常输入队列发送 edit.undo、slide.hide。全部输入、读取与绘图落地,再推进调度器一秒后,历史仍为 (2, 0),第 0 页缓存与内核新画像素不同,保留已撤销的母版内容。临时让此次读取使全部缩略图失效,同一回归通过。修复方向:按实际撤销/重做动作或可靠的历史变更标识失效引用页,覆盖撤销后立即新编辑、重做栈恢复原长度的连续操作回归。 diff --git a/tasks/reviews/C10.2-r1.md b/tasks/reviews/C10.2-r1.md new file mode 100644 index 0000000..ad0840e --- /dev/null +++ b/tasks/reviews/C10.2-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-show/src/object.rs:189(关联 crates/ely-office/src/panel.rs:156):调色板的清除按钮沿用颜色命令,将 `0` 当成黑色发送。真实窗口中插入矩形,打开填充调色板并点击 `No Fill`,矩形变成实心黑色,内核返回 `Paint::Color(0)`;`No Outline` 同样发送 `object.line_color("0")`,得到黑色轮廓。隔离探针 `review_clear_palette_routes_to_a_real_clear` 实测失败。修复方向:清除动作分别发送 `object.fill_none`、`object.line_none`,为清除按钮保留独立命令,并以实际面板回调验证清除后的样式;现有测试直接调用 `object.fill_none`,绕过了出错路径。 + +apps/ely-show/src/object.rs:63(关联 apps/ely-show/src/design.rs:87):数值提交记录长期保留,且选区身份仅使用外框,导致有效编辑被当作重复提交跳过。真实窗口中将矩形透明度设为 50,撤销回 0,再输入 50 并回车,控件仍显示 0,日志输出 `transparency already holds 50`。隔离探针 `review_commit_cache_tracks_the_selected_object` 还复现了两个重叠矩形外框同为 `(9999, 5374, 7999, 4999)` 时,先给一个设 50,再给另一个设 50,第二个仍为 0。修复方向:重复提交判定绑定文档、页面、所选对象及状态版本,已回显、撤销或选择变化时结束旧提交记录的有效期,同时保留回车加失焦只提交一次的回归。 + +apps/ely-show/src/object.rs:90:直线、箭头与连接线的选区跳过整个 `look` 属性组,描边颜色和线宽的编辑入口随之消失,违反 SHOW-04。真实窗口通过 `Insert > Line` 插入并选中直线,右栏和 AX 树仅有位置大小及排列控件;插入菜单与视图菜单也没有描边编辑入口。箭头的内核种类为 `line`,连接线为 `connector`,同样命中此分支。修复方向:按对象能力保留线类对象的描边属性,并验证这三种对象从实际控件修改颜色及线宽后的内核值。 + +apps/ely-show/src/list.rs:64:空的列表选择没有发送取消选中命令,随后读取又恢复原来的内核选择,违反 SHOW-06 的对象列表操作。真实窗口选中对象列表的一行,再按 Space,组件日志发出 `[]`,对象和该行仍保持选中;隔离探针 `review_object_list_can_deselect_its_last_row` 调用 `select_objects(&[])` 后,内核选择仍存在。修复方向:空选择显式清除当前文档的对象标记,确保列表、画布和内核选区一致,并覆盖最后一项取消选择的键盘路径。 + +apps/ely-show/src/object.rs:201:混合透明度和线宽经 `unwrap_or(0)` 显示为 0,且提交判定将这个伪造的当前值当作真实值,阻止用户统一设置为 0。真实窗口将一个矩形透明度设为 25,再在画布 Cmd+A 多选所有对象,透明度框显示 0;输入 0、回车并失焦后,重新单选该矩形仍显示 25,日志输出 `transparency already holds 0`。线宽在第 207 行采用相同处理。修复方向:保留混合状态,以混合占位显示,只有内核报告明确数值时才执行相等值去重,并验证混合选区统一归零的实际控件路径。 + +scripts/build-libreoffice.sh:142:0027 修改了 0023 的重叠补丁上下文,逐个补丁的反向检测使已完成构建的源码在再次构建时被错误识别为尚待应用 0023;再次应用 0023 返回 1,脚本在 `set -e` 下退出。隔离复制相关源码后,带 0027 时 `git apply --check -R 0023-lok-impress-slide-design.patch` 与正向检查均失败;移除 0027 后,0023 的反向检查通过。执行第 142 行的完整命令同样返回 1。修复方向:将重叠的 Impress 补丁作为有序补丁栈从顶部检测,再应用缺失部分,并覆盖完整补丁集已应用后的重复构建检查。 diff --git a/tasks/reviews/C10.2-r2.md b/tasks/reviews/C10.2-r2.md new file mode 100644 index 0000000..d3d52ae --- /dev/null +++ b/tasks/reviews/C10.2-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-show/src/object.rs:125:上一轮的混合数值问题仍有残留:`None` 经 `unwrap_or(0)` 初始化为真实数字 0,第 139 行只附加“混合”后缀。具体场景:一个矩形透明度为 25、另一个椭圆为 0,多选后聚焦透明度框,其实际输入文本为 `0`;直接回车会发送 `object.transparency("0")`,把两个对象统一改为 0。隔离 GPUI 控件探针 `review_real_mixed_field_keeps_its_unknown_value` 使用实际 NumberInput 回调与真实 LOK,读到 `Some("0")`,随后保留混合值的断言失败。现有 `a_mixed_value_can_be_set_to_zero` 直接调用 `commit`,绕过了控件初始化。修复方向:数值控件保留空/混合状态,明确有效编辑后再提交,覆盖实际控件的混合显示、直接回车与主动输入 0。 + +apps/ely-show/src/object.rs:378:自动适配将 `mixed` 当作两个关闭标志,混合选区选择“不适配”时,第 378、381 行条件均为假,整次操作发送零条命令,违反 SHOW-03。具体场景:将标题占位符设为“溢出时缩小文字”、内容占位符设为“调整文本框以适应文字”,多选两者,再从自动适配菜单选择“不适配”;控件日志确认 `select Name("show-fit"): off`,重新读取内核仍为 `mixed`。隔离探针 `review_real_mixed_autofit_can_be_turned_off` 经实际 Select 回调复现,断言得到 `mixed`、期望 `off`。修复方向:混合选区按目标规则明确统一每个对象的缩小和自动增长状态,补上从混合状态切换到各档的控件回归。 + +apps/ely-show/src/object.rs:502:文件共 502 行,超过 agents.md 第 5 条规则规定的 500 行上限。具体场景:本轮源码执行 `wc -l apps/ely-show/src/object.rs` 返回 502,末尾闭合括号位于第 502 行。修复方向:按现有属性组拆分文件,将每个文件控制在 500 行以内。 diff --git a/tasks/reviews/C10.2-r3.md b/tasks/reviews/C10.2-r3.md new file mode 100644 index 0000000..d1336e8 --- /dev/null +++ b/tasks/reviews/C10.2-r3.md @@ -0,0 +1,5 @@ +FAIL + +patches/libreoffice/0027-lok-impress-objects-guides.patch:390:新增自动适配命令只在 `shrink` 时关闭横向自动增长;选择 `off` 会保留 `SDRATTR_TEXT_AUTOGROWWIDTH`,读回与面板却显示“不适配”,违反 SHOW-03。具体场景:打开带 `draw:auto-grow-width="true"`、`draw:auto-grow-height="false"` 的 FODP 文本框,选中后发送 `text.fit("off")`,键入三遍英文字母表并退出编辑;真实 LOK 探针 `review_off_clears_horizontal_autogrow` 两次读到外框宽度由 10000 增至 35289(1/100 mm),保存文件仍含横向自动增长属性。修复方向:按目标规则完整设置横向与纵向增长属性,“不适配”关闭两者,并覆盖导入横向增长文本框后继续键入的回归。 + +patches/libreoffice/0027-lok-impress-objects-guides.patch:386:新命令的撤销改变原有段落格式,恢复后文字排版与保存结果均发生变化。具体场景:FODP 中放置 2 cm × 0.5 cm 的固定文本框,图形样式带 `fo:font-size="20pt"`,`text:p` 使用原有默认段落格式,正文为长英文句子;选择“溢出时缩小文字”后撤销一次。真实 LOK 探针 `review_autofit_repaints_and_undoes` 两次确认规则恢复为 `off`、外框保持原值,但 `paint_part` 的文字字号和换行与操作前不同;保存 XML 给原本没有段落样式属性的 `text:p` 新增 `text:style-name="P7"`(20pt),重开文件仍保持改后的排版。同一探针重复发送 `off` 的对照绘制与原图一致。修复方向:自动适配的撤销完整保留并恢复原段落与文字格式,避免把对象文字属性写入原段落;回归同时核对绘制与保存后重开结果。 diff --git a/tasks/reviews/C10.3-r1.md b/tasks/reviews/C10.3-r1.md new file mode 100644 index 0000000..9c3f4e8 --- /dev/null +++ b/tasks/reviews/C10.3-r1.md @@ -0,0 +1,21 @@ +FAIL + +apps/ely-show/src/table.rs:32 — 插入表格使用尚未提交的旧行列数。真实窗口中把“列”从 3 键入为 7,AX 已显示 7,直接点击“插入”,随后保存 PPTX;两次复现的 a:tblGrid 都只有 3 个 a:gridCol,行数仍为 3。NumberInput 只在 on_commit 更新 Dialog,而按钮读取并移除 Dialog 时字段仍持有草稿。修复方向:在键入时同步对话框草稿,或确认前提交所有数字字段,再发送一次 table.insert;回归测试须覆盖键入数字后直接点击按钮并核对保存文件。 + +apps/ely-show/src/notes.rs:62 — 备注提交用缓存的 slides.current 核对目标,notes.set 却只操作命令执行时的当前幻灯片。复现:在第 1 张输入备注,延迟 worker,先排入 show_part(1),随后触发备注失焦,再释放 worker。review_notes_blur_after_queued_slide_change_keeps_origin 实测第 1 张备注为空,第 2 张变成“Notes only for slide zero”。修复方向:把备注所属幻灯片身份传到内核操作,并保证提交与切页的有序关系;失焦、右键切页及延迟 worker 的回归应逐页核对备注。 + +patches/libreoffice/0027-lok-impress-objects-guides.patch:316 — SlideObjects 过滤批注后,数组下标与 SelectObject 使用的原始对象下标发生错位。复现:依次插入矩形、批注、椭圆,对象列表为原文本、矩形、椭圆;选择列表下标 2 后,椭圆的 selected 为 false,内核选中的是位于 (0,0) 的批注对象。真实 LOK 探针 review_comment_objects_keep_selection_indices 失败。修复方向:读出稳定的内核对象索引并按该索引选择,或让读取与选择共同使用过滤后的索引;覆盖批注前后插入对象、层次调整和多选。 + +apps/ely-show/src/chart.rs:142 — 图表读取回来后直接覆盖当前窗口的对话框,未核对发起标签仍然可见。复现:标签 A 选中图表,延迟读取,发起编辑后切到标签 B,再释放读取。review_delayed_chart_dialog_stays_with_origin_tab 实测当前标签为 1,对话框 editing.tab 为 0;提交路径随后仍向隐藏的 A 发 show.chart。修复方向:采用带发起标签校验的读取,并在窗口回调落地时核对标签、幻灯片及目标图表身份,丢弃已过期的请求;补延迟读取期间切标签和关闭标签的回归。 + +patches/libreoffice/0028-svx-chart-look-and-data-helpers.patch:326 — 图表数据读取以类别描述的数量决定数值行数,无法读取 PPTX 散点图的数值 X/Y 数据。复现:插入含两行、两个系列的散点图,保存 PPTX,重新打开并选中;.uno:Chart 返回 categories=[],四个 series 的 values 全为空。仅改标题再提交即报“show.chart: the undo history did not move”,违反 SHOW-07 保存后仍可编辑。review_pptx_scatter_keeps_editable_data 失败;同一内容的 ODP 读取正常。修复方向:按实际数据序列读取并保留散点图的 X/Y 角色与数值,保证对话框和写回结构匹配;回归须保存、重开、核对原数据并修改标题和数值。 + +patches/libreoffice/0028-svx-chart-look-and-data-helpers.patch:345 — std::isfinite 把 XChartDataArray 的缺失值哨兵 DBL_MIN 当成正常数字。复现:载入数值表含 office:value="NaN" 的柱形图,内核的数据读取给该点 2.2250738585072e-308,界面会显示这个数;将读取结果原样写回并仅改标题,show.chart 被拒绝。ChartDataWrapper::isNotANumber 明确把 DBL_MIN 判为缺失值,当前读取绕过了该判定。修复方向:使用图表接口的缺失值判定,完整支持缺失数据点的读取、对话框保留和写回;补含空点的保存重开及仅改外观回归。 + +patches/libreoffice/0030-lok-impress-charts-notes-comments.patch:311 — 备注格式检查只检查字符属性,替换文本时丢弃段落格式。复现:载入仅有一段、字符格式一致且段落 fo:text-align="end" 的备注,读取给 editable=true、rich=false;执行 notes.set 后保存,原有右对齐属性消失。review_notes_paragraph_format_is_preserved_or_refused 和保存前后 FODP 比较均复现。修复方向:在替换中保留段落属性;当前纯文本编辑无法保留的格式应纳入 rich 判定并明确拒绝,读接口与命令校验保持一致。 + +reference/Ely-GPUI-Components/src/collab/sidebar.rs:157 — 批注条目只有鼠标 on_click,缺少 Tab 停靠、键盘选择和无障碍语义。真实 ElyShow 窗口添加一条批注后,屏幕显示正文、作者和时间,AX 树只有“添加批注”“编辑”“删除”按钮;从备注按 Tab 到“添加批注”,再按 Tab 直接跳到标签栏控件,批注保持未选择,“编辑”“删除”保持禁用。键盘与辅助技术无法完成 SHOW-08 的定位、编辑和删除,也违反 PRD §20.3。修复方向:在 CommentSidebar 中提供有名称和选中状态的可聚焦条目,支持键盘选择并复用 on_select;用真实 Tab 顺序和 AX 树验证。 + +patches/libreoffice/0028-svx-chart-look-and-data-helpers.patch:147 — 带默认值的 get 把错误的 legend 数据静默转换成 true,IsChartLook 随之通过。复现:通过 show.chart 发送 {"type":"column","legend":"banana","data":{"categories":["A"],"series":[{"name":"S","values":[1]}]}},命令返回 Ok,文档新增或修改图表,读回 legend=true。review_bad_look_json_refuses_without_killing_worker 失败,违背项目禁止兜底和本任务“JSON 不合即拒绝”的约定。修复方向:严格校验已提供字段的 JSON 类型,错误值在修改前拒绝,保留文档与撤销历史;覆盖错误类型和有效可选字段。 + +apps/ely-show/src/remarks.rs:84 — 注释“Sends the dialog's text as a new comment or an edit.”共 11 词,超过 agents.md 的十词上限。python3 tests/scripts/comments.py 在该行返回 FAIL。修复方向:将注释压缩至十词以内并重新运行检查。 diff --git a/tasks/reviews/C10.3-r2.md b/tasks/reviews/C10.3-r2.md new file mode 100644 index 0000000..a3d1ddf --- /dev/null +++ b/tasks/reviews/C10.3-r2.md @@ -0,0 +1,7 @@ +FAIL + +patches/libreoffice/0028-svx-chart-look-and-data-helpers.patch:151 — 外观 JSON 的类型校验仍会静默转换错误类型。真实 LOK 探针 review_typed_json_is_refused_before_mutation 发送 {"type":"column","legend":"true"} 返回 Ok,撤销步骤从 0 增至 1,读回 legend=true;title=123 被写成字符串 "123",title={"bad":"object"} 被写成空标题,values=["1"] 被写成数值 1。上一轮的 "banana" 用例已拒绝,但 property_tree 丢失 JSON 类型,当前字符串比较仍无法兑现“类型不合即拒绝”。修复方向:使用保留 JSON 类型的解析与字段校验,在任何插入或修改前拒绝错误类型;回归同时核对文档、撤销与重做历史保持原样。 + +apps/ely-show/src/chart.rs:145 — 异步图表读取以外框坐标识别目标,两个同位置图表会被判为同一个目标。真实 Deck/LOK 探针 review_chart_read_drops_when_same_frame_target_changes 插入同外框的 First、Second 两个图表,选中 First 发起读取,再于读取返回前通过对象选择切到 Second;对话框仍打开,标题为 Second,探针失败。换标签用例已通过,当前守卫仍违反任务约定的“选中的图表已变即丢弃”,不同幻灯片上的同位置图表也共享该识别值。修复方向:记录发起时的幻灯片及图表身份或选择代次,在读取落地和实际创建对话框时核对;补同位置对象切换和跨页的延迟读取回归。 + +patches/libreoffice/0028-svx-chart-look-and-data-helpers.patch:350 — 散点图保存重开后失去数据编辑能力,上一轮修复仅恢复外观修改。独立运行 a_scatter_from_pptx_keeps_its_data_and_takes_a_new_look:用本应用的 show.chart 新建类别 ["1","2"]、North 数值 [1,2] 的 scatter,保存 PPTX、重开并选中后,read.data 为 None;该现有测试直接断言 None。Editing::new 随之得到 table=None,chart_dialog 仅显示外观修改说明,类别、系列和数值编辑控件全部消失。SHOW-07 要求数据与对象在保存后仍可编辑,本应用支持创建的常用图表触发此退化。修复方向:读取并保留散点图的 X/Y 数据角色,提供可往返的编辑与写回结构;回归须在 PPTX 保存重开后修改数据、再次保存重开并核对原值与修改值。 diff --git a/tasks/reviews/C10.3-r3.md b/tasks/reviews/C10.3-r3.md new file mode 100644 index 0000000..e9ef98b --- /dev/null +++ b/tasks/reviews/C10.3-r3.md @@ -0,0 +1,7 @@ +FAIL + +patches/libreoffice/0028-svx-chart-look-helpers.patch:233 — 更新现有散点图时无条件设置 Lines=false,修改标题也会删除原有的连接线。真实 LOK 探针 review_scatter_title_keeps_lines 导入带直线连接的散点图,保存为 PPTX 并重开,确认系列具有连接线;仅修改标题、请求省略 data 后,保存的系列样式变成 draw:stroke="none",探针失败。同时写回读出的原数据时也复现。SHOW-07 的常用图表编辑会破坏已有外观。修复方向:同类型编辑保留连接线设置,仅在创建或切换到散点图时设置初始外观;补带连接线散点图的标题修改及 PPTX 往返回归。 + +apps/ely-show/src/chart.rs:104 — 数据校验只检查系列的 Y 值,散点图的 X 类别直接作为字符串通过;chart_asked 随即移除对话框,内核拒绝后草稿丢失。真实窗口中新建图表、选择 Scatter、保留默认 Category 1–3 并点击 Insert,图表未插入,对话框关闭,只显示 "show.chart: the undo history did not move",缺少错误单元格定位;再次打开得到全新草稿。应用探针 review_scatter_bad_x_keeps_its_draft_and_points_to_cell 同样失败。任务要求非数字格拒绝并指明位置。修复方向:按散点图类型校验每个 X 值的数字及有限性,定位错误类别并保留全部对话框字段;覆盖新建和修改散点图的无效 X 输入。 + +apps/ely-show/src/remarks.rs:171 — 批注的 active 状态仅按批注 ID 保留,切换幻灯片后仍把上一页的批注传为选中;bcf0378 的 CommentSidebar 在 sidebar.rs:164 移除已选中行的点击和键盘激活回调。真实窗口在第一页插入批注、通过 Tab 和 Enter 选中,再新建并切到第二页;重复点击该批注或以 Tab 和 Enter 激活,第二页仍为当前页,批注无法再次定位到第一页,AX 中该行也失去 Pick 动作。违反 SHOW-08 的批注定位要求。修复方向:让选中批注与当前标签、当前幻灯片同步,或保留选中行的定位回调;补选中批注后离开所在页再定位的鼠标及键盘回归。 diff --git a/tasks/reviews/C10.4-r1.md b/tasks/reviews/C10.4-r1.md new file mode 100644 index 0000000..b4f683a --- /dev/null +++ b/tasks/reviews/C10.4-r1.md @@ -0,0 +1,15 @@ +FAIL + +crates/ely-platform/src/media.rs:50 — Player 的 state/play/pause/playing/position/seek/frame 全部只在 macOS 下定义,apps/ely-show/src/media.rs 无条件调用这些方法,Windows/Linux 的 ElyShow 编译失败,无法到达所宣称的“此平台未构建”提示。隔离小 crate 直接引用原始 media.rs,分别以 x86_64-unknown-linux-gnu 和 x86_64-pc-windows-msvc 执行 cargo check,均得到七个 E0599。修复方向:保证各目标上的播放器接口及帧类型可编译,并让已声明的未构建路径明确返回错误;加入对应目标的编译验证。 + +apps/ely-show/src/media.rs:169 — 播放实例仅以标签号和对象列表序号识别对象,同一标签中其他幻灯片的同序号媒体会复用原播放器;media_layer:205 同样沿用原画面。真实 GPUI 主线程探针先播放第一页 index=2 的 clip.mp4,再复制页面、删除副本视频、在第二页相同序号插入 2 秒 WAV,点击播放后仍读到 Ready { seconds: 1.0, video: Some((160, 90)) },实际播放源仍是第一页视频。修复方向:播放实例绑定幻灯片及媒体文件身份,在切页、对象删除或替换时停止并清理实例,播放前重新核对对象身份;覆盖同序号跨页和删除后序号复用场景。 + +apps/ely-show/src/preview.rs:348 — 切换合成始终把新页画在旧页上方,uncover-left 的新页保持整页不透明,因此旧页向左移动的全过程被盖住,预览直接显示新页。使用原 transition() 在真实 Metal 窗口绘制红色旧页、蓝色新页的半程帧,左右采样均为 [0,0,255,255];仅在隔离副本交换两层的叠放顺序,采样变为左红右蓝。现有 halfway_frames_mix_both_slides 只查位移参数,漏掉合成错误。修复方向:按切换类型决定旧、新页叠放顺序,为向左揭开增加实际合成像素断言。 + +apps/ely-show/src/preview.rs:129 — 异步预览结果仅核对标签号,Preview 也仅保存标签号(:150),preview_layer 仅按标签过滤(:179)。在第一页启动预览后切到第二页,第一页图层继续盖在第二页画布上;读取过程中切页或编辑,也会接受已过期的图层及效果配置。隔离 GPUI 探针启动第一页预览,再执行 slide.insert 并等待 current=1,旧 Preview 仍存在。修复方向:预览请求和播放状态绑定发起页面及文档修订,完成读取和每次绘制前核对,页面或修订变化时撤销预览;补充读取途中切页和编辑的测试。 + +apps/ely-show/src/preview.rs:161 — stop_preview 仅清除已生成的 Preview,异步图层读取没有取消状态;回调随后仍会调用 play。具体场景:预览 A 播放期间再次请求预览 B,点击画布停止 A,B 的读取完成后自动重新遮住画布。隔离 GPUI 探针在同一次更新中依次调用 preview_transition 和 stop_preview,调度器完成后 preview 为 Some,停止断言失败。修复方向:停止操作使当前预览请求失效,回调核对请求代次,覆盖停止待完成请求及连续预览场景。 + +apps/ely-show/src/media.rs:173 — 已播完的媒体再次点击播放时直接调用 AVPlayer.play,位置仍停在末尾,用户无法再次播放同一对象。以仓库 1 秒 clip.mp4 在主线程运行系统播放器,等到位置 1.000 且停止后再次 play,运行主循环 400 毫秒后仍为 position=1.000、playing=false;应用的复用路径执行同一操作。修复方向:区分暂停与播放结束,结束后先回到起点再播放,并验证播完后再次播放以及普通暂停继续。 + +apps/ely-show/src/motion.rs:110 — 切换时长的 on_commit 每次都发送完整命令,缺少相同值及待回显值的去重;动画时长、延迟(:268、:280)同样如此。NumberInput 的 Enter 与随后失焦各触发一次提交,补丁 0034:347 每次无条件新增撤销动作,单次数值编辑产生两步撤销,第一次撤销只撤回相同值。真实 LOK 探针连续提交相同 Transition JSON,撤销数实际增加 2;现有 C10.4 测试只验证单次调用。修复方向:在提交路径处理重复值,并让内核对实际无变化的提交保持历史不变;测试 Enter 后失焦及回显前重复提交。 diff --git a/tasks/reviews/C10.4-r2.md b/tasks/reviews/C10.4-r2.md new file mode 100644 index 0000000..8863168 --- /dev/null +++ b/tasks/reviews/C10.4-r2.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-show/src/preview.rs:82 — 预览修订只取撤销列表,文字编辑期间该列表可以保持不变,首轮“编辑后丢弃过期预览”仍有遗漏。隔离 GPUI 探针在标题中输入 A、启动预览,再经 LokView::insert 输入 B 并完成重新读取;内核页面像素实际改变 1,296 个字节,前后 history 均为 ([], []),drop_stale_preview 后旧 Preview 仍存在,旧图层继续遮住新内容。修复方向:预览身份纳入实际输入与编辑代次,检查待落地编辑,并在读取完成及绘制前验证;覆盖文字编辑尚未形成撤销条目的场景。 + +apps/ely-show/src/motion.rs:153 — 各切换字段从渲染时的 base 拼出完整 Transition,去重修复仍会让后一字段覆盖前一字段的待回显改动。隔离 GPUI/真实 LOK 探针先设置自动切换、时长 2 秒、等待 4 秒,在同一次更新中按生产 on_commit 路径提交时长 3.5 秒与等待 7 秒;内核最终读回 duration=2.0、time=7.0,时长修改丢失。修复方向:完整切换参数以该页面最新的待回显状态为底合并各字段,回读时核对并清理;回归连续修改两个字段的路径。 + +apps/ely-show/src/media.rs:246 — 播放身份清理依赖 media_layer 被绘制;关闭最后一个标签后,deck.rs:370 的空文档分支提前返回,播放实例继续保留并发声。真实 GPUI 主线程窗口探针播放 10 秒 WAV 后关闭唯一标签,300 毫秒后仍读到 Ready { seconds: 10.0, video: None }、position=0.521、playing=true,playing 清理断言失败。修复方向:在标签关闭及活动文档变化时执行播放实例的暂停与清理,空文档渲染路径也完成身份核对;覆盖关闭最后一个标签。 + +tests/scripts/media-targets.sh:29 — 编译探针只调用 play 与 frame,遗漏 ElyShow 使用的 state、pause、playing、position、seek,首轮接口缺失的回归门禁仍可误报通过。在隔离副本删除 Player::pause 后,原脚本在 Linux、Windows 两个目标均退出 0 并宣称播放器接口可编译;调用完整接口的 Linux 探针立即报 E0599,ElyShow 的暂停及清理路径因此无法编译。修复方向:两个目标的探针调用应用所用的全部播放器方法,并验证缺失任一方法时门禁失败。 + +apps/ely-show/src/preview/transition.rs:122 — 首轮揭开效果新增的测试只断言 old_on_top 参数,实际合成顺序缺少像素断言。当前真实 Metal 窗口半程帧能同时显示旧、新两页;隔离副本把 preview.rs 的合成分支改为始终新页在上后,左右采样均变成新页颜色 [255,0,1,255],三项现有切换测试仍全部通过,首轮画面缺陷可重新进入。修复方向:通过实际 transition() 合成半程帧,断言两侧分别来自旧页与新页,并让叠放顺序变异触发测试失败。 diff --git a/tasks/reviews/C10.4-r3.md b/tasks/reviews/C10.4-r3.md new file mode 100644 index 0000000..5291371 --- /dev/null +++ b/tasks/reviews/C10.4-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-show/src/motion.rs:67 — 待回显切换仅在数值完全相符或换页时清除,快速撤销会留下已经失效的 transition_asked。隔离 GPUI/真实 LOK 探针先设自动切换、时长 2 秒、等待 4 秒,在同一次更新中提交时长 3.5 秒并撤销;输入与回读全部完成后,内核为 duration=2.0,shown_transition 仍为 3.5,缓存仍存在。随后经生产 send_transition 修改等待为 7 秒,内核变成 duration=3.5、time=7.0,已撤销的时长被重新写回。修复方向:待回显状态绑定命令落地及文档修订,在撤销、重做、失败或已完成回读与请求不同的情况下失效;覆盖提交后立即撤销及后续修改另一字段的路径。 + +apps/ely-show/src/motion.rs:339 — 动画数值去重共用 effect.duration/effect.delay 字段键,缓存身份仅含标签与回读次数,切换选中的效果时会把另一个效果的提交过滤。隔离 GPUI/真实 LOK 探针给同一对象加入两个时长均为 2 秒的淡入效果,通过生产 dedupe 和 send_effect,在回读前依次选中效果 0、1 并各提交 3.5 秒;最终内核时长为 [3.5, 2.0],第二个修改丢失,断言失败。修复方向:去重身份包含所属页面及具体效果,切换效果时正确隔离待提交值;覆盖回读前给两个效果提交相同数值,并保留同一效果 Enter 加失焦只产生一步撤销的验证。 diff --git a/tasks/reviews/C10.5-r1.md b/tasks/reviews/C10.5-r1.md new file mode 100644 index 0000000..6c838ef --- /dev/null +++ b/tasks/reviews/C10.5-r1.md @@ -0,0 +1,19 @@ +FAIL + +apps/ely-show/src/slideshow/start.rs:132 — 开启演讲者视图时,观众窗口的 FocusScope 没有取得键盘焦点;两窗口的 WindowOptions 又都设为 focus=true,后开的观众窗口会取得平台焦点。复现:按该分支创建 Audience,向窗口发送 right,位置仍为 Slide { part: 0, clicks: 0 };给同一窗口的 Audience.focus 聚焦后再次发送 right,正常进入 End。观众窗口上的翻页、黑屏、激光笔和 Esc 因此失效,违反 SHOW-11/12。修复方向:给两个窗口各自的根作用域建立焦点,再独立决定启动时激活哪一个窗口;加入双窗口的实际按键分发回归测试。 + +apps/ely-show/src/slideshow/start.rs:56 — “从当前幻灯片开始”捕获的是异步读取缓存 extra.slides.current,缩略图刚发出的 show_part 尚未更新这个缓存。复现:编辑器位于第三张,点击第二张后立即 Shift+F5;探针在同一次界面更新中调用对应操作,放映位置实际为 part: 2,预期为 part: 1。违反 SHOW-11 的当前页起播要求。修复方向:保留发起时所选页的身份,等待该标签的有序输入落地,再读取并核对起播页;测试覆盖选页后立即启动。 + +apps/ely-show/src/slideshow/media.rs:67 — 放映只处理 Player::open 的同步错误,media_tick 完全跳过 State::Loading/Failed;播放器异步解码失败后原因被丢弃。复现:文档链接到一个存在但内容损坏的 MP4,单击媒体对象;主线程原生探针得到 AVFoundation State::Failed("Cannot Open"),连续调用 media_tick 后放映仍保留失败播放器,应用没有错误提示。违反 SHOW-10、PRD 10.3 与出错暴露规则。修复方向:持续观察载入及播放状态,把异步失败原因交给应用提示,停止失败播放并保留文档对象;加入损坏文件与载入后失败的放映回归测试。 + +apps/ely-show/src/slideshow.rs:155 — 放映图层始终从编辑标签的同一个 Doc 按需读取,启动时读取的 SlideShow 元数据无法冻结页面内容,违反 agents.md C10.5 的“放映中的编辑不进入本次放映”。复现:两张幻灯片,第一页红色矩形;开始放映、跳到第二页使第一页缓存释放,在编辑文档中把第一页矩形改为蓝色,再跳回第一页。真实 LOK 探针比较 RenderImage 的像素字节,重新读取的观众画面已变色;动画元数据仍来自启动时,随后还会与新图层混用。修复方向:在启动时建立固定修订的放映文档,元数据、图层及嵌入媒体均从该文档读取,编辑文档继续独立更新。 + +apps/ely-show/src/slideshow/start.rs:50 — 启动守卫只检查已经打开的 show,后台读取期间没有启动中的状态,回调也只核对活动标签。复现:同一标签连续触发两次 F5 对应的 start_show,两次读取均打开窗口;探针实际得到三个窗口(编辑器加两个观众窗口),预期两个。后一次覆盖 deck.show,前一次窗口退出还会清空后一次的 show 状态。修复方向:在派发读取前占住启动状态,并按启动请求身份核对回调、窗口打开与退出;加入连续启动和旧窗口结束的生命周期测试。 + +apps/ely-show/src/slideshow.rs:199 — 每个单击组直接记录当前时间,前一组的结束时刻和入页切换的结束时刻均未参与约束;steps::runs 随后把各组独立叠加。复现:两个持续十秒的单击淡入,快速按两次下一步;探针得到第二组 begin=0.000393458、第一组 end=10.000387125,两组同时运行。切换期间提前单击还会让短动画在切换遮挡下播放完。违反 SHOW-09 的动画顺序及 C10.5 的切换结束后起播约定。修复方向:以此前全部已触发组的结束时刻(含入页切换)约束下一组起点,并测试快速连续单击和切换期间单击。 + +apps/ely-show/src/preview/compose.rs:45 — 旧页合成强制显示所有动画图层,放映离页时已经执行的退出效果被撤销。复现:第一页红色对象已完成消失动画,第二页使用向左覆盖;Metal 像素探针先确认该对象的最终 visible=false,再渲染切换半程,旧页区域实际读出 Rgba([255, 0, 0, 255]),退出对象重新出现。违反 SHOW-09 的动画播放行为。修复方向:根据旧页离开时的动画时刻和已触发组生成旧页外观,并把该外观用于放映切换;保留预览自己的旧页策略,加入已完成退出效果的像素回归测试。 + +apps/ely-show/src/slideshow.rs:161 — 图层读取回调无条件写入缓存,页面切换时的 retain 只能清除已经返回的结果,过期的在途结果随后重新进入缓存。复现:第一、第二页图层就绪后,在一次界面更新中数字跳到第三页再跳回第一页;第三页后台结果回来后 stack(2) 仍为 Some,当前及下一页分别为第一、第二页。快速遍历大文稿会累计多个全尺寸图层,违反 C10.5 的“只缓存当前页与下一页”。修复方向:结果落地时重新核对当前页和下一页,丢弃过期结果并限制在途读取;加入连续跳页后缓存数量与内容的回归测试。 + +apps/ely-show/src/slideshow.rs:196 — 跳到当前页走“同页单击组”分支,transition=false 没有清除 leaving,也没有按跳转重新建立入页时间。复现:从第一页进入带十秒淡化的第二页,切换期间输入 2、Enter;探针发现 leaving 仍为 Some,stage 继续显示从第一页淡入的切换。违反 C10.5 的“跳转与后退无切换”。修复方向:区分同页动画推进与显式跳转,对显式跳转清除旧页切换状态并重建本页起始状态;加入同页数字跳转和 Home/End 的切换中回归测试。 diff --git a/tasks/reviews/C10.5-r2.md b/tasks/reviews/C10.5-r2.md new file mode 100644 index 0000000..a8637d1 --- /dev/null +++ b/tasks/reviews/C10.5-r2.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-show/src/slideshow.rs:170 — 后退的“全部动画已完成”状态由 clicks 是否为零决定,只有 with/after 自动动画的页面恰好为零,因此后退会重新播放入页动画。复现:第一页矩形设置 with 开始、10 秒淡入,第二页无动画;放映按 right、left 回到第一页,真实 LOK 应用探针得到矩形 opacity=0.00006146328、moving=true,预期为完全显示的已完成状态。违反 C10.5 的后退行为及 SHOW-09。修复方向:显式携带后退的已完成状态,独立于单击组数量;覆盖没有 click 组的入场及退出动画。 + +apps/ely-show/src/slideshow.rs:169 — 入页时立即启动动画时钟,当前页图层尚在后台读取时,stage 显示黑屏而动画照常计时,返回图层后直接显示已结束的效果。复现:第三页设置 with 开始、0.1 秒淡入,从已缓存的第一页数字跳到第三页;以共享 worker 忙碌 300 毫秒模拟其他标签的后台读取,探针确认跳页时 stack(2) 为空,图层首次可用时 elapsed=0.352540625、moving=false,整个淡入已经在黑屏期间结束。违反 SHOW-09 的持续时间与放映播放行为。修复方向:当前页图层可绘制后开始入页切换和自动动画计时,并让读取期间的单击与自动换页遵守同一时间基准;加入延迟图层读取的回归测试。 + +apps/ely-show/src/slideshow/media.rs:90 — media_tick 在循环重播之前计算 keep;视频结束时 keep=false,随后 seek/play 已重启播放器,返回值仍然让 Audience 停止请求动画帧,循环播放的画面停住。复现:主线程 AVFoundation 放映探针播放 fixtures/media/clip.mp4,媒体 loop=true,按 Audience 的返回值决定下一帧;第一遍结束后 media_tick 返回 false,实际播放器已经为 Ready、playing=true、position=0.0。单屏静止幻灯片此后继续播放声音并保持上一帧,违反 SHOW-10。修复方向:循环重启后继续安排视频帧,并按重启后的播放器状态判定刷新;用原生播放器覆盖完整的一次循环边界。 + +apps/ely-show/src/slideshow.rs:156 — Leaving 持有离开页的完整图层,切换完成后一直保留到下一次跳页;layers 的 retain 只约束 HashMap,放映实际仍持有上一页、当前页和下一页三个全尺寸图层栈。复现:三页均可见,第二页设置 0.1 秒淡化,从第一页进入第二页,等第三页预取及切换结束;探针得到 moving=false、leaving.is_some()=true、stack(0..2)=[None, Some, Some],第一栈仍由 leaving.stack 持有。违反 C10.5 的只缓存当前页与下一页约定。修复方向:切换完成时释放离开页图层,回归测试统计全部持有的图层栈,覆盖切换结束后的静止状态。 + +apps/ely-show/src/tests/play_more.rs:174 — 退出效果回归只检查 object_look 的模型值,观众切换画面没有进入断言,第一轮退出对象重新出现的问题仍能通过该测试。复现:在独立副本中把 stage::left 改回 compose::slide(..., |_| true, ...),a_finished_exit_stays_gone_as_the_slide_leaves 仍通过;同一渲染函数的 Metal 半程像素探针读出已退出对象的红色 Rgba([255, 0, 0, 255]) 并失败,当前实现读出黑色 Rgba([0, 0, 0, 255]) 并通过。修复方向:回归测试绘制实际的离页切换并断言对象位置的像素,让恢复全部对象可见的变异必然失败。 + +apps/ely-show/src/tests/play_more.rs:198 — 媒体失败回归在测试线程上打开有效视频,只验证 Player::open 的同步线程错误;第一轮要求的异步解码失败和 media_tick 错误传播仍缺少有效回归。复现:在独立副本中把 media_tick 的 Err 分支改为直接返回 false,media_that_cannot_play_says_why_and_the_show_goes_on 仍通过;主线程放映探针打开存在但损坏的 MP4 后 notice=None 并失败,当前实现得到“无法播放:Cannot Open”。watch 的独立状态测试也没有经过该错误传播分支。修复方向:在主线程让真实播放器进入 Loading/Failed,经实际 media_tick 验证原因提示、失败播放器停止和放映继续,并要求跳过错误传播的变异失败。 diff --git a/tasks/reviews/C10.5-r3.md b/tasks/reviews/C10.5-r3.md new file mode 100644 index 0000000..f204293 --- /dev/null +++ b/tasks/reviews/C10.5-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-platform/src/media.rs:83 — ended() 把末尾 50 毫秒内的手动暂停判为播放结束,tick(true) 随即从头重播,循环媒体的暂停操作因此失效。复现:主线程 AVFoundation 播放 tests/fixtures/media/clip.mp4,在 position=0.963024051 时暂停,ended=true;放映主线程探针实际调用 toggle_media,在 position=0.969812355 时得到 playing=false,紧接着 media_tick 返回 true、playing=true。放映与编辑区每帧都调用该方法,用户在此区间暂停会立即恢复播放,违反 SHOW-10 的媒体播放行为。修复方向:分别记录手动暂停与实际播放结束,循环重启只响应实际结束;回归覆盖循环视频在结尾附近暂停并持续 tick 后仍保持暂停。 + +apps/ely-show/src/tests/play_more.rs:198 — 第二轮第六项要求的应用级异步媒体失败回归仍缺失:此测试在工作线程打开有效视频,仅覆盖 Player::open 的同步线程错误;新增平台测试仅验证 Player::tick 返回错误,未经过 Show::media_tick 的提示传播分支。复现:在独立副本中把 media_tick 的 Err 分支改为直接返回 false,此应用测试与 crates/ely-platform/tests/media.rs 均通过;主线程放映探针打开存在但损坏的 MP4,实际 notice=None,原因提示断言失败,当前实现则得到“无法播放:Cannot Open”。这条变异会恢复 SHOW-10、PRD 10.3 所禁止的异步失败无提示行为,现有回归仍无法捕获。修复方向:添加主线程真实播放器的应用回归,经 Show::media_tick 断言异步失败原因、失败播放实例释放及放映继续,并使移除错误传播的变异失败。 diff --git a/tasks/reviews/C10.6-r1.md b/tasks/reviews/C10.6-r1.md new file mode 100644 index 0000000..51938fe --- /dev/null +++ b/tasks/reviews/C10.6-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-show/src/output.rs:156 — [P1] 在 GPUI 的窗口/实体更新回调中同步调用系统打印的 `NSPrintOperation::runOperation()`,模态事件循环重入仍持有借用的 GPUI App。复现:macOS 真窗口中新建空白文稿,Cmd+P,点击 Print…,系统打印面板出现后读取应用 AX 树及菜单;两次独立运行均在 `gpui/src/platform/app_menu.rs:336` 报 `RefCell already borrowed`,继而在 `menu_will_open` 中触发不可展开的 panic,以 134 退出。幻灯片打印和讲义打印均复现,堆栈包含本行、`write_pdf` 回调和 `NSPrintOperation::runOperation`,违反 SHOW-13。修复方向:在 GPUI App、窗口和 Deck 的借用结束后进入系统打印面板,打印结果再通过安全的应用更新回传;增加真实系统面板的打开、AX/菜单交互、取消回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r1-1hmvpkc_/print-repro-stderr.log` 与 `ui-stderr.log`。 + +apps/ely-show/src/output.rs:246 — [P1] 图片导出仅在界面线程用 `Path::exists()` 预检查,随后 `picture()` 的 `image.save()` 按覆盖方式打开目标。失效符号链接会通过检查;检查到写入之间出现的文件也会被覆盖,违反本任务“任一目标已存在即整体不写”的约定。复现一:输出文件夹里预放 `deck-1.png -> ../unrelated.png`,链接目标尚不存在,导出后实际在所选文件夹外创建了 `unrelated.png`。复现二:让 worker 暂时忙碌,调用导出后在排队期间写入 `deck-1.png`,释放 worker 后文件内容从 `EXISTING USER FILE` 变成 PNG。隔离副本的 `probe_picture_existing_dangling_symlink` 和 `probe_picture_file_created_after_preflight` 均失败,现有图片导出测试通过。修复方向:按目录项检查并暴露检查错误,以排他创建的文件句柄写出;在整批目标完成排他占用前保持实际输出为空,任一冲突时释放本次占用并报错;覆盖上述两个回归场景。 + +apps/ely-show/src/output.rs:123 — [P2] 导出范围取 `tab.extra.slides`,该列表在母版视图里保存母版;写出副本中的 `paint_part(..., false)` 和 PDF 导出则处理标准幻灯片,造成范围与实际对象错配。复现:创建三张幻灯片,隐藏第三张,进入只有一个母版的母版视图,Cmd+E 选择全部图片,实际只写 `deck-1.png`,第二张可见幻灯片漏出;`probe_picture_export_from_master_view` 实测 `count=1`、文件数 1,预期 2。真窗口另建三张可见幻灯片后进入母版视图,From–To 的结束页输入 3 被钳制回 1。违反 SHOW-13 的范围选择和全部输出。修复方向:统一从标准幻灯片取得输出数量、当前页、可见性与尺寸;明确约束母版视图的输出入口,使对话框范围和实际写出对象一致;增加母版视图下全部、当前和指定范围的回归。 + +patches/libreoffice/0038-lok-impress-slide-check.patch:83 — [P2] 无法显示的图片仅由 `GetGraphicType() == GraphicType::NONE` 判定,漏掉实际解码失败后的内核占位状态。复现:将 `trouble.fodp` 第二张的图片 URL 指向确实存在、内容为 `not a valid image` 的 `broken.png`,读取 `.uno:SlideCheck` 得到该链接 `missing:false` 且该页 `broken:[]`;对象 Gone 的“无法显示”问题被漏报。`probe_existing_unreadable_link_is_broken` 在实际 LOK 上失败,原夹具测试通过,违反 SHOW-14,也与 agents.md 的“读不出的图片”声明冲突。修复方向:依据图片实际解码/可渲染状态判定 broken,覆盖错误占位状态,并用存在但损坏的链接图片及嵌入图片验证。 + +patches/libreoffice/0038-lok-impress-slide-check.patch:90 — [P2] OLE 外部链接的缺失标志硬编码为 false。复现:创建 `linked.odt`,以其 URL 替换 `trouble.fodp` 第三张的 `draw:object` 引用并载入,随后删除 `linked.odt`,再次读取 `.uno:SlideCheck`;删除前后均返回同一个 OLE 链接的 `missing:false`、`broken:[]`,界面仍把已丢失源文件显示为普通链接。实际 LOK 探针 `probe_missing_ole_source_is_marked_missing` 失败。违反演示准备的文件检查约定,也与 agents.md 所称“链接图片与 OLE 链接及是否缺失”冲突。修复方向:读取并检查 OLE 链接的实际源文件状态,正确返回 missing 并暴露读取错误,增加带缓存对象的源文件删除回归。 + +apps/ely-show/src/check.rs:127 — [P2] 检查完成时只核对活动标签,随后直接覆盖 `deck.dialog`,丢失用户在等待期间打开的后续对话框。复现:暂时延迟当前文稿的 worker,点击“检查演示…”,在检查尚未返回时按 Cmd+E 打开导出对话框;恢复 worker 后该对话框被检查结果替换。隔离副本的 `probe_delayed_check_keeps_newer_output_dialog` 失败;真窗口通过暂停自己的 worker 独立复现,AX 窗口标题从 Export 变为 Presentation Check。修复方向:记录检查请求及其对话框交互身份,后续操作使旧请求失效,回调只安装仍有效的检查结果;增加延迟检查与导出/打印/取消竞争的回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r1-1hmvpkc_/ui-ax-race-before.txt`、`ui-ax-race-after.txt`。独立构建 worker、`cargo check --workspace`、`cargo test --workspace`(509 通过、1 忽略)及 `tests/scripts/lo-patches.sh` 均通过,上述失败由额外实际内核/原生窗口探针复现。 diff --git a/tasks/reviews/C10.6-r2.md b/tasks/reviews/C10.6-r2.md new file mode 100644 index 0000000..a9daf07 --- /dev/null +++ b/tasks/reviews/C10.6-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-show/src/output.rs:124 — [P1] PNG 编码返回后依赖 `BufWriter` 析构写出缓冲尾部,析构期间的写入错误被吞,批量导出随即保留截断文件并显示成功。复现:实际 LOK 绘制 1920×1080 幻灯片,完整 PNG 为 89518 字节;在导出进程中忽略 SIGXFSZ 并把 RLIMIT_FSIZE 设为 89517,调用同一个 `picture()`,实际文件为 89517 字节,返回 `Ok(())`。这同样覆盖磁盘耗尽或输出设备在最后一次缓冲写入时失败的路径,违反错误必须暴露的规则。隔离副本的 `probe_r2_picture_exposes_final_write_failure` 失败;仅在编码后显式执行并检查 `flush()`,同一探针收到 `FileTooLarge` 并通过。修复方向:检查缓冲写入完成结果,把错误传回整批清理和错误提示路径,增加尾部写入失败回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r2-cxwgwj2s/buffer-1920.log`、`buffer-flush-control.log`。 + +apps/ely-show/src/dialog.rs:52 — [P2] 第一轮第六项仍有对话框生命周期漏洞:请求编号仅在检查与输出读取时增加,其他对话框的打开及关闭保留旧编号;后续对话框关闭后,旧检查重新满足 `dialog_due()` 并弹出。复现:暂停本应用 worker,选择“检查演示…”,再从 Insert 菜单打开“插入表格”,点击 Cancel,然后恢复 worker;AX 窗口从 Insert Table 变为 Presentation Check,日志确认表格关闭后才安装旧结果。实际窗口和隔离副本的 `probe_r2_late_check_after_new_dialog_closed` 均复现;现有回归只覆盖后续对话框仍打开的情况。修复方向:让后续对话框交互和取消使旧请求永久失效,回调按完整交互身份核对;覆盖后续对话框打开后关闭、取消及标签往返的延迟回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r2-cxwgwj2s/ui-race-table-before.ax`、`ui-race-table-after.ax`、`output-regressions.log`。 + +apps/ely-show/src/output.rs:271 — [P2] 图片导出的范围与隐藏标志来自打开对话框时的 `output.hidden`,实际图片来自提交后另存的当前副本,两者可以属于不同修订,导致“全部”漏出可见幻灯片。复现:三张幻灯片中隐藏第三张,打开导出对话框;通过仍可操作的 Slide 菜单显示第三张,再选择 Pictures/All 导出。当前副本有三张可见幻灯片,实际只有 `deck-1.png`、`deck-2.png`。隔离副本的 `probe_r2_picture_all_uses_the_exported_revision` 实测文件数 2、预期 3;真窗口另确认 Export 打开期间 `slide.show` 可执行且对话框继续保留。违反 SHOW-13 的全部输出及 FILE-08 的修订一致性。修复方向:从实际导出副本统一取得张数、可见性和尺寸,再计算范围并排他占用目标;或者固定对话框所属文档修订并在失效时显式报错。增加打开对话框后改变可见性的回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r2-cxwgwj2s/output-regressions.log`、`revision-focused.log`、`ui-export-hidden.ax`、`ui-export-shown.ax`。独立构建 worker 与 Show/Doc/Table、`cargo check --workspace`、`cargo test --workspace`(512 通过、1 忽略)及 `tests/scripts/lo-patches.sh` 均通过。第一轮的打印崩溃(幻灯片与三张一页讲义均打开系统面板、操作菜单、取消)、已有文件与失效链接冲突、母版下实际图片输出、损坏链接及嵌入图片、删除 OLE 源文件均已复验通过;原源码哈希核对通过,仅写入本 verdict。 diff --git a/tasks/reviews/C10.6-r3.md b/tasks/reviews/C10.6-r3.md new file mode 100644 index 0000000..2393b04 --- /dev/null +++ b/tasks/reviews/C10.6-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-show/src/output/dialog.rs:85 — [P2] 范围字段只在 on_commit 更新 Output,实际失焦会把尚未提交的起始值恢复为旧值,Export 按钮又直接取走对话框,终止字段的当前输入也随之丢失。复现:macOS 真窗口打开五张幻灯片(第四张隐藏),选择 PDF/From–To,起始框全选输入 2,点击终止框全选输入 3,直接点击 Export 并保存。日志依次记录 typed 2、提交 1、typed 3,最终导出命令为 pages: Some((1, 5));实际 PDF 含 Alpha、Bravo、Charlie、Echo 四页,预期范围 2–3 应为 Bravo、Charlie 两页,违反 SHOW-13。修复方向:让对话框范围值随有效输入更新,确保失焦及提交动作保留用户输入;增加真实范围控件的连续输入、切换字段和直接导出回归。隔离副本仅把范围字段改用 on_change 后,同样操作保留 AX 值 2、3,实际 PDF 只含 Bravo、Charlie。证据:/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r3-ozsh5ye_/ui-range-typed.ax、ui-data/ely-show/ely.log、range-pdf.txt、control-typed.ax、control-pdf.txt。 + +apps/ely-show/src/tests/output.rs:72 — [P2] 图片导出回归在构造 Output 后立即写出,遗漏第二轮第三项“打开对话框后改变幻灯片可见性”的修订一致性场景。复现测试缺口:在隔离副本把 write_pictures 的隐藏标志来源变异回 output.hidden,现有 7 个输出测试全部通过;另加上一轮 probe_r2_picture_all_uses_the_exported_revision,第三张在构造 Output 时隐藏、导出前显示,实际文件数 2、预期 3,该探针明确失败。当前修复通过同一探针,但提交的测试仍允许旧缺陷回归。修复方向:把该修订变化场景加入项目回归,并断言实际生成三张可读 PNG;保留上述变异失败验证。证据:/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c106-r3-ozsh5ye_/mutation-shipped-output.log、mutation-revision-probe.log、r2-probes.log。第二轮三项复现均通过;独立 worker 与 Show/Doc/Table 构建、cargo check --workspace、cargo test --workspace(515 通过、1 忽略)、注释检查和补丁序列测试均通过;真窗口复验旧检查在表格对话框取消后保持失效,以及系统打印面板的菜单操作和取消。原项目哈希核对通过,仅写入本 verdict。 diff --git a/tasks/reviews/C11.1-r1.md b/tasks/reviews/C11.1-r1.md new file mode 100644 index 0000000..be13c2b --- /dev/null +++ b/tasks/reviews/C11.1-r1.md @@ -0,0 +1,29 @@ +FAIL + +apps/ely-pdf/src/pages.rs:192 — 测量视口的绝对定位 canvas 缺少位置约束,其静态位置落在页面栈之后,`room.origin` 因此指向文档底部;`page_at` 用这个原点换算鼠标位置,文字选择失效,违反 PDF-01。复现:真窗口打开三页 Letter PDF,在第一页拖选文字;实测鼠标窗口坐标为 `(356,179)`,测量原点为 `(240,3349.5)`,拖选始终没有高亮,复制保留测试剪贴板标记。独立构建和原有 17 项测试均通过;跨页复制测试直接设置 Selection,遗漏真实命中路径。修复方向:从实际滚动视口测量固定原点与尺寸,使测量层位置明确绑定视口,并以真实鼠标拖选验证。 + +apps/ely-pdf/src/select.rs:98 — 文字尚未缓存时,首次按下只发起后台读取并将 selection 清空;读取完成只缓存字符,拖选起点永久丢失。复现:校正上述视口坐标后,首次按下文字、等字符读取完成、继续拖动并松开,selection 仍为空;第二次拖动才出现高亮。隔离探针 `review_first_drag_keeps_its_anchor_after_text_arrives` 失败,原有复制测试绕过这条输入路径。修复方向:保留发起标签、页、按下位置和拖动状态,在字符到达后完成仍有效的选择操作,并覆盖首次拖选与跨页迟到读取。 + +apps/ely-pdf/src/search.rs:151 — 跳转将目标页顶部向下留出两倍 GAP,露出上一页底部;`pages.rs:107` 随后把第一张可见页设为 current,导致当前页回退,违反 PDF-01 和 agents.md 中视图变化保持当前页的声明。复现:三页 PDF 连续模式下从第一页按 PageDown,真窗口显示第二页正文,状态栏仍为 `Page 1 of 3`,再次 PageDown 继续计算同一个目标。隔离探针在 `show(1,0)` 后实际执行 page_view,current 为 0,预期为 1。修复方向:协调跳转位置与当前页判定,使目标页在绘制后仍为当前页;通过真实翻页、缩放和旋转后的帧验证。 + +apps/ely-pdf/src/search.rs:98 — 搜索回调只核对局部 round;新 Find 从零计数,旧查询的批次可以进入另一个查找实例,污染命中和已搜索页数。复现:A 有三页和三处 alpha 命中,启动 alpha 搜索后立即切到只有一处 zzz 的单页 B 并查找 zzz,两次查询 round 均为 1;隔离探针实际得到四处命中,页号为 `[0,2,2,0]`,预期只有 B 的 `[0]`。修复方向:结果绑定发起标签和独立查找实例,再核对查询代次;关闭、重开与换标签均使旧实例结果失效,增加延迟批次回归。 + +apps/ely-pdf/src/search.rs:134 — 查找状态属于 find.tab,命中跳转却经 show 修改当前活动标签;后台自动选择首个命中会改变用户已经切换到的文档。复现:在三页 A 搜索只出现于第三页的 two,批次返回前切到单页 B,保留 A 的查找状态;隔离探针由真实搜索回调自动跳转,B 的 current 从 0 变成 2。修复方向:跳转核对并作用于查找所属标签,活动标签改变时延后该实例的自动跳转;覆盖后台搜索期间切换标签。 + +apps/ely-pdf/src/pages.rs:222 — 渲染失败只发提示,随后无条件 notify;图块仍缺失,下一帧继续请求相同图块,形成持续失败与重绘循环,违反空闲无重绘规则。复现:真窗口打开 PDF,终止它的 worker,再点 Zoom In;原始应用在之后两秒空闲期间记录 240 次 `drawing a page` 失败,并不断生成错误提示。修复方向:记录图块或 worker 的失败状态,停止自动调度失败请求,在明确重试或重新打开时恢复,并验证故障后的空闲行为。 + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/ffi.rs:196 — 页内搜索固定使用 512 个 quad 的容量,结果饱和时直接返回成功,遗漏后续命中,违反 PDF-01 全文搜索。复现:一张 612×792 pt 页面放置 50 行、每行 12 个独立 alpha,共 600 处;真实 worker 搜索仅返回 512 处,应用据此报告完整结果。修复方向:使用能够取得全部命中的搜索接口或完整遍历策略,并覆盖单页超过 512 处命中。 + +crates/ely-pdf-engine/shim/ely_mupdf.c:218 — 搜索丢弃 MuPDF 的命中分组信息,应用将每个 quad 计为一处命中;跨行短语的一处匹配因此被拆成多处。复现:单页只有两行 hello 和 world,查询 `hello world`;真实 worker 返回两个 quad,真窗口显示 `1 of 2`,下一处在同一短语的两行之间跳转。修复方向:保留一个逻辑命中与其全部四边形的关系,计数、跳转与高亮按逻辑命中处理,并增加跨行短语回归。 + +apps/ely-pdf/src/viewer.rs:217 — 第二个加密文件直接覆盖第一个密码对话框,前一个已经打开的 worker 文档失去界面入口和关闭路径。复现:依次打开两个 AES-256 PDF,在输入密码前让第二个密码框出现,再取消第二个;隔离探针仍能用正确密码解锁第一个 Doc,证明该文档遗留在 worker。启动参数和多选打开均可进入此路径。修复方向:串行处理待授权打开,或在替换密码请求时显式关闭被替换文档;保证每个待授权 Doc 都有可达的完成或取消路径。 + +apps/ely-pdf/src/pages.rs:138 — 图块淘汰仅核对页号、缩放、旋转和主题;同一张页内已经滚出屏幕的图块持续保留,违背 agents.md 的 C11.1“屏外与旧缩放的块即丢”声明。复现:612×14400 pt 长页在固定缩放下向下滚动 1500 px,隔离探针确认屏外 `ty=0` 图块仍在缓存;沿长页继续滚动会累积整页像素。修复方向:按实际可见图块集合淘汰缓存,并在后台结果到达时核对该图块是否仍可见;覆盖长页内滚动。 + +apps/ely-pdf/src/viewer.rs:387 — 有文档时始终分配左侧书签栏,即使书签列表为空,也只显示 No bookmarks 并常驻占用 240 px,违反 PRD §5.2“无内容的侧栏不占位”。复现:真窗口打开没有书签的普通 PDF,左栏占位且中央适合宽度的页面随之缩小。修复方向:按当前标签实际书签内容决定是否提供左栏,覆盖空书签文件与有书签文件之间切换。 + +apps/ely-pdf/src/viewer.rs:82 — 空白窗口也立即启动 worker,worker 初始化 MuPDF,违反 PERF-01“无文件冷启动……不预加载未使用内核”。复现:以零文件参数启动 ElyPDF,实测空白窗口已有一个 ely-pdf-worker 子进程,日志显示 worker up。修复方向:首个打开请求时再启动 worker,在后台完成初始化并显示打开状态,验证无文件启动时的子进程。 + +crates/ely-ipc/src/lib.rs:23 — 长度头已经读入部分字节时的 UnexpectedEof 被当作正常流结束,截断帧错误被吞掉。复现:输入只有 1、2 或 3 个长度字节后关闭,read_frame 返回 `Ok(None)`;真实 PDF worker 收到一个长度字节后以 0 退出,输出和错误均为空。原有测试只覆盖空流 EOF;新增隔离截断头探针失败。修复方向:区分尚未开始新帧的干净 EOF 和部分长度头的截断,后者返回错误,覆盖三个截断长度。 + +crates/ely-pdf-engine/shim/ely_mupdf.c:100 — 三处注释超过十词:第 100 行 11 词、第 108 行 12 词、第 244 行 11 词,违反 agents.md 的注释长度规则。复现:逐条按空白分词即可得到上述计数;现有 comments.py 检查通过,因为其 C 文件范围只包含 tests/engines,遗漏该垫片。修复方向:缩短三处注释,并让现有注释检查覆盖该项目 C 源文件。 diff --git a/tasks/reviews/C11.1-r2.md b/tasks/reviews/C11.1-r2.md new file mode 100644 index 0000000..e30b92f --- /dev/null +++ b/tasks/reviews/C11.1-r2.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-pdf/src/viewer.rs:276 — R1 的多加密文件缺陷仍存在于取消路径:密码框的关闭回调已经借用窗口,give_up 同步调用 next_locked,再由 ask_password:232 重入 window.update;创建下一密码框失败后只记日志,已弹出队列的文档失去完成与取消入口。复现:真窗口用启动参数打开两个 AES-256 PDF,在第一个密码框按 Esc;第二个密码框没有出现,日志为 `pdf: no window for the password: window not found`。隔离探针在窗口更新内调用 give_up,得到 dialog=None,预期为第二个 Doc;现有队列测试从窗口更新之外取消,遗漏实际事件路径。修复方向:利用当前窗口创建下一密码框,或延后到窗口借用释放后处理队列;创建成功后再移除待授权项,失败时显式处理该文档,并覆盖真实取消事件。 + +apps/ely-pdf/src/pages.rs:105 — R1 的当前页回退仅在连续模式修复;双页模式绘制仍把同一行的左页写为 current,覆盖跳转或切换模式时保留的右页,违反 PDF-01 与 C11.1 的当前页保持约定。复现:三页 PDF 在连续模式进入第 2 页,再切换双页模式,真窗口状态栏回到第 1 页;PageDown 跳到右页后又被绘制改回左页,重复翻页停在第一组。隔离探针设置双页并 show(1,0),实际绘制后 current=0,预期为 1;现有跳转回归只覆盖连续模式。修复方向:双页布局按当前阅读页和目标页维护 current,滚动判定同时处理一行内的两页,覆盖右页跳转、连续翻页与模式切换后的绘制帧。 + +apps/ely-pdf/src/pages.rs:118 — R1 的屏外图块修复只裁掉纵向屏外部分;每个可见页仍按完整页宽请求全部 tx,并把它们都加入 seen,横向屏外图块持续占用渲染与缓存,违反 C11.1“只留屏上的块”的约定。复现:Letter 页以 Zoom::Scale(4.0) 显示,2 倍设备比例的隔离窗口中,可见范围在页内右端为 3808 设备像素,缓存仍包含 tx=8 和 tx=9(起点分别为 4096、4608),横向滚动后屏外列同样保留;真实 worker 与实际 page_view 路径的可见性断言失败。修复方向:用视口的横纵范围和两轴滚动偏移计算可见图块集合,按该集合请求与淘汰,并覆盖放大后的横向滚动。 + +apps/ely-pdf/src/layout.rs:55 — 零页文档被 read_pages/add_tab 接受并显示“第 1 页,共 0 页”,单页布局随后构造页号 0 并在第 65 行越界,导致整个应用退出。复现:打开页树为 `/Count 0 /Kids []` 的 PDF,真实 worker 成功返回 0 页;在真窗口选择“视图 → 单页”,主线程报 `index out of bounds: the len is 0 but the index is 0`,进程以 134 退出。隔离应用探针同样失败,现有坏文件测试只覆盖打开即失败的输入。修复方向:读取阶段校验可阅读页数,向用户报告零页错误并关闭已打开的 Doc;布局处理空页集合,增加内核接受该文件后的应用回归。 diff --git a/tasks/reviews/C11.1-r3.md b/tasks/reviews/C11.1-r3.md new file mode 100644 index 0000000..2367f76 --- /dev/null +++ b/tasks/reviews/C11.1-r3.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-pdf/src/dialog.rs:15 — 密码提交也会触发 PromptDialog 的关闭回调,give_up 提前关闭 Doc,随后 on_submit 的 unlock 与关闭请求竞争,授权打开失败。复现:真窗口打开三页 AES-256 PDF,输入正确密码 secret 并回车,日志依次出现 `prompt dialog ...: sent`、`dialog ...: closed`、`known-locked.pdf: no document 1`,没有文档标签;真实 worker 对同一文件与密码返回解锁成功、页数 3。隔离键盘探针确认字段内容为 secret 后提交,实际标签数 0,预期 1;移除提交时的 Doc 关闭后该探针通过。现有密码队列测试直接调用取消函数,遗漏提交事件。修复方向:区分授权提交与取消的清理路径,提交时保留 Doc 至解锁和读取完成,取消时关闭 Doc,并覆盖正确密码、错误密码与排队文件的实际提交事件。 + +apps/ely-pdf/src/dialog.rs:16 — 连续密码框共用固定 `pdf-locked` 标识;第二轮延后建框后,新框可直接接替旧框的一帧,复用已经归还焦点的组件状态,第二个框失去键盘焦点,Esc 无法取消,违反 UX-03 与密码取消约定。复现:真窗口同时打开两个加密 PDF,第一次 Esc 后 AX 树出现第二个文件的密码框,第二次 Esc 后该框仍存在;点击其“取消”按钮才关闭。隔离探针 `review_esc_dismisses_each_queued_password_dialog` 同样得到 `Some(Doc(2))`,预期 None;密码框标识绑定字段实体后该探针通过。现有 `giving_up_from_the_dialog_itself_asks_the_next` 绕过 Esc 分发,只检查 dialog 状态。修复方向:每次授权请求建立独立的弹窗与焦点身份,覆盖连续两次 Esc 和第二个密码框直接键入、提交。 + +apps/ely-pdf/src/pages.rs:105 — 第二轮双页当前页判定仍只看纵向可见性,第 109 行按页对保留 current 时也忽略横向视口;左页已经完全滚出屏幕后,正在阅读右页的 current 仍指向左页,切换单页便显示错误页面。复现:两张 Letter 页设为双页、Zoom::Scale(4.0),横向滚动到右页左缘,仅右页可见;实际执行 page_view 后 current=0,预期 1。隔离探针 `review_two_page_scroll_tracks_the_visible_right_page` 失败;按两轴可见范围选择当前页后通过。现有右页回归通过 show 预设 current,遗漏横向滚动改变阅读页的路径。修复方向:当前页判定使用实际二维可见范围,仅在原 current 所属页仍可见时保留它,覆盖横向滚动后切换模式。 + +apps/ely-pdf/src/pages.rs:100 — 跳转总把横向偏移重置为零,放大的双页布局中右页完全在视口外;第二轮保留右页 current 的修复使页码正确,目标页仍没有显示,违反 PDF-01 的阅读跳转。复现:两张 Letter 页设为双页、Zoom::Scale(4.0),执行 show(1,0)(PageDown 与搜索跳转共用此路径)并绘制;实际 current=1,目标页范围 x=2480–4928,视口范围 x=0–1920,屏上仍是左页。隔离探针 `review_two_page_zoom_jump_puts_the_target_on_screen` 失败;跳转时将目标页横向移入视口后通过。修复方向:跳转同时计算横纵偏移,使目标页或命中位置进入实际视口,覆盖放大双页下的右页翻页、搜索与书签跳转。 + +apps/ely-pdf/src/pages.rs:125 — 横向图块上界把视口右边缘作为包含端点再整除,边缘恰落在 512 设备像素边界时,会请求并缓存从边缘开始的整列屏外图块,第二轮“只留屏上的块”修复仍有边界遗漏。复现:Letter 页设 Zoom::Scale(4.0),2 倍设备比例,横向滚动使视口右缘等于页内第 9 列图块起点;真实 worker 与 page_view 探针记录图块左缘和视口右缘均为 2320 逻辑像素,缓存仍包含 tx=9,该列可见面积为零。`review_no_tile_starts_at_the_viewport_right_edge` 失败;改用半开区间的最后一个覆盖像素计算上界后通过。修复方向:按视口半开区间计算图块范围,对横纵两轴采用一致的边界规则,并覆盖视口边缘与图块边缘完全重合的滚动位置。 diff --git a/tasks/reviews/C11.2-r1.md b/tasks/reviews/C11.2-r1.md new file mode 100644 index 0000000..ee73660 --- /dev/null +++ b/tasks/reviews/C11.2-r1.md @@ -0,0 +1,17 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_pages.c:450 — 提取在原文档 journal 上建立临时删除步骤,覆盖已有重做分支;worker 的 Doc::sync 仍保留旧位置的编辑 ID,导致历史标记与内容错配(PDF-02、FILE-03)。复现:三页 PDF 旋转第一页、保存、撤销、提取第一页、重做;真实 worker 和应用探针均得到一页未旋转的文档,应用却显示已保存(pages=1, dirty=false),关闭可直接丢掉这个状态。方向:在独立副本中提取,完整保留源文档的撤销、重做及编辑 ID 绑定;增加保存后撤销再提取、拆分的回归测试。 + +apps/ely-pdf/src/edits.rs:101 — 结构编辑只过滤 picked,anchor 仍指向旧页号(PDF-02)。复现:三页文档点选第三页、删除、Shift 点选第一页,chosen_pages 得到 [0,1,2],此时页数为 2;选择 Insert Blank Page 在 organize.rs:138 越界 panic。隔离应用测试复现,macOS 真窗口同样以 abort 退出。方向:结构编辑后清理或重新映射选择锚点及页身份,保证所有选择都属于当前页集;修正锚点的副本通过相同探针。 + +apps/ely-pdf/src/edits.rs:78 — 编辑失败只发送 Failed 与 notify,外壳等待队列由 Reported 才释放(FILE-02、FILE-04)。复现:发起超过页面尺寸的裁剪,在其落地前保存;裁剪已报错且 view.settled() 为 true,core.waiting 仍有一条保存,保存一直挂起。应用探针得到 waiting=1;失败分支补发落定通知后同一探针得到 waiting=0。方向:成功、拒绝与失败均发布队列落定状态,使保存、关闭、提取等等待操作得到完成或明确失败。 + +crates/ely-office/src/close.rs:150 — 窗口关闭回调通过 dirty_now 同步读取历史,close_tab:176 又在界面线程持 worker 锁关闭文档,违反 PERF-02 与 agents.md 的界面线程约束。复现:干净 PDF 的后台渲染或导出占用 worker 锁时点击窗口关闭;隔离测试模拟后台持锁 500 ms,should_close 在界面线程耗时 514–522 ms。方向:将历史检查与引擎关闭排入后台,回到窗口时核对标签身份并完成关闭,界面回调即时返回。 + +crates/ely-pdf-engine/shim/ely_pages.c:385 — 从文件插入仅调用 pdf_graft_mapped_page,源页的链接、批注、字段等页面引用随合并被静默丢弃(PDF-02)。复现:向三页 PDF 末尾插入另一份三页 PDF,源文件第一页含指向源第二页的 /Link;保存的六页文件中 /Link 数量由源文件的 1 变为 0。方向:嫁接页面时同时迁移相关对象,并通过源页到目标页的映射重绑引用;测试读取合并后保存文件的链接目标及字段。 + +crates/ely-pdf-engine/shim/ely_pages.c:124 — 删除字段仅按 widget 的 /P 判定归属;省略可选 /P 的 widget 即使由已删页的 /Annots 持有,也被保留(PDF-02,R-02 的字段移除声明)。复现:第二页 /Annots 引用一个 /FT /Tx widget,AcroForm/Fields 引用同一对象,widget 无 /P;删除第二页并紧缩保存后,gone-field 及其值 value-on-deleted-page 仍在 AcroForm 与文件中。真实 worker 探针确认。方向:从被删页的 /Annots 收集 widget 身份,再清理字段树及相关引用;覆盖省略 /P 与共享字段的样本。 + +apps/ely-pdf/src/ask.rs:66 — 裁剪的上右下左边距直接发送给内核,忽略当前视图的显示旋转,裁剪方向与所见页面错位(PDF-02、agents.md 的按所见裁剪声明)。复现:Letter 页面用 View 菜单旋转显示 90°,裁剪顶部 25.4 mm;实际内核尺寸为 612×720 pt,所见顶部裁剪应得到 540×792 pt 的内核尺寸。应用探针失败;按显示旋转换算边距的副本通过相同探针。方向:将所见边距转换到页面自身的方向,并覆盖显示旋转与页面 /Rotate 叠加的测试。 + +crates/ely-pdf-engine/shim/ely_pages.c:318 — 裁剪将物理 point 边距直接减到 PDF 用户坐标,忽略 /UserUnit,毫米裁剪量随文件比例变化(PDF-02)。复现:MediaBox 为 612×792、UserUnit=2 的页面,真实 worker 初始边界为 1224×1584 pt;裁剪顶部 72 pt(25.4 mm)后高度变成 1440 pt,实际裁掉 144 pt,正确高度为 1512 pt。方向:按每页 UserUnit 将物理边距转换为用户坐标,验证保存、重开和撤销后的实际尺寸。 diff --git a/tasks/reviews/C11.2-r2.md b/tasks/reviews/C11.2-r2.md new file mode 100644 index 0000000..16cb0db --- /dev/null +++ b/tasks/reviews/C11.2-r2.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_insert.c:75 — 字段重名处理用固定 256 字节缓冲区,截断后生成相同候选名称,循环永久占住 worker(PDF-02)。复现:两份有效 PDF 各有一个同名字段,名称为 255 个 ASCII 字符;从文件插入第二份时,追加的序号始终被截掉。真实 worker 的 OPEN 正常返回,INSERT 超过 3 秒仍无回复,后续渲染、编辑和保存均排在该请求之后。隔离副本扩大缓冲区后,同一探针立即完成;现有回归只覆盖短字段名。方向:按完整字段名与序号动态分配候选字符串,保证每次冲突都产生不同名称,并增加长名称冲突的有界完成测试。 + +crates/ely-pdf-engine/shim/ely_insert.c:108 — 插入只并入字段树,遗漏源 AcroForm 的默认外观 /DA 与字体资源 /DR,已存在的表单内容在合并后静默改变外观(PDF-02、FILE-07)。复现:源页含值为 HELLO 的文本字段,字段继承 AcroForm 的 /DA (/Custom 24 Tf 1 0 0 rg) 与 /DR/Font/Custom;插入另一份 PDF 并保存后,目标 AcroForm 只剩 /Fields。用本次构建的真实 worker 渲染,源页红色像素为 326、文字像素为 524,合并页红色像素为 0、文字像素为 143,24 pt 红字变成较小黑字。隔离副本迁移默认外观与资源后,两页均为 326/524;现有字段回归仅检查名称与值。方向:迁移字段依赖的默认外观和资源,处理目标资源名称冲突并重绑引用,增加保存重开后的字段外观回归。 + +crates/ely-pdf-engine/shim/ely_insert.c:60 — 注释 “Joins `field` to the form; a taken name gets a number.” 共 11 词,超过 agents.md 的十词上限。复现:运行 python3 tests/scripts/comments.py,检查以此行报 FAIL。方向:将该注释缩至十词以内,并运行注释检查。 diff --git a/tasks/reviews/C11.2-r3.md b/tasks/reviews/C11.2-r3.md new file mode 100644 index 0000000..a285916 --- /dev/null +++ b/tasks/reviews/C11.2-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_insert.c:145 — 字体引用改写按原始字符串查重命名表,遗漏 PDF 名称的 #xx 解码,保存后字段绑定到目标文档的同名字体(PDF-02)。复现:源表单 /DR/Font/Custom 为 Courier,/DA 为 (/C#75stom 24 Tf 1 0 0 rg);目标同名资源为 Helvetica。真实 worker 插入并保存后,源字体已改名 Custom_2,插入字段的 /DA 仍为 /C#75stom,解析后的 Custom 指向 Helvetica。pdftoppm 在 72 dpi 下得到源页红色像素 297、合并页 364,字形和外框改变;原有回归只覆盖未转义名称。隔离副本解码名称、按 PDF 名称语法写出新引用后,新增绑定回归通过,合并页与源页像素哈希一致。方向:按 PDF token 语法解析、解码和序列化资源名,覆盖转义名称及保存重开的字体绑定与渲染。 + +crates/ely-pdf-engine/shim/ely_insert.c:168 — 字段继承迁移仅处理 /DA,源 AcroForm 的默认对齐 /Q 丢失,插入字段继承目标表单的对齐(PDF-02)。复现:源 AcroForm 设置 /Q 2,文本字段无自身 /Q;目标 AcroForm 设置 /Q 0。真实 worker 插入并保存后,插入字段仍无 /Q,源文档的右对齐变为左对齐;pdftoppm 在 72 dpi 下得到源页文字外框 x=228–296,合并页 x=76–144。隔离副本将源 /Q 迁入缺少该属性的字段树根后,新增保存回归通过,文字外框与整页像素哈希均恢复一致。方向:迁移源表单中字段依赖的继承属性,保留字段自身覆盖值,增加目标与源默认对齐不同的保存重开回归。 diff --git a/tasks/reviews/C11.3-r1.md b/tasks/reviews/C11.3-r1.md new file mode 100644 index 0000000..c25c277 --- /dev/null +++ b/tasks/reviews/C11.3-r1.md @@ -0,0 +1,15 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_write.c:349:预览与缺字失败会清除重做分支。复现:把 “Hello world” 改成 “Hi world”,撤销,再调用 put_text(..., scale=2) 预览并取消,或尝试用 Helvetica 写入“你好”;两种情况随后 undo(..., redo=true) 都返回 “nothing to redo”。单独读取 text_blocks 后重做正常。pdf_begin_operation 后的实际内容/资源改写截断 journal,pdf_abandon_operation 仅回滚当前改写,原重做分支已经丢失;清资源缓存也无法恢复它。违反 §11.1 的统一撤销规则及预览/失败保留原状态的契约。修复方向:预览在独立文档副本执行,正式改写前完成字形和字体校验,并保证失败事务保留已有 journal 分支。实测证据:/tmp/ely-c113-r1/crates/ely-pdf-engine/tests/review_probe.rs 中 preview_preserves_redo_history、missing_font_preserves_redo_history 均失败,block_read_alone_keeps_redo 通过;日志 /tmp/ely-c113-r1-engine-probes.log。 + +crates/ely-pdf-engine/shim/ely_write.c:320;apps/ely-pdf/src/writes.rs:96:字号增大越出原框时会直接提交,跳过用户预览确认。复现:选中样本中的 18pt “Hello world / Second line”,保持“文档自带字体”,把文字改为 “Hi”、字号设为 72pt 后应用。字号扩大四倍,worker 仍给 grew=false,应用实际得到 edited=1、preview=false。判定只比较行数和新框宽,遗漏字号、垂直边界及原文字实际边界的变化,违反 §11.3“替换可能改变布局时提供预览”。修复方向:根据原/新排版的实际区域和字号等布局属性决定确认,确认前保持文档原样;补充同字体、行数减少但字形变大的回归。实测证据:enlarged_single_line_requires_preview、review_enlarging_glyphs_waits_for_confirmation 失败;副本修复判定后分别通过,日志 /tmp/ely-c113-r1-engine-probes.log、/tmp/ely-c113-r1-app-probes.log、/tmp/ely-c113-r1-repair-app.log。 + +crates/ely-pdf-engine/shim/ely_text.c:249:直接用前两个 stext line 的基线差作为行距,允许行距为零,导致新文字各行重叠。复现:有效 PDF 内容为 “BT /F1 18 Tf 1 0 0 1 72 720 Tm (Hello) Tj 1 0 0 1 160 720 Tm (world) Tj ET”;MuPDF 将其识别为一个 editable=true、text="Hello\nworld" 的块,两段基线相同。把块改为 “One two three four five”、框宽设为 50pt,写入成功,但全部 19 个字符顶部都为 52.65,各个折行叠在同一位置。违反 PDF-03 的文本框局部重排契约。修复方向:区分同一基线上的片段与纵向行,依据有效纵向间隔或单行排版规则生成正行距;补充同基线多片段的渲染/字符位置回归。实测证据:wrapped_text_uses_distinct_baselines 失败;副本修复后字符顶部变为 52.65、74.24998、95.85001 等并通过,日志 /tmp/ely-c113-r1-engine-probes.log、/tmp/ely-c113-r1-repair-engine.log。 + +apps/ely-pdf/src/writes.rs:153:图片选择器返回后重新取 active_view,将发起文档的页号和对象序号用于另一份文档;添加图片在 :167、:175 同样重新取活动文档和当前页。复现:在 A 选中图片并发起 Replace Image,选择器待返回期间打开 B,随后确认一张方形图片。真实 worker 修改了 B,A/B 的编辑计数为 (0, 1),A 原对象保留。违反 PDF-04 与 §11.1 的统一文档选择/状态规则。修复方向:打开选择器时绑定发起标签、文档版本、页及对象身份,回调核验后只修改该目标;添加图片也绑定发起页。实测证据:review_replace_image_stays_with_initiating_tab 失败;副本绑定发起视图并核验版本后计数为 (1, 0),日志 /tmp/ely-c113-r1-app-probes.log、/tmp/ely-c113-r1-repair-app.log。 + +apps/ely-pdf/src/content.rs:96:图层读取的清理回调会被 PdfView::read 的版本守卫整体丢弃,reading_layer 永久保留 true,后续每页的对象与文字读取都被阻止。复现:打开两页 PDF,在阅读模式排入真实页面旋转,立即切到编辑工具并请求第一页图层。旋转完成使 version 增加,旧读取回调被丢弃;继续刷新 100 轮后 reading_layer=true、layers 中仍无第一页,无法点选编辑,重新切换工具也保留该状态。修复方向:异步完成时总是清理该请求的读取状态,再按版本决定接纳数据;旧结果清理后允许当前版本重读。实测证据:review_layer_read_recovers_after_overlapping_edit 使用真实 worker 和工具切换失败;副本将清理移到版本判定前后得到 (false, true) 并通过,日志 /tmp/ely-c113-r1-layer-probe.log、/tmp/ely-c113-r1-repair-app.log。 + +apps/ely-pdf/src/writes.rs:24;apps/ely-pdf/src/view.rs:249:Esc 绑定仅覆盖页面视图的 PdfEditing 上下文,右侧编辑面板是页面视图的兄弟元素;在文本输入框获得焦点后,Esc 无法退出工具。复现:开启编辑、点选文字块、聚焦右侧 TextInput,再发送真实键盘 escape,tool 仍为 Edit;现有 escape_leaves_the_edit_tool 只聚焦页面视图。违背 agents.md:174 的“Esc 退出”和 C11.3 的已验证声明。修复方向:使编辑快捷键上下文覆盖页面与编辑面板,并验证字段焦点下的退出行为。实测证据:review_escape_leaves_tool_from_text_field 失败;副本将上下文放到共同窗口根后得到 Read 并通过,日志 /tmp/ely-c113-r1-escape-probe.log、/tmp/ely-c113-r1-repair-app.log。 + +crates/ely-pdf-engine/shim/ely_write.c:294、:330;crates/ely-pdf-engine/shim/ely_place.c:239;crates/ely-pdf-engine/shim/ely_shim.h:18:新增注释违反 agents.md 的“一行、十词以内”。:294 与 ely_shim.h:18 均为 11 词;ely_write.c:330 为两行 21 词,ely_place.c:239 为两行 20 词。运行 python3 tests/scripts/comments.py 即以 FAIL 退出并报告 ely_write.c:294;独立 C 注释扫描另确认其余三处。修复方向:将这些注释压缩到单行十词以内,补充检查器对多行 C 注释和 .h 文件的覆盖。 diff --git a/tasks/reviews/C11.3-r2.md b/tasks/reviews/C11.3-r2.md new file mode 100644 index 0000000..9c6249d --- /dev/null +++ b/tasks/reviews/C11.3-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-pdf/src/writes.rs:103、:112;apps/ely-pdf/src/editing.rs:34:文字预览只记录标签、页号和文字,确认时缺少文档版本及目标身份核验,结构编辑后仍可改写其他页。复现:打开两页均有 “Hello world / Second line” 的 PDF,在第一页改用 Times 写 “Hi world / Second line”,打开预览;经原生“删除页面”菜单删除第一页,预览继续显示;确认后,原第二页的文字被替换。expected 只比对文字,同文页面通过检查。违反 §11.1 的统一选择与状态规则及 §11.3 的预览确认契约。真实窗口另以 18pt→72pt 复验:删除原页后旧预览仍在 AX 树,确认、保存并用独立 worker 重开,剩余一页字号为 72pt。修复方向:预览绑定发起文档修订和页面/块身份,编辑请求使其失效,确认及实际执行前核验目标与队列状态。证据:/tmp/ely-c113-r2/apps/ely-pdf/src/tests/editing.rs 的 review_r2_preview_is_bound_to_document_version 失败;副本加入版本失效处理后通过,恢复原代码后再次失败;日志 /tmp/ely-c113-r2-preview.log、/tmp/ely-c113-r2-repair.log、/tmp/ely-c113-r2-restored.log;原生证据 /tmp/ely-c113-r2-native-stale-ax.log、/tmp/ely-c113-r2-native-saved.log。 + +apps/ely-pdf/src/writes.rs:172:图片选择器的版本守卫只检查已落地版本,允许在已有编辑进行中或排队时追加使用旧页号/对象序号的操作。复现:页面依次有表单 XObject、图片 A、图片 B;选 A 发起替换,在删除表单的操作落地之前返回方形图片。version 仍相同,替换排到删除之后;删除使 B 占据原序号 1,真实 worker 随后替换 B,A 保留。可控调度探针暂停编辑队列直到选择器回调完成,再交真实 worker 顺序执行;B 的框由 [400,100,500,150] 变成 [425,100,475,150],A 的框保持 [72,242,172,292]。同一守卫也用于添加图片,排队的页面组织操作可改变原页号含义。违反 PDF-04 和 §11.1 的目标选择规则。修复方向:选择器绑定包含待落地编辑的修订,回调和执行时核验发起页/对象身份;已有编辑进行中或排队时让旧选择器请求失效。证据:/tmp/ely-c113-r2/apps/ely-pdf/src/tests/editing.rs 的 review_r2_picker_rejects_pending_edit 失败;副本守卫加入 settled() 检查后通过,恢复原代码后再次失败;日志 /tmp/ely-c113-r2-pending.log、/tmp/ely-c113-r2-repair.log、/tmp/ely-c113-r2-restored.log。 diff --git a/tasks/reviews/C11.3-r3.md b/tasks/reviews/C11.3-r3.md new file mode 100644 index 0000000..76ee44a --- /dev/null +++ b/tasks/reviews/C11.3-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-pdf/src/writes.rs:155;apps/ely-pdf/src/editing.rs:144:删除对象直接把当前页号和对象序号追加到编辑队列,遗漏 on_same 的待落地编辑守卫;第一步删除后,后一对象占据相同序号,第二步会删除未选中的对象。复现:页面顶层依次有表单 XObject STAMP 和图片,选中 STAMP,在第一次删除落地前连续按两次面板“删除”;真实 worker 顺序执行后两个对象都消失,保存并由独立 worker 重开,objects=[],原先未选中的图片被永久删除。违反 PDF-04 和 §11.1 的统一选择规则。真实 macOS 窗口复验:暂停本次测试的 worker,连续触发 AX“删除”两次,再恢复 worker;日志记录两次 queues deleting an object,保存后的独立读取确认 objects=[]、正文 Hello world 保留。修复方向:删除入口绑定选中对象的文档版本,并使用与文字确认、图片替换相同的版本和 settled() 核验,使待落地编辑期间的旧选择失效;补充经实际删除入口、真实队列及保存重开的回归。证据:/tmp/ely-c113-r3/apps/ely-pdf/src/tests/editing.rs 的 review_r3_repeated_delete_keeps_the_other_object 失败;副本给删除入口接入 on_same 后通过,恢复当前代码后再次失败,日志 /tmp/ely-c113-r3-delete.log、/tmp/ely-c113-r3-delete-repaired.log、/tmp/ely-c113-r3-delete-restored.log;原生证据 /tmp/ely-c113-r3-native-panel-ax.log、/tmp/ely-c113-r3-native.log、/tmp/ely-c113-r3-native-saved.log。当前应用 53 项测试、引擎 29 项测试均通过,现有回归遗漏连续删除场景;第二轮两个原始复现均已通过,证据 /tmp/ely-c113-r3-r2-restored.log。 diff --git a/tasks/reviews/C11.4-r1.md b/tasks/reviews/C11.4-r1.md new file mode 100644 index 0000000..fe3fd5a --- /dev/null +++ b/tasks/reviews/C11.4-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_form.c:189 — [P1] 分层 AcroForm 字段重命名会生成重复的完整名称,违反 PDF-06 与 agents.md 的字段名称唯一规则。`check_name` 从根查找输入名,实际写入的 `/T` 却仍属于原来的父字段。具体复现:打开父字段 `Person` 下的 `Person.First`、`Person.Second`,在表单工具中把前者的名称改成 `Second`,点击“应用”;操作成功,两个独立字段都成为 `Person.Second`。已在真窗口完成此操作和保存,另一 worker 重开后仍读到两个同名字段,值分别为 `Alice`、`Bob`(`/tmp/ely-c114-r1-native-saved.log`)。独立副本测试 `review_nested_rename_rejects_a_sibling_collision` 原代码失败,补上父路径校验后通过,恢复原代码再次失败。修复方向:按重命名后实际形成的完整字段名校验整棵字段树,在开启撤销步骤之前拒绝冲突,并统一输入名称与写入层级的语义。 + +apps/ely-pdf/src/annotate.rs:163 — [P2] 首次拖选时文字读取尚未完成,高亮操作会直接退出;文字到达后保留普通选区,批注始终没有创建。同一路径也用于下划线和删除线,违反 PDF-05。具体复现:打开两行文字的 PDF,选择高亮工具,将 worker 读取延迟 200 ms,在首次文字回复之前执行按下、移动、松开;等待读取和界面稳定后,批注列表仍为空,预期为一条 `Highlight`。独立副本测试 `review_cold_highlight_finishes_after_text_arrives` 稳定复现(`/tmp/ely-c114-r1-restored-app.log`);保存待完成操作并在文字回复后执行的定向修复通过,恢复原代码再次失败。修复方向:保留松开时的选区与所属视图版本,异步读齐选区涉及的页面后提交一次批注;工具切换、新选区或文档编辑须使该待完成操作失效,读取失败须显示错误。 + +apps/ely-pdf/src/notes.rs:142 — [P2] 新字段默认名称仅检查当前页,跨页添加会给出已被整份文档占用的名称,导致正常创建操作失败。具体复现:第 1 页使用默认名 `Text1` 添加文本字段,再到空白的第 2 页添加文本字段;右栏仍推荐 `Text1`,点击“添加”后 worker 返回 `a field named Text1 exists`,第 2 页字段数仍为零。应用探针 `review_new_field_defaults_are_unique_across_pages` 确认两页均得到 `Text1`,真实 worker 确认第二次创建被拒绝;将名称检查扩大到探针中已读的两页后通过,恢复原代码再次失败(`/tmp/ely-c114-r1-restored-app.log`)。修复方向:按整份文档的字段名称分配可用默认名,并等待所需名称信息读取完成;单选按钮继续按既定规则显式加入本页已有组。 diff --git a/tasks/reviews/C11.4-r2.md b/tasks/reviews/C11.4-r2.md new file mode 100644 index 0000000..6fc6881 --- /dev/null +++ b/tasks/reviews/C11.4-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-pdf/src/notes.rs:155 — [P2] 默认字段名检查遗漏分层 AcroForm 的父字段名称,正常创建仍会推荐已占用名称,违反 PDF-06。R1 的跨页平级字段场景已通过原复现;新增边界复现:打开包含 `Text1.Child` 的 PDF,在空白处创建文本字段,右栏默认给出 `Text1`,点击“添加”后 worker 返回 `a field named Text1 exists`,字段数保持为 1。`suggested` 只收集页面 widget 的完整名称,父节点 `Text1` 同样占用根字段名称。隔离副本测试 `review_default_field_name_avoids_a_named_parent` 稳定失败;macOS 真窗口 AX 读到默认值 `Text1`,点击 Add 得到同一错误,手工输入 `Text2` 后创建成功,保存后另一 worker 读回 `Text1.Child` 与 `Text2`。证据:`/tmp/ely-c114-r2-restored-probes.log`、`/tmp/ely-c114-r2-native-field-ax.log`、`/tmp/ely-c114-r2-native.log`、`/tmp/ely-c114-r2-parent-worker.log`。修复方向:默认名占用检查覆盖整棵字段树,包括父节点及其完整路径,并为分层字段文档增加创建回归;将父路径加入占用集合的定向修复通过,恢复原代码再次失败。 + +apps/ely-pdf/src/viewer.rs:178 — [P2] 手势期间直接移除右栏会关闭已经打开的属性面板,改变页面布局与鼠标坐标换算,导致二次拖动落错,破坏 PRD 11.1 的共用选择规则与 PDF-04 的对象缩放。具体复现:100% 缩放下点击图片,等待属性栏打开;再按住图片右下角,停留超过面板的收起动画时间,向右水平拖动 50 像素并松开。隔离副本真实 worker 应用测试 `review_an_open_panel_stays_put_during_a_second_drag` 测得内容区宽度从 1300 增至 1680 像素,页面左缘从 344 移至 534;图片原框 `[72,242,172,292]` 保持原尺寸,预期等比缩放后为 `[72,242,222,317]`。现有 `the_panel_waits_for_the_drag_so_the_page_stays_put` 只覆盖属性栏最初关闭的情况;现有快速拖动测试在动画完成之前松开,遗漏此路径。证据:`/tmp/ely-c114-r2-panel-failing.log`、`/tmp/ely-c114-r2-restored-probes.log`。修复方向:手势期间保持按下前的面板可见状态、栏宽与页面坐标换算,手势结束后更新面板,并覆盖已打开面板的二次拖动;保留已打开面板的定向修复通过,恢复原代码再次失败。 diff --git a/tasks/reviews/C11.4-r3.md b/tasks/reviews/C11.4-r3.md new file mode 100644 index 0000000..a7a1de8 --- /dev/null +++ b/tasks/reviews/C11.4-r3.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-pdf/src/notes.rs:156 — [P2] 默认字段名仍遗漏 AcroForm 中没有页面 Widget 的字段及其父节点,父名修复只覆盖能从页面控件推导出的路径,违反 PDF-06 和 C11.4 的全文字段名称检查。具体复现:打开字段树含父字段 `Text1`、子字段 `Child`(`/FT /Tx`、`/V (Alice)`,没有 Widget)的 PDF,选择“表单 → 文本字段”,在空白页单击;右栏默认名称为 `Text1`,点击“添加”后报 `a field named Text1 exists`,新字段没有创建。隔离副本的真实 worker 复现相同拒绝;应用探针 `review_default_field_name_avoids_a_nonvisual_parent` 稳定失败(实际控件数 0,预期 1);macOS 真窗口 AX 读到默认值 `Text1`,点击添加得到同一错误,键入 `Text2` 后创建成功,保存并由另一 worker 重开可读到 `Text2`,原父字段 `Text1` 也仍占用名称。证据:`/tmp/ely-c114-r3-nonvisual-restored.log`、`/tmp/ely-c114-r3-parent-worker.log`、`/tmp/ely-c114-r3-native-field-ax.log`、`/tmp/ely-c114-r3-native-free-name-ax.log`、`/tmp/ely-c114-r3-native.log`、`/tmp/ely-c114-r3-native-reopened.log`;原始样本为 `/tmp/ely-c114-r3-probes/nonvisual-parent.pdf`。修复方向:默认名占用集合从整棵 `/AcroForm/Fields` 树读取字段完整名称与父路径,覆盖没有页面控件的节点,并添加该文档的默认创建回归。 diff --git a/tasks/reviews/C11.5-r1.md b/tasks/reviews/C11.5-r1.md new file mode 100644 index 0000000..808599c --- /dev/null +++ b/tasks/reviews/C11.5-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_ocr.c:210 — OCR 文字流设置 `3 Tr` 后沿用该文本渲染状态,导致同页后续插入的普通文字也成为隐藏文字。复现:真实 worker 对空白页写入 OCR 层,再用 `put_text` 插入黑色 24 pt 的 `VISIBLE TEXT`;接口成功、文本提取包含新增文字,但整页渲染与插入前逐像素相同。违反 PRD §11.1 的共享编辑规则,影响 OCR 后的文字编辑与保存内容。隔离探针 `review_ocr_keeps_later_inserted_text_visible` 失败;在副本中为 OCR 流加入配对的 q/Q 后通过。修复方向:在 OCR 流内部保存、恢复图形及文本状态,并覆盖 OCR 后继续插入文字的像素回归。 + +apps/ely-pdf/src/ocr.rs:254 — 范围字段通过 `on_commit` 更新,点击“识别”时仍可能使用当前字段的旧值,违反 PDF-08 的页面范围选择。复现:真窗口打开两页 PDF,选择 Range,将起始页直接键入 2,保持该字段焦点,再点击 Recognize;AX 和截图均显示 From page=2、To page=2,日志却记录 `OCR of 2 pages`,实际范围仍为第 1 至第 2 页。副本改为同步范围草稿后,同样操作记录 `OCR of 1 pages`。修复方向:识别前读取并校验两端当前草稿,或在输入变化时同步有效范围值;补测直接键入后点击提交的真实输入顺序。 + +apps/ely-pdf/src/ocr.rs:138 — OCR 回调只核对标签、文档版本和页码,连续运行可以共用这三个值,导致取消任务的结果或错误进入新任务。复现:`ELY_TESSDATA` 指向仅含有效英文数据的目录,启动繁体中文识别,立即取消并启动英文识别;旧任务的语言加载错误将新任务的 `ocr` 清空,新任务最终写入次数为 0。真实 worker 应用探针 `review_cancelled_run_cannot_abort_a_replacement_run` 失败,加入独立运行标识后通过。修复方向:每次识别分配独立标识,逐页回调核对该标识,并丢弃已取消运行的成功和失败结果;补测取消后立即重启。 + +crates/ely-pdf-engine/shim/ely_ocr.c:51 — 多语言识别接受 Tesseract 的部分语言加载成功,遗漏所选语言时仍向应用返回成功,违反 PDF-08 的语言选择及 agents.md 的错误暴露规则。复现:数据目录只有有效 `eng.traineddata`,调用 `ocr_page(..., "eng+chi_sim", ...)`;stderr 报 `Failed loading language 'chi_sim'`,API 却返回包含 `Hello world` 的 `Ok`。应用默认选择英文和简体中文,也会进入该路径;现有坏语言测试只覆盖所有语言都加载失败。隔离探针 `review_each_requested_ocr_language_is_required` 失败。修复方向:逐一校验所选语言数据,并确保识别初始化完整加载所选语言;部分加载失败必须向应用报错,补测多语言中一项缺失或损坏。 + +crates/ely-pdf-engine/shim/ely_meta.c:137 — 附件枚举和按名读取、删除仅覆盖 EmbeddedFiles 名字树,遗漏页面 FileAttachment 批注的 `/FS` 文件,违反 PDF-07 和 PRD §11.1 的附件查看与管理。复现:打开一页有效 PDF,页面包含 FileAttachment 批注,其 Filespec 指向内容为 `notes` 的嵌入流,文档没有 EmbeddedFiles 名字树;`marks` 正常列出 FileAttachment,`attachments` 返回空列表,附件栏显示空状态,保存附件路径也无法取得该文件。隔离探针 `review_file_attachment_is_listed` 失败;TASKS.md 所列未验证限制在本轮已复现。修复方向:把页面批注里的嵌入文件纳入附件枚举、读取和移除,并以实际文件身份区分重名附件;补测仅有批注附件的 PDF。 + +crates/ely-pdf-engine/shim/ely_meta.c:152 — 附件大小直接采用 Filespec 的可选 `/Params /Size`,字段省略时将引擎给出的 0 显示成已知的 0 B,掩盖实际附件数据。复现:有效 PDF 的 EmbeddedFiles 名字树含 `note.txt`,嵌入流有 5 字节 `notes`,省略 `/Params /Size`;`attachment` 返回这 5 字节,`attachments` 却返回 `size=0`,界面显示 0 B。隔离探针 `review_optional_attachment_size_uses_actual_length` 失败,影响 PDF-07 附件查看,并违反缺失数据显式处理规则。修复方向:通过实际嵌入流确定大小,覆盖省略大小参数与实际空附件的区别。 diff --git a/tasks/reviews/C11.5-r2.md b/tasks/reviews/C11.5-r2.md new file mode 100644 index 0000000..60245f0 --- /dev/null +++ b/tasks/reviews/C11.5-r2.md @@ -0,0 +1,3 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_meta.c:269 — 读取附件会清空文档中共享的嵌入流缓冲区,违反 PDF-07 与 PRD §11.1 的附件管理、保存规则。复现:添加含 5 字节 `notes` 的 `note.txt`,首次 `attachment(doc, "n:note.txt")` 返回原字节,第二次成功返回空字节;首次读取后保存并重新打开,附件内容也为空,文件中 `/Length` 已变为 0,附件栏仍依据 `/Params /Size` 显示 5 B,读取过程的历史标记保持原值。应用的“附件另存为”使用同一路径,因此导出一次新附件即可使随后保存的 PDF 丢失该附件内容。真实 worker 探针 `review_reading_new_attachment_keeps_bytes_for_next_read_and_save` 在原实现失败;隔离副本改为复制缓冲区字节后,该探针及附件重名、撤销、保存重开探针通过,恢复原实现后再次失败。现有保存测试只核对附件名称。修复方向:复制 `pdf_load_embedded_file_contents` 返回的字节到独立输出缓冲区,保留文档持有的流内容;回归核对重复读取、导出后保存重开的完整字节及读取前后的历史标记。 diff --git a/tasks/reviews/C11.5-r3.md b/tasks/reviews/C11.5-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C11.5-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C11.6-r1.md b/tasks/reviews/C11.6-r1.md new file mode 100644 index 0000000..6511cd3 --- /dev/null +++ b/tasks/reviews/C11.6-r1.md @@ -0,0 +1,17 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_redact.c:69 — 部分覆盖的矢量对象仍可从输出恢复,违反 PDF-09、§11.4、QA-14。复现:页面内容为 `1 0 0 rg 100 700 200 40 re f`,删除页面坐标 `[90,45,180,100]`;worker 返回 `text=0, light=0, versions=1`,输出仍完整保存原矩形路径。独立探针移除新增的黑色填充后,在删除区域 `[110,60,50,20]` 恢复了 1000 个红色像素。`REMOVE_IF_COVERED` 只处理被完全覆盖的路径,当前检查接受了这次视觉遮盖。修复方向:对相交的矢量内容执行实质删除或重写,并检查输出对象及移除遮盖后的像素。本轮已自行执行 `cargo build -p ely-pdf-engine --bins`、`cargo test -p ely-pdf-engine`(57 个通过)、`cargo test -p ely-pdf`(88 个通过);该失败路径由额外探针检出,证据保留在 `/tmp/c116-probes.log`。 + +crates/ely-pdf-engine/shim/ely_redact.c:60 — 删除表单控件后保留含敏感值的空父字段,违反 PDF-09、QA-14。复现:`/AcroForm/Fields` 指向文本父字段,其 `/V` 为 `SECRET-FIELD-VALUE`,唯一子控件位于 PDF 矩形 `[70,700,220,740]`;删除页面区域 `[60,40,230,100]`。包含默认字体资源和控件外观的样本同样返回全部检查通过,输出保留 `<>`。垃圾回收保留这个仍被 `/Fields` 引用的对象。修复方向:删除控件时清理失去全部控件的字段及空祖先,检查字段值残留,并补充父子字段的输出对象测试。 + +crates/ely-pdf-engine/shim/ely_redact.c:122 — 隐藏文字的扫描与清理遗漏关闭的可选内容组,违反 PDF-10。复现:`/OCProperties/D/OFF` 关闭一组,组内内容为 `/OC /H BDC ... (SECRET-OFF-LAYER) Tj ... EMC`,页面另有可识别的 `3 Tr` 隐藏文字,使界面可以勾选隐藏内容。清理后返回 `hidden=0, scripts=0`,普通文本提取只见可见文字,但输出内容流仍含 `SECRET-OFF-LAYER`;开启该组即可恢复。只有零透明度文字的样本也被扫描成 `hidden=0`,直接清理时保留原文字。修复方向:扫描覆盖可选内容状态及透明度,删除实际隐藏内容,并以独立对象检查验证清理结果。 + +crates/ely-pdf-engine/shim/ely_redact.c:234 — 元数据清理仅覆盖 trailer `/Info` 和 catalog `/Metadata`,页面元数据保留,违反 PDF-10。复现:catalog 和页面各引用一个带 `/Type /Metadata /Subtype /XML` 的有效 XMP 流,页面 XMP 标题含 `SECRET-PAGE-METADATA`。扫描显示 `meta=1`,勾选元数据后清理返回 `meta=0`,输出页面 `/Metadata` 及其敏感标题仍在。修复方向:将页面等实际承载元数据的对象纳入扫描、删除与输出核验,补充页面 XMP 残留测试。 + +crates/ely-pdf-engine/shim/ely_redact.c:245 — 清理附件或隐藏内容时对缺失的 `/Names` 调用字典删除,合法文件因此清理失败。复现:没有 catalog `/Names` 的普通 PDF 含 `3 Tr` 文字 `SECRET-INVISIBLE`;扫描正确报告 `hidden=1`,执行 `Cleaning { hidden: true, ..Default::default() }` 返回 `not a dict (null)`,输出被删除。第 247 行的脚本删除触发同一问题;仅通过页面 FileAttachment 保存附件的文件也会经过第 245 行。修复方向:按可选名称字典的实际存在情况处理删除,继续处理页面内容,并分别测试没有 `/Names` 的隐藏文字与页面附件。 + +crates/ely-pdf-engine/shim/ely_redact.c:271 — 隐藏批注被排除出普通批注计数,同时遗漏于隐藏内容计数,清理界面无法选择它们。复现:文件只有一条 `/Subtype /Text /F 2 /Contents (SECRET-HIDDEN-NOTE)` 批注;扫描返回 `annots=0, hidden=0, scripts=0`。`apps/ely-pdf/src/redact.rs:221` 据此禁用批注和隐藏内容选项;实际直接执行隐藏内容清理能够删去这条批注,证明扫描范围与执行范围失配。修复方向:准确计入隐藏批注并用于界面的范围显示及输出核验,补充仅含隐藏批注的界面与 worker 测试。 + +apps/ely-pdf/src/redact.rs:87 — 清理扫描返回时直接打开原标签的对话框,缺少当前标签及文档版本核对。复现:在标题为 `Plan` 的 A 标签发起清理,扫描返回前切换到 B;真实应用源码的 GPUI 探针得到 `asked=0, active=1, meta=1`,B 的窗口弹出 A 的处理范围,随后保存操作仍清理 A。对话框也没有标明 A 的文件名,违反明确说明处理对象与范围的 UX-07、PDF-10。修复方向:回调核对发起标签、存活状态及版本,丢弃失效扫描,补充切换和关闭标签期间的扫描测试。证据在 `/tmp/c116-ui-probes.log`。 + +apps/ely-pdf/src/redact.rs:154 — 路径选择返回后才读取删除标记,执行范围可偏离先前预览与确认,违反 PDF-09。复现:标记并确认删除 `Hello`,进入 `choose_output` 的路径选择等待;期间将同一标签的标记换成 `Second line`,随后完成路径选择。GPUI 探针实际调用原应用的输出流程,生成文件保留 `Hello world`、删除 `Second line`。`Removal` 只保存标签,当前代码直接接受后来变化的区域。修复方向:确认时绑定标记快照及文档版本,返回后核对其有效性,范围变化时重新预览确认,并测试路径选择期间的标记变化与排队编辑。 diff --git a/tasks/reviews/C11.6-r2.md b/tasks/reviews/C11.6-r2.md new file mode 100644 index 0000000..742966f --- /dev/null +++ b/tasks/reviews/C11.6-r2.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_redact.c:105 — 删除文字后保留标记内容的 `/ActualText`,违反 PDF-09、§11.4、QA-14。复现:页面内容为 `/Span << /ActualText (SECRET-ACTUAL) >> BDC BT /F1 12 Tf 72 700 Td (SECRET-ACTUAL) Tj ET EMC`,删除页面区域 `[60,70,220,110]`。worker 返回 `text=0, light=0, objects=0, versions=1`;独立读取输出内容流仍得到 `/Span<>BDC q EMC Q`,敏感文字可直接恢复。修复方向:删除字形时同步清理对应标记内容的语义文字,并对输出内容流及关联对象增加独立残留检查。本轮自行执行 `cargo build -p ely-pdf-engine --bins`、`cargo test -p ely-pdf-engine`(61 项通过)、`cargo test -p ely-pdf`(90 项通过)、注释检查;上一轮八项对应回归均通过。额外失败探针及独立对象检查记录在 `/tmp/c116-r2-probes.log`,测试日志在 `/tmp/c116-r2-engine.log`、`/tmp/c116-r2-app.log`;669 个项目文件散列保持一致,临时探针及样本已删除,本轮直接调用原树构建的 worker。 + +crates/ely-pdf-engine/shim/ely_redact.c:100 — 处理后一个区域时会删掉此前刚创建的相交 Redact 批注,重叠标记无法完成删除,违反 PDF-09。复现:页面在 PDF 坐标 `(72,700)` 绘制 `SECRET-OVERLAP`,依次标记页面区域 `[60,70,160,110]` 与 `[140,70,220,110]`。第二次 `drop_touching` 删除第一个标记,最终核验返回 `8 characters, 2936 light pixels`,输出文件被清除;用户通过区域工具画出的两块有效标记无法执行。修复方向:先按全部区域清除原有批注与字段,再创建全部删除标记,补充部分重叠区域的 worker 回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:174 — 附件清理只删除名称树入口,关联文件的 `/AF` 引用继续保留附件,违反 PDF-10。复现:PDF 2.0 catalog 的 `/Names/EmbeddedFiles` 与 `/AF [5 0 R]` 同时引用 `invoice.xml` 文件说明字典,其嵌入流含 `SECRET-ASSOCIATED-FILE`。扫描报告 `files=1`;执行仅附件清理后报告 `files=0`,输出的 `/AF`、文件说明字典与原嵌入流仍可读取。修复方向:将关联文件引用纳入附件计数与删除,清理后独立核对可达的文件说明及嵌入流,增加共享名称树与关联引用的回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:104 — 元数据遍历只检查各间接对象的顶层字典,遗漏嵌套直接字典,违反 PDF-10。复现:页面 `/Resources/Properties/P` 为直接属性字典,含 `/Metadata 6 0 R`;页面通过 `/Span /P BDC ... EMC` 使用该属性,6 号对象为有效 XMP,标题含 `SECRET-MARKED-META`。扫描与元数据清理均返回 `meta=0`,输出仍完整保留属性字典、元数据引用与 XMP 标题,界面也会禁用元数据选项。修复方向:遍历直接字典和数组中的元数据入口,核验使用完整对象范围,并增加标记内容属性字典的回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:149 — 脚本扫描与清理遗漏链接批注及字段树父节点,违反 PDF-10 与 agents.md 的隐藏内容清理说明。复现:页面 Link 批注的 `/A` 为 JavaScript,内容含 `SECRET-LINK-SCRIPT`;文本父字段的 `/AA/K` 含 `SECRET-PARENT-SCRIPT`,其子 Widget 位于页面。扫描返回 `hidden=0, scripts=0, hidden_annots=0`;直接执行隐藏内容清理也返回全零,输出保留两段脚本,界面的隐藏内容选项被禁用。修复方向:覆盖原始页面批注数组中的链接和整棵字段树的动作入口,增加输出对象级回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:85 — 动作检查只识别入口自身的 `/S /JavaScript`,遗漏动作链 `/Next` 中的脚本。复现:catalog 的 `/OpenAction` 为合法 GoTo 动作,`/Next << /S /JavaScript /JS (SECRET-NEXT) >>` 串接 JavaScript。扫描与隐藏内容清理均报告 `scripts=0`,输出保留 `SECRET-NEXT`,打开动作仍可触发该脚本,违反 PDF-10 与 agents.md 的脚本清理说明。修复方向:遍历动作链的字典及数组,清除脚本节点并保留其余合法动作,增加带非脚本入口的链式动作回归。 diff --git a/tasks/reviews/C11.6-r3.md b/tasks/reviews/C11.6-r3.md new file mode 100644 index 0000000..f589c6f --- /dev/null +++ b/tasks/reviews/C11.6-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_alt.c:46 — 结构元素的页归属只按自身或祖先的 `/Pg` 判断,遗漏由子节点 MCR 指定页面的合法结构元素;删除字形后其 `/ActualText`、`/Alt` 仍泄露敏感文字,违反 PDF-09、§11.4、QA-14。复现:带 `/MarkInfo /Marked true`、`/StructParents 0` 与完整 `/ParentTree` 的单页 PDF,正文以 `/MCID 0` 绘制 `SECRET-MCR`;结构元素自身省略 `/Pg`,其 `/K << /Type /MCR /Pg 3 0 R /MCID 0 >>` 指向该页,同时带 `/ActualText (SECRET-MCR)` 与 `/Alt (SECRET-MCR)`。删除 `[60,70,220,110]` 后 worker 返回 `text=0, light=0, objects=0, versions=1`,独立读取输出结构树仍得到两份 `SECRET-MCR`。修复方向:结合子节点 MCR、OBJR 和结构祖先判定受删除页面影响的语义元素,清除相关替代文字,并以该结构形式增加输出对象残留回归。 + +crates/ely-pdf-engine/shim/ely_redact.c:106 — 删除页面内容时保留该页原有 `/Thumb`;原始图片经缩略图引用继续可达,紧缩写出与页面绘制核验均放过残留,违反 PDF-09、§11.4、QA-14。复现:100×50 点单页 PDF,通过 `q 100 0 0 50 0 0 cm /Im Do Q` 绘制 2×1 RGB 图片,像素为 `[255,0,0,0,0,255]`,页面 `/Thumb` 引用同一图片对象。标记整页 `[0,0,100,50]` 删除,worker 返回 `text=0, light=0, objects=0, versions=1`;输出页面全黑,`/Resources` 已空,但 `/Thumb 4 0 R` 及原始六个像素字节完整保留。修复方向:删除或重建受处理页的缩略图,核验覆盖页面附属引用中的图片,增加缩略图与页面图片共享对象的回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:66 — 隐藏内容过滤只删字形,留下标记内容的语义文字;清理后的重复计数将此残留报告为零,违反 PDF-10。复现:正文为 `/Span << /ActualText (SECRET-HIDDEN-ACTUAL) >> BDC BT /F1 12 Tf 3 Tr 72 700 Td (SECRET-HIDDEN-ACTUAL) Tj ET EMC`。扫描返回 `hidden=1`;仅勾选隐藏内容清理成功并返回所有类别为零。独立解码输出内容流仍得到 `/Span<>BDC q EMC Q`,原隐藏文字可恢复。修复方向:过滤隐藏字形时同步清理其标记内容和关联结构元素中的语义文字,输出核验增加独立语义残留检查,并补充带 `/ActualText` 的隐藏文字回归。 + +crates/ely-pdf-engine/shim/ely_clean.c:204 — 隐藏批注遍历只走 `pdf_first_annot`,遗漏 Link 与 Widget;扫描与清理共同漏掉它们,界面据零计数禁用隐藏内容选项,违反 PDF-10 和 agents.md 的隐藏批注清理约定。复现一:页面 `/Annots` 含 `/Subtype /Link /F 2`,`/Contents` 与 `/A /URI` 均含 `SECRET-HIDDEN-LINK`。复现二:页面与 `/AcroForm/Fields` 引用 `/Subtype /Widget /F 2 /FT /Tx /V (SECRET-HIDDEN-WIDGET)`。两份文件均扫描为 `hidden=0, scripts=0, hidden_annots=0`;直接请求仅隐藏内容清理也返回全零,输出完整保留隐藏链接或隐藏字段及其内容。修复方向:按原始 `/Annots` 覆盖全部隐藏或不可见批注类型,删除隐藏 Widget 时同步清理字段树与值,补充隐藏 Link、Widget 的扫描及输出对象回归。本轮自行执行 worker 构建、65 项引擎测试、90 项应用测试与范围内行数及注释检查,均通过;上一轮六项原始复现场景的独立重放全部通过。新增失败场景与独立对象检查见 `/tmp/c116-r3-probes.log`,构建及测试日志见 `/tmp/c116-r3-build.log`、`/tmp/c116-r3-engine.log`、`/tmp/c116-r3-app.log`;665 个项目文件散列保持一致,临时探针和 PDF 已在写结论前删除,本轮直接使用原树构建的 worker,清理记录见 `/tmp/c116-r3-audit.log`。 diff --git a/tasks/reviews/C11.7-r1.md b/tasks/reviews/C11.7-r1.md new file mode 100644 index 0000000..699ba7e --- /dev/null +++ b/tasks/reviews/C11.7-r1.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_sign.c:210 — 签名验证列表只遍历页面的 Widget,遗漏 AcroForm 字段树里的不可视数字签名,违反 PDF-12、QA-15。复现:创建 `/AcroForm /Fields` 下含 `/FT /Sig /V`、无页面 Widget 的字段,以测试证书对正确 ByteRange 创建 detached PKCS #7 签名;OpenSSL 独立验证成功,worker 的 `signed()` 返回 1,`signatures()` 返回 `[]`。真实窗口的提示条与状态栏显示已签名,打开 Signatures 后却显示 “No signature fields”。修复方向:按字段树枚举并校验数字签名,将页面与外观作为可选定位信息。证据:`/tmp/ely-c117-review-logs/probes.log`、`native.log`、`native-signatures-ax.txt`,并已人工核对窗口截图。 + +apps/ely-pdf/src/engine.rs:138;crates/ely-office/src/save.rs:265 — 已签文件另存为后,标签切换到已去签名的新文件,内存文档与签名验证结果仍来自原文件,违反 PDF-12、QA-15。复现:打开已签 PDF,读取签名列表,再通过 `write_as()` 另存为 `copy.pdf`;独立 worker 读回新文件得到 `signed=0`,当前标签已经指向新路径且 `risk=None`,签名列表仍含原来的有效签名。应用探针记录 `disk_signed=0, displayed_signed=1, risk=None` 并断言失败;清缓存后重新读取也会查询仍持有原签名的 worker 文档。修复方向:成功另存为时同步标签、引擎文档与签名状态,使验证对象和当前文件一致,并增加保存后重新读取签名的回归。证据:`/tmp/ely-c117-review-logs/app-probes.log`、`probes.log`。 + +apps/ely-pdf/src/sign.rs:292 — 后台签名成功回调通过 `cancel_sign()` 清除当前活动标签的 `sign_at`,操作归属随标签切换而改变。复现:标签 A 发起写出签名副本,完成前切到标签 B 并建立 B 的签名位置;A 完成后 B 的签名位置被清除,A 的位置继续保留,返回 A 会再次出现旧的签名表单。应用探针实际得到 `(A=Some(at), B=None)`,预期为 `(A=None, B=Some(at))`;副本中改为按发起标签及位置清理后,同一探针通过。修复方向:成功回调绑定发起时的标签、视图与签名位置,只关闭对应操作的表单。证据:`/tmp/ely-c117-review-logs/app-probes.log`、`mutation-repair.log`。 + +apps/ely-pdf/src/protect.rs:98 — 所有者密码认证成功后无条件清空 `viewer.secure`,会关闭后来打开的其他文档对话框。复现:在受限标签 A 提交正确所有者密码,等待期间取消旧对话框、切到同样受限的标签 B 并打开 B 的所有者密码框;A 的认证回包清空 B 的框,B 仍受限制。应用探针记录 A 权限为 127、`newer_dialog_survives=false` 并失败;副本中增加发起标签核对后,同一探针通过。修复方向:保留认证发起时的标签与输入框身份,只清理仍对应这次请求的对话框;权限更新继续归属 A。证据:`/tmp/ely-c117-review-logs/app-probes.log`、`mutation-repair.log`。 + +patches/mupdf/0004-signature-reason-and-location.patch:14 — 新增 C 注释含 13 个英文词,另在第 37 行新增 12 词注释,均超过 agents.md 的十词上限。按空白分词可直接复现;`tests/scripts/comments.py` 仍返回 PASS,因为它对 `.patch` 只匹配新增的 `//` 注释。修复方向:将这两条注释缩短到十词以内,并让现有检查覆盖补丁新增的 C 注释。 diff --git a/tasks/reviews/C11.7-r2.md b/tasks/reviews/C11.7-r2.md new file mode 100644 index 0000000..9861574 --- /dev/null +++ b/tasks/reviews/C11.7-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:305;crates/ely-office/src/save.rs:111 — 保存尚未提交到目标文件就清除打开文档的签名,失败会破坏当前验证对象,违反 PDF-12、QA-15。复现:打开已签 PDF,另存为到一个名为 `directory.pdf` 的现有目录;worker 导出成功,外壳写入目标失败,标签仍指向原文件且 `risk=Signed`,内存签名数却由 1 变为 0,独立重开原文件仍为 1。直接向不存在的父目录发 SAVE 同样报错并清除签名。应用探针输出 `path=.../signed.pdf, risk=Some(Signed), memory_signed=0`,保留原签名的断言失败。修复方向:将打开文档的去签名状态与最终目标文件成功提交绑定,导出或原子写入失败时保留原文档、签名和撤销历史;增加两阶段失败回归。证据:`/tmp/ely-c117-r2-logs/app-probes.log`、`probes.log`;副本中仅将去签名移到 worker 写出成功之后,直接 SAVE 失败探针恢复正确状态,外壳提交仍需共同处理。 + +crates/ely-pdf-engine/shim/ely_sign.c:421;crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:76 — 去签名作为一个可撤销的保存步骤加入 journal,撤销会恢复原签名,重做到编辑步骤就提前返回,使已保存副本的验证列表再次读取原文件签名,违反 PDF-12、QA-15。复现:打开已签 PDF,旋转页面,另存为 `copy.pdf`,撤销,再重做;磁盘副本签名数为 0,应用列表却含原签名并报告摘要完整,标签已回到保存点。应用探针输出 `displayed_signed=true, disk_signed=0, dirty=false`,当前文件与验证对象一致性的断言失败;worker 独立探针也在撤销、重做后读到签名数 1。修复方向:让签名状态在保存后的撤销、重做期间持续对应当前文件及实际文档修订,避免保存步骤重新引入已经移除的签名;增加保存后完整撤销、重做循环的验证回归。证据:`/tmp/ely-c117-r2-logs/app-probes.log`、`probes.log`。上一轮新增的保存回归与本轮原有测试均通过,两个失败路径由本轮独立探针复现。 diff --git a/tasks/reviews/C11.7-r3.md b/tasks/reviews/C11.7-r3.md new file mode 100644 index 0000000..cb77cc1 --- /dev/null +++ b/tasks/reviews/C11.7-r3.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_sign.c:309;crates/ely-pdf-engine/src/bin/ely-pdf-worker/sign.rs:106 — 另存为成功后,验证列表读取新文件,后续签名却仍从含原签名的内存文档生成快照,违反 PDF-12、QA-15。复现:打开含 Signature1 的已签 PDF,另存为 copy.pdf;独立读取 copy.pdf 确认该字段为空。随后在新位置用证书签名,输出竟同时含 Signature1 和 Signature2,前者报告 Intact、later=true;对 copy.pdf 已清空的原字段签名则报 `field 7 is signed already`。应用探针实际走 write_as、write_signed,输出 `signed_fields=[("Signature1", Some((Intact, true))), ("Signature2", Some((Intact, false)))]`;原字段探针输出 `disk_empty=true, current_memory_signed=1`。修复方向:在保存提交成功后,让后续签名使用与当前文件一致的快照和字段状态,保留保存失败及撤销重做的正确语义;增加另存为后原字段重签、新字段签名的回归。证据:`/tmp/ely-c117-r3-probes.log`。 + +apps/ely-pdf/src/sign.rs:50 — 验证结果只按路径缓存,文件字节变化后再次打开签名对话框持续使用旧摘要结果,违反 PDF-12、QA-15。复现:核验已签 PDF 得到 Intact,关闭对话框;外部程序在同一路径把正文 Hello 的首字节改为 J,再打开签名对话框。应用仍显示签名后未改动,独立 worker 重开同一文件已报告 Changed。应用探针输出 `displayed=Intact, disk=Changed`,一致性断言失败。修复方向:将缓存绑定实际文件版本,使文件变化后的重新打开触发核验;增加同路径文件被修改后的验证回归。证据:`/tmp/ely-c117-r3-probes.log`。 + +crates/ely-pdf-engine/shim/ely_pages.c:424;crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:304 — 快照写入失败后 MuPDF 的操作与历史状态未恢复,后续真实编辑返回原历史标记,撤销失效。复现:打开已签 PDF,在标签工作目录创建名为 save.pdf.raw 的目录,执行另存为使内核导出失败;移除该目录后旋转页面。应用页面尺寸已变为 792×612,视图却为 `mark=0, saved=0, modified=false`。独立 worker 对不存在的父目录 SAVE 失败后也复现:旋转返回 `Ok("0")`,随后保存成功,撤销报 `nothing to undo`;对照组旋转返回 `Ok("1")` 且撤销成功。原签名保留的上一轮修复已通过,此失败路径仍破坏后续编辑与撤销契约。修复方向:确保快照异常退出时恢复 MuPDF operation/journal 状态,让后续编辑独立进入历史;增加导出失败后继续编辑、保存、撤销的回归。证据:`/tmp/ely-c117-r3-probes.log`、`/tmp/ely-c117-r3-worker-probes.log`。 diff --git a/tasks/reviews/C11.8-r1.md b/tasks/reviews/C11.8-r1.md new file mode 100644 index 0000000..6011e88 --- /dev/null +++ b/tasks/reviews/C11.8-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-office/src/save.rs:236 — 修复文件或已签文件另存成功后,无条件异步 reload 会丢掉保存快照之后的新编辑,违反 FILE-03、QA-08。实测:打开损坏 PDF,开始另存为 fixed.pdf;worker 已生成 work/save.pdf、界面尚未完成目标写入时,排入旋转 90°。另存回调随后关闭原标签并打开旧快照,最终页面尺寸仍为 612×792、dirty=false;应保留旋转后的 792×612 页面及未保存状态。隔离副本的真实 worker 应用探针 review_save_as_keeps_an_edit_queued_after_the_snapshot 稳定失败,证据见 /tmp/ely-c118-save-race.log。修复方向:将重开绑定到发起视图及保存修订,并在实际执行重开前核对已落地和排队中的编辑;保存期间产生的新内容须保留在会话中。 + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:339 — 修复检测仅在 OPEN/UNLOCK 后执行,遗漏读取页树、页面或内容时才发生的 MuPDF 延迟修复,违反 PDF-14。实测:保留正常 startxref,把页树对象 2 的 xref 偏移改为 0000000009;文件可打开并读出页面,MuPDF 输出 repairing PDF document,但应用 risk=None、repair=None,worker 保存报 Can't do incremental writes on a repaired file。独立 IPC 探针还复现了页面、内容及字体对象偏移损坏的同类失败;证据见 /tmp/ely-c118-lazy-repair.log、/tmp/ely-c118-lazy-app.log。修复方向:捕获延迟读取期间的修复诊断,在修复发生后建立可保存的完整副本,并把报告与修复风险同步给应用;回归须覆盖有效 startxref 配合损坏对象偏移。 + +apps/ely-pdf/src/output.rs:334 — 降采样参数只经 NumberInput::on_commit 写入 Exporting,直接点击导出会移除输入框并带走旧参数,违反 PDF-13。真窗口实测:原图片为 600×600、绘制宽度 100 pt;把 Down to 从 150 键入为 72,AX 确认字段值为 72,直接点击 Export… 并保存,输出图片仍为 208×208,对应默认 150 dpi;同样键入后先按 Enter,输出为预期的 100×100。证据见 /tmp/ely-c118-native-result.log、/tmp/ely-c118-native-ax-typed.txt.log、/tmp/ely-c118-native-stderr.log.log。Above 字段采用同一提交路径。修复方向:在导出取走对话框状态前取得两项当前有效输入,并以该值进行参数关系校验和输出;覆盖键入后直接点击导出的真实交互。 diff --git a/tasks/reviews/C11.8-r2.md b/tasks/reviews/C11.8-r2.md new file mode 100644 index 0000000..39f86bb --- /dev/null +++ b/tasks/reviews/C11.8-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_mupdf.c:103 — 打开扫描只缓存对象,恢复判定只采用结构修复标记,内容流读取错误会被当作正常、可覆盖保存的文件,违反 PDF-14 及错误须暴露的规则。具体场景:文件的 xref、对象偏移和流长度均正确,带 /Filter /FlateDecode 的页面内容流为 `not a zlib stream`。真实 worker 输出 `zlib error: incorrect header check`、`read error; treating as end of file`,文字导出却返回成功并生成空文件,修复报告为空。应用探针确认 risk=None、repair=None;旋转后执行 Save,损坏原件从 582 字节变为 593 字节,原件保留断言失败。证据:/tmp/ely-c118-r2-malformed.log、/tmp/ely-c118-r2-corrupt-app.log。修复方向:将内容流解码及页面读取失败纳入加载、恢复风险和错误报告,输出遇读取失败须返回错误;此类损坏会话须保留原件,保存使用独立副本。回归覆盖有效 xref 配合损坏压缩内容流。 + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/output.rs:116 — PRINT_COPY 复用普通保存并保留加密,系统打印端收到锁定副本,违反 PDF-13。具体场景:PDF 设置非空打开密码 `user`、所有者密码 `owner`,仅允许 PRINT;worker 解锁成功、权限为 PRINT,print_copy 返回成功,但生成的 print.pdf 在 PDFKit 中 isLocked=true,创建打印操作返回空,ely_platform::print::print_pdf 因而报 `no print operation`,打印面板无法打开。对照确认普通 PDF 可创建打印操作,同一 print.pdf 用 `user` 解锁后也可创建。证据:/tmp/ely-c118-r2-encrypted-print.log、/tmp/ely-c118-r2-print-control.log。修复方向:通过 PRINT 权限核验后生成仅供打印的已解密临时副本,普通保存继续保留原加密;回归覆盖非空打开密码及 PDFKit 创建打印操作。 diff --git a/tasks/reviews/C11.8-r3.md b/tasks/reviews/C11.8-r3.md new file mode 100644 index 0000000..b616036 --- /dev/null +++ b/tasks/reviews/C11.8-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-pdf-engine/shim/ely_mupdf.c:125 — 损坏扫描跳过图片流,图片解码失败后的普通渲染也返回成功,受损文件仍能通过 Save 覆盖原件,违反 PDF-14 和错误须暴露的规则。具体场景:xref、对象偏移与流长度正确,页面绘制一个 4×4 RGB 图片,其 /FlateDecode 流为 `not a zlib!!`。真实 worker 报 `zlib error: incorrect header check`、`read error; treating as end of file`、`padding truncated image`;修复报告却为 None,图片导出报错,普通渲染和保存成功。应用探针确认 risk=None、repair=None;旋转后执行 Save,原件从 685 字节变为 692 字节,原件保留断言失败。证据:/tmp/ely-c118-r3-final-probes.log;副本中去掉图片流的扫描豁免后,修复报告断言通过(/tmp/ely-c118-r3-image-repair-probe.log)。修复方向:把图片解码失败纳入损坏判定和错误回报;延迟解码时也须在渲染或保存路径更新恢复风险,受损会话保存到独立副本。补充有效 xref 配合损坏图片流的应用保存回归。 + +apps/ely-pdf/src/output.rs:101 — 图片导出在等待排队编辑及路径选择之前捕获页码,执行时继续使用旧页码,缺少文档版本核对;结构编辑后会成功导出另一页,违反 PDF-13。具体场景:三页宽度依次为 100、200、300 点,选择第二页;删除第一页仍在排队时打开导出、选择“所选”、72 dpi,再选输出目录。经实际 on_export、choose_export 和路径选择回调复现:删除完成后,旧索引 1 指向原第三页,生成的 doc-2.png 宽度为 300 像素,所选页应为 200 像素,所选页内容断言失败。证据:/tmp/ely-c118-r3-final-probes.log,r3_a_picked_image_keeps_its_page_across_pending_delete。修复方向:将所选页与发起时的文档版本绑定,在等待编辑完成及路径回调后核对;版本变化时明确拒绝过期请求,或从该版本的快照导出。回归须覆盖排队删页、移动页面以及延迟路径选择。 diff --git a/tasks/reviews/C13.1-r1.md b/tasks/reviews/C13.1-r1.md new file mode 100644 index 0000000..4bc0b11 --- /dev/null +++ b/tasks/reviews/C13.1-r1.md @@ -0,0 +1,15 @@ +FAIL + +apps/ely-tree/src/work.rs:59 — 暂存已暂存重命名文件的后续编辑会失败。场景:提交 b.txt,执行 `git mv b.txt c.txt`,继续修改 c.txt,再点“改动”分区中 c.txt 的暂存按钮。生成的 `git add --all -- c.txt b.txt` 返回 `fatal: pathspec 'b.txt' did not match any files`(退出码 128),新编辑仍留在工作区。真实 Git 探针及真窗口 AXPress 均复现;现有 15 项 ElyTree 测试全部通过,却漏掉此 RM 状态。修复方向:按操作和当前索引决定旧路径;已暂存重命名的后续暂存使用当前路径,取消暂存保留其所需的旧路径,并补此场景的回归测试。 + +apps/ely-tree/src/work.rs:36 — 首个提交前,文件暂存后继续编辑会导致取消暂存失败;全部取消的第 49 行分支同样受影响。场景:初始化仓库,写入并暂存 a.txt,再修改其工作区内容,执行取消暂存。当前 `git rm --cached -r -q -- a.txt` 返回 `staged content different from both the file and the HEAD`(退出码 1),索引保留原内容。真实 Git 探针复现 AM 状态;现有测试仅覆盖索引与工作区内容相同的情况。修复方向:显式取消暂存时允许移除首提交前内容不同的索引条目,保持工作区文件,并覆盖单文件及全部取消暂存。 + +apps/ely-tree/src/watch.rs:27 — 链接 worktree 的监听范围遗漏共享 Git 目录,外部引用更新无法触发重读,违反 TREE-02 和 PRD §13.3。场景:仅打开链接 worktree;在主仓库执行 `git update-ref refs/heads/linked SECOND`。引用写入主仓库的 `.git/refs`,当前监听仅覆盖链接工作区与 `.git/worktrees/linked`。窗口探针等待 3 秒后仍持有旧 HEAD;直接调用 `status::read` 已读到 SECOND。修复方向:解析并监听实际的 `--git-common-dir`,同时保留 worktree 私有 Git 目录监听,并增加共享引用更新的窗口回归测试。 + +apps/ely-tree/src/repo.rs:111 — 克隆输出按 CR 单独切块,使 LF 结尾的完整进度行积压。场景:克隆到一个接受连接后持续等待的 `git://` 服务。Git 已输出 `Cloning into '…'...\n`,`follow` 的回调在等待期间保持为零;停止后流关闭才交付该行。真实 Git 探针等待 800 毫秒并复现,当前测试仅在克隆结束后检查输出。修复方向:同时识别 CR 和 LF 并立即交付完整行,传播读取错误,补充克隆进行期间的输出断言。 + +apps/ely-tree/src/status.rs:25 — 缺失的上游比较计数被默认零值掩盖,界面错误显示同步状态。场景:设置 origin/main 为上游后删除 `refs/remotes/origin/main`。Git 的 porcelain 保留 `branch.upstream`、省略 `branch.ab`,`git branch -vv` 显示 `[origin/main: gone]`;当前 Head 与上游存在且同步时完全相同,真窗口仍显示 `origin/main ↑0 ↓0`。修复方向:保存计数是否可用的状态,第 25、26 行的零值仅代表实际收到的零计数;`view.rs:85` 按此状态呈现上游,并补充上游引用删除的回归测试。 + +apps/ely-tree/src/view.rs:109 — 界面缺少当前 Git 可执行文件路径,未满足 PRD §13.3 的“显示当前路径和版本”。场景:使用随附 Git 或通过 ELY_GIT 选择系统 Git,状态栏只呈现版本与来源;左侧路径属于工作区,应用内没有所选 Git 路径的入口。真窗口核对与渲染代码一致,Git.path 仅在启动日志中出现。修复方向:在当前 Git 的界面信息中呈现实际使用的可执行文件路径。 + +/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/git/changes.rs:24 — 4f8f3bd 新增的 Changed::lines 注释包含 15 个英文单词,超过 agents.md 的十词上限。场景:对新增注释按空白分词,`Added and removed; None when not counted, as for a binary file or a folder.` 得到 15 词。修复方向:将该注释缩短到十词以内,保持单行。 diff --git a/tasks/reviews/C13.1-r2.md b/tasks/reviews/C13.1-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C13.1-r2.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C13.2-r1.md b/tasks/reviews/C13.2-r1.md new file mode 100644 index 0000000..9de7001 --- /dev/null +++ b/tasks/reviews/C13.2-r1.md @@ -0,0 +1,17 @@ +FAIL + +apps/ely-tree/src/pick.rs:113 — 异步差异结果只核对 serial,每次 show 又把 serial 归零(:56),不同文件的首读都会得到 1。复现:延迟 a.txt 的读取,切到 b.txt 并等它显示,再放行 a.txt;真窗口 AX 行从“− before-b / + after-b”变成“− before-a / + after-a”,当前选择仍是 b.txt,违反 TREE-03、TREE-04。隔离 GPUI 延迟回调探针同样失败。修复方向:读取序号在窗口生命周期内保持唯一,并在回调核对发起时的仓库、路径、分区与读取身份;补上跨文件延迟完成的回归。 + +apps/ely-tree/src/diff.rs:197 — 把未选的删除行转成上下文时,照搬其无结尾换行标记,随后写入所选新增行会把两行拼接。复现:索引为 "one\ntwo",工作树为 "one\nTWO\nmore\n",只选 +more 暂存;Git apply 成功,实际索引变成 "one\ntwomore\n",所选独立行被合入保留的 two 行。原有无结尾换行测试只覆盖整对替换;补充探针失败。修复方向:按切片后两侧的实际行边界生成换行标记,保留 EOF 行后追加所选行时正确生成分隔换行,并覆盖暂存与反向取消暂存。 + +apps/ely-tree/src/work.rs:74 — 局部补丁的 apply 参数继承 apply.whitespace,用户配置能静默改写精确比较中选中的内容。复现:git config apply.whitespace fix,把 a.txt 从 "one\ntwo\n" 改成 "one\nTWO \n",选择 -two 与 +TWO 的两行暂存;操作成功,索引得到 "one\nTWO\n",两个尾随空格消失,违反 TREE-04 的可验证行选择。补充探针直接读取索引确认失败。修复方向:为局部补丁显式固定保留所选字节的 whitespace 行为,并增加带用户 apply.whitespace 配置的索引字节断言。 + +apps/ely-tree/src/shown.rs:190 — 通过差异输出判断 UTF-8 后,对旧 blob 使用 from_utf8_lossy;差异上下文以外的非法字节被替换成 U+FFFD,文件仍进入文本视图并开放局部暂存。复现:提交首行为 0xff、后续为 2 至 30 的文本文件,再仅追加 last 行;Git 输出的末尾差异是有效 UTF-8,shown::read 返回 Found::Text,展开首段显示替换字符。补充探针失败,违背 agents.md C13.2 对非 UTF-8 文件显示两侧大小并整体暂存的约定。修复方向:验证实际两侧内容的编码,以原始字节判断文件类型;任一侧含非法 UTF-8 时显示二进制大小并限制为整份操作,覆盖非法字节位于差异之外的场景。 + +apps/ely-tree/src/shown.rs:80 — 机器差异读取继承 diff.suppressBlankEmpty,但 parse 要求每条上下文行具有前导空格。复现:提交 "one\n\ntwo\n",改为 "one\n\nTWO\n",设置 git config diff.suppressBlankEmpty true;点击文件得到“无法读取此差异”,错误为 an unread diff line "",普通文本差异与局部暂存均不可用。补充探针失败。修复方向:显式规范 Git 的补丁输出格式,或兼容该配置生成的空上下文行,并确保后续补丁恢复规范格式;补上配置回归。 + +apps/ely-tree/src/shown.rs:220 — 空旧文件经 split('\n') 被算成一个空白行,生成文件中实际不存在的折叠行及行号。复现:提交空的 a.txt,再写入 "hello\n";stretches 返回新增 hello 的块以及一个 Folded 行,后者 old=1、new=2、text="",界面可展开虚构的“1 行未改动”。补充探针断言实际行数失败,违反 TREE-03 的差异内容和 C13.2 行号约定。修复方向:空 blob 按零行处理,增加空旧文件的行数与展开内容断言。 + +../Ely-GPUI-Components/src/git/viewer.rs:513 — 指定组件版本 2e080b8 的此文件实测为 513 行,超过项目和本轮审查要求的 500 行上限。复现:wc -l src/git/viewer.rs 输出 513,渲染实现本身已延伸到 :503。修复方向:将现有实现整理至每个文件最多 500 行,并以实际行数校验。 + +../Ely-GPUI-Components/src/git/diff.rs:163 — 本次组件改动引入的注释为 11 个词,超过本轮要求的十词上限;同批 src/git/tests.rs:155、:216 的新增注释也各为 11 个词。复现:按空白分词,分别得到 “Marks the words each removal and the addition beside it changed.”、“Past the two layout segments: the hunk's button, then its lines.”、“Each row is a stop; its icons stay hidden until hover.”,均为 11。修复方向:将这三条注释各缩至十词以内,并把组件改动纳入注释长度检查。 diff --git a/tasks/reviews/C13.2-r2.md b/tasks/reviews/C13.2-r2.md new file mode 100644 index 0000000..9b9c7b3 --- /dev/null +++ b/tasks/reviews/C13.2-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-tree/src/diff.rs:219 — 反向切片保留了所选删除行的无末尾换行标记,即使恢复后的该行后面还有保留行,导致取消暂存时静默拼接内容,违反 TREE-04。复现:HEAD 为 "one\ntwo",索引与工作树为 "one\nTWO\nmore\n",只选 −two 取消暂存;Git apply 返回成功,索引变成 "one\ntwoTWO\nmore\n",预期为 "one\ntwo\nTWO\nmore\n"。应用路径回归探针与真窗口均复现;上一轮新增的追加行回归通过。修复方向:所选恢复行也按切片后实际行边界生成换行标记,覆盖单选删除行及保留新增行的反向场景,断言索引字节。 + +apps/ely-tree/src/diff.rs:140 — 解析器把一个路径的差异当作单段补丁,遇到文件类型变化时在第二个文件头报错,普通文件与符号链接之间的差异无法显示,违反 TREE-03。复现:提交内容为 "one\ntwo\n" 的 a.txt,再把它替换为指向有效 other.txt 的符号链接;点击改动区 a.txt,Git 输出同一路径的删除与新增两段,解析器返回 an unread diff line "diff --git a/a.txt b/a.txt",真窗口记录同一读取错误。应用路径回归探针失败。修复方向:识别同一路径类型变化的多段输出,正确呈现两侧内容并采用整份操作,补上改动区与已暂存区的回归。 + +../Ely-GPUI-Components/src/git/rows.rs:26 — e226d79 新拆出的文件保留了 14 词注释,超过本轮明确的十词上限。复现:按空白分词,"A folded stretch: its place among the stretches and how many lines it hides." 得到 14;上一轮指出的三条注释已各缩至十词以内,viewer.rs 当前为 431 行。修复方向:将此注释缩至十词以内,并让组件注释检查覆盖新拆出的文件。 diff --git a/tasks/reviews/C13.2-r3.md b/tasks/reviews/C13.2-r3.md new file mode 100644 index 0000000..23b8c29 --- /dev/null +++ b/tasks/reviews/C13.2-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-tree/src/shown.rs:294 — 文件类型变化的两段补丁共用折叠行游标,第二段新增块把 next 重置为 1,已删除的旧内容再次被显示成未改动行,违反 TREE-03、04。复现:HEAD 的 a.txt 为 "one\ntwo\n",将其换成指向 other.txt 的符号链接;改动区与已暂存区都在删除块和新增块后生成 "2 unchanged lines",展开后出现 one、two,右侧行号为 2、3,而 Git 新侧只有一行 other.txt。应用路径探针与真实窗口均复现;现有类型变化回归在 tests/diffs.rs:417 过滤了 Folded,仍然通过。修复方向:按文件段保存两侧边界并计算折叠区,覆盖类型变化时全部展示行与行号,确认已删除内容仅出现在删除块。 + +apps/ely-tree/src/shown.rs:194 — 工作树侧用 fs::read 跟随符号链接,以目标文件内容判定链接差异,导致合法链接差异读取失败或被错误分类,违反 TREE-03。复现:提交普通 a.txt 后将其替换成指向 other.txt 的符号链接,other.txt 缺失时,Git diff 正常给出删除与新增两段,点击改动区 a.txt 却显示 "This diff could not be read / No such file or directory";other.txt 含非 UTF-8 字节时,链接路径本身为 UTF-8,界面仍把其文本差异归为二进制。应用路径探针覆盖两种情形,真实窗口确认缺失目标的读取错误;已暂存区通过 cat-file 读取链接 blob,能正常显示。修复方向:按工作树文件类型读取 Git 比较的对象,符号链接读取链接路径字节,并覆盖缺失目标及非 UTF-8 目标的回归。 diff --git a/tasks/reviews/C13.3-r1.md b/tasks/reviews/C13.3-r1.md new file mode 100644 index 0000000..5f3089c --- /dev/null +++ b/tasks/reviews/C13.3-r1.md @@ -0,0 +1,17 @@ +FAIL + +apps/ely-tree/src/tree.rs:107 — 提交草稿与 amend 状态由所有仓库共用,标签切换直接改变 active(view.rs:60),随后 committing.rs:80 把共用状态用于当前仓库。场景:在 A 勾选修改最近提交,等待读完,再切到干净的 B 点击提交;隔离窗口探针确认 B 的 HEAD 被改写,提交信息变成 A 的信息。真窗口还确认 A 的迟到读取会填进 B 的 Summary。违反 TREE-05 的提交对象一致性。修复方向:按仓库保存提交框、草稿与 amend 目标,切换时恢复对应状态,并核对读取所属仓库;补充双仓库 amend 回归。 + +apps/ely-tree/src/committing.rs:58 — amend 读取缺少请求序号与取消校验,每次成功回调都会填入信息并设 amend=true。场景:最近提交读取较慢时连续点击两次勾选框,放行第一次读取后取消勾选,再放行第二次读取;真窗口 AX 从取消后的 Commit to master 变回 Amend master,Summary 再次成为 A commit。隔离探针 set_amend(true)→set_amend(false) 同样被迟到回复重新开启。修复方向:保存发起时的草稿,为读取分配序号,取消时使待处理请求失效,回调核对当前请求与用户选择;测试重复点击和取消期间的迟到回复。 + +apps/ely-tree/src/work.rs:243 — 提交完成调用 committed(),committing.rs:88 无条件清空当前提交框,丢失提交期间新增的输入,也会清空用户切换到另一仓库后输入的草稿。场景:提交信息为 submitted,提交尚在执行时把摘要改为 next draft;隔离窗口探针确认 Git 保存 submitted,完成后摘要却成为空串。修复方向:提交请求携带所属仓库和草稿修订,只清理此次已提交且仍保持同一修订的输入;补充慢 hook 期间继续键入及切换仓库的测试。 + +apps/ely-tree/src/look.rs:186 — Blame 回调仅比较路径,文件历史回调(75)同样只比较路径,文件历史差异回调(141)只增加列表下标校验,均遗漏仓库与读取实例。场景:延迟 A/clean.txt 的读取,切到 B 并打开同名文件,先显示 B 的结果再放行 A;真实 Git、真窗口 AX 确认 Blame 的 B commit 被 A commit 替换,文件历史也从 B commit 变成 A commit。两个隔离窗口探针分别复现。违反 agents.md:181 声明的仓库、提交与文件核对。修复方向:三个回调均核对仓库和面板实例序号,差异读取再核对提交身份;关闭、重开或换仓库时使旧请求失效,增加迟到结果回归。 + +apps/ely-tree/src/work.rs:85 — commit -F - 沿用 commit.cleanup,用户配置 strip 时会删掉提交说明中以 # 开头的正文,与 agents.md:181 的保留承诺冲突。场景:设置 commit.cleanup=strip,输入 probe\n\n# required body 并提交;随附 Git 2.56.0 的隔离探针读回的信息只有 probe。原有提交测试只覆盖默认清理设置。修复方向:明确采用保留正文 # 行的 cleanup 模式,并覆盖用户已配置 strip 的提交与 amend。 + +apps/ely-tree/src/pane.rs:253 — 历史提交的文件差异走 view_only(),其 headless() 路径提供的操作缺少文件历史和 Blame;这两个功能的唯一入口位于改动文件的头部(117)。场景:打开只有已提交 clean.txt 的干净仓库,在 History 选提交、再选 clean.txt;真窗口截图与 AX 确认文件差异已显示,所有菜单和该文件界面均缺少 File History / Blame,用户须先修改文件才能访问 TREE-06 功能。修复方向:在已有提交文件列表或差异中接入文件历史和适用的 Blame 操作,覆盖干净仓库的实际入口测试。 + +apps/ely-tree/src/hist_pane.rs:70 — 提交详情的 DescriptionList 和文本节点缺少 AX 文本语义,作者、完整提交号、签名、引用与说明落不到辅助技术;组件 src/git/commits.rs:115 的引用标签也只参与绘制。场景:真窗口选择 Native history fixture,再选择 clean.txt;截图显示 Review 、完整提交号、Not signed、HEAD → master,AX 树仅包含提交摘要与 clean.txt,以上字段均缺失。违反 UX-12,也与 agents.md:181 声明的 AX 提交详情和引用验证冲突。修复方向:给详情与引用提供对应的名称、角色、文本和状态,使用真实 AX 对具体字段值断言;当前 ElyTree 43 项与组件 Git 27 项测试全部通过,未覆盖这些缺失。 + +../Ely-GPUI-Components/src/git/blame.rs:68 — 范围内注释超过十词:此处为 40 词,blame.rs:17、27、39、80 分别为 21、12、18、11 词;commits.rs:17、30、188 分别为 18、20、26 词;changes.rs:19、40、62、101、127、333、338 分别为 14、11、35、12、13、12、30 词。按空白分词的源码检查直接复现,违反本轮明确要求的单行十词上限。修复方向:压缩这些注释至十词以内,保留必要说明。 diff --git a/tasks/reviews/C13.3-r2.md b/tasks/reviews/C13.3-r2.md new file mode 100644 index 0000000..6a0ad3d --- /dev/null +++ b/tasks/reviews/C13.3-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-tree/src/committing.rs:95 — R1 的 amend 取消修复仍遗漏实际复选框的等待状态:发起读取后 amend 保持 false,side_log.rs:52 用这个值重建复选框,直到成功回调(112)才设为 true。场景:延迟 log -1 --format=%B,连续按两次“修改最近一次提交”,再放行读取;真窗口日志记录两次 checkbox on,AX 的按钮从“提交到 master”变成“修改 master 的最近提交”,摘要被填入最近提交信息。独立探针同样得到 amend=true;现有 unticking_amend_voids_a_read_on_its_way 直接调用 set_amend(false),绕过了控件始终传 true 的路径。违反 TREE-05 的取消行为及 agents.md:181 的取消承诺。修复方向:立即记录并绘制用户的勾选与待加载状态,第二次点击使请求作废;提交就绪状态绑定已完成的读取,补充真实控件连续点击的回归。 + +apps/ely-tree/src/pane.rs:243 — 历史提交与文件历史的只读差异缺少正文 AX 文本。view_only() 使用的 DiffViewer 仅在提供 on_line 时为新增、删除行设置 role 和 aria_label,当前调用生成的行只有 id;块头、行号和正文均留在绘制树。场景:干净仓库含 clean.txt,内容为 clean 换行;在历史选根提交,再选该文件及其文件历史提交。真窗口截图显示 @@ -0,0 +1,1 @@ 与 + clean,AX 树只有提交详情、文件名和操作按钮,缺少差异内容。违反 UX-12,也使 agents.md:181 的 AX 差异验证声明缺少对应文本证据。修复方向:组件的只读差异行也提供行号、变更类型和正文语义,应用继续通过共用组件展示;增加真实 AX 对具体差异行文本的断言。 + +../Ely-GPUI-Components/src/git/blame.rs:256 — BlameView 的正文 StyledText 与行号缺少 id、role 和 AX 文本;只有每段首行的作者按钮进入 AX。场景:从 clean.txt 的历史差异按“逐行追溯”,截图显示第 1 行 clean,真实 AX 仅有“Review, 2026-10-07 02:01 · Native history fixture”的按钮,缺少 clean 和行号,辅助技术无法读取逐行追溯的文件内容。违反 UX-12。ElyTree 48 项、组件 Git 27 项测试均通过,现有 Blame 键盘测试只断言提交回调。修复方向:为每个可见正文行提供行号、内容及归属语义,并以真实 AX 校验具体行文本。 diff --git a/tasks/reviews/C13.3-r3.md b/tasks/reviews/C13.3-r3.md new file mode 100644 index 0000000..f7b0a66 --- /dev/null +++ b/tasks/reviews/C13.3-r3.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-tree/src/hist.rs:191 — 历史读取期间的外部更新会永久漏掉:refresh_log 在 hist.read=false 或 hist.loading=true 时直接返回,读取完成后也没有补读。场景:真窗口首次打开历史,用包装器保留随附 Git 已生成的旧 log 输出;等待期间从终端创建 External while history loads,确认 watcher 的状态读取已经完成,再放行旧输出。Git 有两条提交,AX 历史列表持续只有 Native history fixture,master 引用也仍附在旧提交上;再次切换历史页仍保持旧列表。违反 TREE-06 与 agents.md:181 的外部引用变化即重读承诺。修复方向:记录读取期间收到的刷新请求,在当前读取结束后重新核对引用并从头读取;增加慢历史读取与外部提交交错的回归。 + +apps/ely-tree/src/hist.rs:202 — 引用刷新失败只写日志,用户继续看到旧历史,缺少错误反馈。场景:历史已显示后,在隔离仓库的 .git/refs/heads/broken 写入 broken-ref;真实 Git show-ref --head 返回 128,日志包含 fatal: git show-ref: bad ref refs/heads/broken,真窗口 AX 仍只有原历史内容,用户界面没有对应错误。违反项目“出错就暴露”规则。修复方向:把引用读取失败记录到该仓库的历史错误状态并通知界面,保留 Git 的原话;覆盖失败及修复引用后的恢复。 + +apps/ely-tree/src/tree.rs:395 — 外部 Git 配置变化触发状态重读时,提交身份与签名缓存保持旧值;read_me 仅在打开仓库和本应用写配置成功后调用。场景:真窗口显示 Review ,从终端设置 user.name=ChangedIdentity、commit.gpgsign=true、gpg.format=ssh;日志确认配置变化后的状态与引用读取完成,提交框仍显示 Review,身份对话框也填入 Review。随后 Git 提交使用新的身份与签名设置,界面给出的提交身份已经失真。违反 TREE-05 与 agents.md:181 的实际 Git 设置展示。修复方向:配置变化后使身份与签名缓存失效并在后台重读,核对读取版本;增加窗口内外部配置变化的回归。 + +apps/ely-tree/src/committing.rs:166 — 身份对话框的姓名、邮箱 TextInput 没有无障碍名称;asks.rs:95 的 DescriptionList::item 只绘制旁边的标签。场景:真窗口点击提交身份,AX 中两个输入框分别只有 value=Review 和 value=review@example.test,title 与 label 均为空,“姓名”“邮箱”也未进入 AX;屏幕阅读器无法识别两个字段的用途。违反 UX-12。现有身份测试仅核对 Git 配置值,窗口测试未覆盖此对话框。修复方向:给两个 TextInput 设置对应的本地化 label,并以真实 AX 断言字段名称与值。 + +../Ely-GPUI-Components/src/git/viewer.rs:36 — 范围内仍有超过十词的注释:viewer.rs:36、126、137 分别为 41、18、11 词;src/i18n/builtin.rs:1、3、173 分别为 16、13、20 词。按空白分词的源码检查直接复现,违反本轮明确要求的单行十词上限。修复方向:把这些注释压缩至十词以内。 diff --git a/tasks/reviews/C13.4-r1.md b/tasks/reviews/C13.4-r1.md new file mode 100644 index 0000000..b8e590d --- /dev/null +++ b/tasks/reviews/C13.4-r1.md @@ -0,0 +1,15 @@ +FAIL + +apps/ely-tree/src/conflict.rs:143:保存符号链接冲突会覆盖链接目标,破坏其他文件。复现:三个索引阶段分别是指向 `base-target`、`ours-target`、`theirs-target` 的符号链接;合并冲突后,结果编辑器显示 `ours-target`,改为 `theirs-target` 并保存。实测 `ours-target` 文件内容从 `keep target\n` 变成 `theirs-target`,冲突链接仍指向 `ours-target`。`std::fs::write` 跟随了工作树链接;当前冲突模型还丢弃了索引模式。修复方向:保留各阶段的文件模式,符号链接冲突按链接对象解决,保存时保护链接目标。增加真实符号链接冲突的内容与模式断言。 + +apps/ely-tree/src/conflict.rs:143:冲突保存绕过仓库写队列,违反 PRD §13.3 的串行规则。真实窗口复现:`post-checkout` hook 创建进入标记后等待释放标记;编辑结果为 `review saved result`,点击“用当前侧的文件”,等 hook 进入后点击“保存”。释放标记仍不存在、子进程仍执行 `git checkout --ours -- a.txt` 时,工作树文件已变为 `review saved result`。保存与正在执行的 Git 写操作重叠,最终文件取决于执行时序,关闭标签的忙碌判断也覆盖不到保存。修复方向:将结果保存纳入同仓库写队列,并在保存完成时按发起的仓库、路径和结果更新保存状态。 + +apps/ely-tree/src/moves.rs:95:任意进行中操作都会让 Git 失败原话被“因冲突停下”提示覆盖,违反 PRD §13.3 的 hook 错误可见要求。复现:开始有冲突的 merge,解决并 `git add` 全部冲突,安装输出 `REVIEW_HOOK_DENIED` 并退出 1 的 `pre-commit` hook,再点击“继续”。探针确认索引已经解决全部冲突,`MERGE_HEAD` 仍存在,应用没有错误对话框,hook 原话被丢弃。修复方向:根据实际未合并索引判断冲突停顿,并展示继续操作的 hook、签名及锁等失败输出;增加解决冲突后继续失败的测试。 + +apps/ely-tree/src/shown.rs:173:非 UTF-8 冲突进入普通 `Binary` 视图,丢失冲突解决操作。复现:两分支分别修改包含 NUL 与 `0xff` 的 `asset.bin`,merge 停在冲突后选中文件。真实窗口只显示 `Binary file` 和 `— → —`,提供的按钮为文件历史、Blame、打开文件;整文件取当前侧、取传入侧和标为已解决等控件均缺失。`pane.rs:184` 仅为 `View::Conflict` 打开冲突面板。这违背 TREE-09 和 agents.md C13.4“二进制显示,整份取一侧”的声明。修复方向:为二进制冲突保留阶段存在性和冲突身份,提供整文件侧选择与明确解决动作;覆盖真实二进制及修改/删除冲突。 + +apps/ely-tree/src/conflict.rs:112:独立计算的冲突块与 Git 标记块分组不同,会拒绝未经手改的块选择。复现:base 为 `first\na\nunchanged\nb\nlast\n`,两侧分别将 `a`、`b` 改成 `OURS a/b`、`THEIRS a/b`。随附 Git 2.56.0 将两处冲突合为一个标记块,`Merging::new` 经组件 `regions` 计算出两个冲突;实测 `model_conflicts=2 git_markers=1`。真实窗口显示两个选择区,点击任一“Take theirs”都提示结果被手改,结果保持原样,违反 TREE-09 的块级选择要求。修复方向:以 Git 实际冲突块建立展示与选择的对应关系,避免用另一套合并分组直接按序映射;加入邻近冲突与 diff3 分组测试。 + +apps/ely-tree/src/branches.rs:93:把 `%(refname:short)` 直接作为分支/标签操作名称,导致合法的同名引用无法操作。复现:创建分支 `topic` 和标签 `topic` 后,Git 返回 `heads/topic`、`tags/topic`,应用原样展示并传入命令。随附 Git 实测 `switch heads/topic` 报 `a branch is expected`,`branch -m heads/topic new`、`branch -d heads/topic` 和 `tag -d tags/topic` 分别报引用不存在,违反 TREE-07。修复方向:按引用类型从完整 `refname` 取得实际名称,合并/变基等修订定位保留完整引用身份;覆盖分支、标签及远程引用的名称冲突。 + +apps/ely-tree/src/side_refs.rs:136:分支列表只绑定 `on_pick`,组件 829ef68 的 `src/git/refs.rs:363` 却始终绘制非当前分支的切换与删除按钮,按钮没有处理器。真实窗口复现:创建普通分支 `plain`,点击该行的 `Switch to it`,HEAD 保持 `master` 且没有 Git 命令;点击行内 `Delete`,分支保留且没有确认对话框。选择该行后点击详情区 `Switch To`,HEAD 成功变为 `plain`,证明行内控件未接通。修复方向:将行内动作接到对应切换及逐次确认删除入口,或让组件按已提供的处理器呈现动作;测试应实际点击每个动作并断言仓库变化与确认行为。 diff --git a/tasks/reviews/C13.4-r2.md b/tasks/reviews/C13.4-r2.md new file mode 100644 index 0000000..494cf64 --- /dev/null +++ b/tasks/reviews/C13.4-r2.md @@ -0,0 +1,13 @@ +FAIL + +/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/git/merging.rs:144:冲突解析固定匹配七个标记字符,并把任意 `=======` 前缀行当作分隔符,违反 TREE-09,且会丢失所选侧的内容。真实 Git 与窗口探针复现:`.gitattributes` 设置 `a.txt conflict-marker-size=5`,Git 写出一个冲突块,界面得到零个块;直接“标为已解决”将仍带五字符标记的文件暂存,整个过程没有剩余标记确认。设置宽度为 10,传入侧为 `theirs-top\n======= literal\nTHEIRS-END\n`,点击取传入侧后结果仅剩 `THEIRS-END\n`。修复方向:按 Git 实际标记宽度及完整标记行结构解析,展示、块选择和剩余标记确认共用同一判定,并保留正文中的相似字符行。 + +apps/ely-tree/src/moves.rs:95:只要索引仍有未合并条目,就丢弃所有 Git 写操作的错误原话,上一轮 hook 错误可见性的修复仍有遗漏。真实 macOS 窗口复现:保持文本合并冲突,安装输出 `REVIEW_POST_CHECKOUT_DENIED` 并退出 1 的 `post-checkout` hook,点击“用当前侧的文件”;`git checkout --ours -- a.txt` 返回 1,应用只提示因冲突停下,错误对话框与 hook 原话均缺失。解决全部冲突后继续时的 `pre-commit` 拒绝回归已通过。修复方向:结合失败命令及实际失败原因判断冲突停顿,保留 hook、锁、签名及其他失败的完整输出;覆盖仍有未合并条目时的失败路径。 + +apps/ely-tree/src/conflict.rs:97:以 UTF-8 解码成功作为文本冲突的依据,含 NUL 的二进制内容会进入文本合并,随后读取失败并失去解决操作。复现:基底与两侧分别为 `one\0base\n`、`one\0ours\n`、`one\0theirs\n`,Git 合并停在二进制冲突;`conflict::read` 调用 `merge-file --zdiff3` 得到退出码 255 与 `Cannot merge binary files`,真实窗口缺少取当前侧、取传入侧和标为已解决控件,违反 TREE-09。上一轮含 `0xff` 的冲突已通过模型与真窗口复核。修复方向:按 Git 的二进制判定保留 `Stages::Whole` 及阶段存在性,覆盖含 NUL 且合法 UTF-8 的二进制冲突。 + +apps/ely-tree/src/conflict.rs:120:过滤掉已有基底的冲突块后,仍从第一份基底开始依次填充,导致后续冲突显示其他块的基底,违反 TREE-09。真实 Git diff3 复现:第一块双方替换 `base-first`,中间有十行相同内容,第二块双方在同一位置插入不同文字;Git 的第二块 `|||||||` 与 `=======` 之间为空。探针读回第一块基底为 `base-first\n`,第二块也被填成 `base-first\n`,实际共同祖先在第二处没有这行。修复方向:按原始冲突块序号对应各基底,区分已有 diff3 基底与待补基底,保留合法的空基底。 + +apps/ely-tree/src/moves.rs:134:远程分支切换仍使用简写引用,上一轮同名引用缺陷在远程分支路径上保留。复现:同时创建 `refs/remotes/origin/topic` 与标签 `refs/tags/origin/topic`,配置 origin 的 fetch 映射后,在应用切换远程分支;传出的 `git switch --track origin/topic` 报 `fatal: ambiguous object name: 'origin/topic'`,HEAD 保持原分支。普通同名本地分支/标签的读取与切换回归已通过。修复方向:将所选远程分支的完整引用身份用于切换及上游设置,覆盖远程分支与标签同名的操作。 + +/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/git/refs.rs:200:`Pick` 前的连续两行 `///` 构成同一段 rustdoc 注释,共 19 个英文词,违反注释单行且至多十词的规则。复现:922cd53 的第 200、201 行同时附着于 `Pick`。修复方向:保留一行描述 `Pick` 的注释并控制在十词内。 diff --git a/tasks/reviews/C13.4-r3.md b/tasks/reviews/C13.4-r3.md new file mode 100644 index 0000000..4253c48 --- /dev/null +++ b/tasks/reviews/C13.4-r3.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-tree/src/conflict.rs:104:`conflict-marker-size` 直接解析为 `usize`,与 Git 对非正数和无效值采用七字符标记的规则不一致,违反 TREE-09。真实 Git 2.56.0 与 macOS 窗口复现:`.gitattributes` 写入 `a.txt conflict-marker-size=0`,双方修改后合并,Git 生成一个七字符冲突块,应用读成 `size: 0`、零个冲突块,三侧都显示带标记的工作树正文;点击“标为已解决”直接清空未合并索引条目,并把全部冲突标记暂存,剩余标记确认缺失。值为 `-1` 或 `abc` 时,Git 同样生成七字符块,应用读取失败,解决控件消失。修复方向:按 Git 的有效标记宽度规则处理属性值,展示、块选择及剩余标记确认共用有效宽度,覆盖零值、负值和无效值。 + +reference/Ely-GPUI-Components/src/git/merging.rs:153:`tokenize_lines()` 把正文中的独立 `\r` 当作行边界,Git 冲突标记按 LF 行划分,因而正文内相似字符被误认成分隔符,违反 TREE-09 并破坏块级选择结果。真实 Git 复现:基底为 `base\n`,当前侧为 `prefix\r=======\rOURS_END\n`,传入侧为 `THEIRS\n`;Git 写出一个正常冲突块。使用钉住版本的实际解析代码读取后,当前侧只剩 `prefix\r`,传入侧混入 `OURS_END\n=======\n`;取传入侧得到 `OURS_END\n=======\nTHEIRS\n`,实际所选侧应为 `THEIRS\n`。修复方向:冲突扫描、区域切分及替换统一采用 Git 的标记行边界,保留正文中的独立回车和原始字节,并覆盖混合 LF/独立 CR 的真实合并。 + +apps/ely-tree/src/moves.rs:89:通过任意子串 `CONFLICT` 判断冲突停顿,仍会隐藏 hook 的真实失败输出,违反 §13.3。真实 macOS 窗口复现:合并冲突已暂存,未合并索引条目为空,安装输出 `CONFLICT_CHECK_FAILED: commit message lacks required issue ID` 并退出 1 的 `pre-commit` hook;点击“继续”,`git merge --continue` 返回 1,应用只显示“Stopped on conflicts. Resolve them, then continue.”,错误对话框和 hook 原话均缺失。修复方向:核实 Git 自身的冲突诊断及实际未合并状态,保留 hook 失败原话,覆盖包含 `CONFLICT` 字样且已无未合并条目的拒绝路径。 diff --git a/tasks/reviews/C13.5-r1.md b/tasks/reviews/C13.5-r1.md new file mode 100644 index 0000000..75d6256 --- /dev/null +++ b/tasks/reviews/C13.5-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-tree/src/remotes.rs:110:强制推送计划只固定远端租约,源仍是执行时的分支引用,实际覆盖范围可超出确认内容,违反 PRD §13.3。复现:本地与远端 `main` 都在 `base -> keep-me`,读取计划得到 `lost=[]`;确认前从终端把本地 `main` 重置到 `base`,再执行原计划。随附 Git 实测推送成功,远端分支退回 `base`,确认框遗漏了被移除的 `keep-me`。修复方向:记录源提交,以固定源提交计算覆盖清单;在写队列执行前核对计划所属分支与源提交,变化后重新预览、确认,推送使用已确认的源提交。增加确认期间本地引用变化的测试。 + +apps/ely-tree/src/places.rs:273:储藏、远程与工作树表单执行时使用当前活动仓库,表单状态缺少发起仓库身份;`asks_places.rs:131` 的推送远程及上游选择也沿用当前仓库。窗口探针复现:在 A 发起 B 的异步打开,随后立即打开 A 的储藏对话框并填入 `for A`;B 打开完成后仍显示原对话框,提交后 A 的储藏数为 0,B 为 1,B 的改动被收走。修复方向:表单、选择和工作树路径选择回调携带发起仓库 ID,通过该 ID 入队;所属仓库关闭时使请求失效。增加异步打开其他仓库期间提交这些对话框的测试。 + +apps/ely-tree/src/running.rs:58:按冒号前缀合并所有 stderr 行,会删掉独立的错误与 hook 说明,违反 PRD §13.3 的完整失败输出要求,也与 agents.md C13.5 的输出声明冲突。复现:真实本地远端的 `pre-receive` hook 依次输出 `POLICY_A required`、`POLICY_B required` 并退出 1;随附 Git 推送的实时输出包含两行,`follow` 返回的错误仅保留 `remote: POLICY_B required`,`POLICY_A` 被删除。修复方向:根据实际 CR 重写与 LF 换行保留记录,独立换行的诊断全部保留;增加真实 hook 连续同前缀报错的断言。 + +apps/ely-tree/src/network.rs:211:首次推送的远程名称前缺少 `--`,`remotes.rs:109` 的强制推送同样如此,合法名称被解析成选项;agents.md C13.5 声明的参数隔离在这两处失效。复现:通过应用支持的 `remote add -- -dash ` 添加远程,新分支推送时选择 `-dash`;实际参数 `push --progress --set-upstream -dash refs/heads/main:refs/heads/main` 被随附 Git 拒绝,报 `unknown switch 'a'`。修复方向:两个推送入口均在远程位置参数前放入 `--`,覆盖以连字符开头的远程名称。 + +apps/ely-tree/src/stash.rs:41:过滤全部空字段破坏了合法空 reflog 说明的三字段记录,导致整个储藏列表读失败,违反 TREE-10。复现:修改文件后执行 `oid=$(git stash create)`、`git stash store -m '' "$oid"`;Git 能正常列出这个储藏,实际 `stash::list` 返回 `InvalidData`,其字段只剩提交号和时间。修复方向:按 NUL 分隔的固定三字段记录解析,仅处理记录流末尾的分隔符,保留空说明;增加空说明与普通储藏混合列表的测试。 + +reference/Ely-GPUI-Components/src/git/refs.rs:230:共用行调用 `focus_ring` 却没有边框宽度,键盘聚焦行时焦点框无法绘制;同文件第 290 行还把行内动作及其焦点框放在仅悬停时显示的透明层中,违反 UX-03 和 PRD §20.3。钉住的 1c96a3c 组件窗口探针复现:鼠标位于列表外,依次 Tab 到储藏行、应用、弹出、删除,四步可见边框绘制数均为 0,Enter 仍会执行透明动作;分支与标签共用此行布局。修复方向:给行的焦点框提供边框宽度,并在键盘焦点进入动作时显示动作层;增加鼠标离开列表时逐个 Tab 停靠点的绘制与执行断言。 diff --git a/tasks/reviews/C13.5-r2.md b/tasks/reviews/C13.5-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C13.5-r2.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C13.6-r1.md b/tasks/reviews/C13.6-r1.md new file mode 100644 index 0000000..45ee34e --- /dev/null +++ b/tasks/reviews/C13.6-r1.md @@ -0,0 +1,17 @@ +FAIL + +apps/ely-tree/src/recovering.rs:93:清理把预览中的目录作为递归 pathspec 交给 `clean -f -d`,删除范围包含确认前新增的子文件,违反 TREE-13 的危险操作预览,也与 agents.md C13.6 的“预览后新出现的文件不动”声明冲突。独立探针复现:先有 `out/early.txt`,预览得到 `["out/"]`,随后创建 `out/late.txt`,执行确认参数后两个文件都被删除。现有 tests/recovery.rs:87 只覆盖仓库根目录新增文件。修复方向:固定实际删除文件清单,或在写队列中核对目录内容,变化时重新预览并确认;覆盖预览目录内新增文件的测试。 + +apps/ely-tree/src/work.rs:107:`reset --keep` 会重置索引,确认框所承诺的“工作区与暂存的改动保留”与执行结果冲突,独立暂存内容会丢失。独立探针复现:两个提交中 `a.txt` 都为 `base`,把 `staged` 暂存,再把工作区改成 `unstaged`,确认移回前一提交;执行成功后索引恢复成 `base`,工作区仍为 `unstaged`,原先独立暂存的 `staged` 消失。现有移动测试只检查未暂存改动。修复方向:采用能够同时保留暂存和未暂存差异的恢复流程,遇到冲突先拒绝;用独立 Git 命令核对移动前后的索引内容和工作区内容。 + +apps/ely-tree/src/work.rs:207:移动操作只核对 HEAD 的 OID,`recovering.rs:228` 构造写操作时丢掉了预览中的分支身份,确认可能移动另一条分支,违反 TREE-13 的预览要求。独立探针复现:`main` 和 `other` 指向同一个第二提交,在 `main` 预览移回第一提交;确认前从终端切到 `other`,原操作成功,`other` 被移回而 `main` 留在第二提交。修复方向:携带完整分支引用或游离状态,在写队列执行前同时核对身份与 OID,变化后重新预览确认;覆盖同 OID 分支切换以及转为游离 HEAD 的测试。 + +apps/ely-tree/src/recover.rs:42:引用日志的时间取 `%ct`,这是提交的 committer 时间;界面将它标为“移动于”,导致 checkout、reset 等移动的时间错误。独立探针复现:提交的 committer 时间设为 2000-01-01,当前执行 checkout;独立 Git 的 `reflog show -1 --date=raw --format=%gD` 给出 `HEAD@{1791363853 -0400}`,`recover::reflog` 回报的时间仍为 `946684800`。修复方向:读取 reflog 记录自身的事件时间,保留位置、原因与该事件的对应关系;增加提交时间与移动时间不同的测试。 + +apps/ely-tree/src/side_places.rs:284:引用日志选择仅以提交 OID 标识;`places_pane.rs:126` 也按 OID 取第一条记录,同一提交的多次移动会一起被选中,并显示另一条记录的位置、原因和时间。独立探针复现:在初始提交创建 `other` 后切过去,`HEAD@{0}` 的 checkout 和 `HEAD@{1}` 的初始提交具有同一 OID;点击初始提交记录时两行都满足 selected,详情取最新 checkout 的原因。修复方向:让选择保留具体 reflog 记录身份,详情使用所选记录;提交详情读取继续使用该记录的 OID,刷新时核对记录身份。增加同一 OID 多条日志的选择测试。 + +apps/ely-tree/src/tools.rs:46:编辑器配置只验证 JSON 语法,错误类型经 `as_str()` 静默变成“尚未选择编辑器”,随后允许覆盖损坏文件,违反错误暴露规则及 agents.md C13.6 的损坏配置保护声明。独立探针复现:`tools.json` 写入 `{"editor":42}`,`Tools::load` 得到 `editor=None`、`broken=None`;再次选择编辑器后原文件被覆盖成新配置。现有测试仅使用非法 JSON 文本。修复方向:验证配置结构和字段类型,结构错误进入 broken 状态并保留原文件;覆盖合法 JSON 中错误字段类型的测试。 + +apps/ely-tree/src/tools.rs:160:macOS 的 `.app` 启动只检查 `open` 是否成功 spawn,启动助手随后返回的错误被丢掉,GUI 无法显示编辑器打开失败。独立探针复现:已保存的编辑器为随后删除的 `DeletedEditor.app`;实际 `open -a ` 成功 spawn,随后退出 1 并报告应用不存在,当前错误分支始终跳过。修复方向:在后台收集启动助手的退出状态与诊断,将失败显示在界面;增加已保存 app 被删除或失效后的打开测试。 + +apps/ely-tree/src/work.rs:222:锁竞争识别只匹配 `.lock': File exists`,真实的配置锁失败进入普通错误提示,缺少 C13.6 声明的锁对话框和原操作重试。独立探针复现:保留 `.git/config.lock` 后执行身份设置对应的 `Write::Config`;随附 Git 返回 `git config (exit status: 255): error: could not lock config file .git/config: File exists`,`locked` 判定为 false。现有锁测试仅覆盖 index.lock。修复方向:覆盖各写命令实际的锁失败诊断,统一进入保留原写操作的锁重试流程;增加 config.lock 竞争、移除锁后重试和锁文件保留的测试。 diff --git a/tasks/reviews/C13.6-r2.md b/tasks/reviews/C13.6-r2.md new file mode 100644 index 0000000..051388c --- /dev/null +++ b/tasks/reviews/C13.6-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-tree/src/work.rs:233:固定文件路径后仍直接交给 `git clean -f -- `;Git 会把确认时已变成目录的文件路径递归展开,删除预览外的文件,违反 TREE-13 的危险操作预览及 agents.md C13.6 的“预览后新出现的文件不动”。实际执行函数探针复现:创建 `out/early.txt`,预览得到 `files=["out/early.txt"]`;确认前把该文件替换为同名目录并创建 `out/early.txt/late.txt`;执行返回 `Ok(())`,新增的 `late.txt` 被删除。随附 Git 的独立命令也复现相同结果。R1 的既有目录内新增文件场景已通过,现有清理测试覆盖该文件变为目录的场景时失败。修复方向:保留并核对预览对象类型,以限制为具体文件的删除流程执行;类型变化时拒绝并重新预览,增加上述回归测试。副本中的类型核对修复使新增文件保留、探针通过。 + +apps/ely-tree/src/places.rs:91:刷新引用日志时保留旧的列表下标与 OID,`places_pane.rs:135` 再按旧下标取记录;前插同 OID 的事件会将选中状态和详情静默转移到另一条日志,违反 TREE-13 的 reflog 查看及 agents.md C13.6 的“详情显示所选那一条”。实际窗口与执行探针复现:初始提交后创建并切换到 `other`,选中 `HEAD@{1}` 的初始提交记录;从独立 Git 执行 `checkout --detach HEAD`;刷新后初始提交位于 `HEAD@{2}`,界面仍选中 `HEAD@{1}`,详情标题从 `commit (initial): original event` 变为 `checkout: moving from master to other`。原先同 OID 多行的首次选择已修复,现有 `tests/recovery.rs:389` 的测试遗漏刷新后的事件身份。修复方向:刷新时按所选事件身份重新定位,或在身份失效时清除选择和详情;覆盖同 OID 日志前插的窗口回归。副本中按原事件重新定位后,探针保留原记录并通过。 diff --git a/tasks/reviews/C13.6-r3.md b/tasks/reviews/C13.6-r3.md new file mode 100644 index 0000000..476c59e --- /dev/null +++ b/tasks/reviews/C13.6-r3.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-tree/src/work.rs:236:清理改为直接 `remove_file` 后,执行前只检查文件类型,遗漏当前索引状态,会删除已成为跟踪文件的工作区内容,违反 TREE-13 的“清理未跟踪文件”。独立构建成功,108 项应用测试通过,R2 文件变目录和日志前插场景均通过;新增执行函数探针复现:创建 `new.txt` 并预览,随后从外部 Git 执行 `git add new.txt`,再写入尚未暂存的新内容,确认清理返回 `Ok(())`,文件消失,独立 `git status --porcelain` 显示 `AD new.txt`,索引仍保留旧内容,新工作区内容丢失。同场景直接执行 `git clean -f -- new.txt` 会保留文件。修复方向:删除前重新核对预览路径的 Git 状态,已跟踪的路径应拒绝并重新预览;补上预览后外部暂存且继续编辑的回归。副本中增加索引核对后,该探针与 R2 回归通过。 + +apps/ely-tree/src/work.rs:228:`symlink_metadata(root.join(file))` 只检查末级对象,路径的父目录仍会跟随符号链接;随后删除可越过仓库边界,违反 TREE-13 的危险操作预览和 agents.md C13.6 的“只删所列”。执行函数探针复现:预览 `out/early.txt`,确认前把 `out` 改名为 `saved-out`,创建仓库外的 `outside/early.txt`,再把 `out` 换成指向 `outside` 的符号链接;确认返回 `Ok(())`,仓库外文件被删,原预览文件仍留在 `saved-out/early.txt`。同场景独立执行 `git clean -f -- out/early.txt` 会保留仓库外文件。修复方向:验证父路径身份,并以禁止跟随父路径链接的删除方式绑定仓库内预览对象;路径身份变化时拒绝并重新预览。补上父目录被链接替换的回归;副本中增加父链接核对后,该探针与 R2 回归通过。 + +apps/ely-tree/src/recover.rs:41:以提交、原因和秒级时间判定 reflog 事件身份,多个真实事件可以具有相同三元组;`side_places.rs:284` 会同时选中它们,`places_pane.rs:137` 会将详情定位到首个匹配事件,违反 TREE-13 的 reflog 查看和 agents.md C13.6 的“详情显示所选那一条”。随附 Git 与实际 `reflog`、`Moved::same` 探针复现:在同一秒连续执行两次 `git reset --keep HEAD`,两条记录分别为 `HEAD@{0}`、`HEAD@{1}`,提交、`reset: moving to HEAD` 和时间完全相同;选中 `HEAD@{1}` 时,两条记录均满足选中条件,详情取 `HEAD@{0}`。探针固定 `GIT_COMMITTER_DATE` 重现秒级碰撞,唯一选中断言得到 2,预期为 1。现有 R2 回归覆盖了原因不同的同提交事件。修复方向:为当前快照中的重复事件保留独立行身份,刷新时按日志序列追踪原事件,身份有歧义时清除选择;增加同秒同原因重复事件的选中、详情和刷新回归。 diff --git a/tasks/reviews/C14.0-r1.md b/tasks/reviews/C14.0-r1.md new file mode 100644 index 0000000..922477d --- /dev/null +++ b/tasks/reviews/C14.0-r1.md @@ -0,0 +1,15 @@ +FAIL + +crates/ely-web-content/src/handlers.rs:317 — The content boundary covers navigation only; subresource requests bypass it. In the real-window probe, a page under its permitted root loaded a script from the sibling outside directory and reported `{"externalCode":"outside-code-ran"}` through elyQuery. An image also reached the local HTTP server at `/pixel?text=REVIEW_SENTINEL`. This breaks PRD §17.3's bundled-code and restricted-resource contract. Apply resource authorization before subresource requests and redirects, covering scripts, images and other resource types; enforce the permitted file identities and deny unauthorized network destinations. + +crates/ely-web-content/src/view.rs:81 — The folder check compares lexical paths and follows symlinks during the eventual load. Create `root/link` pointing to a sibling outside directory, then navigate the loaded page to `file:///.../root/link/outside.html`. The real CEF page loads and executes the outside script, reporting `{"outside":true,"href":"file:///.../root/link/outside.html"}`. This contradicts the C14.0 claim that pages cannot leave their content folder. Resolve the target's filesystem identity and verify containment in the canonical root before allowing navigation or resources. + +crates/ely-web-content/src/handlers.rs:301 — Closing a browser removes the registry entry while leaving `Surface.browser` populated. Surface owns Browser, Browser retains its client handlers, and those handlers own Surface, producing a reference cycle. A real-window open/close probe tracked surfaces with Weak references: after closing 20 views it reported `release: dropped=20 alive=20`. Clearing the stored browser reference in the copy's close callback changed the same result to `alive=0`. This violates PERF-08. Break the browser/surface ownership cycle during completed browser closure and add a regression that verifies release after repeated closes. + +crates/ely-web-content/src/handlers.rs:275 — Load failures have no error callback handling, and on_load_end unconditionally produces Event::Loaded. Delete the page after WebView::open validates it and before the asynchronous browser loads it: the probe reports Loaded, with location `chrome-error://chromewebdata/` and body `Your file couldn’t be accessed ... ERR_FILE_NOT_FOUND`; no application failure event is emitted. Chromium's default error UI consequently occupies the content area. Handle load errors explicitly, report failure to the owner, and display the error through GPUI; emit Loaded only for a successful content load. + +crates/ely-web-content/src/view.rs:155 — File URLs are assembled from raw path text, so legal filename characters are interpreted as URL syntax. Opening an existing `name#part.html` succeeds at the host API and then loads Chromium's ERR_FILE_NOT_FOUND page because the suffix becomes a fragment. The real-window probe confirmed this with the existing file still present. Construct a properly escaped file URL from the filesystem path and test reserved characters in both content directories and page filenames. + +apps/ely-mark/src/main.rs:36 — CEF initialization and renderer failures are handled solely by logging. In the independently built .app, terminating its renderer processes emits `mark: the page crashed: TerminationStatus(TS_PROCESS_WAS_KILLED) (9)`, while the window continues displaying the previous editor image with no visible fault or reopening action. Screenshots before and after termination confirm the unchanged content. This breaks the visible-failure contract in PRD §6.3. Handle Event::Crashed by updating the application UI to a fault state and providing a reopening path, including failures from asynchronous initialization. + +tests/scripts/webview.sh:21 — The live-window gate checks page-reported strings without checking the rendered output. In the isolated copy, replacing WebView's window.paint_image call with a successful no-op removed all page painting; the unchanged script still exited successfully with `ok: webview`. The core C14.0 rendering regression therefore escapes the acceptance gate. Add an assertion on actual window pixels that fails when the page is absent, and prove it with the same removed-paint mutation. diff --git a/tasks/reviews/C14.0-r2.md b/tasks/reviews/C14.0-r2.md new file mode 100644 index 0000000..38a1294 --- /dev/null +++ b/tasks/reviews/C14.0-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-web-content/src/handlers.rs:406 — The resource fence leaves WebSocket connections unrestricted. In the independently built real-window probe, replacing the blocked image request with `new WebSocket('ws://127.0.0.1:/pixel?text=REVIEW_SENTINEL')` made the same local listener report `probe: network=reached`; its captured bytes contained `GET /pixel?text=REVIEW_SENTINEL HTTP/1.1` and `Upgrade: websocket`. Thus page data reaches an unauthorized network destination despite the C14.0 claim that all requests outside the content folder are cancelled, violating PRD §17.3's restricted-resource contract. Enforce the destination policy for WebSocket connections as well as ordinary resource loads, and add a live regression that verifies the listener receives no handshake. + +tests/scripts/webview.sh:42 — The crash/recovery gate accepts a missing fault interface and recovery button. Its pixel assertion runs before the renderer is terminated, and recovery is driven directly by the probe calling `WebView::reload`. In the isolated copy, suppressing the entire fault-rendering branch with `self.fault.clone().filter(|_| false)` kept the previous page image visible after the crash and removed the Reload button, while the unchanged gate still exited successfully with `ok: webview (marker share 0.0871, 23 pages freed)`. This lets the PRD §6.3 visible-failure and reopening contract regress undetected. Assert the rendered fault state after termination and recover by activating the displayed Reload control; require the suppressed-fault mutation to fail. diff --git a/tasks/reviews/C14.0-r3.md b/tasks/reviews/C14.0-r3.md new file mode 100644 index 0000000..388ee9d --- /dev/null +++ b/tasks/reviews/C14.0-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-web-content/src/handlers.rs:103 — The dead HTTP proxy leaves WebRTC UDP traffic unrestricted, so page data can still reach destinations outside the content folder. In the independently built real-window probe, bundled JavaScript created an RTCPeerConnection and data channel, installed an answer derived from its offer with `a=ice-ufrag:REVIEW_SENTINEL`, and added a host ICE candidate targeting a local UDP listener at `127.0.0.1:52027`. The listener received 173 STUN packets containing the page-controlled `REVIEW_SENTINEL` bytes while the current gate exited successfully with `ok: webview (marker share 0.0871, 23 pages freed)`. A simpler `iceServers: [{urls: 'stun:127.0.0.1:'}]` probe also reached its UDP listener. This violates PRD §17.3's restricted-resource contract and the all-network restriction claimed in agents.md:185. Apply the outbound restriction to WebRTC networking and add a live UDP regression that checks for both STUN requests and page-controlled ICE payloads. Evidence: `/tmp/c14-r3-ice-capture.log`, `/tmp/c14-r3-ice-gate.log`. + +tests/scripts/webview.sh:59 — Recovery is accepted from a load event without checking that the recovered page is displayed. `examples/webprobe.rs:67` prints `probe: recovered` and immediately quits on `Event::Loaded`. In the isolated copy, removing both assignments that clear `WebView::fault` during reload and successful loading left the renderer permanently selecting the fault view, yet the unchanged gate still exited successfully with `ok: webview (marker share 0.0871, 23 pages freed)`. A separate observation with exit delayed three seconds measured a post-reload marker share of `0.0000`. Thus the PRD §6.3 reopening interface can remain broken while the recovery gate passes. Keep the probe alive until the recovered content is displayed, assert the page marker and disappearance of the fault state after activating Reload, and require the retained-fault mutation to fail. Evidence: `/tmp/c14-r3-stuck-fault.log`, `/tmp/c14-r3-stuck-fault-visible.log`. diff --git a/tasks/reviews/C14.1-r1.md b/tasks/reviews/C14.1-r1.md new file mode 100644 index 0000000..2c2faa9 --- /dev/null +++ b/tasks/reviews/C14.1-r1.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-mark/web/src/main.css:150 — SVG 图标样式同时命中 Mermaid 的整张图,强制图宽、高为 14px,违反 §14 的 Mermaid 渲染要求。复现:在预览模式显示包含 `graph TD\n A --> B` 的 Mermaid 代码块,真窗口中 SVG 的 viewBox 为 `4 4 168 152`,实际尺寸为 14×14,节点文字缩成无法阅读的小点;窗口截图确认该结果。独立副本中将选择器收窄为 `.milkdown-code-block .tools svg` 后,同一图恢复为 168×152。修复方向:将图标尺寸规则限定在工具按钮内,保留预览图的自然尺寸及响应式约束,并验证实际可见图形。证据:`/tmp/ely-c141-r1-logs/deep.log`、`repair.log`。 + +apps/ely-mark/web/src/diagram.js:21 — Mermaid 的主题颜色在生成 SVG 时固化;页面主题 token 更新后,已显示图形继续使用旧主题,违反 §17.3 的主题同步要求。复现:渲染上述图后,经页面主题入口更新 `--ely-surface`、`--ely-fg`,节点填充仍为 `rgb(27, 25, 23)`,SVG 内容完全相同;再次显示相同 Markdown 才得到新 token 对应的 `rgb(255, 0, 255)`。系统深浅色变化走同一 token 更新路径,已有图形也会保留旧色。修复方向:在主题变化时更新现有图形的样式或重新渲染当前 Mermaid 源码,补充显示完成后的主题切换回归断言。证据:`/tmp/ely-c141-r1-logs/deep.log`。 + +apps/ely-mark/web/src/main.js:9 — `show()` 的异步创建缺少调用顺序与当前实例校验,连续模式请求会在同一根节点留下两个独立可写编辑器,违反 §14.1 的单一当前编辑模式约束。复现:在真实 CEF 页面连续调用 `ely.show('# OLD', 'visual', texts)` 与 `ely.show('# NEW', 'source', texts)`,等待两者完成后,根节点的 mode 为 `source`,CodeMirror 显示 `# NEW`,同时仍有显示 `OLD` 的可编辑 Milkdown;两次调用都报告成功。原因是第二次调用执行时,第一次创建尚未完成,`close` 仍属于前一个实例;第一次完成后又挂入旧编辑器并覆盖关闭函数。修复方向:按调用顺序管理创建与销毁,校验当前请求身份,销毁过期创建结果,保证根节点始终只保留当前模式的实例。证据:`/tmp/ely-c141-r1-logs/deep.log` 中 `raceMode`、`raceHeads`、`raceSource`、`raceEditors`。 + +tests/scripts/mark-content.sh:39 — 渲染验收只读取 DOM 数量与属性,缺少对实际可见内容的断言,整页不可见也会通过,属于 C14.1 的弱测试。复现:独立构建与原有脚本通过;随后仅在副本的页面样式追加 `#document { opacity: 0; }`,重新构建并运行同一脚本,仍输出 `ok: mark content`。现有断言也允许前述 14×14 Mermaid 图通过;代码块数量虽由探针读取,脚本同样未检查。修复方向:对标题、代码和图形建立与内容及尺寸相关的真窗口可见性断言,并要求隐藏正文、压缩图形等变异触发失败。证据:`/tmp/ely-c141-r1-logs/gate.log`、`hidden-gate.log`;`cargo test -p ely-mark` 独立运行通过,当前为 0 项单测。审查结束前已删除探针源码、独立构建目录与自建 CEF 缓存,原始范围内 22 个文件的散列保持一致,核对记录在 `verification.log`。 diff --git a/tasks/reviews/C14.1-r2.md b/tasks/reviews/C14.1-r2.md new file mode 100644 index 0000000..95e2365 --- /dev/null +++ b/tasks/reviews/C14.1-r2.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-mark/web/src/code.js:32 — 异步结果只核对 Mermaid 请求编号,代码块切换语言后旧请求仍有效,导致 Rust 代码块显示旧 Mermaid 图。复现:真实 CEF 页面显示 `graph TD\n A --> B` 的 Mermaid 代码块,渲染完成前用 ProseMirror 的 `setNodeMarkup` 将语言改为 `rust`;等待后文档属性和语言按钮均为 `rust`,旧 SVG 仍可见,`checkVisibility(...)` 为 true,预览面板仍显示。修复方向:将当前代码块的语言、原文和存活状态纳入结果校验,在语言变化、清空和销毁时使旧渲染失效。证据:`/tmp/ely-c141-r2-logs/recheck-summary.log`、`logic.log`。 + +apps/ely-mark/web/src/code.js:45 — 主题观察器把正在渲染、尚无 `data-diagram` 元素的有效请求当作已脱离页面的记录删除,随后旧主题 SVG 仍能回填,违反 §17.3 的主题同步要求。复现:真实 CEF 中开始显示 Mermaid,在副本中仅将生成 SVG 到回填之间延迟 800 毫秒以固定交错;此时把 `--ely-surface` 改为 `#ff00ff`,最终节点填充仍为旧色 `rgb(27, 25, 23)`,渲染记录已被删至 0。上一轮的已显示图形换主题场景通过,本场景覆盖首次渲染期间换主题。修复方向:按代码块生命周期区分待渲染和已销毁状态,主题变化使旧主题结果失效并调度当前源码重绘。副本修复后同一交错得到 `rgb(255, 0, 255)`。证据:`/tmp/ely-c141-r2-logs/recheck-summary.log`、`repair-summary.log`。 + +apps/ely-mark/web/src/code.js:61 — 全局 `drawn` 强引用每次渲染的源码和回调,编辑器销毁及模式切换缺少相应释放;清理依赖下一次主题变化,导致固定主题下持续积累失效记录,违反 PERF-08 的资源释放要求。复现:真实 CEF 页面连续 20 次显示 Mermaid 预览并切回源码,等待所有渲染结束后缓存由 2 项增至 22 项,22 项对应的图均已脱离 DOM;回调和源码继续由 Map 持有。修复方向:让每个编辑器或代码块拥有其渲染记录,在替换、清空和销毁时释放源码及回调,并取消失效任务。副本加入生命周期释放后,同一 20 次循环回到 0 项。证据:`/tmp/ely-c141-r2-logs/recheck-summary.log`、`repair-summary.log`。 + +tests/scripts/mark-content.sh:14 — EXIT trap 在 `set -e` 下终止已经由 `wait` 回收的探针,`kill` 返回 1 使成功验收变成失败,并跳过临时目录清理。复现:独立构建后运行原脚本,全部内容断言通过,打印 `ok: mark content (background share 0.9422)`,最终退出码为 1,工作目录仍存在;最小 Bash 复验同样在打印 success 后返回 1,清理语句未执行。修复方向:只终止仍在运行的探针,保证清理总能执行,并保留验收本身的退出状态。证据:`/tmp/ely-c141-r2-logs/gate.log`、`gate-report.log`、`trap.log`。 + +tests/scripts/mark-content.sh:72 — 块公式验收只检查 KaTeX 节点存在,整块公式完全透明仍满足“公式已显示”的断言,属于 C14.1 的弱测试。复现:在独立副本中仅修正上述 EXIT trap,再给样式追加 `.milkdown-code-block .preview .katex { opacity: 0; }`;三种模式的内容检查全部通过,脚本退出 0,打印 `ok: mark content (background share 0.9427)`,预览中的块公式实际不可见。隐藏整个正文的变异已按预期失败,背景占比为 0.9991。修复方向:将块公式滚入视口后核对实际可见性,并要求隐藏块公式的变异触发失败。证据:`/tmp/ely-c141-r2-logs/hidden-math-gate.log`、`hidden-math-report.log`、`hidden-body-gate.log`;`cargo test -p ely-mark` 独立运行通过,当前为 0 项单测,打包构建及内容比对通过。审查副本、独立构建目录和本轮 CEF 缓存均已删除,原始范围内 22 个文件散列保持一致,见 `verification.log`。 diff --git a/tasks/reviews/C14.1-r3.md b/tasks/reviews/C14.1-r3.md new file mode 100644 index 0000000..f7fc831 --- /dev/null +++ b/tasks/reviews/C14.1-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-mark/web/src/diagrams.js:44 — 主题重绘复用同一个 record,结果校验只检查占位和对象身份,旧主题任务仍能回填;R2 的首次绘制期间换主题缺陷仍有残留,违反 PRD §17.3 的主题同步要求。复现:真实 CEF 中渲染验收探针同款 150 节点 Mermaid,在副本中仅将首次 SVG 生成到返回之间延迟 800 毫秒以固定交错,此时把 --ely-surface 改为 #ff00ff。首次回填的可见 SVG 节点仍为 rgb(27, 25, 23),滚入视口后的命中与可见性检查均通过,旧色持续 1129.5 毫秒后才被新色覆盖;tests/scripts/mark-content.sh:98 只检查最终颜色,原脚本仍退出 0。修复方向:为每次绘制记录代次,主题变化立即使旧结果失效,回填校验当前代次;验收覆盖换主题后的首次回填。副本加入代次校验后,同一交错的首次可见填充为 rgb(255, 0, 255)。证据:/tmp/ely-c141-r3-logs/recheck.log、repair.log、gate.log。 + +apps/ely-mark/web/src/diagrams.js:47 — 全局 Promise 队列在调用 Mermaid 前无存活检查,release 清空记录后,排队闭包仍持有源码和回调并逐个执行旧图渲染,导致已经切到源码模式的页面持续处理失效文档,违反 PERF-02 的交互要求。复现:真实 CEF 中将首个异步绘制暂挂以固定交错,连续 20 次显示验收探针同款 Mermaid 后切回源码,再放行队列。此时渲染记录与属主均已为 0、页面只有一个源码编辑器,队列仍完成全部 20 个旧图,其中 19 个在源码模式下开始,耗时 37784.6 毫秒,页面 16 毫秒定时器的最大间隔达 1819 毫秒。修复方向:销毁或替换编辑器时使待执行任务失效,并在调用渲染器前检查当前属主、占位和绘制代次,跳过失效任务;增加待渲染时销毁的回归断言。副本增加执行前校验后,同一交错只完成已进入渲染器的一项,其余 19 项均被跳过。证据:/tmp/ely-c141-r3-logs/queue.log、queue-repair.log、verification.log。独立构建、基础内容验收、包内离线渲染和隐藏公式变异验证均已执行;审查副本、独立 target 与临时缓存已删除,原范围 24 个文件散列保持一致。 diff --git a/tasks/reviews/C14.2-r1.md b/tasks/reviews/C14.2-r1.md new file mode 100644 index 0000000..fdbb6d2 --- /dev/null +++ b/tasks/reviews/C14.2-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-mark/web/src/preserve.js:74 — 修改段落时直接序列化整个段落,破坏其中的未知扩展语法,违反 PRD §14.3、QA-21。复现:打开 `Alpha with [[Wiki Link]].\n`,在段首键入 `x` 后保存,实际字节为 `xAlpha with \[\[Wiki Link]].\n`;未知链接被加上转义。真窗口原生键入与保存已复现。修复方向:保留未编辑的未知语法原文;无法保证保留的结构使用原文块或源码编辑,并加入在未知语法所在段落编辑的字节断言。证据:`/tmp/ely-c142-r1-logs/wiki-bytes.log`、`wiki-native.log`。现有 `tests/scripts/mark-session.sh` 和四项文本单测均独立运行通过。 + +apps/ely-mark/web/src/preserve.js:60 — 原文对应与差异仅覆盖顶层节点,引用和列表内部的局部编辑会序列化整个容器,改写无关段落与代码块,违反 PRD §14.3、QA-21。复现:打开 `> First.\n>\n> Setext\n> ======\n>\n> Last with [[Wiki]].\n`,仅在首段键入 `x` 并保存,后面的 Setext 标题变成 `> # Setext`,末段变成 `> Last with \[\[Wiki]].`;另一个引用样本中,未编辑的 `~~~~python` 围栏变成三个反引号。列表首项编辑同样重写后续项目。修复方向:对应到实际修改的子块,保留其余子块原文;无法对应的容器走原文块或源码模式,补充嵌套段落和代码块的保存回归。证据:`/tmp/ely-c142-r1-logs/quote-bytes.log`、`quote-native.log`、`preservation-results.log`。 + +apps/ely-mark/web/src/preserve.js:79 — 保留编辑器隐藏的块间原文时调用 `trim()`,随后重新插入到修改块末尾,造成未编辑的链接定义被改写,违反 PRD §14.3、QA-21。复现:打开 `Alpha.\n\n[r]: /path " T " \n\nBeta.\n\nTail.\n`,将插入点移动到首段末尾并按 Forward Delete 合并两个可见段落,保存结果为 `Alpha.Beta.\n\n[r]: /path " T "\n\nTail.\n`;链接定义被移到合并段落后,原有三个尾部空格被删除。真窗口键盘操作与保存已复现。修复方向:将隐藏语法作为带原始字节和位置关系的保留片段处理,保持其原文;无法可靠合并的边界使用源码编辑,补充跨隐藏定义的合并和删除测试。证据:`/tmp/ely-c142-r1-logs/gap-bytes.log`、`gap-native-clean.log`。 + +apps/ely-mark/src/view.rs:62 — 页面重载后沿用旧的 `changed`、`reported`,而 main.js:8 的页面版本从零开始,恢复后的新编辑会被判为旧文本丢弃,保存仍宣称成功,违反文件会话内容与修订一致性的要求。复现:分别键入 `x`、`z` 并保存,使旧版本为 2;通过真实 CEF `Page.crash` 终止页面,经原生 AX 按下 Reload 后用 CEF `Input.insertText` 输入 `y`。页面显示 `yxzAlpha with [[Wiki Link]].`,日志出现 `mark: an older text was dropped`;原生 Cmd+S 记录保存成功,磁盘仍为 `xzAlpha with \[\[Wiki Link]].\n`,新增的 `y` 丢失。修复方向:为每次页面载入建立独立代次并同步版本基线,过滤旧代次回报,确保保存等待当前代次的全文;加入崩溃恢复后的编辑、保存和关闭测试。证据:`/tmp/ely-c142-r1-logs/wiki-native.log`、`crashed-ax.log`、`reload-dom.log`、`reload-bytes.log`。 + +apps/ely-mark/src/engine.rs:52 — 普通 Markdown 读取的 `PermissionDenied` 原样进入外壳的加密询问路径(open.rs:181),系统权限错误被隐藏为密码错误。复现:创建 UTF-8 文件并设为 `chmod 000` 后用 ElyMark 打开,窗口出现密码框;输入 `abc` 并回车后再次要求密码,日志将普通文件记为 `is locked`、`a password failed: true`,应用未显示实际读取权限错误。修复方向:在 Mark 载入路径明确区分普通文件读取错误与引擎确认的加密状态,使权限失败显示系统错误并结束打开流程;补充无读取权限的文件测试。证据:`/tmp/ely-c142-r1-logs/denied-native.log`、`denied-ax.log`。 + +apps/ely-mark/web/src/preserve.js:4 — 注释 `Pairs of equal runs between a and b, by Myers' diff.` 共 11 个英文词,超过 agents.md 的十词限制。复现:去除注释界定符和内联代码标记后按空白分词得到 11。修复方向:缩短至十词以内。证据:`/tmp/ely-c142-r1-logs/verification.log`;该日志同时记录五个应用的独立编译检查通过、审查副本已删除及原源码散列保持一致。 diff --git a/tasks/reviews/C14.2-r2.md b/tasks/reviews/C14.2-r2.md new file mode 100644 index 0000000..1ce182b --- /dev/null +++ b/tasks/reviews/C14.2-r2.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-mark/src/engine.rs:79 — 保存字节与保存修订取自两份文本快照,保存期间的新编辑会被错误标记为已保存,并在关闭时丢失,违反 PRD §14.3 的内容与当前修订一致性。`write` 写出后释放文本锁,外壳读回临时文件再调用 `history`;此时 `current` 可已被页面更新,返回的新文本哈希随后成为 `MarkView.saved` 的基线。具体复现:真 CEF 窗口打开 `Alpha.\n`,原生 Cmd+S 导出 7 字节后,用内部 `save.md` 的 FIFO 延迟读回;经 CEF `Input.insertText` 输入 `x`,等待应用收到版本 1、长度 8 的全文,再放行原来的 7 字节。日志记录 `write len 7`、`text version 1 len 8`、`history len 8`;磁盘仍为 `Alpha.\n`,页面为 `xAlpha.`,原生 Cmd+Q 直接以 0 退出,新增字符丢失。修复方向:将导出字节与修订标记绑定到同一文本快照,保存完成后以该快照标记已保存,后续编辑继续保持未保存状态;补充导出与历史读取之间继续编辑的回归。副本中的修订绑定变异已验证:同一调度下关闭会弹出 `Save changes to race.md?`,原生 AX 可见 Save、Cancel 和 Don't Save。证据:`/tmp/ely-c142-r2-logs/race-method.log`、`race-native-broken.log`、`race-instrument.log`、`race-repair.log`、`race-repair-ax.log`。 + +tests/scripts/mark-session.sh:132 — 链接定义回归的多行 `grep -qF` 将各行视为独立模式,其中的空行模式匹配任意非空文件,后面的全文比较随之被跳过,无法验证 R1 的定义移动与修剪缺陷。具体复现:将 gap2 的保存结果替换为 R1 的损坏结果 `Alpha.Beta.\n\n[r]: /path " T "\n\nTail.\n`,断言通过;进一步在副本测试脚本中于此断言前将 gap2 全文写成 `CORRUPTED\n`,完整真窗口测试仍以 0 退出并报告 `ok: mark session`。修复方向:使用逐字节比较,并要求上述损坏结果变异使测试失败。证据:`/tmp/ely-c142-r2-logs/predicate.log`、`session-mutant.log`。R1 六项已复核通过;独立网页构建、`cargo test -p ely-mark`(四项通过)、原始 mark-session 测试及五个受影响应用的编译检查均通过。审查源码副本与其独立构建目录已在写结论前删除,原范围内文件散列一致;验证记录:`/tmp/ely-c142-r2-logs/verification.log`。 diff --git a/tasks/reviews/C14.2-r3.md b/tasks/reviews/C14.2-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C14.2-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C14.3-r1.md b/tasks/reviews/C14.3-r1.md new file mode 100644 index 0000000..2e9388f --- /dev/null +++ b/tasks/reviews/C14.3-r1.md @@ -0,0 +1,15 @@ +FAIL + +apps/ely-mark/web/src/main.js:92 — 在另一模式编辑后返回时,`drop(entry)` 销毁原编辑器及其撤销历史,违反 PRD §14.3 的“保留内容历史与当前修订”。真窗口复现:打开 `Alpha.\n`,可视模式键入 `v`,源码模式键入 `s`,返回可视,连续 Cmd+Z 两次并保存,落盘仍为 `svAlpha.\n`;同模式编辑、撤销的对照用例通过。CEF 探针测得可视历史深度从 1 变为 0,经预览往返的对照仍为 1。方向:让本文件的编辑事务和历史跨模式保持连续,模式切换同步当前修订。证据:`/tmp/ely-c143-r1-logs/mark-history.log`、`history-after.log`、`reviewprobe.log`。 + +crates/ely-office/src/rename.rs:82 — 重命名完成回调在发起标签已关闭时仍调用 `sessions.open`,创建没有标签承接的会话登记,随后打开该文件会被这个登记挡住。隔离副本仅在文件系统改名调用前延迟 2 秒:发起 `a.md → closed.md`,通过正常 `request_close` 关闭干净标签,等改名完成后重新打开 `closed.md`;实测关闭后的标签数为 0、登记为 `Ok(true)`,重新打开进入 `Open::Focus`,最终仍是 `tabs=0, active=None`。方向:完成回调先核对发起标签的存活和路径身份,再更新登记;关闭标签后完成的操作应保持登记与现存标签一致。证据:`/tmp/ely-c143-r1-logs/rename-race.err`。 + +crates/ely-web-content/src/surface.rs:118 — `file_url` 把 Windows 驱动器冒号和路径反斜杠都转义到 `file://` 的 authority 中,生成无效 URL。按现有函数算法处理 `C:\Users\Alice\notes\doc.md`,结果为 `file://C%3A%5CUsers%5CAlice%5Cnotes%5Cdoc.md`,URL 解析器直接报 `Invalid URL`;Windows 的内容页面路径和图片目录均经过此函数,因而页面载入及本地图片解析受影响。本轮验证的是路径转换与 URL 解析,Windows 应用运行仍未验证。方向:按平台处理驱动器、UNC 和规范化路径,生成合法 file URL,并让路径围栏使用对应的反向转换。证据:`/tmp/ely-c143-r1-logs/file-url.log`。 + +apps/ely-mark/web/src/main.js:128 — 新模式异步构建期间 `current` 为 null,后续模式请求直接返回,用户最后选择的模式被丢弃。真 CEF 页面从源码模式连续调用 `ely.mode('visual')`、`ely.mode('preview')`,第二次调用返回 undefined,最终页面模式为 visual。相同路径可由快速连续的 Cmd+1、Cmd+3 触发。方向:保留最新模式请求和对应文本修订,使构建完成后呈现最后一次选择,并覆盖构建中的连续切换。证据:`/tmp/ely-c143-r1-logs/reviewprobe.log` 的 `mode_race`。 + +tests/scripts/mark-modes.sh:122 — “新建为空文档”的检查只等待 visual 模式日志,缺少内容断言,支撑不了 TASKS.md 的空文档验证声明。隔离副本把 `Desk::on_new` 的空字符串改成 `REVIEW_NONEMPTY\n`,重新构建后完整脚本仍输出 `ok: mark modes`、退出码 0。方向:对新建标签的实际空内容做断言,让非空初始化变异触发失败。证据:`/tmp/ely-c143-r1-logs/new-mutation.diff`、`mark-modes-new-mutation.log`;原实现的 mark-modes.sh、mark-content.sh、mark-session.sh 均独立通过,相关五个 crate 单测共 140 通过、1 忽略,Doc/Table/Show/PDF/Mark 调用点编译检查通过。 + +crates/ely-web-content/src/handlers.rs:501 — 文件实测 507 行,违反 agents.md 的每文件 500 行上限。方向:拆出完整的一组 CEF handler 职责,使各源码文件符合行数上限。 + +crates/ely-office/src/rename.rs:29 — `Renaming: Host` 为全部 Host 引入通用扩展 trait,经 lib.rs 和 prelude 公开,产品调用者实际只有 `apps/ely-mark/src/desk.rs`,违反 agents.md 的“无第二个使用者的抽象”约束。全项目调用点扫描确认其他文档应用沿用 FILE 命令表,只有 Desk 调用该 trait 的 rename。方向:将 Mark 专属的改名流程落实到 Desk,保留 ely-files 的平台原子改名函数。 diff --git a/tasks/reviews/C14.3-r2.md b/tasks/reviews/C14.3-r2.md new file mode 100644 index 0000000..831efcb --- /dev/null +++ b/tasks/reviews/C14.3-r2.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-mark/web/src/main.js:100 — 跨模式撤销调用 changed,把刚建立的 forward 链当作新编辑销毁,丢失重做历史,违反 PRD §14.3。真窗口打开 `Alpha.\n`,可视模式键入 `v`,源码模式键入 `s`,返回可视;Cmd+Z 后保存得到 `vAlpha.\n`,Shift+Cmd+Z 后保存仍为 `vAlpha.\n`,预期恢复 `svAlpha.\n`。隔离副本将历史跳转与新编辑的报告分开后,同一操作恢复 `svAlpha.\n`。方向:历史跳转保留重做链,仅新的内容编辑截断重做分支,并覆盖跨模式撤销、重做。证据:`/tmp/ely-c143-r2-logs/redo.json`、`redo-repair.json`、`repair.diff`。 + +apps/ely-mark/web/src/main.js:127 — back 指向该模式的旧编辑器,形成按模式分开的历史,破坏本文件编辑事务的撤销顺序(PRD §14.3)。真窗口可视模式键入 `v`、源码模式键入 `s`,在源码连续撤销两次并保存,仍为 `vAlpha.\n`,较早的可视编辑无法撤销;另一路径返回可视撤销 `s`,再切源码并撤销,落盘反而从 `vAlpha.\n` 变回 `svAlpha.\n`,恢复了已撤销的编辑。方向:按本文件实际编辑顺序连接历史与修订,让两种编辑器的历史边界共同推进同一条时间线,覆盖切换后的连续撤销。证据:`/tmp/ely-c143-r2-logs/linear.json`、`reverse.json`。 + +apps/ely-mark/src/rename.rs:130 — 将 Open::Focus 当作新登记赋给发起标签,导致两个可写标签共享文件和登记号,违反 PRD §6.1 的同文件会话规则。隔离副本仅在文件系统改名成功后、完成回调前延迟两秒:发起 `a.md → closed.md`,期间通过正常 open_file 打开 `closed.md`;完成后实测 `tabs=2`,两个标签路径均为 `closed.md`、registry 均为 `Some(1)`。关闭任一标签还会删除另一标签共用的登记。上一轮“关闭发起标签后产生孤立登记”的复现已通过,本项是目标在异步间隙被另一标签打开的竞争。方向:改名期间保留目标身份的占用,并一致地迁移文件会话登记;完成回调遇到已有目标会话时处理其归属与待保存内容,避免复用其登记号创建第二个可写会话。证据:`/tmp/ely-c143-r2-logs/rename-owner.log`、`rename-closed.log`。 + +apps/ely-mark/web/src/main.js:142 — shown 回报的异步确认回来后,直接执行旧 asked 请求;期间正常发起的新模式请求既未清除它,完成回调也未重核 latest,最后一次选择仍可能被覆盖。真 CEF 探针将回报确认延迟 300 毫秒:从源码请求可视,构建中请求预览,可视挂载后、确认前再请求源码;页面先报告源码,随后被旧预览请求覆盖,最终 mode 为 preview。隔离副本在新请求时清除旧排队请求、确认回来时核对代次后,同一探针最终为 source。方向:所有模式请求共享递增代次,异步确认与构建完成都只落实最新请求,并覆盖确认期间的三次连续切换。证据:`/tmp/ely-c143-r2-logs/asked-native.log`、`asked-repair.log`、`repair.diff`。原版 mark-modes.sh、mark-content.sh、mark-session.sh 均独立通过;相关五个 crate 单测 142 通过、1 忽略,Doc/Table/Show/PDF/Mark 编译检查通过。上一轮新建非空变异已使脚本失败;Windows 驱动器与 UNC URL 单测通过;handlers.rs 为 424 行;Renaming 扩展 trait 已移除。源码散列核对一致,隔离源码副本与构建目录均已在写结论前删除;完整验证记录为 `/tmp/ely-c143-r2-logs/verification.log`。 diff --git a/tasks/reviews/C14.3-r3.md b/tasks/reviews/C14.3-r3.md new file mode 100644 index 0000000..2928a27 --- /dev/null +++ b/tasks/reviews/C14.3-r3.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-mark/web/src/main.js:87 — 模式切换销毁当前编辑器,时间线只记录离开时的正文,编辑器内部撤销产生的重做历史随之丢失,违反 PRD §14.3。真窗口打开 `Alpha.\n`,源码模式键入 `v`、Cmd+Z 撤销,切预览再回源码,Shift+Cmd+Z 后保存仍为 `Alpha.\n`,预期 `vAlpha.\n`;可视模式同样失败。对照组直接撤销、重做并保存得到 `vAlpha.\n`。第二轮跨模式跳转后的重做与连续撤销场景已通过,本项覆盖编辑器内部撤销后的模式切换。方向:将编辑器内的编辑、撤销和重做纳入文件的内容历史,切换时保留历史游标与可重做事务,并增加两种编辑模式经预览返回后的重做断言。证据:`/tmp/ely-c143-r3-logs/local-redo.json`、`visual-redo.json`、`local-redo-control.json`、`r2-redo.json`、`r2-linear.json`、`r2-reverse.json`。 + +crates/ely-files/src/session/registry.rs:101 — 原路径消失时使用首次登记的文件身份判重;正常原子保存已更换文件身份,改名间隙因此再次建立可写会话,违反 PRD §6.1。真窗口打开 `a.md`、Cmd+S 保存一次、改名为 `b.md`;隔离副本仅在文件系统改名成功后延迟完成回调两秒,期间经正常 `open_file` 打开 `b.md`。完成后两个标签都指向 `b.md`,登记号分别为 `Some(0)`、`Some(1)`。首次打开后直接改名的第二轮场景已有一个标签;保存后再改名仍失败。独立单测经 `Session::load`、`save`、`rename_new`、`Sessions::open` 同样得到 `Fresh(1)`,预期 `Focus(0)`;改名前刷新当前文件身份后,单测和真窗口竞争均保持一个会话。方向:让登记中的身份随原子保存更新,改名前占用实际文件身份并在完成时迁移,覆盖保存后改名期间打开目标的竞争。证据:`/tmp/ely-c143-r3-logs/rename-native.log`、`rename-refresh.log`、`saved-rename-unit.log`、`saved-rename-refresh.log`、`rename-probe.diff`、`rename-refresh.diff`。 + +apps/ely-mark/web/src/main.js:98 — 旧 `shown` 确认回调先读取并清空共享 `asked`,随后才检查请求代次,会删除较新构建期间排队的模式请求,违反 MARK-02。真 CEF 探针在隔离副本仅注入预览构建完成延迟 500 毫秒、可视 `shown` 确认延迟 300 毫秒:从源码请求可视并排队预览,可视等待确认时切源码,随后请求预览并立即再请求源码;旧可视确认在较新预览完成前清空排队的源码请求,最终显示预览。将代次检查移到读取与清空 `asked` 之前,同一探针最终显示源码。第二轮三次切换的原版断言已通过,本项覆盖旧确认与较新构建重叠。方向:旧回调在代次失效时直接退出,每个构建只读取和清理属于自己的排队请求,并增加上述连续请求回归。证据:`/tmp/ely-c143-r3-logs/asked-native.log`、`asked-repair.log`、`asked-probe.diff`、`asked-latency.diff`、`asked-repair.diff`。原版 `mark-modes.sh`、`mark-content.sh`、`mark-session.sh` 均独立通过,五个相关 crate 单测 143 通过、1 忽略,Doc/Table/Show/PDF/Mark 编译检查通过;完整记录为 `/tmp/ely-c143-r3-logs/verification.log`。原项目 854 个文件的散列保持一致,隔离源码、副本依赖、CEF 缓存与独立构建目录已在写结论前删除。 diff --git a/tasks/reviews/C14.4-r1.md b/tasks/reviews/C14.4-r1.md new file mode 100644 index 0000000..d8fac74 --- /dev/null +++ b/tasks/reviews/C14.4-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-mark/web/src/visual.js:116 — 搜索行定位只把光标放到所属块的起点,丢失块内行位置(MARK-10)。复现:b.md 为 `# Other\n\nFirst line.\nwords too\n`,搜索 words,打开第 4 行结果,键入 Q 后保存。真窗口探针得到 `# Other\n\nQFirst line.\nwords too\n`,应改动的命中行保持原样。原 mark-places.sh 通过,把其样本改为这个多行段落后,现有保存断言报 FAIL。修复方向:把命中行的源码偏移映射到块内对应选区;覆盖多行段落、列表与代码块。 + +apps/ely-mark/web/src/main.js:85 — 可视及预览模式剥离 YAML front matter 后,直接沿用正文编辑器的 line 方法,传入的全文行号缺少偏移换算(MARK-10)。复现:文档为 `---\ntitle: Test\n---\n# Heading\n\nneedle here.\n\n# Other\n\nother paragraph.\n`,打开第 6 行 needle 的搜索结果。真实 CEF 的 `ely.reveal({line:6})` 把选区放到 other paragraph.;第 4 行 Heading 的定位也落到 Other。修复方向:在全文与正文之间换算行号,并处理位于 front matter 内的命中。 + +apps/ely-mark/web/src/visual.js:94 — 大纲只读取 heading 节点;preserve.js:72 将需要保留写法的标题替换成 ely_raw 后,其标题语义丢失(MARK-05)。复现:打开合法 CommonMark `# Heading #\n\nBody.\n`。真实 CEF 显示 visual、fallback false,但发送的 outline items 为 [],按标题跳转回报 nothing at {"heading":0}。修复方向:保留原始标题信息及其与编辑节点的位置对应,使原文保留的标题同样进入大纲并可跳转。 + +apps/ely-mark/src/side.rs:45 — 观察 Desk 时只重绘,当前文件搜索结果没有随活跃标签改变,也没有按标签身份作废(MARK-10)。复现:在 b.md 的 This file 范围搜索 words,切到 a.md;真窗口 AX 仍显示 b.md 的 Qwords too,并继续标示 This file。激活该结果会打开旧标签。修复方向:活跃标签变化时重新执行当前文件查询,后台回包同时校验查询代次与所属标签。 + +apps/ely-mark/src/side.rs:120 — 文件读取或解码失败后仅写日志并 continue,随后把其余命中作为正常搜索结果展示,用户看不到搜索范围有缺漏(agents.md 规则 2、MARK-10)。复现:打开含可读文件及 bad.md 的文件夹,令 bad.md 内容为 words 并 chmod 000,再搜索 words。真窗口日志出现 Permission denied,AX 只显示其余文件的命中,缺少失败提示;非 UTF-8 文件也走同一条路径。修复方向:将失败文件与原因返回界面,经现有错误反馈显示本次搜索的失败情况。 + +apps/ely-mark/src/desk.rs:73 — 最近列表按标签 id 只登记一次,同一会话重命名或另存后的新路径无法更新记录(MARK-05)。复现:mark-modes.sh 真窗口把 doc.md 重命名为 moved.md 并成功继续保存;读取 recent.json,仍只有 doc.md,moved.md 缺失。重启后的最近项因此指向已消失的文件。修复方向:按会话当前路径更新最近项,改名成功时迁移旧路径。独立构建后 mark-places.sh、mark-modes.sh、mark-session.sh、mark-content.sh 通过;ely-mark 8 项、ely-files 108 项、ely-tree 110 项单测通过,这些现有门缺少上述失败场景。 diff --git a/tasks/reviews/C14.4-r2.md b/tasks/reviews/C14.4-r2.md new file mode 100644 index 0000000..c396ad4 --- /dev/null +++ b/tasks/reviews/C14.4-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-mark/web/src/visual.js:153 — 搜索定位把源码中的命中次数直接对应到编辑器文本,链接地址等源码内容会打乱次序,随后静默落到块首(MARK-10)。复现:文件内容为 `# Other\n\nwords [link](words)\nwords too\n`,搜索 words,打开第 4 行结果,键入 Q 并保存。把此样本放入 mark-places.sh 后,真窗口测试失败,实际文件为 `# Other\n\nQwords [link](words)\nwords too\n`;目标行保持原样。修复方向:按命中行的源码位置映射到编辑节点,正确处理链接属性、格式标记与可见文本的对应关系,并增加该多行链接回归。上一轮六项原始场景已通过 mark-places.sh、mark-modes.sh;mark-content.sh、mark-session.sh、cargo check --workspace、ely-mark 8 项、ely-files 108 项及 ely-ui 21 项单测也通过。失败日志:`/tmp/ely-c144-r2-logs-enj2h8k5/places-url.log`。 + +apps/ely-mark/web/src/preserve.js:80 — 原文保留只保存顶层 heading 的级别,引用或列表块中的标题转换成 ely_raw 后丢失大纲语义(MARK-05)。复现:打开 `> First.\n>\n> Setext\n> ======\n>\n> Last with [[Wiki]].\n`,真实 CEF 回报 visual、fallback false,大纲 items 为 [];`ely.reveal({heading:0})` 回报 nothing at。切到源码后同一文件的大纲包含 Setext。把 mark-places.sh 的 Beta 标题及后续段落放入引用块,真窗口大纲跳转保存断言同样失败,Beta 保持原样;普通引用块中的标题控制样本可以跳转。修复方向:保留原文块内全部标题及对应源码位置,使可视与预览大纲能够列出并定位内部标题,增加引用与列表中的保留块回归。证据:`/tmp/ely-c144-r2-logs-enj2h8k5/reveal3.log`、`places-nested.log`。 + +apps/ely-mark/src/rename.rs:138 — 重命名成功后更新标签和最近项,文件树与文件夹搜索继续使用旧的 folder.files 快照(MARK-05、MARK-10)。复现:打开含 a.md 的文件夹及该文件,在窗口内重命名为 moved.md。磁盘只剩 moved.md,真窗口 AX 标签为 moved.md,AXRow 仍为 a.md;激活旧树行并按 Enter,应用报 `a.md did not open: No such file or directory`。窗口一直保持激活时,此路径持续失效。修复方向:成功重命名后同步刷新当前文件夹清单及相关搜索状态,增加打开文件夹后的重命名导航回归。证据:`/tmp/ely-c144-r2-logs-enj2h8k5/rename-after.ax`、`rename.err`。 diff --git a/tasks/reviews/C14.4-r3.md b/tasks/reviews/C14.4-r3.md new file mode 100644 index 0000000..f9e9fc7 --- /dev/null +++ b/tasks/reviews/C14.4-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-mark/web/src/visual.js:135 — 搜索定位的两处计数采用不同的重叠规则:前文用 split 计数,nth 每次前进一个字符,导致光标落在前一行(MARK-10)。复现:文件为 `# Other\n\nbanana\nana too\n`,搜索 ana,打开第 4 行结果,键入 Q 并保存。真窗口实际保存为 `# Other\n\nbanQana\nana too\n`,第 4 行保持原样。修复方向:统一前文计数与节点内定位的匹配规则,增加重叠子串跨行回归;隔离副本中让 nth 按查询长度前进后,真实 CEF 编辑结果为 `# Other\n\nbanana\nQana too\n`。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/mark-overlap.log`、`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/nav-dom.log`、`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/nav-repair.log`。 + +apps/ely-mark/web/src/visual.js:160 — 源码中链接地址的搜索命中直接使用编辑器内下一处可见匹配,定位跨到另一行(MARK-10)。复现:文件为 `# Other\n\n[link](words)\nwords too\n`,搜索 words,打开第 3 行的 `[link](words)` 结果,键入 Q 并保存。真窗口实际保存为 `# Other\n\n[link](words)\nQwords too\n`,目标链接地址保持原样;真实 CEF 的 visual、fallback false 控制样本同样复现。修复方向:按目标源码行及命中位置映射编辑节点,链接地址等隐藏命中转源码精确定位,并增加该回归。证据:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/mark-hidden-url.log`、`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/nav-dom.log`。上一轮三项原始场景已复核通过;mark-places.sh、mark-modes.sh、mark-content.sh、mark-session.sh、嵌套 Setext 标题变体、cargo check --workspace、ely-mark 8 项、ely-files 108 项及 ely-ui 21 项单测均通过。原源码 596 个文件散列一致,隔离副本及其构建目录已于写入结论前删除;检查记录:`/var/folders/g4/sj7njkr93jz0rgmg0b_zqnvr0000gn/T/ely-c144-r3-logs-p_gj2rg_/summary.log`。 diff --git a/tasks/reviews/C14.5-r1.md b/tasks/reviews/C14.5-r1.md new file mode 100644 index 0000000..8071669 --- /dev/null +++ b/tasks/reviews/C14.5-r1.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-mark/src/finding.rs:95 — 查找面板全窗口共用,各标签网页各自保留查询;替换直接调用当前网页的旧查询。复现:A、B 均为 `alpha beta`;A 设置 `alpha → X`,切 B 设置 `beta → Y`,再切 A 点击“Replace all”。面板显示 `beta → Y`,A 保存结果却为 `X beta`,应为 `alpha Y`(MARK-06)。真实窗口证据:`/tmp/ely-c145-r1-logs-5o5drxqq/tabs.log`;副本中替换前同步当前面板规格后,同一操作得到 `alpha Y`。修复方向:标签激活时同步查询与结果状态,查找步进及替换核对当前面板规格,消除过期的标签查询。 + +apps/ely-mark/src/finding.rs:50;apps/ely-mark/web/src/find.js:62、101 — 替换字段每次变化调用 seek,两种编辑器的 seek 都执行“下一个命中”,导致输入替换文本移动当前目标。复现:可视文档 `alpha beta alpha beta alpha`,搜索 `alpha` 选中第一处,在替换字段连续输入 `XY` 后执行单处替换,结果为 `alpha beta alpha beta XY`(MARK-06)。CEF 日志依次报告 current 为 0、1、2,见 `/tmp/ely-c145-r1-logs-5o5drxqq/probe-final.log`;副本只在搜索条件改变时移动命中后,结果为 `XY beta alpha beta alpha`。修复方向:更新替换文本时保留命中选区,把查询更新与显式步进分开,并覆盖两种模式的逐字符输入测试。 + +apps/ely-mark/web/src/source.js:61 — 图片 alt 直接拼入 Markdown,文件名中的方括号与反斜杠未经转义。复现:在已保存文档的源码模式拖入 `a]b.png`,attach 使用文件 stem `a]b`,生成 `![a]b](assets/a%5Db.png)`;切预览显示原文,图片节点为 0(MARK-07)。CEF 插入入口的实际文本与渲染结果见 `/tmp/ely-c145-r1-logs-5o5drxqq/probe-final.log`;副本转义 alt 后生成 `![a\]b](assets/a%5Db.png)`,预览出现图片节点。修复方向:正确转义 alt 的 Markdown 特殊字符,补充特殊文件名附件的保存与预览回归。 + +apps/ely-mark/src/attach.rs:106、120 — 后台复制捕获原目录,完成后直接向已经移动目录的文档插入 `assets/`。复现:拖入复制较慢的图片,在复制完成前将文档另存到另一目录;图片落在旧目录的 assets,相对链接按新目录解析,图片缺失(MARK-07)。延迟文件读取探针调用真实 attach、moved 路径,得到 `![slow](assets/slow.png)before`;旧目录有图片、新目录为空,CEF 报资源请求被拒绝,见 `/tmp/ely-c145-r1-logs-5o5drxqq/race.log`、`race.err`。副本增加目录身份核对后,过期操作保留 `before`。修复方向:绑定发起时的文档路径与目录身份,完成时核对;路径变化须显式中止,或把资源迁入当前目录后再插入有效链接,并加入延迟复制期间另存的测试。 + +tests/scripts/mark-tools.sh:136;tests/scripts/drop.swift:1;/Users/zacharyzhang/Documents/GitHub/Ely-GPUI-Components/src/buttons/group.rs:55、218 — 四处注释分别为 11、13、11、17 个英文单词,超过本轮明确要求的十词上限;组件文件已核对为 8f4729c。按空白分词即可复现,其中 drop.swift 的首行是 `Drags a file from a small window and drops it at x, y.`。修复方向:删去多余注释或压缩至一行十词以内,并让源码规则检查覆盖脚本与本轮组件改动。 diff --git a/tasks/reviews/C14.5-r2.md b/tasks/reviews/C14.5-r2.md new file mode 100644 index 0000000..5b1c5cf --- /dev/null +++ b/tasks/reviews/C14.5-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-mark/web/src/find.js:112、125 — 空查询仍调用 CodeMirror 的 findNext;该命令会为无效查询打开自带查找面板,clear 也留下这个面板。复现:打开文档,切源码,按 Cmd+F,保持查询为空;窗口顶部出现 Ely 查找框,底部同时出现 CodeMirror 的 Find/Replace 面板。按 Esc 后顶部查找框关闭,底部面板继续占据正文区域。真实窗口截图已检查,原始状态与关闭后的 panels=1 见 `/tmp/ely-c145-r2-logs-f2qn59cz/native.err`;副本按查询有效性守卫命令后,同一路径始终 panels=0,见 `native-patched.err`。修复方向:空查询及无效查询只更新搜索状态,各步进、替换入口先核对查询有效性,关闭操作完整清除查找界面;加入空查询、清空字段和 Esc 的窗口回归。 + +apps/ely-mark/web/src/find.js:24 — 正则校验使用无标志的 RegExp,两种编辑器实际使用带 Unicode 标志的正则,导致部分编译错误被回报为零命中。复现:源码模式搜索 `a{` 并开启正则,界面显示 No results;实际查询因 Incomplete quantifier 无效。搜索 `\a` 同样被编辑器拒绝,两种模式的回报均为 current=null、total=0,缺少 error,见 `/tmp/ely-c145-r2-logs-f2qn59cz/regex-state.log`、`probe.log`。副本按实际编辑器的标志校验后,窗口显示编译错误,两种模式均回报 Invalid escape,见 `patched-probe.log`。修复方向:让错误校验与编辑器采用相同的正则编译规则,显式显示编译失败,并覆盖这类受 Unicode 标志影响的表达式。 + +tests/scripts/comments.py:59 — 跨行块注释检测只覆盖 C/H,新增的 JavaScript 扫描会漏过跨行块注释。复现:在隔离副本的 find.js 末尾加入两行 `/* A comment` 和 `spanning lines. */`,运行 comments.py,退出码仍为 0,并声称全部注释均为单行,违反本轮明确要求。原始检查与扩展检测后的失败结果见 `/tmp/ely-c145-r2-logs-f2qn59cz/comments-mutation.log`。修复方向:覆盖 JavaScript 的跨行块注释,增加该变异必须失败的回归检查。 diff --git a/tasks/reviews/C14.5-r3.md b/tasks/reviews/C14.5-r3.md new file mode 100644 index 0000000..5c9b970 --- /dev/null +++ b/tasks/reviews/C14.5-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-mark/web/src/find.js:54 — 零宽正则命中后仅前进一个 UTF-16 单元;落到代理对中间时,Unicode 正则会再次返回前一个位置,循环持续收集同一命中,耗尽内存并使页面进程崩溃。复现:打开正文为 U+1F600 字符后接 ` alpha` 的文档,在可视模式开启正则并搜索 `(?=.)`;实际 CEF 探针停在 `before unicode seek`,随后报告 `TS_PROCESS_CRASHED`,V8 日志显示约 4 GB 堆分配失败。实际 docFinder 的 Node 探针也超时;副本按 Unicode 字符边界推进后,真窗口返回 7 个命中并完成。证据:`/tmp/ely-c145-r3-logs-v1e5g43e/native-baseline.out`、`native-baseline.err`、`native-patched.out`、`zero-width-node.log`。修复方向:让命中枚举始终向前推进并遵守 Unicode 字符边界,加入含代理对字符的零宽正则回归,验证查找完成与页面存活。 + +tests/scripts/comments.py:59 — 跨行块注释检测要求注释起点位于行首,仍漏过代码之后开始的 JavaScript 块注释。复现:在副本 find.js 末尾加入 `const marker = 1 /* A comment`,下一行为 `spanning lines. */`;运行 comments.py 返回 0,并声称全部注释均为单行,违反本轮单行注释规则。扩大起点检测的副本对同一变异返回 1;既有 comments-rules.sh 仅覆盖行首起点。证据:`/tmp/ely-c145-r3-logs-v1e5g43e/comments-inline.log`、`comments-inline-patched.log`。修复方向:识别代码行后的真实块注释起点,加入该跨行变异必须失败的回归检查。 diff --git a/tasks/reviews/C14.6-r1.md b/tasks/reviews/C14.6-r1.md new file mode 100644 index 0000000..6b6bbf1 --- /dev/null +++ b/tasks/reviews/C14.6-r1.md @@ -0,0 +1,13 @@ +FAIL + +apps/ely-mark/src/compare.rs:93 — 比较结果使用旧磁盘内容,却通过 `session.reload()` 接受当前磁盘指纹,随后自动保存会覆盖比较期间的新修改,违反 §14.4、FILE-05、QA-22。真窗口复现:基线为 `base\n\nend\n`,本地插入 `X`,外部追加 `tailA`,打开 Compare;外部再把末段改为 `tailB`,等待监听确认第二次变更后点击 Use the Merge。最终磁盘为 `Xbase\n\nend\n\ntailA\n`,`tailB` 丢失。证据:`/tmp/c146-stale2-1.log`、`/tmp/c146-probes-extra.log`。修复方向:比较快照携带对应磁盘指纹与本地修订,提交前核对;变化时重新比较,并让写入检查使用参与比较的指纹。`outside.rs` 的异步读取与重载也须保持内容和指纹配对。 + +apps/ely-mark/src/restore.rs:194 — 有路径文件的恢复内容直接进入原文件会话,下一次编辑会自动覆盖原件,违反 §6.3 的“恢复生成新副本,不先覆盖源文件”。真窗口复现:原件为 `ORIGINAL\n`,崩溃恢复点为 `RECOVERED\n`;重启恢复后原件仍为 `ORIGINAL\n`,键入 `X` 并等待自动保存,原件变为 `XRECOVERED\n`。证据:`/tmp/c146-recovered-1.log`、`/tmp/c146-probes.log`。修复方向:将恢复内容打开为独立恢复副本,保留原件身份与内容;写回原件需经过明确选择和外部变更检查。现有脚本只验证未命名文档的崩溃恢复,遗漏此路径。 + +apps/ely-mark/src/restore.rs:98 — 原路径无法 canonicalize 时直接跳过标签,已读出的有效恢复内容随之丢失,违反 MARK-12 和 §14.4。复现:文件有未保存文本并生成恢复点,外部删除原件后终止应用;重启时该标签及其恢复文本均被跳过。独立执行 `deleted_original_keeps_recovered_text`,实际等待恢复标签数为 0,期望为 1;在隔离副本中将缺失原件的有效恢复内容保留为无路径副本后,同一探针通过。证据:`/tmp/c146-mark-tests.log`、`/tmp/c146-deleted-repair.log`。修复方向:区分原件不可访问与恢复内容可用,保留有效恢复内容并提供另存入口;将原路径读取错误呈现给用户。 + +apps/ely-mark/src/restore.rs:280 — 退出开始时将 `keeping.leaving` 永久置为 true,取消退出后仍持续跳过会话清单写入,违反 MARK-12。真窗口复现:新建并输入 `first`,Cmd+Q 后 Cancel;再新建并输入 `second`,等待超过 10 秒。两个标签均已生成恢复点,`session.json` 仍只记录 Untitled 1;终止并重启后只恢复一个标签,第二份文本失去恢复入口。证据:`/tmp/c146-cancelled-restart.log`、`/tmp/c146-cancelled3-2.log`。修复方向:取消退出或关闭流程时解除清单冻结,并更新实际存活标签;已完成的放弃操作同步移除对应恢复记录。 + +apps/ely-mark/web/src/main.js:157 — 恢复光标只调用 `current.line(place.line, '')`,`Place` 也仅保存行号和滚动值,行内光标位置丢失,违反 MARK-12。真窗口复现:源码为 `abcdef\n`,将光标置于 `abcd|ef`,等待位置上报后正常退出;重启并键入 `X`,保存结果为 `Xabcdef\n`,期望为 `abcdXef\n`。证据:`/tmp/c146-caret-2.log`、`/tmp/c146-probes.log`。修复方向:位置上报、会话序列化与编辑器恢复共同保存并还原完整光标位置;增加行内位置的重启回归检查。 + +apps/ely-mark/src/compare.rs:81 — Keep Yours 成功解决冲突后,保留了被 `external_modified()` 停止的自动保存策略,后续编辑持续无法按 §6.2 在输入停止 1 秒后保存。真窗口复现:本地输入 `X`,外部写入触发冲突,Compare → Keep Yours,保存得到 `Xbase\n`;再输入 `Z` 并等待 3 秒,磁盘仍为 `Xbase\n`;Cmd+S 后得到 `XZbase\n`,确认新编辑已在文档中。证据:`/tmp/c146-stopped3-1.log`、`/tmp/c146-probes-verify.log`。修复方向:显式冲突解决成功后重新建立自动保存策略,并与已保存修订及后续编辑同步。现有 `mark-outside.sh` 在 Keep Yours 后进入合并流程,合并分支重建策略,掩盖了该缺陷。 diff --git a/tasks/reviews/C14.6-r2.md b/tasks/reviews/C14.6-r2.md new file mode 100644 index 0000000..862a96b --- /dev/null +++ b/tasks/reviews/C14.6-r2.md @@ -0,0 +1,11 @@ +FAIL + +apps/ely-mark/src/compare.rs:106 — Keep Yours 在异步导出完成后调用无条件覆盖的 `keep_mine`,比较指纹仅在点击时核对,第一轮的并行写入缺陷仍有窗口,违反 FILE-05、§14.4、QA-22。复现:基线为 `base\n\nend\n`,本地插入 X,外部追加 tailA;Compare → Keep Yours 后、导出完成前,外部写入 tailB。在隔离副本中仅给后台导出加入 750 毫秒延迟,最终磁盘为 `Xbase\n\nend\n`,新写入的 tailB 丢失。证据:`/tmp/c146r2-slowmine-result.log`。将比较指纹设为受检保存的基线并走普通保存后,同一探针保留 tailB,见 `/tmp/c146r2-slowmine-repair-result.log`。修复方向:把选定快照的指纹带到导出完成后的实际写入检查,遇到后续变化重新比较;Keep Yours 也须走该受检路径。 + +apps/ely-mark/src/restore.rs:184 — 所有无路径恢复项都匹配任意无路径标签,且 `Desk::observed` 在每次 Reported 后调用 `restore_shown`;一个标签会连续消耗多份恢复文本,违反 MARK-12、§6.3。真窗口复现:两个合法恢复槽分别含 `FIRST\n` 和 `SECOND\n`,重启时日志连续两次记录 `restores tab 1 as source`;第二份正文覆盖第一份,另一标签为空。等待 10 秒后,清单只引用第二个恢复槽和一个新的空标签槽,FIRST 的旧槽被 sweep 删除。证据:`/tmp/c146r2-multirestore3-result.log`、`/tmp/c146r2-multirestore3-1.log`。隔离副本限制每个标签只恢复一次后,两份正文均保留,见 `/tmp/c146r2-multirestore-fullrepair-result.log`。现有 `mark-outside.sh` 仅统计恢复提示数量,此场景仍能满足两条提示。修复方向:创建恢复标签时绑定恢复槽与唯一标签 ID,按该身份消费一次,并逐份断言重启后的正文及所属标签。 + +crates/ely-office/src/dialogs.rs:109 — 未保存询问的 Save 分支继续关闭流程;后续系统保存面板取消时,`save.rs:187` 仅写日志,`close_cancelled` 没有被调用,ElyMark 的 `keeping.leaving` 持续为 true,第一轮的清单冻结缺陷仍存在,违反 MARK-12。真窗口复现:新建并输入 first,Cmd+Q,按 AXButton 角色定位询问中的 Save,取消系统保存面板;再新建并输入 second,等待 12 秒。AX 有两个标签、两份恢复点已写入,session.json 仍只记录 Untitled 1;终止并重启仅恢复第一份。证据:`/tmp/c146r2-savecancel-button-result.log`、`/tmp/c146r2-savecancelbutton-2.log`。保存面板取消时调用关闭取消钩子后,清单记录两个标签,见 `/tmp/c146r2-savecancel-button-repair-result.log`。修复方向:关闭流程所有后续取消与失败出口统一结束关闭状态,并恢复会话清单更新;增加 Save → 系统面板 Cancel 的重启回归。 + +apps/ely-mark/src/desk.rs:75 — 取消退出只解除 leaving 标记,已放弃标签的清单记录与恢复槽仍等到下一次十秒定时任务才删除,违反 §6.2 的显式放弃契约,也违背 TASKS.md 的“随即删去”声明。真窗口复现:恢复含 FIRST 的 Untitled 1,再新建含 SECOND 的 Untitled 2;Cmd+Q,对第一个标签选 Don't Save,对第二个选 Cancel,立即终止应用。此时清单与恢复目录仍保留第一个标签;重启显示 Untitled 1 的恢复提示,已放弃内容再次返回。证据:`/tmp/c146r2-discarded2-result.log`。隔离副本在取消关闭时立即同步实际标签与恢复槽,同一探针只恢复 Untitled 2,见 `/tmp/c146r2-discarded2-repair-result.log`。修复方向:完成放弃或关闭时立即持久化结果并清理对应恢复槽,处理在途恢复任务,保证随后崩溃也遵守放弃结果。 + +apps/ely-mark/src/view.rs:28 — 新增 column 为必填反序列化字段,第一轮已写出的合法 `{line, scroll}` 光标记录会导致整个应用启动失败,违反 MARK-12。复现:session.json 保留有效文件路径、source 模式和 `place: {"line": 1, "scroll": 0}`,运行当前已构建的 ElyMark;进程退出码为 1,报 `missing field column`,标签和正文均未打开。证据:`/tmp/c146r2-oldplace.log`。在隔离副本兼容旧记录的缺列字段后,同一格式恢复为 source,原件字节保持 `abc\n`,见 `/tmp/c146r2-oldplace-repair-result.log`。修复方向:为既有会话格式迁移新增列字段,保留已有标签、正文、模式、行号和滚动位置,并加入第一轮会话文件的启动回归。 diff --git a/tasks/reviews/C14.6-r3.md b/tasks/reviews/C14.6-r3.md new file mode 100644 index 0000000..6b9df05 --- /dev/null +++ b/tasks/reviews/C14.6-r3.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-office/src/save.rs:332 — 退出保存冲突框的 Keep Mine 仍调用无条件覆盖的 `save_overwrite`,导出期间的新外部写入会被覆盖,违反 FILE-05、§14.4、QA-22。真窗口复现:基线 `base\n\nend\n`,本地插入 X,外部追加 tailA;Cmd+Q → Save → Keep Mine,在导出完成前外部改成 tailB。隔离副本仅给 `Mark::write` 加入 750 毫秒延迟,最终磁盘为 `Xbase\n\nend\n`,tailB 丢失,应用退出。同样延迟下 Compare → Keep Yours 能保留 tailB。证据:`/tmp/c146r3-slowclosemine-result.log`、`/tmp/c146r3-slowmine-result.log`。修复方向:退出冲突的保留本地分支也携带选定磁盘版本的指纹,走受检保存,在实际写入时遇到新变化继续报冲突。隔离副本改为指纹基线加普通保存后,tailB 保留且应用停留在冲突询问,见 `/tmp/c146r3-slowclosemine-repair-result.log`。 + +crates/ely-office/src/dialogs.rs:132 — Reload from Disk 分支丢弃挂起的 `then`,重开标签后退出流程停止,ElyMark 的 `keeping.leaving` 持续为 true,会话清单冻结,违反 MARK-12。真窗口复现:有路径文档输入本地内容并发生外部冲突,Cmd+Q → Save → Reload from Disk;随后新建并输入 AFTERRELOAD,等待超过十秒。恢复点已包含 `AFTERRELOAD\n`,session.json 仍仅记录原文件;终止并重启后,新正文失去恢复入口。证据:`/tmp/c146r3-conflictreload-result.log`。修复方向:Reload 分支明确结束挂起的关闭状态,调用关闭取消钩子恢复清单更新,再重开文档,并增加该路径的崩溃重启回归。隔离副本加入该钩子后清单记录两个标签,重启恢复 AFTERRELOAD,见 `/tmp/c146r3-conflictreload-repair-result.log`。 + +apps/ely-mark/src/restore.rs:336 — 退出时的冻结清单保留保存前的路径;未命名标签首次保存成功后,关闭钩子收到的是有路径标签,此分支留下旧的 `path: null`,违反 MARK-12 的打开标签恢复要求。真窗口复现:新建并输入 SAVEDTEXT,Cmd+Q → Save,在系统面板保存到新文件并完成退出。磁盘文件含 `SAVEDTEXT\n`,session.json 仍记录 Untitled 1 和 `path: null`;重启恢复标签数为 0。证据:`/tmp/c146r3-savedquit-result.log`。修复方向:按恢复槽身份,将退出期间保存成功后的路径和名称同步到冻结记录并持久化,再完成关闭;增加首次保存后退出的重启回归。隔离副本同步路径和名称后,重启恢复标签数为 1,见 `/tmp/c146r3-savedquit-repair-result.log`。 diff --git a/tasks/reviews/C14.7-r1.md b/tasks/reviews/C14.7-r1.md new file mode 100644 index 0000000..be644e3 --- /dev/null +++ b/tasks/reviews/C14.7-r1.md @@ -0,0 +1,17 @@ +FAIL + +apps/ely-mark/src/export.rs:256 — PDF 导出将源文件保护参数传为 `None`,会经符号链接覆盖正在编辑的 Markdown,违反 FILE-06。真窗口复现:打开 `t.md`,创建 `alias.pdf -> t.md`,导出 PDF 到 `alias.pdf` 并确认 Replace;随后 `t.md` 的文件头变成 `%PDF-`,应用报告导出成功。修复方向:将来源会话身份带到 PDF 写入阶段,以文件身份核对目标与打开的文档,拒绝覆盖。副本加入身份检查后,相同操作显示错误并保留 `# Title` 原文。 + +apps/ely-mark/src/paper.rs:152、apps/ely-mark/src/export.rs:353 — 资源目录与图片文件名直接拼成 URL,保留字符会改变 URL 的含义,破坏 MARK-11 的带资源 HTML。实际调用 `write`:将引用 `a%23b.png` 的文档导出为 `report#1.html`,生成 `src="report#1_files/1-a#b.png"` 与 `href="report#1_files/export.css"`;浏览器将 `#` 后的内容作为片段,图片和样式路径均错误。修复方向:对资源 URL 的路径部分做百分号编码,再做 HTML 属性转义。原版复现断言失败;副本编码后断言通过。 + +apps/ely-mark/src/paper.rs:55 — 图片扫描只识别双引号的 ` src="`,遗漏合法 HTML 图片,破坏 MARK-11 的资源打包。页面实测将 `` 原样交给导出;对存在的 `pic.png` 调用 `inline` 后仍得到相对引用,且缺图列表为 `[]`;调用资源目录导出也会遗漏该图片。自包含 HTML 移到其他目录后丢图,PDF 的暂存页面也失去图片路径。修复方向:按 HTML 标签和属性语法解析图片来源,覆盖单引号等合法写法,并让遗漏资源进入错误结果。 + +apps/ely-mark/web/src/render.js:48 — 纯文本输出在转换时丢弃整个 HTML 原文块,导致 MARK-11 输出丢失正文。实际 CEF 页面打开 `
VISIBLE_RAW_TEXT
\n\nEND\n` 后请求纯文本,返回结果只有 `END`;HTML 输出仍包含该可见正文。修复方向:将 HTML 块中的可见文字纳入文本转换,并加入核对实际正文的导出测试。 + +apps/ely-mark/src/export.rs:324 — HTML 导出忽略渲染结果的 `failed` 列表,吞掉 Mermaid 解析错误;PDF/打印路径同样遗漏该列表,违反项目错误暴露规则及 §14.2 的语法错误要求。实际 CEF 渲染 `graph TD; A -->` 返回带行号的解析错误;真窗口 HTML 导出仅留下代码块,应用记录 `mark: exported` 并显示成功。修复方向:将图表错误及位置传递到所有输出完成路径,向用户显示失败或明确的部分输出提示,并覆盖错误图表场景。 + +apps/ely-mark/src/export.rs:187 — PDF/打印阶段丢弃 `paper::inline` 返回的缺图列表,最终以空错误列表报告成功。真窗口打开引用 `missing.png` 的文档并导出 PDF,CEF 拒绝加载暂存目录中的图片,PDF 只留下图片替代文字,应用仍记录 `mark: exported` 并显示成功。修复方向:让缺图结果随暂存、打印和最终写入传递,完成时明确报告缺失资源;输出测试应核对缺图提示。 + +apps/ely-mark/src/vcs.rs:49 — `rev-parse` 的所有失败均被转换为 `Tracked::Outside`,吞掉真实 Git 错误。实际创建仓库后将 `.git/config` 写为 `[bad\n`,Git 返回配置解析失败,`ask` 却返回 `Outside`,界面隐藏 Git 状态和错误。修复方向:识别明确的仓库外结果,其余失败保留 Git 错误并显示 `Unknown`。原版错误暴露断言失败;副本区分错误后通过。 + +apps/ely-mark/src/vcs.rs:52 — 对仓库根路径调用 `trim()`,裁掉合法路径末尾的空格,破坏 MARK-13 的 ElyTree 联动。实际在名为 `repo ` 的目录中创建仓库并打开 `t.md`,`ask` 返回的根路径变为 `repo`;点击状态项会将这个错误目录交给 ElyTree。修复方向:只移除 Git 输出的行结束分隔符,完整保留路径字符。原版路径身份断言失败;副本保留末尾空格后通过。 diff --git a/tasks/reviews/C14.7-r2.md b/tasks/reviews/C14.7-r2.md new file mode 100644 index 0000000..f42bb58 --- /dev/null +++ b/tasks/reviews/C14.7-r2.md @@ -0,0 +1,9 @@ +FAIL + +apps/ely-mark/src/export.rs:296、apps/ely-mark/src/export.rs:132 — 导出仅保护发起标签的文件身份,仍会覆盖其他已打开标签的原件,违反 FILE-06。真窗口复现:同时打开 `t.md` 与 `b.md`,选中 `t.md`,建立 `other.pdf -> b.md`,导出 PDF 到 `other.pdf` 并确认 Replace;`b.md` 的文件头变为 `%PDF-1.4`,应用报告已导出。上一轮的本标签符号链接用例已通过。修复方向:所有输出在最终写入前按文件身份核对全部打开标签;副本在 PDF 写入前加入 `holder` 检查后,同一操作显示错误,`b.md` 保持 `# Other document`。 + +apps/ely-mark/src/paper.rs:102、apps/ely-mark/src/paper.rs:66、apps/ely-mark/src/paper.rs:25 — 图片扫描仍按字符串寻找标签结束和属性名,遗漏或改错合法 HTML 图片,违反 MARK-11。真窗口导出含 `a > b` 的文档,HTML 保留相对图片路径,缺图列表为 `[]`;移走导出文件即丢图。实际 `inline` 探针还确认:`example src='trap.png'` 会改写 alt 中的文字并留下真实 src;`` 在 `pic.png` 存在时仍报缺图。现有四项 paper 单测通过,三个新增合法 HTML 用例均失败;实际 CEF 渲染保留了这些写法。修复方向:按 HTML 标签、带引号属性和字符引用的完整语法提取并解码真实 src,覆盖上述资源打包场景。 + +apps/ely-mark/web/src/render.js:51 — HTML 原文块经 `textContent` 转成单个文本节点,丢掉块边界与换行分隔,导致纯文本输出合并正文,违反 MARK-11。实际 CEF 页面打开 `
ONE
TWO
` 后请求纯文本,得到 `ONETWO`;上一轮的 `VISIBLE_RAW_TEXT` 用例已通过。修复方向:按 HTML 的可见文本结构转换,保留块与换行的分隔;副本经 DOM 转 HAST 再取文本后,同一输入得到 `ONE TWO`。 + +apps/ely-mark/src/export.rs:167、apps/ely-mark/src/export.rs:195 — 新增缺失内容提示将含正文片段的 Mermaid 解析诊断写入日志,违反 agents.md 第 4 条“日志不含正文”。真窗口导出含 `graph TD; PRIVATE_DOCUMENT_TEXT -->` 的 Mermaid 块,WARN 日志的 `Lost` 调试值包含原文片段 `...E_DOCUMENT_TEXT -->`;同一诊断经提示组件再次出现在 INFO 日志,PDF 输出也复现。修复方向:日志只记错误类别、位置与数量,并处理提示组件记录文案的路径,使正文诊断保持在界面内。 diff --git a/tasks/reviews/C14.7-r3.md b/tasks/reviews/C14.7-r3.md new file mode 100644 index 0000000..53e68ab --- /dev/null +++ b/tasks/reviews/C14.7-r3.md @@ -0,0 +1,5 @@ +FAIL + +apps/ely-mark/web/src/render.js:43 — 图片收集只遍历 `img[src]`,遗漏 `srcset` 与 `picture/source` 的资源,并在导出后保留其相对路径,违反 MARK-11。真窗口打开含 `` 和 `` 的文档,两张 PNG 均存在;导出自包含 HTML、带资源目录 HTML 和 PDF,应用均显示成功。将 HTML 移到其他目录后,实际 CEF 的两个 `currentSrc` 都指向未打包的 `assets/high.png`,`naturalWidth` 均为 0;资源目录只有备用图 `1-pic.png`,PDF 只有损坏图片图标。当前 16 项单测、`mark-output.sh` 与 `mark-content.sh` 均通过。修复方向:收集并重写响应式图片的资源来源,或将所选来源归一为已打包的 `src`,同时传递缺失资源;隔离副本归一来源后,两种 HTML 的两张图片均为 1×1,PDF 也包含两张 1×1 图片。 + +apps/ely-mark/web/src/render.js:56、apps/ely-mark/web/src/render.js:58 — KaTeX 的解析诊断被丢弃,`failed` 仅包含 Mermaid 的失败,公式错误经静默回退后仍报告完整导出成功,违反错误暴露规则与 §14.2 的语法错误位置要求。真窗口导出第 3 行为 `$\privateDocumentToken$` 的文档:HTML、带资源目录 HTML、PDF 均显示 `Exported`,输出只有红色命令原文,缺少诊断与行号。实际 CEF 渲染返回 `failed: []`;同一渲染管线保留 `VFile` 时可读到第 3 行的 `parseerror` 与 `Undefined control sequence`。修复方向:保留并处理 KaTeX 的文件诊断,将错误类别和位置传递至 HTML、PDF、打印的完成路径,日志保持正文隔离,并覆盖错误公式场景;副本保留诊断后,同一 CEF 请求明确返回公式错误所在行。 diff --git a/tasks/reviews/C16.1-r1.md b/tasks/reviews/C16.1-r1.md new file mode 100644 index 0000000..9535591 --- /dev/null +++ b/tasks/reviews/C16.1-r1.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-tree/src/tree.rs:277:对整个文件路径做 canonicalize 会把 Git 跟踪的符号链接换成目标文件,破坏 §16 的文件历史交接。复现:同一仓库分别提交 real.md(REAL_FILE)和 link.md -> real.md(LINK_ONLY),把 link.md 交给 ElyTree;本轮构建记录 `file history of real.md`,原生 AX 显示 REAL_FILE。链接指向仓库外 Markdown 时,交接报 `prefix not found`,已提交链接的历史打不开。修复方向:解析父目录的真实路径,保留 Git 跟踪的文件名,再计算仓库相对路径;补链接文件的交接回归。副本按此修正后,AX 分别显示 LINK_ONLY 和仓库外链接的 EXTERNAL_LINK。 + +crates/ely-ui/src/shell/root.rs:287:系统默认程序打开分支调用无结果的 open_with_system 后固定返回 Ok(()),底层启动失败被吞掉,规则 2 要求的错误反馈缺失。复现:导出的 TXT 被移走后点击提示中的 Open;真实 Root 按钮探针中,系统命令输出 `The file ... does not exist.`,成功提示被关闭,界面没有失败提示。修复方向:通过可返回实际启动结果、检查退出状态的系统打开接口执行,并把失败交回现有错误提示。副本检查同一命令退出状态后,出现 Danger 提示。 + +tests/scripts/mark-output.sh:85:输入自定义文件名后只发送一次回车,保存面板的提交没有得到保证,导致脚本在到达 PDF 打开验收前失败。本机两次独立运行都报 `no HTML and Folder export`;AX 确认面板仍显示 Save,文件名已是 report#1.html。副本仅把该步改为 AXPress Save 后,完整脚本通过,日志确认 ElyTree 的文件历史和 ElyPDF 的输出新标签。修复方向:按实际保存面板的 Save 按钮提交,并确认面板关闭。构建、测试、独立重跑和原生探针的小日志保存在 `/tmp/C16.1-r1-logs-20261008-0636/`,完整执行结果见 verification.log;探针副本及其专用 target 已删除,原项目源码散列和文件清单核对一致。 diff --git a/tasks/reviews/C16.1-r2.md b/tasks/reviews/C16.1-r2.md new file mode 100644 index 0000000..b750d6b --- /dev/null +++ b/tasks/reviews/C16.1-r2.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-tree/src/tests.rs:328:新增的符号链接回归测试缺少平台条件,直接调用 `std::os::unix::fs::symlink`,导致 Windows 测试编译失败。复现:使用项目钉住的 Rust 1.96,将该单测及其调用的 `inside()` 原样提取到使用真实 tempfile 依赖的独立 Cargo 探针,执行 `cargo check --tests --target x86_64-pc-windows-msvc --offline`;两次 symlink 调用均报 `E0433: cannot find unix in os`。修复方向:给 Unix 专用测试加 `#[cfg(unix)]`,或按平台调用符号链接接口。探针仅添加该条件后,Windows 检查与 macOS 单测均通过。编译、修复验证及上一轮三项缺陷的复验记录保存在 `/tmp/C16.1-r2-20261008-024755/logs/`;探针副本及其构建目录已删除,原项目源码散列和文件清单核对一致。 diff --git a/tasks/reviews/C16.1-r3.md b/tasks/reviews/C16.1-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C16.1-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C17.1-r1.md b/tasks/reviews/C17.1-r1.md new file mode 100644 index 0000000..d635d74 --- /dev/null +++ b/tasks/reviews/C17.1-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-lok/src/worker.rs:306 — 文档会话编号直接取原生指针,释放后的地址会被下一份文档复用,破坏 §17.2 的会话隔离,并使新文档的事件流失效。实测通过公开 API 连续载入、关闭同一 FODT,保留旧 Doc 的 clone:第 4 次载入复用了第 2 次的编号,旧 Doc.size 返回新文档的尺寸;对新文档键入后,poll_events 仍返回 seq=0、空正文和空状态。原因是 worker 用 doc.id() 重新登记文档,而 Feed.closed 保留该编号并持续丢弃其事件。修复方向:为每次载入分配唯一会话编号,分离协议会话与原生指针,并让文档请求、回调与事件订阅共同使用该会话;加入关闭再打开及旧句柄访问的回归测试。证据:/tmp/ely-c171-probes.log。 + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:157 — 编辑请求只读文档编号和操作参数,缺少 §17.2 要求的预期修订,worker 会执行基于旧页面布局的请求。实测打开 A、B、C 三页,history=0 时记录 B 的页码 1;删除 A 后 history=1,提交此前准备的 rotate_pages(doc, &[1], 90),返回成功标记 "2",实际旋转了 C,C 的尺寸从 300×500 变为 500×300。LOK 请求在 crates/ely-lok/src/client.rs:287 同样只发送请求编号与操作 payload。修复方向:为依赖文档状态的请求携带会话与预期修订,在访问内核和执行副作用前校验,过期请求返回明确错误;加入过期编辑及读取的回归测试。证据:/tmp/ely-c171-probes.log。 + +crates/ely-pdf-engine/src/client.rs:25 — Doc 仅保存 worker 内递增的 u64,各 worker 都从 1 开始,客户端接受其他 worker 会话的旧句柄。实测 worker A 打开 A.pdf 得到 Doc(1),销毁 A 后 worker B 打开 B.pdf 也得到 Doc(1);调用 B.close(A 的旧 Doc) 返回 Ok(()),随后 B.pages(B 的有效 Doc) 报 "no document 1"。旧会话请求因此可以关闭新会话的文档。修复方向:将 Doc 绑定到唯一 worker 会话,并在客户端发送和 worker 接收时核对会话身份;加入 worker 重启及跨 worker 句柄的回归测试。证据:/tmp/ely-c171-probes.log。 + +crates/ely-ipc/src/lib.rs:13 — write_frame 允许写出超过 MAX_FRAME 的消息,read_frame 在第 48 行拒绝相同消息,发送端可把正常数据请求变成协议故障。实测写入 MAX_FRAME+1 字节返回 Ok(()),产生 67,108,878 字节,随后读取报 InvalidData("frame over the cap");LOK 粘贴同样大小的 payload 会触发 worker 退出,连带中断其他打开文档。修复方向:写入任何字节前检查双方共同的大小上限,对超限请求或结果显式报错,并补充边界与管道保持可用的测试。临时副本增加发送前检查后,同一断言通过且输出长度为 0;原实现断言失败。证据:/tmp/ely-c171-cap.log。 + +crates/ely-lok/src/client.rs:291 — 输出读取失败被 frames::read_frames 转成 None,request 随即无期限等待 child.wait;仍存活的 worker 使调用永远无法返回错误,原始协议错误也仅留在日志中。实测假 worker 正确完成版本与图块握手,收到 SET_AUTHOR 后发出超限帧头并保持存活:调用 4 秒后仍阻塞,sample 栈显示 Worker::set_author → Worker::request → __wait4。修复方向:通过回复通道传递实际读取错误,协议或输出通道故障时终止并回收 worker,再向调用方返回该错误;加入损坏输出且子进程存活的失败路径测试。证据:/tmp/ely-c171-bad-frame.log。 + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:333 — 原生文档打开成功后,locked、unlock 或 journal 失败直接经 ? 返回,Open 没有释放逻辑,尚未登记的文档也无法经 CLOSE 回收,违反 §17.2 的 FFI 资源释放要求。实测同一 worker 连续打开一个 787,252 字节的有效 PNG:MuPDF 打开图像后 journal 每次报 "not a PDF",120 次失败使 RSS 从 5,392 KiB 增至 103,920 KiB,之后有效 PDF 仍可打开。临时副本在 journal 错误路径调用 close 后,同样 120 次失败的 RSS 增量降至 1,488 KiB。修复方向:让打开全过程拥有明确的资源守卫,任一初始化或 repair 失败都释放原生对象、撤销登记并清理临时文件;加入重复打开失败的资源回归测试。证据:/tmp/ely-c171-native-leak.log。 diff --git a/tasks/reviews/C17.1-r2.md b/tasks/reviews/C17.1-r2.md new file mode 100644 index 0000000..21f27f4 --- /dev/null +++ b/tasks/reviews/C17.1-r2.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-lok/src/client.rs:263 — 上一轮第二项的 LOK 部分仍缺少预期修订:run 只发送文档号、命令与参数,worker 直接针对当前内核状态执行,违反 PRD §17.2。真实 LOK 探针打开 A、B、C 三个工作表,保留当时针对 B 的序号 2 与 Doc clone;执行 sheet.delete("1") 删除 A 后,以旧句柄提交 sheet.rename("Renamed B", "2") 返回 Ok,实际将 C 改名,最终工作表为 B、Renamed B。修复方向:为依赖文档状态的 LOK 请求携带预期修订,并在执行内核操作前校验;加入结构编辑之后提交旧请求的真实 worker 回归测试。证据:/tmp/ely-c171-r2-probes.log。 + +crates/ely-lok/src/worker.rs:315 — 每个 worker 的文档会话号均从 1 开始,client.rs:57 的 Doc 仅保存该数字,跨 worker 句柄可访问、关闭另一会话的文档,违反 PRD §17.2 的会话隔离。真实探针分别在 worker A、B 载入首份文档:A 的 Doc.size(&mut B) 返回 B 文档尺寸;B.close_doc(A 的 Doc clone) 返回 Ok;随后 B 的有效 Doc.size 报 "no such document"。每次 load 自增只覆盖同一 worker 内的指针复用。修复方向:将 Doc 与唯一 worker 会话绑定,发送前与 worker 接收时校验会话,并让事件订阅遵循相同归属;补充跨 worker 与重启后的旧句柄回归测试。证据:/tmp/ely-c171-r2-probes.log。 + +crates/ely-pdf-engine/src/structure.rs:209 — detach 使用 whole 将预期修订写成 ANY,导致依赖已读取附件身份的删除跳过 current 校验;第 199 行的附件读取同样跳过该校验,违反 PRD §17.2。真实 PDF worker 探针在修订 1 添加 report.txt 并记录 key=n:report.txt;删除后在修订 3 添加同名的新附件,再以修订 1 的 Doc 和旧 key 提交 detach,返回成功标记 "4",修订 3 的新附件被删除,附件列表变为空。修复方向:附件按 key 读取、删除等依赖文档状态的请求使用 Doc 的预期修订,在读取或修改前拒绝过期请求;加入同名附件替换后旧读取、旧删除均被拒绝且新附件字节保留的回归测试。证据:/tmp/ely-c171-r2-probes.log。 diff --git a/tasks/reviews/C17.1-r3.md b/tasks/reviews/C17.1-r3.md new file mode 100644 index 0000000..03d95cf --- /dev/null +++ b/tasks/reviews/C17.1-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-lok/src/commands.rs:66 — 修订校验仅识别 Index 与 nTabNumber,notes.set 的 Part 等状态相关编号跳过 EXPECT,违反 PRD §17.2。真实 LOK worker 打开 One、Two、Three,在修订 2 保留指向 Two 的备注请求;删除 One 后修订为 3,再用旧句柄执行 notes.set("B edit applied to C", "1") 返回 Ok,Three 的备注实际被覆盖。修复方向:完整识别状态相关编号,并在 worker 操作前校验读取时的修订;加入删除前序幻灯片后旧备注请求被拒、目标备注保留的回归测试。原始及临时修复探针分别见 /tmp/ely-c171-r3-probes.log、/tmp/ely-c171-r3-notes-fixed.log。 + +apps/ely-table/src/sheets.rs:298 — 重命名对话框只保存工作表编号,确认时 run_in 重新取最新 read_at,把旧编号绑定到结构编辑后的新修订,绕过过期保护,违反 PRD §17.2。真实 worker 加 GPUI 探针建立 A、B、C,B 为当前;删除 A 的命令入队后、落地前打开 B 的重命名框,待后台重读得到 B、C,再确认 Renamed B,实际工作表变为 B、Renamed B,C 被改名。修复方向:对话框保存编号对应的 Doc 修订,确认时用 run_on 提交该句柄,并测试对话框期间结构编辑的竞态。证据:/tmp/ely-c171-r3-rename.log;临时修复后同一探针通过。 + +crates/ely-lok/src/view/lane.rs:114 — 请求失败时丢失 worker 已递增的修订:worker/revision.rs:64 在执行前加一,input::run 在错误路径提前返回,视图与应用缓存继续保留旧修订,后续编号命令持续被 stale 拒绝。真实 worker 加 GPUI 探针在空白工作表 A1 执行 cells.split;拆分失败后 worker 修订为 1,视图及缓存仍为 0,再重命名第一张工作表为 Plan 被拒,工作表仍叫 工作表1。修复方向:失败路径也在后台取得并交回实际修订,驱动缓存重读,同时保留原始操作错误;加入失败之后继续执行编号命令的回归测试。证据:/tmp/ely-c171-r3-split.log;临时修复后同一探针成功改名。 + +crates/ely-lok/src/view.rs:279 — 新增的编号命令拒绝规则阻断 ElyDoc 删除现有制表位;apps/ely-doc/src/props.rs:166 的属性栏仍调用 view.run,paragraph.tab 的删除参数 Index=0 每次都会立即报错。真实 worker 加 GPUI 探针先通过属性栏添加 1134 的制表位,再沿删除按钮相同路径提交 ["0", "0", "true"],得到 "paragraph.tab names something by number; run it at the state it was read",制表位保留。修复方向:属性栏按所展示制表位对应的视图修订调用 run_at,并覆盖添加后删除现有制表位的完整调用链。证据:/tmp/ely-c171-r3-tabs.log;临时修复后同一探针通过。 diff --git a/tasks/reviews/C18.1-r1.md b/tasks/reviews/C18.1-r1.md new file mode 100644 index 0000000..74a6db8 --- /dev/null +++ b/tasks/reviews/C18.1-r1.md @@ -0,0 +1,11 @@ +FAIL + +crates/ely-ui/src/settings/window.rs:94 — 作者字段只在窗口创建时读取一次,外部更新后,旧字段失焦会覆盖其他应用刚写入的值。真窗口复现:以 author=Ana 打开 ElyPDF 设置并聚焦作者框,将共用配置的 author 改成 Bob,等待一秒;AX 字段仍为 Ana,直接按 Tab 后磁盘被写回 Ana。证据:/tmp/c18-r1-native-probe.log、/tmp/c18-r1-external-author.ax。修复方向:跟随共用设置更新尚未编辑的字段,提交依据本次实际编辑及其基准值;覆盖“外部更新后原字段直接失焦”的回归场景。 + +crates/ely-ui/src/settings/window.rs:111 — Git 校验任务各自直接落盘,旧任务较晚完成会覆盖后一次提交,回调也缺少输入版本核对。真窗口复现:先提交 A(--version 等待五秒后执行随带 Git 的脚本),随后提交 B(随带 Git 的路径);B 先保存成功,A 完成后配置被改回 A,AX 字段继续显示 B。证据:/tmp/c18-r1-git-race.log。修复方向:为校验、写入与错误回显绑定提交序号,保证落盘遵守最新提交,丢弃过期任务的写入和回显;补延迟校验的回归测试。 + +crates/ely-ui/src/settings/mod.rs:109 — 写入失败刚设置的 Current.error 被紧接着的成功重读清除,保存错误仅进入日志。真窗口复现:保留有效的浅色配置,将同目录 .settings.lock 建成目录,点击 Dark;日志报告 appearance not saved: Is a directory,磁盘仍为 light,设置窗口的 AX 树没有错误提示。证据:/tmp/c18-r1-native-probe.log、/tmp/c18-r1-failed-write.ax。修复方向:更新读入状态后保留此次写入失败,向用户显示保存错误,并覆盖“配置可读、写入失败”的测试路径。 + +crates/ely-ui/src/settings/window.rs:76 — ElyTree 应用偏好读取失败时被换成空字符串,git_error 初始化为 None;窗口的通用错误 Banner 只读取共用配置错误,应用配置错误因此只出现在日志。真窗口复现:共用 settings.json 有效,ely-tree/settings.json 内容为 {,启动 ElyTree 并打开设置的 Git 页;日志记录解析失败,AX 显示空 Git program 字段和默认随带 Git 的说明,窗口缺少读取错误提示。证据:/tmp/c18-r1-corrupt-tree.log、/tmp/c18-r1-corrupt-tree.ax。修复方向:保留并显示应用配置的读取错误,字段状态明确关联该错误;补共用文件正常、应用文件损坏的窗口测试。 + +crates/ely-platform/src/git.rs:43(同类路径 apps/ely-tree/src/git.rs:132)— 版本校验只解析 stdout,吞掉进程失败状态及 stderr。独立调用复现:程序向 stdout 输出 git version 2.56.0,向 stderr 输出 fatal: version query failed,并退出 7;system_version 返回 Ok("git version 2.56.0"),设置提交会接受该失败结果。证据:/tmp/c18-r1-git-probe.log。修复方向:先核对 --version 的成功退出状态,将失败状态与错误输出传回,再解析版本;两条版本读取路径均补带合法版本文本的非零退出测试。 diff --git a/tasks/reviews/C18.1-r2.md b/tasks/reviews/C18.1-r2.md new file mode 100644 index 0000000..452ea78 --- /dev/null +++ b/tasks/reviews/C18.1-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-ui/src/settings/window.rs:132 — 作者字段在写入成功前将用户输入记为 author_base,保存失败后 follow_author 将其视为可替换的未编辑字段,违反 PRD UX-06 的失败保留输入要求。真窗口复现:共用配置 author=Ana,在 ElyPDF 设置中输入 Bob,将 .settings.lock 建成目录后按 Enter;AX 字段从 Bob 回到 Ana,磁盘仍为 Ana,错误 Banner 显示 Is a directory。恢复可写后再次按 Enter,Bob 仍未保存。证据:/tmp/c18-r2-native-final.log、/tmp/c18-r2-author-typed.ax、/tmp/c18-r2-author-failed.ax、/tmp/c18-r2-author-retry.ax。修复方向:将已保存基准与待保存编辑分开,成功保存后更新基准;写入失败时保留输入并允许原值重试,补失败后恢复可写的窗口回归测试。 + +crates/ely-ui/src/settings/window.rs:140 — 提交值与磁盘值相同时提前返回,尚未递增 git_asked,先前仍在校验的提交继续拥有写入资格,最终覆盖用户最新选择。真窗口复现:ElyTree 初始使用随带 Git,提交一个 --version 等待五秒的程序路径,随后清空字段并按 Enter 恢复随带 Git;旧校验完成后 settings.json 的 git 被写成慢程序路径,AX 的 Git program 字段仍为空。证据:/tmp/c18-r2-native-final.log、/tmp/c18-r2-git-reset-before.ax、/tmp/c18-r2-git-reset-after.ax。修复方向:每次实际提交先使旧校验过期,再处理与磁盘相同的值;补校验期间恢复已保存值的窗口回归测试。 diff --git a/tasks/reviews/C18.1-r3.md b/tasks/reviews/C18.1-r3.md new file mode 100644 index 0000000..8f718a2 --- /dev/null +++ b/tasks/reviews/C18.1-r3.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-ui/src/settings/window.rs:131 — 作者名的在途保存缺少提交序号;恢复已保存值时直接返回,旧写入随后覆盖最后一次提交,违反配置提交与共用偏好同步要求。真窗口复现两次:初始 author=Ana,另一进程持有共用 .settings.lock;输入 Bob 并按 Enter,等待保存阻塞,再输入 Ana 并按 Enter。释放锁后,磁盘 author 与 AX 作者字段都变成 Bob。第二轮的写入失败保留输入及重试场景已通过,settings.sh 整体通过,当前回归未覆盖等待锁时恢复原值。证据:/tmp/c18-r3-native.log、/tmp/c18-r3-native-repeat.log、/tmp/c18-r3-author-pending.ax、/tmp/c18-r3-author-reverted.ax。修复方向:每次作者提交都更新提交序号,包括恢复已保存值;落盘与成功回显核对最新提交,覆盖持锁期间改回原值的窗口回归。 + +crates/ely-ui/src/settings/window.rs:161 — 最新 Git 提交核对发生在 Store::set 获取实际 .settings.lock 之前;等待文件锁期间已被后一次提交作废的写入,仍会在锁释放后原子替换配置。真窗口复现两次:初始 git="",另一进程持有 ElyTree 的 .settings.lock;提交 --version 成功返回 git version 2.56.0 的程序,等它进入保存等待,再清空 Git 字段并按 Enter。释放锁后,AX 字段保持空,磁盘 git 却变成旧程序路径;日志同时记录旧校验 was overtaken。第二轮的慢校验期间恢复已保存值及空值场景已通过,settings.sh 整体通过,当前回归未覆盖校验通过后的锁等待。证据:/tmp/c18-r3-native.log、/tmp/c18-r3-native-repeat.log、/tmp/c18-r3-git-pending.ax、/tmp/c18-r3-git-reset.ax、/tmp/c18-r3-ely-tree-settings.log。修复方向:在实际文件锁保护的提交阶段核对最新提交,并协调提交序号更新与最终替换;补校验通过后等待文件锁期间恢复已保存值的回归。 diff --git a/tasks/reviews/C18.2-r1.md b/tasks/reviews/C18.2-r1.md new file mode 100644 index 0000000..b4d2c5e --- /dev/null +++ b/tasks/reviews/C18.2-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-ipc/src/relay.rs:10 — 原样记录引擎 stderr 会把文档正文写入诊断日志,违反 PRD §18.2。复现:生成一页 PDF,内容流包含 `(ZqxPrivate42) Tj` 和损坏的操作符 `ZqxPrivate42`;对本轮独立构建的 PDF worker 发 OPEN、TEXT,stderr 出现 `syntax error: unknown keyword: 'ZqxPrivate42'`。同一 relay 将该行逐字写入应用的 ely.log;默认诊断导出也会携带该正文。证据:`/tmp/ely-c18-2-r1/pdf-privacy.log`、`relay.log`。修复方向:在引擎原生错误/警告输出与应用日志之间建立内容边界,日志仅记录经过审核的诊断字段;包含文档数据的原始错误通过用户错误通道交付。增加损坏 PDF 的真实 worker 日志回归。 + +crates/ely-platform/src/diagnostics.rs:101 — “去掉路径与个人姓名”仅替换家目录前缀,仍导出其他完整路径。复现:日志为 `opened /Volumes/ClientSecret/payroll.pdf`,使用家目录 `/Users/ana` 的默认 Redact 调用 bundle,导出的内容仍含 `/Volumes/ClientSecret/payroll.pdf`,文件分隔标题也保留原始路径。补充测试 `probe_bundle`、`probe_paths_and_names` 均失败,见 `/tmp/ely-c18-2-r1/probes.log`;现有 privacy.sh 只检查家目录,两个完整运行均打印成功。修复方向:脱敏日志正文与文件标题中的文件路径及 URL,覆盖外置卷、家目录外的目录和各平台路径形式,并加入对应导出断言。 + +crates/ely-platform/src/diagnostics.rs:105 — 按原顺序逐个替换姓名会让长姓名被短姓名提前拆开,个人信息仍进入默认导出。复现:账户名为 `Ana`、设置作者名为 `Ana Lee`;of_account 将账户名排在作者名前,apply(`Ana Lee`) 得到 ` Lee`,姓氏仍保留。`probe_overlapping_names` 实测失败,见 `/tmp/ely-c18-2-r1/probes.log`。修复方向:优先匹配完整的最长姓名,避免替换结果破坏后续匹配;为账户名与作者名重叠的情况加入回归断言。 + +crates/ely-platform/src/diagnostics.rs:26 — ely.log 元数据查询的错误被吞掉,诊断页面将读取失败显示为空列表。复现:将日志位置设为指向自身的符号链接,metadata 返回 ELOOP,gather 却返回 `Ok([])`,页面显示“暂无可导出的内容”;目录权限导致的 PermissionDenied 也走同一路径。`probe_log_error` 实测失败,见 `/tmp/ely-c18-2-r1/probes.log`。修复方向:仅将 NotFound 作为日志尚未创建处理,其余元数据错误返回给现有诊断错误界面,并测试读取失败的情况。 + +crates/ely-ipc/src/relay.rs:10 — relay 将 worker 原始模块 target 改为 `lok-worker`/`pdf-worker` 后再次过滤,导致两端共享 ELY_LOG 时丢失按模块开启的日志。复现:设置 `ELY_LOG=ely_lok=warn`,worker 的 `[... WARN ely_lok::worker] worker reached` 满足其日志过滤条件;应用 relay 使用 `lok-worker` target,落盘日志为 0 字节。独立构建的 relay 探针在 `ELY_LOG=info` 下记录同一告警,见 `/tmp/ely-c18-2-r1/relay.log`、`relay-filter.log`。修复方向:保留结构化 worker 记录的原始模块 target 与等级,使同一 ELY_LOG 过滤规则在汇总后继续有效,并覆盖模块过滤配置的回归。 + +tests/scripts/privacy.sh:8 — EXIT trap 中终止子进程后直接 wait,在 set -e 下以子进程的 SIGTERM 状态中断清理,成功验收也返回失败。复现:本轮两次完整真窗口运行均输出 `ok: privacy`,随后退出码为 143,两个临时目录均残留;相同 trap 的最小子进程复现也返回 143,后续清理语句跳过。证据:`/tmp/ely-c18-2-r1/privacy.log`、`privacy-repeat.log`、`verification.log`。修复方向:保存验收退出状态,对已设置的 pid 执行终止与回收,显式处理预期的 wait 状态,确保临时目录清理完成后返回原验收状态;同时覆盖成功和启动失败的清理路径。 diff --git a/tasks/reviews/C18.2-r2.md b/tasks/reviews/C18.2-r2.md new file mode 100644 index 0000000..828c0c8 --- /dev/null +++ b/tasks/reviews/C18.2-r2.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-pdf-engine/src/bin/ely-pdf-worker/main.rs:463 — 失败日志仍将未经内容审核的错误串交给 `scrubbed`;该函数仅遮盖成对引号中的字符,未加引号的数据和字段内的引号都会泄露。具体复现:对独立构建的真实 worker 调用 `change_link`,地址为 `ftp://ana:ZqxPrivate42@files.example/document`,请求被拒绝后,应用日志记录 `only web and mail addresses: ftp://ana:ZqxPrivate42@files.example/document`,包含凭据。正常打开文件的路径也可触发:PDF 的 `/Encrypt /Filter` 为 `/Zqx#27Private42` 时,OPEN 返回 `unknown encryption handler: 'Zqx'Private42'`,日志中的遮盖结果为 `'…'Private42'…`,保留输入文档的字段片段。证据:`/tmp/ely-c18-2-r2/pdf-link-probe.log`、`pdf-encrypt-quote-probe.log`;副本中改为仅记录请求号与失败事件后,两种泄露探针通过,完整错误仍由 ERR 回复交回。修复方向:为失败日志限定经过审核的错误类别及诊断字段,保留现有用户错误通道;增加真实 worker 的无引号敏感字段和内嵌引号回归。 + +crates/ely-platform/src/diagnostics.rs:105 — 按空白拆词后逐词判定路径,使带空格的文件名尾部进入默认诊断导出,路径移除功能仍泄露路径信息。具体复现:日志含 `opened '/Volumes/ClientSecret/Tax Salary Report.pdf'` 或 Windows 路径 `C:\Clients\Tax Salary Report.pdf`,调用 `bundle(..., Some(&Redact::default()))` 后分别得到 `opened ' Salary Report.pdf'` 和 `saved Salary Report.pdf`。补充断言两种路径的文件名都应被移除,实测失败;现有脱敏测试通过。证据:`/tmp/ely-c18-2-r2/redact-probes.log`。修复方向:识别完整路径范围,覆盖引号、空格和 Windows 路径形式,再替换整个路径;为含空格的真实日志及崩溃报告路径加入回归。 + +crates/ely-platform/src/diagnostics.rs:111 — 姓名过滤条件 `name.chars().count() > 1` 丢弃所有单字姓名,默认开启的个人姓名移除功能原样导出这些名字。具体复现:作者名设为 `李`,使用 `Redact { names: vec!["李".into()] }` 脱敏 `author: 李`,结果仍为 `author: 李`。补充断言实测失败;副本中允许全部非空姓名参与替换后通过。证据:`/tmp/ely-c18-2-r2/redact-probes.log`、`name-repair.log`。修复方向:对全部非空配置姓名执行脱敏,并覆盖单字中文姓名及单字署名。 diff --git a/tasks/reviews/C18.2-r3.md b/tasks/reviews/C18.2-r3.md new file mode 100644 index 0000000..4fd449a --- /dev/null +++ b/tasks/reviews/C18.2-r3.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-platform/src/diagnostics.rs:130 — 引号路径的结束位置未处理转义,默认脱敏会保留路径内转义引号之后的文件名。具体复现:把 macOS 崩溃报告格式的 `{"usedImages":[{"path":"/Volumes/ClientSecret/Tax \"Salary Report\".pdf"}]}` 作为诊断包的一部分,调用 `bundle(..., Some(&Redact::default()))`,结果为 `{"usedImages":[{"path":""Salary Report\".pdf"}]}`,仍含 `Salary Report`。现有脱敏测试通过,新增断言在原实现失败;临时副本识别转义后的结束引号后通过。证据:`/tmp/ely-c18-2-r3/diagnostic-extra.log`、`diagnostic-baseline-tests.log`、`diagnostic-repair-tests.log`。修复方向:按输入格式识别完整引号字符串,处理转义引号,再替换整个路径;覆盖崩溃报告中的合法带引号文件名。 + +crates/ely-platform/src/diagnostics.rs:163 — 按字节截取日志尾部后直接脱敏,截断点落在路径中间时,路径前缀消失,剩余文件名被原样导出。具体复现:日志以 `opened '/Volumes/ClientSecret/Tax Salary Report.pdf'\n` 开始,后接填充日志,使最后 4 MiB 恰好从 `Tax Salary Report.pdf` 开始;经 `gather(AppId::Doc)` 和默认脱敏的 `bundle` 导出,第一行仍含该文件名。新增断言在原实现失败;副本跳过被截断的首行后通过,原有日志尾部测试也通过。证据:`/tmp/ely-c18-2-r3/diagnostic-probe.log`、`diagnostic-baseline-tests.log`、`diagnostic-repair-tests.log`。修复方向:尾部读取从完整记录边界开始,覆盖截断点位于路径及姓名内部的回归。 + +crates/ely-platform/src/diagnostics.rs:97 — `of_account` 忽略姓名来源的读取错误,导出流程继续把缺少作者名的过滤器当作成功脱敏。具体复现:隔离 HOME 下的共享配置为 `{"version":1,"appearance":"ZqxAuthor42","author":"ZqxAuthor42"}`;启动真实 ElyDoc 后,设置读取失败的应用日志含姓名 `ZqxAuthor42`。使用 `Redact::of_account()` 导出该应用日志,调用成功且姓名完整保留。账户姓名读取的 `.ok()` 也会丢弃读取错误。证据:`/tmp/ely-c18-2-r3/invalid-settings-app.log`、`diagnostic-extra.log`;副本让姓名来源错误经 `Result` 返回后,阻止脱敏导出的断言通过。修复方向:将脱敏资料读取错误传回设置导出流程,显示具体失败并保留重试入口,资料读取成功后再导出。 + +crates/ely-lok/src/bin/ely-lok-worker.rs:14 — worker 启动失败仍用非结构化 `eprintln!` 输出原因,该记录被新的 `relay` 当作原生输出丢弃,客户端也仅收到管道关闭。具体复现:使用有效的空 profile 目录,对不存在的安装目录调用真实 `Worker::spawn`;worker 的原始错误为 `lok-worker: No such file or directory (os error 2)`,客户端只返回 `the lok worker failed: the lok worker closed its output (exit status: 1)`,文件系统错误原因消失。证据:`/tmp/ely-c18-2-r3/startup-probe.log`。修复方向:让 worker 自己的启动失败使用中继可识别的结构化记录,并通过启动错误通道向客户端保留可显示的失败原因;增加真实 worker 初始化失败的回归。 diff --git a/tasks/reviews/C18.3-r1.md b/tasks/reviews/C18.3-r1.md new file mode 100644 index 0000000..ff03cc3 --- /dev/null +++ b/tasks/reviews/C18.3-r1.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-ui/src/settings/storage.rs:62 — 恢复点数与空间的两个闭包各自捕获读取时的 Limits,随后把整个对象异步写回,连续修改会覆盖另一项选择。真窗口复现:初始为 20 个点、64 MB,持有本应用的 .settings.lock,依次点击 10 与 32,再释放锁;settings.json 最终为 {"keep":20,"megabytes":32},用户刚选的 10 被恢复成 20。证据:/tmp/c18.3-r1-native.log 的 BOTH_CHOICES_PERSISTED。修复方向:维护最新的待保存 Limits,并保证连续提交与回读按版本生效,保留两个控件各自的最新选择。 + +crates/ely-ui/src/settings/storage.rs:106 — 缓存与恢复副本的清除按钮首次点击就提交删除标记,违反 PRD §4.2 对删除缓存与本地历史单独确认的要求。真窗口中分别点击两行的 Clear at next start,.clear-web 与 .clear-recovery 立即出现,AX 树中 dialog=False;随后启动会消费这些标记并删除数据。证据:/tmp/c18.3-r1-native.log 的 CACHE_REQUEST、RECOVERY_REQUEST。修复方向:通过确认对话框明确删除范围,确认后再写入清理标记。 + +crates/ely-ui/src/run.rs:70 — 启动清理失败仅记日志,随后继续打开待清理的数据;失败原因也未进入设置页的错误状态。复现:在 web/locked 中放入文件,把该目录权限设为 0555,并创建 .clear-web 后启动;日志报 Permission denied (os error 13),旧文件与标记均保留,CEF 继续使用该缓存,存储页只给出禁用的 Clears at next start 按钮。这破坏 C18.3 的“打开任何东西之前清掉”约定,并隐藏本次清理失败。证据:/tmp/c18.3-r1-failure.log。修复方向:在打开待清理数据前处理失败并展示原因,保留标记供重试。 + +tests/scripts/storage.sh:52 — 真窗口验收只核对缓存行名称,恢复限制只核对 settings.json 的 keep 字段;大小显示与实际恢复点限制缺乏行为断言。隔离副本中把三类大小显示全部改为 0 KB,并把 ElyMark 恢复写入固定为 20 个点、64 MB,重建后 storage.sh 仍退出 0、输出 ok: storage。另一次 AX 检查确认缓存实际有 5,566,125 字节,界面仍显示 0 KB。证据:/tmp/c18.3-r1-mutation.log、/tmp/c18.3-r1-mutation-ax.log。修复方向:用已知大小的样本断言各行的实际显示值,并生成恢复点验证用户选择的点数与容量约束,使上述突变触发失败。 diff --git a/tasks/reviews/C18.3-r2.md b/tasks/reviews/C18.3-r2.md new file mode 100644 index 0000000..4ddfbe5 --- /dev/null +++ b/tasks/reviews/C18.3-r2.md @@ -0,0 +1,9 @@ +FAIL + +crates/ely-ui/src/settings/storage.rs:47 — 异步读取无条件覆盖尚在等待保存的恢复限制,页面切换会丢失已经选择的值。真窗口复现:初始 keep=20、megabytes=64,持有 .settings.lock,选择 10,切到 General 后回到 Storage,再选择 32,最后释放锁;settings.json 最终为 keep=20、megabytes=32。证据:/tmp/c18.3-r2-native.log 的 NAVIGATED_CHOICES 与 BOTH_CHOICES_PERSISTED False;同页连续选择能正确保留两项。修复方向:保留待提交的 Limits,磁盘回读按提交版本生效,避免等待写入期间的旧值覆盖最新选择。 + +crates/ely-ui/src/run.rs:71 — R1 第3项的启动顺序问题仍存在:清理失败后记录设置页错误,继续执行 root 并打开待清理的缓存。复现:web/locked 内放文件,将目录权限设为 0555,创建 .clear-web,再启动 ElyMark 打开 opened.md;日志先报 Permission denied,随后记录 CEF 在同一 web 路径启动和文档显示,旧文件及标记均保留。这违反 C18.3 的“打开任何东西之前清掉”约定。证据:/tmp/c18.3-r2-failure.log、/tmp/c18.3-r2-native.log。修复方向:清理失败时阻止打开对应数据,显示原因并保留标记供重试。 + +tests/scripts/ax-values.swift:28 — 坐标后新增 help 字段破坏现有调用方的尾部解析。tests/scripts/settings.sh:56 用 sed 提取 at= 后的全部内容,传给 click.swift 的 y 因而成为带制表符和 help= 的字符串;Double(y) 返回 nil,强制解包崩溃。独立运行 settings.sh,在首次点击 Author name 时触发 Trace/BPT trap,退出 133,后续设置用例无法执行。证据:/tmp/c18.3-r2-settings-script.log、/tmp/c18.3-r2-coordinate.log。修复方向:统一输出与既有调用方的字段解析,让点击坐标仅含 x、y,并回归 settings.sh。 + +tests/scripts/storage.sh:57 — R1 第4项的大小验收仍只覆盖缓存,日志与恢复副本的显示值缺少断言。隔离副本仅将这两行显示改成 0 KB,重建后原有存储断言全部通过,脚本输出 ok: storage;此时日志实际有 10,594 字节,恢复点合计 370 字节,AX 两行均为 0 KB。证据:/tmp/c18.3-r2-mutation.log;缓存显示为 0 KB 的另一个突变会触发大小断言失败,见 /tmp/c18.3-r2-cache-mutation-repeat.log。修复方向:用已知非零样本分别核对日志、缓存、恢复副本的实际显示值,使各行大小错误都触发失败。 diff --git a/tasks/reviews/C18.3-r3.md b/tasks/reviews/C18.3-r3.md new file mode 100644 index 0000000..0811431 --- /dev/null +++ b/tasks/reviews/C18.3-r3.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-ui/src/settings/window.rs:117 — 重开设置窗口会重建 Storage,丢掉仍在等待写入的恢复限额及提交序号,新窗口用磁盘旧值组装整份限额,覆盖已经选择的另一项。真窗口复现:初始 keep=20、megabytes=64,持有本应用 .settings.lock,选 10,关闭设置窗口,重开 Storage 后选 32,再释放锁;settings.json 最终为 keep=20、megabytes=32,已经选择的 10 丢失。当前构建与两次独立复跑结果一致,见 /tmp/c18.3-r3-native-current.log、/tmp/c18.3-r3-native-repeat.log 的 REOPEN_BOTH_PERSISTED False。修复方向:让待提交限额与提交序号跨设置窗口存续,新窗口依据最新待提交值修改另一项,并补充持锁关闭、重开、连续选择的回归。 + +crates/ely-ui/src/settings/storage.rs:130 — 清理与限额写入共用一个失败状态,清理成功会清除仍未解决的限额保存错误;pending 同时让回读继续显示未落盘的选择。真窗口复现:将 .settings.lock 替换为目录,选择恢复点数 50,出现 Not done;恢复锁文件,确认清空日志,失败提示随即消失,磁盘仍为 keep=20。继续选空间 64 时,写出的 keep=50 证明未保存的选择一直保留在界面状态。见 /tmp/c18.3-r3-native-current.log 的 LIMIT_SAVE_FAILURE_VISIBLE True、AFTER_CLEAR_FAILURE_VISIBLE False、AFTER_CLEAR_DISK 与 PENDING_CARRIED_TO_NEXT_SAVE,以及 /tmp/c18.3-r3-after-log-clear.ax。修复方向:分别保留限额保存与清理操作的失败状态,限额失败提示持续到对应保存成功,并回归失败后执行其他存储操作的场景。 + +crates/ely-ui/src/run.rs:118 — 当前新增的文档注释“Logs each time the main thread stayed busy past 100 ms.”共 11 个英文单词,违反 agents.md 的注释最多 10 词规则。独立运行 python3 tests/scripts/comments.py 会在此处报错,见 /tmp/c18.3-r3-comments-current.log。修复方向:将该注释缩短至 10 词以内。 diff --git a/tasks/reviews/C19.1-r1.md b/tasks/reviews/C19.1-r1.md new file mode 100644 index 0000000..4a0910f --- /dev/null +++ b/tasks/reviews/C19.1-r1.md @@ -0,0 +1,13 @@ +FAIL + +tests/scripts/ooxml.py:45 — DOCX comment verification accepts an orphaned comment part as a preserved comment. Reproducer: take the DOCX produced by `a_docx_keeps_text_comments_and_tracking_across_sessions`, remove every `w:commentReference` from `word/document.xml`, and retain `word/comments.xml`. `python3 tests/scripts/ooxml.py contract ` exits 0, including the textutil check, although the document has zero comment references and the saved comment has lost its attachment to the document. This leaves the comment preservation claim in docs/compatibility.md:15 without an effective regression check (PRD §19.2). Fix direction: verify the document's comment references, their IDs and intended anchors against the related comment part, and require this mutation to fail. + +tests/scripts/ooxml.py:50 — The tracking-state assertion checks only the element's presence. Reproducer: replace the generated DOCX's `` with ``. The independent checker exits 0 while the persisted setting explicitly disables tracking; docs/compatibility.md:15 claims the tracking switch is preserved. Fix direction: interpret the OOXML OnOff value, assert tracking is enabled, and exercise the explicit-false case. + +tests/scripts/ooxml.py:85 — XLSX verification omits the input cells used to calculate its independent expectations, allowing changed inputs and stale formula caches to pass. Reproducer: in the XLSX produced by `an_xlsx_keeps_formulas_and_results_across_sessions`, change H1 from 7 to 70 and retain the saved formulas and caches. The checker exits 0 with H3=35, H4=2.33333333333333, H8=12 and H9=36, although those values disagree with the saved H1=70 and H2=5. This misses content corruption and stale results covered by PRD §19.2. Fix direction: independently verify the specified input cells and their types alongside the formula texts and computed expectations; require the input-only mutation to fail. + +tests/scripts/ooxml.py:68 — The XLSX reader inspects the physical `xl/worksheets/sheet1.xml` entry without resolving the workbook's sheet relationships. Reproducer: change the generated file's worksheet relationship in `xl/_rels/workbook.xml.rels` from `worksheets/sheet1.xml` to `worksheets/missing.xml`, retaining the original worksheet entry. `python3 tests/scripts/ooxml.py sheet ` exits 0 even though the workbook's declared sheet points to a missing part. PRD §19.2 requires saved structure to survive independent verification. Fix direction: resolve the workbook's declared sheets through their relationship IDs, validate the targets, and check the intended worksheet through that mapping. + +tests/scripts/ooxml.py:116 — PPTX verification counts slide IDs and scans archive entries while ignoring the relationships that make slides and notes part of the presentation. Two independent mutations of the generated PPTX each exit 0: change the slide-two relationship in `ppt/_rels/presentation.xml.rels` to `slides/missing.xml`; remove the notesSlide relationship from `ppt/slides/_rels/slide1.xml.rels` while retaining the notes XML. The former breaks a declared slide; the latter disconnects the saved speaker notes from slide one. The checker still supports the preservation claim in docs/compatibility.md:17. Fix direction: resolve the ordered slide list and each slide's notes through their relationships, verify the referenced targets, and assert the expected content, visibility and notes on their owning slides. + +tests/scripts/ooxml.py:58 — `column_row` is dead code, violating the project's rule 6. Repository search finds only its definition; AST inspection finds one FunctionDef and zero Name references. Running all three CASES leaves this function unused. Fix direction: remove the unused function. diff --git a/tasks/reviews/C19.1-r2.md b/tasks/reviews/C19.1-r2.md new file mode 100644 index 0000000..f0ee9a1 --- /dev/null +++ b/tasks/reviews/C19.1-r2.md @@ -0,0 +1,7 @@ +FAIL + +tests/scripts/ooxml.py:88 — Comment placement verification records only the containing paragraph, so moving a preserved comment onto different text in that paragraph passes. Reproducer: in the DOCX produced by `a_docx_keeps_text_comments_and_tracking_across_sessions`, remove `` from its original run after `Alpha 甲` and insert `` immediately before that paragraph's ``, after `Hello 世界`. `python3 tests/scripts/ooxml.py contract ` exits 0, including textutil. Reopening both files in the freshly built worker and exporting FODT confirms that `Check alpha` moves from immediately after `Alpha 甲` to immediately after `Hello 世界`. The new paragraph-membership check therefore misses a real change to the comment anchor required by PRD §19.2. Fix direction: verify the comment ID and its expected text position or anchored range, and add this same-paragraph movement to the rejection selftests. + +tests/scripts/ooxml.py:179 — The new ordered-slide reader checks count, the first title and aggregate visibility, allowing a different ordered deck to pass. Reproducer: in the generated PPTX, change the `rId5` target in `ppt/_rels/presentation.xml.rels` from `slides/slide2.xml` to `slides/slide1.xml`. The second-session blank slide is replaced by another reference to the first slide; `python3 tests/scripts/ooxml.py deck ` exits 0. A separate mutation swapping the `rId5` and `rId6` entries in `ppt/presentation.xml` also exits 0. Worker reopen and FODP export confirm ordered slide texts `["First", "First", "Second"]` and `["First", "Second", ""]`, respectively, against the original `["First", "", "Second"]`; the duplicate also copies the first slide's notes. This leaves the saved structure and second-session insertion insufficiently checked under PRD §19.2. Fix direction: assert distinct slide targets and the complete expected ordered content, visibility and notes of this fixture; require duplicate-target and reordered-slide mutations to fail. + +tests/scripts/ooxml.py:86 — The newly added comment contains 13 words and continues as a second comment line at line 87, violating agents.md rule 5. Reproducer: `python3 tests/scripts/comments.py` exits 1 and reports both the overlong line and the multiline comment. Fix direction: replace these two lines with one necessary comment containing at most ten words. diff --git a/tasks/reviews/C19.1-r3.md b/tasks/reviews/C19.1-r3.md new file mode 100644 index 0000000..55e604d --- /dev/null +++ b/tasks/reviews/C19.1-r3.md @@ -0,0 +1,7 @@ +FAIL + +tests/scripts/ooxml.py:107 — The comment-anchor check verifies text before the reference while ignoring the comment's range markers. In the DOCX generated by `a_docx_keeps_text_comments_and_tracking_across_sessions`, insert `` immediately before the insertion containing `Gamma `, and `` immediately before the existing reference run after `Alpha 甲 `. Both `ooxml.py contract` and `ooxml.py selftest contract` exit 0. Reopening with the freshly built worker and exporting FODT confirms that the original point comment after `Alpha 甲` becomes a range spanning `Gamma Alpha 甲`: the annotation starts before Gamma and ends after Alpha. This misses changed comment attachment under PRD §19.2. Fix direction: resolve reference and range markers together by comment ID, assert this fixture retains its point anchor, and add the range mutation to rejection selftests. + +tests/scripts/ooxml.py:98 — Revision verification covers Beta while Gamma from the second editing session is checked only as ordinary document text. In the generated DOCX, remove the opening and closing `w:ins` tags around the run containing `Gamma `, retaining that run, Beta's insertion and the enabled tracking setting. Both `ooxml.py contract` and `ooxml.py selftest contract` exit 0. Worker reopen and FODT export confirm that the revision count falls from two to one and Gamma becomes ordinary text. The test therefore accepts lost second-session revision metadata covered by PRD §19.2 and the documented tracked-insertion preservation claim. Fix direction: verify the expected tracked text from both editing sessions and require the Gamma-wrapper removal to fail. + +tests/scripts/ooxml.py:200 — Notes verification concatenates every DrawingML text node in the notes part, accepting speaker text in a footer as preserved editable notes. In the generated PPTX, change `` to `` in `ppt/notesSlides/notesSlide1.xml`, leaving the text and relationships intact. Both `ooxml.py deck` and `ooxml.py selftest deck` exit 0. Reopening with the freshly built worker reports first-slide `.uno:SlideNotes` as `editable: false, text: ""`; the original reports `editable: true, text: "Speaker 甲"`. This misses loss of the actual notes body and its editability under PRD §19.2. Fix direction: check speaker text in the owning slide's notes-body placeholder and add this placeholder mutation to rejection selftests. diff --git a/tasks/reviews/C19.2-r1.md b/tasks/reviews/C19.2-r1.md new file mode 100644 index 0000000..8c79fbb --- /dev/null +++ b/tasks/reviews/C19.2-r1.md @@ -0,0 +1,7 @@ +FAIL + +crates/ely-platform/src/print.rs:22 — 复制 sharedPrintInfo 后保留了系统共享的 scalingFactor,PageScaleNone 只控制 PDF 页面适配,实际打印仍叠加该缩放,违反 PRD §19.3 的缩放一致性。具体场景:共享打印设置为 50%,PDF 内的图形为 200×100 点;独立副本执行生产打印函数,仅关闭面板并将系统作业指定为保存 PDF,输出图形变为 100×50 点,位置也发生偏移。在副本中加入 info.setScalingFactor(1.0) 后,同一输入恢复为 200×100 点。修复方向:用文档的输出缩放初始化此次打印设置,当前已完成布局的 PDF 应设为 1.0,并增加带非默认共享缩放值的实际输出回归。 + +crates/ely-platform/src/print.rs:20 — 只读取首页的 MediaBox 并把同一纸张用于整个打印操作,后续较大页面在 PageScaleNone 下被裁切,违反 PRD §19.3 的纸张及输出一致性。具体场景:两页 PDF 的首页为 A5 横向 595×420 点,第二页为 A3 横向 1191×842 点;执行生产打印函数的系统保存作业后,两页都变为 595×420 点,第二页靠两侧放置的两个 40×40 点图形全部消失。72 DPI 独立栅格检查:原第二页有 3200 个黑像素,打印第二页为 0;打印函数仍返回 Ok(true)。任务中所列“各节纸张不同未验证”已由此复现为内容裁切。修复方向:打印操作按页保留文档的纸张尺寸,预览与输出使用相同的逐页设置,并覆盖混合纸张内容边缘的回归。 + +tests/scripts/print.sh:64 — 纸张断言读取的是打印函数记录的输入尺寸,实际 NSPrintOperation 的纸张、边距、缩放与输出内容均未被核对,测试会放过此次任务直接修复的纸张回归。具体场景:运行时将 ElyDoc 的 NSPrintInfo.setPaperSize 替换为设置 Letter 纸张,记录确认实际纸张为 792×612 点,原函数日志仍为 595.30×419.56;保持脚本断言不变、仅调整启动以加载变异库后,脚本仍退出 0 并输出“ok: print”。原脚本和 91 项相关单测均通过。修复方向:独立读取实际系统打印设置并核对保存出的页面尺寸、边距与内容比例,使这类纸张变异能够触发测试失败。 diff --git a/tasks/reviews/C19.2-r2.md b/tasks/reviews/C19.2-r2.md new file mode 100644 index 0000000..3dafd5b --- /dev/null +++ b/tasks/reviews/C19.2-r2.md @@ -0,0 +1,5 @@ +FAIL + +crates/ely-platform/src/print.rs:24 — 按面积选择最大页面作为整份作业的纸张,宽高交叉的混合纸张仍会裁切内容,违反 PRD §19.3 的输出一致性及本轮修复所称的“不裁不缩”。具体场景:两页 PDF 分别为 1000×200 点和 600×600 点,第一页左右两端各有 40×40 点黑块及文字;调用当前生产 print_pdf,并用 ELY_PRINT_TO 保存实际系统作业,函数返回 Ok(true),两页输出均为 600×600 点,第一页两端的文字与黑块全部消失。独立 72 DPI 栅格检查:原第一页有 3373 个黑色像素,输出第一页为 0;90 度旋转的同类页面亦复现。上一轮 A5 加 A3 的场景已通过,此宽高交叉场景仍失败。修复方向:结合每页有效方向,逐轴确定能容纳全部页面的纸张尺寸,并增加宽高交叉混排的实际输出回归。 + +tests/scripts/print.sh:117 — 实际输出回归仍缺少非默认共享缩放的前置条件,撤掉上一轮 scalingFactor 修复后整套脚本照样通过。具体场景:在已独立构建的 ElyDoc 中,用运行时变异将 NSPrintInfo.setScalingFactor(1.0) 改为空操作,复跑脚本的取消、A5 与混合纸张断言,退出 0 并输出 ok: print;同一变异作用于共享缩放预设为 0.5 的生产打印探针时,40×40 点黑块实际变成 20×20 点,文字左边距由 72 点变为 36 点。当前修复在该 0.5 前置条件下已确认保留原尺寸,脚本未覆盖这一必要回归。修复方向:在执行生产打印函数的同一进程中预设非默认 sharedPrintInfo 缩放,核对实际作业的内容尺寸与位置,并要求撤掉缩放重置时测试失败。 diff --git a/tasks/reviews/C19.2-r3.md b/tasks/reviews/C19.2-r3.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/C19.2-r3.md @@ -0,0 +1 @@ +PASS diff --git a/tasks/reviews/C20.1-r1.md b/tasks/reviews/C20.1-r1.md new file mode 100644 index 0000000..c22e6c3 --- /dev/null +++ b/tasks/reviews/C20.1-r1.md @@ -0,0 +1,13 @@ +FAIL + +crates/ely-lok/src/client.rs:108 — `ready()` 在校验开始时取走 `unready`,校验失败后留下与成功相同的状态。真实 worker 探针先将 `profile/lok-verify.fodt` 创建为目录:首次 `ready()` 返回 `IsADirectory`,第二次返回 `Ok(())`,随后 `set_author` 和 `load("private:factory/swriter")` 均成功。图块自检失败的 worker 因而继续接受正常请求,违反启动校验承诺。修复方向:区分握手中、校验成功与校验失败,保留失败状态并阻止后续业务请求,补充自检失败后继续请求的回归测试。证据:`/tmp/c20-r1-ready.log`。 + +tests/perf/bench.py:299 — PERF-03、PERF-04 把 `lok: tile (` 当作首屏成功标记;该日志在 `crates/ely-lok/src/view/lane.rs:69` 对成功和失败的绘制请求都会输出,且先于视图接收与绘制。真实进程代理将全部 `PAINT_TILE` 请求返回 `injected tile failure`,应用持续报告页面绘制失败,`opened()` 仍返回有效耗时;ElyTable 得到 4528 ms,记录器标记 `4528 ✓`。此结果无法证明 PRD 要求的首屏显示与接受操作。修复方向:以成功显示文档首屏并接受操作作为结束条件,将图块错误判为失败,并让该失败注入用例检出错误。证据:`/tmp/c20-r1-false-tile.log`。 + +tests/perf/samples.py:54 — DOCX 生成器的 100 个分页元素实际排版为 62 页,PERF-03 使用了不足规定页数的样本。用本轮构建的真实 worker 载入 `engines/bench/doc.docx`,连续等待并读取页框均为 62;导出后 `pdfinfo` 同样报告 62 页,正文包含全部 100 个 Page 标题。重新生成的 DOCX 与缓存样本的正文 XML 完全相同,缓存样本哈希也与提交的性能记录一致。`docs/perf/README.md:9` 的 100 页说明与实物存在差异。修复方向:修正生成器的实际分页,使内核排版达到 100 页,并在基准运行前核验样本页数。证据:`/tmp/c20-r1-samples.log`。 + +tests/perf/bench.py:170 — PERF-02 直接用收集到的计时计算 P95,并按耗时标记达标,缺少请求数、反馈数和规定 30 次样本之间的核验。真实输入代理保留第一个 `POST_TEXT`,对后续 59 个文本请求仅返回成功确认;脚本发送 30 次原生按键后只收到一条 42 ms 计时,仍输出 `Runs 1 / P95 42 ✓`。输入丢失或计时缺失可因此成为达标结果。修复方向:逐次确认新的输入反馈与对应计时,校验完整样本数量,将丢失、超时和样本不足记录为失败。证据:`/tmp/c20-r1-native-missing.log`、`/tmp/c20-r1-dropped-inputs.log`。 + +tests/perf/bench.py:212 — PERF-08 从首次关闭后开始采样,只记录 RSS 与 `lsof` 行数,缺少循环前基线及活动文档对象数量,无法验证 PRD 要求的对象与句柄回到基线。资源探针设初始状态为 100 MB / 20 个文件,首次关闭泄漏一个文档和句柄,此后保持 110 MB / 21 个文件;脚本仅生成一串恒定的 `110/21`,原始基线与泄漏状态均未进入记录。现有 40 次循环的平稳序列也无法补足这些验收证据。修复方向:记录循环前基线与活动文档对象、句柄数量,等待关闭完成后核对其恢复,并加入首次关闭泄漏的回归探针。证据:`/tmp/c20-r1-resource-baseline.log`。 + +crates/ely-lok/src/view/clock.rs:1 — 本文件第 1、15、20 行注释各有 11 个词,超过 agents.md 的十词限制;`tests/perf/bench.py:36` 的类文档字符串有 12 个词。本轮运行 `python3 tests/scripts/comments.py`,范围内的三处 Rust 注释均被报告为 FAIL。修复方向:将这些注释缩至十词以内,并核对 Python 内部文档字符串的长度。 diff --git a/tasks/reviews/C20.1-r2.md b/tasks/reviews/C20.1-r2.md new file mode 100644 index 0000000..ac697eb --- /dev/null +++ b/tasks/reviews/C20.1-r2.md @@ -0,0 +1,9 @@ +FAIL + +tests/perf/bench.py:131 — 首屏成功标记仍早于窗口绘制,错误检查也漏掉实际绘制失败。`crates/ely-lok/src/view.rs:475` 在缓存图块时记录 `shown`,实际 `window.paint_image` 的失败日志为 `lok: tile paint failed`,与 `PAINT_FAILED` 不匹配。本轮在隔离副本中让实际绘制调用返回错误,真实窗口正文为空,日志持续报告 GPU 上传失败;`opened()` 仍接受 ElyDoc 的 3522 ms 与 ElyTable 的 4017 ms,两者均低于 5000 ms 目标。上一轮的 worker 图块请求失败注入已能检出,此绘制阶段失败仍使 PERF-03、04 假达标。修复方向:以成功绘制首屏并确认可操作作为结束条件,覆盖窗口绘制错误,并让该绘制失败注入使基准失败。证据:`/tmp/c20-r2-gpu-probe.log`、`/tmp/c20-r2-replay.log`。 + +tests/perf/bench.py:286 — PERF-08 将第一次关闭后的文件数作为通过条件,首次关闭泄漏的句柄因此进入允许基线。本轮在隔离副本的 `release_tab` 中仅于首次关闭泄漏一个 `/dev/null` 文件描述符,真实窗口运行 20 次新建关闭后,脚本接受循环前 556 个文件、首次及以后每轮 561 个文件的结果,活动文档数每轮为 1。额外泄漏与正常的一次性系统载入一起被放行,违反句柄回到基线的要求。修复方向:依据句柄身份核验初始基线,仅扣除有证据的一次性系统载入,首次关闭也须检出遗留文档句柄;补充首次关闭泄漏的回归探针。证据:`/tmp/c20-r2-leak-native.log`、`/tmp/c20-r2-resource-probes.log`。 + +tests/perf/bench.py:242 — 活动文档数遥测缺失时返回 `None`,后续等待与最终核验均允许 `None == None`,整个 PERF-08 仍成功返回。本轮资源探针保留打开关闭完成事件、移除全部 `documents open` 日志,20 次循环被接受,基线与最终文档数均为 `None`。该路径将缺失的必需度量当作符合基线,无法证明活动文档对象已释放。修复方向:基线及各轮关闭后的文档数必须来自完整的有效遥测;缺失或超时即失败,并加入计数日志缺失的回归测试。证据:`/tmp/c20-r2-resource-probes.log`。 + +docs/perf/2026-10-08-Mac16,10.md:52 — 提交的达标记录仍使用上一轮的证据,无法支持修复后的基准结论。报告所记 DOCX 哈希为 `5ab234c7…`,上一轮已核实该样本仅排出 62 页;本轮核验修正样本为 100 页,其当前哈希为 `44320bb4…`。第 59–61 行 PERF-08 仍只有内存与文件数序列,缺少循环前基线及活动文档数,格式也沿用旧记录器。修复方向:在修复后的发行构建与符合要求的样本上重测并提交对应记录,包含实际样本哈希、资源基线及每轮活动文档数;旧记录明确其测量范围。证据:`tasks/reviews/C20.1-r1.md`、`/tmp/c20-r2-record-audit.log`、`/tmp/c20-r2-replay.log`。 diff --git a/tasks/reviews/C20.1-r3.md b/tasks/reviews/C20.1-r3.md new file mode 100644 index 0000000..594c3a4 --- /dev/null +++ b/tasks/reviews/C20.1-r3.md @@ -0,0 +1,7 @@ +FAIL + +tests/perf/bench.py:196 — PERF-02 的结束标记仍来自缓存图块,早于窗口实际绘制;`crates/ely-lok/src/view.rs:482` 在 `finish_tile` 中结束时钟,交互基准也跳过了绘制失败检查。场景:在隔离发行构建中让窗口图块绘制返回错误,真实窗口累计记录 1045 次绘制错误,首块成功绘制标记为零;基准仍接受全部 30 次按键,P95 为 37 毫秒,落入 100 毫秒目标,违背 PERF-02 的可见反馈要求。第二轮的首屏绘制失败探针已被拒绝,交互路径仍存在同类假达标。修复方向:在对应编辑的可见图块成功绘制后结束计时,交互基准遇绘制失败即失败,并加入实际窗口绘制失败的回归探针。证据:`/tmp/c20-r3-native-probes.log`、`/tmp/c20-r3-draw-failure.log`、`/tmp/c20-r3-mutations.log`。 + +tests/perf/bench.py:250 — 文档计数取整份日志的最后一条,循环中的核验复用初始基线;第二轮对遥测缺失的修复仅覆盖整份日志完全缺少计数的情况。场景:隔离构建保留初次打开的 `1 documents open`,省去后续打开与关闭的全部计数日志;真实窗口完成 20 次循环后,`perf08` 成功返回,日志中计数回报仅有一条,基线及每轮结果均记录为 1。该结果无法证明各轮活动文档对象回到基线,也违反记录说明中每次关闭缺少计数即失败的约定。修复方向:用每轮日志游标或请求标识绑定本轮打开、关闭的有效计数,缺少新回报或超时即失败,补充仅保留初始计数的回归测试。证据:`/tmp/c20-r3-native-probes.log`、`/tmp/c20-r3-stale-count-native.log`、`/tmp/c20-r3-stale-count-mock.log`。 + +tests/perf/bench.py:101 — R2 将资源核验迁到 `handles()` 后遗留了 `App.files()`,构成项目规则禁止的 dead code。场景:完整 PERF-01 至 PERF-08 基准、`soak.py` 及测试均通过 `handles()` 或 `memory_mb()` 读取资源;全项目 Python 源码搜索 `\.files\b` 得到零个使用点。修复方向:删除遗留的 `files()` 方法。证据:`/tmp/c20-r3-original-audit.log`。 diff --git a/tasks/reviews/R1.1-r1.md b/tasks/reviews/R1.1-r1.md new file mode 100644 index 0000000..ed3a12a --- /dev/null +++ b/tasks/reviews/R1.1-r1.md @@ -0,0 +1,11 @@ +FAIL + +packaging/macos/bundle.sh:149 — 正式签名分支为 CEF helper 开启 hardened runtime,却未附加 JIT entitlement。Apple Silicon 上,对临时 ElyMark 包按该分支开启 runtime,固定 disable-library-validation 以排除 ad-hoc 签名的 Team ID 差异后,renderer 报 `V8 process OOM (Failed to reserve virtual memory for CodeRange)`,应用报 `TS_PROCESS_CRASHED`,Markdown 正文加载失败;保持其他选项相同、增加 `com.apple.security.cs.allow-jit` 后,两次出现 `mark: shown as "visual"`。修复方向:为执行 V8 的 helper 配置所需 entitlement,并覆盖正式签名选项下的真实页面加载。 + +scripts/build-git.sh:19 — Git 构建继承主机 SDK 的最低部署版本,打包却统一声明 macOS 14.0。本机 SDK 27.0 下重新运行该脚本,Git 的 `LC_BUILD_VERSION` 为 `minos 27.0`;现有 ElyTree.app 内的 Git 与 HTTPS helper 同样为 27.0,而 Info.plist 的 `LSMinimumSystemVersion` 为 14.0,必需运行程序的部署目标超出 PRD §4.1 支持范围。设置 `MACOSX_DEPLOYMENT_TARGET=14.0` 后重新构建,Git 的 `minos` 成为 14.0。修复方向:在构建中固定受支持的部署目标,并校验包内全部可执行文件的最低系统版本。 + +apps/ely-table/src/text.rs:244 — 每个异步 CSV 打开请求直接替换唯一的导入对话框,前一个请求被静默丢弃。以 `ely-table a.csv b.csv` 启动,两份输入均记录 `table: import asks`,AX 树仅出现 `Open b.csv`;按 Open 后仅产生 b.csv 标签,A1 为 `second`,a.csv 的导入询问及标签均消失。重新构建 ElyTable 后复跑,异步读回顺序相反,仅 a.csv 被导入,进一步确认胜出的文件取决于读回顺序。访达传入多份 CSV 也进入此路径。修复方向:保留待处理的导入请求,逐个完成或取消后继续处理,加入多文件系统打开的回归验证。 + +packaging/macos/release.sh:20 — 发行流程只封装应用生成 DMG,缺少 PRD §21.2、§21.3 要求的许可证、资源来源及对应发行资料。实际执行 `bundle.sh ely-pdf debug` 后,包内只有应用、worker、Info.plist、签名、图标和三份 traineddata,MuPDF/应用许可证与 OCR 来源记录均缺失;现有 ElyTree.app 的 Git 目录也没有 COPYING/LICENSE 或对应源码取得说明,dist 只有六组 app/DMG。修复方向:保留所打包组件及资源的许可证与来源,并随对应版本生成源码、锁定依赖清单、补丁、构建资料和第三方通知,资料缺失时阻断发行。 + +tests/scripts/icons.sh:34 — 图标测试只检查 PNG 尺寸及两个透明像素,完全不可见的图标仍获通过。在隔离副本中将 `ely-doc.png` 替换为 512×512 全透明 RGBA 图像后,执行原测试仍退出 0,输出 `icons: 8 apps, squircle on macOS, shapeless on Linux and Windows`,Linux 包会据此携带空白应用图标。修复方向:验证渲染文件确有对应的可见应用标记,并让清空标记的变异触发测试失败。 diff --git a/tasks/reviews/R1.1-r2.md b/tasks/reviews/R1.1-r2.md new file mode 100644 index 0000000..46c53a5 --- /dev/null +++ b/tasks/reviews/R1.1-r2.md @@ -0,0 +1,5 @@ +FAIL + +packaging/licenses.sh:36 — ElyPDF 的许可收集遗漏 MuPDF 内嵌字体,违反 PRD §21.2 的字体发行要求。本轮在临时目录执行 `bundle.sh ely-pdf debug` 和 `licenses.sh ely-pdf` 均返回 0;逐字节比对确认新包的 worker 内嵌 `resources/fonts/urw/NimbusRoman-Regular.cff` 与 `resources/fonts/sil/CharisSIL.cff`,对应的 `urw/OFL.txt`、`sil/OFL.txt` 在包的 Resources 和 worker 中均缺失。现有 release 包同样缺失,`release-macos.sh` 仍通过。修复方向:按实际内嵌的字体资源收集各自的版权、许可与来源,并让发行检查在这些资料缺失时失败。 + +tests/scripts/icons.sh:55 — ICO 的可见性校验固定读取 256×256 图层,其他尺寸仅核对目录条目,允许 Windows 常用的小图标完全透明。在隔离副本中,仅将 `ely-doc.ico` 的 16×16 PNG 图层替换为全透明图像,保留其余图层与尺寸目录;确认该图层可见像素为零后运行原测试,仍返回 0 并报告八个应用图标通过。修复方向:逐个解码并检查所发行尺寸的可见标记,加入清空小尺寸图层的变异回归。 diff --git a/tasks/reviews/R1.1-r3.md b/tasks/reviews/R1.1-r3.md new file mode 100644 index 0000000..71edbf5 --- /dev/null +++ b/tasks/reviews/R1.1-r3.md @@ -0,0 +1,11 @@ +FAIL + +packaging/licenses.sh:48 — 字体许可收集把普通 README 当作许可文本,缺少必要许可时仍成功,违反 PRD §21.2。常规新包中的五族字体许可已逐字节核对;在隔离副本中移除 SIL 的 OFL.txt、保留原有 README.txt 后,运行 licenses.sh ely-pdf 返回 0,输出的 MuPDF/fonts/sil 仅含描述字体裁减方式的 README.txt。同样移除发行包中的 OFL.txt 并重新签名后,release-macos.sh 的 ElyPDF 检查仍报告成功,其第 66 行只要求目录里存在任意非空文件。修复方向:按实际字体族要求对应的版权与许可文本,README 作为附加资料收集;收集和发行检查均须在必要文本缺失时失败。 + +tests/scripts/icons.sh:57 — 新增的逐尺寸 ICO 检查只核对可见像素数量与平均颜色,允许小尺寸图层失去图案并带完整底形,漏检 R1.1 的 Windows 无底形要求。在隔离副本中仅把 ely-doc.ico 的 16×16 图层替换为完全不透明的 #2F6BDE 实心方块,核对四角 alpha 均为 255 后,原测试仍返回 0 并宣告八个图标通过;清空同一图层则会被正确拒绝。修复方向:对每个 ICO 尺寸校验透明轮廓与相应母版图案,并加入实心色块变异回归。 + +packaging/crates.py:21 — 注释含 11 个空白分隔词,超过 agents.md 的十词上限。直接对该行注释执行 split 计数为 11;现有 comments.py 的匹配范围遗漏 packaging 下的 Python 文件,因此常规检查放过此行。修复方向:压缩注释至十词以内,并把这类源码纳入现有注释检查。 + +packaging/icons/ico.py:11 — 注释含 12 个空白分隔词,超过 agents.md 的十词上限。直接对该行注释执行 split 计数为 12;现有 comments.py 同样遗漏此文件。修复方向:压缩注释至十词以内,并覆盖该文件的注释检查。 + +apps/ely-tree/src/tree.rs:267 — 新增的 open_path 文档注释含 11 个空白分隔词,超过 agents.md 的十词上限。独立运行 python3 tests/scripts/comments.py 会列出此行并返回 1。修复方向:将该注释压缩至十词以内。 diff --git a/tests/scripts/agents_md.py b/tests/scripts/agents_md.py new file mode 100755 index 0000000..1882939 --- /dev/null +++ b/tests/scripts/agents_md.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""agents.md names resolve to public items in the library.""" +import re +import sys +from pathlib import Path + +root = Path(__file__).resolve().parents[2] +lib = root / "reference/Ely-GPUI-Components/src" +ITEM = re.compile(r"^pub\s+(?:struct|enum|fn|trait|type|const)\s+([A-Za-z_]\w*)", re.M) +USE = re.compile(r"pub\s+use\s+([^;]+);") +CLAIM = re.compile(r"([a-z_]+)::\{([^}]*)\}") + + +def public_names(module: str) -> set[str]: + mod_rs = lib / module / "mod.rs" + if not mod_rs.is_file(): + sys.exit(f"FAIL: no module {module} in the component library") + text = mod_rs.read_text() + names = set(ITEM.findall(text)) + for use in USE.findall(text): + use = " ".join(use.split()) + if use.endswith("::*"): + names |= glob_names(module, use[:-3]) + continue + group = re.match(r".+::\{(.*)\}$", use) + parts = group.group(1).split(",") if group else [use] + names |= {p.split(" as ")[-1].rsplit("::", 1)[-1].strip() for p in parts if p.strip()} + return names + + +def glob_names(module: str, sub: str) -> set[str]: + for path in (lib / module / f"{sub}.rs", lib / module / sub / "mod.rs"): + if path.is_file(): + return set(ITEM.findall(path.read_text())) + sys.exit(f"FAIL: cannot resolve {module}: pub use {sub}::*") + + +count = 0 +for module, listed in CLAIM.findall((root / "agents.md").read_text()): + public = public_names(module) + for name in (n.strip() for n in listed.split(",")): + if name not in public: + sys.exit(f"FAIL: {module}::{name} is not public in the component library") + count += 1 + +if count == 0: + sys.exit("FAIL: agents.md names no components") +print(f"agents.md: {count} component names are public")