detached 87325b3

fix(favorites): persist shortcut updates

commit
87325b338c34ab7490045acfabf29040a8b1bf6a
author
brice · · 2026-06-28 08:21 UTC
parent
3991a6e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +58 -9
M internal/infrastructure/persistence/sqlite/favorite_repo.go +8 -3
@@ -27,10 +27,15 @@ func (r *favoriteRepo) Save(ctx context.Context, fav *entity.Favorite) error {
2727 log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite")
2828
2929 if fav.ID > 0 {
30+ shortcutKey := sql.NullInt64{}
31+ if fav.ShortcutKey != nil {
32+ shortcutKey = sql.NullInt64{Int64: int64(*fav.ShortcutKey), Valid: true}
33+ }
3034 return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{
31- Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""},
32- FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""},
33- ID: int64(fav.ID),
35+ Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""},
36+ FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""},
37+ ShortcutKey: shortcutKey,
38+ ID: int64(fav.ID),
3439 })
3540 }
3641
@@ -27,10 +27,15 @@ func (r *favoriteRepo) Save(ctx context.Context, fav *entity.Favorite) error {
27 log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite")27 log.Debug().Str("url", fav.URL).Int64("id", int64(fav.ID)).Msg("saving favorite")
28 28
29 if fav.ID > 0 {29 if fav.ID > 0 {
30+ shortcutKey := sql.NullInt64{}
31+ if fav.ShortcutKey != nil {
32+ shortcutKey = sql.NullInt64{Int64: int64(*fav.ShortcutKey), Valid: true}
33+ }
30 return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{34 return r.queries.UpdateFavorite(ctx, sqlc.UpdateFavoriteParams{
31- Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""},35+ Title: sql.NullString{String: fav.Title, Valid: fav.Title != ""},
32- FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""},36+ FaviconUrl: sql.NullString{String: fav.FaviconURL, Valid: fav.FaviconURL != ""},
33- ID: int64(fav.ID),37+ ShortcutKey: shortcutKey,
38+ ID: int64(fav.ID),
34 })39 })
35 }40 }
36 41
M internal/infrastructure/persistence/sqlite/favorite_repo_test.go +38 -0
@@ -116,6 +116,44 @@ func TestFavoriteRepositoryNoTagsHydratesEmptyTags(t *testing.T) {
116116 }
117117 }
118118
119+func TestFavoriteRepositorySaveUpdatesShortcutKey(t *testing.T) {
120+ db := openFavoriteRepoTestDB(t)
121+ mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://shortcut.example', 'Shortcut', 1)`)
122+ repo := NewFavoriteRepository(db)
123+
124+ fav, err := repo.FindByID(context.Background(), 1)
125+ if err != nil {
126+ t.Fatalf("FindByID: %v", err)
127+ }
128+ key := 5
129+ fav.ShortcutKey = &key
130+ err = repo.Save(context.Background(), fav)
131+ if err != nil {
132+ t.Fatalf("Save: %v", err)
133+ }
134+
135+ updated, err := repo.GetByShortcut(context.Background(), 5)
136+ if err != nil {
137+ t.Fatalf("GetByShortcut: %v", err)
138+ }
139+ if updated == nil || updated.ID != fav.ID {
140+ t.Fatalf("expected favorite %d by shortcut, got %#v", fav.ID, updated)
141+ }
142+
143+ updated.ShortcutKey = nil
144+ err = repo.Save(context.Background(), updated)
145+ if err != nil {
146+ t.Fatalf("Save clear shortcut: %v", err)
147+ }
148+ cleared, err := repo.FindByID(context.Background(), updated.ID)
149+ if err != nil {
150+ t.Fatalf("FindByID after clear: %v", err)
151+ }
152+ if cleared.ShortcutKey != nil {
153+ t.Fatalf("expected shortcut cleared, got %d", *cleared.ShortcutKey)
154+ }
155+}
156+
119157 func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) {
120158 t.Helper()
121159 if fav == nil {
@@ -116,6 +116,44 @@ func TestFavoriteRepositoryNoTagsHydratesEmptyTags(t *testing.T) {
116 }116 }
117 }117 }
118 118
119+func TestFavoriteRepositorySaveUpdatesShortcutKey(t *testing.T) {
120+ db := openFavoriteRepoTestDB(t)
121+ mustExec(t, db, `INSERT INTO favorites(id, url, title, position) VALUES (1, 'https://shortcut.example', 'Shortcut', 1)`)
122+ repo := NewFavoriteRepository(db)
123+
124+ fav, err := repo.FindByID(context.Background(), 1)
125+ if err != nil {
126+ t.Fatalf("FindByID: %v", err)
127+ }
128+ key := 5
129+ fav.ShortcutKey = &key
130+ err = repo.Save(context.Background(), fav)
131+ if err != nil {
132+ t.Fatalf("Save: %v", err)
133+ }
134+
135+ updated, err := repo.GetByShortcut(context.Background(), 5)
136+ if err != nil {
137+ t.Fatalf("GetByShortcut: %v", err)
138+ }
139+ if updated == nil || updated.ID != fav.ID {
140+ t.Fatalf("expected favorite %d by shortcut, got %#v", fav.ID, updated)
141+ }
142+
143+ updated.ShortcutKey = nil
144+ err = repo.Save(context.Background(), updated)
145+ if err != nil {
146+ t.Fatalf("Save clear shortcut: %v", err)
147+ }
148+ cleared, err := repo.FindByID(context.Background(), updated.ID)
149+ if err != nil {
150+ t.Fatalf("FindByID after clear: %v", err)
151+ }
152+ if cleared.ShortcutKey != nil {
153+ t.Fatalf("expected shortcut cleared, got %d", *cleared.ShortcutKey)
154+ }
155+}
156+
119 func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) {157 func assertTagNames(t *testing.T, fav *entity.Favorite, names ...string) {
120 t.Helper()158 t.Helper()
121 if fav == nil {159 if fav == nil {
M internal/infrastructure/persistence/sqlite/queries/favorites.sql +1 -1
@@ -32,7 +32,7 @@ RETURNING id, url, title, favicon_url, shortcut_key, position, created_at, updat
3232
3333 -- name: UpdateFavorite :exec
3434 UPDATE favorites
35-SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP
35+SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP
3636 WHERE id = ?;
3737
3838 -- name: UpdateFavoritePosition :exec
@@ -32,7 +32,7 @@ RETURNING id, url, title, favicon_url, shortcut_key, position, created_at, updat
32 32
33 -- name: UpdateFavorite :exec33 -- name: UpdateFavorite :exec
34 UPDATE favorites34 UPDATE favorites
35-SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP35+SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP
36 WHERE id = ?;36 WHERE id = ?;
37 37
38 -- name: UpdateFavoritePosition :exec38 -- name: UpdateFavoritePosition :exec
M internal/infrastructure/persistence/sqlite/sqlc/favorites.sql.go +11 -5
@@ -273,18 +273,24 @@ func (q *Queries) SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortc
273273
274274 const UpdateFavorite = `-- name: UpdateFavorite :exec
275275 UPDATE favorites
276-SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP
276+SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP
277277 WHERE id = ?
278278 `
279279
280280 type UpdateFavoriteParams struct {
281- Title sql.NullString `json:"title"`
282- FaviconUrl sql.NullString `json:"favicon_url"`
283- ID int64 `json:"id"`
281+ Title sql.NullString `json:"title"`
282+ FaviconUrl sql.NullString `json:"favicon_url"`
283+ ShortcutKey sql.NullInt64 `json:"shortcut_key"`
284+ ID int64 `json:"id"`
284285 }
285286
286287 func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error {
287- _, err := q.db.ExecContext(ctx, UpdateFavorite, arg.Title, arg.FaviconUrl, arg.ID)
288+ _, err := q.db.ExecContext(ctx, UpdateFavorite,
289+ arg.Title,
290+ arg.FaviconUrl,
291+ arg.ShortcutKey,
292+ arg.ID,
293+ )
288294 return err
289295 }
290296
@@ -273,18 +273,24 @@ func (q *Queries) SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortc
273 273
274 const UpdateFavorite = `-- name: UpdateFavorite :exec274 const UpdateFavorite = `-- name: UpdateFavorite :exec
275 UPDATE favorites275 UPDATE favorites
276-SET title = ?, favicon_url = ?, updated_at = CURRENT_TIMESTAMP276+SET title = ?, favicon_url = ?, shortcut_key = ?, updated_at = CURRENT_TIMESTAMP
277 WHERE id = ?277 WHERE id = ?
278 `278 `
279 279
280 type UpdateFavoriteParams struct {280 type UpdateFavoriteParams struct {
281- Title sql.NullString `json:"title"`281+ Title sql.NullString `json:"title"`
282- FaviconUrl sql.NullString `json:"favicon_url"`282+ FaviconUrl sql.NullString `json:"favicon_url"`
283- ID int64 `json:"id"`283+ ShortcutKey sql.NullInt64 `json:"shortcut_key"`
284+ ID int64 `json:"id"`
284 }285 }
285 286
286 func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error {287 func (q *Queries) UpdateFavorite(ctx context.Context, arg UpdateFavoriteParams) error {
287- _, err := q.db.ExecContext(ctx, UpdateFavorite, arg.Title, arg.FaviconUrl, arg.ID)288+ _, err := q.db.ExecContext(ctx, UpdateFavorite,
289+ arg.Title,
290+ arg.FaviconUrl,
291+ arg.ShortcutKey,
292+ arg.ID,
293+ )
288 return err294 return err
289 }295 }
290 296

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