v0.2.0 80c61e3

AGENTS.md RawHistoryFolder

vev agent guide

Project

vev is a Linux terminal multiplexer written in Go. It has a per-user daemon, a thin client, server-side rendering, and minimal socket/SSH diffs.

Commands

go build -o vev .
make test
go test ./internal/usecase/daemon -race -run TestName
make lint
make mocks
go test ./pkg/renderer ./pkg/vt ./internal/adapters/ipc ./internal/usecase/daemon -run '^$' -bench=. -benchmem
  • Format with goimports.
  • Install dev tools once with go install golang.org/x/tools/cmd/goimports@latest and go install github.com/vektra/mockery/v3@v3.7.1.
  • Use Mockery v3 consistently across Go projects.
  • make test runs go test ./... -race.
  • make lint checks goimports -l, then go vet.
  • Enable daemon pprof with VEV_PPROF_ADDR=127.0.0.1:6060 vev --daemon.
  • Set VEV_LOG=debug|warn|error to change log verbosity (default info).
  • Logs live in $XDG_STATE_HOME/vev, or ~/.local/state/vev when unset.
  • Use feature worktrees under .worktrees/<branch-name>. Remove the worktree once its branch is merged or abandoned (git worktree remove <path>).
  • Create vev-owned private directories with pkg/safedir.EnsurePrivate, not os.MkdirAll.

Architecture rules

Hexagonal boundaries are enforced by boundary_test.go.

  • pkg/ is reusable and must not import internal/.
  • internal/usecase/ must not import internal/adapters/.
  • Usecases depend on internal/ports and internal/domain.

Layer map:

  • main.go → internal/app: CLI parsing, wiring, daemon startup, hidden subcommands.
  • internal/ports: interfaces and wire protocol.
  • internal/usecase/daemon: sessions, tabs, panes, VT screens, renderer shadows, daemon features.
  • internal/usecase/client: raw-mode thin client; writes output bytes verbatim and interprets nothing.
  • internal/adapters: IPC, SSH stdio, PTY, terminal, and clock implementations.

Before touching daemon teardown paths, read the lock-ordering notes at the top of internal/usecase/daemon/client.go.

Ports and mocks

  • Define cross-layer interfaces only in internal/ports.
  • Adapters implement ports; usecases and internal/app consume ports.
  • Tests should prefer generated portsmocks.MockX with .EXPECT().
  • Hand-written fakes are okay only when mocks make the test harder to understand.
  • After changing a port, run make mocks and commit the generated updates.
  • Time-dependent daemon code must use ports.Clock/ports.Timer, not direct wall-clock APIs.

Tests

  • Write Go tests as table-driven tests by default.
  • Keep wire tests byte-for-byte and include truncated-prefix and trailing-garbage coverage.
  • Prefer fake clocks over sleeps.

Wire protocol

Wire payload types/codecs live in internal/ports/frame.go and internal/ports/wire.go. Connection framing lives in internal/adapters/ipc/transport.go.

  • IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.
  • Client message types occupy 1–13 (MsgOutputResetRequest is 13) and 15 (MsgRemotePreviewRequest); server types occupy 16–23 (MsgNavigationAction is 23) and 25–30 (MsgAttachTarget, MsgRemotePreviewResponse, MsgCommittedRouteIdentity, MsgRecentRouteSnapshot, MsgNavigateRecentRoute, and MsgRouteNavigationFailure). Types 14 and 24 remain reserved.
  • Version negotiation requires strict equality.
  • Hello.Version and CommandRequest.Version must stay first so their version peekers work.
  • Bump ProtocolVersion for any message layout change.

Session flow

  • Ephemeral numbered sessions survive detach while the daemon retains them, but are not persisted.
  • Named sessions survive headless and persist across daemon restarts.
  • The daemon starts on first use and exits when the last session ends.
  • Each connection has a 15-second handshake budget from connect through the initial committed publication.
  • Local and remote attach use the same Hello/Welcome session protocol, then pump Input/Resize and Output frames over a byte-only Transport.
  • Remote attach opens a direct connection to the selected daemon over UDP by default; VEV_REMOTE_TRANSPORT=stdio explicitly selects an SSH-only carriage.
  • A session owns shared PTYs, VT state, tabs, panes, PTY content geometry selected by the latest valid attachment claim, and ordered mutations. Each attachment owns its window/view, copy and overlay state, rendering/output state, and reconnect lifecycle; when the latest claimant detaches, the most recently claimed remaining attachment becomes authoritative.
  • Command requests have a 10-second result deadline and are tracked per connection.
