detached d079703

fix: boost domain matches in omnibox search results

commit
d07970329eb70cc78ba5faa108426fa6d3001416
author
brice · · 2026-01-23 11:25 UTC
parent
c4c037a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +98 -3
M internal/infrastructure/persistence/sqlite/history_repo.go +17 -3
@@ -66,13 +66,15 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
6666 ftsQuery := strings.Join(parts, " ")
6767
6868 // Search both URL and title columns, then merge results
69- urlRows, urlErr := r.queries.SearchHistoryFTSUrl(ctx, sqlc.SearchHistoryFTSUrlParams{
69+ // Use domain-boosted query for URL search to prioritize domain matches
70+ urlRows, urlErr := r.queries.SearchHistoryFTSUrlWithDomainBoost(ctx, sqlc.SearchHistoryFTSUrlWithDomainBoostParams{
71+ Term: sql.NullString{String: words[0], Valid: true},
7072 Query: ftsQuery,
7173 Limit: int64(limit),
7274 })
7375 if urlErr != nil {
7476 log.Debug().Err(urlErr).Str("query", query).Msg("FTS URL search failed")
75- urlRows = []sqlc.History{}
77+ urlRows = []sqlc.SearchHistoryFTSUrlWithDomainBoostRow{}
7678 }
7779
7880 titleRows, titleErr := r.queries.SearchHistoryFTSTitle(ctx, sqlc.SearchHistoryFTSTitleParams{
@@ -97,7 +99,7 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
9799 if !seen[row.ID] {
98100 seen[row.ID] = true
99101 matches = append(matches, entity.HistoryMatch{
100- Entry: historyFromRow(row),
102+ Entry: historyFromDomainBoostRow(row),
101103 Score: 1.0,
102104 })
103105 }
@@ -120,6 +122,18 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
120122 return matches, nil
121123 }
122124
125+func historyFromDomainBoostRow(row sqlc.SearchHistoryFTSUrlWithDomainBoostRow) *entity.HistoryEntry {
126+ return &entity.HistoryEntry{
127+ ID: row.ID,
128+ URL: row.Url,
129+ Title: row.Title.String,
130+ FaviconURL: row.FaviconUrl.String,
131+ VisitCount: row.VisitCount.Int64,
132+ LastVisited: row.LastVisited.Time,
133+ CreatedAt: row.CreatedAt.Time,
134+ }
135+}
136+
123137 // sanitizeFTS5Word removes FTS5 special characters from a search word.
124138 // FTS5 special chars: " * ( ) : ^ - AND OR NOT NEAR
125139 func sanitizeFTS5Word(word string) string {
@@ -66,13 +66,15 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
66 ftsQuery := strings.Join(parts, " ")66 ftsQuery := strings.Join(parts, " ")
67 67
68 // Search both URL and title columns, then merge results68 // Search both URL and title columns, then merge results
69- urlRows, urlErr := r.queries.SearchHistoryFTSUrl(ctx, sqlc.SearchHistoryFTSUrlParams{69+ // Use domain-boosted query for URL search to prioritize domain matches
70+ urlRows, urlErr := r.queries.SearchHistoryFTSUrlWithDomainBoost(ctx, sqlc.SearchHistoryFTSUrlWithDomainBoostParams{
71+ Term: sql.NullString{String: words[0], Valid: true},
70 Query: ftsQuery,72 Query: ftsQuery,
71 Limit: int64(limit),73 Limit: int64(limit),
72 })74 })
73 if urlErr != nil {75 if urlErr != nil {
74 log.Debug().Err(urlErr).Str("query", query).Msg("FTS URL search failed")76 log.Debug().Err(urlErr).Str("query", query).Msg("FTS URL search failed")
75- urlRows = []sqlc.History{}77+ urlRows = []sqlc.SearchHistoryFTSUrlWithDomainBoostRow{}
76 }78 }
77 79
78 titleRows, titleErr := r.queries.SearchHistoryFTSTitle(ctx, sqlc.SearchHistoryFTSTitleParams{80 titleRows, titleErr := r.queries.SearchHistoryFTSTitle(ctx, sqlc.SearchHistoryFTSTitleParams{
@@ -97,7 +99,7 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
97 if !seen[row.ID] {99 if !seen[row.ID] {
98 seen[row.ID] = true100 seen[row.ID] = true
99 matches = append(matches, entity.HistoryMatch{101 matches = append(matches, entity.HistoryMatch{
100- Entry: historyFromRow(row),102+ Entry: historyFromDomainBoostRow(row),
101 Score: 1.0,103 Score: 1.0,
102 })104 })
103 }105 }
@@ -120,6 +122,18 @@ func (r *historyRepo) Search(ctx context.Context, query string, limit int) ([]en
120 return matches, nil122 return matches, nil
121 }123 }
122 124
125+func historyFromDomainBoostRow(row sqlc.SearchHistoryFTSUrlWithDomainBoostRow) *entity.HistoryEntry {
126+ return &entity.HistoryEntry{
127+ ID: row.ID,
128+ URL: row.Url,
129+ Title: row.Title.String,
130+ FaviconURL: row.FaviconUrl.String,
131+ VisitCount: row.VisitCount.Int64,
132+ LastVisited: row.LastVisited.Time,
133+ CreatedAt: row.CreatedAt.Time,
134+ }
135+}
136+
123 // sanitizeFTS5Word removes FTS5 special characters from a search word.137 // sanitizeFTS5Word removes FTS5 special characters from a search word.
124 // FTS5 special chars: " * ( ) : ^ - AND OR NOT NEAR138 // FTS5 special chars: " * ( ) : ^ - AND OR NOT NEAR
125 func sanitizeFTS5Word(word string) string {139 func sanitizeFTS5Word(word string) string {
M internal/infrastructure/persistence/sqlite/queries/history.sql +15 -0
@@ -57,6 +57,21 @@ WHERE fts.url MATCH @query
5757 ORDER BY h.visit_count DESC, h.last_visited DESC
5858 LIMIT @limit;
5959
60+-- name: SearchHistoryFTSUrlWithDomainBoost :many
61+SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at,
62+ CASE
63+ WHEN h.url LIKE '%://' || @term || '.%' THEN 2
64+ WHEN h.url LIKE '%://%.' || @term || '.%' THEN 2
65+ WHEN h.url LIKE '%://' || @term || '/%' THEN 2
66+ WHEN h.url LIKE '%://%.' || @term || '/%' THEN 1
67+ ELSE 0
68+ END as domain_boost
69+FROM history_fts fts
70+JOIN history h ON fts.rowid = h.id
71+WHERE fts.url MATCH @query
72+ORDER BY domain_boost DESC, h.visit_count DESC, h.last_visited DESC
73+LIMIT @limit;
74+
6075 -- name: SearchHistoryFTSTitle :many
6176 SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at
6277 FROM history_fts fts
@@ -57,6 +57,21 @@ WHERE fts.url MATCH @query
57 ORDER BY h.visit_count DESC, h.last_visited DESC57 ORDER BY h.visit_count DESC, h.last_visited DESC
58 LIMIT @limit;58 LIMIT @limit;
59 59
60+-- name: SearchHistoryFTSUrlWithDomainBoost :many
61+SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at,
62+ CASE
63+ WHEN h.url LIKE '%://' || @term || '.%' THEN 2
64+ WHEN h.url LIKE '%://%.' || @term || '.%' THEN 2
65+ WHEN h.url LIKE '%://' || @term || '/%' THEN 2
66+ WHEN h.url LIKE '%://%.' || @term || '/%' THEN 1
67+ ELSE 0
68+ END as domain_boost
69+FROM history_fts fts
70+JOIN history h ON fts.rowid = h.id
71+WHERE fts.url MATCH @query
72+ORDER BY domain_boost DESC, h.visit_count DESC, h.last_visited DESC
73+LIMIT @limit;
74+
60 -- name: SearchHistoryFTSTitle :many75 -- name: SearchHistoryFTSTitle :many
61 SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at76 SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at
62 FROM history_fts fts77 FROM history_fts fts
M internal/infrastructure/persistence/sqlite/sqlc/history.sql.go +65 -0
@@ -529,6 +529,71 @@ func (q *Queries) SearchHistoryFTSUrl(ctx context.Context, arg SearchHistoryFTSU
529529 return items, nil
530530 }
531531
532+const SearchHistoryFTSUrlWithDomainBoost = `-- name: SearchHistoryFTSUrlWithDomainBoost :many
533+SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at,
534+ CASE
535+ WHEN h.url LIKE '%://' || ?1 || '.%' THEN 2
536+ WHEN h.url LIKE '%://%.' || ?1 || '.%' THEN 2
537+ WHEN h.url LIKE '%://' || ?1 || '/%' THEN 2
538+ WHEN h.url LIKE '%://%.' || ?1 || '/%' THEN 1
539+ ELSE 0
540+ END as domain_boost
541+FROM history_fts fts
542+JOIN history h ON fts.rowid = h.id
543+WHERE fts.url MATCH ?2
544+ORDER BY domain_boost DESC, h.visit_count DESC, h.last_visited DESC
545+LIMIT ?3
546+`
547+
548+type SearchHistoryFTSUrlWithDomainBoostParams struct {
549+ Term sql.NullString `json:"term"`
550+ Query string `json:"query"`
551+ Limit int64 `json:"limit"`
552+}
553+
554+type SearchHistoryFTSUrlWithDomainBoostRow struct {
555+ ID int64 `json:"id"`
556+ Url string `json:"url"`
557+ Title sql.NullString `json:"title"`
558+ FaviconUrl sql.NullString `json:"favicon_url"`
559+ VisitCount sql.NullInt64 `json:"visit_count"`
560+ LastVisited sql.NullTime `json:"last_visited"`
561+ CreatedAt sql.NullTime `json:"created_at"`
562+ DomainBoost int64 `json:"domain_boost"`
563+}
564+
565+func (q *Queries) SearchHistoryFTSUrlWithDomainBoost(ctx context.Context, arg SearchHistoryFTSUrlWithDomainBoostParams) ([]SearchHistoryFTSUrlWithDomainBoostRow, error) {
566+ rows, err := q.db.QueryContext(ctx, SearchHistoryFTSUrlWithDomainBoost, arg.Term, arg.Query, arg.Limit)
567+ if err != nil {
568+ return nil, err
569+ }
570+ defer rows.Close()
571+ items := []SearchHistoryFTSUrlWithDomainBoostRow{}
572+ for rows.Next() {
573+ var i SearchHistoryFTSUrlWithDomainBoostRow
574+ if err := rows.Scan(
575+ &i.ID,
576+ &i.Url,
577+ &i.Title,
578+ &i.FaviconUrl,
579+ &i.VisitCount,
580+ &i.LastVisited,
581+ &i.CreatedAt,
582+ &i.DomainBoost,
583+ ); err != nil {
584+ return nil, err
585+ }
586+ items = append(items, i)
587+ }
588+ if err := rows.Close(); err != nil {
589+ return nil, err
590+ }
591+ if err := rows.Err(); err != nil {
592+ return nil, err
593+ }
594+ return items, nil
595+}
596+
532597 const UpsertHistory = `-- name: UpsertHistory :exec
533598 INSERT INTO history (url, title, favicon_url)
534599 VALUES (?, ?, ?)
@@ -529,6 +529,71 @@ func (q *Queries) SearchHistoryFTSUrl(ctx context.Context, arg SearchHistoryFTSU
529 return items, nil529 return items, nil
530 }530 }
531 531
532+const SearchHistoryFTSUrlWithDomainBoost = `-- name: SearchHistoryFTSUrlWithDomainBoost :many
533+SELECT h.id, h.url, h.title, h.favicon_url, h.visit_count, h.last_visited, h.created_at,
534+ CASE
535+ WHEN h.url LIKE '%://' || ?1 || '.%' THEN 2
536+ WHEN h.url LIKE '%://%.' || ?1 || '.%' THEN 2
537+ WHEN h.url LIKE '%://' || ?1 || '/%' THEN 2
538+ WHEN h.url LIKE '%://%.' || ?1 || '/%' THEN 1
539+ ELSE 0
540+ END as domain_boost
541+FROM history_fts fts
542+JOIN history h ON fts.rowid = h.id
543+WHERE fts.url MATCH ?2
544+ORDER BY domain_boost DESC, h.visit_count DESC, h.last_visited DESC
545+LIMIT ?3
546+`
547+
548+type SearchHistoryFTSUrlWithDomainBoostParams struct {
549+ Term sql.NullString `json:"term"`
550+ Query string `json:"query"`
551+ Limit int64 `json:"limit"`
552+}
553+
554+type SearchHistoryFTSUrlWithDomainBoostRow struct {
555+ ID int64 `json:"id"`
556+ Url string `json:"url"`
557+ Title sql.NullString `json:"title"`
558+ FaviconUrl sql.NullString `json:"favicon_url"`
559+ VisitCount sql.NullInt64 `json:"visit_count"`
560+ LastVisited sql.NullTime `json:"last_visited"`
561+ CreatedAt sql.NullTime `json:"created_at"`
562+ DomainBoost int64 `json:"domain_boost"`
563+}
564+
565+func (q *Queries) SearchHistoryFTSUrlWithDomainBoost(ctx context.Context, arg SearchHistoryFTSUrlWithDomainBoostParams) ([]SearchHistoryFTSUrlWithDomainBoostRow, error) {
566+ rows, err := q.db.QueryContext(ctx, SearchHistoryFTSUrlWithDomainBoost, arg.Term, arg.Query, arg.Limit)
567+ if err != nil {
568+ return nil, err
569+ }
570+ defer rows.Close()
571+ items := []SearchHistoryFTSUrlWithDomainBoostRow{}
572+ for rows.Next() {
573+ var i SearchHistoryFTSUrlWithDomainBoostRow
574+ if err := rows.Scan(
575+ &i.ID,
576+ &i.Url,
577+ &i.Title,
578+ &i.FaviconUrl,
579+ &i.VisitCount,
580+ &i.LastVisited,
581+ &i.CreatedAt,
582+ &i.DomainBoost,
583+ ); err != nil {
584+ return nil, err
585+ }
586+ items = append(items, i)
587+ }
588+ if err := rows.Close(); err != nil {
589+ return nil, err
590+ }
591+ if err := rows.Err(); err != nil {
592+ return nil, err
593+ }
594+ return items, nil
595+}
596+
532 const UpsertHistory = `-- name: UpsertHistory :exec597 const UpsertHistory = `-- name: UpsertHistory :exec
533 INSERT INTO history (url, title, favicon_url)598 INSERT INTO history (url, title, favicon_url)
534 VALUES (?, ?, ?)599 VALUES (?, ?, ?)
M internal/infrastructure/persistence/sqlite/sqlc/querier.go +1 -0
@@ -72,6 +72,7 @@ type Querier interface {
7272 SearchHistory(ctx context.Context, arg SearchHistoryParams) ([]History, error)
7373 SearchHistoryFTSTitle(ctx context.Context, arg SearchHistoryFTSTitleParams) ([]History, error)
7474 SearchHistoryFTSUrl(ctx context.Context, arg SearchHistoryFTSUrlParams) ([]History, error)
75+ SearchHistoryFTSUrlWithDomainBoost(ctx context.Context, arg SearchHistoryFTSUrlWithDomainBoostParams) ([]SearchHistoryFTSUrlWithDomainBoostRow, error)
7576 SetFavoriteFolder(ctx context.Context, arg SetFavoriteFolderParams) error
7677 SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortcutParams) error
7778 SetZoomLevel(ctx context.Context, arg SetZoomLevelParams) error
@@ -72,6 +72,7 @@ type Querier interface {
72 SearchHistory(ctx context.Context, arg SearchHistoryParams) ([]History, error)72 SearchHistory(ctx context.Context, arg SearchHistoryParams) ([]History, error)
73 SearchHistoryFTSTitle(ctx context.Context, arg SearchHistoryFTSTitleParams) ([]History, error)73 SearchHistoryFTSTitle(ctx context.Context, arg SearchHistoryFTSTitleParams) ([]History, error)
74 SearchHistoryFTSUrl(ctx context.Context, arg SearchHistoryFTSUrlParams) ([]History, error)74 SearchHistoryFTSUrl(ctx context.Context, arg SearchHistoryFTSUrlParams) ([]History, error)
75+ SearchHistoryFTSUrlWithDomainBoost(ctx context.Context, arg SearchHistoryFTSUrlWithDomainBoostParams) ([]SearchHistoryFTSUrlWithDomainBoostRow, error)
75 SetFavoriteFolder(ctx context.Context, arg SetFavoriteFolderParams) error76 SetFavoriteFolder(ctx context.Context, arg SetFavoriteFolderParams) error
76 SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortcutParams) error77 SetFavoriteShortcut(ctx context.Context, arg SetFavoriteShortcutParams) error
77 SetZoomLevel(ctx context.Context, arg SetZoomLevelParams) error78 SetZoomLevel(ctx context.Context, arg SetZoomLevelParams) error

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