detached 412bdb0

fix(config): warn instead of panicking on an app-id that breaks its anchoring

commit
412bdb00043b8d1ae36e0a56b417112fbc094a51
author
brice · · 2026-10-08 19:54 UTC
parent
8f14070
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +8 -1
M internal/adapters/config/config.go +6 -1
@@ -1035,7 +1035,12 @@ func setRule(r *ports.WindowRule, field, v string) error {
10351035 return fmt.Errorf("invalid regular expression: %v", err)
10361036 }
10371037 // The whole app ID must match: "steam" does not match "steam_app_1".
1038- r.AppID = regexp.MustCompile("^(?:" + v + ")$")
1038+ // A value can still break the wrapping ("\Qsteam" quotes the ")").
1039+ re, err := regexp.Compile("^(?:" + v + ")$")
1040+ if err != nil {
1041+ return fmt.Errorf("invalid regular expression: %v", err)
1042+ }
1043+ r.AppID = re
10391044 case "floating":
10401045 b, err := onOff(v)
10411046 if err != nil {
@@ -1035,7 +1035,12 @@ func setRule(r *ports.WindowRule, field, v string) error {
1035 return fmt.Errorf("invalid regular expression: %v", err)1035 return fmt.Errorf("invalid regular expression: %v", err)
1036 }1036 }
1037 // The whole app ID must match: "steam" does not match "steam_app_1".1037 // The whole app ID must match: "steam" does not match "steam_app_1".
1038- r.AppID = regexp.MustCompile("^(?:" + v + ")$")1038+ // A value can still break the wrapping ("\Qsteam" quotes the ")").
1039+ re, err := regexp.Compile("^(?:" + v + ")$")
1040+ if err != nil {
1041+ return fmt.Errorf("invalid regular expression: %v", err)
1042+ }
1043+ r.AppID = re
1039 case "floating":1044 case "floating":
1040 b, err := onOff(v)1045 b, err := onOff(v)
1041 if err != nil {1046 if err != nil {
M internal/adapters/config/rules_test.go +2 -0
@@ -61,6 +61,8 @@ func TestRuleWarnings(t *testing.T) {
6161 {"bad regex", "rule.a.app-id = (\n", "rule.a.app-id: invalid regular expression", 1, 0},
6262 // Closing the anchoring group would match any app ID.
6363 {"anchor breakout", "rule.a.app-id = steam)|(.*\n", "rule.a.app-id: invalid regular expression", 1, 0},
64+ // Quoting the anchoring group's ")" must warn, not panic.
65+ {"quoted anchor", "rule.a.app-id = \\Qsteam\n", "rule.a.app-id: invalid regular expression", 1, 0},
6466 {"missing app-id", "rule.a.floating = on\n", "rule.a: no app-id, rule ignored", 1, 0},
6567 {"unknown field", "rule.a.app-id = foo\nrule.a.title = x\n", "rule.a.title: unknown key", 2, 1},
6668 {"bad floating", "rule.a.app-id = foo\nrule.a.floating = maybe\n", "rule.a.floating: must be on or off", 2, 0},
@@ -61,6 +61,8 @@ func TestRuleWarnings(t *testing.T) {
61 {"bad regex", "rule.a.app-id = (\n", "rule.a.app-id: invalid regular expression", 1, 0},61 {"bad regex", "rule.a.app-id = (\n", "rule.a.app-id: invalid regular expression", 1, 0},
62 // Closing the anchoring group would match any app ID.62 // Closing the anchoring group would match any app ID.
63 {"anchor breakout", "rule.a.app-id = steam)|(.*\n", "rule.a.app-id: invalid regular expression", 1, 0},63 {"anchor breakout", "rule.a.app-id = steam)|(.*\n", "rule.a.app-id: invalid regular expression", 1, 0},
64+ // Quoting the anchoring group's ")" must warn, not panic.
65+ {"quoted anchor", "rule.a.app-id = \\Qsteam\n", "rule.a.app-id: invalid regular expression", 1, 0},
64 {"missing app-id", "rule.a.floating = on\n", "rule.a: no app-id, rule ignored", 1, 0},66 {"missing app-id", "rule.a.floating = on\n", "rule.a: no app-id, rule ignored", 1, 0},
65 {"unknown field", "rule.a.app-id = foo\nrule.a.title = x\n", "rule.a.title: unknown key", 2, 1},67 {"unknown field", "rule.a.app-id = foo\nrule.a.title = x\n", "rule.a.title: unknown key", 2, 1},
66 {"bad floating", "rule.a.app-id = foo\nrule.a.floating = maybe\n", "rule.a.floating: must be on or off", 2, 0},68 {"bad floating", "rule.a.app-id = foo\nrule.a.floating = maybe\n", "rule.a.floating: must be on or off", 2, 0},

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 23

Tags 10

↑↓ moveenter openesc close