diff --git a/agents.md b/agents.md index aaacc4f..482ae91 100644 --- a/agents.md +++ b/agents.md @@ -172,6 +172,7 @@ tests/scripts/ooxml.py <用例> <文件> 不经 LibreOffice 读 DOCX、XLSX、P tests/scripts/webview.sh 真窗口 CEF 页面:输入法组字与提交、键入、焦点、主题变量、缩放、不可离开所在文件夹 cd site && pnpm deploy 官网构建并经 wrangler 发到 Cloudflare Workers(只有静态资源),地址 https://ely-office.zhangyanghaha0407.workers.dev cd site && pnpm test && pnpm e2e 官网单测(几何、道路连通)与 Playwright 端到端(悬停、聚焦、按压、命中归属) +cd site/video/scenes && npm run verify 发布视频:离场六块屏幕在切场后各播自己的录屏帧(真页面渲染) cargo check --workspace cargo test --workspace rm -rf target/debug/incremental 每次构建或测试后都删(用户定:增量缓存会占满磁盘) diff --git a/site/video/montage/scene.ts b/site/video/montage/scene.ts index f97306e..ecfa30c 100644 --- a/site/video/montage/scene.ts +++ b/site/video/montage/scene.ts @@ -1,6 +1,7 @@ // Montage: six screens on the turning model, in WebGL. import { model } from "../../src/model.ts"; -import { type Box, grown, screens } from "../scenes/parts.ts"; +import { type Box, screens } from "../scenes/parts.ts"; +import { slabs } from "../scenes/turn.ts"; import { BEAT, glide, toward } from "../scenes/time.ts"; import recs from "./recs.json"; @@ -14,7 +15,6 @@ const TURN: [number, number] = [128, 136]; const PIVOT = [8, 5.5]; const apps = recs.apps; -const slabs = apps.map((a) => grown(a.app)); const ground = model.flatMap((p) => p.blocks).filter((b) => b !== screens.ElyShow); type Rgb = [number, number, number]; diff --git a/site/video/scenes/package.json b/site/video/scenes/package.json index a8eafc5..3063d88 100644 --- a/site/video/scenes/package.json +++ b/site/video/scenes/package.json @@ -4,7 +4,8 @@ "type": "module", "scripts": { "render": "node render.mjs", - "check": "tsc -p ." + "check": "tsc -p .", + "verify": "node verify.mjs" }, "dependencies": { "playwright": "1.64.0" diff --git a/site/video/scenes/parts.ts b/site/video/scenes/parts.ts index 76c4d48..a45c145 100644 --- a/site/video/scenes/parts.ts +++ b/site/video/scenes/parts.ts @@ -164,7 +164,7 @@ export const props: Prop[] = [ }, ]; -/** A screen grown about its bottom centre; Show on its base. */ +/** A screen grown about its base; Show stands apart. */ export function grown(app: string): Box { if (app === "ElyShow") return { x: 8.6, y: 2, z: model[0].blocks[0].h + 0.25, w: 4.8, d: 0.08, h: 2.7, tone: SCREEN }; const b = screens[app]; diff --git a/site/video/scenes/turn.ts b/site/video/scenes/turn.ts index 2337cd1..f80f490 100644 --- a/site/video/scenes/turn.ts +++ b/site/video/scenes/turn.ts @@ -17,7 +17,7 @@ export function turned(a: number, x: number, y: number, z: number): [number, num /** The montage's grown screens, which leave in bar 35. */ export const slabs: Box[] = recs.apps.map(({ app }) => grown(app)); -/** The recording a leaving slab plays at frame `f`, if any. */ +/** A leaving slab's recording at frame `f`, if any. */ export function recording(box: Box, land: number, f: number): string | null { const slab = slabs.indexOf(box); if (slab < 0 || land <= 0) return null; diff --git a/site/video/scenes/verify.mjs b/site/video/scenes/verify.mjs new file mode 100644 index 0000000..ba71b14 --- /dev/null +++ b/site/video/scenes/verify.mjs @@ -0,0 +1,34 @@ +// Checks the leaving screens play their recordings at the cut. +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { chromium } from "playwright"; +import { createServer } from "vite"; +import recs from "../montage/recs.json" with { type: "json" }; + +const here = dirname(fileURLToPath(import.meta.url)); +const server = await createServer({ root: resolve(here, "../.."), configFile: false, logLevel: "error", server: { port: 0 } }); +await server.listen(); +const browser = await chromium.launch(); +try { + const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); + page.on("pageerror", (e) => { + throw e; + }); + // Any frame will do; only which frame is asked matters. + await page.route("**/rec/**", (route) => route.fulfill({ path: resolve(here, "../../public/film/poster.jpg") })); + await page.goto(`${server.resolvedUrls.local[0]}video/scenes/index.html`, { waitUntil: "networkidle" }); + // The first frame after the montage is the cut. + const cut = recs.first + 129; + for (const f of [cut, cut + 1, cut + 2]) { + await page.evaluate((f) => window.ely.render("files", f), f); + const hrefs = await page.$$eval("#world image", (els) => els.map((el) => el.getAttribute("href"))); + const want = String(f - recs.first + 1).padStart(5, "0"); + if (hrefs.length !== 6 || !hrefs.every((h) => h.endsWith(`/${want}.jpg`)) || new Set(hrefs).size !== 6) { + throw new Error(`frame ${f}: ${hrefs.length} recordings ${JSON.stringify(hrefs)}`); + } + } + console.log("verify: six screens play their own frames across the cut"); +} finally { + await browser.close(); + await server.close(); +} diff --git a/tasks/reviews/R2.10-r1.md b/tasks/reviews/R2.10-r1.md new file mode 100644 index 0000000..27c9fda --- /dev/null +++ b/tasks/reviews/R2.10-r1.md @@ -0,0 +1,5 @@ +FAIL + +patches/libreoffice/0041-lok-calc-chart-beside-its-data.patch:21 — 图表定位仍受视口限制,大数据区或窄视口下会压住源数据。补丁扩大 aPositionRange 后继续调用 GetChartInsertPos;该函数将超出视口的图表挤回视口。实测把 A1:P50 全部填入数值 1,设置可见区为 (0,0,15000,10000) twips,光标回 A1,再以 A1:P50 插入柱图:源数据矩形为 (0,0,20479,12799),图表为 (5872,57,9071,5101),图表整个落在数据区内。A1:C5 配合 (0,0,6000,3500) 视口也复现,图表为 (57,57,9070,5101)。两次独立运行结果一致,保存后的 FODS 坐标及导出 PDF 渲染确认覆盖。修复方向:定位时保证最终图表矩形与源数据分离,允许放到当前视口之外并滚动展示;在 a_new_chart_lands_beside_its_data 中加入大数据区和窄视口用例。独立构建通过,现有 analysis 六项测试全部通过,补丁序列测试通过;证据见 /tmp/ely-r2-10-tests.log、/tmp/ely-r2-10-probes-repeat.log、/tmp/ely-r2-10-persisted.log。 + +patches/libreoffice/0041-lok-calc-chart-beside-its-data.patch:23 — 位置计算只参考图表源范围,其旁已有的数据会被覆盖,违反 R2.10 要求的“数据区旁的空白处”。具体场景:A1:C5 和 F1:P20 填入数值 1,D:E 留空,可见区为 (0,0,15000,10000) twips;在 A1 以 A1:C5 插入柱图。图表矩形为 (3897,57,9070,5101),覆盖数值单元格 F1,其矩形为 (6400,0,1279,255),并覆盖其下方多行数据;保存后的 FODS 与 PDF 渲染同样复现。现有 analysis.rs:352 的断言在此仍成立,因为图表左边界 3897 大于源数据右边界 3839。修复方向:检查整个候选图表矩形对应的已有单元格,选择足以容纳图表的空白区域;补充相邻独立数据块的回归断言。证据见 /tmp/ely-r2-10-probes-repeat.log、/tmp/ely-r2-10-persisted.log、/tmp/ely-r2-10-validation.log。 diff --git a/tasks/reviews/R2.12-r2.md b/tasks/reviews/R2.12-r2.md new file mode 100644 index 0000000..057196f --- /dev/null +++ b/tasks/reviews/R2.12-r2.md @@ -0,0 +1,9 @@ +FAIL + +../Ely-GPUI-Components/src/git/rows.rs:128 — `CMakeLists.txt` 先命中扩展名分支,随后因 `txt` 属于 PROSE 返回 None;131 行为它设置的 `#` 分支永远到不了,CMake 主文件的语法着色整体丢失。复现:旧内容为 `# return 42\n`,新内容追加 `value = 1\n`,分别以 `CMakeLists.txt` 和 `build.cmake` 打开 DiffViewer。前者的注释使用正文色,后者使用注释色;原生窗口的行内、并排布局均复现,同一注释区域分别相差 896、1792 个像素。证据:/tmp/R2.12-r2-language.log、/tmp/R2.12-r2-render.log。修复方向:先识别特殊文件名,再执行扩展名分类,并覆盖 `CMakeLists.txt` 的实际着色。 + +../Ely-GPUI-Components/src/git/rows.rs:145 — 文件类型只被转换成注释起始符,实际着色仍共用 forms/code.rs 的跨语言关键字与常量表,上一轮要求的语言着色修复仍有遗漏。复现:`script.py` 中的有效代码 `assert ready and True` 将 `assert`、`and` 着为 variable,将 `True` 着为 type;`config.toml` 中有效的裸键 `return = 42` 将 `return` 着为 keyword。定向探针记录了实际 HighlightStyle,原生窗口两种布局均显示对应错误颜色。证据:/tmp/R2.12-r2-language.log、/tmp/R2.12-r2-native.log。修复方向:将语言类型传到着色层,按该语言识别关键字、常量及配置键,并加入这些输入的颜色回归断言。 + +../Ely-GPUI-Components/src/forms/code.rs:69 — 注释判定只检查当前位置是否以起始符开头,遗漏 Shell 的词边界条件,将普通参数的后半段吞成注释。复现:`run.sh` 内容为 `echo foo#bar 42`;实际 Bash 输出 `foo#bar 42`,DiffViewer 却将 `#bar 42` 整段着为 comment,原生窗口行内、并排布局均复现。证据:/tmp/R2.12-r2-language.log、/tmp/R2.12-r2-native.log。修复方向:按语言处理注释起始条件,Shell 的 `#` 在词中保持参数内容,补充真实注释和词中 `#` 的颜色断言。 + +../Ely-GPUI-Components/src/git/rows.rs:179 — 新测试直接调用 colors_of,上一轮指出的渲染接线回归覆盖仍然缺失。复现:在隔离副本中将 viewer.rs:197 改为 `let path: SharedString = "forced.rs".into();`,组件库 `cargo test --lib --features test-support --offline --locked git::` 仍为 35 项全过;实际 DiffViewer 的 Markdown 在行内、并排布局重新出现代码颜色,文本区域分别改变 890、1780 个像素,Python/TOML 行注释也重新出现关键字与数字颜色。证据:/tmp/R2.12-r2-mutation-test.log、/tmp/R2.12-r2-render.log。基线 ElyTree 检查、构建、111 项测试及组件库全量 1726 项测试均通过;探针副本与独立 target 已删除,两个原始仓库的源码散列与状态核对通过。修复方向:增加经过实际 DiffViewer 两种布局的文本颜色断言,使渲染器收到错误文件类型的突变触发失败。 diff --git a/tasks/reviews/R2.13-r2.md b/tasks/reviews/R2.13-r2.md new file mode 100644 index 0000000..7da863e --- /dev/null +++ b/tasks/reviews/R2.13-r2.md @@ -0,0 +1,7 @@ +FAIL + +apps/ely-mark/web/src/preserve.js:58 — 上一轮的引用链接表格缺陷仍可复现,未满足 R2.13 与 MARK-03。打开 `|a|[link][r]|\n|-|-|\n|1|2|\n\n[r]: https://example.com\n`,可视模式实际渲染零张表格,整张表和引用定义均显示为原文块。序列化将引用链接展开为内联链接,`canon` 仍按两种链接的源码逐字比较,结果继续拒绝该合法 GFM 表格。原生键入 x 并保存得到 `x|a|[link][r]|…`,首格编辑断言失败;证据:`/tmp/R2.13-r2-browser.log`、`/tmp/R2.13-r2-native-reference.log`。修复方向:结合全文引用定义识别等价链接,同时保留引用写法的源码保护;补充实际表格渲染和首格编辑保存断言。 + +tests/scripts/mark-session.sh:133 — 新增用例将上一轮仍失败的列表首块表格写成成功预期,掩盖 R2.13 与 MARK-03 的缺口。打开合法 GFM `- |alpha|beta|\n |---|---|\n |1|2|\n`,实际渲染零张表格,整个列表显示为原文块;原生键入 x 后保存得到 `x- |alpha|beta|…`,而本行测试正接受这种编辑 Markdown 标记的行为。引用块内表格和段落后的列表表格已通过,列表以表格起始的场景仍失败。证据:`/tmp/R2.13-r2-browser.log`、`/tmp/R2.13-r2-native-leading.log`,现有原生脚本全部通过见 `/tmp/R2.13-r2-native-session.log`。修复方向:为表格起始的列表项建立可视编辑表示并保留源文映射,将测试改为断言真实表格和首格编辑后的保存结果。 + +apps/ely-mark/web/src/tables.js:25 — 规范比较把 GFM 隐式补齐的空单元格视为内容差异,导致本轮新增可视表格回退,未满足 R2.13 与 MARK-03。打开 `|a|b|c|\n|-|-|-|\n|1|2|\n`,最后一行合法地省略第三格;Milkdown 序列化为 `|1|2||`,当前 `rows` 对原文保留两个单元格、对序列化结果保留三个,规范结果不同,整张表成为原文块。父提交独立构建后显示一张表格并可编辑首格,本提交显示零张;原生首格编辑保存断言同样失败,保存为 `x|a|b|c|…`。证据:`/tmp/R2.13-r2-parent.log`、`/tmp/R2.13-r2-browser.log`、`/tmp/R2.13-r2-native-missing.log`。修复方向:按表头列数为比较用的短行补齐空格,同时保留未编辑原文的字节和格内未知语法保护;加入短行表格的渲染、未编辑保存及首格编辑回归断言。 diff --git a/tasks/reviews/R2.3-r2.md b/tasks/reviews/R2.3-r2.md new file mode 100644 index 0000000..47d44c5 --- /dev/null +++ b/tasks/reviews/R2.3-r2.md @@ -0,0 +1,5 @@ +FAIL + +site/test/film.test.ts:23、site/test/film.test.ts:26:离场回归测试只比较辅助函数生成的几何、检查录屏路径的真值,上一轮要求的实际渲染验收仍有缺口。独立执行 15 项单测、官网构建、场景类型检查和两项影片端到端测试均通过;在隔离副本中将 `site/video/scenes/page.ts:180` 改为 `if (href && land > 2)` 后,15 项单测与两项影片端到端测试仍全部通过,实际调用 `window.ely.render("files", 2186)` 的录屏 image 数量从 6 降为 0,六块屏幕在切场强拍变黑。另将群像的 ElyTree 屏幕深度单独恢复为 y=7.9,15 项单测同样全部通过,直接渲染第 2185→2186 帧测得该屏幕左上角跳移 18.701 像素;正常版本为 0.006 像素。修复方向:测试直接调用群像与离场渲染器,比较交界帧的屏幕角点,并在屏幕可见期间核对实际加载的六段录屏及其帧号,确保上述渲染变异触发失败。 + +site/video/scenes/turn.ts:20、site/video/scenes/parts.ts:167:本轮新增或迁移的注释各有 11 词,超过 agents.md 的单行十词限制。分别读取 `The recording a leaving slab plays at frame f, if any.` 与 `A screen grown about its bottom centre; Show on its base.`,按空白分词均可复现;上一轮指出的 page.ts 注释已缩短为 8 词。修复方向:将这两处注释各缩短到十词以内。 diff --git a/tasks/reviews/R2.5-r2.md b/tasks/reviews/R2.5-r2.md new file mode 100644 index 0000000..f87f6d3 --- /dev/null +++ b/tasks/reviews/R2.5-r2.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-show/src/slideshow/start.rs:342:新增全屏回归测试只核对 `bounds()` 的枚举值,macOS 分支期待 `Windowed`,遗漏了决定菜单栏与程序坞覆盖行为的 `cover()`。具体失败场景:在隔离副本中移除 `window.toggle_simple_fullscreen()`,新增测试及完整 76 项 ElyShow 测试全部通过;运行该副本按 F5,3008×1269 屏幕上的放映窗口位于 `[0,30,3008,1269]`,顶部菜单栏仍占位,窗口底部越出屏幕。原版连续两次放映均为 `[0,0,3008,1269]`;原版构建、76 项测试、Metal compose 与注释检查通过,上一轮 Windows/Linux 原生全屏路径已复核修复。证据见 `/tmp/ely-r25-r2-verification.log`、`/tmp/ely-r25-r2-mutant-full.log`、`/tmp/ely-r25-r2-mutant-native-frames.log`;副本及其独立构建目录已删除。修复方向:让回归测试经过实际放映窗口创建路径,验证 macOS 进入 simple fullscreen 后的窗口覆盖范围及菜单栏、程序坞隐藏状态,并确保移除全屏调用会使测试失败。