@@ -36,6 +36,7 @@ describe('SessionState unified store', () => {
3636 expect ( state . sessionStates [ 's1' ] . executionClock ) . toEqual ( { startAt : null , endAt : null } ) ;
3737 expect ( state . sessionStates [ 's1' ] . traceSteps ) . toEqual ( [ ] ) ;
3838 expect ( state . sessionStates [ 's1' ] . contextWindow ) . toBe ( 0 ) ;
39+ expect ( state . sessionScrollPositions [ 's1' ] ) . toBeUndefined ( ) ;
3940 } ) ;
4041 } ) ;
4142
@@ -48,6 +49,7 @@ describe('SessionState unified store', () => {
4849 useAppStore . getState ( ) . removeSession ( 's1' ) ;
4950 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] ) . toBeUndefined ( ) ;
5051 expect ( useAppStore . getState ( ) . sessions ) . toHaveLength ( 0 ) ;
52+ expect ( useAppStore . getState ( ) . sessionScrollPositions [ 's1' ] ) . toBeUndefined ( ) ;
5153 } ) ;
5254
5355 it ( 'should clear activeSessionId when removing active session' , ( ) => {
@@ -59,6 +61,24 @@ describe('SessionState unified store', () => {
5961 } ) ;
6062 } ) ;
6163
64+ describe ( 'scroll positions' , ( ) => {
65+ it ( 'stores viewport positions independently for each session' , ( ) => {
66+ useAppStore . getState ( ) . setSessionScrollPosition ( 's1' , 120 ) ;
67+ useAppStore . getState ( ) . setSessionScrollPosition ( 's2' , 480 ) ;
68+
69+ expect ( useAppStore . getState ( ) . sessionScrollPositions ) . toEqual ( { s1 : 120 , s2 : 480 } ) ;
70+ } ) ;
71+
72+ it ( 'removes viewport positions with their sessions' , ( ) => {
73+ useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
74+ useAppStore . getState ( ) . setSessionScrollPosition ( 's1' , 120 ) ;
75+
76+ useAppStore . getState ( ) . removeSession ( 's1' ) ;
77+
78+ expect ( useAppStore . getState ( ) . sessionScrollPositions [ 's1' ] ) . toBeUndefined ( ) ;
79+ } ) ;
80+ } ) ;
81+
6282 describe ( 'removeSessions (batch)' , ( ) => {
6383 it ( 'should remove multiple sessions at once' , ( ) => {
6484 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
@@ -116,8 +136,20 @@ describe('SessionState unified store', () => {
116136 it ( 'should set messages (bulk replace)' , ( ) => {
117137 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
118138 const msgs = [
119- { id : 'a' , sessionId : 's1' , role : 'user' as const , content : [ { type : 'text' as const , text : 'hi' } ] , timestamp : 1 } ,
120- { id : 'b' , sessionId : 's1' , role : 'assistant' as const , content : [ { type : 'text' as const , text : 'hello' } ] , timestamp : 2 } ,
139+ {
140+ id : 'a' ,
141+ sessionId : 's1' ,
142+ role : 'user' as const ,
143+ content : [ { type : 'text' as const , text : 'hi' } ] ,
144+ timestamp : 1 ,
145+ } ,
146+ {
147+ id : 'b' ,
148+ sessionId : 's1' ,
149+ role : 'assistant' as const ,
150+ content : [ { type : 'text' as const , text : 'hello' } ] ,
151+ timestamp : 2 ,
152+ } ,
121153 ] ;
122154 useAppStore . getState ( ) . setMessages ( 's1' , msgs ) ;
123155 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] . messages ) . toHaveLength ( 2 ) ;
@@ -219,8 +251,11 @@ describe('SessionState unified store', () => {
219251 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
220252 // Setup an active turn first
221253 useAppStore . getState ( ) . addMessage ( 's1' , {
222- id : 'msg1' , sessionId : 's1' , role : 'user' ,
223- content : [ { type : 'text' , text : 'test' } ] , timestamp : Date . now ( ) ,
254+ id : 'msg1' ,
255+ sessionId : 's1' ,
256+ role : 'user' ,
257+ content : [ { type : 'text' , text : 'test' } ] ,
258+ timestamp : Date . now ( ) ,
224259 } ) ;
225260 useAppStore . getState ( ) . activateNextTurn ( 's1' , 'step1' ) ;
226261 useAppStore . getState ( ) . updateActiveTurnStep ( 's1' , 'step2' ) ;
@@ -233,8 +268,11 @@ describe('SessionState unified store', () => {
233268 it ( 'should clear active turn' , ( ) => {
234269 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
235270 useAppStore . getState ( ) . addMessage ( 's1' , {
236- id : 'msg1' , sessionId : 's1' , role : 'user' ,
237- content : [ { type : 'text' , text : 'test' } ] , timestamp : Date . now ( ) ,
271+ id : 'msg1' ,
272+ sessionId : 's1' ,
273+ role : 'user' ,
274+ content : [ { type : 'text' , text : 'test' } ] ,
275+ timestamp : Date . now ( ) ,
238276 } ) ;
239277 useAppStore . getState ( ) . activateNextTurn ( 's1' , 'step1' ) ;
240278 useAppStore . getState ( ) . clearActiveTurn ( 's1' ) ;
@@ -244,8 +282,11 @@ describe('SessionState unified store', () => {
244282 it ( 'should only clear active turn when stepId matches' , ( ) => {
245283 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
246284 useAppStore . getState ( ) . addMessage ( 's1' , {
247- id : 'msg1' , sessionId : 's1' , role : 'user' ,
248- content : [ { type : 'text' , text : 'test' } ] , timestamp : Date . now ( ) ,
285+ id : 'msg1' ,
286+ sessionId : 's1' ,
287+ role : 'user' ,
288+ content : [ { type : 'text' , text : 'test' } ] ,
289+ timestamp : Date . now ( ) ,
249290 } ) ;
250291 useAppStore . getState ( ) . activateNextTurn ( 's1' , 'step1' ) ;
251292 // Try clearing with wrong stepId - should not clear
@@ -259,8 +300,11 @@ describe('SessionState unified store', () => {
259300 it ( 'should clear pending turns' , ( ) => {
260301 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
261302 useAppStore . getState ( ) . addMessage ( 's1' , {
262- id : 'msg1' , sessionId : 's1' , role : 'user' ,
263- content : [ { type : 'text' , text : 'test' } ] , timestamp : Date . now ( ) ,
303+ id : 'msg1' ,
304+ sessionId : 's1' ,
305+ role : 'user' ,
306+ content : [ { type : 'text' , text : 'test' } ] ,
307+ timestamp : Date . now ( ) ,
264308 } ) ;
265309 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] . pendingTurns ) . toHaveLength ( 1 ) ;
266310 useAppStore . getState ( ) . clearPendingTurns ( 's1' ) ;
@@ -273,8 +317,21 @@ describe('SessionState unified store', () => {
273317 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
274318 // Manually set messages with queued status
275319 useAppStore . getState ( ) . setMessages ( 's1' , [
276- { id : 'msg1' , sessionId : 's1' , role : 'user' , content : [ { type : 'text' , text : 'a' } ] , timestamp : 1 , localStatus : 'queued' } ,
277- { id : 'msg2' , sessionId : 's1' , role : 'user' , content : [ { type : 'text' , text : 'b' } ] , timestamp : 2 } ,
320+ {
321+ id : 'msg1' ,
322+ sessionId : 's1' ,
323+ role : 'user' ,
324+ content : [ { type : 'text' , text : 'a' } ] ,
325+ timestamp : 1 ,
326+ localStatus : 'queued' ,
327+ } ,
328+ {
329+ id : 'msg2' ,
330+ sessionId : 's1' ,
331+ role : 'user' ,
332+ content : [ { type : 'text' , text : 'b' } ] ,
333+ timestamp : 2 ,
334+ } ,
278335 ] ) ;
279336 useAppStore . getState ( ) . clearQueuedMessages ( 's1' ) ;
280337 const msgs = useAppStore . getState ( ) . sessionStates [ 's1' ] . messages ;
@@ -285,7 +342,14 @@ describe('SessionState unified store', () => {
285342 it ( 'should cancel queued messages' , ( ) => {
286343 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
287344 useAppStore . getState ( ) . setMessages ( 's1' , [
288- { id : 'msg1' , sessionId : 's1' , role : 'user' , content : [ { type : 'text' , text : 'a' } ] , timestamp : 1 , localStatus : 'queued' } ,
345+ {
346+ id : 'msg1' ,
347+ sessionId : 's1' ,
348+ role : 'user' ,
349+ content : [ { type : 'text' , text : 'a' } ] ,
350+ timestamp : 1 ,
351+ localStatus : 'queued' ,
352+ } ,
289353 ] ) ;
290354 useAppStore . getState ( ) . cancelQueuedMessages ( 's1' ) ;
291355 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] . messages [ 0 ] . localStatus ) . toBe ( 'cancelled' ) ;
@@ -295,7 +359,14 @@ describe('SessionState unified store', () => {
295359 describe ( 'trace steps' , ( ) => {
296360 it ( 'should add and update trace steps' , ( ) => {
297361 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
298- const step = { id : 'ts1' , type : 'tool_call' as const , status : 'running' as const , title : 'read' , toolName : 'read' , timestamp : Date . now ( ) } ;
362+ const step = {
363+ id : 'ts1' ,
364+ type : 'tool_call' as const ,
365+ status : 'running' as const ,
366+ title : 'read' ,
367+ toolName : 'read' ,
368+ timestamp : Date . now ( ) ,
369+ } ;
299370 useAppStore . getState ( ) . addTraceStep ( 's1' , step ) ;
300371 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] . traceSteps ) . toHaveLength ( 1 ) ;
301372
@@ -306,8 +377,21 @@ describe('SessionState unified store', () => {
306377 it ( 'should set trace steps (bulk replace)' , ( ) => {
307378 useAppStore . getState ( ) . addSession ( makeSession ( 's1' ) ) ;
308379 const steps = [
309- { id : 'ts1' , type : 'tool_call' as const , status : 'completed' as const , title : 'read' , toolName : 'read' , timestamp : 1 } ,
310- { id : 'ts2' , type : 'thinking' as const , status : 'completed' as const , title : 'thinking' , timestamp : 2 } ,
380+ {
381+ id : 'ts1' ,
382+ type : 'tool_call' as const ,
383+ status : 'completed' as const ,
384+ title : 'read' ,
385+ toolName : 'read' ,
386+ timestamp : 1 ,
387+ } ,
388+ {
389+ id : 'ts2' ,
390+ type : 'thinking' as const ,
391+ status : 'completed' as const ,
392+ title : 'thinking' ,
393+ timestamp : 2 ,
394+ } ,
311395 ] ;
312396 useAppStore . getState ( ) . setTraceSteps ( 's1' , steps ) ;
313397 expect ( useAppStore . getState ( ) . sessionStates [ 's1' ] . traceSteps ) . toHaveLength ( 2 ) ;
0 commit comments