detached ac89917

Reuse shared runtime for WebKit downloads

Agent-Logs-Url: https://github.com/bnema/dumber/sessions/1efb54e2-29d8-4b77-924a-ea1cf2cfa5ec

Co-authored-by: bnema <5212137+bnema@users.noreply.github.com>
commit
ac8991785e2c20e716b9fd147d4b62cf44d4313b
author
copilot-swe-agent[bot] · · 2026-04-14 13:09 UTC
committer
brice · · 2026-04-19 19:06 UTC
parent
e7be50f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +15 -5
M internal/infrastructure/cef/download_handler.go +2 -2
@@ -101,10 +101,10 @@ func (h *downloadHandler) onDownloadUpdated(
101101 if !h.markFinished(id, state) {
102102 return
103103 }
104- err := fmt.Errorf("download failed: %s", state.filename)
104+ var err error
105105 if downloadItem.IsInterrupted() {
106106 err = fmt.Errorf("download interrupted: %s (%v)", state.filename, downloadItem.GetInterruptReason())
107- } else if downloadItem.IsCanceled() {
107+ } else {
108108 err = fmt.Errorf("download canceled: %s", state.filename)
109109 }
110110 h.runtime.EmitFailed(ctx, state.filename, state.destination, err)
@@ -101,10 +101,10 @@ func (h *downloadHandler) onDownloadUpdated(
101 if !h.markFinished(id, state) {101 if !h.markFinished(id, state) {
102 return102 return
103 }103 }
104- err := fmt.Errorf("download failed: %s", state.filename)104+ var err error
105 if downloadItem.IsInterrupted() {105 if downloadItem.IsInterrupted() {
106 err = fmt.Errorf("download interrupted: %s (%v)", state.filename, downloadItem.GetInterruptReason())106 err = fmt.Errorf("download interrupted: %s (%v)", state.filename, downloadItem.GetInterruptReason())
107- } else if downloadItem.IsCanceled() {107+ } else {
108 err = fmt.Errorf("download canceled: %s", state.filename)108 err = fmt.Errorf("download canceled: %s", state.filename)
109 }109 }
110 h.runtime.EmitFailed(ctx, state.filename, state.destination, err)110 h.runtime.EmitFailed(ctx, state.filename, state.destination, err)
M internal/infrastructure/cef/download_handler_test.go +1 -0
@@ -86,6 +86,7 @@ func TestCEFDownloadHandlerLifecycle(t *testing.T) {
8686 item.complete = true
8787 item.fullPath = callback.path
8888 handler.onDownloadUpdated(ctx, item, nil)
89+ // Repeated completion updates should not emit duplicate finished events.
8990 handler.onDownloadUpdated(ctx, item, nil)
9091
9192 require.Len(t, events.events, 2)
@@ -86,6 +86,7 @@ func TestCEFDownloadHandlerLifecycle(t *testing.T) {
86 item.complete = true86 item.complete = true
87 item.fullPath = callback.path87 item.fullPath = callback.path
88 handler.onDownloadUpdated(ctx, item, nil)88 handler.onDownloadUpdated(ctx, item, nil)
89+ // Repeated completion updates should not emit duplicate finished events.
89 handler.onDownloadUpdated(ctx, item, nil)90 handler.onDownloadUpdated(ctx, item, nil)
90 91
91 require.Len(t, events.events, 2)92 require.Len(t, events.events, 2)
M internal/infrastructure/downloadruntime/runtime.go +7 -2
@@ -52,11 +52,16 @@ func (r *Runtime) ResolveDestination(
5252 return nil, fmt.Errorf("failed to create download directory %q: %w", downloadPath, err)
5353 }
5454
55- return preparer.Execute(ctx, port.DownloadPrepareInput{
55+ output := preparer.Execute(ctx, port.DownloadPrepareInput{
5656 SuggestedFilename: suggestedFilename,
5757 Response: response,
5858 DownloadDir: downloadPath,
59- }), nil
59+ })
60+ if output == nil {
61+ return nil, fmt.Errorf("failed to prepare download destination for %q", suggestedFilename)
62+ }
63+
64+ return output, nil
6065 }
6166
6267 func (r *Runtime) EmitStarted(ctx context.Context, output *port.DownloadPrepareOutput) {
@@ -52,11 +52,16 @@ func (r *Runtime) ResolveDestination(
52 return nil, fmt.Errorf("failed to create download directory %q: %w", downloadPath, err)52 return nil, fmt.Errorf("failed to create download directory %q: %w", downloadPath, err)
53 }53 }
54 54
55- return preparer.Execute(ctx, port.DownloadPrepareInput{55+ output := preparer.Execute(ctx, port.DownloadPrepareInput{
56 SuggestedFilename: suggestedFilename,56 SuggestedFilename: suggestedFilename,
57 Response: response,57 Response: response,
58 DownloadDir: downloadPath,58 DownloadDir: downloadPath,
59- }), nil59+ })
60+ if output == nil {
61+ return nil, fmt.Errorf("failed to prepare download destination for %q", suggestedFilename)
62+ }
63+
64+ return output, nil
60 }65 }
61 66
62 func (r *Runtime) EmitStarted(ctx context.Context, output *port.DownloadPrepareOutput) {67 func (r *Runtime) EmitStarted(ctx context.Context, output *port.DownloadPrepareOutput) {
M internal/infrastructure/webkit/download_handler_test.go +5 -1
@@ -1,6 +1,7 @@
11 package webkit
22
33 import (
4+ "context"
45 "testing"
56
67 "github.com/bnema/dumber/internal/application/usecase"
@@ -31,7 +32,10 @@ func TestDownloadHandler_SetDownloadPath(t *testing.T) {
3132
3233 handler.SetDownloadPath("/new/path")
3334
34- assert.NotNil(t, handler.runtime)
35+ output, err := handler.runtime.ResolveDestination(context.Background(), "artifact.pdf", nil)
36+
37+ require.NoError(t, err)
38+ assert.Equal(t, "/new/path/artifact.pdf", output.DestinationPath)
3539 }
3640
3741 func TestURIResponseAdapter(t *testing.T) {
@@ -1,6 +1,7 @@
1 package webkit1 package webkit
2 2
3 import (3 import (
4+ "context"
4 "testing"5 "testing"
5 6
6 "github.com/bnema/dumber/internal/application/usecase"7 "github.com/bnema/dumber/internal/application/usecase"
@@ -31,7 +32,10 @@ func TestDownloadHandler_SetDownloadPath(t *testing.T) {
31 32
32 handler.SetDownloadPath("/new/path")33 handler.SetDownloadPath("/new/path")
33 34
34- assert.NotNil(t, handler.runtime)35+ output, err := handler.runtime.ResolveDestination(context.Background(), "artifact.pdf", nil)
36+
37+ require.NoError(t, err)
38+ assert.Equal(t, "/new/path/artifact.pdf", output.DestinationPath)
35 }39 }
36 40
37 func TestURIResponseAdapter(t *testing.T) {41 func TestURIResponseAdapter(t *testing.T) {

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