detached 8171186

fix(app): surface catalogue reset guidance

commit
8171186d8cccc56f269f34e7e9183f0d1c36e175
author
brice · · 2026-07-26 13:25 UTC
parent
836c233
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +28 -1
M internal/app/run.go +3 -0
@@ -1297,6 +1297,9 @@ func runList(ctx context.Context) (retErr error) {
12971297 defer joinLifecycleReleaseError(&retErr, owner)
12981298 records, loadErr := persist.LoadReadOnly(platform.StateDir())
12991299 if loadErr != nil {
1300+ if errors.Is(loadErr, persist.ErrCatalogueUnreadable) {
1301+ return unreadableCatalogueError(platform.StateDir())
1302+ }
13001303 return fmt.Errorf("vev: reading stored sessions: %w", loadErr)
13011304 }
13021305 infos := make([]ports.SessionInfo, 0, len(records))
@@ -1297,6 +1297,9 @@ func runList(ctx context.Context) (retErr error) {
1297 defer joinLifecycleReleaseError(&retErr, owner)1297 defer joinLifecycleReleaseError(&retErr, owner)
1298 records, loadErr := persist.LoadReadOnly(platform.StateDir())1298 records, loadErr := persist.LoadReadOnly(platform.StateDir())
1299 if loadErr != nil {1299 if loadErr != nil {
1300+ if errors.Is(loadErr, persist.ErrCatalogueUnreadable) {
1301+ return unreadableCatalogueError(platform.StateDir())
1302+ }
1300 return fmt.Errorf("vev: reading stored sessions: %w", loadErr)1303 return fmt.Errorf("vev: reading stored sessions: %w", loadErr)
1301 }1304 }
1302 infos := make([]ports.SessionInfo, 0, len(records))1305 infos := make([]ports.SessionInfo, 0, len(records))
M internal/app/run_test.go +24 -0
@@ -721,6 +721,30 @@ func TestRunListReadsStoppedSessionsWithoutDaemon(t *testing.T) {
721721 }
722722 }
723723
724+func TestRunListUnreadableCatalogueProvidesResetGuidance(t *testing.T) {
725+ stateRoot, runtimeRoot := t.TempDir(), t.TempDir()
726+ stateDir := filepath.Join(stateRoot, "vev")
727+ runtimeDir := filepath.Join(runtimeRoot, "vev")
728+ t.Setenv("XDG_STATE_HOME", stateRoot)
729+ t.Setenv("XDG_RUNTIME_DIR", runtimeRoot)
730+ require.NoError(t, os.Mkdir(stateDir, 0o700))
731+
732+ catalogue := persist.StorePath(stateDir)
733+ before := []byte("corrupt catalogue")
734+ require.NoError(t, os.WriteFile(catalogue, before, 0o600))
735+
736+ var runErr error
737+ stdout := captureStdout(t, func() { runErr = Run([]string{"ls"}) })
738+ require.Empty(t, stdout)
739+ require.Error(t, runErr)
740+ require.ErrorContains(t, runErr, stateDir)
741+ require.ErrorContains(t, runErr, "rm -rf "+stateDir)
742+ require.NoFileExists(t, filepath.Join(runtimeDir, "daemon.sock"))
743+ after, err := os.ReadFile(catalogue)
744+ require.NoError(t, err)
745+ require.Equal(t, before, after)
746+}
747+
724748 func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) {
725749 stateRoot, runtimeRoot := t.TempDir(), t.TempDir()
726750 t.Setenv("XDG_STATE_HOME", stateRoot)
@@ -721,6 +721,30 @@ func TestRunListReadsStoppedSessionsWithoutDaemon(t *testing.T) {
721 }721 }
722 }722 }
723 723
724+func TestRunListUnreadableCatalogueProvidesResetGuidance(t *testing.T) {
725+ stateRoot, runtimeRoot := t.TempDir(), t.TempDir()
726+ stateDir := filepath.Join(stateRoot, "vev")
727+ runtimeDir := filepath.Join(runtimeRoot, "vev")
728+ t.Setenv("XDG_STATE_HOME", stateRoot)
729+ t.Setenv("XDG_RUNTIME_DIR", runtimeRoot)
730+ require.NoError(t, os.Mkdir(stateDir, 0o700))
731+
732+ catalogue := persist.StorePath(stateDir)
733+ before := []byte("corrupt catalogue")
734+ require.NoError(t, os.WriteFile(catalogue, before, 0o600))
735+
736+ var runErr error
737+ stdout := captureStdout(t, func() { runErr = Run([]string{"ls"}) })
738+ require.Empty(t, stdout)
739+ require.Error(t, runErr)
740+ require.ErrorContains(t, runErr, stateDir)
741+ require.ErrorContains(t, runErr, "rm -rf "+stateDir)
742+ require.NoFileExists(t, filepath.Join(runtimeDir, "daemon.sock"))
743+ after, err := os.ReadFile(catalogue)
744+ require.NoError(t, err)
745+ require.Equal(t, before, after)
746+}
747+
724 func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) {748 func TestRunKillMissingStoppedSessionDoesNotCreateStore(t *testing.T) {
725 stateRoot, runtimeRoot := t.TempDir(), t.TempDir()749 stateRoot, runtimeRoot := t.TempDir(), t.TempDir()
726 t.Setenv("XDG_STATE_HOME", stateRoot)750 t.Setenv("XDG_STATE_HOME", stateRoot)
M internal/persist/persist.go +1 -1
@@ -133,7 +133,7 @@ func LoadCatalogueReadOnly(dir string) ([]domain.CatalogueRecord, error) {
133133 if errors.Is(err, errPersistenceUnavailable) {
134134 return []domain.CatalogueRecord{}, nil
135135 }
136- return nil, err
136+ return nil, fmt.Errorf("%w: %s: %w", ErrCatalogueUnreadable, StorePath(dir), err)
137137 }
138138 if err := p.Close(); err != nil {
139139 return nil, err
@@ -133,7 +133,7 @@ func LoadCatalogueReadOnly(dir string) ([]domain.CatalogueRecord, error) {
133 if errors.Is(err, errPersistenceUnavailable) {133 if errors.Is(err, errPersistenceUnavailable) {
134 return []domain.CatalogueRecord{}, nil134 return []domain.CatalogueRecord{}, nil
135 }135 }
136- return nil, err136+ return nil, fmt.Errorf("%w: %s: %w", ErrCatalogueUnreadable, StorePath(dir), err)
137 }137 }
138 if err := p.Close(); err != nil {138 if err := p.Close(); err != nil {
139 return nil, err139 return nil, err

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 12

Tags 11

↑↓ moveenter openesc close