detached 65e5beb

fix(selection): show the rec mode badge in the neutral tag style

commit
65e5beb177aa2968d938e4f2be2c264414c16ce6
author
brice · · 2026-09-30 16:06 UTC
parent
1805145
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +10 -20
M README.md +1 -1
@@ -15,7 +15,7 @@ make bin
1515 ./bin/nefercap status
1616 ```
1717
18-`nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A badge at the top left of every overlay always shows the mode: `SHOT`, or a red `● REC`.
18+`nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A neutral badge at the top left of every overlay always shows the mode: `SHOT` or `REC`.
1919
2020 The selector covers the connected outputs, up to nine:
2121
@@ -15,7 +15,7 @@ make bin
15 ./bin/nefercap status15 ./bin/nefercap status
16 ```16 ```
17 17
18-`nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A badge at the top left of every overlay always shows the mode: `SHOT`, or a red `● REC`.18+`nefercap` without a command is all-in-one: it stops an active recording, otherwise opens the selector in shot mode. **Tab** switches shot ⇄ rec and keeps the chosen region, monitor or workspace; Enter or a click confirms in the current mode. It takes no flags except `-debug` and reads its settings from the config file. `shot` and `rec` keep a fixed mode (no Tab) and accept `-file`/`-clipboard` (shot) to override the config per run. A neutral badge at the top left of every overlay always shows the mode: `SHOT` or `REC`.
19 19
20 The selector covers the connected outputs, up to nine:20 The selector covers the connected outputs, up to nine:
21 21
M internal/adapters/selection/mode_test.go +3 -3
@@ -29,7 +29,7 @@ func TestTabTogglesModeKeepingTarget(t *testing.T) {
2929 assert.Equal(t, ports.Record, m.mode)
3030 assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept")
3131 assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText)
32- assert.Equal(t, "● REC", badgeText(m.mode))
32+ assert.Equal(t, "REC", badgeText(m.mode))
3333 m.input(key("Tab"))
3434 assert.Equal(t, ports.Screenshot, m.mode)
3535 assert.Contains(t, m.footerText, "Enter capture monitor")
@@ -70,8 +70,8 @@ func TestTabSharedAcrossOverlaysAndResult(t *testing.T) {
7070
7171 func TestBadgeClassAndText(t *testing.T) {
7272 assert.Equal(t, "SHOT", badgeText(ports.Screenshot))
73- assert.Equal(t, "● REC", badgeText(ports.Record))
74- assert.Contains(t, styleSheet, "box.rec { background-color: #e5484d;")
73+ assert.Equal(t, "REC", badgeText(ports.Record))
74+ assert.NotContains(t, styleSheet, "box.rec", "the rec badge keeps the neutral tag style")
7575 }
7676
7777 func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) {
@@ -29,7 +29,7 @@ func TestTabTogglesModeKeepingTarget(t *testing.T) {
29 assert.Equal(t, ports.Record, m.mode)29 assert.Equal(t, ports.Record, m.mode)
30 assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept")30 assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept")
31 assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText)31 assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText)
32- assert.Equal(t, "● REC", badgeText(m.mode))32+ assert.Equal(t, "REC", badgeText(m.mode))
33 m.input(key("Tab"))33 m.input(key("Tab"))
34 assert.Equal(t, ports.Screenshot, m.mode)34 assert.Equal(t, ports.Screenshot, m.mode)
35 assert.Contains(t, m.footerText, "Enter capture monitor")35 assert.Contains(t, m.footerText, "Enter capture monitor")
@@ -70,8 +70,8 @@ func TestTabSharedAcrossOverlaysAndResult(t *testing.T) {
70 70
71 func TestBadgeClassAndText(t *testing.T) {71 func TestBadgeClassAndText(t *testing.T) {
72 assert.Equal(t, "SHOT", badgeText(ports.Screenshot))72 assert.Equal(t, "SHOT", badgeText(ports.Screenshot))
73- assert.Equal(t, "● REC", badgeText(ports.Record))73+ assert.Equal(t, "REC", badgeText(ports.Record))
74- assert.Contains(t, styleSheet, "box.rec { background-color: #e5484d;")74+ assert.NotContains(t, styleSheet, "box.rec", "the rec badge keeps the neutral tag style")
75 }75 }
76 76
77 func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) {77 func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) {
M internal/adapters/selection/scene.go +3 -2
@@ -61,10 +61,11 @@ func footerText(mode ports.Mode, kind core.PickKind, workspace, toggle bool) str
6161 return b.String()
6262 }
6363
64-// badgeText is the always-visible mode badge; recBadge is styled red.
64+// badgeText is the always-visible mode badge. Both modes share the neutral
65+// tag style: a red badge would read as an active recording.
6566 func badgeText(mode ports.Mode) string {
6667 if mode == ports.Record {
67- return "● REC"
68+ return "REC"
6869 }
6970 return "SHOT"
7071 }
@@ -61,10 +61,11 @@ func footerText(mode ports.Mode, kind core.PickKind, workspace, toggle bool) str
61 return b.String()61 return b.String()
62 }62 }
63 63
64-// badgeText is the always-visible mode badge; recBadge is styled red.64+// badgeText is the always-visible mode badge. Both modes share the neutral
65+// tag style: a red badge would read as an active recording.
65 func badgeText(mode ports.Mode) string {66 func badgeText(mode ports.Mode) string {
66 if mode == ports.Record {67 if mode == ports.Record {
67- return "● REC"68+ return "REC"
68 }69 }
69 return "SHOT"70 return "SHOT"
70 }71 }
M internal/adapters/selection/style.css +0 -2
@@ -13,5 +13,3 @@ box.tag {
1313 background-color: rgba(13, 16, 20, 0.92); color: #e6edf3;
1414 font-family: monospace; font-size: 1rem; text-align: center;
1515 }
16-/* Recording mode badge: the HUD's red. */
17-box.rec { background-color: #e5484d; color: #ffffff; }
@@ -13,5 +13,3 @@ box.tag {
13 background-color: rgba(13, 16, 20, 0.92); color: #e6edf3;13 background-color: rgba(13, 16, 20, 0.92); color: #e6edf3;
14 font-family: monospace; font-size: 1rem; text-align: center;14 font-family: monospace; font-size: 1rem; text-align: center;
15 }15 }
16-/* Recording mode badge: the HUD's red. */
17-box.rec { background-color: #e5484d; color: #ffffff; }
M internal/adapters/selection/view.go +3 -12
@@ -1,10 +1,6 @@
11 package selection
22
3-import (
4- "github.com/bnema/nefergui"
5-
6- "github.com/bnema/nefercap/internal/ports"
7-)
3+import "github.com/bnema/nefergui"
84
95 // view draws the scene. Geometry goes through Node.Rect, so no CSS strings
106 // are built per frame; classes come from the embedded stylesheet. Boxes with no
@@ -28,13 +24,8 @@ func view(f *nefergui.Frame, m *model) {
2824 if n := tag(st, "label", s.label); n.ok {
2925 n.node.Text(m.label.text)
3026 }
31- if s.badge.w > 0 && s.badge.h > 0 {
32- class := "tag"
33- if m.mode == ports.Record {
34- class = "rec"
35- }
36- st.Box(nefergui.Key("badge"), nefergui.Class("tag"), nefergui.Class(class)).
37- Rect(s.badge.x, s.badge.y, s.badge.w, s.badge.h).Text(badgeText(m.mode))
27+ if n := tag(st, "badge", s.badge); n.ok {
28+ n.node.Text(badgeText(m.mode))
3829 }
3930 if n := tag(st, "header", s.header); n.ok {
4031 n.node.Text(m.header)
@@ -1,10 +1,6 @@
1 package selection1 package selection
2 2
3-import (3+import "github.com/bnema/nefergui"
4- "github.com/bnema/nefergui"
5-
6- "github.com/bnema/nefercap/internal/ports"
7-)
8 4
9 // view draws the scene. Geometry goes through Node.Rect, so no CSS strings5 // view draws the scene. Geometry goes through Node.Rect, so no CSS strings
10 // are built per frame; classes come from the embedded stylesheet. Boxes with no6 // are built per frame; classes come from the embedded stylesheet. Boxes with no
@@ -28,13 +24,8 @@ func view(f *nefergui.Frame, m *model) {
28 if n := tag(st, "label", s.label); n.ok {24 if n := tag(st, "label", s.label); n.ok {
29 n.node.Text(m.label.text)25 n.node.Text(m.label.text)
30 }26 }
31- if s.badge.w > 0 && s.badge.h > 0 {27+ if n := tag(st, "badge", s.badge); n.ok {
32- class := "tag"28+ n.node.Text(badgeText(m.mode))
33- if m.mode == ports.Record {
34- class = "rec"
35- }
36- st.Box(nefergui.Key("badge"), nefergui.Class("tag"), nefergui.Class(class)).
37- Rect(s.badge.x, s.badge.y, s.badge.w, s.badge.h).Text(badgeText(m.mode))
38 }29 }
39 if n := tag(st, "header", s.header); n.ok {30 if n := tag(st, "header", s.header); n.ok {
40 n.node.Text(m.header)31 n.node.Text(m.header)

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 2

Tags 9

↑↓ moveenter openesc close