detached 8022d74

feat(infra): add session spawner for restoration

commit
8022d74265249bdff148769b33c0bcac0a1b8dca
author
bnema · · 2025-12-24 21:27 UTC
committer
brice · · 2025-12-25 05:18 UTC
parent
76add37
signature
unsigned
1 file · +45 -0
M internal/infrastructure/desktop/adapter.go +45 -0
@@ -10,6 +10,7 @@ import (
1010 "strings"
1111
1212 "github.com/bnema/dumber/internal/application/port"
13+ "github.com/bnema/dumber/internal/domain/entity"
1314 "github.com/bnema/dumber/internal/logging"
1415 )
1516
@@ -348,3 +349,47 @@ func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error {
348349 log.Warn().Msg("dumber was default browser - user should set a new default")
349350 return nil
350351 }
352+
353+// SessionSpawner implements port.SessionSpawner by launching a new dumber process.
354+type SessionSpawner struct {
355+ ctx context.Context
356+}
357+
358+// NewSessionSpawner creates a new session spawner.
359+func NewSessionSpawner(ctx context.Context) *SessionSpawner {
360+ return &SessionSpawner{ctx: ctx}
361+}
362+
363+// SpawnWithSession starts a new dumber instance to restore a session.
364+func (s *SessionSpawner) SpawnWithSession(sessionID entity.SessionID) error {
365+ log := logging.FromContext(s.ctx)
366+
367+ execPath, err := getExecutablePath()
368+ if err != nil {
369+ return fmt.Errorf("get executable path: %w", err)
370+ }
371+
372+ // Start a detached process with the --restore-session flag
373+ cmd := exec.Command(execPath, "browse", "--restore-session", string(sessionID))
374+
375+ // Detach from current process group so the new process survives
376+ cmd.Stdin = nil
377+ cmd.Stdout = nil
378+ cmd.Stderr = nil
379+
380+ if err := cmd.Start(); err != nil {
381+ return fmt.Errorf("spawn session: %w", err)
382+ }
383+
384+ // Release the process so it continues running after we close
385+ if err := cmd.Process.Release(); err != nil {
386+ log.Warn().Err(err).Msg("failed to release spawned process (non-fatal)")
387+ }
388+
389+ log.Info().
390+ Str("session_id", string(sessionID)).
391+ Int("pid", cmd.Process.Pid).
392+ Msg("spawned dumber with session restoration")
393+
394+ return nil
395+}
@@ -10,6 +10,7 @@ import (
10 "strings"10 "strings"
11 11
12 "github.com/bnema/dumber/internal/application/port"12 "github.com/bnema/dumber/internal/application/port"
13+ "github.com/bnema/dumber/internal/domain/entity"
13 "github.com/bnema/dumber/internal/logging"14 "github.com/bnema/dumber/internal/logging"
14 )15 )
15 16
@@ -348,3 +349,47 @@ func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error {
348 log.Warn().Msg("dumber was default browser - user should set a new default")349 log.Warn().Msg("dumber was default browser - user should set a new default")
349 return nil350 return nil
350 }351 }
352+
353+// SessionSpawner implements port.SessionSpawner by launching a new dumber process.
354+type SessionSpawner struct {
355+ ctx context.Context
356+}
357+
358+// NewSessionSpawner creates a new session spawner.
359+func NewSessionSpawner(ctx context.Context) *SessionSpawner {
360+ return &SessionSpawner{ctx: ctx}
361+}
362+
363+// SpawnWithSession starts a new dumber instance to restore a session.
364+func (s *SessionSpawner) SpawnWithSession(sessionID entity.SessionID) error {
365+ log := logging.FromContext(s.ctx)
366+
367+ execPath, err := getExecutablePath()
368+ if err != nil {
369+ return fmt.Errorf("get executable path: %w", err)
370+ }
371+
372+ // Start a detached process with the --restore-session flag
373+ cmd := exec.Command(execPath, "browse", "--restore-session", string(sessionID))
374+
375+ // Detach from current process group so the new process survives
376+ cmd.Stdin = nil
377+ cmd.Stdout = nil
378+ cmd.Stderr = nil
379+
380+ if err := cmd.Start(); err != nil {
381+ return fmt.Errorf("spawn session: %w", err)
382+ }
383+
384+ // Release the process so it continues running after we close
385+ if err := cmd.Process.Release(); err != nil {
386+ log.Warn().Err(err).Msg("failed to release spawned process (non-fatal)")
387+ }
388+
389+ log.Info().
390+ Str("session_id", string(sessionID)).
391+ Int("pid", cmd.Process.Pid).
392+ Msg("spawned dumber with session restoration")
393+
394+ return nil
395+}

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