fix(web): mount the provider catalog for the geometry regression and assert the real model label
A bare /plan command never calls a model, so the scaffold's replay row did not mount and the model directory was empty: the trigger rendered the short fallback label, which fits beside the chip even on the pre-fix layout, silently defanging the regression. The scaffold gains a replayProvidersOnly option (provider catalog without a recorded script, consumption check skipped), the test mounts it, and asserts the trigger aria-label contains DeepSeek-V4-Flash before measuring — verified that removing the wrap fix makes the test fail (click areas disjoint: false).
This commit is contained in:
1 parent
aae246ef6d
commit
d5ec1189a6
6 files changed
+29
-12
No files matched your search
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-06-plan-narrow-viewport-regression.md
|
||||
2026-08-06-plan-narrow-viewport-regression.md: c4d7281d09b706c1270e9c43592d558825c63250
|
||||
2026-08-06-plan-narrow-viewport-regression.zh.md: aed430dc95793b8086a838ae534ffaed0256012e
|
||||
2026-08-06-plan-narrow-viewport-regression.md: 45cb969bc4c3d0856c78dae49159eb41be24dc91
|
||||
2026-08-06-plan-narrow-viewport-regression.zh.md: 8767e1b9f86f45942ba3ad5735e245ccfc994b9f
|
||||
@@ -20,7 +20,7 @@ The geometry golden records stable facts — viewport membership on both axes an
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Seed a cold session (composer-tab-geometry pattern).** Rejected: the exit path executes `/plan off` through `commands.execute`, which needs the live agent a cold seeded session does not have. The recorded turn keeps one, matching the product's user path.
|
||||
**Seed a cold session (composer-tab-geometry pattern).** Rejected: the exit path executes `/plan off` through `commands.execute`, which needs the live agent a cold seeded session does not have; `connectFreshWorkspace` keeps one, matching the product's user path.
|
||||
|
||||
**Pin absolute bounding boxes in the golden.** Rejected: chip and trigger widths depend on the installed fonts, so absolute coordinates would churn across platforms without a behavior change.
|
||||
|
||||
@@ -30,4 +30,4 @@ The geometry golden records stable facts — viewport membership on both axes an
|
||||
|
||||
## Consequences
|
||||
|
||||
Any future change to the control row layout — fonts, gaps, media or container queries — that re-introduces overlap or moves the chip out of the viewport on either axis fails this test. The test needs no API key: plan mode toggles through the command handler without a model round, and the golden is compared in replay/refresh modes.
|
||||
Any future change to the control row layout — fonts, gaps, media or container queries — that re-introduces overlap or moves the chip out of the viewport on either axis fails this test. The test needs no API key: plan mode toggles through the command handler without a model round, and a providers-only replay fixture (no recorded script, consumption check skipped) mounts the model directory so the trigger renders its real long label — the width that made the reported overlap measurable; the test asserts that label before measuring. The golden is compared in replay and record modes and rewritten in refresh mode.
|
||||
@@ -20,7 +20,7 @@ Status: implemented
|
||||
|
||||
## 备选方案
|
||||
|
||||
**冷会话 seed(composer-tab-geometry 模式)。** 否决:退出路径经 `commands.execute` 执行 `/plan off`,需要 live agent,而冷 seed 会话没有。录制的回合保留一个,与产品的用户路径一致。
|
||||
**冷会话 seed(composer-tab-geometry 模式)。** 否决:退出路径经 `commands.execute` 执行 `/plan off`,需要 live agent,而冷 seed 会话没有;`connectFreshWorkspace` 保留一个,与产品的用户路径一致。
|
||||
|
||||
**golden 固定绝对 bounding box。** 否决:chip 与 trigger 宽度依赖安装字体,绝对坐标会在平台间漂移而不反映行为变化。
|
||||
|
||||
@@ -30,4 +30,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
任何改变控制行布局的后续改动——字体、间距、媒体查询或容器查询——一旦重新引入重叠或把 chip 沿任一轴移出视口,本测试即失败。测试无需 API key:Plan 模式经命令 handler 切换,不经模型回合;golden 在 replay/refresh 模式下比较。
|
||||
任何改变控制行布局的后续改动——字体、间距、媒体查询或容器查询——一旦重新引入重叠或把 chip 沿任一轴移出视口,本测试即失败。测试无需 API key:Plan 模式经命令 handler 切换,不经模型回合;providers-only replay fixture(无录制脚本,跳过消费检查)挂载模型目录,使触发器渲染真实的长标签——正是使报告重叠可测量的宽度;测试在测量前断言该标签。golden 在 replay 与 record 模式下比较,在 refresh 模式下重写。
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/plan-narrow-viewport', import.meta.url))
|
||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||
const LAYOUT_EXPECTED = join(SNAPSHOT_DIR, 'layout.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
@@ -52,7 +53,11 @@ describe('web e2e: plan chip click area at the narrow viewport', () => {
|
||||
const sessionEvents: SessionEvent[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({})
|
||||
// The fixture carries the deterministic provider catalog (no model call
|
||||
// happens — the /plan command never steers a message), so the model
|
||||
// trigger renders its real long label, which is what made the reported
|
||||
// overlap measurable.
|
||||
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, replayProvidersOnly: true })
|
||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser, VIEWPORT.height)
|
||||
@@ -82,6 +87,9 @@ describe('web e2e: plan chip click area at the narrow viewport', () => {
|
||||
const trigger = page.getByRole('button', { name: /Select model/ })
|
||||
await chip.waitFor({ timeout: 30_000 })
|
||||
await trigger.waitFor({ timeout: 10_000 })
|
||||
// The regression depends on the real model label width: a bare fallback
|
||||
// trigger would fit beside the chip even on the pre-fix layout.
|
||||
expect(await trigger.getAttribute('aria-label')).toContain('DeepSeek-V4-Flash')
|
||||
const chipBox = await chip.boundingBox()
|
||||
const triggerBox = await trigger.boundingBox()
|
||||
expect(chipBox).not.toBeNull()
|
||||
@@ -132,6 +140,6 @@ describe('web e2e: plan chip click area at the narrow viewport', () => {
|
||||
}, 200_000)
|
||||
|
||||
it('keeps the snapshot inventory closed', async () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['layout.expected.md'])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl', 'layout.expected.md'])
|
||||
})
|
||||
})
|
||||
@@ -129,6 +129,13 @@ export interface LaunchOptions {
|
||||
* mounts).
|
||||
*/
|
||||
replayFixture?: string
|
||||
/**
|
||||
* Mount the replay provider catalog (the model directory the UI shows)
|
||||
* without any recorded script to consume: for scenarios that never call a
|
||||
* model but need the real provider/model labels rendered. The teardown
|
||||
* consumption check is skipped for this mode.
|
||||
*/
|
||||
replayProvidersOnly?: boolean
|
||||
/**
|
||||
* Recorded child logs assigned in child creation order. Each child owns its
|
||||
* own positional replay cursor across initial and continuation turns.
|
||||
@@ -402,10 +409,12 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
// Fixture-consumption check first, while the run's binding state is
|
||||
// still authoritative — a scenario that drove fewer model calls than
|
||||
// recorded fails here instead of drifting green.
|
||||
try {
|
||||
replayHandle?.assertConsumed()
|
||||
} catch (error) {
|
||||
failures.push(error)
|
||||
if (!options.replayProvidersOnly) {
|
||||
try {
|
||||
replayHandle?.assertConsumed()
|
||||
} catch (error) {
|
||||
failures.push(error)
|
||||
}
|
||||
}
|
||||
try {
|
||||
failures.push(...await cleanupScaffoldWorld(ctx, workspaceCwd, persistenceRoot))
|
||||
|
||||
Whitespace-only changes.
Reference in new issue
Block a user