source
1# vev agent guide
2
3## Project
4
5vev is a Linux terminal multiplexer written in Go. It has a per-user daemon, a thin client, server-side rendering, and minimal socket/SSH diffs.
6
7## Commands
8
9```sh
10go build -o vev .
11make test
12go test ./internal/usecase/daemon -race -run TestName
13make lint
14make mocks
15go test ./pkg/renderer ./pkg/vt ./internal/adapters/ipc ./internal/usecase/daemon -run '^$' -bench=. -benchmem
16```
17
18- Format with **goimports**.
19- Install dev tools once with `go install golang.org/x/tools/cmd/goimports@latest` and `go install github.com/vektra/mockery/v3@v3.7.1`.
20- Use Mockery v3 consistently across Go projects.
21- `make test` runs `go test ./... -race`.
22- `make lint` checks `goimports -l`, then `go vet`.
23- Enable daemon pprof with `VEV_PPROF_ADDR=127.0.0.1:6060 vev --daemon`.
24- Set `VEV_LOG=debug|warn|error` to change log verbosity (default info).
25- Logs live in `$XDG_STATE_HOME/vev`, or `~/.local/state/vev` when unset.
26- Use feature worktrees under `.worktrees/<branch-name>`. Remove the worktree once its branch is merged or abandoned (`git worktree remove <path>`).
27- Create vev-owned private directories with `pkg/safedir.EnsurePrivate`, not `os.MkdirAll`.
28
29## Architecture rules
30
31Hexagonal boundaries are enforced by `boundary_test.go`.
32
33- `pkg/` is reusable and must not import `internal/`.
34- `internal/usecase/` must not import `internal/adapters/`.
35- Usecases depend on `internal/ports` and `internal/domain`.
36
37Layer map:
38
39- `main.go` → `internal/app`: CLI parsing, wiring, daemon startup, hidden subcommands.
40- `internal/ports`: interfaces and wire protocol.
41- `internal/usecase/daemon`: sessions, tabs, panes, VT screens, renderer shadows, daemon features.
42- `internal/usecase/client`: raw-mode thin client; writes output bytes verbatim and interprets nothing.
43- `internal/adapters`: IPC, SSH stdio, PTY, terminal, and clock implementations.
44
45Before touching daemon teardown paths, read the lock-ordering notes at the top of `internal/usecase/daemon/client.go`.
46
47## Ports and mocks
48
49- Define cross-layer interfaces only in `internal/ports`.
50- Adapters implement ports; usecases and `internal/app` consume ports.
51- Tests should prefer generated `portsmocks.MockX` with `.EXPECT()`.
52- Hand-written fakes are okay only when mocks make the test harder to understand.
53- After changing a port, run `make mocks` and commit the generated updates.
54- Time-dependent daemon code must use `ports.Clock`/`ports.Timer`, not direct wall-clock APIs.
55
56## Tests
57
58- Write Go tests as table-driven tests by default.
59- Keep wire tests byte-for-byte and include truncated-prefix and trailing-garbage coverage.
60- Prefer fake clocks over sleeps.
61
62## Wire protocol
63
64Wire payload types/codecs live in `internal/ports/frame.go` and `internal/ports/wire.go`. Connection framing lives in `internal/adapters/ipc/transport.go`.
65
66- IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.
67- Client message types occupy `1–13` (`MsgOutputResetRequest` is `13`) and `15` (`MsgRemotePreviewRequest`); server types occupy `16–23` (`MsgNavigationAction` is `23`) and `25–30` (`MsgAttachTarget`, `MsgRemotePreviewResponse`, `MsgCommittedRouteIdentity`, `MsgRecentRouteSnapshot`, `MsgNavigateRecentRoute`, and `MsgRouteNavigationFailure`). Types `14` and `24` remain reserved.
68- Version negotiation requires strict equality.
69- `Hello.Version` and `CommandRequest.Version` must stay first so their version peekers work.
70- Bump `ProtocolVersion` for any message layout change.
71
72## Session flow
73
74- Ephemeral numbered sessions survive detach while the daemon retains them, but are not persisted.
75- Named sessions survive headless and persist across daemon restarts.
76- The daemon starts on first use and exits when the last session ends.
77- Each connection has a 15-second handshake budget from connect through the initial committed publication.
78- Local and remote attach use the same `Hello`/`Welcome` session protocol, then pump `Input`/`Resize` and `Output` frames over a byte-only Transport.
79- Remote attach opens a direct connection to the selected daemon over UDP by default; `VEV_REMOTE_TRANSPORT=stdio` explicitly selects an SSH-only carriage.
80- A session owns shared PTYs, VT state, tabs, panes, PTY content geometry selected by the latest valid attachment claim, and ordered mutations. Each attachment owns its window/view, copy and overlay state, rendering/output state, and reconnect lifecycle; when the latest claimant detaches, the most recently claimed remaining attachment becomes authoritative.
81- Command requests have a 10-second result deadline and are tracked per connection.

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