detached 5446b2d

feat(domain): add session state entities and repository interfaces

commit
5446b2d4c96ddadbab45ab33dade4ee240988734
author
bnema · · 2025-12-24 21:27 UTC
committer
brice · · 2025-12-25 05:18 UTC
parent
351fdaf
signature
unsigned
6 files · +679 -0
M .mockery.yaml +3 -0
@@ -9,6 +9,8 @@ packages:
99 pkgname: mocks
1010 interfaces:
1111 SessionLogger: {}
12+ SessionSpawner: {}
13+ TabListProvider: {}
1214 WebView: {}
1315 WebViewFactory: {}
1416 WebViewPool: {}
@@ -23,6 +25,7 @@ packages:
2325 FolderRepository: {}
2426 HistoryRepository: {}
2527 SessionRepository: {}
28+ SessionStateRepository: {}
2629 TagRepository: {}
2730 ZoomRepository: {}
2831 github.com/bnema/dumber/internal/infrastructure/filtering:
@@ -9,6 +9,8 @@ packages:
9 pkgname: mocks9 pkgname: mocks
10 interfaces:10 interfaces:
11 SessionLogger: {}11 SessionLogger: {}
12+ SessionSpawner: {}
13+ TabListProvider: {}
12 WebView: {}14 WebView: {}
13 WebViewFactory: {}15 WebViewFactory: {}
14 WebViewPool: {}16 WebViewPool: {}
@@ -23,6 +25,7 @@ packages:
23 FolderRepository: {}25 FolderRepository: {}
24 HistoryRepository: {}26 HistoryRepository: {}
25 SessionRepository: {}27 SessionRepository: {}
28+ SessionStateRepository: {}
26 TagRepository: {}29 TagRepository: {}
27 ZoomRepository: {}30 ZoomRepository: {}
28 github.com/bnema/dumber/internal/infrastructure/filtering:31 github.com/bnema/dumber/internal/infrastructure/filtering:
M internal/application/port/mocks/mocks.go +195 -0
@@ -120,6 +120,201 @@ func (_c *MockSessionLogger_CreateLogger_Call) RunAndReturn(run func(ctx context
120120 return _c
121121 }
122122
123+// NewMockTabListProvider creates a new instance of MockTabListProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
124+// The first argument is typically a *testing.T value.
125+func NewMockTabListProvider(t interface {
126+ mock.TestingT
127+ Cleanup(func())
128+}) *MockTabListProvider {
129+ mock := &MockTabListProvider{}
130+ mock.Mock.Test(t)
131+
132+ t.Cleanup(func() { mock.AssertExpectations(t) })
133+
134+ return mock
135+}
136+
137+// MockTabListProvider is an autogenerated mock type for the TabListProvider type
138+type MockTabListProvider struct {
139+ mock.Mock
140+}
141+
142+type MockTabListProvider_Expecter struct {
143+ mock *mock.Mock
144+}
145+
146+func (_m *MockTabListProvider) EXPECT() *MockTabListProvider_Expecter {
147+ return &MockTabListProvider_Expecter{mock: &_m.Mock}
148+}
149+
150+// GetSessionID provides a mock function for the type MockTabListProvider
151+func (_mock *MockTabListProvider) GetSessionID() entity.SessionID {
152+ ret := _mock.Called()
153+
154+ if len(ret) == 0 {
155+ panic("no return value specified for GetSessionID")
156+ }
157+
158+ var r0 entity.SessionID
159+ if returnFunc, ok := ret.Get(0).(func() entity.SessionID); ok {
160+ r0 = returnFunc()
161+ } else {
162+ r0 = ret.Get(0).(entity.SessionID)
163+ }
164+ return r0
165+}
166+
167+// MockTabListProvider_GetSessionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSessionID'
168+type MockTabListProvider_GetSessionID_Call struct {
169+ *mock.Call
170+}
171+
172+// GetSessionID is a helper method to define mock.On call
173+func (_e *MockTabListProvider_Expecter) GetSessionID() *MockTabListProvider_GetSessionID_Call {
174+ return &MockTabListProvider_GetSessionID_Call{Call: _e.mock.On("GetSessionID")}
175+}
176+
177+func (_c *MockTabListProvider_GetSessionID_Call) Run(run func()) *MockTabListProvider_GetSessionID_Call {
178+ _c.Call.Run(func(args mock.Arguments) {
179+ run()
180+ })
181+ return _c
182+}
183+
184+func (_c *MockTabListProvider_GetSessionID_Call) Return(sessionID entity.SessionID) *MockTabListProvider_GetSessionID_Call {
185+ _c.Call.Return(sessionID)
186+ return _c
187+}
188+
189+func (_c *MockTabListProvider_GetSessionID_Call) RunAndReturn(run func() entity.SessionID) *MockTabListProvider_GetSessionID_Call {
190+ _c.Call.Return(run)
191+ return _c
192+}
193+
194+// GetTabList provides a mock function for the type MockTabListProvider
195+func (_mock *MockTabListProvider) GetTabList() *entity.TabList {
196+ ret := _mock.Called()
197+
198+ if len(ret) == 0 {
199+ panic("no return value specified for GetTabList")
200+ }
201+
202+ var r0 *entity.TabList
203+ if returnFunc, ok := ret.Get(0).(func() *entity.TabList); ok {
204+ r0 = returnFunc()
205+ } else {
206+ if ret.Get(0) != nil {
207+ r0 = ret.Get(0).(*entity.TabList)
208+ }
209+ }
210+ return r0
211+}
212+
213+// MockTabListProvider_GetTabList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTabList'
214+type MockTabListProvider_GetTabList_Call struct {
215+ *mock.Call
216+}
217+
218+// GetTabList is a helper method to define mock.On call
219+func (_e *MockTabListProvider_Expecter) GetTabList() *MockTabListProvider_GetTabList_Call {
220+ return &MockTabListProvider_GetTabList_Call{Call: _e.mock.On("GetTabList")}
221+}
222+
223+func (_c *MockTabListProvider_GetTabList_Call) Run(run func()) *MockTabListProvider_GetTabList_Call {
224+ _c.Call.Run(func(args mock.Arguments) {
225+ run()
226+ })
227+ return _c
228+}
229+
230+func (_c *MockTabListProvider_GetTabList_Call) Return(tabList *entity.TabList) *MockTabListProvider_GetTabList_Call {
231+ _c.Call.Return(tabList)
232+ return _c
233+}
234+
235+func (_c *MockTabListProvider_GetTabList_Call) RunAndReturn(run func() *entity.TabList) *MockTabListProvider_GetTabList_Call {
236+ _c.Call.Return(run)
237+ return _c
238+}
239+
240+// NewMockSessionSpawner creates a new instance of MockSessionSpawner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
241+// The first argument is typically a *testing.T value.
242+func NewMockSessionSpawner(t interface {
243+ mock.TestingT
244+ Cleanup(func())
245+}) *MockSessionSpawner {
246+ mock := &MockSessionSpawner{}
247+ mock.Mock.Test(t)
248+
249+ t.Cleanup(func() { mock.AssertExpectations(t) })
250+
251+ return mock
252+}
253+
254+// MockSessionSpawner is an autogenerated mock type for the SessionSpawner type
255+type MockSessionSpawner struct {
256+ mock.Mock
257+}
258+
259+type MockSessionSpawner_Expecter struct {
260+ mock *mock.Mock
261+}
262+
263+func (_m *MockSessionSpawner) EXPECT() *MockSessionSpawner_Expecter {
264+ return &MockSessionSpawner_Expecter{mock: &_m.Mock}
265+}
266+
267+// SpawnWithSession provides a mock function for the type MockSessionSpawner
268+func (_mock *MockSessionSpawner) SpawnWithSession(sessionID entity.SessionID) error {
269+ ret := _mock.Called(sessionID)
270+
271+ if len(ret) == 0 {
272+ panic("no return value specified for SpawnWithSession")
273+ }
274+
275+ var r0 error
276+ if returnFunc, ok := ret.Get(0).(func(entity.SessionID) error); ok {
277+ r0 = returnFunc(sessionID)
278+ } else {
279+ r0 = ret.Error(0)
280+ }
281+ return r0
282+}
283+
284+// MockSessionSpawner_SpawnWithSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SpawnWithSession'
285+type MockSessionSpawner_SpawnWithSession_Call struct {
286+ *mock.Call
287+}
288+
289+// SpawnWithSession is a helper method to define mock.On call
290+// - sessionID entity.SessionID
291+func (_e *MockSessionSpawner_Expecter) SpawnWithSession(sessionID interface{}) *MockSessionSpawner_SpawnWithSession_Call {
292+ return &MockSessionSpawner_SpawnWithSession_Call{Call: _e.mock.On("SpawnWithSession", sessionID)}
293+}
294+
295+func (_c *MockSessionSpawner_SpawnWithSession_Call) Run(run func(sessionID entity.SessionID)) *MockSessionSpawner_SpawnWithSession_Call {
296+ _c.Call.Run(func(args mock.Arguments) {
297+ var arg0 entity.SessionID
298+ if args[0] != nil {
299+ arg0 = args[0].(entity.SessionID)
300+ }
301+ run(
302+ arg0,
303+ )
304+ })
305+ return _c
306+}
307+
308+func (_c *MockSessionSpawner_SpawnWithSession_Call) Return(err error) *MockSessionSpawner_SpawnWithSession_Call {
309+ _c.Call.Return(err)
310+ return _c
311+}
312+
313+func (_c *MockSessionSpawner_SpawnWithSession_Call) RunAndReturn(run func(sessionID entity.SessionID) error) *MockSessionSpawner_SpawnWithSession_Call {
314+ _c.Call.Return(run)
315+ return _c
316+}
317+
123318 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
124319 // The first argument is typically a *testing.T value.
125320 func NewMockWebView(t interface {
@@ -120,6 +120,201 @@ func (_c *MockSessionLogger_CreateLogger_Call) RunAndReturn(run func(ctx context
120 return _c120 return _c
121 }121 }
122 122
123+// NewMockTabListProvider creates a new instance of MockTabListProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
124+// The first argument is typically a *testing.T value.
125+func NewMockTabListProvider(t interface {
126+ mock.TestingT
127+ Cleanup(func())
128+}) *MockTabListProvider {
129+ mock := &MockTabListProvider{}
130+ mock.Mock.Test(t)
131+
132+ t.Cleanup(func() { mock.AssertExpectations(t) })
133+
134+ return mock
135+}
136+
137+// MockTabListProvider is an autogenerated mock type for the TabListProvider type
138+type MockTabListProvider struct {
139+ mock.Mock
140+}
141+
142+type MockTabListProvider_Expecter struct {
143+ mock *mock.Mock
144+}
145+
146+func (_m *MockTabListProvider) EXPECT() *MockTabListProvider_Expecter {
147+ return &MockTabListProvider_Expecter{mock: &_m.Mock}
148+}
149+
150+// GetSessionID provides a mock function for the type MockTabListProvider
151+func (_mock *MockTabListProvider) GetSessionID() entity.SessionID {
152+ ret := _mock.Called()
153+
154+ if len(ret) == 0 {
155+ panic("no return value specified for GetSessionID")
156+ }
157+
158+ var r0 entity.SessionID
159+ if returnFunc, ok := ret.Get(0).(func() entity.SessionID); ok {
160+ r0 = returnFunc()
161+ } else {
162+ r0 = ret.Get(0).(entity.SessionID)
163+ }
164+ return r0
165+}
166+
167+// MockTabListProvider_GetSessionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSessionID'
168+type MockTabListProvider_GetSessionID_Call struct {
169+ *mock.Call
170+}
171+
172+// GetSessionID is a helper method to define mock.On call
173+func (_e *MockTabListProvider_Expecter) GetSessionID() *MockTabListProvider_GetSessionID_Call {
174+ return &MockTabListProvider_GetSessionID_Call{Call: _e.mock.On("GetSessionID")}
175+}
176+
177+func (_c *MockTabListProvider_GetSessionID_Call) Run(run func()) *MockTabListProvider_GetSessionID_Call {
178+ _c.Call.Run(func(args mock.Arguments) {
179+ run()
180+ })
181+ return _c
182+}
183+
184+func (_c *MockTabListProvider_GetSessionID_Call) Return(sessionID entity.SessionID) *MockTabListProvider_GetSessionID_Call {
185+ _c.Call.Return(sessionID)
186+ return _c
187+}
188+
189+func (_c *MockTabListProvider_GetSessionID_Call) RunAndReturn(run func() entity.SessionID) *MockTabListProvider_GetSessionID_Call {
190+ _c.Call.Return(run)
191+ return _c
192+}
193+
194+// GetTabList provides a mock function for the type MockTabListProvider
195+func (_mock *MockTabListProvider) GetTabList() *entity.TabList {
196+ ret := _mock.Called()
197+
198+ if len(ret) == 0 {
199+ panic("no return value specified for GetTabList")
200+ }
201+
202+ var r0 *entity.TabList
203+ if returnFunc, ok := ret.Get(0).(func() *entity.TabList); ok {
204+ r0 = returnFunc()
205+ } else {
206+ if ret.Get(0) != nil {
207+ r0 = ret.Get(0).(*entity.TabList)
208+ }
209+ }
210+ return r0
211+}
212+
213+// MockTabListProvider_GetTabList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTabList'
214+type MockTabListProvider_GetTabList_Call struct {
215+ *mock.Call
216+}
217+
218+// GetTabList is a helper method to define mock.On call
219+func (_e *MockTabListProvider_Expecter) GetTabList() *MockTabListProvider_GetTabList_Call {
220+ return &MockTabListProvider_GetTabList_Call{Call: _e.mock.On("GetTabList")}
221+}
222+
223+func (_c *MockTabListProvider_GetTabList_Call) Run(run func()) *MockTabListProvider_GetTabList_Call {
224+ _c.Call.Run(func(args mock.Arguments) {
225+ run()
226+ })
227+ return _c
228+}
229+
230+func (_c *MockTabListProvider_GetTabList_Call) Return(tabList *entity.TabList) *MockTabListProvider_GetTabList_Call {
231+ _c.Call.Return(tabList)
232+ return _c
233+}
234+
235+func (_c *MockTabListProvider_GetTabList_Call) RunAndReturn(run func() *entity.TabList) *MockTabListProvider_GetTabList_Call {
236+ _c.Call.Return(run)
237+ return _c
238+}
239+
240+// NewMockSessionSpawner creates a new instance of MockSessionSpawner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
241+// The first argument is typically a *testing.T value.
242+func NewMockSessionSpawner(t interface {
243+ mock.TestingT
244+ Cleanup(func())
245+}) *MockSessionSpawner {
246+ mock := &MockSessionSpawner{}
247+ mock.Mock.Test(t)
248+
249+ t.Cleanup(func() { mock.AssertExpectations(t) })
250+
251+ return mock
252+}
253+
254+// MockSessionSpawner is an autogenerated mock type for the SessionSpawner type
255+type MockSessionSpawner struct {
256+ mock.Mock
257+}
258+
259+type MockSessionSpawner_Expecter struct {
260+ mock *mock.Mock
261+}
262+
263+func (_m *MockSessionSpawner) EXPECT() *MockSessionSpawner_Expecter {
264+ return &MockSessionSpawner_Expecter{mock: &_m.Mock}
265+}
266+
267+// SpawnWithSession provides a mock function for the type MockSessionSpawner
268+func (_mock *MockSessionSpawner) SpawnWithSession(sessionID entity.SessionID) error {
269+ ret := _mock.Called(sessionID)
270+
271+ if len(ret) == 0 {
272+ panic("no return value specified for SpawnWithSession")
273+ }
274+
275+ var r0 error
276+ if returnFunc, ok := ret.Get(0).(func(entity.SessionID) error); ok {
277+ r0 = returnFunc(sessionID)
278+ } else {
279+ r0 = ret.Error(0)
280+ }
281+ return r0
282+}
283+
284+// MockSessionSpawner_SpawnWithSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SpawnWithSession'
285+type MockSessionSpawner_SpawnWithSession_Call struct {
286+ *mock.Call
287+}
288+
289+// SpawnWithSession is a helper method to define mock.On call
290+// - sessionID entity.SessionID
291+func (_e *MockSessionSpawner_Expecter) SpawnWithSession(sessionID interface{}) *MockSessionSpawner_SpawnWithSession_Call {
292+ return &MockSessionSpawner_SpawnWithSession_Call{Call: _e.mock.On("SpawnWithSession", sessionID)}
293+}
294+
295+func (_c *MockSessionSpawner_SpawnWithSession_Call) Run(run func(sessionID entity.SessionID)) *MockSessionSpawner_SpawnWithSession_Call {
296+ _c.Call.Run(func(args mock.Arguments) {
297+ var arg0 entity.SessionID
298+ if args[0] != nil {
299+ arg0 = args[0].(entity.SessionID)
300+ }
301+ run(
302+ arg0,
303+ )
304+ })
305+ return _c
306+}
307+
308+func (_c *MockSessionSpawner_SpawnWithSession_Call) Return(err error) *MockSessionSpawner_SpawnWithSession_Call {
309+ _c.Call.Return(err)
310+ return _c
311+}
312+
313+func (_c *MockSessionSpawner_SpawnWithSession_Call) RunAndReturn(run func(sessionID entity.SessionID) error) *MockSessionSpawner_SpawnWithSession_Call {
314+ _c.Call.Return(run)
315+ return _c
316+}
317+
123 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.318 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
124 // The first argument is typically a *testing.T value.319 // The first argument is typically a *testing.T value.
125 func NewMockWebView(t interface {320 func NewMockWebView(t interface {
A internal/application/port/session_state.go +18 -0
@@ -0,0 +1,18 @@
1+package port
2+
3+import "github.com/bnema/dumber/internal/domain/entity"
4+
5+// TabListProvider provides access to the current tab list state.
6+// Implemented by the UI layer to allow the snapshot service to read state.
7+type TabListProvider interface {
8+ // GetTabList returns the current tab list state.
9+ GetTabList() *entity.TabList
10+ // GetSessionID returns the current session ID.
11+ GetSessionID() entity.SessionID
12+}
13+
14+// SessionSpawner spawns a new dumber instance for resurrection.
15+type SessionSpawner interface {
16+ // SpawnWithSession starts a new dumber instance to restore a session.
17+ SpawnWithSession(sessionID entity.SessionID) error
18+}
@@ -0,0 +1,18 @@
1+package port
2+
3+import "github.com/bnema/dumber/internal/domain/entity"
4+
5+// TabListProvider provides access to the current tab list state.
6+// Implemented by the UI layer to allow the snapshot service to read state.
7+type TabListProvider interface {
8+ // GetTabList returns the current tab list state.
9+ GetTabList() *entity.TabList
10+ // GetSessionID returns the current session ID.
11+ GetSessionID() entity.SessionID
12+}
13+
14+// SessionSpawner spawns a new dumber instance for resurrection.
15+type SessionSpawner interface {
16+ // SpawnWithSession starts a new dumber instance to restore a session.
17+ SpawnWithSession(sessionID entity.SessionID) error
18+}
A internal/domain/entity/session_state.go +169 -0
@@ -0,0 +1,169 @@
1+package entity
2+
3+import "time"
4+
5+// SessionStateVersion is the current schema version for session state.
6+// Increment when making breaking changes to the serialization format.
7+const SessionStateVersion = 1
8+
9+// SessionState represents a complete snapshot of a browser session.
10+// This is serialized to JSON and stored in the database.
11+type SessionState struct {
12+ Version int `json:"version"`
13+ SessionID SessionID `json:"session_id"`
14+ Tabs []TabSnapshot `json:"tabs"`
15+ ActiveTabIndex int `json:"active_tab_index"`
16+ SavedAt time.Time `json:"saved_at"`
17+}
18+
19+// TabSnapshot captures the state of a single tab.
20+type TabSnapshot struct {
21+ ID TabID `json:"id"`
22+ Name string `json:"name"`
23+ Position int `json:"position"`
24+ IsPinned bool `json:"is_pinned"`
25+ Workspace WorkspaceSnapshot `json:"workspace"`
26+}
27+
28+// WorkspaceSnapshot captures the pane tree layout.
29+type WorkspaceSnapshot struct {
30+ ID WorkspaceID `json:"id"`
31+ Root *PaneNodeSnapshot `json:"root"`
32+ ActivePaneID PaneID `json:"active_pane_id"`
33+}
34+
35+// PaneNodeSnapshot captures a node in the pane tree.
36+type PaneNodeSnapshot struct {
37+ ID string `json:"id"`
38+ Pane *PaneSnapshot `json:"pane,omitempty"` // Non-nil for leaf nodes
39+ Children []*PaneNodeSnapshot `json:"children,omitempty"` // Non-nil for containers
40+ SplitDir SplitDirection `json:"split_dir"`
41+ SplitRatio float64 `json:"split_ratio"`
42+ IsStacked bool `json:"is_stacked"`
43+ ActiveStackIndex int `json:"active_stack_index"`
44+}
45+
46+// PaneSnapshot captures the essential state of a pane.
47+type PaneSnapshot struct {
48+ ID PaneID `json:"id"`
49+ URI string `json:"uri"`
50+ Title string `json:"title"`
51+ ZoomFactor float64 `json:"zoom_factor"`
52+}
53+
54+// SnapshotFromTabList creates a SessionState from a live TabList.
55+func SnapshotFromTabList(sessionID SessionID, tabs *TabList) *SessionState {
56+ if tabs == nil {
57+ return &SessionState{
58+ Version: SessionStateVersion,
59+ SessionID: sessionID,
60+ Tabs: []TabSnapshot{},
61+ SavedAt: time.Now(),
62+ }
63+ }
64+
65+ snapTabs := make([]TabSnapshot, 0, len(tabs.Tabs))
66+ activeTabIndex := 0
67+
68+ for i, tab := range tabs.Tabs {
69+ if tab.ID == tabs.ActiveTabID {
70+ activeTabIndex = i
71+ }
72+ snapTabs = append(snapTabs, snapshotTab(tab))
73+ }
74+
75+ return &SessionState{
76+ Version: SessionStateVersion,
77+ SessionID: sessionID,
78+ Tabs: snapTabs,
79+ ActiveTabIndex: activeTabIndex,
80+ SavedAt: time.Now(),
81+ }
82+}
83+
84+func snapshotTab(tab *Tab) TabSnapshot {
85+ return TabSnapshot{
86+ ID: tab.ID,
87+ Name: tab.Name,
88+ Position: tab.Position,
89+ IsPinned: tab.IsPinned,
90+ Workspace: snapshotWorkspace(tab.Workspace),
91+ }
92+}
93+
94+func snapshotWorkspace(ws *Workspace) WorkspaceSnapshot {
95+ if ws == nil {
96+ return WorkspaceSnapshot{}
97+ }
98+ return WorkspaceSnapshot{
99+ ID: ws.ID,
100+ Root: snapshotPaneNode(ws.Root),
101+ ActivePaneID: ws.ActivePaneID,
102+ }
103+}
104+
105+func snapshotPaneNode(node *PaneNode) *PaneNodeSnapshot {
106+ if node == nil {
107+ return nil
108+ }
109+
110+ snapshot := &PaneNodeSnapshot{
111+ ID: node.ID,
112+ SplitDir: node.SplitDir,
113+ SplitRatio: node.SplitRatio,
114+ IsStacked: node.IsStacked,
115+ ActiveStackIndex: node.ActiveStackIndex,
116+ }
117+
118+ if node.Pane != nil {
119+ snapshot.Pane = &PaneSnapshot{
120+ ID: node.Pane.ID,
121+ URI: node.Pane.URI,
122+ Title: node.Pane.Title,
123+ ZoomFactor: node.Pane.ZoomFactor,
124+ }
125+ }
126+
127+ if len(node.Children) > 0 {
128+ snapshot.Children = make([]*PaneNodeSnapshot, 0, len(node.Children))
129+ for _, child := range node.Children {
130+ snapshot.Children = append(snapshot.Children, snapshotPaneNode(child))
131+ }
132+ }
133+
134+ return snapshot
135+}
136+
137+// SessionInfo provides summary information for the session manager UI.
138+type SessionInfo struct {
139+ Session *Session
140+ State *SessionState
141+ TabCount int
142+ PaneCount int
143+ IsActive bool // Has active lock file
144+ IsCurrent bool // Is the current session
145+ UpdatedAt time.Time // When the state was last saved
146+}
147+
148+// CountPanes returns the total number of panes in the session state.
149+func (s *SessionState) CountPanes() int {
150+ count := 0
151+ for _, tab := range s.Tabs {
152+ count += countPanesInNode(tab.Workspace.Root)
153+ }
154+ return count
155+}
156+
157+func countPanesInNode(node *PaneNodeSnapshot) int {
158+ if node == nil {
159+ return 0
160+ }
161+ if node.Pane != nil {
162+ return 1
163+ }
164+ count := 0
165+ for _, child := range node.Children {
166+ count += countPanesInNode(child)
167+ }
168+ return count
169+}
@@ -0,0 +1,169 @@
1+package entity
2+
3+import "time"
4+
5+// SessionStateVersion is the current schema version for session state.
6+// Increment when making breaking changes to the serialization format.
7+const SessionStateVersion = 1
8+
9+// SessionState represents a complete snapshot of a browser session.
10+// This is serialized to JSON and stored in the database.
11+type SessionState struct {
12+ Version int `json:"version"`
13+ SessionID SessionID `json:"session_id"`
14+ Tabs []TabSnapshot `json:"tabs"`
15+ ActiveTabIndex int `json:"active_tab_index"`
16+ SavedAt time.Time `json:"saved_at"`
17+}
18+
19+// TabSnapshot captures the state of a single tab.
20+type TabSnapshot struct {
21+ ID TabID `json:"id"`
22+ Name string `json:"name"`
23+ Position int `json:"position"`
24+ IsPinned bool `json:"is_pinned"`
25+ Workspace WorkspaceSnapshot `json:"workspace"`
26+}
27+
28+// WorkspaceSnapshot captures the pane tree layout.
29+type WorkspaceSnapshot struct {
30+ ID WorkspaceID `json:"id"`
31+ Root *PaneNodeSnapshot `json:"root"`
32+ ActivePaneID PaneID `json:"active_pane_id"`
33+}
34+
35+// PaneNodeSnapshot captures a node in the pane tree.
36+type PaneNodeSnapshot struct {
37+ ID string `json:"id"`
38+ Pane *PaneSnapshot `json:"pane,omitempty"` // Non-nil for leaf nodes
39+ Children []*PaneNodeSnapshot `json:"children,omitempty"` // Non-nil for containers
40+ SplitDir SplitDirection `json:"split_dir"`
41+ SplitRatio float64 `json:"split_ratio"`
42+ IsStacked bool `json:"is_stacked"`
43+ ActiveStackIndex int `json:"active_stack_index"`
44+}
45+
46+// PaneSnapshot captures the essential state of a pane.
47+type PaneSnapshot struct {
48+ ID PaneID `json:"id"`
49+ URI string `json:"uri"`
50+ Title string `json:"title"`
51+ ZoomFactor float64 `json:"zoom_factor"`
52+}
53+
54+// SnapshotFromTabList creates a SessionState from a live TabList.
55+func SnapshotFromTabList(sessionID SessionID, tabs *TabList) *SessionState {
56+ if tabs == nil {
57+ return &SessionState{
58+ Version: SessionStateVersion,
59+ SessionID: sessionID,
60+ Tabs: []TabSnapshot{},
61+ SavedAt: time.Now(),
62+ }
63+ }
64+
65+ snapTabs := make([]TabSnapshot, 0, len(tabs.Tabs))
66+ activeTabIndex := 0
67+
68+ for i, tab := range tabs.Tabs {
69+ if tab.ID == tabs.ActiveTabID {
70+ activeTabIndex = i
71+ }
72+ snapTabs = append(snapTabs, snapshotTab(tab))
73+ }
74+
75+ return &SessionState{
76+ Version: SessionStateVersion,
77+ SessionID: sessionID,
78+ Tabs: snapTabs,
79+ ActiveTabIndex: activeTabIndex,
80+ SavedAt: time.Now(),
81+ }
82+}
83+
84+func snapshotTab(tab *Tab) TabSnapshot {
85+ return TabSnapshot{
86+ ID: tab.ID,
87+ Name: tab.Name,
88+ Position: tab.Position,
89+ IsPinned: tab.IsPinned,
90+ Workspace: snapshotWorkspace(tab.Workspace),
91+ }
92+}
93+
94+func snapshotWorkspace(ws *Workspace) WorkspaceSnapshot {
95+ if ws == nil {
96+ return WorkspaceSnapshot{}
97+ }
98+ return WorkspaceSnapshot{
99+ ID: ws.ID,
100+ Root: snapshotPaneNode(ws.Root),
101+ ActivePaneID: ws.ActivePaneID,
102+ }
103+}
104+
105+func snapshotPaneNode(node *PaneNode) *PaneNodeSnapshot {
106+ if node == nil {
107+ return nil
108+ }
109+
110+ snapshot := &PaneNodeSnapshot{
111+ ID: node.ID,
112+ SplitDir: node.SplitDir,
113+ SplitRatio: node.SplitRatio,
114+ IsStacked: node.IsStacked,
115+ ActiveStackIndex: node.ActiveStackIndex,
116+ }
117+
118+ if node.Pane != nil {
119+ snapshot.Pane = &PaneSnapshot{
120+ ID: node.Pane.ID,
121+ URI: node.Pane.URI,
122+ Title: node.Pane.Title,
123+ ZoomFactor: node.Pane.ZoomFactor,
124+ }
125+ }
126+
127+ if len(node.Children) > 0 {
128+ snapshot.Children = make([]*PaneNodeSnapshot, 0, len(node.Children))
129+ for _, child := range node.Children {
130+ snapshot.Children = append(snapshot.Children, snapshotPaneNode(child))
131+ }
132+ }
133+
134+ return snapshot
135+}
136+
137+// SessionInfo provides summary information for the session manager UI.
138+type SessionInfo struct {
139+ Session *Session
140+ State *SessionState
141+ TabCount int
142+ PaneCount int
143+ IsActive bool // Has active lock file
144+ IsCurrent bool // Is the current session
145+ UpdatedAt time.Time // When the state was last saved
146+}
147+
148+// CountPanes returns the total number of panes in the session state.
149+func (s *SessionState) CountPanes() int {
150+ count := 0
151+ for _, tab := range s.Tabs {
152+ count += countPanesInNode(tab.Workspace.Root)
153+ }
154+ return count
155+}
156+
157+func countPanesInNode(node *PaneNodeSnapshot) int {
158+ if node == nil {
159+ return 0
160+ }
161+ if node.Pane != nil {
162+ return 1
163+ }
164+ count := 0
165+ for _, child := range node.Children {
166+ count += countPanesInNode(child)
167+ }
168+ return count
169+}
M internal/domain/repository/mocks/mocks.go +271 -0
@@ -3064,6 +3064,277 @@ func (_c *MockSessionRepository_Save_Call) RunAndReturn(run func(ctx context.Con
30643064 return _c
30653065 }
30663066
3067+// NewMockSessionStateRepository creates a new instance of MockSessionStateRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
3068+// The first argument is typically a *testing.T value.
3069+func NewMockSessionStateRepository(t interface {
3070+ mock.TestingT
3071+ Cleanup(func())
3072+}) *MockSessionStateRepository {
3073+ mock := &MockSessionStateRepository{}
3074+ mock.Mock.Test(t)
3075+
3076+ t.Cleanup(func() { mock.AssertExpectations(t) })
3077+
3078+ return mock
3079+}
3080+
3081+// MockSessionStateRepository is an autogenerated mock type for the SessionStateRepository type
3082+type MockSessionStateRepository struct {
3083+ mock.Mock
3084+}
3085+
3086+type MockSessionStateRepository_Expecter struct {
3087+ mock *mock.Mock
3088+}
3089+
3090+func (_m *MockSessionStateRepository) EXPECT() *MockSessionStateRepository_Expecter {
3091+ return &MockSessionStateRepository_Expecter{mock: &_m.Mock}
3092+}
3093+
3094+// DeleteSnapshot provides a mock function for the type MockSessionStateRepository
3095+func (_mock *MockSessionStateRepository) DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error {
3096+ ret := _mock.Called(ctx, sessionID)
3097+
3098+ if len(ret) == 0 {
3099+ panic("no return value specified for DeleteSnapshot")
3100+ }
3101+
3102+ var r0 error
3103+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) error); ok {
3104+ r0 = returnFunc(ctx, sessionID)
3105+ } else {
3106+ r0 = ret.Error(0)
3107+ }
3108+ return r0
3109+}
3110+
3111+// MockSessionStateRepository_DeleteSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteSnapshot'
3112+type MockSessionStateRepository_DeleteSnapshot_Call struct {
3113+ *mock.Call
3114+}
3115+
3116+// DeleteSnapshot is a helper method to define mock.On call
3117+// - ctx context.Context
3118+// - sessionID entity.SessionID
3119+func (_e *MockSessionStateRepository_Expecter) DeleteSnapshot(ctx interface{}, sessionID interface{}) *MockSessionStateRepository_DeleteSnapshot_Call {
3120+ return &MockSessionStateRepository_DeleteSnapshot_Call{Call: _e.mock.On("DeleteSnapshot", ctx, sessionID)}
3121+}
3122+
3123+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) Run(run func(ctx context.Context, sessionID entity.SessionID)) *MockSessionStateRepository_DeleteSnapshot_Call {
3124+ _c.Call.Run(func(args mock.Arguments) {
3125+ var arg0 context.Context
3126+ if args[0] != nil {
3127+ arg0 = args[0].(context.Context)
3128+ }
3129+ var arg1 entity.SessionID
3130+ if args[1] != nil {
3131+ arg1 = args[1].(entity.SessionID)
3132+ }
3133+ run(
3134+ arg0,
3135+ arg1,
3136+ )
3137+ })
3138+ return _c
3139+}
3140+
3141+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) Return(err error) *MockSessionStateRepository_DeleteSnapshot_Call {
3142+ _c.Call.Return(err)
3143+ return _c
3144+}
3145+
3146+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) RunAndReturn(run func(ctx context.Context, sessionID entity.SessionID) error) *MockSessionStateRepository_DeleteSnapshot_Call {
3147+ _c.Call.Return(run)
3148+ return _c
3149+}
3150+
3151+// GetAllSnapshots provides a mock function for the type MockSessionStateRepository
3152+func (_mock *MockSessionStateRepository) GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error) {
3153+ ret := _mock.Called(ctx)
3154+
3155+ if len(ret) == 0 {
3156+ panic("no return value specified for GetAllSnapshots")
3157+ }
3158+
3159+ var r0 []*entity.SessionState
3160+ var r1 error
3161+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.SessionState, error)); ok {
3162+ return returnFunc(ctx)
3163+ }
3164+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.SessionState); ok {
3165+ r0 = returnFunc(ctx)
3166+ } else {
3167+ if ret.Get(0) != nil {
3168+ r0 = ret.Get(0).([]*entity.SessionState)
3169+ }
3170+ }
3171+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
3172+ r1 = returnFunc(ctx)
3173+ } else {
3174+ r1 = ret.Error(1)
3175+ }
3176+ return r0, r1
3177+}
3178+
3179+// MockSessionStateRepository_GetAllSnapshots_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllSnapshots'
3180+type MockSessionStateRepository_GetAllSnapshots_Call struct {
3181+ *mock.Call
3182+}
3183+
3184+// GetAllSnapshots is a helper method to define mock.On call
3185+// - ctx context.Context
3186+func (_e *MockSessionStateRepository_Expecter) GetAllSnapshots(ctx interface{}) *MockSessionStateRepository_GetAllSnapshots_Call {
3187+ return &MockSessionStateRepository_GetAllSnapshots_Call{Call: _e.mock.On("GetAllSnapshots", ctx)}
3188+}
3189+
3190+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) Run(run func(ctx context.Context)) *MockSessionStateRepository_GetAllSnapshots_Call {
3191+ _c.Call.Run(func(args mock.Arguments) {
3192+ var arg0 context.Context
3193+ if args[0] != nil {
3194+ arg0 = args[0].(context.Context)
3195+ }
3196+ run(
3197+ arg0,
3198+ )
3199+ })
3200+ return _c
3201+}
3202+
3203+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) Return(sessionStates []*entity.SessionState, err error) *MockSessionStateRepository_GetAllSnapshots_Call {
3204+ _c.Call.Return(sessionStates, err)
3205+ return _c
3206+}
3207+
3208+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.SessionState, error)) *MockSessionStateRepository_GetAllSnapshots_Call {
3209+ _c.Call.Return(run)
3210+ return _c
3211+}
3212+
3213+// GetSnapshot provides a mock function for the type MockSessionStateRepository
3214+func (_mock *MockSessionStateRepository) GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error) {
3215+ ret := _mock.Called(ctx, sessionID)
3216+
3217+ if len(ret) == 0 {
3218+ panic("no return value specified for GetSnapshot")
3219+ }
3220+
3221+ var r0 *entity.SessionState
3222+ var r1 error
3223+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) (*entity.SessionState, error)); ok {
3224+ return returnFunc(ctx, sessionID)
3225+ }
3226+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) *entity.SessionState); ok {
3227+ r0 = returnFunc(ctx, sessionID)
3228+ } else {
3229+ if ret.Get(0) != nil {
3230+ r0 = ret.Get(0).(*entity.SessionState)
3231+ }
3232+ }
3233+ if returnFunc, ok := ret.Get(1).(func(context.Context, entity.SessionID) error); ok {
3234+ r1 = returnFunc(ctx, sessionID)
3235+ } else {
3236+ r1 = ret.Error(1)
3237+ }
3238+ return r0, r1
3239+}
3240+
3241+// MockSessionStateRepository_GetSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSnapshot'
3242+type MockSessionStateRepository_GetSnapshot_Call struct {
3243+ *mock.Call
3244+}
3245+
3246+// GetSnapshot is a helper method to define mock.On call
3247+// - ctx context.Context
3248+// - sessionID entity.SessionID
3249+func (_e *MockSessionStateRepository_Expecter) GetSnapshot(ctx interface{}, sessionID interface{}) *MockSessionStateRepository_GetSnapshot_Call {
3250+ return &MockSessionStateRepository_GetSnapshot_Call{Call: _e.mock.On("GetSnapshot", ctx, sessionID)}
3251+}
3252+
3253+func (_c *MockSessionStateRepository_GetSnapshot_Call) Run(run func(ctx context.Context, sessionID entity.SessionID)) *MockSessionStateRepository_GetSnapshot_Call {
3254+ _c.Call.Run(func(args mock.Arguments) {
3255+ var arg0 context.Context
3256+ if args[0] != nil {
3257+ arg0 = args[0].(context.Context)
3258+ }
3259+ var arg1 entity.SessionID
3260+ if args[1] != nil {
3261+ arg1 = args[1].(entity.SessionID)
3262+ }
3263+ run(
3264+ arg0,
3265+ arg1,
3266+ )
3267+ })
3268+ return _c
3269+}
3270+
3271+func (_c *MockSessionStateRepository_GetSnapshot_Call) Return(sessionState *entity.SessionState, err error) *MockSessionStateRepository_GetSnapshot_Call {
3272+ _c.Call.Return(sessionState, err)
3273+ return _c
3274+}
3275+
3276+func (_c *MockSessionStateRepository_GetSnapshot_Call) RunAndReturn(run func(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error)) *MockSessionStateRepository_GetSnapshot_Call {
3277+ _c.Call.Return(run)
3278+ return _c
3279+}
3280+
3281+// SaveSnapshot provides a mock function for the type MockSessionStateRepository
3282+func (_mock *MockSessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
3283+ ret := _mock.Called(ctx, state)
3284+
3285+ if len(ret) == 0 {
3286+ panic("no return value specified for SaveSnapshot")
3287+ }
3288+
3289+ var r0 error
3290+ if returnFunc, ok := ret.Get(0).(func(context.Context, *entity.SessionState) error); ok {
3291+ r0 = returnFunc(ctx, state)
3292+ } else {
3293+ r0 = ret.Error(0)
3294+ }
3295+ return r0
3296+}
3297+
3298+// MockSessionStateRepository_SaveSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveSnapshot'
3299+type MockSessionStateRepository_SaveSnapshot_Call struct {
3300+ *mock.Call
3301+}
3302+
3303+// SaveSnapshot is a helper method to define mock.On call
3304+// - ctx context.Context
3305+// - state *entity.SessionState
3306+func (_e *MockSessionStateRepository_Expecter) SaveSnapshot(ctx interface{}, state interface{}) *MockSessionStateRepository_SaveSnapshot_Call {
3307+ return &MockSessionStateRepository_SaveSnapshot_Call{Call: _e.mock.On("SaveSnapshot", ctx, state)}
3308+}
3309+
3310+func (_c *MockSessionStateRepository_SaveSnapshot_Call) Run(run func(ctx context.Context, state *entity.SessionState)) *MockSessionStateRepository_SaveSnapshot_Call {
3311+ _c.Call.Run(func(args mock.Arguments) {
3312+ var arg0 context.Context
3313+ if args[0] != nil {
3314+ arg0 = args[0].(context.Context)
3315+ }
3316+ var arg1 *entity.SessionState
3317+ if args[1] != nil {
3318+ arg1 = args[1].(*entity.SessionState)
3319+ }
3320+ run(
3321+ arg0,
3322+ arg1,
3323+ )
3324+ })
3325+ return _c
3326+}
3327+
3328+func (_c *MockSessionStateRepository_SaveSnapshot_Call) Return(err error) *MockSessionStateRepository_SaveSnapshot_Call {
3329+ _c.Call.Return(err)
3330+ return _c
3331+}
3332+
3333+func (_c *MockSessionStateRepository_SaveSnapshot_Call) RunAndReturn(run func(ctx context.Context, state *entity.SessionState) error) *MockSessionStateRepository_SaveSnapshot_Call {
3334+ _c.Call.Return(run)
3335+ return _c
3336+}
3337+
30673338 // NewMockZoomRepository creates a new instance of MockZoomRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
30683339 // The first argument is typically a *testing.T value.
30693340 func NewMockZoomRepository(t interface {
@@ -3064,6 +3064,277 @@ func (_c *MockSessionRepository_Save_Call) RunAndReturn(run func(ctx context.Con
3064 return _c3064 return _c
3065 }3065 }
3066 3066
3067+// NewMockSessionStateRepository creates a new instance of MockSessionStateRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
3068+// The first argument is typically a *testing.T value.
3069+func NewMockSessionStateRepository(t interface {
3070+ mock.TestingT
3071+ Cleanup(func())
3072+}) *MockSessionStateRepository {
3073+ mock := &MockSessionStateRepository{}
3074+ mock.Mock.Test(t)
3075+
3076+ t.Cleanup(func() { mock.AssertExpectations(t) })
3077+
3078+ return mock
3079+}
3080+
3081+// MockSessionStateRepository is an autogenerated mock type for the SessionStateRepository type
3082+type MockSessionStateRepository struct {
3083+ mock.Mock
3084+}
3085+
3086+type MockSessionStateRepository_Expecter struct {
3087+ mock *mock.Mock
3088+}
3089+
3090+func (_m *MockSessionStateRepository) EXPECT() *MockSessionStateRepository_Expecter {
3091+ return &MockSessionStateRepository_Expecter{mock: &_m.Mock}
3092+}
3093+
3094+// DeleteSnapshot provides a mock function for the type MockSessionStateRepository
3095+func (_mock *MockSessionStateRepository) DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error {
3096+ ret := _mock.Called(ctx, sessionID)
3097+
3098+ if len(ret) == 0 {
3099+ panic("no return value specified for DeleteSnapshot")
3100+ }
3101+
3102+ var r0 error
3103+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) error); ok {
3104+ r0 = returnFunc(ctx, sessionID)
3105+ } else {
3106+ r0 = ret.Error(0)
3107+ }
3108+ return r0
3109+}
3110+
3111+// MockSessionStateRepository_DeleteSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteSnapshot'
3112+type MockSessionStateRepository_DeleteSnapshot_Call struct {
3113+ *mock.Call
3114+}
3115+
3116+// DeleteSnapshot is a helper method to define mock.On call
3117+// - ctx context.Context
3118+// - sessionID entity.SessionID
3119+func (_e *MockSessionStateRepository_Expecter) DeleteSnapshot(ctx interface{}, sessionID interface{}) *MockSessionStateRepository_DeleteSnapshot_Call {
3120+ return &MockSessionStateRepository_DeleteSnapshot_Call{Call: _e.mock.On("DeleteSnapshot", ctx, sessionID)}
3121+}
3122+
3123+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) Run(run func(ctx context.Context, sessionID entity.SessionID)) *MockSessionStateRepository_DeleteSnapshot_Call {
3124+ _c.Call.Run(func(args mock.Arguments) {
3125+ var arg0 context.Context
3126+ if args[0] != nil {
3127+ arg0 = args[0].(context.Context)
3128+ }
3129+ var arg1 entity.SessionID
3130+ if args[1] != nil {
3131+ arg1 = args[1].(entity.SessionID)
3132+ }
3133+ run(
3134+ arg0,
3135+ arg1,
3136+ )
3137+ })
3138+ return _c
3139+}
3140+
3141+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) Return(err error) *MockSessionStateRepository_DeleteSnapshot_Call {
3142+ _c.Call.Return(err)
3143+ return _c
3144+}
3145+
3146+func (_c *MockSessionStateRepository_DeleteSnapshot_Call) RunAndReturn(run func(ctx context.Context, sessionID entity.SessionID) error) *MockSessionStateRepository_DeleteSnapshot_Call {
3147+ _c.Call.Return(run)
3148+ return _c
3149+}
3150+
3151+// GetAllSnapshots provides a mock function for the type MockSessionStateRepository
3152+func (_mock *MockSessionStateRepository) GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error) {
3153+ ret := _mock.Called(ctx)
3154+
3155+ if len(ret) == 0 {
3156+ panic("no return value specified for GetAllSnapshots")
3157+ }
3158+
3159+ var r0 []*entity.SessionState
3160+ var r1 error
3161+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.SessionState, error)); ok {
3162+ return returnFunc(ctx)
3163+ }
3164+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.SessionState); ok {
3165+ r0 = returnFunc(ctx)
3166+ } else {
3167+ if ret.Get(0) != nil {
3168+ r0 = ret.Get(0).([]*entity.SessionState)
3169+ }
3170+ }
3171+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
3172+ r1 = returnFunc(ctx)
3173+ } else {
3174+ r1 = ret.Error(1)
3175+ }
3176+ return r0, r1
3177+}
3178+
3179+// MockSessionStateRepository_GetAllSnapshots_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllSnapshots'
3180+type MockSessionStateRepository_GetAllSnapshots_Call struct {
3181+ *mock.Call
3182+}
3183+
3184+// GetAllSnapshots is a helper method to define mock.On call
3185+// - ctx context.Context
3186+func (_e *MockSessionStateRepository_Expecter) GetAllSnapshots(ctx interface{}) *MockSessionStateRepository_GetAllSnapshots_Call {
3187+ return &MockSessionStateRepository_GetAllSnapshots_Call{Call: _e.mock.On("GetAllSnapshots", ctx)}
3188+}
3189+
3190+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) Run(run func(ctx context.Context)) *MockSessionStateRepository_GetAllSnapshots_Call {
3191+ _c.Call.Run(func(args mock.Arguments) {
3192+ var arg0 context.Context
3193+ if args[0] != nil {
3194+ arg0 = args[0].(context.Context)
3195+ }
3196+ run(
3197+ arg0,
3198+ )
3199+ })
3200+ return _c
3201+}
3202+
3203+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) Return(sessionStates []*entity.SessionState, err error) *MockSessionStateRepository_GetAllSnapshots_Call {
3204+ _c.Call.Return(sessionStates, err)
3205+ return _c
3206+}
3207+
3208+func (_c *MockSessionStateRepository_GetAllSnapshots_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.SessionState, error)) *MockSessionStateRepository_GetAllSnapshots_Call {
3209+ _c.Call.Return(run)
3210+ return _c
3211+}
3212+
3213+// GetSnapshot provides a mock function for the type MockSessionStateRepository
3214+func (_mock *MockSessionStateRepository) GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error) {
3215+ ret := _mock.Called(ctx, sessionID)
3216+
3217+ if len(ret) == 0 {
3218+ panic("no return value specified for GetSnapshot")
3219+ }
3220+
3221+ var r0 *entity.SessionState
3222+ var r1 error
3223+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) (*entity.SessionState, error)); ok {
3224+ return returnFunc(ctx, sessionID)
3225+ }
3226+ if returnFunc, ok := ret.Get(0).(func(context.Context, entity.SessionID) *entity.SessionState); ok {
3227+ r0 = returnFunc(ctx, sessionID)
3228+ } else {
3229+ if ret.Get(0) != nil {
3230+ r0 = ret.Get(0).(*entity.SessionState)
3231+ }
3232+ }
3233+ if returnFunc, ok := ret.Get(1).(func(context.Context, entity.SessionID) error); ok {
3234+ r1 = returnFunc(ctx, sessionID)
3235+ } else {
3236+ r1 = ret.Error(1)
3237+ }
3238+ return r0, r1
3239+}
3240+
3241+// MockSessionStateRepository_GetSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSnapshot'
3242+type MockSessionStateRepository_GetSnapshot_Call struct {
3243+ *mock.Call
3244+}
3245+
3246+// GetSnapshot is a helper method to define mock.On call
3247+// - ctx context.Context
3248+// - sessionID entity.SessionID
3249+func (_e *MockSessionStateRepository_Expecter) GetSnapshot(ctx interface{}, sessionID interface{}) *MockSessionStateRepository_GetSnapshot_Call {
3250+ return &MockSessionStateRepository_GetSnapshot_Call{Call: _e.mock.On("GetSnapshot", ctx, sessionID)}
3251+}
3252+
3253+func (_c *MockSessionStateRepository_GetSnapshot_Call) Run(run func(ctx context.Context, sessionID entity.SessionID)) *MockSessionStateRepository_GetSnapshot_Call {
3254+ _c.Call.Run(func(args mock.Arguments) {
3255+ var arg0 context.Context
3256+ if args[0] != nil {
3257+ arg0 = args[0].(context.Context)
3258+ }
3259+ var arg1 entity.SessionID
3260+ if args[1] != nil {
3261+ arg1 = args[1].(entity.SessionID)
3262+ }
3263+ run(
3264+ arg0,
3265+ arg1,
3266+ )
3267+ })
3268+ return _c
3269+}
3270+
3271+func (_c *MockSessionStateRepository_GetSnapshot_Call) Return(sessionState *entity.SessionState, err error) *MockSessionStateRepository_GetSnapshot_Call {
3272+ _c.Call.Return(sessionState, err)
3273+ return _c
3274+}
3275+
3276+func (_c *MockSessionStateRepository_GetSnapshot_Call) RunAndReturn(run func(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error)) *MockSessionStateRepository_GetSnapshot_Call {
3277+ _c.Call.Return(run)
3278+ return _c
3279+}
3280+
3281+// SaveSnapshot provides a mock function for the type MockSessionStateRepository
3282+func (_mock *MockSessionStateRepository) SaveSnapshot(ctx context.Context, state *entity.SessionState) error {
3283+ ret := _mock.Called(ctx, state)
3284+
3285+ if len(ret) == 0 {
3286+ panic("no return value specified for SaveSnapshot")
3287+ }
3288+
3289+ var r0 error
3290+ if returnFunc, ok := ret.Get(0).(func(context.Context, *entity.SessionState) error); ok {
3291+ r0 = returnFunc(ctx, state)
3292+ } else {
3293+ r0 = ret.Error(0)
3294+ }
3295+ return r0
3296+}
3297+
3298+// MockSessionStateRepository_SaveSnapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveSnapshot'
3299+type MockSessionStateRepository_SaveSnapshot_Call struct {
3300+ *mock.Call
3301+}
3302+
3303+// SaveSnapshot is a helper method to define mock.On call
3304+// - ctx context.Context
3305+// - state *entity.SessionState
3306+func (_e *MockSessionStateRepository_Expecter) SaveSnapshot(ctx interface{}, state interface{}) *MockSessionStateRepository_SaveSnapshot_Call {
3307+ return &MockSessionStateRepository_SaveSnapshot_Call{Call: _e.mock.On("SaveSnapshot", ctx, state)}
3308+}
3309+
3310+func (_c *MockSessionStateRepository_SaveSnapshot_Call) Run(run func(ctx context.Context, state *entity.SessionState)) *MockSessionStateRepository_SaveSnapshot_Call {
3311+ _c.Call.Run(func(args mock.Arguments) {
3312+ var arg0 context.Context
3313+ if args[0] != nil {
3314+ arg0 = args[0].(context.Context)
3315+ }
3316+ var arg1 *entity.SessionState
3317+ if args[1] != nil {
3318+ arg1 = args[1].(*entity.SessionState)
3319+ }
3320+ run(
3321+ arg0,
3322+ arg1,
3323+ )
3324+ })
3325+ return _c
3326+}
3327+
3328+func (_c *MockSessionStateRepository_SaveSnapshot_Call) Return(err error) *MockSessionStateRepository_SaveSnapshot_Call {
3329+ _c.Call.Return(err)
3330+ return _c
3331+}
3332+
3333+func (_c *MockSessionStateRepository_SaveSnapshot_Call) RunAndReturn(run func(ctx context.Context, state *entity.SessionState) error) *MockSessionStateRepository_SaveSnapshot_Call {
3334+ _c.Call.Return(run)
3335+ return _c
3336+}
3337+
3067 // NewMockZoomRepository creates a new instance of MockZoomRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.3338 // NewMockZoomRepository creates a new instance of MockZoomRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
3068 // The first argument is typically a *testing.T value.3339 // The first argument is typically a *testing.T value.
3069 func NewMockZoomRepository(t interface {3340 func NewMockZoomRepository(t interface {
A internal/domain/repository/session_state.go +23 -0
@@ -0,0 +1,23 @@
1+package repository
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/dumber/internal/domain/entity"
7+)
8+
9+// SessionStateRepository persists session state snapshots.
10+type SessionStateRepository interface {
11+ // SaveSnapshot saves or updates a session state snapshot.
12+ SaveSnapshot(ctx context.Context, state *entity.SessionState) error
13+
14+ // GetSnapshot returns the latest snapshot for a session.
15+ GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error)
16+
17+ // DeleteSnapshot removes a session's snapshot.
18+ DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error
19+
20+ // GetAllSnapshots returns all snapshots with summary info.
21+ // Used by ListSessionsUseCase.
22+ GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error)
23+}
@@ -0,0 +1,23 @@
1+package repository
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/dumber/internal/domain/entity"
7+)
8+
9+// SessionStateRepository persists session state snapshots.
10+type SessionStateRepository interface {
11+ // SaveSnapshot saves or updates a session state snapshot.
12+ SaveSnapshot(ctx context.Context, state *entity.SessionState) error
13+
14+ // GetSnapshot returns the latest snapshot for a session.
15+ GetSnapshot(ctx context.Context, sessionID entity.SessionID) (*entity.SessionState, error)
16+
17+ // DeleteSnapshot removes a session's snapshot.
18+ DeleteSnapshot(ctx context.Context, sessionID entity.SessionID) error
19+
20+ // GetAllSnapshots returns all snapshots with summary info.
21+ // Used by ListSessionsUseCase.
22+ GetAllSnapshots(ctx context.Context) ([]*entity.SessionState, error)
23+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close