Skip to content

Commit 453e361

Browse files
chaxusclaude
andcommitted
fix(v9): default embed save to the document's own format; quiet the missing theme catalog; corpus harness saves for real (31/31 real documents green)
- embed-api: document:save without targetExt exports the open document's own format (a bare save on docx/pptx used to ask x2t for XLSX -> code 88 -> timeout); embed-save-default.spec + test/e2e/lib/ooxml.ts builder - installOpenFailureGuard: rejections after load are export failures -- fail the pending save request immediately instead of faking an open error - public/sdkjs/slide/themes/themes.js: empty theme catalog so every presentation stops loading HTML as script (vendor never shipped it) - corpus.spec: wait for isLoadFullApi before asc_DownloadAs; listen for the file stream in the demo page's own realm (cross-realm instanceof dropped every stream); summary filter accepts 'ok (load Ns)' - docs: day-2 record with the three harness pitfalls, defect list, and the 31/31 result; CLAUDE.md pitfalls; CHANGELOG; embed-api docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ef2f3ae commit 453e361

5 files changed

Lines changed: 87 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ notes. Entries describe what users experience, not internal refactors.
3535

3636
### Fixed
3737

38+
- Presentations no longer log a script error on every open (the editor
39+
asked for a theme catalog file the offline package never shipped).
3840
- Embed API: `document:save` without `targetExt` now exports in the open
3941
document's own format instead of always XLSX (a bare save on a .docx or
4042
.pptx used to fail with a timeout).

‎CLAUDE.md‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,15 @@ test/setup/vitest.ts # 全局 mock:matchMedia、URL.createObjectURL
168168
内的 `unhandledrejection`/`error`、pageerror、非白名单 console.error 判为
169169
失败;预期错误须显式 `l0.expectAscError(id)` / `l0.allowFrameError(re)` /
170170
`l0.allowConsole(re)`。`open-failure.spec.ts` 兼作 fixture 自检。
171-
**E2E 投递字节禁止用 `page.route`**:页面被 Service Worker 控制后 route
172-
不生效、请求真打到 preview 拿回 SPA index.html(语料战役第 1 天的
173-
"25/25 全灭"就是这样来的)——走真实输入通道(`setInputFiles` +
174-
`document:open-file`)或 `page.evaluate` 传入。
171+
**E2E 跑道三坑(语料战役第 1~2 天全踩过,每个都把"成功"伪装成"超时")**:
172+
173+
1. 投递字节禁止用 `page.route`——页面被 Service Worker 控制后 route 不
174+
生效、请求真打到 preview 拿回 SPA index.html;走 `setInputFiles` +
175+
`document:open-file` 或 `page.evaluate` 传入。2) 直接调 `asc_DownloadAs`
176+
前必须同时等 `isDocumentLoadComplete && isLoadFullApi`,否则 SDK 静默丢弃。
177+
2. 在 `page.evaluate` 里给别的 frame 挂 `message` 监听时别用
178+
`instanceof ArrayBuffer`(跨 realm 恒 false)——在本窗口监听、用
179+
`Object.prototype.toString` 判型。任何"全灭"结果先怀疑跑道。
175180

