detached 842d401

refactor(services): remove legacy favicon handlers, simplify omnibox

- Remove handleFaviconChanged() and handleFaviconURIChanged() from BrowserService
- Simplify omnibox handler: use favicon_url from DB only (no buildFavicon)
- Add GetHistoryEntry() to HistoryQuerier interface
- Update omnibox favicon styling: remove border, add border-radius
- Improve favicon image rendering (image-rendering, interpolation-mode)
- Fix formatting in defaults.go, migrations_test.go
commit
842d401ba2938e6d860c9c901635d1e896fcbc1d
author
bnema · · 2025-10-18 10:55 UTC
parent
e6af2b6
signature
GPG Unverified2B82207D370127A0 The key is unknown
6 files · +21 -147
M gui/src/components/omnibox/OmniboxSuggestions.svelte +6 -1
@@ -190,17 +190,22 @@
190190 .suggestion-favicon {
191191 width: 20px;
192192 height: 20px;
193- border: 1px solid var(--dynamic-border);
193+ border: none;
194+ border-radius: 3px;
194195 display: flex;
195196 align-items: center;
196197 justify-content: center;
197198 background: var(--dynamic-bg);
199+ flex-shrink: 0;
200+ overflow: hidden;
198201 }
199202
200203 .suggestion-favicon-img {
201204 width: 16px;
202205 height: 16px;
203206 object-fit: contain;
207+ image-rendering: -webkit-optimize-contrast;
208+ -ms-interpolation-mode: nearest-neighbor;
204209 }
205210
206211 .suggestion-text {
@@ -190,17 +190,22 @@
190 .suggestion-favicon {190 .suggestion-favicon {
191 width: 20px;191 width: 20px;
192 height: 20px;192 height: 20px;
193- border: 1px solid var(--dynamic-border);193+ border: none;
194+ border-radius: 3px;
194 display: flex;195 display: flex;
195 align-items: center;196 align-items: center;
196 justify-content: center;197 justify-content: center;
197 background: var(--dynamic-bg);198 background: var(--dynamic-bg);
199+ flex-shrink: 0;
200+ overflow: hidden;
198 }201 }
199 202
200 .suggestion-favicon-img {203 .suggestion-favicon-img {
201 width: 16px;204 width: 16px;
202 height: 16px;205 height: 16px;
203 object-fit: contain;206 object-fit: contain;
207+ image-rendering: -webkit-optimize-contrast;
208+ -ms-interpolation-mode: nearest-neighbor;
204 }209 }
205 210
206 .suggestion-text {211 .suggestion-text {
M internal/app/messaging/handler.go +1 -31
@@ -5,7 +5,6 @@ import (
55 "encoding/json"
66 "log"
77 "strconv"
8- "strings"
98 "time"
109
1110 "github.com/bnema/dumber/internal/app/constants"
@@ -226,33 +225,6 @@ func (h *Handler) handleQuery(msg Message) {
226225 Favicon string `json:"favicon,omitempty"`
227226 }
228227
229- buildFavicon := func(raw string) string {
230- if raw == "" {
231- return ""
232- }
233- scheme := "https"
234- host := raw
235- if i := strings.Index(raw, "://"); i >= 0 {
236- if i > 0 {
237- scheme = raw[:i]
238- }
239- rest := raw[i+3:]
240- if j := strings.IndexByte(rest, '/'); j >= 0 {
241- host = rest[:j]
242- } else {
243- host = rest
244- }
245- } else {
246- if j := strings.IndexByte(raw, '/'); j >= 0 {
247- host = raw[:j]
248- }
249- }
250- if host == "" {
251- return ""
252- }
253- return scheme + "://" + host + "/favicon.ico"
254- }
255-
256228 results := make([]suggestion, 0, limit)
257229 seen := make(map[string]struct{}, limit*2)
258230
@@ -284,12 +256,10 @@ func (h *Handler) handleQuery(msg Message) {
284256 if _, ok := seen[url]; ok {
285257 continue
286258 }
287- // Use favicon_url from database if available, otherwise build it
259+ // Use favicon_url from database (populated by FaviconService)
288260 favicon := ""
289261 if s, ok := m["favicon_url"].(string); ok && s != "" {
290262 favicon = s
291- } else {
292- favicon = buildFavicon(url)
293263 }
294264 results = append(results, suggestion{URL: url, Favicon: favicon})
295265 seen[url] = struct{}{}
@@ -5,7 +5,6 @@ import (
5 "encoding/json"5 "encoding/json"
6 "log"6 "log"
7 "strconv"7 "strconv"
8- "strings"
9 "time"8 "time"
10 9
11 "github.com/bnema/dumber/internal/app/constants"10 "github.com/bnema/dumber/internal/app/constants"
@@ -226,33 +225,6 @@ func (h *Handler) handleQuery(msg Message) {
226 Favicon string `json:"favicon,omitempty"`225 Favicon string `json:"favicon,omitempty"`
227 }226 }
228 227
229- buildFavicon := func(raw string) string {
230- if raw == "" {
231- return ""
232- }
233- scheme := "https"
234- host := raw
235- if i := strings.Index(raw, "://"); i >= 0 {
236- if i > 0 {
237- scheme = raw[:i]
238- }
239- rest := raw[i+3:]
240- if j := strings.IndexByte(rest, '/'); j >= 0 {
241- host = rest[:j]
242- } else {
243- host = rest
244- }
245- } else {
246- if j := strings.IndexByte(raw, '/'); j >= 0 {
247- host = raw[:j]
248- }
249- }
250- if host == "" {
251- return ""
252- }
253- return scheme + "://" + host + "/favicon.ico"
254- }
255-
256 results := make([]suggestion, 0, limit)228 results := make([]suggestion, 0, limit)
257 seen := make(map[string]struct{}, limit*2)229 seen := make(map[string]struct{}, limit*2)
258 230
@@ -284,12 +256,10 @@ func (h *Handler) handleQuery(msg Message) {
284 if _, ok := seen[url]; ok {256 if _, ok := seen[url]; ok {
285 continue257 continue
286 }258 }
287- // Use favicon_url from database if available, otherwise build it259+ // Use favicon_url from database (populated by FaviconService)
288 favicon := ""260 favicon := ""
289 if s, ok := m["favicon_url"].(string); ok && s != "" {261 if s, ok := m["favicon_url"].(string); ok && s != "" {
290 favicon = s262 favicon = s
291- } else {
292- favicon = buildFavicon(url)
293 }263 }
294 results = append(results, suggestion{URL: url, Favicon: favicon})264 results = append(results, suggestion{URL: url, Favicon: favicon})
295 seen[url] = struct{}{}265 seen[url] = struct{}{}
M internal/config/defaults.go +5 -5
@@ -32,11 +32,11 @@ const (
3232 defaultPopupPlacement = "right"
3333
3434 // Workspace styling defaults
35- defaultBorderWidth = 2
36- defaultBorderColor = "@theme_selected_bg_color"
37- defaultPaneModeBorderColor = "#FFA500" // Orange for pane mode indicator
38- defaultTransitionDuration = 120
39- defaultBorderRadius = 0
35+ defaultBorderWidth = 2
36+ defaultBorderColor = "@theme_selected_bg_color"
37+ defaultPaneModeBorderColor = "#FFA500" // Orange for pane mode indicator
38+ defaultTransitionDuration = 120
39+ defaultBorderRadius = 0
4040 )
4141
4242 // getDefaultLogDir returns the default log directory, falls back to empty string on error
@@ -32,11 +32,11 @@ const (
32 defaultPopupPlacement = "right"32 defaultPopupPlacement = "right"
33 33
34 // Workspace styling defaults34 // Workspace styling defaults
35- defaultBorderWidth = 235+ defaultBorderWidth = 2
36- defaultBorderColor = "@theme_selected_bg_color"36+ defaultBorderColor = "@theme_selected_bg_color"
37- defaultPaneModeBorderColor = "#FFA500" // Orange for pane mode indicator37+ defaultPaneModeBorderColor = "#FFA500" // Orange for pane mode indicator
38- defaultTransitionDuration = 12038+ defaultTransitionDuration = 120
39- defaultBorderRadius = 039+ defaultBorderRadius = 0
40 )40 )
41 41
42 // getDefaultLogDir returns the default log directory, falls back to empty string on error42 // getDefaultLogDir returns the default log directory, falls back to empty string on error
M internal/db/interfaces.go +1 -0
@@ -17,6 +17,7 @@ type ZoomQuerier interface {
1717 // HistoryQuerier defines the interface for history-related database operations
1818 type HistoryQuerier interface {
1919 GetHistory(ctx context.Context, limit int64) ([]History, error)
20+ GetHistoryEntry(ctx context.Context, url string) (History, error)
2021 GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error)
2122 SearchHistory(ctx context.Context, column1 sql.NullString, column2 sql.NullString, limit int64) ([]History, error)
2223 AddOrUpdateHistory(ctx context.Context, url string, title sql.NullString) error
@@ -17,6 +17,7 @@ type ZoomQuerier interface {
17 // HistoryQuerier defines the interface for history-related database operations17 // HistoryQuerier defines the interface for history-related database operations
18 type HistoryQuerier interface {18 type HistoryQuerier interface {
19 GetHistory(ctx context.Context, limit int64) ([]History, error)19 GetHistory(ctx context.Context, limit int64) ([]History, error)
20+ GetHistoryEntry(ctx context.Context, url string) (History, error)
20 GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error)21 GetHistoryWithOffset(ctx context.Context, limit int64, offset int64) ([]History, error)
21 SearchHistory(ctx context.Context, column1 sql.NullString, column2 sql.NullString, limit int64) ([]History, error)22 SearchHistory(ctx context.Context, column1 sql.NullString, column2 sql.NullString, limit int64) ([]History, error)
22 AddOrUpdateHistory(ctx context.Context, url string, title sql.NullString) error23 AddOrUpdateHistory(ctx context.Context, url string, title sql.NullString) error
M internal/migrations/migrations_test.go +4 -4
@@ -418,10 +418,10 @@ func TestExpectedTablesExistAfterMigrations(t *testing.T) {
418418
419419 // Expected tables based on current migrations
420420 expectedTables := []string{
421- "schema_migrations", // Migration tracking
422- "history", // 001_initial.sql
423- "shortcuts", // 001_initial.sql
424- "zoom_levels", // 003_add_zoom_levels.sql
421+ "schema_migrations", // Migration tracking
422+ "history", // 001_initial.sql
423+ "shortcuts", // 001_initial.sql
424+ "zoom_levels", // 003_add_zoom_levels.sql
425425 "certificate_validations", // 005_add_certificate_validations.sql
426426 }
427427
@@ -418,10 +418,10 @@ func TestExpectedTablesExistAfterMigrations(t *testing.T) {
418 418
419 // Expected tables based on current migrations419 // Expected tables based on current migrations
420 expectedTables := []string{420 expectedTables := []string{
421- "schema_migrations", // Migration tracking421+ "schema_migrations", // Migration tracking
422- "history", // 001_initial.sql422+ "history", // 001_initial.sql
423- "shortcuts", // 001_initial.sql423+ "shortcuts", // 001_initial.sql
424- "zoom_levels", // 003_add_zoom_levels.sql424+ "zoom_levels", // 003_add_zoom_levels.sql
425 "certificate_validations", // 005_add_certificate_validations.sql425 "certificate_validations", // 005_add_certificate_validations.sql
426 }426 }
427 427
M internal/services/browser.go +4 -106
@@ -3,7 +3,6 @@ package services
33
44 import (
55 "context"
6- "crypto/md5"
76 "database/sql"
87 "embed"
98 "errors"
@@ -13,7 +12,6 @@ import (
1312 neturl "net/url"
1413 "time"
1514
16- "github.com/bnema/dumber/internal/cache"
1715 "github.com/bnema/dumber/internal/config"
1816 "github.com/bnema/dumber/internal/db"
1917 "github.com/bnema/dumber/pkg/webkit"
@@ -91,20 +89,7 @@ func (s *BrowserService) SetWindowTitleUpdater(updater WindowTitleUpdater) {
9189 // AttachWebView connects a native WebKit WebView to this service for integration.
9290 func (s *BrowserService) AttachWebView(view *webkit.WebView) {
9391 s.webView = view
94-
95- // Register legacy favicon change handler (binary data - fallback)
96- // This is kept for backward compatibility but is rarely used
97- // Modern favicon handling uses the URI-based handler registered in buildPane()
98- view.RegisterFaviconChangedHandler(func(data []byte) {
99- if currentURL := view.GetCurrentURL(); currentURL != "" {
100- log.Printf("[favicon] Favicon detected for %s, data size: %d bytes", currentURL, len(data))
101- s.handleFaviconChanged(currentURL, data)
102- }
103- })
104-
105- // Note: RegisterFaviconURIChangedHandler is now only called in buildPane()
106- // to avoid duplicate handler registration which causes multiple downloads
107- // of the same favicon and file handle race conditions
92+ // Favicon handling is now managed by FaviconService
10893 }
10994
11095 // LoadGUIBundle loads the unified GUI bundle from assets
@@ -302,11 +287,13 @@ func (s *BrowserService) SearchHistory(ctx context.Context, query string, limit
302287 result := make([]HistoryEntry, len(entries))
303288 for i, entry := range entries {
304289 vc := clampToInt32(entry.VisitCount.Int64)
290+ // For omnibox display, convert favicon URI to file path when available
291+ faviconURL := entry.FaviconUrl.String
305292 result[i] = HistoryEntry{
306293 ID: entry.ID,
307294 URL: entry.Url,
308295 Title: entry.Title.String,
309- FaviconURL: entry.FaviconUrl.String,
296+ FaviconURL: faviconURL,
310297 VisitCount: vc,
311298 LastVisited: entry.LastVisited.Time,
312299 CreatedAt: entry.CreatedAt.Time,
@@ -568,95 +555,6 @@ func zoomKeyFromURL(raw string) string {
568555 // ZoomKeyForLog exposes the derived zoom key (host or raw URL) for logging from other packages.
569556 func ZoomKeyForLog(raw string) string { return zoomKeyFromURL(raw) }
570557
571-// handleFaviconChanged processes favicon data received from WebKit (legacy binary data method)
572-func (s *BrowserService) handleFaviconChanged(pageURL string, pngData []byte) {
573- log.Printf("[favicon] Processing favicon for %s, data size: %d bytes", pageURL, len(pngData))
574-
575- if len(pngData) == 0 {
576- log.Printf("[favicon] No favicon data for %s", pageURL)
577- return
578- }
579-
580- ctx := context.Background()
581-
582- // Parse URL for validation
583- parsedURL, err := neturl.Parse(pageURL)
584- if err != nil {
585- return
586- }
587-
588- // Skip favicon update for localhost, file://, or special schemes
589- if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
590- return
591- }
592- if parsedURL.Host == "localhost" || parsedURL.Host == "127.0.0.1" {
593- return
594- }
595-
596- // Cache the PNG data directly
597- if faviconCache, err := cache.NewFaviconCache(); err == nil {
598- if err := faviconCache.CachePNGData(pageURL, pngData); err != nil {
599- // Log error but don't fail
600- log.Printf("[favicon] Failed to cache favicon for %s: %v", pageURL, err)
601- } else {
602- log.Printf("[favicon] Successfully cached favicon for %s (%d bytes)", pageURL, len(pngData))
603- }
604- } else {
605- log.Printf("[favicon] Failed to create favicon cache: %v", err)
606- }
607-
608- // Store a reference to the cached file in the database
609- // We use a consistent path format for the favicon URL
610- faviconCacheURL := fmt.Sprintf("dumb://favicon/%x.png", md5.Sum([]byte(pageURL)))
611- faviconNullString := sql.NullString{String: faviconCacheURL, Valid: true}
612- if err := s.dbQueries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
613- // Silently fail - favicon is not critical
614- log.Printf("[browser] Failed to update favicon URL in database for %s: %v", pageURL, err)
615- }
616-}
617-
618-// ProcessFaviconURI is a public wrapper for handling favicon URI changes from any webview
619-func (s *BrowserService) ProcessFaviconURI(pageURL string, faviconURI string) {
620- s.handleFaviconURIChanged(pageURL, faviconURI)
621-}
622-
623-// handleFaviconURIChanged processes favicon URI changes from WebKit's native favicon database
624-func (s *BrowserService) handleFaviconURIChanged(pageURL string, faviconURI string) {
625- log.Printf("[favicon] Processing favicon URI for %s: %s", pageURL, faviconURI)
626-
627- ctx := context.Background()
628-
629- // Parse URL for validation
630- parsedURL, err := neturl.Parse(pageURL)
631- if err != nil {
632- log.Printf("[favicon] Invalid page URL: %s", pageURL)
633- return
634- }
635-
636- // Skip favicon update for localhost, file://, or special schemes
637- if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
638- return
639- }
640- if parsedURL.Host == "localhost" || parsedURL.Host == "127.0.0.1" {
641- return
642- }
643-
644- // Store the favicon URI directly in the database
645- // The frontend will handle SVG vs PNG/ICO detection
646- faviconNullString := sql.NullString{String: faviconURI, Valid: true}
647- if err := s.dbQueries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
648- log.Printf("[browser] Failed to update favicon URI in database for %s: %v", pageURL, err)
649- }
650-
651- // Download and cache the favicon asynchronously (same approach as dmenu)
652- if faviconCache, err := cache.NewFaviconCache(); err == nil {
653- faviconCache.CacheAsync(faviconURI)
654- log.Printf("[favicon] Started async download of favicon from %s for page %s", faviconURI, pageURL)
655- } else {
656- log.Printf("[favicon] Failed to create favicon cache: %v", err)
657- }
658-}
659-
660558 // ColorPalettesResponse holds light and dark palettes for JSON marshaling
661559 type ColorPalettesResponse struct {
662560 Light config.ColorPalette `json:"light"`
@@ -3,7 +3,6 @@ package services
3 3
4 import (4 import (
5 "context"5 "context"
6- "crypto/md5"
7 "database/sql"6 "database/sql"
8 "embed"7 "embed"
9 "errors"8 "errors"
@@ -13,7 +12,6 @@ import (
13 neturl "net/url"12 neturl "net/url"
14 "time"13 "time"
15 14
16- "github.com/bnema/dumber/internal/cache"
17 "github.com/bnema/dumber/internal/config"15 "github.com/bnema/dumber/internal/config"
18 "github.com/bnema/dumber/internal/db"16 "github.com/bnema/dumber/internal/db"
19 "github.com/bnema/dumber/pkg/webkit"17 "github.com/bnema/dumber/pkg/webkit"
@@ -91,20 +89,7 @@ func (s *BrowserService) SetWindowTitleUpdater(updater WindowTitleUpdater) {
91 // AttachWebView connects a native WebKit WebView to this service for integration.89 // AttachWebView connects a native WebKit WebView to this service for integration.
92 func (s *BrowserService) AttachWebView(view *webkit.WebView) {90 func (s *BrowserService) AttachWebView(view *webkit.WebView) {
93 s.webView = view91 s.webView = view
94-92+ // Favicon handling is now managed by FaviconService
95- // Register legacy favicon change handler (binary data - fallback)
96- // This is kept for backward compatibility but is rarely used
97- // Modern favicon handling uses the URI-based handler registered in buildPane()
98- view.RegisterFaviconChangedHandler(func(data []byte) {
99- if currentURL := view.GetCurrentURL(); currentURL != "" {
100- log.Printf("[favicon] Favicon detected for %s, data size: %d bytes", currentURL, len(data))
101- s.handleFaviconChanged(currentURL, data)
102- }
103- })
104-
105- // Note: RegisterFaviconURIChangedHandler is now only called in buildPane()
106- // to avoid duplicate handler registration which causes multiple downloads
107- // of the same favicon and file handle race conditions
108 }93 }
109 94
110 // LoadGUIBundle loads the unified GUI bundle from assets95 // LoadGUIBundle loads the unified GUI bundle from assets
@@ -302,11 +287,13 @@ func (s *BrowserService) SearchHistory(ctx context.Context, query string, limit
302 result := make([]HistoryEntry, len(entries))287 result := make([]HistoryEntry, len(entries))
303 for i, entry := range entries {288 for i, entry := range entries {
304 vc := clampToInt32(entry.VisitCount.Int64)289 vc := clampToInt32(entry.VisitCount.Int64)
290+ // For omnibox display, convert favicon URI to file path when available
291+ faviconURL := entry.FaviconUrl.String
305 result[i] = HistoryEntry{292 result[i] = HistoryEntry{
306 ID: entry.ID,293 ID: entry.ID,
307 URL: entry.Url,294 URL: entry.Url,
308 Title: entry.Title.String,295 Title: entry.Title.String,
309- FaviconURL: entry.FaviconUrl.String,296+ FaviconURL: faviconURL,
310 VisitCount: vc,297 VisitCount: vc,
311 LastVisited: entry.LastVisited.Time,298 LastVisited: entry.LastVisited.Time,
312 CreatedAt: entry.CreatedAt.Time,299 CreatedAt: entry.CreatedAt.Time,
@@ -568,95 +555,6 @@ func zoomKeyFromURL(raw string) string {
568 // ZoomKeyForLog exposes the derived zoom key (host or raw URL) for logging from other packages.555 // ZoomKeyForLog exposes the derived zoom key (host or raw URL) for logging from other packages.
569 func ZoomKeyForLog(raw string) string { return zoomKeyFromURL(raw) }556 func ZoomKeyForLog(raw string) string { return zoomKeyFromURL(raw) }
570 557
571-// handleFaviconChanged processes favicon data received from WebKit (legacy binary data method)
572-func (s *BrowserService) handleFaviconChanged(pageURL string, pngData []byte) {
573- log.Printf("[favicon] Processing favicon for %s, data size: %d bytes", pageURL, len(pngData))
574-
575- if len(pngData) == 0 {
576- log.Printf("[favicon] No favicon data for %s", pageURL)
577- return
578- }
579-
580- ctx := context.Background()
581-
582- // Parse URL for validation
583- parsedURL, err := neturl.Parse(pageURL)
584- if err != nil {
585- return
586- }
587-
588- // Skip favicon update for localhost, file://, or special schemes
589- if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
590- return
591- }
592- if parsedURL.Host == "localhost" || parsedURL.Host == "127.0.0.1" {
593- return
594- }
595-
596- // Cache the PNG data directly
597- if faviconCache, err := cache.NewFaviconCache(); err == nil {
598- if err := faviconCache.CachePNGData(pageURL, pngData); err != nil {
599- // Log error but don't fail
600- log.Printf("[favicon] Failed to cache favicon for %s: %v", pageURL, err)
601- } else {
602- log.Printf("[favicon] Successfully cached favicon for %s (%d bytes)", pageURL, len(pngData))
603- }
604- } else {
605- log.Printf("[favicon] Failed to create favicon cache: %v", err)
606- }
607-
608- // Store a reference to the cached file in the database
609- // We use a consistent path format for the favicon URL
610- faviconCacheURL := fmt.Sprintf("dumb://favicon/%x.png", md5.Sum([]byte(pageURL)))
611- faviconNullString := sql.NullString{String: faviconCacheURL, Valid: true}
612- if err := s.dbQueries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
613- // Silently fail - favicon is not critical
614- log.Printf("[browser] Failed to update favicon URL in database for %s: %v", pageURL, err)
615- }
616-}
617-
618-// ProcessFaviconURI is a public wrapper for handling favicon URI changes from any webview
619-func (s *BrowserService) ProcessFaviconURI(pageURL string, faviconURI string) {
620- s.handleFaviconURIChanged(pageURL, faviconURI)
621-}
622-
623-// handleFaviconURIChanged processes favicon URI changes from WebKit's native favicon database
624-func (s *BrowserService) handleFaviconURIChanged(pageURL string, faviconURI string) {
625- log.Printf("[favicon] Processing favicon URI for %s: %s", pageURL, faviconURI)
626-
627- ctx := context.Background()
628-
629- // Parse URL for validation
630- parsedURL, err := neturl.Parse(pageURL)
631- if err != nil {
632- log.Printf("[favicon] Invalid page URL: %s", pageURL)
633- return
634- }
635-
636- // Skip favicon update for localhost, file://, or special schemes
637- if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
638- return
639- }
640- if parsedURL.Host == "localhost" || parsedURL.Host == "127.0.0.1" {
641- return
642- }
643-
644- // Store the favicon URI directly in the database
645- // The frontend will handle SVG vs PNG/ICO detection
646- faviconNullString := sql.NullString{String: faviconURI, Valid: true}
647- if err := s.dbQueries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
648- log.Printf("[browser] Failed to update favicon URI in database for %s: %v", pageURL, err)
649- }
650-
651- // Download and cache the favicon asynchronously (same approach as dmenu)
652- if faviconCache, err := cache.NewFaviconCache(); err == nil {
653- faviconCache.CacheAsync(faviconURI)
654- log.Printf("[favicon] Started async download of favicon from %s for page %s", faviconURI, pageURL)
655- } else {
656- log.Printf("[favicon] Failed to create favicon cache: %v", err)
657- }
658-}
659-
660 // ColorPalettesResponse holds light and dark palettes for JSON marshaling558 // ColorPalettesResponse holds light and dark palettes for JSON marshaling
661 type ColorPalettesResponse struct {559 type ColorPalettesResponse struct {
662 Light config.ColorPalette `json:"light"`560 Light config.ColorPalette `json:"light"`

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