detached c97e5fd

fix: filter accent detection JS to only accent-capable keys

Generate JS allowlist from entity.AccentMap so only a,c,e,i,n,o,s,u,y
trigger bridge messages. Reduces unnecessary JS-to-Go traffic for the
17 non-accent letters.
commit
c97e5fd0abedf7c2376a6357c8d0a01aedd66cf5
author
brice · · 2026-03-13 06:32 UTC
parent
b0dae66
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +20 -19
M internal/infrastructure/webkit/injector.go +20 -19
@@ -3,9 +3,11 @@ package webkit
33 import (
44 "context"
55 "fmt"
6+ "sort"
67 "strings"
78
89 "github.com/bnema/dumber/internal/application/port"
10+ "github.com/bnema/dumber/internal/domain/entity"
911 "github.com/bnema/dumber/internal/logging"
1012 "github.com/bnema/puregotk-webkit/webkit"
1113 )
@@ -141,16 +143,23 @@ const autoCopySelectionScript = `(function() {
141143 });
142144 })();`
143145
144-// accentDetectionScript detects keydown/keyup of a-z keys in WebView inputs and
145-// forwards them to the Go-side InsertAccentUseCase via the message bridge.
146-// Go handles timing, accent lookup, and picker display — JS only reports events.
147-const accentDetectionScript = `(function() {
148- 'use strict';
146+// accentDetectionScript is built at init from entity.AccentMap so the JS
147+// filter stays in sync with the Go-side accent table.
148+var accentDetectionScript string
149+
150+func init() {
151+ // Build JS Set literal from AccentMap keys: "new Set(['a','c','e',...])"
152+ keys := make([]string, 0, len(entity.AccentMap))
153+ for k := range entity.AccentMap {
154+ keys = append(keys, fmt.Sprintf("'%c'", k))
155+ }
156+ sort.Strings(keys)
149157
150- // Track pressed key for release matching
158+ accentDetectionScript = fmt.Sprintf(`(function() {
159+ 'use strict';
160+ const accentKeys = new Set([%s]);
151161 let pressedKey = null;
152162
153- // Check if the event target is an editable element (input, textarea, contenteditable)
154163 function isEditableTarget(target) {
155164 const el = target instanceof Element ? target : null;
156165 if (!el) return false;
@@ -158,29 +167,20 @@ const accentDetectionScript = `(function() {
158167 if (el.closest('textarea')) return true;
159168 const input = el.closest('input');
160169 if (!input) return false;
161- // Exclude non-text input types
162170 return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type);
163171 }
164172
165173 document.addEventListener('keydown', function(e) {
166174 if (!isEditableTarget(e.target)) return;
167-
168- // Only handle a-z keys, with optional Shift
169- // Skip if Ctrl or Alt are held (those are shortcuts)
170175 if (e.ctrlKey || e.altKey || e.metaKey) return;
171-
172- // Only single character keys a-z
173176 const key = e.key.toLowerCase();
174- if (key.length !== 1 || key < 'a' || key > 'z') return;
175-
177+ if (key.length !== 1 || !accentKeys.has(key)) return;
176178 pressedKey = key;
177-
178- // Send to Go for accent detection
179179 window.webkit.messageHandlers.dumber.postMessage({
180180 type: 'accent_key_press',
181181 payload: { char: key, shift: e.shiftKey }
182182 });
183- }, true); // capture phase to see events first
183+ }, true);
184184
185185 document.addEventListener('keyup', function(e) {
186186 if (!isEditableTarget(e.target)) return;
@@ -193,7 +193,8 @@ const accentDetectionScript = `(function() {
193193 });
194194 }
195195 }, true);
196-})();`
196+})();`, strings.Join(keys, ","))
197+}
197198
198199 // accentDetectionInjectionMode controls which frames receive the accent detection script.
199200 // Set to AllFrames so the script runs in iframes as well as the top-level document.
@@ -3,9 +3,11 @@ package webkit
3 import (3 import (
4 "context"4 "context"
5 "fmt"5 "fmt"
6+ "sort"
6 "strings"7 "strings"
7 8
8 "github.com/bnema/dumber/internal/application/port"9 "github.com/bnema/dumber/internal/application/port"
10+ "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/logging"11 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/puregotk-webkit/webkit"12 "github.com/bnema/puregotk-webkit/webkit"
11 )13 )
@@ -141,16 +143,23 @@ const autoCopySelectionScript = `(function() {
141 });143 });
142 })();`144 })();`
143 145
144-// accentDetectionScript detects keydown/keyup of a-z keys in WebView inputs and146+// accentDetectionScript is built at init from entity.AccentMap so the JS
145-// forwards them to the Go-side InsertAccentUseCase via the message bridge.147+// filter stays in sync with the Go-side accent table.
146-// Go handles timing, accent lookup, and picker display — JS only reports events.148+var accentDetectionScript string
147-const accentDetectionScript = `(function() {149+
148- 'use strict';150+func init() {
151+ // Build JS Set literal from AccentMap keys: "new Set(['a','c','e',...])"
152+ keys := make([]string, 0, len(entity.AccentMap))
153+ for k := range entity.AccentMap {
154+ keys = append(keys, fmt.Sprintf("'%c'", k))
155+ }
156+ sort.Strings(keys)
149 157
150- // Track pressed key for release matching158+ accentDetectionScript = fmt.Sprintf(`(function() {
159+ 'use strict';
160+ const accentKeys = new Set([%s]);
151 let pressedKey = null;161 let pressedKey = null;
152 162
153- // Check if the event target is an editable element (input, textarea, contenteditable)
154 function isEditableTarget(target) {163 function isEditableTarget(target) {
155 const el = target instanceof Element ? target : null;164 const el = target instanceof Element ? target : null;
156 if (!el) return false;165 if (!el) return false;
@@ -158,29 +167,20 @@ const accentDetectionScript = `(function() {
158 if (el.closest('textarea')) return true;167 if (el.closest('textarea')) return true;
159 const input = el.closest('input');168 const input = el.closest('input');
160 if (!input) return false;169 if (!input) return false;
161- // Exclude non-text input types
162 return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type);170 return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type);
163 }171 }
164 172
165 document.addEventListener('keydown', function(e) {173 document.addEventListener('keydown', function(e) {
166 if (!isEditableTarget(e.target)) return;174 if (!isEditableTarget(e.target)) return;
167-
168- // Only handle a-z keys, with optional Shift
169- // Skip if Ctrl or Alt are held (those are shortcuts)
170 if (e.ctrlKey || e.altKey || e.metaKey) return;175 if (e.ctrlKey || e.altKey || e.metaKey) return;
171-
172- // Only single character keys a-z
173 const key = e.key.toLowerCase();176 const key = e.key.toLowerCase();
174- if (key.length !== 1 || key < 'a' || key > 'z') return;177+ if (key.length !== 1 || !accentKeys.has(key)) return;
175-
176 pressedKey = key;178 pressedKey = key;
177-
178- // Send to Go for accent detection
179 window.webkit.messageHandlers.dumber.postMessage({179 window.webkit.messageHandlers.dumber.postMessage({
180 type: 'accent_key_press',180 type: 'accent_key_press',
181 payload: { char: key, shift: e.shiftKey }181 payload: { char: key, shift: e.shiftKey }
182 });182 });
183- }, true); // capture phase to see events first183+ }, true);
184 184
185 document.addEventListener('keyup', function(e) {185 document.addEventListener('keyup', function(e) {
186 if (!isEditableTarget(e.target)) return;186 if (!isEditableTarget(e.target)) return;
@@ -193,7 +193,8 @@ const accentDetectionScript = `(function() {
193 });193 });
194 }194 }
195 }, true);195 }, true);
196-})();`196+})();`, strings.Join(keys, ","))
197+}
197 198
198 // accentDetectionInjectionMode controls which frames receive the accent detection script.199 // accentDetectionInjectionMode controls which frames receive the accent detection script.
199 // Set to AllFrames so the script runs in iframes as well as the top-level document.200 // Set to AllFrames so the script runs in iframes as well as the top-level document.

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