detached 37d29ee

refactor(ports): split capability contracts

commit
37d29ee20a6316accc9b22de2bdf74dd8f6d1632
author
brice · · 2026-07-14 19:21 UTC
parent
201f7b0
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +180 -155
A internal/ports/clipboard.go +23 -0
@@ -0,0 +1,23 @@
1+package ports
2+
3+import (
4+ "context"
5+ "errors"
6+)
7+
8+// ErrNoClipboardImage is returned by ClipboardReader.ReadImage when the
9+// clipboard holds no image (or an unsupported type). Callers fall back to
10+// forwarding whatever keystroke triggered the read.
11+var ErrNoClipboardImage = errors.New("ports: no image on clipboard")
12+
13+// ClipboardReader reads an image off the CLIENT-side system clipboard, for
14+// forwarding to a remote session's focused pane. Implementations live in
15+// internal/adapters (e.g. a wl-paste-backed one for Wayland); usecases only
16+// see this interface. See
17+// docs/superpowers/specs/2026-07-04-clipboard-image-transfer-design.md.
18+type ClipboardReader interface {
19+ // ReadImage returns the clipboard's image bytes and MIME type. It returns
20+ // ErrNoClipboardImage when the clipboard holds no image (or an
21+ // unsupported type).
22+ ReadImage(ctx context.Context) (mime string, data []byte, err error)
23+}
@@ -0,0 +1,23 @@
1+package ports
2+
3+import (
4+ "context"
5+ "errors"
6+)
7+
8+// ErrNoClipboardImage is returned by ClipboardReader.ReadImage when the
9+// clipboard holds no image (or an unsupported type). Callers fall back to
10+// forwarding whatever keystroke triggered the read.
11+var ErrNoClipboardImage = errors.New("ports: no image on clipboard")
12+
13+// ClipboardReader reads an image off the CLIENT-side system clipboard, for
14+// forwarding to a remote session's focused pane. Implementations live in
15+// internal/adapters (e.g. a wl-paste-backed one for Wayland); usecases only
16+// see this interface. See
17+// docs/superpowers/specs/2026-07-04-clipboard-image-transfer-design.md.
18+type ClipboardReader interface {
19+ // ReadImage returns the clipboard's image bytes and MIME type. It returns
20+ // ErrNoClipboardImage when the clipboard holds no image (or an
21+ // unsupported type).
22+ ReadImage(ctx context.Context) (mime string, data []byte, err error)
23+}
A internal/ports/clock.go +16 -0
@@ -0,0 +1,16 @@
1+package ports
2+
3+import "time"
4+
5+// Clock abstracts time so usecases can be tested without real delays.
6+type Clock interface {
7+ Now() time.Time
8+ NewTimer(d time.Duration) Timer
9+}
10+
11+// Timer abstracts a cancellable, resettable time.Timer.
12+type Timer interface {
13+ C() <-chan time.Time
14+ Reset(d time.Duration) bool
15+ Stop() bool
16+}
@@ -0,0 +1,16 @@
1+package ports
2+
3+import "time"
4+
5+// Clock abstracts time so usecases can be tested without real delays.
6+type Clock interface {
7+ Now() time.Time
8+ NewTimer(d time.Duration) Timer
9+}
10+
11+// Timer abstracts a cancellable, resettable time.Timer.
12+type Timer interface {
13+ C() <-chan time.Time
14+ Reset(d time.Duration) bool
15+ Stop() bool
16+}
D internal/ports/ports.go +0 -155
@@ -1,155 +0,0 @@
1-package ports
2-
3-import (
4- "context"
5- "errors"
6- "io"
7- "time"
8-
9- "github.com/bnema/vev/internal/domain"
10-)
11-
12-// ErrNoClipboardImage is returned by ClipboardReader.ReadImage when the
13-// clipboard holds no image (or an unsupported type). Callers fall back to
14-// forwarding whatever keystroke triggered the read.
15-var ErrNoClipboardImage = errors.New("ports: no image on clipboard")
16-
17-// PTY is a running pseudo-terminal-backed child process.
18-type PTY interface {
19- io.ReadWriteCloser // Read = child output, Write = child input
20- Resize(sz domain.Size) error
21- Pid() int
22- ForegroundPgid() (int, error)
23-}
24-
25-// PTYFactory creates PTYs by spawning a command attached to a new pseudo-terminal.
26-type PTYFactory interface {
27- // Open creates a PTY while honoring ctx cancellation. Implementations must
28- // return promptly once ctx is cancelled and must not leave a child running.
29- Open(ctx context.Context, cmd string, args []string, env []string, dir string, sz domain.Size) (PTY, error)
30-}
31-
32-// Terminal is the CLIENT-side controlling terminal.
33-type Terminal interface {
34- EnterRaw() (restore func() error, err error)
35- Size() (domain.Size, error)
36- ResizeEvents() <-chan domain.Size
37- QueryColors() error
38- In() io.Reader
39- Out() io.Writer
40- Flush() error
41-}
42-
43-// Transport is a framed message channel over a single connection. Close must
44-// be safe to call concurrently with Send and Recv, and must unblock active
45-// Send and Recv calls.
46-type Transport interface {
47- Send(Frame) error
48- Recv() (Frame, error) // blocking; io.EOF on close
49- Close() error
50-}
51-
52-// DatagramTransport marks transports backed by a datagram link. It lets
53-// usecases negotiate a conservative output window without importing adapters.
54-type DatagramTransport interface {
55- Transport
56- DatagramTransport()
57-}
58-
59-// AsyncTransport accepts frames for ordered background transmission. SendAsync
60-// returns once the adapter owns the frame; Send retains its synchronous wire-
61-// attempt contract. Daemon paint output may use this capability to pipeline.
62-type AsyncTransport interface {
63- SendAsync(Frame) error
64-}
65-
66-// OwnedSynchronousTransport owns the complete bounded synchronous operation,
67-// including adapter queues, pacing, write deadlines, and close cancellation.
68-type OwnedSynchronousTransport interface {
69- SendSynchronous(Frame) error
70-}
71-
72-// Dialer establishes outbound Transport connections.
73-type Dialer interface {
74- Dial(ctx context.Context) (Transport, error)
75-}
76-
77-// Listener accepts incoming Transport connections.
78-type Listener interface {
79- Accept() (Transport, error)
80- Close() error
81- Addr() string
82-}
83-
84-// Store is a small byte-key/value persistence port.
85-//
86-// Implementations may buffer writes; Sync is the durability barrier.
87-type Store interface {
88- Get(key []byte) ([]byte, bool)
89- Set(key, val []byte) error
90- Delete(key []byte) error
91- // Range iterates key/value pairs; fn returning false stops iteration early.
92- Range(fn func(k, v []byte) bool)
93- Sync() error
94- Close() error
95-}
96-
97-// SnapshotBlob is a durable named session snapshot payload.
98-type SnapshotBlob struct {
99- Name string
100- Data []byte
101-}
102-
103-// SnapshotStore persists encoded session snapshots by name.
104-type SnapshotStore interface {
105- Write(name string, data []byte) error
106- Load() ([]SnapshotBlob, error)
107- Delete(name string) error
108-}
109-
110-// Clock abstracts time so usecases can be tested without real delays.
111-type Clock interface {
112- Now() time.Time
113- NewTimer(d time.Duration) Timer
114-}
115-
116-// Timer abstracts a cancellable, resettable time.Timer.
117-type Timer interface {
118- C() <-chan time.Time
119- Reset(d time.Duration) bool
120- Stop() bool
121-}
122-
123-// CommandSpec describes a bounded shell command execution request.
124-type CommandSpec struct {
125- Command string
126- Env []string
127- Timeout time.Duration
128- StdoutLimit int
129-}
130-
131-// ShellCommandRunner runs shell commands and returns bounded stdout bytes.
132-type ShellCommandRunner interface {
133- Run(ctx context.Context, spec CommandSpec) ([]byte, error)
134-}
135-
136-// ProcessInspector reads process metadata for daemon snapshot/persistence use.
137-// Implementations may live in platform packages; usecases depend on this port.
138-type ProcessInspector interface {
139- Cwd(pid int) (string, error)
140- Comm(pid int) (string, error)
141- Argv(pid int) ([]string, error)
142- GroupArgv(pgid int, shellPid int) ([]string, error)
143-}
144-
145-// ClipboardReader reads an image off the CLIENT-side system clipboard, for
146-// forwarding to a remote session's focused pane. Implementations live in
147-// internal/adapters (e.g. a wl-paste-backed one for Wayland); usecases only
148-// see this interface. See
149-// docs/superpowers/specs/2026-07-04-clipboard-image-transfer-design.md.
150-type ClipboardReader interface {
151- // ReadImage returns the clipboard's image bytes and MIME type. It returns
152- // ErrNoClipboardImage when the clipboard holds no image (or an
153- // unsupported type).
154- ReadImage(ctx context.Context) (mime string, data []byte, err error)
155-}
@@ -1,155 +0,0 @@
1-package ports
2-
3-import (
4- "context"
5- "errors"
6- "io"
7- "time"
8-
9- "github.com/bnema/vev/internal/domain"
10-)
11-
12-// ErrNoClipboardImage is returned by ClipboardReader.ReadImage when the
13-// clipboard holds no image (or an unsupported type). Callers fall back to
14-// forwarding whatever keystroke triggered the read.
15-var ErrNoClipboardImage = errors.New("ports: no image on clipboard")
16-
17-// PTY is a running pseudo-terminal-backed child process.
18-type PTY interface {
19- io.ReadWriteCloser // Read = child output, Write = child input
20- Resize(sz domain.Size) error
21- Pid() int
22- ForegroundPgid() (int, error)
23-}
24-
25-// PTYFactory creates PTYs by spawning a command attached to a new pseudo-terminal.
26-type PTYFactory interface {
27- // Open creates a PTY while honoring ctx cancellation. Implementations must
28- // return promptly once ctx is cancelled and must not leave a child running.
29- Open(ctx context.Context, cmd string, args []string, env []string, dir string, sz domain.Size) (PTY, error)
30-}
31-
32-// Terminal is the CLIENT-side controlling terminal.
33-type Terminal interface {
34- EnterRaw() (restore func() error, err error)
35- Size() (domain.Size, error)
36- ResizeEvents() <-chan domain.Size
37- QueryColors() error
38- In() io.Reader
39- Out() io.Writer
40- Flush() error
41-}
42-
43-// Transport is a framed message channel over a single connection. Close must
44-// be safe to call concurrently with Send and Recv, and must unblock active
45-// Send and Recv calls.
46-type Transport interface {
47- Send(Frame) error
48- Recv() (Frame, error) // blocking; io.EOF on close
49- Close() error
50-}
51-
52-// DatagramTransport marks transports backed by a datagram link. It lets
53-// usecases negotiate a conservative output window without importing adapters.
54-type DatagramTransport interface {
55- Transport
56- DatagramTransport()
57-}
58-
59-// AsyncTransport accepts frames for ordered background transmission. SendAsync
60-// returns once the adapter owns the frame; Send retains its synchronous wire-
61-// attempt contract. Daemon paint output may use this capability to pipeline.
62-type AsyncTransport interface {
63- SendAsync(Frame) error
64-}
65-
66-// OwnedSynchronousTransport owns the complete bounded synchronous operation,
67-// including adapter queues, pacing, write deadlines, and close cancellation.
68-type OwnedSynchronousTransport interface {
69- SendSynchronous(Frame) error
70-}
71-
72-// Dialer establishes outbound Transport connections.
73-type Dialer interface {
74- Dial(ctx context.Context) (Transport, error)
75-}
76-
77-// Listener accepts incoming Transport connections.
78-type Listener interface {
79- Accept() (Transport, error)
80- Close() error
81- Addr() string
82-}
83-
84-// Store is a small byte-key/value persistence port.
85-//
86-// Implementations may buffer writes; Sync is the durability barrier.
87-type Store interface {
88- Get(key []byte) ([]byte, bool)
89- Set(key, val []byte) error
90- Delete(key []byte) error
91- // Range iterates key/value pairs; fn returning false stops iteration early.
92- Range(fn func(k, v []byte) bool)
93- Sync() error
94- Close() error
95-}
96-
97-// SnapshotBlob is a durable named session snapshot payload.
98-type SnapshotBlob struct {
99- Name string
100- Data []byte
101-}
102-
103-// SnapshotStore persists encoded session snapshots by name.
104-type SnapshotStore interface {
105- Write(name string, data []byte) error
106- Load() ([]SnapshotBlob, error)
107- Delete(name string) error
108-}
109-
110-// Clock abstracts time so usecases can be tested without real delays.
111-type Clock interface {
112- Now() time.Time
113- NewTimer(d time.Duration) Timer
114-}
115-
116-// Timer abstracts a cancellable, resettable time.Timer.
117-type Timer interface {
118- C() <-chan time.Time
119- Reset(d time.Duration) bool
120- Stop() bool
121-}
122-
123-// CommandSpec describes a bounded shell command execution request.
124-type CommandSpec struct {
125- Command string
126- Env []string
127- Timeout time.Duration
128- StdoutLimit int
129-}
130-
131-// ShellCommandRunner runs shell commands and returns bounded stdout bytes.
132-type ShellCommandRunner interface {
133- Run(ctx context.Context, spec CommandSpec) ([]byte, error)
134-}
135-
136-// ProcessInspector reads process metadata for daemon snapshot/persistence use.
137-// Implementations may live in platform packages; usecases depend on this port.
138-type ProcessInspector interface {
139- Cwd(pid int) (string, error)
140- Comm(pid int) (string, error)
141- Argv(pid int) ([]string, error)
142- GroupArgv(pgid int, shellPid int) ([]string, error)
143-}
144-
145-// ClipboardReader reads an image off the CLIENT-side system clipboard, for
146-// forwarding to a remote session's focused pane. Implementations live in
147-// internal/adapters (e.g. a wl-paste-backed one for Wayland); usecases only
148-// see this interface. See
149-// docs/superpowers/specs/2026-07-04-clipboard-image-transfer-design.md.
150-type ClipboardReader interface {
151- // ReadImage returns the clipboard's image bytes and MIME type. It returns
152- // ErrNoClipboardImage when the clipboard holds no image (or an
153- // unsupported type).
154- ReadImage(ctx context.Context) (mime string, data []byte, err error)
155-}
A internal/ports/process.go +10 -0
@@ -0,0 +1,10 @@
1+package ports
2+
3+// ProcessInspector reads process metadata for daemon snapshot/persistence use.
4+// Implementations may live in platform packages; usecases depend on this port.
5+type ProcessInspector interface {
6+ Cwd(pid int) (string, error)
7+ Comm(pid int) (string, error)
8+ Argv(pid int) ([]string, error)
9+ GroupArgv(pgid int, shellPid int) ([]string, error)
10+}
@@ -0,0 +1,10 @@
1+package ports
2+
3+// ProcessInspector reads process metadata for daemon snapshot/persistence use.
4+// Implementations may live in platform packages; usecases depend on this port.
5+type ProcessInspector interface {
6+ Cwd(pid int) (string, error)
7+ Comm(pid int) (string, error)
8+ Argv(pid int) ([]string, error)
9+ GroupArgv(pgid int, shellPid int) ([]string, error)
10+}
A internal/ports/pty.go +23 -0
@@ -0,0 +1,23 @@
1+package ports
2+
3+import (
4+ "context"
5+ "io"
6+
7+ "github.com/bnema/vev/internal/domain"
8+)
9+
10+// PTY is a running pseudo-terminal-backed child process.
11+type PTY interface {
12+ io.ReadWriteCloser // Read = child output, Write = child input
13+ Resize(sz domain.Size) error
14+ Pid() int
15+ ForegroundPgid() (int, error)
16+}
17+
18+// PTYFactory creates PTYs by spawning a command attached to a new pseudo-terminal.
19+type PTYFactory interface {
20+ // Open creates a PTY while honoring ctx cancellation. Implementations must
21+ // return promptly once ctx is cancelled and must not leave a child running.
22+ Open(ctx context.Context, cmd string, args []string, env []string, dir string, sz domain.Size) (PTY, error)
23+}
@@ -0,0 +1,23 @@
1+package ports
2+
3+import (
4+ "context"
5+ "io"
6+
7+ "github.com/bnema/vev/internal/domain"
8+)
9+
10+// PTY is a running pseudo-terminal-backed child process.
11+type PTY interface {
12+ io.ReadWriteCloser // Read = child output, Write = child input
13+ Resize(sz domain.Size) error
14+ Pid() int
15+ ForegroundPgid() (int, error)
16+}
17+
18+// PTYFactory creates PTYs by spawning a command attached to a new pseudo-terminal.
19+type PTYFactory interface {
20+ // Open creates a PTY while honoring ctx cancellation. Implementations must
21+ // return promptly once ctx is cancelled and must not leave a child running.
22+ Open(ctx context.Context, cmd string, args []string, env []string, dir string, sz domain.Size) (PTY, error)
23+}
A internal/ports/shell.go +19 -0
@@ -0,0 +1,19 @@
1+package ports
2+
3+import (
4+ "context"
5+ "time"
6+)
7+
8+// CommandSpec describes a bounded shell command execution request.
9+type CommandSpec struct {
10+ Command string
11+ Env []string
12+ Timeout time.Duration
13+ StdoutLimit int
14+}
15+
16+// ShellCommandRunner runs shell commands and returns bounded stdout bytes.
17+type ShellCommandRunner interface {
18+ Run(ctx context.Context, spec CommandSpec) ([]byte, error)
19+}
@@ -0,0 +1,19 @@
1+package ports
2+
3+import (
4+ "context"
5+ "time"
6+)
7+
8+// CommandSpec describes a bounded shell command execution request.
9+type CommandSpec struct {
10+ Command string
11+ Env []string
12+ Timeout time.Duration
13+ StdoutLimit int
14+}
15+
16+// ShellCommandRunner runs shell commands and returns bounded stdout bytes.
17+type ShellCommandRunner interface {
18+ Run(ctx context.Context, spec CommandSpec) ([]byte, error)
19+}
A internal/ports/store.go +27 -0
@@ -0,0 +1,27 @@
1+package ports
2+
3+// Store is a small byte-key/value persistence port.
4+//
5+// Implementations may buffer writes; Sync is the durability barrier.
6+type Store interface {
7+ Get(key []byte) ([]byte, bool)
8+ Set(key, val []byte) error
9+ Delete(key []byte) error
10+ // Range iterates key/value pairs; fn returning false stops iteration early.
11+ Range(fn func(k, v []byte) bool)
12+ Sync() error
13+ Close() error
14+}
15+
16+// SnapshotBlob is a durable named session snapshot payload.
17+type SnapshotBlob struct {
18+ Name string
19+ Data []byte
20+}
21+
22+// SnapshotStore persists encoded session snapshots by name.
23+type SnapshotStore interface {
24+ Write(name string, data []byte) error
25+ Load() ([]SnapshotBlob, error)
26+ Delete(name string) error
27+}
@@ -0,0 +1,27 @@
1+package ports
2+
3+// Store is a small byte-key/value persistence port.
4+//
5+// Implementations may buffer writes; Sync is the durability barrier.
6+type Store interface {
7+ Get(key []byte) ([]byte, bool)
8+ Set(key, val []byte) error
9+ Delete(key []byte) error
10+ // Range iterates key/value pairs; fn returning false stops iteration early.
11+ Range(fn func(k, v []byte) bool)
12+ Sync() error
13+ Close() error
14+}
15+
16+// SnapshotBlob is a durable named session snapshot payload.
17+type SnapshotBlob struct {
18+ Name string
19+ Data []byte
20+}
21+
22+// SnapshotStore persists encoded session snapshots by name.
23+type SnapshotStore interface {
24+ Write(name string, data []byte) error
25+ Load() ([]SnapshotBlob, error)
26+ Delete(name string) error
27+}
A internal/ports/terminal.go +18 -0
@@ -0,0 +1,18 @@
1+package ports
2+
3+import (
4+ "io"
5+
6+ "github.com/bnema/vev/internal/domain"
7+)
8+
9+// Terminal is the CLIENT-side controlling terminal.
10+type Terminal interface {
11+ EnterRaw() (restore func() error, err error)
12+ Size() (domain.Size, error)
13+ ResizeEvents() <-chan domain.Size
14+ QueryColors() error
15+ In() io.Reader
16+ Out() io.Writer
17+ Flush() error
18+}
@@ -0,0 +1,18 @@
1+package ports
2+
3+import (
4+ "io"
5+
6+ "github.com/bnema/vev/internal/domain"
7+)
8+
9+// Terminal is the CLIENT-side controlling terminal.
10+type Terminal interface {
11+ EnterRaw() (restore func() error, err error)
12+ Size() (domain.Size, error)
13+ ResizeEvents() <-chan domain.Size
14+ QueryColors() error
15+ In() io.Reader
16+ Out() io.Writer
17+ Flush() error
18+}
A internal/ports/transport.go +44 -0
@@ -0,0 +1,44 @@
1+package ports
2+
3+import "context"
4+
5+// Transport is a framed message channel over a single connection. Close must
6+// be safe to call concurrently with Send and Recv, and must unblock active
7+// Send and Recv calls.
8+type Transport interface {
9+ Send(Frame) error
10+ Recv() (Frame, error) // blocking; io.EOF on close
11+ Close() error
12+}
13+
14+// DatagramTransport marks transports backed by a datagram link. It lets
15+// usecases negotiate a conservative output window without importing adapters.
16+type DatagramTransport interface {
17+ Transport
18+ DatagramTransport()
19+}
20+
21+// AsyncTransport accepts frames for ordered background transmission. SendAsync
22+// returns once the adapter owns the frame; Send retains its synchronous wire-
23+// attempt contract. Daemon paint output may use this capability to pipeline.
24+type AsyncTransport interface {
25+ SendAsync(Frame) error
26+}
27+
28+// OwnedSynchronousTransport owns the complete bounded synchronous operation,
29+// including adapter queues, pacing, write deadlines, and close cancellation.
30+type OwnedSynchronousTransport interface {
31+ SendSynchronous(Frame) error
32+}
33+
34+// Dialer establishes outbound Transport connections.
35+type Dialer interface {
36+ Dial(ctx context.Context) (Transport, error)
37+}
38+
39+// Listener accepts incoming Transport connections.
40+type Listener interface {
41+ Accept() (Transport, error)
42+ Close() error
43+ Addr() string
44+}
@@ -0,0 +1,44 @@
1+package ports
2+
3+import "context"
4+
5+// Transport is a framed message channel over a single connection. Close must
6+// be safe to call concurrently with Send and Recv, and must unblock active
7+// Send and Recv calls.
8+type Transport interface {
9+ Send(Frame) error
10+ Recv() (Frame, error) // blocking; io.EOF on close
11+ Close() error
12+}
13+
14+// DatagramTransport marks transports backed by a datagram link. It lets
15+// usecases negotiate a conservative output window without importing adapters.
16+type DatagramTransport interface {
17+ Transport
18+ DatagramTransport()
19+}
20+
21+// AsyncTransport accepts frames for ordered background transmission. SendAsync
22+// returns once the adapter owns the frame; Send retains its synchronous wire-
23+// attempt contract. Daemon paint output may use this capability to pipeline.
24+type AsyncTransport interface {
25+ SendAsync(Frame) error
26+}
27+
28+// OwnedSynchronousTransport owns the complete bounded synchronous operation,
29+// including adapter queues, pacing, write deadlines, and close cancellation.
30+type OwnedSynchronousTransport interface {
31+ SendSynchronous(Frame) error
32+}
33+
34+// Dialer establishes outbound Transport connections.
35+type Dialer interface {
36+ Dial(ctx context.Context) (Transport, error)
37+}
38+
39+// Listener accepts incoming Transport connections.
40+type Listener interface {
41+ Accept() (Transport, error)
42+ Close() error
43+ Addr() string
44+}

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 12

Tags 11

↑↓ moveenter openesc close