fix(desktop): dedupe error UI + wire iframe error listener#14
Conversation
There was a problem hiding this comment.
Findings
-
[Major] 旧 iframe 错误会泄漏到下一次生成结果中。
CanvasErrorBar现在会一直挂在预览区顶部,但这次改动没有在新一轮sendPrompt或新 iframe 建立时清空iframeErrors;结果是上一个 preview 的运行时异常会继续显示在新的 loading / preview 上,用户会把旧错误误判成当前结果的问题。证据:apps/desktop/src/renderer/src/components/PreviewPane.tsx:52、apps/desktop/src/renderer/src/store.ts:116-117、apps/desktop/src/renderer/src/store.ts:155-159
Suggested fix:set((s) => ({ messages: [...s.messages, userMessage], isGenerating: true, errorMessage: null, iframeErrors: [], }));
-
[Major]
iframeErrors现在是无上限追加的。window.messagelistener 会把每个IFRAME_ERROR都推入 store,而pushIframeError每次都复制并扩展整个数组;AI 生成的 HTML 很容易出现setInterval/循环重试导致的重复 throw/reject,这里会持续占用内存并放大 rerender 成本。证据:apps/desktop/src/renderer/src/App.tsx:35、apps/desktop/src/renderer/src/store.ts:117
Suggested fix:pushIframeError(message: string) { set((s) => { const last = s.iframeErrors[s.iframeErrors.length - 1]; const next = last === message ? s.iframeErrors : [...s.iframeErrors, message]; return { iframeErrors: next.slice(-5) }; }); }
Summary
- Review mode: initial
- 发现 2 个 Major 问题,都是这次新增的 iframe error surface 引入的:旧错误会跨 preview 泄漏,且错误缓冲区无上限增长。
- 未发现新增依赖、直接 provider SDK import 或 UI token 字面量违规。DCO / license / install-size 变化在本次 diff 中未涉及。
Testing
- Not run (automation)
- 缺少 Vitest coverage:
window.postMessage->pushIframeError、重新生成时清空旧错误、重复错误的去重/上限行为。
open-codesign Bot
| return ( | ||
| <div className="flex flex-col min-h-0 flex-1"> | ||
| <PreviewToolbar /> | ||
| <CanvasErrorBar /> |
There was a problem hiding this comment.
这里把 CanvasErrorBar 变成了常驻 UI,但本 PR 没有配套清空 iframeErrors。现在只要上一个 preview 抛过一次错,这条 bar 就会一直挂到后续新的 loading / preview 上,误导用户把旧错误当成当前结果的问题。
Suggested fix:
set((s) => ({
messages: [...s.messages, userMessage],
isGenerating: true,
errorMessage: null,
iframeErrors: [],
}));| iframeErrors: [], | ||
|
|
||
| pushIframeError(message: string) { | ||
| set((s) => ({ iframeErrors: [...s.iframeErrors, message] })); |
There was a problem hiding this comment.
这里是无上限追加;如果 preview 里的脚本在定时器里反复 throw / reject,store 会持续增长,而且每次都复制整数组。对 AI 生成的任意 HTML 来说,这个路径很容易被打满。
Suggested fix:
pushIframeError(message: string) {
set((s) => {
const last = s.iframeErrors[s.iframeErrors.length - 1];
const next = last === message ? s.iframeErrors : [...s.iframeErrors, message];
return { iframeErrors: next.slice(-5) };
});
}|
目前不建议合并,先处理下面这些问题:
CI 主体已经是绿的,但上面这些行为问题和 DCO 没处理前,还是不适合进 |
There was a problem hiding this comment.
Findings
- [Major] 重新生成期间,上一版 preview 仍会把旧的
IFRAME_ERROR重新灌回这次新清空的错误条。sendPrompt()现在会在开始时清空iframeErrors,但PreviewPane只有在“没有旧 preview”时才切到LoadingState;已有 preview 时,旧 iframe 会继续挂着并继续postMessage。这样一来,新增的全局 listener 会在同一轮生成过程中重新写入旧错误,CanvasErrorBar也会把它显示成当前结果的问题。证据:apps/desktop/src/renderer/src/App.tsx:32、apps/desktop/src/renderer/src/components/PreviewPane.tsx:31、apps/desktop/src/renderer/src/store.ts:159
Suggested fix:const handler = (e: MessageEvent) => { if (!isIframeErrorMessage(e.data)) return; const { isGenerating, pushIframeError } = useCodesignStore.getState(); if (isGenerating) return; pushIframeError(e.data.message); };
Summary
- Review mode: follow-up after new commits
- 还剩 1 个 Major 问题:这次虽然补上了清空和去重/上限,但旧 preview 在重新生成期间仍能继续污染新的 iframe error surface。
- 未发现这次新增/修改行引入新的依赖、直接 provider SDK import、silent fallback 或 UI token 字面量违规。DCO / license / install-size 变化未涉及本次 diff。
Testing
- Not run (automation)
- 建议补一个集成测试:在已有
previewHtml时触发新一轮sendPrompt(),并在isGenerating=true期间模拟旧 iframe 发出IFRAME_ERROR,断言CanvasErrorBar不会重新出现。
open-codesign Bot
|
|
||
| useEffect(() => { | ||
| const handler = (e: MessageEvent) => { | ||
| if (isIframeErrorMessage(e.data)) { |
There was a problem hiding this comment.
这里仍然会把“上一版 preview 还在运行时抛出的错误”记到这次新生成里。sendPrompt() 虽然先清空了 iframeErrors,但 PreviewPane 在已有 previewHtml 时不会卸载旧 iframe,所以它在生成期间还能继续 postMessage;这个全局 listener 会把这些旧错误重新写回 store,最后让 CanvasErrorBar 看起来像是当前结果出了问题。
Suggested fix:
const handler = (e: MessageEvent) => {
if (!isIframeErrorMessage(e.data)) return;
const { isGenerating, pushIframeError } = useCodesignStore.getState();
if (isGenerating) return;
pushIframeError(e.data.message);
};There was a problem hiding this comment.
Findings
- 未发现基于本次新增/修改行、且置信度足够高的问题。
Summary
- Review mode: follow-up after new commits
- 上一轮指出的 Major 问题已经在
apps/desktop/src/renderer/src/App.tsx:13与apps/desktop/src/renderer/src/store.ts:159的改动中得到处理:重新生成期间会忽略IFRAME_ERROR,并在开始新一轮生成时清空旧的iframeErrors;本轮未发现新的 diff-attributable 问题。 - 本次 diff 未引入新依赖,也未看到直接 provider SDK import、silent fallback 或新增 UI token 字面量违规。license / install-size 变化不涉及本次 diff。
- 残余风险是测试边界仍偏窄:当前只覆盖了辅助函数和 store action,未见覆盖
window.message监听到CanvasErrorBar渲染联动的组件级测试。
Testing
- Not run (automation)
- 本环境缺少
pnpm,未执行 Vitest / Playwright。
open-codesign Bot
There was a problem hiding this comment.
Findings
-
[Major] 旧 iframe 的晚到错误仍会污染新预览状态。新的全局
message监听只校验isIframeErrorMessage,不再校验event.source是否仍然指向当前预览 iframe;这样一来,上一轮生成在切换前后迟到的postMessage仍会被pushIframeError收下,stale runtime error 会重新出现在新预览上,证据apps/desktop/src/renderer/src/App.tsx:37、apps/desktop/src/renderer/src/components/PreviewPane.tsx:31。
Suggested fix:if (!isTrustedPreviewMessageSource(event.source, iframeRef.current?.contentWindow)) return; const state = useCodesignStore.getState(); if (state.isGenerating || !isIframeErrorMessage(event.data)) return; state.pushIframeError(formatIframeError(event.data));
-
[Minor] iframe 运行时错误的定位信息被截断。
readIframeErrorMessage()现在只保留message,把原先的kind/source/lineno都丢掉了;CanvasErrorBar最终只能显示裸文本,和运行时消息契约提供的上下文不一致,证据apps/desktop/src/renderer/src/App.tsx:13、packages/runtime/src/iframe-errors.ts:13。
Suggested fix:const location = data.source && data.lineno ? ` (${data.source}:${data.lineno})` : ''; return `${data.kind}: ${data.message}${location}`;
Summary
- Review mode: follow-up after new commits
- 本轮主要问题集中在
App.tsx新增的 iframe 错误监听:它解决了“生成中忽略旧错误”的一部分场景,但同时丢掉了当前 iframe source 校验和错误位置信息。 - 未看到本次 diff 引入新依赖、直接 provider SDK import、silent fallback,license / install-size 变化也不涉及本次修改。
Testing
- Not run (automation)
- 按本任务的安全约束,未执行 PR 中代码,因此未跑 Vitest / Playwright。
open-codesign Bot
| useEffect(() => { | ||
| const handler = (e: MessageEvent) => { | ||
| const state = useCodesignStore.getState(); | ||
| const message = readIframeErrorMessage(state.isGenerating, e.data); |
There was a problem hiding this comment.
这里少了对 event.source 的约束。上一版错误监听虽然会把 stale error 带进来,但至少只接受当前 preview iframe 发出的消息;现在改成 App 级全局监听后,旧 iframe 在切换边界上迟到的 IFRAME_ERROR 也会被收下,导致新预览重新出现上一轮的 runtime error。更稳妥的做法是继续复用 isTrustedPreviewMessageSource(...),或者把 error 监听留在持有 iframeRef 的组件里。
|
|
||
| export function readIframeErrorMessage(isGenerating: boolean, data: unknown): string | null { | ||
| if (isGenerating || !isIframeErrorMessage(data)) return null; | ||
| return data.message; |
There was a problem hiding this comment.
readIframeErrorMessage() 现在只返回 data.message,把 kind/source/lineno 都丢掉了。当前 runtime 合同仍然会提供这些字段,所以这会把原本可定位的 error: boom (foo.js:12) 退化成裸 message,CanvasErrorBar 的排障价值明显下降。建议在这里保留最少的上下文格式化。
Signed-off-by: Sun-sunshine06 <[email protected]> Signed-off-by: hqhq1025 <[email protected]>
- Change pushIframeError cap from 10 to 50; use shift-oldest strategy so the cap is explicit and the no-op dedup path is allocation-free. - Return early (no state update) when new message equals the last entry. - Add store.test.ts with three focused tests: clear-on-sendPrompt, consecutive-duplicate dedup, and the 50-entry hard cap. - Fix import order in App.tsx to satisfy biome organizeImports. Signed-off-by: hqhq1025 <[email protected]>
9a7d0e8 to
ed3d56e
Compare
There was a problem hiding this comment.
Findings
- [Blocker]
App.tsx新增的全局message监听会与PreviewPane现有监听并行消费同一条IFRAME_ERROR,且这里没有event.source校验,导致两类回归:1) 同一错误被重复写入(kind: message+ 裸message);2) 非当前 preview iframe 的消息也可能被接受,重新污染当前画布错误栏。证据apps/desktop/src/renderer/src/App.tsx:39、apps/desktop/src/renderer/src/App.tsx:42、apps/desktop/src/renderer/src/App.tsx:44(对比现有 source 校验apps/desktop/src/renderer/src/components/PreviewPane.tsx:36)。
Suggested fix:// App.tsx // 删除 App 级 iframe 错误监听,保留 PreviewPane 内部监听(已有 source 校验 + 定位信息) // useEffect(() => { ...window.addEventListener('message', handler)... }, []); // export function readIframeErrorMessage(...) { ... }
Summary
- Review mode: follow-up after new commits
- 本轮发现 1 个阻塞问题:
App层新增监听引入重复/越权来源接收,属于本次 diff 直接引入的行为回归。 - 本次 diff 未引入新第三方依赖;未发现 direct provider SDK import。
Testing
- Not run (automation): 当前环境缺少
pnpm(pnpm: command not found),无法执行 Vitest/Playwright。
open-codesign Bot
| useEffect(() => { | ||
| const handler = (e: MessageEvent) => { | ||
| const state = useCodesignStore.getState(); | ||
| const message = readIframeErrorMessage(state.isGenerating, e.data); |
There was a problem hiding this comment.
这里直接消费 window.message 的 e.data,但没有任何 event.source 约束。由于 PreviewPane 仍在监听并已做 source 校验,这里会形成重复消费并允许非当前 preview 源消息进入 store。建议删除 App 级监听,或至少把 source 校验前置到与 preview iframe 绑定的上下文中。
// App.tsx
// remove this listener and keep PreviewPane's onMessage handler only…ion info - Delete readIframeErrorMessage helper and the global window.addEventListener in App.tsx that was consuming IFRAME_ERROR without source validation, causing duplicate pushes to iframeErrors alongside the one in PreviewPane. - Extract formatIframeError pure helper in PreviewPane.tsx so the kind + source:lineno location string is testable independently. - Replace App.test.ts (which tested the now-deleted helper) with tests covering formatIframeError location formatting edge cases. Signed-off-by: hqhq1025 <[email protected]>
There was a problem hiding this comment.
Findings
- No Blocker/Major/Minor/Nit findings with >=80% confidence that are introduced by the current added/modified lines.
Summary
- Review mode: follow-up after new commits
- No diff-attributable issues found in the latest PR diff.
- Constraint checks in scope: no new dependencies added (license/install-size impact not introduced by this diff), no direct provider SDK imports in changed files, and no newly introduced silent fallback pattern found in changed lines.
- Residual risk/testing gap: runtime behavior was reviewed statically only; integration behavior was not executed in this review run.
Testing
- Not run (automation): PR-content code execution was intentionally not performed in this review run.
open-codesign Bot
Summary
sendPromptcatch 块里的 toast 错误调用,生成失败只走 ErrorStateIFRAME_ERROR推到 storepushIframeErroractionTest plan
pnpm --filter @open-codesign/desktop typecheck✅pnpm lint✅throw new Error(),确认 CanvasErrorBar 显示🤖 Generated with subagent