ui.go RawHistoryFolder

1package ports
2
3import (
4	"context"
5	"errors"
6	"time"
7
8	"github.com/bnema/vev/internal/domain"
9	"github.com/bnema/vev/internal/protocol"
10)
11
12// UIPresentationStatus describes what the client is currently presenting.
13//
14// It is a closed union of exactly three presentations. Picker and Connecting
15// own no session, so their published context carries neither an actionable
16// generation nor a session identity; only Attached publishes the real action
17// generation with a validated session identity and a committed publication.
18// The former transition states are removed without alias: a reconnect is
19// Connecting, a detach is Picker, and terminating closes the UI service instead
20// of becoming a fourth persistent presentation.
21type UIPresentationStatus string
22
23const (
24	// UIStatusPicker presents the local session picker. It is ready without a
25	// broker and without a daemon.
26	UIStatusPicker UIPresentationStatus = "picker"
27	// UIStatusConnecting presents an attachment attempt that has not committed
28	// its first frame yet.
29	UIStatusConnecting UIPresentationStatus = "connecting"
30	// UIStatusAttached presents a committed attachment.
31	UIStatusAttached UIPresentationStatus = "attached"
32)
33
34// Valid reports whether s is one of the three decided presentations. The zero
35// value is not a presentation: a publication must name the state it means.
36func (s UIPresentationStatus) Valid() bool {
37	switch s {
38	case UIStatusPicker, UIStatusConnecting, UIStatusAttached:
39		return true
40	default:
41		return false
42	}
43}
44
45// UIColorKind identifies how a terminal color is represented.
46type UIColorKind uint8
47
48const (
49	UIColorDefault UIColorKind = iota
50	UIColorIndexed
51	UIColorRGB
52)
53
54// UIColor is an owned terminal color value.
55type UIColor struct {
56	Kind  UIColorKind
57	Index uint8
58	R     uint8
59	G     uint8
60	B     uint8
61}
62
63// UIStyle is the rendered style of one terminal cell.
64type UIStyle struct {
65	Foreground     UIColor
66	Background     UIColor
67	Bold           bool
68	Dim            bool
69	Italic         bool
70	Underline      bool
71	UnderlineStyle uint8
72	Blink          bool
73	Inverse        bool
74	Strikethrough  bool
75}
76
77// UICell is one row-major viewport cell. Continuation cells have Width zero.
78type UICell struct {
79	Text         string
80	Width        int
81	Continuation bool
82	Style        UIStyle
83}
84
85// UICursor is the cursor captured with a viewport revision.
86type UICursor struct {
87	Row      int
88	Column   int
89	Visible  bool
90	Style    int
91	StyleSet bool
92}
93
94// UIContext binds rendered cells to client and daemon semantic state.
95type UIContext struct {
96	AttachmentHandle string
97	Generation       uint64
98	Route            protocol.CommittedRouteIdentity
99	TabID            domain.TabStableID
100	FocusedPaneID    domain.PaneStableID
101	OutputEpoch      uint64
102	OutputState      uint64
103	ViewRevision     uint64
104	ViewPublication  uint64
105	Status           UIPresentationStatus
106}
107
108// UISnapshot is an immutable, owned viewport publication.
109type UISnapshot struct {
110	Revision          uint64
111	Context           UIContext
112	Columns           int
113	Rows              int
114	Cursor            UICursor
115	Cells             []UICell
116	AutoWrap          bool
117	ApplicationCursor bool
118}
119
120// UIErrorCode is a stable machine-readable API error value.
121type UIErrorCode string
122
123// UI error codes are stable API values shared by client ownership and adapters.
124const (
125	UIErrInvalidRequest        UIErrorCode = "invalid_request"
126	UIErrUnsupportedVersion    UIErrorCode = "unsupported_version"
127	UIErrPermissionDenied      UIErrorCode = "permission_denied"
128	UIErrStaleAttachment       UIErrorCode = "stale_attachment"
129	UIErrUnavailable           UIErrorCode = "unavailable"
130	UIErrTimeout               UIErrorCode = "timeout"
131	UIErrOutcomeUnknown        UIErrorCode = "outcome_unknown"
132	UIErrCaptureTooLarge       UIErrorCode = "capture_too_large"
133	UIErrActionExpired         UIErrorCode = "action_expired"
134	UIErrInputBusy             UIErrorCode = "input_busy"
135	UIErrBusy                  UIErrorCode = "busy"
136	UIErrNavigationFailed      UIErrorCode = "navigation_failed"
137	UIErrEndpointNotConfigured UIErrorCode = "endpoint_not_configured"
138)
139
140// UIError reports a sanitized operation failure without captured content.
141type UIError struct {
142	Code     UIErrorCode
143	Accepted bool
144	ActionID uint64
145}
146
147func (e *UIError) Error() string { return string(e.Code) }
148
149// UIActionRequest contains one validated operation, not raw terminal bytes.
150type UIActionRequest struct {
151	Attachment string
152	Generation uint64
153	Keys       []string
154	Text       string
155	Timeout    time.Duration
156}
157
158// UIActionStatus is the lifecycle of one admitted automation action.
159type UIActionStatus string
160
161// UI action statuses are stable API values, separate from presentation status.
162const (
163	UIActionPending          UIActionStatus = "pending"
164	UIActionProcessed        UIActionStatus = "processed"
165	UIActionOutcomeUnknown   UIActionStatus = UIActionStatus(UIErrOutcomeUnknown)
166	UIActionNavigationFailed UIActionStatus = UIActionStatus(UIErrNavigationFailed)
167	UIActionUnavailable      UIActionStatus = UIActionStatus(UIErrUnavailable)
168)
169
170type UIActionResult struct {
171	ActionID uint64
172	Accepted bool
173	Status   UIActionStatus
174	Revision uint64
175	Context  UIContext
176}
177
178type UIExpect struct {
179	TextContains *string
180	Session      *protocol.ExactSessionTarget
181	Focus        *UIFocus
182	Status       *UIPresentationStatus
183}
184
185type UIFocus struct {
186	TabID  domain.TabStableID
187	PaneID domain.PaneStableID
188}
189
190type UIWaitRequest struct {
191	Attachment  string
192	AfterAction uint64
193	Expect      UIExpect
194	Timeout     time.Duration
195}
196
197type UIWaitResult struct {
198	ActionID     uint64
199	ActionStatus UIActionStatus
200	Revision     uint64
201	Context      UIContext
202}
203
204// UIService is attachment-scoped. Implementations bound actions and waits;
205// adapters own permissions, decoding, socket quotas and response serialization.
206type UIService interface {
207	Capture(attachment string) (UISnapshot, error)
208	Action(ctx context.Context, request UIActionRequest) (UIActionResult, error)
209	Wait(ctx context.Context, request UIWaitRequest) (UIWaitResult, error)
210}
211
212var ErrUIUnavailable = errors.New("UI capture unavailable")
213
214// UIObservationSink receives successfully emitted terminal bytes and flush
215// boundaries. Implementations must copy bytes before returning and never block.
216type UIObservationSink interface {
217	ObserveTerminalWrite(data []byte)
218	ObserveTerminalFlush()
219	ObserveTerminalResize(geometry domain.Geometry)
220	InvalidateTerminalObservation()
221}
222
223// UIOutputTransaction binds terminal output to coherent semantic context.
224type UIOutputTransaction interface {
225	BeginOutput(context UIContext)
226	EndOutput(success bool)
227	PublishContext(context UIContext) error
228}
229
230// UIState exposes the latest immutable publication and a coalesced change
231// signal. Snapshot returns ErrUIUnavailable rather than stale state.
232// Boundary reports the same publication's revision and context without its
233// cells, under the same availability rules, for per-frame bookkeeping that
234// must not pay for a full capture.
235type UIState interface {
236	Snapshot() (UISnapshot, error)
237	Boundary() (UIActionResult, error)
238	Changes() <-chan struct{}
239}

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