diff --git a/.project-docs/30-worklog/tasks/20260924-agent-rendering-a8c2d419.md b/.project-docs/30-worklog/tasks/20260924-agent-rendering-a8c2d419.md new file mode 100644 index 00000000..220273a8 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260924-agent-rendering-a8c2d419.md @@ -0,0 +1,42 @@ +# Task: Fix consultation response rendering + +## Identity + +- Task ID: 20260924-agent-rendering-a8c2d419 +- Mode: Feature +- Branch: codex/20260924-agent-rendering-a8c2d419-agent-rendering +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260924-agent-rendering-a8c2d419 +- Base commit: 0a8fce67a4ea6de291f82c09c17d3efe0ea7b92c +- Owner: codex +- Status: Ready for Integration + +## Scope + +- 修复用户截图中的咨询面板渲染:空旧草稿导致数字 0;定位序列化项目上下文进入智能体回复的问题。客户端仅修改证据确认的显示缺陷,云端事件生产问题在独立 Yuxi 任务处理。 + +## Intent And Constraints + +- maintain-project-docs 与 diagnosing-bugs 已加载。官方 check/start/status、项目记忆与 peer scope 检查完成,Concurrent/Planning Gates Passed;Feature 模式仅写本人记录和必要产品/测试文件。 +- 保留用户话题原文,不批量替换转义字符,不以 JSON 外形猜测并删除回复。线上事件探针被自动审批以 blocked by policy 拒绝,已停止该访问方式,改用本地数据和真实协议测试。用户随后批准创建 1 个全新只读 Reviewer,已委派两端补丁审查。 + +## Outcome + +- 已从本地历史确认异常回复含序列化上下文;Yuxi 对非 AI 消息的错误投影在关联任务修复,客户端不添加猜测式内容清洗。 +- 空旧草稿条件显式转换为 Boolean,避免 references.length 为 0 时作为 React 子节点显示。保留有内容的旧草稿入口,产品流程不变;README 无需改变。 + +## Verification + +- pnpm exec vitest run tests/unit/coding-teacher-ui.test.tsx -t 'does not render a zero' --maxWorkers=1:预期失败,复现截图数字 0。 +- pnpm exec vitest run tests/unit/coding-teacher-ui.test.tsx --maxWorkers=1:30 passed。 +- MAKELORE_LAYOUT_BROWSER_CHANNEL=msedge pnpm exec playwright test tests/e2e/teacher-discussion-layout.spec.ts --grep ordinary:319/508px 两个真实浏览器页面用例通过,使用现有隔离页面与模拟 API。 +- pnpm run typecheck、三处改动文件的 eslint、pnpm run build:vite、git diff --check:通过。构建仅有既有 chunk 大小及动态/静态混用提示。 +- 官方 check_doc_drift.py:Feature 文件所有权边界通过。未打包、安装、合并、推送或部署;未进行线上收费对话。 +- 用户授权的全新只读 Reviewer agent_rendering_review 独立审查 PASS,无 findings;复跑客户端 30/30、Yuxi 55/55 通过。已目视核对508px页面截图,无数字0,回复/草稿/快捷提问显示正常。 + +## Follow-ups + +- 部署 Yuxi 并更新客户端后才能影响用户当前安装,部署后验收一次真实提问及只读工具续接。历史异常原文保持不变。当前仅交付任务分支修复,不含合并或部署。 + +## Promotion Candidates + +- None recorded. diff --git a/src/pages/Chat/TeacherChatPanel.tsx b/src/pages/Chat/TeacherChatPanel.tsx index 94b36f70..70d8033e 100644 --- a/src/pages/Chat/TeacherChatPanel.tsx +++ b/src/pages/Chat/TeacherChatPanel.tsx @@ -443,7 +443,7 @@ export function TeacherChatPanel({ {topic?.unsaved &&
回复尚未保存,请复制或重试保存。
} {!topicEnabled && !error && !busy &&

{legacyFriend ? '这是旧版内置朋友的话题,仅供查看。请选择智能体新建话题。' : `${label}暂未开放,历史仍可查看。`}

}
{ e.preventDefault(); void send(); }}> - {(legacyDraft.text?.trim() || legacyDraft.references?.length) &&
+ {Boolean(legacyDraft.text?.trim() || legacyDraft.references?.length) &&
查看旧版朋友草稿
{legacyDraft.text} diff --git a/tests/e2e/teacher-discussion-layout.spec.ts b/tests/e2e/teacher-discussion-layout.spec.ts index 83c028e6..7edcbd71 100644 --- a/tests/e2e/teacher-discussion-layout.spec.ts +++ b/tests/e2e/teacher-discussion-layout.spec.ts @@ -24,9 +24,14 @@ for (const width of [319, 508]) for (const kind of ['ordinary', 'ideas', 'struct test(`${kind} pins the current tool and composer at ${width}px`, async ({ page }) => { await page.setViewportSize({ width: 1180, height: 800 }); const errors: string[] = []; page.on('pageerror', error => errors.push(error.message)); + await page.addInitScript(() => localStorage.setItem( + 'makelore-consultation-draft:fixture:fixture:friend', JSON.stringify({ text: '', references: [] }), + )); await page.goto(`${url}?kind=${kind}&width=${width}`); const input = page.getByRole('textbox', { name: '向智能体提问' }); await expect(input).toBeEnabled(); + await expect(page.locator('form')).not.toHaveText(/^0/); + await expect(page.getByText('查看旧版朋友草稿')).toHaveCount(0); const tool = page.getByTestId('teacher-discussion'); if (kind === 'ordinary') { await expect(tool).toHaveCount(0); diff --git a/tests/unit/coding-teacher-ui.test.tsx b/tests/unit/coding-teacher-ui.test.tsx index 3f2b7827..f4f3d658 100644 --- a/tests/unit/coding-teacher-ui.test.tsx +++ b/tests/unit/coding-teacher-ui.test.tsx @@ -96,6 +96,15 @@ async function ready() { await waitFor(() => expect(streams.has('first')).toBe(true)); } describe('teacher side chat', () => { + it('does not render a zero for an empty legacy friend draft', async () => { + localStorage.setItem('makelore-consultation-draft:signed-out:p:friend', JSON.stringify({ text: '', references: [] })); + render(); + await ready(); + const composer = screen.getByLabelText('向智能体提问').closest('form'); + expect(composer).not.toHaveTextContent(/^0/); + expect(screen.queryByText('查看旧版朋友草稿')).not.toBeInTheDocument(); + }); + it('retains failed input and retries with the same request id, then appends a reply only on click', async () => { const bringBack = vi.fn(); api.send