1- import { expect , publish , test , update } from "./fixtures.ts" ;
1+ import {
2+ expect ,
3+ expectNoHorizontalOverflow ,
4+ publish ,
5+ publishParts ,
6+ test ,
7+ update ,
8+ } from "./fixtures.ts" ;
29
310test ( "the sidebar groups sessions by recency and sinks empty ones to the bottom" , async ( {
411 page,
@@ -105,7 +112,7 @@ test("comment typed in the composer round-trips to the API", async ({ page, serv
105112 const snippet = await publish ( server . url , { html : "<p>v1</p>" , title : "Doc" , agent : "e2e" } ) ;
106113
107114 await page . goto ( server . url ) ;
108- const card = page . locator ( ".card" ) ;
115+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
109116 await card . locator ( ".act.comment" ) . click ( ) ;
110117 const input = card . locator ( ".composer input" ) ;
111118 await input . fill ( "ship it" ) ;
@@ -138,7 +145,7 @@ test("a comment's copy button puts an agent-ready paste block on the clipboard",
138145 }
139146
140147 await page . goto ( server . url ) ;
141- const card = page . locator ( ".card" ) ;
148+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
142149 await card . locator ( ".act.comment" ) . click ( ) ;
143150 const input = card . locator ( ".composer input" ) ;
144151 await input . fill ( "tighten the spacing" ) ;
@@ -165,7 +172,7 @@ test("a failed comment send restores the input instead of losing the message", a
165172 await publish ( server . url , { html : "<p>x</p>" , title : "Doc" , agent : "e2e" } ) ;
166173
167174 await page . goto ( server . url ) ;
168- const card = page . locator ( ".card" ) ;
175+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
169176 await page . route ( "**/api/comments" , ( route ) =>
170177 route . request ( ) . method ( ) === "POST" ? route . abort ( ) : route . fallback ( ) ,
171178 ) ;
@@ -192,7 +199,7 @@ test("a comment echoes immediately, before the SSE round-trip confirms it", asyn
192199 await publish ( server . url , { html : "<p>x</p>" , title : "Doc" , agent : "e2e" } ) ;
193200
194201 await page . goto ( server . url ) ;
195- const card = page . locator ( ".card" ) ;
202+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
196203 // hold the POST open so only the optimistic echo can render
197204 await page . route ( "**/api/comments" , async ( route ) => {
198205 if ( route . request ( ) . method ( ) !== "POST" ) return route . fallback ( ) ;
@@ -220,7 +227,7 @@ test("a comment containing raw HTML is sandboxed and escaped, never a live node"
220227 await publish ( server . url , { html : "<p>x</p>" , title : "Doc" , agent : "e2e" } ) ;
221228
222229 await page . goto ( server . url ) ;
223- const card = page . locator ( ".card" ) ;
230+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
224231 await card . locator ( ".act.comment" ) . click ( ) ;
225232 const input = card . locator ( ".composer input" ) ;
226233 await input . fill ( "<img src=x onerror=alert(1)> hi" ) ;
@@ -306,12 +313,34 @@ test("at phone width the sidebar collapses into a drawer and actions stay visibl
306313 server,
307314} ) => {
308315 await publish ( server . url , { html : "<p>m</p>" , title : "Mobile" , agent : "e2e" } ) ;
316+ const longSession = ( await (
317+ await fetch ( `${ server . url } /api/sessions` , {
318+ method : "POST" ,
319+ headers : { "content-type" : "application/json" } ,
320+ body : JSON . stringify ( {
321+ agent : "e2e" ,
322+ title : "A deliberately long mobile sidebar session title that should not shove controls" ,
323+ } ) ,
324+ } )
325+ ) . json ( ) ) as { id : string } ;
326+ await publish ( server . url , {
327+ html : "<p>long session row</p>" ,
328+ title : "Long title mobile" ,
329+ agent : "e2e" ,
330+ session : longSession . id ,
331+ } ) ;
309332
310- await page . setViewportSize ( { width : 375 , height : 667 } ) ;
311- await page . goto ( server . url ) ;
333+ await fetch ( `${ server . url } /api/sessions` , {
334+ method : "POST" ,
335+ headers : { "content-type" : "application/json" } ,
336+ body : JSON . stringify ( { agent : "idle" , title : "Empty session still shown in drawer" } ) ,
337+ } ) ;
338+
339+ await page . setViewportSize ( { width : 393 , height : 852 } ) ;
340+ await page . goto ( `${ server . url } /session/${ longSession . id } ` ) ;
312341
313342 // the sidebar is off-canvas and the stream gets the full width
314- const card = page . locator ( ".card" ) ;
343+ const card = page . locator ( ".card:not(#whatsNew) " ) ;
315344 await expect ( card ) . toBeVisible ( ) ;
316345 await expect ( page . locator ( "aside" ) ) . not . toBeInViewport ( ) ;
317346 expect ( ( await card . boundingBox ( ) ) ! . width ) . toBeGreaterThan ( 300 ) ;
@@ -322,10 +351,72 @@ test("at phone width the sidebar collapses into a drawer and actions stay visibl
322351 // the menu button opens the drawer; picking a session closes it again
323352 await page . locator ( "#menuBtn" ) . click ( ) ;
324353 await expect ( page . locator ( "aside" ) ) . toBeInViewport ( ) ;
325- await page . locator ( ".sess" ) . click ( ) ;
354+ await expectNoHorizontalOverflow ( page , "main" ) ;
355+ await expectNoHorizontalOverflow ( page , "aside" ) ;
356+ const longSessionTitle = page . getByText ( "A deliberately long mobile sidebar" , { exact : false } ) ;
357+ const longSessionRow = page . locator ( '[role="button"]' ) . filter ( { has : longSessionTitle } ) ;
358+ await expect ( longSessionRow ) . toBeVisible ( ) ;
359+ const deleteLongSession = longSessionRow . getByRole ( "button" , { name : / ^ D e l e t e s e s s i o n / } ) ;
360+ await expect ( deleteLongSession ) . toBeVisible ( ) ;
361+ await deleteLongSession . click ( { trial : true } ) ;
362+ await longSessionRow . click ( ) ;
326363 await expect ( page . locator ( "aside" ) ) . not . toBeInViewport ( ) ;
327364} ) ;
328365
366+ test ( "timeline traces wrap cleanly at iPhone 14 Pro width" , async ( { page, server } ) => {
367+ const surface = await publishParts ( server . url , {
368+ title : "Timeline anchor" ,
369+ agent : "e2e" ,
370+ parts : [
371+ { kind : "markdown" , markdown : "## Timeline card\n\nThe trace wraps around this card." } ,
372+ ] ,
373+ } ) ;
374+ await fetch ( `${ server . url } /api/sessions/${ surface . sessionId } /trace` , {
375+ method : "POST" ,
376+ headers : { "content-type" : "application/json" } ,
377+ body : JSON . stringify ( {
378+ steps : [
379+ {
380+ kind : "prompt" ,
381+ label : "prompt-" + "unbrokenprompttoken" . repeat ( 8 ) ,
382+ detail :
383+ "A longer prompt detail that should expand without creating horizontal document scroll." ,
384+ } ,
385+ {
386+ kind : "say" ,
387+ label : "response-" + "unbrokenresponsetoken" . repeat ( 8 ) ,
388+ } ,
389+ {
390+ kind : "shell" ,
391+ label :
392+ "npm run trace-check -- --device=iPhone14Pro --case=long-command-label-without-spaces" ,
393+ detail :
394+ "command output: " +
395+ "unbroken-token-for-overflow-regression-" . repeat ( 8 ) +
396+ "\nsecond line with normal words" ,
397+ } ,
398+ { kind : "say" , label : "The timeline remains readable on a phone." } ,
399+ ] ,
400+ reset : true ,
401+ } ) ,
402+ } ) ;
403+
404+ await page . setViewportSize ( { width : 393 , height : 852 } ) ;
405+ await page . goto ( `${ server . url } /session/${ surface . sessionId } ` ) ;
406+ await page . locator ( ".view-toggle button" , { hasText : "Timeline" } ) . click ( ) ;
407+
408+ await expect ( page . locator ( ".timeline" ) ) . toBeVisible ( ) ;
409+ await expect ( page . getByText ( "prompt-unbrokenprompttoken" , { exact : false } ) ) . toBeVisible ( ) ;
410+ await page . getByRole ( "button" , { name : "Show 1 work step" } ) . click ( ) ;
411+ await expect ( page . getByRole ( "button" , { name : "Hide 1 work step" } ) ) . toBeVisible ( ) ;
412+ await page . getByText ( "npm run trace-check" , { exact : false } ) . click ( ) ;
413+ await expect (
414+ page . getByText ( "unbroken-token-for-overflow-regression" , { exact : false } ) ,
415+ ) . toBeVisible ( ) ;
416+ await expectNoHorizontalOverflow ( page , "main" ) ;
417+ await expectNoHorizontalOverflow ( page , ".timeline" ) ;
418+ } ) ;
419+
329420test ( "the Connect Claude Code modal shows the plugin install commands" , async ( {
330421 page,
331422 server,
0 commit comments