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}