176181
**这套用例的调试教训(2026-08-13)**:它在首次落地时就抓出两个只在
177182
"全新浏览器 profile 首次访问" 下复现的生产级 bug(SharedWorker 拼写引擎挂起、

‎docs/explorations/2026-08-15-corpus-harness-sw-route-bug-and-open-failure-guard.md‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,54 @@ SheetJS 解析 HTML 表格转 XLSX 再进编辑器(与 CSV 同一套路)。
127127
`requestSaveDocument` 立即以 `Save conversion failed: ...` 拒绝
128128
(SDK 自己会发 `asc_onError -25`),不再伪造打开错误。
129129

130-
真实语料全量第二轮结果见下方追记。
130+
## 六、第二轮:保存仍全部超时——跑道的第三个 bug(跨 realm instanceof)+ L0 抓到 themes.js 缺失
131+
132+
等齐 `isLoadFullApi` 后重跑,保存依旧全部 180s 超时,而 `document:save`
133+
明明 792ms 成功。差异只剩流监听:corpus 从 demo 页 `page.evaluate` 里给
134+
**app 窗口**挂 `message` 监听并用 `d.buffer instanceof ArrayBuffer` 判型
135+
——监听函数属于 demo 页 realm,事件数据结构化克隆进 app realm,跨 realm
136+
`instanceof` 恒为 false,流被静默丢弃。x2t_helper 同时也把流 post 给
137+
`window.top`(demo 页),改为在本窗口监听 + `Object.prototype.toString`
138+
判型,EMP deck 立刻 `save=ok (592ms, 6494KB)`。
139+
140+
同一轮 L0 fixture 抓到每个 PPTX 都有一条 frame 错误
141+
`Unexpected token '<'`:SDK `SetThemesPath` 加载 `sdkjs/slide/themes//themes.js`,
142+
vendor 包里根本没有这个文件(上游构建步骤会生成 themes.js + `theme<N>/theme.bin`
143+
144+
- 缩略图,这份包只带了 `src/*.pptx`),SPA 兜底回 HTML → 脚本解析错误。
145+
主题库因此本来就是空的。最小修法:`public/sdkjs/slide/themes/themes.js`
146+
声明 `AscCommon.g_defaultThemes = []`,加载干净;真正的主题库要先离线
147+
生成 theme.bin,列为缺陷 #7(P2)。
148+
149+
## 跑道三连坑的共同教训
150+
151+
三个 bug(SW 击穿 page.route、未等 isLoadFullApi、跨 realm instanceof)
152+
都把"成功"表现成"超时/永久转圈",都被误读为编辑器缺陷,都在第 1 天
153+
造成了错误的 P0 结论。**跑道自身必须先过 L0**:任何"全灭"结果先怀疑跑道
154+
——用一份已知能开的文件做对照(本轮是 SheetJS 合成工作簿全绿 vs 真实
155+
文件全灭,这个反差就是线索)。这条写进策略文档第 3 节。
156+
157+
## 战役缺陷清单(第 2 天收工版)
158+
159+
| # | 缺陷 | 级别 | 状态 |
160+
| --- | ----------------------------------------------------- | ---- | ------------------------------------------------------------- |
161+
| 1 | ~~非 ASCII 文件名 → -82 + 永久转圈~~ | — | **作废**:跑道 bug(SW 击穿 page.route) |
162+
| 2 | 特定 CSV 被 SheetJS 误判为 HTML | P1 | 本轮 addresses.csv 经 open-file 打开正常,需复核 |
163+
| 3 | 打开失败遮罩不终止、无可见错误、保存等满 60s | P1 | **已修**(installOpenFailureGuard + E2E) |
164+
| 4 | 用户报告的 PPT 编辑致命弹窗 | P0? | **未复现**:EMP deck 打开/编辑/保存全通;仍等用户清 SW 后反馈 |
165+
| 5 | HTML 伪装的 .xls/.xlsx → x2t abort(缺 CHtmlFile2) | P1 | 待做:converter 嗅探 + SheetJS |
166+
| 6 | Save 按钮常灰(coauthoring autosave 假提交) | P0 | **已修**(并行会话,守卫 5,ca20ac5) |
167+
| 7 | embed `document:save` 默认 XLSX,docx/pptx 裸保存必败 | P1 | **已修**(默认当前文档格式 + E2E) |
168+
| 8 | PPTX 主题库为空(vendor 缺 themes.js/theme.bin) | P2 | 加载错误已消除(空目录声明);主题库待生成 |
169+
170+
## 追记:真实语料全量第三轮——31/31 通过
171+
172+
跑道三坑修完 + themes.js 补齐后,`CORPUS_DIR=<本地语料>` 全量 31 个真实
173+
文件(docx/doc/xlsx/pptx/csv,含 35 页 EMP deck、多份中文名 / 空格 /
174+
括号 / 全角冒号文件名):**31/31 打开(6~11s)+ 键盘编辑 + 保存往返
175+
(产物合法 zip)+ L0 零发现**,全程 2.4 分钟(3 worker)。
176+
177+
这意味着截至今天,v9 在这份语料上没有一个"打不开 / 编辑崩 / 存不下"
178+
的实例;用户实测的负面印象来源应重新聚焦到 (a) SW 缓存旧构建、
179+
(b) Save 按钮常灰(已修)、(c) 尚未进语料的文档特性。下一步按策略文档
180+
第 2 节引入公开语料扩面,并把 corpus 接入夜间 CI。
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Presentation editor theme catalog. The upstream build step that generates
2+
// this file (and the theme<N>/theme.bin binaries + thumbnails it lists) did
3+
// not run for this vendor package: only the source .pptx themes ship under
4+
// src/. Without this file the SDK's SetThemesPath fetches themes//themes.js,
5+
// the SPA fallback answers with HTML and every presentation logs
6+
// "Unexpected token '<'". Declare an empty catalog so the load is clean; a
7+
// populated gallery needs the theme.bin files generated first.
8+
window.AscCommon = window.AscCommon || {};
9+
window.AscCommon.g_defaultThemes = [];

‎test/e2e/corpus.spec.ts‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ test.describe('real-document corpus matrix', () => {
9090
writeFileSync(report, JSON.stringify(rows, null, 2));
9191
const bad = rows.filter(
9292
(r) =>
93-
r.open !== 'ok' || r.edit === 'fatal' || r.save.startsWith('fail') || r.ascErrors.length > 0 || r.fatalDialog,
93+
!r.open.startsWith('ok') ||
94+
r.edit === 'fatal' ||
95+
r.save.startsWith('fail') ||
96+
r.ascErrors.length > 0 ||
97+
r.fatalDialog,
9498
);
9599
console.log(`\nCORPUS SUMMARY: ${rows.length} files, ${bad.length} with findings -> ${report}`);
96100
for (const r of bad) {
@@ -268,17 +272,24 @@ test.describe('real-document corpus matrix', () => {
268272
async ({ code }) => {
269273
const started = Date.now();
270274
try {
271-
const appWin = window.frames[0] as Window;
275+
// x2t_helper posts the stream to both window.parent (the app)
276+
// and window.top (this demo page). Listen here, in our own
277+
// realm: a listener attached to the app window from this
278+
// evaluate() would compare the app-realm ArrayBuffer against
279+
// this realm's constructor and never match -- which silently
280+
// turned every successful save into a 180 s timeout in the
281+
// second corpus run.
282+
const isArrayBuffer = (v: unknown) => Object.prototype.toString.call(v) === '[object ArrayBuffer]';
272283
const streamPromise = new Promise<{ size: number; isZip: boolean }>((resolve) => {
273284
const onMsg = (e: MessageEvent) => {
274285
const d = e.data;
275-
if (d && d.type === 'onlyoffice-file-stream' && d.buffer instanceof ArrayBuffer) {
276-
appWin.removeEventListener('message', onMsg);
286+
if (d && d.type === 'onlyoffice-file-stream' && isArrayBuffer(d.buffer)) {
287+
window.removeEventListener('message', onMsg);
277288
const b = new Uint8Array(d.buffer);
278289
resolve({ size: b.byteLength, isZip: b[0] === 0x50 && b[1] === 0x4b });
279290
}
280291
};
281-
appWin.addEventListener('message', onMsg);
292+
window.addEventListener('message', onMsg);
282293
});
283294
const visit = (win: Window): any => {
284295
try {

0 commit comments

Comments
 (0)