v0.9.0 e5e4ded

architecture.md RawHistoryFolder

Architecture

vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 66.

CommandResult has a closed explicit outcome contract:

Outcome Meaning Fields
succeeded The command completed successfully. code and text are empty; output may be present.
failed A definite failure, including validation/cancellation before dispatch. code is nonzero; output is empty.
outcome_unknown Dispatch was attempted but no final outcome can be established (timeout, cancellation, EOF, or lost reply). code and output are empty; callers must not replay automatically.

Every result keeps its request ID for strict correlation.

Package ownership

  • internal/domain: pure shared values and invariants. domain/terminalcap owns terminal capability values and environment detection policy. domain/layout owns the pure pane split tree and its geometry.
  • internal/protocol: typed, transport-neutral client/daemon messages, protocol version, semantic validation, and handshake policy.
  • internal/protocol/catalogue: independently versioned remote discovery JSON schema and bounded validation.
  • internal/protocol/wire: Protobuf wire contract. Sources of truth are schema/*.proto (generated *.pb.go is never edited); scan.go owns strict protowire inspection, preamble.go owns limits/magic/epoch, and transport.go owns raw Transport, Dialer, and Listener contracts carrying Envelope{Payload} (one complete serialized directional envelope, no type byte).
  • internal/ports: application-facing interfaces and the values required by those interfaces. It contains no codecs, raw frames, environment policy, or worker implementations.
  • internal/usecase/colorprofile: the single terminalcap.ColorCapabilities → vev-vt color profile mapping shared by the client picker and daemon output renderer.
  • internal/usecase/fuzzy: the exact/prefix/subsequence matcher and tie-break order shared by the session picker and the command palette; each caller maps match kinds to its own ranks.
  • internal/usecase: client, daemon, broker, and supporting application behavior. Production use cases may consume semantic protocol packages but never protocol/wire or concrete adapters. The broker may use its own subpackages but cannot import sibling use cases. Client, daemon, and remote-registry use cases cannot own or import each other; they collaborate only through ports and application composition.
  • internal/adapters/sessionwire: translates between typed session connections and raw wire transports: directional oneof envelope wrapping/unwrapping, the staged preamble state machine (PreambleRequest first, PreambleResponse acceptance, exact version, negotiated ceilings, one absolute deadline), direction checks, and decode-failure classification.
  • internal/adapters/brokerwire: typed broker messages ↔ the broker Protobuf conversation (schema/broker.proto). See Broker.
  • internal/adapters/brokeripc: the private broker.sock endpoint. Implements ports.BrokerListener and ports.BrokerService.
  • internal/adapters/quic: one-stream QUIC carriage (TLS 1.3, epoch ALPN, exact SHA-256 pin, single bidirectional stream, bounded admission) plus the short-lived SSH bootstrap (ephemeral certificate, 32-byte token, nonce, ≤4 KiB readiness, ≤15 s expiry, atomic one-time consumption). QUIC library types never leave the package.
  • internal/adapters/brokerconfig: parses broker.json (registrations, local binding, warm-transport settings) and provides the immutable ports.BrokerEndpointResolver.
  • internal/adapters/uidriver: owns strict JSONL decoding, response serialization, bounded controller queues, private Unix sockets, peer credentials, and the stdio bridge. It consumes ports.UIService and never creates an attachment.
  • internal/adapters/webterm: owns the authenticated loopback HTTP/WebSocket frontend, browser event encoding, VT-backed terminal and transactional HTML output. It implements ports.Terminal; application composition supplies the ordinary client Supervisor.
  • internal/adapters/uiterm: owns the immutable VT mirror/snapshot and deterministic headless terminal. The concrete terminal writer (adapters/term) taps successful writes and flushes into this sink only when observation is enabled.
  • internal/adapters: IPC, QUIC, SSH stdio, PTY, terminal, VT-backed UI terminal/observation, JSONL UI-driver sockets, persistence-facing, broker-wire, and observability implementations. streamframe owns the shared 4-byte big-endian length framing of one complete Protobuf envelope used by every stream carriage.
  • internal/app: CLI parsing and composition. It selects local or remote carriage, wraps raw dialers with sessionwire, and injects typed ports into use cases. The UI driver keeps no endpoint authority of its own: it composes the process' production broker connector and delegates every transport, daemon, and session decision to the broker.
  • pkg: reusable packages that never import internal.

Dependency direction

main → app → usecase → ports
             │          │
             └────────→ protocol ← catalogue
                              ↑
                        protocol/wire
                              ↑
                    carriage adapters

internal/app may import every layer to compose the process. Adapters depend inward on ports, protocol, and domain. internal/snapshotcodec owns the durable snapshot format (session codec, manifests, objects, preflight); it depends only on domain and ports, and only the daemon use case and the snapshot adapter consume it. Production dependency rules and the separate test-import policy are executable in boundary_test.go.

UI observation and control

Headless UI-driver mode composes one normal client.Supervisor, one uiterm terminal, one client.UI service, and one bounded JSONL adapter. The supervisor obtains snapshots and opens every attachment through the broker's ports.BrokerNavigator surface; the client does not select a daemon carriage or construct a remote registry. It does not bypass the picker, input scanners, foreground leases, navigation handoffs, output replay chain, or render publication fences. EOF cancels only that driver and detaches the attachment; it does not kill a session shell.

Interactive observation is a separate opt-in composition. term.Terminal remains the owner of raw mode, physical geometry, terminal queries, serialized output writes, and flush success. Its optional observation sink mirrors byte prefixes and successful flush boundaries into uiterm; UI transaction methods are delegated through a composition wrapper so semantic context is committed atomically with the physical flush. A private per-client Unix endpoint uses the same ports.UIService; --ui-control is the only mode that permits input operations. Slow or disconnected observers cannot claim geometry or block the physical terminal.

Browser terminal

--web-daemon launches a separate gateway with an inherited HTTP listener, loopback by default. App composition resolves web.listen/web.origin and CLI overrides before binding. The web adapter validates the configured browser-facing Host and Origin without trusting forwarded headers; a proxy owns TLS. The same-user local control socket returns the running gateway's settings and memory-only credential; readiness probes contact only its local listener. Each authenticated WebSocket owns a normal client Supervisor and a webterm.Terminal. The adapter encodes browser events as terminal input, mirrors flushed client output through vev-vt, and sends transactional HTML updates. Disconnect cancels only that attachment. The multiplexer remains daemon-owned; browser input does not weaken the restricted UI-driver automation contract.

Client-owned picker presentation

A client publishes exactly one of three presentations at a time. picker is the local picker: the client is usable and describable without a broker and without a daemon. connecting is an attachment attempt that has not committed its first frame. attached is a committed attachment, and only it carries the validated session identity, the committed output boundary, and the real actionable generation; picker and connecting carry the run's stable service handle and their status with every session field zeroed, so a capture can never present session metadata as an attachment and no caller can fence an input action against a generation no attachment committed. The former detached, reconnecting, and transitioning values are removed without alias: a reconnect is connecting, a detach returns to picker, and terminating closes the UI service instead of becoming a fourth persistent presentation. The presentation is published inside the terminal's UI output transaction by its owner: the supervisor publishes connecting before the attachment foreground is granted and picker after the run drained and revoked, while the admitted foreground publishes attached only after its first frame was written, flushed, and committed.

Session navigation is client-owned. While attached, Alt+Space opens the daemon command palette; SSP / session-picker opens the client picker as an overlay over the live attachment. Cancel returns to the same attachment without reconnecting. Committing another live session on the daemon already serving the attachment moves that attachment in place with SamePeerSwitchRequest, without a new stream or Hello; any other destination, or a refused in-place switch, switches attachments through connecting; attachment loss or explicit detach leaves the picker as the standalone presentation. Search, sorting, cursor movement, selection, and broker-backed attachment opening are client-owned. Daemon move-destination offers still use the attached worker's separate move picker.

The selected row owns one client-scoped live preview subscription through ports.BrokerNavigator. The broker opens one long-lived observation stream over its local or remote physical pool; the daemon pushes changed RemotePreview frames at a broker-selected interval (33 ms local, 125 ms remote). Cursor changes are debounced for 80 ms, and the client retains eight recent previews so a selection can remain visible while refreshing. Broker loss, commit, picker close, and client exit cancel the watch. The client checks epoch, connection, generation, target, and dimensions before painting; previews remain bounded, memory-only, and are never persisted.

The daemon does not own the session picker or resolve cross-daemon navigation; it can offer a move-destination picker to the attached client and receives its typed selection or close reply.

Pane/tab move destination pickers are separate from session navigation. The daemon captures and revalidates the move source; the attached worker displays the offer and sends the selected move target back. The command palette remains daemon-owned.

Resize repaint fence

A terminal resize reaches the client as one coalesced invalidation owned by the attachment geometry collector in internal/usecase/client, and the supervisor's serialized waits (connect attempt, established ready loop, retry cadence, and non-retryable picker wait) consume it and repaint. The fence is exactly PickerPresentation, which is PresentPicker: the composition samples Terminal.Geometry at render time, so a repaint always observes the latest size. The pre-attachment PresentConnecting transition is deliberately refused, because Begin has already admitted the attachment foreground that owns the same terminal writer before MarkAttached and the initial publication; painting there would overwrite session output. For the same reason the attachment settlement wait never consumes the invalidation, so it stays coalesced and buffered through connecting and attached and is consumed with a repaint only after the attachment returns to the picker. The invalidation is independent of the attachment's own geometry sequence/wakeup, which still drives the typed resize to the live session.

Attention and notifications

Attention remains a daemon fact. The broker publishes it from fresh observations but strips it from durable snapshots. The client's route ledger forwards attention order to the serving daemon for status-bar bells and cross-daemon jump-to-attention; the picker displays session and tab bells. The serving daemon uses that same route snapshot to display remote palette destinations, without monitoring other daemons.

Terminal focus

On remote attachments the client predicts local echo, after mosh. The attached loop mirrors every applied Output in a local vev-vt screen, guesses printable keys and backspace on the cursor row, and draws the guesses through the foreground's output lease. Output.Echo acknowledges input once its echo reached the screen, so the client checks each guess against the mirror and erases a wrong one. A smoothed round trip from those acknowledgements decides when guesses show and when they are underlined. Input the client cannot model makes later guesses tentative until a correct guess confirms them. Client-owned overlays, resizes, and a hidden cursor drop every guess. Daemon modals with an input line (command palette, prompt, copy search) place the real terminal cursor on their caret instead of drawing one, so typing in them is predicted like a shell line. echo.predict selects adaptive, always, or never.

The client turns on focus reporting (DEC mode 1004). Its terminal input pump strips each ESC [ I/ESC [ O report into a TerminalFocusState that any client feature can read or watch, Hello declares the focus known at attach, so the first paint already respects it, and the attached loop sends every later change to the daemon as TerminalFocus ahead of the input read that carried it. The daemon keeps it per attachment as a lock-free terminalFocus(). Unknown focus, from a terminal that never reports, counts as possibly seen, so every feature keeps its previous behavior there. Attention uses it: only an attachment that may be seen clears a bell. Geometry uses it too: a reported focus gain makes the attachment the latest shared PTY geometry claimant, so session content resizes to the focused window.

With the kitty keyboard protocol the client asks its terminal for every key event, modifier presses and releases included (kittykey.RequestFlags). The input pump runs a kittykey.Normalizer that rewrites that stream back to the OuterFlags encoding every consumer reads, so routers, pickers, and panes see the same bytes as before, and tracks whether Ctrl is held on its own. The attached loop reports a hold that lasted 150 ms, and its release, as ModifierState; the daemon keeps it per attachment and prefixes the status-bar history with its Ctrl+N ranks while it is held.

Broker-owned hosts and daemon composition

vev _broker-ready --require local-authority|local-catalogue is an internal, dial-only machine probe. It connects directly to the selected broker IPC socket, registers, subscribes, and validates complete snapshots. It never ensures or spawns, opens a logical stream, reconciles, or mutates. An optional --offline-root ABS selects the offline layout. Output is exactly one bounded vev.broker-ready/v1 JSON line with decimal-string epoch/revision IDs. Exit codes are 0 ready, 1 internal/write, 2 arguments, 3 timeout, 4 terminal security/protocol/configuration failure, and 5 cancellation.

The broker is the sole façade for local and remote inventory, endpoint membership, policy, observation, and logical session streams. client.Supervisor consumes ports.BrokerNavigator (connection, observation, and streams; never host mutation); it does not import or compose a remote registry, remote adapter, endpoint factory, or direct session dialer. Host mutations and attachment requests therefore pass through the same broker authority and committed snapshot.

A daemon serves only the sessions on its own machine. It has no remote-directory monitor and does not compose remote transports. In production it accepts the broker's authenticated multiplexed physical carriage through internal/adapters/daemonmux; logical observation, control, and attachment streams share that carriage while retaining independent admission and lifetime. The daemon's Unix daemonmux listener is local-only composition infrastructure between the broker and daemon, not a public client endpoint. Ordinary clients connect to broker.sock and never dial a daemon socket directly.

Session composition

client or daemon use case
  ↕ ports.ClientConnection / ports.ServerConnection
adapters/sessionwire
  ↕ protocol/wire.Transport carrying wire.Envelope (Protobuf)
IPC, QUIC, or SSH stdio adapter

Use cases exchange protocol.ClientMessage and protocol.ServerMessage values. sessionwire alone maps those values to directional oneof envelopes. Blind proxies may forward raw envelopes, but no proxy path exposes bytes to a use case.

Adding code

  • Add application behavior to a use case and define any cross-layer interface in internal/ports.
  • Add transport-neutral session meaning to internal/protocol.
  • Add remote catalogue schema fields and validation to internal/protocol/catalogue.
  • Add Protobuf message variants, envelope fields, or preamble members to internal/protocol/wire/schema/*.proto, regenerate with go tool buf generate, and handle the new variant in sessionwire. Never edit generated *.pb.go; never add manual IDs or dispatch tables.
  • Implement I/O, queues, workers, environment integration, or technology selection in an adapter or internal/app.
  • Bump internal/protocol.Version for negotiated wire layout changes (currently 66). The preamble epoch (wire.ProtocolEpoch, QUIC ALPN vev/1) bumps only for an intentional clean break.

Broker

The broker is a per-user process that sits between every client and every daemon. It starts on first use and stops after 5 minutes idle (broker.DefaultIdleGrace).

client ──broker.sock──▶ broker ──daemonmux──▶ local daemon
                          │
                          └──SSH stdio or SSH+QUIC──▶ remote daemon

What it owns:

  • Hosts. internal/usecase/broker.Registry holds configured hosts and publishes one ports.BrokerSnapshot: the local daemon first, then remotes. Configured authority (endpoint, policy, rank) comes from membership; observed facts (identity, version, availability, session catalogue) come only from probes. Only remote observations are persisted.
  • Observation and routing. broker.RemoteProbe and broker.LocalDaemonProbe observe daemons over the pooled carriage with an existing-only observation stream. broker.Routes resolves remote dial targets from committed membership and re-authorizes each one immediately before dialing. internal/app only supplies carriages, codecs, and route address derivation.
  • Connections. broker.Pool keys one physical connection by authenticated identity plus exact policy. Many logical streams share it. Display aliases and addresses are never keys. Idle connections stay warm (warmTransports, warmIdleTimeout).
  • Daemon start. Every stream carries a ports.BrokerDaemonStartMode: ExistingOnly (observation, stop) or StartIfNeeded (attach, create). The mode can narrow the launch policy, never widen it. A running daemon is never restarted. The start path lives in internal/app/broker_daemon_start.go and uses the shared spawn election in spawn.go.
  • Wire. internal/adapters/brokerwire owns the broker's own Protobuf conversation (schema/broker.proto, client tags 101+, server tags 201+). It uses the same preamble, epoch, and exact protocol.Version as sessions. Streams carry the typed session protocol unchanged.
  • Local IPC. internal/adapters/brokeripc serves broker.sock: owner-only directory, 0600 socket, same-user peer credentials. Setup, queues, and clients are bounded. A lost reply to a mutating operation is reported as outcome unknown and never replayed.
  • Remote helpers. Hidden commands _broker-mux-stdio, _broker-mux-quic-bootstrap, and _broker-mux-quic-proxy run on the remote host and bridge to that host's private daemonmux socket. They take a closed --daemon-start existing-only|if-needed flag (default existing-only). QUIC credentials are fresh per connection and used once.

Other hidden entry points: _broker-production-serve, _broker-production-launcher, and _broker-ready (a dial-only readiness probe that prints one vev.broker-ready/v1 JSON line).

Locks: the broker holds lifecycle.lock in its runtime directory while running. A separate spawn lock elects one process to start it. Locks are never stolen by age.

source
1# Architecture
2
3vev uses a hexagonal core with typed session messages at the client and daemon boundary. Binary framing and carriage selection stay outside the use cases. The current strict protocol version is 66.
4
5`CommandResult` has a closed explicit outcome contract:
6
7| Outcome | Meaning | Fields |
8| --- | --- | --- |
9| `succeeded` | The command completed successfully. | code and text are empty; output may be present. |
10| `failed` | A definite failure, including validation/cancellation before dispatch. | code is nonzero; output is empty. |
11| `outcome_unknown` | Dispatch was attempted but no final outcome can be established (timeout, cancellation, EOF, or lost reply). | code and output are empty; callers must not replay automatically. |
12
13Every result keeps its request ID for strict correlation.
14
15## Package ownership
16
17- `internal/domain`: pure shared values and invariants. `domain/terminalcap` owns terminal capability values and environment detection policy. `domain/layout` owns the pure pane split tree and its geometry.
18- `internal/protocol`: typed, transport-neutral client/daemon messages, protocol version, semantic validation, and handshake policy.
19- `internal/protocol/catalogue`: independently versioned remote discovery JSON schema and bounded validation.
20- `internal/protocol/wire`: Protobuf wire contract. Sources of truth are
21  `schema/*.proto` (generated `*.pb.go` is never edited); `scan.go` owns
22  strict protowire inspection, `preamble.go` owns limits/magic/epoch, and
23  `transport.go` owns raw `Transport`, `Dialer`, and `Listener` contracts
24  carrying `Envelope{Payload}` (one complete serialized directional
25  envelope, no type byte).
26- `internal/ports`: application-facing interfaces and the values required by those interfaces. It contains no codecs, raw frames, environment policy, or worker implementations.
27- `internal/usecase/colorprofile`: the single `terminalcap.ColorCapabilities` → vev-vt color profile mapping shared by the client picker and daemon output renderer.
28- `internal/usecase/fuzzy`: the exact/prefix/subsequence matcher and tie-break order shared by the session picker and the command palette; each caller maps match kinds to its own ranks.
29- `internal/usecase`: client, daemon, broker, and supporting application behavior. Production use cases may consume semantic protocol packages but never `protocol/wire` or concrete adapters. The broker may use its own subpackages but cannot import sibling use cases. Client, daemon, and remote-registry use cases cannot own or import each other; they collaborate only through ports and application composition.
30- `internal/adapters/sessionwire`: translates between typed session
31  connections and raw wire transports: directional `oneof` envelope
32  wrapping/unwrapping, the staged preamble state machine
33  (PreambleRequest first, PreambleResponse acceptance, exact version,
34  negotiated ceilings, one absolute deadline), direction checks, and
35  decode-failure classification.
36- `internal/adapters/brokerwire`: typed broker messages ↔ the broker Protobuf conversation (`schema/broker.proto`). See [Broker](#broker).
37- `internal/adapters/brokeripc`: the private `broker.sock` endpoint. Implements `ports.BrokerListener` and `ports.BrokerService`.
38- `internal/adapters/quic`: one-stream QUIC carriage (TLS 1.3, epoch ALPN,
39  exact SHA-256 pin, single bidirectional stream, bounded admission) plus
40  the short-lived SSH bootstrap (ephemeral certificate, 32-byte token,
41  nonce, ≤4 KiB readiness, ≤15 s expiry, atomic one-time consumption).
42  QUIC library types never leave the package.
43- `internal/adapters/brokerconfig`: parses `broker.json` (registrations, local binding, warm-transport settings) and provides the immutable `ports.BrokerEndpointResolver`.
44- `internal/adapters/uidriver`: owns strict JSONL decoding, response serialization, bounded controller queues, private Unix sockets, peer credentials, and the stdio bridge. It consumes `ports.UIService` and never creates an attachment.
45- `internal/adapters/webterm`: owns the authenticated loopback HTTP/WebSocket frontend, browser event encoding, VT-backed terminal and transactional HTML output. It implements `ports.Terminal`; application composition supplies the ordinary client `Supervisor`.
46- `internal/adapters/uiterm`: owns the immutable VT mirror/snapshot and deterministic headless terminal. The concrete terminal writer (`adapters/term`) taps successful writes and flushes into this sink only when observation is enabled.
47- `internal/adapters`: IPC, QUIC, SSH stdio, PTY, terminal, VT-backed UI terminal/observation, JSONL UI-driver sockets, persistence-facing, broker-wire, and observability implementations. `streamframe` owns the shared 4-byte big-endian length framing of one complete Protobuf envelope used by every stream carriage.
48- `internal/app`: CLI parsing and composition. It selects local or remote carriage, wraps raw dialers with `sessionwire`, and injects typed ports into use cases. The UI driver keeps no endpoint authority of its own: it composes the process' production broker connector and delegates every transport, daemon, and session decision to the broker.
49- `pkg`: reusable packages that never import `internal`.
50
51## Dependency direction
52
53```text
54main → app → usecase → ports
55             │          │
56             └────────→ protocol ← catalogue
57                              ↑
58                        protocol/wire
59                              ↑
60                    carriage adapters
61```
62
63`internal/app` may import every layer to compose the process. Adapters depend inward on ports, protocol, and domain. `internal/snapshotcodec` owns the durable snapshot format (session codec, manifests, objects, preflight); it depends only on domain and ports, and only the daemon use case and the snapshot adapter consume it. Production dependency rules and the separate test-import policy are executable in `boundary_test.go`.
64
65## UI observation and control
66
67Headless UI-driver mode composes one normal `client.Supervisor`, one `uiterm` terminal, one `client.UI` service, and one bounded JSONL adapter. The supervisor obtains snapshots and opens every attachment through the broker's `ports.BrokerNavigator` surface; the client does not select a daemon carriage or construct a remote registry. It does not bypass the picker, input scanners, foreground leases, navigation handoffs, output replay chain, or render publication fences. EOF cancels only that driver and detaches the attachment; it does not kill a session shell.
68
69Interactive observation is a separate opt-in composition. `term.Terminal` remains the owner of raw mode, physical geometry, terminal queries, serialized output writes, and flush success. Its optional observation sink mirrors byte prefixes and successful flush boundaries into `uiterm`; UI transaction methods are delegated through a composition wrapper so semantic context is committed atomically with the physical flush. A private per-client Unix endpoint uses the same `ports.UIService`; `--ui-control` is the only mode that permits input operations. Slow or disconnected observers cannot claim geometry or block the physical terminal.
70
71## Browser terminal
72
73`--web-daemon` launches a separate gateway with an inherited HTTP listener, loopback by default. App composition resolves `web.listen`/`web.origin` and CLI overrides before binding. The web adapter validates the configured browser-facing Host and Origin without trusting forwarded headers; a proxy owns TLS. The same-user local control socket returns the running gateway's settings and memory-only credential; readiness probes contact only its local listener. Each authenticated WebSocket owns a normal client `Supervisor` and a `webterm.Terminal`. The adapter encodes browser events as terminal input, mirrors flushed client output through `vev-vt`, and sends transactional HTML updates. Disconnect cancels only that attachment. The multiplexer remains daemon-owned; browser input does not weaken the restricted UI-driver automation contract.
74
75## Client-owned picker presentation
76
77A client publishes exactly one of three presentations at a time. `picker` is the local picker: the client is usable and describable without a broker and without a daemon. `connecting` is an attachment attempt that has not committed its first frame. `attached` is a committed attachment, and only it carries the validated session identity, the committed output boundary, and the real actionable generation; `picker` and `connecting` carry the run's stable service handle and their status with every session field zeroed, so a capture can never present session metadata as an attachment and no caller can fence an input action against a generation no attachment committed. The former `detached`, `reconnecting`, and `transitioning` values are removed without alias: a reconnect is `connecting`, a detach returns to `picker`, and terminating closes the UI service instead of becoming a fourth persistent presentation. The presentation is published inside the terminal's UI output transaction by its owner: the supervisor publishes `connecting` before the attachment foreground is granted and `picker` after the run drained and revoked, while the admitted foreground publishes `attached` only after its first frame was written, flushed, and committed.
78
79Session navigation is client-owned. While attached, Alt+Space opens the daemon command palette; `SSP` / `session-picker` opens the client picker as an overlay over the live attachment. Cancel returns to the same attachment without reconnecting. Committing another live session on the daemon already serving the attachment moves that attachment in place with `SamePeerSwitchRequest`, without a new stream or `Hello`; any other destination, or a refused in-place switch, switches attachments through `connecting`; attachment loss or explicit detach leaves the picker as the standalone presentation. Search, sorting, cursor movement, selection, and broker-backed attachment opening are client-owned. Daemon move-destination offers still use the attached worker's separate move picker.
80
81The selected row owns one client-scoped live preview subscription through `ports.BrokerNavigator`. The broker opens one long-lived observation stream over its local or remote physical pool; the daemon pushes changed `RemotePreview` frames at a broker-selected interval (33 ms local, 125 ms remote). Cursor changes are debounced for 80 ms, and the client retains eight recent previews so a selection can remain visible while refreshing. Broker loss, commit, picker close, and client exit cancel the watch. The client checks epoch, connection, generation, target, and dimensions before painting; previews remain bounded, memory-only, and are never persisted.
82
83The daemon does not own the session picker or resolve cross-daemon navigation; it can offer a move-destination picker to the attached client and receives its typed selection or close reply.
84
85Pane/tab move destination pickers are separate from session navigation. The daemon captures and revalidates the move source; the attached worker displays the offer and sends the selected move target back. The command palette remains daemon-owned.
86
87### Resize repaint fence
88
89A terminal resize reaches the client as one coalesced invalidation owned by the attachment geometry collector in `internal/usecase/client`, and the supervisor's serialized waits (connect attempt, established ready loop, retry cadence, and non-retryable picker wait) consume it and repaint. The fence is exactly `PickerPresentation`, which is `PresentPicker`: the composition samples `Terminal.Geometry` at render time, so a repaint always observes the latest size. The pre-attachment `PresentConnecting` transition is deliberately refused, because `Begin` has already admitted the attachment foreground that owns the same terminal writer before `MarkAttached` and the initial publication; painting there would overwrite session output. For the same reason the attachment settlement wait never consumes the invalidation, so it stays coalesced and buffered through connecting and attached and is consumed with a repaint only after the attachment returns to the picker. The invalidation is independent of the attachment's own geometry sequence/wakeup, which still drives the typed resize to the live session.
90
91### Attention and notifications
92
93Attention remains a daemon fact. The broker publishes it from fresh observations but strips it from durable snapshots. The client's route ledger forwards attention order to the serving daemon for status-bar bells and cross-daemon jump-to-attention; the picker displays session and tab bells. The serving daemon uses that same route snapshot to display remote palette destinations, without monitoring other daemons.
94
95### Terminal focus
96
97On remote attachments the client predicts local echo, after mosh. The attached loop mirrors every applied `Output` in a local `vev-vt` screen, guesses printable keys and backspace on the cursor row, and draws the guesses through the foreground's output lease. `Output.Echo` acknowledges input once its echo reached the screen, so the client checks each guess against the mirror and erases a wrong one. A smoothed round trip from those acknowledgements decides when guesses show and when they are underlined. Input the client cannot model makes later guesses tentative until a correct guess confirms them. Client-owned overlays, resizes, and a hidden cursor drop every guess. Daemon modals with an input line (command palette, prompt, copy search) place the real terminal cursor on their caret instead of drawing one, so typing in them is predicted like a shell line. `echo.predict` selects `adaptive`, `always`, or `never`.
98
99The client turns on focus reporting (DEC mode 1004). Its terminal input pump strips each `ESC [ I`/`ESC [ O` report into a `TerminalFocusState` that any client feature can read or watch, `Hello` declares the focus known at attach, so the first paint already respects it, and the attached loop sends every later change to the daemon as `TerminalFocus` ahead of the input read that carried it. The daemon keeps it per attachment as a lock-free `terminalFocus()`. Unknown focus, from a terminal that never reports, counts as possibly seen, so every feature keeps its previous behavior there. Attention uses it: only an attachment that may be seen clears a bell. Geometry uses it too: a reported focus gain makes the attachment the latest shared PTY geometry claimant, so session content resizes to the focused window.
100
101With the kitty keyboard protocol the client asks its terminal for every key event, modifier presses and releases included (`kittykey.RequestFlags`). The input pump runs a `kittykey.Normalizer` that rewrites that stream back to the `OuterFlags` encoding every consumer reads, so routers, pickers, and panes see the same bytes as before, and tracks whether Ctrl is held on its own. The attached loop reports a hold that lasted 150 ms, and its release, as `ModifierState`; the daemon keeps it per attachment and prefixes the status-bar history with its Ctrl+N ranks while it is held.
102
103## Broker-owned hosts and daemon composition
104
105`vev _broker-ready --require local-authority|local-catalogue` is an internal,
106dial-only machine probe. It connects directly to the selected broker IPC
107socket, registers, subscribes, and validates complete snapshots. It never
108ensures or spawns, opens a logical stream, reconciles, or mutates. An optional
109`--offline-root ABS` selects the offline layout. Output is exactly one bounded
110`vev.broker-ready/v1` JSON line with decimal-string epoch/revision IDs. Exit
111codes are 0 ready, 1 internal/write, 2 arguments, 3 timeout, 4 terminal
112security/protocol/configuration failure, and 5 cancellation.
113
114The broker is the sole façade for local and remote inventory, endpoint membership, policy, observation, and logical session streams. `client.Supervisor` consumes `ports.BrokerNavigator` (connection, observation, and streams; never host mutation); it does not import or compose a remote registry, remote adapter, endpoint factory, or direct session dialer. Host mutations and attachment requests therefore pass through the same broker authority and committed snapshot.
115
116A daemon serves only the sessions on its own machine. It has no remote-directory monitor and does not compose remote transports. In production it accepts the broker's authenticated multiplexed physical carriage through `internal/adapters/daemonmux`; logical observation, control, and attachment streams share that carriage while retaining independent admission and lifetime. The daemon's Unix daemonmux listener is local-only composition infrastructure between the broker and daemon, not a public client endpoint. Ordinary clients connect to `broker.sock` and never dial a daemon socket directly.
117
118## Session composition
119
120```text
121client or daemon use case
122  ↕ ports.ClientConnection / ports.ServerConnection
123adapters/sessionwire
124  ↕ protocol/wire.Transport carrying wire.Envelope (Protobuf)
125IPC, QUIC, or SSH stdio adapter
126```
127
128Use cases exchange `protocol.ClientMessage` and `protocol.ServerMessage` values. `sessionwire` alone maps those values to directional `oneof` envelopes. Blind proxies may forward raw envelopes, but no proxy path exposes bytes to a use case.
129
130## Adding code
131
132- Add application behavior to a use case and define any cross-layer interface in `internal/ports`.
133- Add transport-neutral session meaning to `internal/protocol`.
134- Add remote catalogue schema fields and validation to `internal/protocol/catalogue`.
135- Add Protobuf message variants, envelope fields, or preamble members to
136  `internal/protocol/wire/schema/*.proto`, regenerate with
137  `go tool buf generate`, and handle the new variant in `sessionwire`.
138  Never edit generated `*.pb.go`; never add manual IDs or dispatch tables.
139- Implement I/O, queues, workers, environment integration, or technology selection in an adapter or `internal/app`.
140- Bump `internal/protocol.Version` for negotiated wire layout changes (currently `66`). The preamble epoch (`wire.ProtocolEpoch`, QUIC ALPN `vev/1`) bumps only for an intentional clean break.
141
142## Broker
143
144The broker is a per-user process that sits between every client and every daemon. It starts on first use and stops after 5 minutes idle (`broker.DefaultIdleGrace`).
145
146```text
147client ──broker.sock──▶ broker ──daemonmux──▶ local daemon
148                          │
149                          └──SSH stdio or SSH+QUIC──▶ remote daemon
150```
151
152What it owns:
153
154- **Hosts.** `internal/usecase/broker.Registry` holds configured hosts and publishes one `ports.BrokerSnapshot`: the local daemon first, then remotes. Configured authority (endpoint, policy, rank) comes from membership; observed facts (identity, version, availability, session catalogue) come only from probes. Only remote observations are persisted.
155- **Observation and routing.** `broker.RemoteProbe` and `broker.LocalDaemonProbe` observe daemons over the pooled carriage with an existing-only observation stream. `broker.Routes` resolves remote dial targets from committed membership and re-authorizes each one immediately before dialing. `internal/app` only supplies carriages, codecs, and route address derivation.
156- **Connections.** `broker.Pool` keys one physical connection by authenticated identity plus exact policy. Many logical streams share it. Display aliases and addresses are never keys. Idle connections stay warm (`warmTransports`, `warmIdleTimeout`).
157- **Daemon start.** Every stream carries a `ports.BrokerDaemonStartMode`: `ExistingOnly` (observation, stop) or `StartIfNeeded` (attach, create). The mode can narrow the launch policy, never widen it. A running daemon is never restarted. The start path lives in `internal/app/broker_daemon_start.go` and uses the shared spawn election in `spawn.go`.
158- **Wire.** `internal/adapters/brokerwire` owns the broker's own Protobuf conversation (`schema/broker.proto`, client tags 101+, server tags 201+). It uses the same preamble, epoch, and exact `protocol.Version` as sessions. Streams carry the typed session protocol unchanged.
159- **Local IPC.** `internal/adapters/brokeripc` serves `broker.sock`: owner-only directory, `0600` socket, same-user peer credentials. Setup, queues, and clients are bounded. A lost reply to a mutating operation is reported as `outcome unknown` and never replayed.
160- **Remote helpers.** Hidden commands `_broker-mux-stdio`, `_broker-mux-quic-bootstrap`, and `_broker-mux-quic-proxy` run on the remote host and bridge to that host's private daemonmux socket. They take a closed `--daemon-start existing-only|if-needed` flag (default `existing-only`). QUIC credentials are fresh per connection and used once.
161
162Other hidden entry points: `_broker-production-serve`, `_broker-production-launcher`, and `_broker-ready` (a dial-only readiness probe that prints one `vev.broker-ready/v1` JSON line).
163
164Locks: the broker holds `lifecycle.lock` in its runtime directory while running. A separate spawn lock elects one process to start it. Locks are never stolen by age.

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