detached a531dc8

feat(app): add InitialURL support and normalize URLs with shared utility

commit
a531dc8e5cdfb5e6e2b72daded56de728f237b05
author
bnema · · 2025-12-17 20:45 UTC
parent
4b325e3
signature
unsigned
4 files · +17 -17
M internal/application/usecase/manage_tabs.go +3 -2
@@ -5,6 +5,7 @@ import (
55 "fmt"
66
77 "github.com/bnema/dumber/internal/domain/entity"
8+ "github.com/bnema/dumber/internal/domain/url"
89 "github.com/bnema/dumber/internal/logging"
910 )
1011
@@ -54,10 +55,10 @@ func (uc *ManageTabsUseCase) Create(ctx context.Context, input CreateTabInput) (
5455 workspaceID := entity.WorkspaceID(uc.idGenerator())
5556 paneID := entity.PaneID(uc.idGenerator())
5657
57- // Create initial pane
58+ // Create initial pane with normalized URL
5859 pane := entity.NewPane(paneID)
5960 if input.InitialURL != "" {
60- pane.URI = input.InitialURL
61+ pane.URI = url.Normalize(input.InitialURL)
6162 }
6263
6364 // Create tab with workspace
@@ -5,6 +5,7 @@ import (
5 "fmt"5 "fmt"
6 6
7 "github.com/bnema/dumber/internal/domain/entity"7 "github.com/bnema/dumber/internal/domain/entity"
8+ "github.com/bnema/dumber/internal/domain/url"
8 "github.com/bnema/dumber/internal/logging"9 "github.com/bnema/dumber/internal/logging"
9 )10 )
10 11
@@ -54,10 +55,10 @@ func (uc *ManageTabsUseCase) Create(ctx context.Context, input CreateTabInput) (
54 workspaceID := entity.WorkspaceID(uc.idGenerator())55 workspaceID := entity.WorkspaceID(uc.idGenerator())
55 paneID := entity.PaneID(uc.idGenerator())56 paneID := entity.PaneID(uc.idGenerator())
56 57
57- // Create initial pane58+ // Create initial pane with normalized URL
58 pane := entity.NewPane(paneID)59 pane := entity.NewPane(paneID)
59 if input.InitialURL != "" {60 if input.InitialURL != "" {
60- pane.URI = input.InitialURL61+ pane.URI = url.Normalize(input.InitialURL)
61 }62 }
62 63
63 // Create tab with workspace64 // Create tab with workspace
M internal/ui/app.go +6 -2
@@ -215,8 +215,12 @@ func (a *App) onActivate(ctx context.Context) {
215215 a.navCoord.SetOmniboxProvider(a)
216216 log.Debug().Msg("omnibox config stored, provider set")
217217
218- // Create an initial tab using coordinator - opens dumb://home homepage
219- if _, err := a.tabCoord.Create(ctx, "dumb://home"); err != nil {
218+ // Create an initial tab using coordinator
219+ initialURL := "dumb://home"
220+ if a.deps.InitialURL != "" {
221+ initialURL = a.deps.InitialURL
222+ }
223+ if _, err := a.tabCoord.Create(ctx, initialURL); err != nil {
220224 log.Error().Err(err).Msg("failed to create initial tab")
221225 }
222226
@@ -215,8 +215,12 @@ func (a *App) onActivate(ctx context.Context) {
215 a.navCoord.SetOmniboxProvider(a)215 a.navCoord.SetOmniboxProvider(a)
216 log.Debug().Msg("omnibox config stored, provider set")216 log.Debug().Msg("omnibox config stored, provider set")
217 217
218- // Create an initial tab using coordinator - opens dumb://home homepage218+ // Create an initial tab using coordinator
219- if _, err := a.tabCoord.Create(ctx, "dumb://home"); err != nil {219+ initialURL := "dumb://home"
220+ if a.deps.InitialURL != "" {
221+ initialURL = a.deps.InitialURL
222+ }
223+ if _, err := a.tabCoord.Create(ctx, initialURL); err != nil {
220 log.Error().Err(err).Msg("failed to create initial tab")224 log.Error().Err(err).Msg("failed to create initial tab")
221 }225 }
222 226
M internal/ui/component/omnibox.go +5 -11
@@ -9,6 +9,7 @@ import (
99
1010 "github.com/bnema/dumber/internal/application/usecase"
1111 "github.com/bnema/dumber/internal/domain/entity"
12+ "github.com/bnema/dumber/internal/domain/url"
1213 "github.com/bnema/dumber/internal/infrastructure/config"
1314 "github.com/bnema/dumber/internal/logging"
1415 "github.com/bnema/dumber/internal/ui/cache"
@@ -953,11 +954,7 @@ func (o *Omnibox) navigateToSelected() {
953954 // looksLikeURL checks if the input appears to be a URL (not a search query).
954955 // Returns true for strings like "github.com", "google.com/search", etc.
955956 func (o *Omnibox) looksLikeURL(input string) bool {
956- if input == "" {
957- return false
958- }
959- // Contains a dot and no spaces = likely a URL
960- return strings.Contains(input, ".") && !strings.Contains(input, " ")
957+ return url.LooksLikeURL(input)
961958 }
962959
963960 // toggleFavorite adds or removes the selected item from favorites.
@@ -1058,12 +1055,9 @@ func (o *Omnibox) buildURL(input string) string {
10581055 }
10591056 }
10601057
1061- // Check if it looks like a URL
1062- if strings.Contains(input, ".") && !strings.Contains(input, " ") {
1063- if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
1064- return "https://" + input
1065- }
1066- return input
1058+ // Check if it looks like a URL - use shared URL normalization
1059+ if url.LooksLikeURL(input) {
1060+ return url.Normalize(input)
10671061 }
10681062
10691063 // Use default search
@@ -9,6 +9,7 @@ import (
9 9
10 "github.com/bnema/dumber/internal/application/usecase"10 "github.com/bnema/dumber/internal/application/usecase"
11 "github.com/bnema/dumber/internal/domain/entity"11 "github.com/bnema/dumber/internal/domain/entity"
12+ "github.com/bnema/dumber/internal/domain/url"
12 "github.com/bnema/dumber/internal/infrastructure/config"13 "github.com/bnema/dumber/internal/infrastructure/config"
13 "github.com/bnema/dumber/internal/logging"14 "github.com/bnema/dumber/internal/logging"
14 "github.com/bnema/dumber/internal/ui/cache"15 "github.com/bnema/dumber/internal/ui/cache"
@@ -953,11 +954,7 @@ func (o *Omnibox) navigateToSelected() {
953 // looksLikeURL checks if the input appears to be a URL (not a search query).954 // looksLikeURL checks if the input appears to be a URL (not a search query).
954 // Returns true for strings like "github.com", "google.com/search", etc.955 // Returns true for strings like "github.com", "google.com/search", etc.
955 func (o *Omnibox) looksLikeURL(input string) bool {956 func (o *Omnibox) looksLikeURL(input string) bool {
956- if input == "" {957+ return url.LooksLikeURL(input)
957- return false
958- }
959- // Contains a dot and no spaces = likely a URL
960- return strings.Contains(input, ".") && !strings.Contains(input, " ")
961 }958 }
962 959
963 // toggleFavorite adds or removes the selected item from favorites.960 // toggleFavorite adds or removes the selected item from favorites.
@@ -1058,12 +1055,9 @@ func (o *Omnibox) buildURL(input string) string {
1058 }1055 }
1059 }1056 }
1060 1057
1061- // Check if it looks like a URL1058+ // Check if it looks like a URL - use shared URL normalization
1062- if strings.Contains(input, ".") && !strings.Contains(input, " ") {1059+ if url.LooksLikeURL(input) {
1063- if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {1060+ return url.Normalize(input)
1064- return "https://" + input
1065- }
1066- return input
1067 }1061 }
1068 1062
1069 // Use default search1063 // Use default search
M internal/ui/deps.go +3 -2
@@ -16,8 +16,9 @@ import (
1616 // This struct is created once at startup and passed to UI components.
1717 type Dependencies struct {
1818 // Core context and configuration
19- Ctx context.Context
20- Config *config.Config
19+ Ctx context.Context
20+ Config *config.Config
21+ InitialURL string // URL to open on startup (optional)
2122
2223 // Theme management
2324 Theme *theme.Manager
@@ -16,8 +16,9 @@ import (
16 // This struct is created once at startup and passed to UI components.16 // This struct is created once at startup and passed to UI components.
17 type Dependencies struct {17 type Dependencies struct {
18 // Core context and configuration18 // Core context and configuration
19- Ctx context.Context19+ Ctx context.Context
20- Config *config.Config20+ Config *config.Config
21+ InitialURL string // URL to open on startup (optional)
21 22
22 // Theme management23 // Theme management
23 Theme *theme.Manager24 Theme *theme.Manager

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