From 64bd006981d5c72b4eb8ff3144fcbaaecae393b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 16 Sep 2026 23:52:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=AA=A8=E6=9E=B6=E7=9A=84?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=88=87=E7=89=87=E6=8C=89=20limit=20?= =?UTF-8?q?=E4=B8=8E=E9=94=9A=E7=82=B9=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - harness.ts 的 read_direct_project_history_slice 按生产口径从队尾取一屏、锚点条目不进窗口 - 按本屏最老一条算 firstItemId,收满一屏后再有条目才算 hasMore - project-development.suite.ts 补一条用例:26 条历史只显示尾部 20 条,点「显示更早的对话」按锚点取回前 6 条后按钮消失 --- .../tests/appSurface/harness.ts | 29 ++++++- .../appSurface/project-development.suite.ts | 81 +++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/harness.ts b/apps/ai-game-creator-shell/tests/appSurface/harness.ts index 043c814d8..2eebd7640 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/harness.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/harness.ts @@ -1023,10 +1023,33 @@ function createProjectSupervisorRuntimeHarness({ return { events }; } if (command === 'read_direct_project_history_slice') { + // 生产口径:从文件尾反向取一屏可显示条目,锚点条目本身不进窗口;收满一屏之后 + // 再见一条才算 `hasMore`,`firstItemId` 是本屏最老一条的 itemId。 + const requestedLimit = Number(args?.limit ?? 20); + const limit = Math.min( + Math.max(Number.isFinite(requestedLimit) ? requestedLimit : 20, 1), + 200, + ); + const beforeItemId = + typeof args?.beforeItemId === 'string' && args.beforeItemId + ? args.beforeItemId + : null; + let end = directThreadHistoryItems.length; + if (beforeItemId) { + const anchorIndex = directThreadHistoryItems.findIndex( + (item) => String(item.itemId ?? '') === beforeItemId, + ); + end = + anchorIndex >= 0 ? anchorIndex : directThreadHistoryItems.length; + } + const start = Math.max(0, end - limit); + const window = directThreadHistoryItems.slice(start, end); + const oldest = window[0]; return { - items: [...directThreadHistoryItems], - hasMore: false, - firstItemId: null, + items: [...window], + hasMore: start > 0, + firstItemId: + oldest && typeof oldest.itemId === 'string' ? oldest.itemId : null, }; } if (command === 'start_game_creator_supervisor_runtime_task') { diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 70c2d799c..6aa05e4bc 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -8342,6 +8342,87 @@ export function registerProjectSupervisorSurfaceTests() { ).toHaveLength(2); }); + it('loads the earlier direct history page from the first-item anchor', async () => { + // 分页锚点走的是首屏切片给出的 `firstItemId`:切片本身从队尾取,老的一屏靠锚点继续向前。 + const projectPath = '/tmp/launcher-direct-history-pagination-game'; + const manifest = createGameCreationAppManifest( + 'local-project-draft', + 'launcher-direct-history-pagination-game', + ); + const supervisorHarness = createProjectSupervisorRuntimeHarness({ + projectPath, + }); + // 26 条 > 首屏 20 条:首屏只显示最后 20 条,前面 6 条要靠"显示更早"翻页取回。 + supervisorHarness.setDirectThreadHistory( + Array.from({ length: 26 }, (_, index) => { + const label = `历史对话 ${String(index + 1).padStart(2, '0')}`; + return { + itemType: 'message', + itemId: `direct-codex:turn-${label}:user`, + role: 'user', + text: label, + at: 1000 + index, + }; + }), + ); + const invoke = vi.fn( + async (command: string, args?: Record) => { + if (command === 'get_design_agent_runtime_mode') return null; + if (command === 'inspect_local_project_directory') { + return { + projectPath, + exists: true, + isDirectory: true, + isGameCreatorProject: true, + projectName: 'launcher-direct-history-pagination-game', + recentRunStatus: null, + recentRunStopReason: null, + }; + } + if (command === 'get_local_game_manifest') { + return manifest; + } + if (command === 'get_local_game_preview_status') { + return { status: 'stopped', url: null, port: null, root: null }; + } + return supervisorHarness.invoke(command, args); + }, + ); + window.__TAURI__ = { + core: { invoke }, + event: { listen: supervisorHarness.listen }, + }; + renderLauncherProjectsAt('/?launcher'); + + pickProjectFromLauncher(projectPath); + + const supervisorSurface = await screen.findByLabelText('陶泥儿项目对话'); + expect( + await within(supervisorSurface).findByText('历史对话 26'), + ).not.toBeNull(); + // 首屏是尾部的 20 条:第 7 条起可见,更早的 6 条还没读进来。 + expect(within(supervisorSurface).getByText('历史对话 07')).not.toBeNull(); + expect(within(supervisorSurface).queryByText('历史对话 06')).toBeNull(); + expect(invoke).toHaveBeenCalledWith('read_direct_project_history_slice', { + projectPath, + limit: 20, + }); + + fireEvent.click(screen.getByRole('button', { name: '显示更早的对话' })); + + // 翻页按首屏最老一条的锚点向前取;取到文件头后按钮消失。 + expect( + await within(supervisorSurface).findByText('历史对话 01'), + ).not.toBeNull(); + expect(within(supervisorSurface).getByText('历史对话 06')).not.toBeNull(); + expect(screen.queryByRole('button', { name: /显示更早/ })).toBeNull(); + expect(invoke).toHaveBeenCalledWith('read_direct_project_history_slice', { + projectPath, + beforeItemId: 'direct-codex:turn-历史对话 07:user', + limit: 20, + }); + }); + it('drains a notify that lands before the subscribe receipt so the running turn is not stranded', async () => { // 真实竞态:`subscribe` 在 Rust 侧已经注册好 subscriber 并开始通知,但前端还没拿到 // subscriptionId。此时的通知不能白丢——拿到回执后必须补一次 consume,否则整个回合会卡在队列里。