detached 4d548f3

chore(deps): integrate approved upstream foundations

commit
4d548f3f69cfbdc7fc18360aeb70966c0b334bd3
author
brice · · 2026-07-14 14:11 UTC
parent
41933ea
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
13 files · +482 -125
M go.mod +3 -3
@@ -6,10 +6,10 @@ require (
66 github.com/a-h/templ v0.3.1020
77 github.com/andybalholm/brotli v1.2.2
88 github.com/bnema/purego v0.11.0-bnema.4
9- github.com/bnema/purego-cef v0.13.3
9+ github.com/bnema/purego-cef v0.13.4-0.20260714140305-f0b7414af67e
1010 github.com/bnema/purego-cef2gtk v0.8.4
11- github.com/bnema/purego-pipewire v0.1.4
12- github.com/bnema/purego-sqlite v0.1.3
11+ github.com/bnema/purego-pipewire v0.1.5-0.20260714124450-6504b9cdf02a
12+ github.com/bnema/purego-sqlite v0.1.4-0.20260714131321-3614fe683b9c
1313 github.com/bnema/puregotk v0.7.0
1414 github.com/charmbracelet/bubbles v1.0.0
1515 github.com/charmbracelet/bubbletea v1.3.10
@@ -6,10 +6,10 @@ require (
6 github.com/a-h/templ v0.3.10206 github.com/a-h/templ v0.3.1020
7 github.com/andybalholm/brotli v1.2.27 github.com/andybalholm/brotli v1.2.2
8 github.com/bnema/purego v0.11.0-bnema.48 github.com/bnema/purego v0.11.0-bnema.4
9- github.com/bnema/purego-cef v0.13.39+ github.com/bnema/purego-cef v0.13.4-0.20260714140305-f0b7414af67e
10 github.com/bnema/purego-cef2gtk v0.8.410 github.com/bnema/purego-cef2gtk v0.8.4
11- github.com/bnema/purego-pipewire v0.1.411+ github.com/bnema/purego-pipewire v0.1.5-0.20260714124450-6504b9cdf02a
12- github.com/bnema/purego-sqlite v0.1.312+ github.com/bnema/purego-sqlite v0.1.4-0.20260714131321-3614fe683b9c
13 github.com/bnema/puregotk v0.7.013 github.com/bnema/puregotk v0.7.0
14 github.com/charmbracelet/bubbles v1.0.014 github.com/charmbracelet/bubbles v1.0.0
15 github.com/charmbracelet/bubbletea v1.3.1015 github.com/charmbracelet/bubbletea v1.3.10
M go.sum +6 -0 generated file: collapsed
A internal/infrastructure/persistence/sqlite/history_repo_benchmark_test.go +66 -0
@@ -0,0 +1,66 @@
1+package sqlite_test
2+
3+import (
4+ "fmt"
5+ "path/filepath"
6+ "testing"
7+
8+ "github.com/bnema/dumber/internal/domain/entity"
9+ "github.com/bnema/dumber/internal/domain/repository"
10+ "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
11+)
12+
13+// newHistoryBenchmarkRepository creates a representative local history corpus
14+// outside the measured operation. Sidebar reads use GetRecent and omnibox
15+// history suggestions use Search.
16+func newHistoryBenchmarkRepository(b *testing.B) (repository.HistoryRepository, func()) {
17+ b.Helper()
18+
19+ ctx := historyTestCtx()
20+ db, err := sqlite.NewConnection(ctx, filepath.Join(b.TempDir(), "dumber.db"))
21+ if err != nil {
22+ b.Fatal(err)
23+ }
24+
25+ repo := sqlite.NewHistoryRepository(db)
26+ for i := range 200 {
27+ entry := &entity.HistoryEntry{
28+ URL: fmt.Sprintf("https://example%d.test/dumber/history/%d", i%20, i),
29+ Title: fmt.Sprintf("Dumber history entry %d", i),
30+ }
31+ if err := repo.Save(ctx, entry); err != nil {
32+ _ = db.Close()
33+ b.Fatal(err)
34+ }
35+ }
36+
37+ return repo, func() { _ = db.Close() }
38+}
39+
40+func BenchmarkHistorySQLiteSidebarRecent(b *testing.B) {
41+ repo, closeDB := newHistoryBenchmarkRepository(b)
42+ defer closeDB()
43+ ctx := historyTestCtx()
44+
45+ b.ReportAllocs()
46+ b.ResetTimer()
47+ for range b.N {
48+ if _, err := repo.GetRecent(ctx, 50, 0); err != nil {
49+ b.Fatal(err)
50+ }
51+ }
52+}
53+
54+func BenchmarkHistorySQLiteOmniboxSearch(b *testing.B) {
55+ repo, closeDB := newHistoryBenchmarkRepository(b)
56+ defer closeDB()
57+ ctx := historyTestCtx()
58+
59+ b.ReportAllocs()
60+ b.ResetTimer()
61+ for range b.N {
62+ if _, err := repo.Search(ctx, "dumber history", 10); err != nil {
63+ b.Fatal(err)
64+ }
65+ }
66+}
@@ -0,0 +1,66 @@
1+package sqlite_test
2+
3+import (
4+ "fmt"
5+ "path/filepath"
6+ "testing"
7+
8+ "github.com/bnema/dumber/internal/domain/entity"
9+ "github.com/bnema/dumber/internal/domain/repository"
10+ "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
11+)
12+
13+// newHistoryBenchmarkRepository creates a representative local history corpus
14+// outside the measured operation. Sidebar reads use GetRecent and omnibox
15+// history suggestions use Search.
16+func newHistoryBenchmarkRepository(b *testing.B) (repository.HistoryRepository, func()) {
17+ b.Helper()
18+
19+ ctx := historyTestCtx()
20+ db, err := sqlite.NewConnection(ctx, filepath.Join(b.TempDir(), "dumber.db"))
21+ if err != nil {
22+ b.Fatal(err)
23+ }
24+
25+ repo := sqlite.NewHistoryRepository(db)
26+ for i := range 200 {
27+ entry := &entity.HistoryEntry{
28+ URL: fmt.Sprintf("https://example%d.test/dumber/history/%d", i%20, i),
29+ Title: fmt.Sprintf("Dumber history entry %d", i),
30+ }
31+ if err := repo.Save(ctx, entry); err != nil {
32+ _ = db.Close()
33+ b.Fatal(err)
34+ }
35+ }
36+
37+ return repo, func() { _ = db.Close() }
38+}
39+
40+func BenchmarkHistorySQLiteSidebarRecent(b *testing.B) {
41+ repo, closeDB := newHistoryBenchmarkRepository(b)
42+ defer closeDB()
43+ ctx := historyTestCtx()
44+
45+ b.ReportAllocs()
46+ b.ResetTimer()
47+ for range b.N {
48+ if _, err := repo.GetRecent(ctx, 50, 0); err != nil {
49+ b.Fatal(err)
50+ }
51+ }
52+}
53+
54+func BenchmarkHistorySQLiteOmniboxSearch(b *testing.B) {
55+ repo, closeDB := newHistoryBenchmarkRepository(b)
56+ defer closeDB()
57+ ctx := historyTestCtx()
58+
59+ b.ReportAllocs()
60+ b.ResetTimer()
61+ for range b.N {
62+ if _, err := repo.Search(ctx, "dumber history", 10); err != nil {
63+ b.Fatal(err)
64+ }
65+ }
66+}
M vendor/github.com/bnema/purego-cef/cef/bridge.go +8 -13 generated file: collapsed
M vendor/github.com/bnema/purego-pipewire/internal/core/player.go +5 -0 generated file: collapsed
M vendor/github.com/bnema/purego-pipewire/internal/core/player_rt.go +10 -6 generated file: collapsed
M vendor/github.com/bnema/purego-pipewire/internal/core/pwbuffer.go +66 -32
@@ -66,55 +66,89 @@ var (
6666 ErrFrameOverflow = errors.New("requested frames exceed buffer capacity")
6767 )
6868
69+// newReusablePWBufferView allocates the Go slice headers once per player.
70+// refresh replaces their native backing pointers for each dequeued buffer.
71+func newReusablePWBufferView(channels, frames int) pwBufferView {
72+ // Preserve constructor behavior for invalid channel counts: refresh will
73+ // reject them against the native metadata instead of panicking in make.
74+ var samples [][]float32
75+ if channels > 0 {
76+ samples = make([][]float32, channels)
77+ }
78+ return pwBufferView{
79+ channels: channels,
80+ frames: frames,
81+ pcm: PCMBuffer{
82+ Frames: frames,
83+ Channels: channels,
84+ Stride: 4,
85+ Samples: samples,
86+ },
87+ samples: samples,
88+ }
89+}
90+
6991 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that
70-// exposes planar float32 samples for each channel. The caller must ensure
71-// bufPtr is a valid pw_buffer pointer returned by pw_stream_dequeue_buffer.
72-// The view holds a pointer directly into the original pwBuffer so that Commit
73-// writes back into the shared memory structure, not a Go copy.
92+// exposes planar float32 samples for each channel. It is retained for callers
93+// that need a standalone view; the realtime player instead reuses one view.
7494 func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView, error) {
95+ view := newReusablePWBufferView(channels, frames)
96+ if err := view.refresh(bufPtr); err != nil {
97+ return nil, err
98+ }
99+ return &view, nil
100+}
101+
102+// refresh updates a reusable view from a dequeued native buffer. It clears all
103+// native references before validation, so invalid metadata can never leave a
104+// later callback writing through stale pointers.
105+func (v *pwBufferView) refresh(bufPtr unsafe.Pointer) error {
106+ v.clear()
75107 if bufPtr == nil {
76- return nil, ErrNilBufferPointer
108+ return ErrNilBufferPointer
77109 }
78110
79111 pw := (*pwBuffer)(bufPtr)
80112 if pw.Buffer == nil {
81- return nil, ErrNilBufferPointer
113+ return ErrNilBufferPointer
82114 }
83-
84- if pw.Buffer.NDatas != uint32(channels) {
85- return nil, ErrChannelMismatch
115+ if pw.Buffer.NDatas != uint32(v.channels) {
116+ return ErrChannelMismatch
117+ }
118+ if v.channels > 0 && pw.Buffer.Datas == nil {
119+ return ErrUnmappedData
86120 }
87121
88- // Datas is a pointer to a C array of spaData structs.
122+ // Datas is a pointer to a C array of spaData structs. unsafe.Slice creates
123+ // only a slice header; it does not copy or allocate the native metadata.
89124 dataSlice := unsafe.Slice((*spaData)(pw.Buffer.Datas), int(pw.Buffer.NDatas))
90-
91- samples := make([][]float32, channels)
92- for i := 0; i < channels; i++ {
93- data := dataSlice[i]
125+ for i := 0; i < v.channels; i++ {
126+ data := &dataSlice[i]
94127 if data.Data == nil {
95- return nil, ErrUnmappedData
128+ v.clear()
129+ return ErrUnmappedData
96130 }
97- // Reject if the buffer cannot hold the requested number of frames.
98131 maxFrames := int(data.Maxsize) / 4
99- if frames > maxFrames {
100- return nil, fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, frames, maxFrames)
132+ if v.frames > maxFrames {
133+ v.clear()
134+ return fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, v.frames, maxFrames)
101135 }
102- samples[i] = unsafe.Slice((*float32)(data.Data), frames)
136+ // The length and capacity intentionally match the configured callback
137+ // frame count, preserving the previous view contract even if PipeWire
138+ // supplied a larger mapped buffer.
139+ v.samples[i] = unsafe.Slice((*float32)(data.Data), v.frames)
103140 }
141+ v.buf = pw
142+ v.data = dataSlice
143+ return nil
144+}
104145
105- return &pwBufferView{
106- buf: pw,
107- channels: channels,
108- frames: frames,
109- data: dataSlice,
110- pcm: PCMBuffer{
111- Frames: frames,
112- Channels: channels,
113- Stride: 4,
114- Samples: samples,
115- },
116- samples: samples,
117- }, nil
146+func (v *pwBufferView) clear() {
147+ v.buf = nil
148+ v.data = nil
149+ for i := range v.samples {
150+ v.samples[i] = nil
151+ }
118152 }
119153
120154 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.
@@ -66,55 +66,89 @@ var (
66 ErrFrameOverflow = errors.New("requested frames exceed buffer capacity")66 ErrFrameOverflow = errors.New("requested frames exceed buffer capacity")
67 )67 )
68 68
69+// newReusablePWBufferView allocates the Go slice headers once per player.
70+// refresh replaces their native backing pointers for each dequeued buffer.
71+func newReusablePWBufferView(channels, frames int) pwBufferView {
72+ // Preserve constructor behavior for invalid channel counts: refresh will
73+ // reject them against the native metadata instead of panicking in make.
74+ var samples [][]float32
75+ if channels > 0 {
76+ samples = make([][]float32, channels)
77+ }
78+ return pwBufferView{
79+ channels: channels,
80+ frames: frames,
81+ pcm: PCMBuffer{
82+ Frames: frames,
83+ Channels: channels,
84+ Stride: 4,
85+ Samples: samples,
86+ },
87+ samples: samples,
88+ }
89+}
90+
69 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that91 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that
70-// exposes planar float32 samples for each channel. The caller must ensure92+// exposes planar float32 samples for each channel. It is retained for callers
71-// bufPtr is a valid pw_buffer pointer returned by pw_stream_dequeue_buffer.93+// that need a standalone view; the realtime player instead reuses one view.
72-// The view holds a pointer directly into the original pwBuffer so that Commit
73-// writes back into the shared memory structure, not a Go copy.
74 func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView, error) {94 func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView, error) {
95+ view := newReusablePWBufferView(channels, frames)
96+ if err := view.refresh(bufPtr); err != nil {
97+ return nil, err
98+ }
99+ return &view, nil
100+}
101+
102+// refresh updates a reusable view from a dequeued native buffer. It clears all
103+// native references before validation, so invalid metadata can never leave a
104+// later callback writing through stale pointers.
105+func (v *pwBufferView) refresh(bufPtr unsafe.Pointer) error {
106+ v.clear()
75 if bufPtr == nil {107 if bufPtr == nil {
76- return nil, ErrNilBufferPointer108+ return ErrNilBufferPointer
77 }109 }
78 110
79 pw := (*pwBuffer)(bufPtr)111 pw := (*pwBuffer)(bufPtr)
80 if pw.Buffer == nil {112 if pw.Buffer == nil {
81- return nil, ErrNilBufferPointer113+ return ErrNilBufferPointer
82 }114 }
83-115+ if pw.Buffer.NDatas != uint32(v.channels) {
84- if pw.Buffer.NDatas != uint32(channels) {116+ return ErrChannelMismatch
85- return nil, ErrChannelMismatch117+ }
118+ if v.channels > 0 && pw.Buffer.Datas == nil {
119+ return ErrUnmappedData
86 }120 }
87 121
88- // Datas is a pointer to a C array of spaData structs.122+ // Datas is a pointer to a C array of spaData structs. unsafe.Slice creates
123+ // only a slice header; it does not copy or allocate the native metadata.
89 dataSlice := unsafe.Slice((*spaData)(pw.Buffer.Datas), int(pw.Buffer.NDatas))124 dataSlice := unsafe.Slice((*spaData)(pw.Buffer.Datas), int(pw.Buffer.NDatas))
90-125+ for i := 0; i < v.channels; i++ {
91- samples := make([][]float32, channels)126+ data := &dataSlice[i]
92- for i := 0; i < channels; i++ {
93- data := dataSlice[i]
94 if data.Data == nil {127 if data.Data == nil {
95- return nil, ErrUnmappedData128+ v.clear()
129+ return ErrUnmappedData
96 }130 }
97- // Reject if the buffer cannot hold the requested number of frames.
98 maxFrames := int(data.Maxsize) / 4131 maxFrames := int(data.Maxsize) / 4
99- if frames > maxFrames {132+ if v.frames > maxFrames {
100- return nil, fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, frames, maxFrames)133+ v.clear()
134+ return fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, v.frames, maxFrames)
101 }135 }
102- samples[i] = unsafe.Slice((*float32)(data.Data), frames)136+ // The length and capacity intentionally match the configured callback
137+ // frame count, preserving the previous view contract even if PipeWire
138+ // supplied a larger mapped buffer.
139+ v.samples[i] = unsafe.Slice((*float32)(data.Data), v.frames)
103 }140 }
141+ v.buf = pw
142+ v.data = dataSlice
143+ return nil
144+}
104 145
105- return &pwBufferView{146+func (v *pwBufferView) clear() {
106- buf: pw,147+ v.buf = nil
107- channels: channels,148+ v.data = nil
108- frames: frames,149+ for i := range v.samples {
109- data: dataSlice,150+ v.samples[i] = nil
110- pcm: PCMBuffer{151+ }
111- Frames: frames,
112- Channels: channels,
113- Stride: 4,
114- Samples: samples,
115- },
116- samples: samples,
117- }, nil
118 }152 }
119 153
120 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.154 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.
M vendor/github.com/bnema/purego-sqlite/driver/driver.go +268 -63 generated file: collapsed
M vendor/github.com/bnema/purego-sqlite/internal/core/db.go +8 -0 generated file: collapsed
M vendor/github.com/bnema/purego-sqlite/internal/core/rows.go +13 -0 generated file: collapsed
M vendor/github.com/bnema/purego-sqlite/internal/loader/loader.go +23 -5 generated file: collapsed
M vendor/github.com/bnema/purego-sqlite/internal/ports/in/ports_gen.go +3 -0 generated file: collapsed
M vendor/modules.txt +3 -3 generated file: collapsed

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