detached fff93b8

docs: describe broker preview subscription

commit
fff93b82cee24d19a8703b75ee6f2a7a47f48c84
author
brice · · 2026-09-22 19:23 UTC
parent
c56f581
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +14 -17
M docs/architecture.md +6 -10
@@ -98,17 +98,13 @@ Interactive observation is a separate opt-in composition. `term.Terminal` remain
9898
9999 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.
100100
101-Every attachment presents the navigation picker itself. The serving daemon publishes a typed interaction instead of installing an overlay: `PickerOffer` names the intent and the acquisition barrier, `PickerSnapshot` carries opaque row keys plus display text, `PickerSelection` commits a key at the displayed revision, `PickerClose` retires the interaction in either direction, and `PickerFailure` reports a bounded rejection. The daemon owns no presentation model: it keeps facts, eligibility, and mutations, and resolves committed keys to navigation, move, or kill targets.
101+Session navigation is an autonomous client presentation, not an attachment interaction. While attached, Alt+Space opens the daemon command palette; `SSP` / `session-picker` ends the current attachment with `Detached{Reason: ReasonDetachToPicker}`. The session stays headless. The attachment worker maps this to `AttachmentDetachToPicker` settlement; the supervisor drains and revokes the foreground before giving its input lease and terminal writer to the local picker. Search, sorting, cursor movement, selection, and broker-backed attachment opening are client-owned. No picker bytes or navigation selections are sent to the serving daemon.
102102
103-Source eligibility travels with every row. `PickerLine.Focusable` is the source's authorisation to rest the cursor on that row, while `PickerLine.Actions` is what the row may commit; an action row is always focusable, a section header never is, and a focusable row without actions is published for inspection only. The client's cursor visits focusable rows, narrowed by its own search query, so it never lands on a row the source skipped and the footer never promises a commit the cursor cannot make.
103+The selected row owns one client-scoped live preview subscription through `ports.BrokerService`. The broker opens sequential observation streams over its existing local or remote physical pool, sends bounded `RemotePreviewRequest` values, and publishes the newest fenced result. Cursor movement and resize replace the subscription with a strictly newer generation; broker loss, commit, picker close, and client exit cancel it. The client accepts a publication only when broker epoch, connection, generation, target, and dimensions still match, so a late capture cannot repaint another selection. Preview cells remain bounded, memory-only, and are never persisted or logged.
104104
105-The client owns who writes the terminal for the interaction lifetime, while the daemon keeps mutation authority and resolves keys. `pickerLease` in `internal/usecase/client` tracks the presentation states: a snapshot is admitted only once the client applied output through the barrier epoch/state it carries, daemon paints are then applied to the output shadow and acknowledged inside the bounded window without being written, and the interaction is released only by a full paint accepted after the close boundary. Locally composed frames and repaints go through the same single writer, UI transaction, and flush as daemon output, so a frame is never claimed before it is displayed.
105+There is no forwarding or compatibility path through `PickerOffer`. The daemon does not dispatch `client picker request` or `picker control request`, cannot open a navigation interaction, and cannot resolve a `PickerActionNavigate` or `PickerActionKill` interaction selection.
106106
107-Input ownership is exclusive for the interaction lifetime. `pickerConsumer` in `internal/usecase/client` publishes the current owner to the stdin pump: while the picker owns input the pump decodes physical bytes and admitted automation batches into identified operations and the attach loop, its single model writer, applies them. Nothing falls back to the session pipeline: unknown, oversized, and paste-sized batches are consumed, a multi-key batch is never applied as a command, and an ambiguous escape prefix is bounded by a short deadline instead of being forwarded. The serving daemon mirrors the rule: while `pickerOpen` it drops raw key and mouse input for that attachment (`handleInput`/`handleInputForAttachment` return before the mouse scanner and the key router), so only typed `PickerSelection`, `PickerClose`, and `PickerPreviewRequest` can act. A terminal close — a resolved selection, a cancel, a stale-revision or retired-target rejection, or a superseding overlay — sends `PickerClose` when the client must retire the interaction itself, keeps consuming and dropping input until the daemon's authoritative repaint lands, and only then resumes normal routing.
108-
109-### Row previews
110-
111-A preview is data, not a daemon paint: the client asks for the row its cursor rests on with a debounced `PickerPreviewRequest` (80 ms, one request in flight per interaction), and the serving daemon answers with a bounded styled-cell `PickerPreview`. The daemon captures local rows through the navigation preview facts it already owns (`previewTarget` plus `snapshotPickerPreview`), captures remote rows through its own remote viewport client and cache, and registers the requested row as that viewer's render subscription, so a row that keeps changing keeps publishing while a superseded request publishes nothing. Unchanged or retired rows answer with a status-only preview, and the client clears its viewport rather than showing another row's content. The client composes the viewport into its own modal; the daemon never writes it.
107+Pane/tab move destination pickers are not forwarded through session navigation. The existing daemon interaction remains temporarily available only to the daemon command palette; it captures and revalidates the move source and accepts only move intents. The autonomous attachment worker does not consume that interaction. Explicit scripted move commands remain available. The command palette itself remains daemon-owned and is separate from session-picker presentation.
112108
113109 ### Resize repaint fence
114110
@@ -116,7 +112,7 @@ A terminal resize reaches the client as one coalesced invalidation owned by the
116112
117113 ### Attention and notifications
118114
119-Attention is a daemon fact, not an overlay: a bell raised by a PTY, by a status-line notification, or by an agent run marks the owning tab, and the daemon publishes it in the frames its attachments receive. The status bar carries it as a pulsing glyph, the picker session header carries it inline, and `PickerLine.Attention` carries it on the tab row the client renders. Because a client-owned modal suppresses daemon paints, the attention pulse republishes the picker source for every attachment whose interaction the client presents, so a bell raised while the picker is open still reaches the displayed rows.
115+Attention remains a daemon fact. The autonomous picker renders catalogue facts obtained through the broker; it does not subscribe to an attachment's legacy picker interaction. Legacy move-destination interactions retain their own attention refresh machinery.
120116
121117 ## Broker-owned hosts and daemon composition
122118
@@ -160,7 +156,7 @@ Use cases exchange `protocol.ClientMessage` and `protocol.ServerMessage` values.
160156 ## Broker wire core (Plan 001 P3.1, not activated)
161157
162158 `internal/adapters/brokerwire` owns the broker's separate Protobuf
163-conversation end to end: client tags 101-111 and server tags 201-209 are
159+conversation end to end: client tags 101-113 and server tags 201-210 are
164160 disjoint from every session tag, and each direction is its own closed
165161 directional `oneof` union. A broker connection opens with exactly one
166162 broker preamble (roles 3/4) that carries the same magic, epoch, exact
@@ -98,17 +98,13 @@ Interactive observation is a separate opt-in composition. `term.Terminal` remain
98 98
99 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.99 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.
100 100
101-Every attachment presents the navigation picker itself. The serving daemon publishes a typed interaction instead of installing an overlay: `PickerOffer` names the intent and the acquisition barrier, `PickerSnapshot` carries opaque row keys plus display text, `PickerSelection` commits a key at the displayed revision, `PickerClose` retires the interaction in either direction, and `PickerFailure` reports a bounded rejection. The daemon owns no presentation model: it keeps facts, eligibility, and mutations, and resolves committed keys to navigation, move, or kill targets.101+Session navigation is an autonomous client presentation, not an attachment interaction. While attached, Alt+Space opens the daemon command palette; `SSP` / `session-picker` ends the current attachment with `Detached{Reason: ReasonDetachToPicker}`. The session stays headless. The attachment worker maps this to `AttachmentDetachToPicker` settlement; the supervisor drains and revokes the foreground before giving its input lease and terminal writer to the local picker. Search, sorting, cursor movement, selection, and broker-backed attachment opening are client-owned. No picker bytes or navigation selections are sent to the serving daemon.
102 102
103-Source eligibility travels with every row. `PickerLine.Focusable` is the source's authorisation to rest the cursor on that row, while `PickerLine.Actions` is what the row may commit; an action row is always focusable, a section header never is, and a focusable row without actions is published for inspection only. The client's cursor visits focusable rows, narrowed by its own search query, so it never lands on a row the source skipped and the footer never promises a commit the cursor cannot make.103+The selected row owns one client-scoped live preview subscription through `ports.BrokerService`. The broker opens sequential observation streams over its existing local or remote physical pool, sends bounded `RemotePreviewRequest` values, and publishes the newest fenced result. Cursor movement and resize replace the subscription with a strictly newer generation; broker loss, commit, picker close, and client exit cancel it. The client accepts a publication only when broker epoch, connection, generation, target, and dimensions still match, so a late capture cannot repaint another selection. Preview cells remain bounded, memory-only, and are never persisted or logged.
104 104
105-The client owns who writes the terminal for the interaction lifetime, while the daemon keeps mutation authority and resolves keys. `pickerLease` in `internal/usecase/client` tracks the presentation states: a snapshot is admitted only once the client applied output through the barrier epoch/state it carries, daemon paints are then applied to the output shadow and acknowledged inside the bounded window without being written, and the interaction is released only by a full paint accepted after the close boundary. Locally composed frames and repaints go through the same single writer, UI transaction, and flush as daemon output, so a frame is never claimed before it is displayed.105+There is no forwarding or compatibility path through `PickerOffer`. The daemon does not dispatch `client picker request` or `picker control request`, cannot open a navigation interaction, and cannot resolve a `PickerActionNavigate` or `PickerActionKill` interaction selection.
106 106
107-Input ownership is exclusive for the interaction lifetime. `pickerConsumer` in `internal/usecase/client` publishes the current owner to the stdin pump: while the picker owns input the pump decodes physical bytes and admitted automation batches into identified operations and the attach loop, its single model writer, applies them. Nothing falls back to the session pipeline: unknown, oversized, and paste-sized batches are consumed, a multi-key batch is never applied as a command, and an ambiguous escape prefix is bounded by a short deadline instead of being forwarded. The serving daemon mirrors the rule: while `pickerOpen` it drops raw key and mouse input for that attachment (`handleInput`/`handleInputForAttachment` return before the mouse scanner and the key router), so only typed `PickerSelection`, `PickerClose`, and `PickerPreviewRequest` can act. A terminal close — a resolved selection, a cancel, a stale-revision or retired-target rejection, or a superseding overlay — sends `PickerClose` when the client must retire the interaction itself, keeps consuming and dropping input until the daemon's authoritative repaint lands, and only then resumes normal routing.107+Pane/tab move destination pickers are not forwarded through session navigation. The existing daemon interaction remains temporarily available only to the daemon command palette; it captures and revalidates the move source and accepts only move intents. The autonomous attachment worker does not consume that interaction. Explicit scripted move commands remain available. The command palette itself remains daemon-owned and is separate from session-picker presentation.
108-
109-### Row previews
110-
111-A preview is data, not a daemon paint: the client asks for the row its cursor rests on with a debounced `PickerPreviewRequest` (80 ms, one request in flight per interaction), and the serving daemon answers with a bounded styled-cell `PickerPreview`. The daemon captures local rows through the navigation preview facts it already owns (`previewTarget` plus `snapshotPickerPreview`), captures remote rows through its own remote viewport client and cache, and registers the requested row as that viewer's render subscription, so a row that keeps changing keeps publishing while a superseded request publishes nothing. Unchanged or retired rows answer with a status-only preview, and the client clears its viewport rather than showing another row's content. The client composes the viewport into its own modal; the daemon never writes it.
112 108
113 ### Resize repaint fence109 ### Resize repaint fence
114 110
@@ -116,7 +112,7 @@ A terminal resize reaches the client as one coalesced invalidation owned by the
116 112
117 ### Attention and notifications113 ### Attention and notifications
118 114
119-Attention is a daemon fact, not an overlay: a bell raised by a PTY, by a status-line notification, or by an agent run marks the owning tab, and the daemon publishes it in the frames its attachments receive. The status bar carries it as a pulsing glyph, the picker session header carries it inline, and `PickerLine.Attention` carries it on the tab row the client renders. Because a client-owned modal suppresses daemon paints, the attention pulse republishes the picker source for every attachment whose interaction the client presents, so a bell raised while the picker is open still reaches the displayed rows.115+Attention remains a daemon fact. The autonomous picker renders catalogue facts obtained through the broker; it does not subscribe to an attachment's legacy picker interaction. Legacy move-destination interactions retain their own attention refresh machinery.
120 116
121 ## Broker-owned hosts and daemon composition117 ## Broker-owned hosts and daemon composition
122 118
@@ -160,7 +156,7 @@ Use cases exchange `protocol.ClientMessage` and `protocol.ServerMessage` values.
160 ## Broker wire core (Plan 001 P3.1, not activated)156 ## Broker wire core (Plan 001 P3.1, not activated)
161 157
162 `internal/adapters/brokerwire` owns the broker's separate Protobuf158 `internal/adapters/brokerwire` owns the broker's separate Protobuf
163-conversation end to end: client tags 101-111 and server tags 201-209 are159+conversation end to end: client tags 101-113 and server tags 201-210 are
164 disjoint from every session tag, and each direction is its own closed160 disjoint from every session tag, and each direction is its own closed
165 directional `oneof` union. A broker connection opens with exactly one161 directional `oneof` union. A broker connection opens with exactly one
166 broker preamble (roles 3/4) that carries the same magic, epoch, exact162 broker preamble (roles 3/4) that carries the same magic, epoch, exact
M docs/configuration.md +2 -0
@@ -126,6 +126,8 @@ rm -rf .dev/dev
126126
127127 The selected absolute root is retained by the detached daemon and inherited vev processes even when their working directory changes. A name isolates configuration, runtime files, durable sessions, hosts, snapshots, and logs from normal XDG locations and from other names. Invocations using the same name intentionally share a daemon and files; use separate names for side-by-side binaries.
128128
129+If the runtime path would exceed the portable Unix-socket limit (103 bytes, including `vev/broker/broker.sock`), sockets and lifecycle locks instead use `/tmp/vev-<uid>-<sha256>`; the hash identifies the complete original runtime directory. Configuration and durable state remain under `.dev/<name>/`. All roles resolve the same private runtime directory, and distinct roots remain isolated. Short paths keep their existing location. Stop processes using an older binary before switching versions if their runtime location changes; do not delete a live runtime directory or its lock files.
130+
129131 This feature isolates vev-owned data only. It does not sandbox pane processes or provide OS, filesystem, or network isolation.
130132
131133 ## Remote hosts
@@ -126,6 +126,8 @@ rm -rf .dev/dev
126 126
127 The selected absolute root is retained by the detached daemon and inherited vev processes even when their working directory changes. A name isolates configuration, runtime files, durable sessions, hosts, snapshots, and logs from normal XDG locations and from other names. Invocations using the same name intentionally share a daemon and files; use separate names for side-by-side binaries.127 The selected absolute root is retained by the detached daemon and inherited vev processes even when their working directory changes. A name isolates configuration, runtime files, durable sessions, hosts, snapshots, and logs from normal XDG locations and from other names. Invocations using the same name intentionally share a daemon and files; use separate names for side-by-side binaries.
128 128
129+If the runtime path would exceed the portable Unix-socket limit (103 bytes, including `vev/broker/broker.sock`), sockets and lifecycle locks instead use `/tmp/vev-<uid>-<sha256>`; the hash identifies the complete original runtime directory. Configuration and durable state remain under `.dev/<name>/`. All roles resolve the same private runtime directory, and distinct roots remain isolated. Short paths keep their existing location. Stop processes using an older binary before switching versions if their runtime location changes; do not delete a live runtime directory or its lock files.
130+
129 This feature isolates vev-owned data only. It does not sandbox pane processes or provide OS, filesystem, or network isolation.131 This feature isolates vev-owned data only. It does not sandbox pane processes or provide OS, filesystem, or network isolation.
130 132
131 ## Remote hosts133 ## Remote hosts
M docs/protocol.md +6 -7
@@ -17,8 +17,7 @@ Client → server: `Hello`, `Input`, `Resize`, `Detach`, `Ping`, `List`,
1717 `RecentRouteSnapshot`, `RouteNavigationFailure`,
1818 `SessionCreationFailure`, `UIFence`, `NavigationInventoryRequest`,
1919 `NavigationInventoryPublication`, `NavigationInventoryFailure`,
20-`PickerBegin`, `PickerClose`, `PickerSelection`, `PickerPreviewRequest`,
21-`PickerControlRequest`.
20+`PickerClose`, `PickerSelection`, `PickerPreviewRequest`.
2221
2322 Server → client: `Welcome`, `ErrorMsg`, `Output`, `Detached`, `Pong`,
2423 `Sessions`, `CommandResult`, `AttachTarget`, `RemotePreview`,
@@ -28,7 +27,7 @@ Server → client: `Welcome`, `ErrorMsg`, `Output`, `Detached`, `Pong`,
2827 `NavigationInventoryResponse`, `NavigationInventoryDemand`,
2928 `NavigationInventorySelection`, `PickerOffer`, `PickerSnapshot`,
3029 `PickerClosed`, `PickerResult`, `PickerFailure`, `PickerPreview`,
31-`PickerControlResponse`, `KillResult`.
30+`KillResult`.
3231
3332 `RouteNavigationFailure` is the one payload that appears in both unions.
3433 `sessionwire` conversion switches (`decodeProtoClient`/`decodeProtoServer`,
@@ -68,7 +67,7 @@ negotiated ceiling.
6867
6968 First frame routing (`daemon.go:handleConn`): `Hello` → full attach;
7069 `List`, `CommandRequest`, `RemotePreviewRequest`,
71-`NavigationInventoryRequest`, `PickerControlRequest`, `Kill` are one-shot
70+`NavigationInventoryRequest`, `Kill` are one-shot
7271 control connections answered and closed. Anything else, including any
7372 malformed first frame, gets a typed compatibility response and no session
7473 mutation:
@@ -84,9 +83,9 @@ mutation:
8483 | other / unknown | `ErrorMsg{ErrInternal, "expected hello"}` | no |
8584
8685 Live checks after decode: `Hello.Version`, `CommandRequest.Version`,
87-`NavigationInventoryRequest.Version`, `PickerControlRequest.Version`,
88-and `RemotePreviewRequest.Version` (independent catalogue-era schema
89-version `1`) must equal the negotiated constant or the request fails
86+`NavigationInventoryRequest.Version`, and `RemotePreviewRequest.Version`
87+(independent catalogue-era schema version `1`) must equal the negotiated
88+constant or the request fails
9089 with the version-mismatch response above. There is no multi-version
9190 negotiation: equality is mandatory.
9291
@@ -17,8 +17,7 @@ Client → server: `Hello`, `Input`, `Resize`, `Detach`, `Ping`, `List`,
17 `RecentRouteSnapshot`, `RouteNavigationFailure`,17 `RecentRouteSnapshot`, `RouteNavigationFailure`,
18 `SessionCreationFailure`, `UIFence`, `NavigationInventoryRequest`,18 `SessionCreationFailure`, `UIFence`, `NavigationInventoryRequest`,
19 `NavigationInventoryPublication`, `NavigationInventoryFailure`,19 `NavigationInventoryPublication`, `NavigationInventoryFailure`,
20-`PickerBegin`, `PickerClose`, `PickerSelection`, `PickerPreviewRequest`,20+`PickerClose`, `PickerSelection`, `PickerPreviewRequest`.
21-`PickerControlRequest`.
22 21
23 Server → client: `Welcome`, `ErrorMsg`, `Output`, `Detached`, `Pong`,22 Server → client: `Welcome`, `ErrorMsg`, `Output`, `Detached`, `Pong`,
24 `Sessions`, `CommandResult`, `AttachTarget`, `RemotePreview`,23 `Sessions`, `CommandResult`, `AttachTarget`, `RemotePreview`,
@@ -28,7 +27,7 @@ Server → client: `Welcome`, `ErrorMsg`, `Output`, `Detached`, `Pong`,
28 `NavigationInventoryResponse`, `NavigationInventoryDemand`,27 `NavigationInventoryResponse`, `NavigationInventoryDemand`,
29 `NavigationInventorySelection`, `PickerOffer`, `PickerSnapshot`,28 `NavigationInventorySelection`, `PickerOffer`, `PickerSnapshot`,
30 `PickerClosed`, `PickerResult`, `PickerFailure`, `PickerPreview`,29 `PickerClosed`, `PickerResult`, `PickerFailure`, `PickerPreview`,
31-`PickerControlResponse`, `KillResult`.30+`KillResult`.
32 31
33 `RouteNavigationFailure` is the one payload that appears in both unions.32 `RouteNavigationFailure` is the one payload that appears in both unions.
34 `sessionwire` conversion switches (`decodeProtoClient`/`decodeProtoServer`,33 `sessionwire` conversion switches (`decodeProtoClient`/`decodeProtoServer`,
@@ -68,7 +67,7 @@ negotiated ceiling.
68 67
69 First frame routing (`daemon.go:handleConn`): `Hello` → full attach;68 First frame routing (`daemon.go:handleConn`): `Hello` → full attach;
70 `List`, `CommandRequest`, `RemotePreviewRequest`,69 `List`, `CommandRequest`, `RemotePreviewRequest`,
71-`NavigationInventoryRequest`, `PickerControlRequest`, `Kill` are one-shot70+`NavigationInventoryRequest`, `Kill` are one-shot
72 control connections answered and closed. Anything else, including any71 control connections answered and closed. Anything else, including any
73 malformed first frame, gets a typed compatibility response and no session72 malformed first frame, gets a typed compatibility response and no session
74 mutation:73 mutation:
@@ -84,9 +83,9 @@ mutation:
84 | other / unknown | `ErrorMsg{ErrInternal, "expected hello"}` | no |83 | other / unknown | `ErrorMsg{ErrInternal, "expected hello"}` | no |
85 84
86 Live checks after decode: `Hello.Version`, `CommandRequest.Version`,85 Live checks after decode: `Hello.Version`, `CommandRequest.Version`,
87-`NavigationInventoryRequest.Version`, `PickerControlRequest.Version`,86+`NavigationInventoryRequest.Version`, and `RemotePreviewRequest.Version`
88-and `RemotePreviewRequest.Version` (independent catalogue-era schema87+(independent catalogue-era schema version `1`) must equal the negotiated
89-version `1`) must equal the negotiated constant or the request fails88+constant or the request fails
90 with the version-mismatch response above. There is no multi-version89 with the version-mismatch response above. There is no multi-version
91 negotiation: equality is mandatory.90 negotiation: equality is mandatory.
92 91

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