detached 25b2387

feat(application): add port interfaces for WebView, Clipboard, and Notification

commit
25b2387ad9c1dbf939c9685d3ff83d52034179db
author
bnema · · 2025-12-09 22:29 UTC
parent
26908df
signature
unsigned
3 files · +253 -0
A internal/application/port/clipboard.go +20 -0
@@ -0,0 +1,20 @@
1+package port
2+
3+import "context"
4+
5+// Clipboard defines the port interface for clipboard operations.
6+// This abstracts platform-specific clipboard implementations (GTK, etc.).
7+type Clipboard interface {
8+ // WriteText copies text to the clipboard.
9+ WriteText(ctx context.Context, text string) error
10+
11+ // ReadText reads text from the clipboard.
12+ // Returns empty string if clipboard is empty or contains non-text data.
13+ ReadText(ctx context.Context) (string, error)
14+
15+ // Clear clears the clipboard contents.
16+ Clear(ctx context.Context) error
17+
18+ // HasText returns true if the clipboard contains text data.
19+ HasText(ctx context.Context) (bool, error)
20+}
@@ -0,0 +1,20 @@
1+package port
2+
3+import "context"
4+
5+// Clipboard defines the port interface for clipboard operations.
6+// This abstracts platform-specific clipboard implementations (GTK, etc.).
7+type Clipboard interface {
8+ // WriteText copies text to the clipboard.
9+ WriteText(ctx context.Context, text string) error
10+
11+ // ReadText reads text from the clipboard.
12+ // Returns empty string if clipboard is empty or contains non-text data.
13+ ReadText(ctx context.Context) (string, error)
14+
15+ // Clear clears the clipboard contents.
16+ Clear(ctx context.Context) error
17+
18+ // HasText returns true if the clipboard contains text data.
19+ HasText(ctx context.Context) (bool, error)
20+}
A internal/application/port/notification.go +55 -0
@@ -0,0 +1,55 @@
1+package port
2+
3+import "context"
4+
5+// NotificationType indicates the visual style of a notification.
6+type NotificationType int
7+
8+const (
9+ // NotificationInfo is for informational messages.
10+ NotificationInfo NotificationType = iota
11+ // NotificationSuccess is for success confirmations.
12+ NotificationSuccess
13+ // NotificationError is for error messages.
14+ NotificationError
15+ // NotificationWarning is for warning messages.
16+ NotificationWarning
17+)
18+
19+// String returns a human-readable representation of the notification type.
20+func (t NotificationType) String() string {
21+ switch t {
22+ case NotificationInfo:
23+ return "info"
24+ case NotificationSuccess:
25+ return "success"
26+ case NotificationError:
27+ return "error"
28+ case NotificationWarning:
29+ return "warning"
30+ default:
31+ return "info"
32+ }
33+}
34+
35+// NotificationID uniquely identifies a displayed notification.
36+type NotificationID string
37+
38+// Notification represents the port interface for user notifications/toasts.
39+// This abstracts the presentation layer (GTK/libadwaita toasts, frontend overlay, etc.).
40+type Notification interface {
41+ // Show displays a notification with the given message and type.
42+ // Duration is in milliseconds; pass 0 for default duration.
43+ // Returns an ID that can be used to dismiss the notification.
44+ Show(ctx context.Context, message string, notifType NotificationType, durationMs int) NotificationID
45+
46+ // ShowZoom displays a special zoom level notification.
47+ // These typically appear in a fixed position and auto-dismiss.
48+ ShowZoom(ctx context.Context, zoomPercent int) NotificationID
49+
50+ // Dismiss removes a specific notification by ID.
51+ Dismiss(ctx context.Context, id NotificationID)
52+
53+ // Clear removes all displayed notifications.
54+ Clear(ctx context.Context)
55+}
@@ -0,0 +1,55 @@
1+package port
2+
3+import "context"
4+
5+// NotificationType indicates the visual style of a notification.
6+type NotificationType int
7+
8+const (
9+ // NotificationInfo is for informational messages.
10+ NotificationInfo NotificationType = iota
11+ // NotificationSuccess is for success confirmations.
12+ NotificationSuccess
13+ // NotificationError is for error messages.
14+ NotificationError
15+ // NotificationWarning is for warning messages.
16+ NotificationWarning
17+)
18+
19+// String returns a human-readable representation of the notification type.
20+func (t NotificationType) String() string {
21+ switch t {
22+ case NotificationInfo:
23+ return "info"
24+ case NotificationSuccess:
25+ return "success"
26+ case NotificationError:
27+ return "error"
28+ case NotificationWarning:
29+ return "warning"
30+ default:
31+ return "info"
32+ }
33+}
34+
35+// NotificationID uniquely identifies a displayed notification.
36+type NotificationID string
37+
38+// Notification represents the port interface for user notifications/toasts.
39+// This abstracts the presentation layer (GTK/libadwaita toasts, frontend overlay, etc.).
40+type Notification interface {
41+ // Show displays a notification with the given message and type.
42+ // Duration is in milliseconds; pass 0 for default duration.
43+ // Returns an ID that can be used to dismiss the notification.
44+ Show(ctx context.Context, message string, notifType NotificationType, durationMs int) NotificationID
45+
46+ // ShowZoom displays a special zoom level notification.
47+ // These typically appear in a fixed position and auto-dismiss.
48+ ShowZoom(ctx context.Context, zoomPercent int) NotificationID
49+
50+ // Dismiss removes a specific notification by ID.
51+ Dismiss(ctx context.Context, id NotificationID)
52+
53+ // Clear removes all displayed notifications.
54+ Clear(ctx context.Context)
55+}
A internal/application/port/webview.go +178 -0
@@ -0,0 +1,178 @@
1+// Package port defines application-layer interfaces for external capabilities.
2+// Ports abstract infrastructure concerns, allowing the application layer to
3+// remain independent of specific implementations (WebKit, GTK, etc.).
4+package port
5+
6+import (
7+ "context"
8+)
9+
10+// WebViewID uniquely identifies a WebView instance.
11+type WebViewID uint64
12+
13+// LoadEvent represents page load state transitions.
14+type LoadEvent int
15+
16+const (
17+ // LoadStarted indicates navigation has begun.
18+ LoadStarted LoadEvent = iota
19+ // LoadRedirected indicates a redirect occurred.
20+ LoadRedirected
21+ // LoadCommitted indicates content is being received.
22+ LoadCommitted
23+ // LoadFinished indicates the page has fully loaded.
24+ LoadFinished
25+)
26+
27+// String returns a human-readable representation of the load event.
28+func (e LoadEvent) String() string {
29+ switch e {
30+ case LoadStarted:
31+ return "started"
32+ case LoadRedirected:
33+ return "redirected"
34+ case LoadCommitted:
35+ return "committed"
36+ case LoadFinished:
37+ return "finished"
38+ default:
39+ return "unknown"
40+ }
41+}
42+
43+// WebViewState represents a snapshot of the current WebView state.
44+// This is an immutable struct that can be safely passed between components.
45+type WebViewState struct {
46+ URI string
47+ Title string
48+ IsLoading bool
49+ Progress float64 // 0.0 to 1.0
50+ CanGoBack bool
51+ CanGoFwd bool
52+ ZoomLevel float64
53+}
54+
55+// WebViewCallbacks defines callback handlers for WebView events.
56+// Implementations should invoke these on the main thread/goroutine.
57+type WebViewCallbacks struct {
58+ // OnLoadChanged is called when load state changes.
59+ OnLoadChanged func(event LoadEvent)
60+ // OnTitleChanged is called when the page title changes.
61+ OnTitleChanged func(title string)
62+ // OnURIChanged is called when the URI changes.
63+ OnURIChanged func(uri string)
64+ // OnProgressChanged is called during page load with progress 0.0-1.0.
65+ OnProgressChanged func(progress float64)
66+ // OnClose is called when the WebView requests to close.
67+ OnClose func()
68+}
69+
70+// WebView defines the port interface for browser view operations.
71+// This interface abstracts the underlying browser engine (WebKit, etc.)
72+// and exposes only the navigation and state capabilities needed by
73+// the application layer.
74+type WebView interface {
75+ // ID returns the unique identifier for this WebView.
76+ ID() WebViewID
77+
78+ // --- Navigation ---
79+
80+ // LoadURI navigates to the specified URI.
81+ LoadURI(ctx context.Context, uri string) error
82+
83+ // LoadHTML loads HTML content with an optional base URI for relative links.
84+ LoadHTML(ctx context.Context, content, baseURI string) error
85+
86+ // Reload reloads the current page.
87+ Reload(ctx context.Context) error
88+
89+ // ReloadBypassCache reloads the current page, bypassing cache.
90+ ReloadBypassCache(ctx context.Context) error
91+
92+ // Stop stops the current page load.
93+ Stop(ctx context.Context) error
94+
95+ // GoBack navigates back in history.
96+ // Returns error if back navigation is not possible.
97+ GoBack(ctx context.Context) error
98+
99+ // GoForward navigates forward in history.
100+ // Returns error if forward navigation is not possible.
101+ GoForward(ctx context.Context) error
102+
103+ // --- State Queries ---
104+
105+ // State returns the current WebView state as a snapshot.
106+ State() WebViewState
107+
108+ // URI returns the current URI.
109+ URI() string
110+
111+ // Title returns the current page title.
112+ Title() string
113+
114+ // IsLoading returns true if a page is currently loading.
115+ IsLoading() bool
116+
117+ // EstimatedProgress returns the load progress (0.0 to 1.0).
118+ EstimatedProgress() float64
119+
120+ // CanGoBack returns true if back navigation is available.
121+ CanGoBack() bool
122+
123+ // CanGoForward returns true if forward navigation is available.
124+ CanGoForward() bool
125+
126+ // --- Zoom ---
127+
128+ // SetZoomLevel sets the zoom level (1.0 = 100%).
129+ SetZoomLevel(ctx context.Context, level float64) error
130+
131+ // GetZoomLevel returns the current zoom level.
132+ GetZoomLevel() float64
133+
134+ // --- Callbacks ---
135+
136+ // SetCallbacks registers callback handlers for WebView events.
137+ // Pass nil to clear all callbacks.
138+ SetCallbacks(callbacks *WebViewCallbacks)
139+
140+ // --- Lifecycle ---
141+
142+ // IsDestroyed returns true if the WebView has been destroyed.
143+ IsDestroyed() bool
144+
145+ // Destroy releases all resources associated with this WebView.
146+ // After calling Destroy, the WebView should not be used.
147+ Destroy()
148+}
149+
150+// WebViewPool defines the port interface for WebView pooling.
151+// Pools maintain pre-created WebViews for fast tab creation.
152+type WebViewPool interface {
153+ // Acquire obtains a WebView from the pool or creates a new one.
154+ // The context can be used for cancellation.
155+ Acquire(ctx context.Context) (WebView, error)
156+
157+ // Release returns a WebView to the pool for reuse.
158+ // The WebView will be reset to a blank state.
159+ // If the pool is full, the WebView will be destroyed.
160+ Release(wv WebView)
161+
162+ // Prewarm creates WebViews in the background to populate the pool.
163+ // Pass count <= 0 to use the default prewarm count.
164+ Prewarm(count int)
165+
166+ // Size returns the current number of available WebViews in the pool.
167+ Size() int
168+
169+ // Close shuts down the pool and destroys all pooled WebViews.
170+ Close()
171+}
172+
173+// WebViewFactory creates new WebView instances.
174+// This is used when direct WebView creation is needed without pooling.
175+type WebViewFactory interface {
176+ // Create creates a new WebView instance.
177+ Create(ctx context.Context) (WebView, error)
178+}
@@ -0,0 +1,178 @@
1+// Package port defines application-layer interfaces for external capabilities.
2+// Ports abstract infrastructure concerns, allowing the application layer to
3+// remain independent of specific implementations (WebKit, GTK, etc.).
4+package port
5+
6+import (
7+ "context"
8+)
9+
10+// WebViewID uniquely identifies a WebView instance.
11+type WebViewID uint64
12+
13+// LoadEvent represents page load state transitions.
14+type LoadEvent int
15+
16+const (
17+ // LoadStarted indicates navigation has begun.
18+ LoadStarted LoadEvent = iota
19+ // LoadRedirected indicates a redirect occurred.
20+ LoadRedirected
21+ // LoadCommitted indicates content is being received.
22+ LoadCommitted
23+ // LoadFinished indicates the page has fully loaded.
24+ LoadFinished
25+)
26+
27+// String returns a human-readable representation of the load event.
28+func (e LoadEvent) String() string {
29+ switch e {
30+ case LoadStarted:
31+ return "started"
32+ case LoadRedirected:
33+ return "redirected"
34+ case LoadCommitted:
35+ return "committed"
36+ case LoadFinished:
37+ return "finished"
38+ default:
39+ return "unknown"
40+ }
41+}
42+
43+// WebViewState represents a snapshot of the current WebView state.
44+// This is an immutable struct that can be safely passed between components.
45+type WebViewState struct {
46+ URI string
47+ Title string
48+ IsLoading bool
49+ Progress float64 // 0.0 to 1.0
50+ CanGoBack bool
51+ CanGoFwd bool
52+ ZoomLevel float64
53+}
54+
55+// WebViewCallbacks defines callback handlers for WebView events.
56+// Implementations should invoke these on the main thread/goroutine.
57+type WebViewCallbacks struct {
58+ // OnLoadChanged is called when load state changes.
59+ OnLoadChanged func(event LoadEvent)
60+ // OnTitleChanged is called when the page title changes.
61+ OnTitleChanged func(title string)
62+ // OnURIChanged is called when the URI changes.
63+ OnURIChanged func(uri string)
64+ // OnProgressChanged is called during page load with progress 0.0-1.0.
65+ OnProgressChanged func(progress float64)
66+ // OnClose is called when the WebView requests to close.
67+ OnClose func()
68+}
69+
70+// WebView defines the port interface for browser view operations.
71+// This interface abstracts the underlying browser engine (WebKit, etc.)
72+// and exposes only the navigation and state capabilities needed by
73+// the application layer.
74+type WebView interface {
75+ // ID returns the unique identifier for this WebView.
76+ ID() WebViewID
77+
78+ // --- Navigation ---
79+
80+ // LoadURI navigates to the specified URI.
81+ LoadURI(ctx context.Context, uri string) error
82+
83+ // LoadHTML loads HTML content with an optional base URI for relative links.
84+ LoadHTML(ctx context.Context, content, baseURI string) error
85+
86+ // Reload reloads the current page.
87+ Reload(ctx context.Context) error
88+
89+ // ReloadBypassCache reloads the current page, bypassing cache.
90+ ReloadBypassCache(ctx context.Context) error
91+
92+ // Stop stops the current page load.
93+ Stop(ctx context.Context) error
94+
95+ // GoBack navigates back in history.
96+ // Returns error if back navigation is not possible.
97+ GoBack(ctx context.Context) error
98+
99+ // GoForward navigates forward in history.
100+ // Returns error if forward navigation is not possible.
101+ GoForward(ctx context.Context) error
102+
103+ // --- State Queries ---
104+
105+ // State returns the current WebView state as a snapshot.
106+ State() WebViewState
107+
108+ // URI returns the current URI.
109+ URI() string
110+
111+ // Title returns the current page title.
112+ Title() string
113+
114+ // IsLoading returns true if a page is currently loading.
115+ IsLoading() bool
116+
117+ // EstimatedProgress returns the load progress (0.0 to 1.0).
118+ EstimatedProgress() float64
119+
120+ // CanGoBack returns true if back navigation is available.
121+ CanGoBack() bool
122+
123+ // CanGoForward returns true if forward navigation is available.
124+ CanGoForward() bool
125+
126+ // --- Zoom ---
127+
128+ // SetZoomLevel sets the zoom level (1.0 = 100%).
129+ SetZoomLevel(ctx context.Context, level float64) error
130+
131+ // GetZoomLevel returns the current zoom level.
132+ GetZoomLevel() float64
133+
134+ // --- Callbacks ---
135+
136+ // SetCallbacks registers callback handlers for WebView events.
137+ // Pass nil to clear all callbacks.
138+ SetCallbacks(callbacks *WebViewCallbacks)
139+
140+ // --- Lifecycle ---
141+
142+ // IsDestroyed returns true if the WebView has been destroyed.
143+ IsDestroyed() bool
144+
145+ // Destroy releases all resources associated with this WebView.
146+ // After calling Destroy, the WebView should not be used.
147+ Destroy()
148+}
149+
150+// WebViewPool defines the port interface for WebView pooling.
151+// Pools maintain pre-created WebViews for fast tab creation.
152+type WebViewPool interface {
153+ // Acquire obtains a WebView from the pool or creates a new one.
154+ // The context can be used for cancellation.
155+ Acquire(ctx context.Context) (WebView, error)
156+
157+ // Release returns a WebView to the pool for reuse.
158+ // The WebView will be reset to a blank state.
159+ // If the pool is full, the WebView will be destroyed.
160+ Release(wv WebView)
161+
162+ // Prewarm creates WebViews in the background to populate the pool.
163+ // Pass count <= 0 to use the default prewarm count.
164+ Prewarm(count int)
165+
166+ // Size returns the current number of available WebViews in the pool.
167+ Size() int
168+
169+ // Close shuts down the pool and destroys all pooled WebViews.
170+ Close()
171+}
172+
173+// WebViewFactory creates new WebView instances.
174+// This is used when direct WebView creation is needed without pooling.
175+type WebViewFactory interface {
176+ // Create creates a new WebView instance.
177+ Create(ctx context.Context) (WebView, error)
178+}

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