detached 37526a6

Format swarm code

commit
37526a628691317f0ec831e401e07ec733e3e2a9
author
patriceckhart · · 2026-05-16 12:01 UTC
parent
36f190a
signature
unsigned
8 files · +32 -32
M internal/agent/modes/swarm_dialog.go +8 -8
@@ -948,7 +948,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
948948 return d.renderTranscript(th, width)
949949 }
950950
951- out := []string{frameHeader(th, "swarm (n new · p prompt · R resume · ↑/↓ move · enter view · k kill · r remove · esc close)", width)}
951+ out := []string{frameHeader(th, "swarm (n new, p prompt, R resume, ↑/↓ move, enter view, k kill, r remove, esc close)", width)}
952952 if d.prompting {
953953 return d.renderPromptEditor(th, width, out)
954954 }
@@ -960,7 +960,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
960960 d.openModelPicker()
961961 }
962962 out = d.modelPicker.Render(th, width)
963- out = append(out, " "+th.FG256(th.Muted, "select model for next spawn · esc to cancel"))
963+ out = append(out, " "+th.FG256(th.Muted, "select model for next spawn, esc to cancel"))
964964 return out
965965 }
966966 if d.spawning {
@@ -998,7 +998,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
998998 // Matching blank row below before the hint, mirroring the
999999 // main input's editor breathing room.
10001000 out = append(out, "")
1001- out = append(out, " "+th.FG256(th.Muted, "enter spawn · /model pick model · @ file/dir picker · paste/drop paths become [file:] / [dir:] chips · esc cancel"))
1001+ out = append(out, " "+th.FG256(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))
10021002 out = append(out, frameRule(th, width))
10031003 return out
10041004 }
@@ -1031,11 +1031,11 @@ func (d *swarmDialog) renderTranscript(th tui.Theme, width int) []string {
10311031 return d.Render(th, width)
10321032 }
10331033 header := []string{
1034- frameHeader(th, "swarm: "+a.ID+" (type to send · esc back)", width),
1034+ frameHeader(th, "swarm: "+a.ID+" (type to send, esc back)", width),
10351035 " " + th.FG256(th.Muted, "task: "+a.Task),
10361036 " " + th.FG256(th.Muted, "branch: "+a.Branch),
10371037 " " + th.FG256(th.Muted, "dir: "+a.Dir),
1038- " " + th.FG256(th.Muted, fmt.Sprintf("status: %s · %s", a.Status, a.Activity)),
1038+ " " + th.FG256(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),
10391039 }
10401040 if a.Model != "" {
10411041 modelLine := "model: " + a.Model
@@ -1135,7 +1135,7 @@ func (d *swarmDialog) appendTranscriptEditor(out []string, th tui.Theme, width i
11351135 d.transcriptSpin.Start()
11361136 }
11371137 out = append(out, "")
1138- prefix := fmt.Sprintf("%s %s · %s",
1138+ prefix := fmt.Sprintf("%s %s, %s",
11391139 th.FG256(th.Assistant, d.transcriptSpin.Frame()),
11401140 th.FG256(th.Assistant, a.Activity),
11411141 th.FG256(th.Muted, d.transcriptSpin.Elapsed().String()),
@@ -1153,7 +1153,7 @@ func (d *swarmDialog) appendTranscriptEditor(out []string, th tui.Theme, width i
11531153 out = append(out, " "+l)
11541154 }
11551155 out = append(out, "")
1156- out = append(out, " "+th.FG256(th.Muted, "enter send · @ file/dir picker · esc back"))
1156+ out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc back"))
11571157 return out
11581158 }
11591159
@@ -1315,7 +1315,7 @@ func (d *swarmDialog) renderPromptEditor(th tui.Theme, width int, out []string)
13151315 }
13161316 // Matching blank row below before the hint.
13171317 out = append(out, "")
1318- out = append(out, " "+th.FG256(th.Muted, "enter send · @ file/dir picker · esc cancel"))
1318+ out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc cancel"))
13191319 out = append(out, frameRule(th, width))
13201320 return out
13211321 }
@@ -948,7 +948,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
948 return d.renderTranscript(th, width)948 return d.renderTranscript(th, width)
949 }949 }
950 950
951- out := []string{frameHeader(th, "swarm (n new · p prompt · R resume · ↑/↓ move · enter view · k kill · r remove · esc close)", width)}951+ out := []string{frameHeader(th, "swarm (n new, p prompt, R resume, ↑/↓ move, enter view, k kill, r remove, esc close)", width)}
952 if d.prompting {952 if d.prompting {
953 return d.renderPromptEditor(th, width, out)953 return d.renderPromptEditor(th, width, out)
954 }954 }
@@ -960,7 +960,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
960 d.openModelPicker()960 d.openModelPicker()
961 }961 }
962 out = d.modelPicker.Render(th, width)962 out = d.modelPicker.Render(th, width)
963- out = append(out, " "+th.FG256(th.Muted, "select model for next spawn · esc to cancel"))963+ out = append(out, " "+th.FG256(th.Muted, "select model for next spawn, esc to cancel"))
964 return out964 return out
965 }965 }
966 if d.spawning {966 if d.spawning {
@@ -998,7 +998,7 @@ func (d *swarmDialog) Render(th tui.Theme, width int) []string {
998 // Matching blank row below before the hint, mirroring the998 // Matching blank row below before the hint, mirroring the
999 // main input's editor breathing room.999 // main input's editor breathing room.
1000 out = append(out, "")1000 out = append(out, "")
1001- out = append(out, " "+th.FG256(th.Muted, "enter spawn · /model pick model · @ file/dir picker · paste/drop paths become [file:] / [dir:] chips · esc cancel"))1001+ out = append(out, " "+th.FG256(th.Muted, "enter spawn, /model pick model, @ file/dir picker, paste/drop paths become [file:] / [dir:] chips, esc cancel"))
1002 out = append(out, frameRule(th, width))1002 out = append(out, frameRule(th, width))
1003 return out1003 return out
1004 }1004 }
@@ -1031,11 +1031,11 @@ func (d *swarmDialog) renderTranscript(th tui.Theme, width int) []string {
1031 return d.Render(th, width)1031 return d.Render(th, width)
1032 }1032 }
1033 header := []string{1033 header := []string{
1034- frameHeader(th, "swarm: "+a.ID+" (type to send · esc back)", width),1034+ frameHeader(th, "swarm: "+a.ID+" (type to send, esc back)", width),
1035 " " + th.FG256(th.Muted, "task: "+a.Task),1035 " " + th.FG256(th.Muted, "task: "+a.Task),
1036 " " + th.FG256(th.Muted, "branch: "+a.Branch),1036 " " + th.FG256(th.Muted, "branch: "+a.Branch),
1037 " " + th.FG256(th.Muted, "dir: "+a.Dir),1037 " " + th.FG256(th.Muted, "dir: "+a.Dir),
1038- " " + th.FG256(th.Muted, fmt.Sprintf("status: %s · %s", a.Status, a.Activity)),1038+ " " + th.FG256(th.Muted, fmt.Sprintf("status: %s, %s", a.Status, a.Activity)),
1039 }1039 }
1040 if a.Model != "" {1040 if a.Model != "" {
1041 modelLine := "model: " + a.Model1041 modelLine := "model: " + a.Model
@@ -1135,7 +1135,7 @@ func (d *swarmDialog) appendTranscriptEditor(out []string, th tui.Theme, width i
1135 d.transcriptSpin.Start()1135 d.transcriptSpin.Start()
1136 }1136 }
1137 out = append(out, "")1137 out = append(out, "")
1138- prefix := fmt.Sprintf("%s %s · %s",1138+ prefix := fmt.Sprintf("%s %s, %s",
1139 th.FG256(th.Assistant, d.transcriptSpin.Frame()),1139 th.FG256(th.Assistant, d.transcriptSpin.Frame()),
1140 th.FG256(th.Assistant, a.Activity),1140 th.FG256(th.Assistant, a.Activity),
1141 th.FG256(th.Muted, d.transcriptSpin.Elapsed().String()),1141 th.FG256(th.Muted, d.transcriptSpin.Elapsed().String()),
@@ -1153,7 +1153,7 @@ func (d *swarmDialog) appendTranscriptEditor(out []string, th tui.Theme, width i
1153 out = append(out, " "+l)1153 out = append(out, " "+l)
1154 }1154 }
1155 out = append(out, "")1155 out = append(out, "")
1156- out = append(out, " "+th.FG256(th.Muted, "enter send · @ file/dir picker · esc back"))1156+ out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc back"))
1157 return out1157 return out
1158 }1158 }
1159 1159
@@ -1315,7 +1315,7 @@ func (d *swarmDialog) renderPromptEditor(th tui.Theme, width int, out []string)
1315 }1315 }
1316 // Matching blank row below before the hint.1316 // Matching blank row below before the hint.
1317 out = append(out, "")1317 out = append(out, "")
1318- out = append(out, " "+th.FG256(th.Muted, "enter send · @ file/dir picker · esc cancel"))1318+ out = append(out, " "+th.FG256(th.Muted, "enter send, @ file/dir picker, esc cancel"))
1319 out = append(out, frameRule(th, width))1319 out = append(out, frameRule(th, width))
1320 return out1320 return out
1321 }1321 }
M internal/agent/modes/swarm_dialog_test.go +2 -2
@@ -1074,7 +1074,7 @@ func TestSwarmTranscriptGrowsWithNewMessages(t *testing.T) {
10741074 // both the new content AND keep the old content (no clipping).
10751075 cur = []swarm.AgentSnapshot{{
10761076 ID: "agent-1", Task: "x", Status: swarm.StatusRunning,
1077- Lines: []string{"old line 1", "old line 2", "new reply A", "new reply B"},
1077+ Lines: []string{"old line 1", "old line 2", "new reply A", "new reply B"},
10781078 Started: cur[0].Started,
10791079 }}
10801080 out = strings.Join(d.Render(tui.Theme{}, 80), "\n")
@@ -1262,7 +1262,7 @@ func TestSwarmTranscriptBusySpinnerRenders(t *testing.T) {
12621262 t.Error("spinner stayed live after agent reported idle")
12631263 }
12641264 // The bare word "thinking" must not appear anymore.
1265- if strings.Contains(out, "· thinking ·") {
1265+ if strings.Contains(out, "· thinking, ") {
12661266 t.Errorf("busy line still rendered when agent is idle:\n%s", out)
12671267 }
12681268 }
@@ -1074,7 +1074,7 @@ func TestSwarmTranscriptGrowsWithNewMessages(t *testing.T) {
1074 // both the new content AND keep the old content (no clipping).1074 // both the new content AND keep the old content (no clipping).
1075 cur = []swarm.AgentSnapshot{{1075 cur = []swarm.AgentSnapshot{{
1076 ID: "agent-1", Task: "x", Status: swarm.StatusRunning,1076 ID: "agent-1", Task: "x", Status: swarm.StatusRunning,
1077- Lines: []string{"old line 1", "old line 2", "new reply A", "new reply B"},1077+ Lines: []string{"old line 1", "old line 2", "new reply A", "new reply B"},
1078 Started: cur[0].Started,1078 Started: cur[0].Started,
1079 }}1079 }}
1080 out = strings.Join(d.Render(tui.Theme{}, 80), "\n")1080 out = strings.Join(d.Render(tui.Theme{}, 80), "\n")
@@ -1262,7 +1262,7 @@ func TestSwarmTranscriptBusySpinnerRenders(t *testing.T) {
1262 t.Error("spinner stayed live after agent reported idle")1262 t.Error("spinner stayed live after agent reported idle")
1263 }1263 }
1264 // The bare word "thinking" must not appear anymore.1264 // The bare word "thinking" must not appear anymore.
1265- if strings.Contains(out, "· thinking ·") {1265+ if strings.Contains(out, "· thinking, ") {
1266 t.Errorf("busy line still rendered when agent is idle:\n%s", out)1266 t.Errorf("busy line still rendered when agent is idle:\n%s", out)
1267 }1267 }
1268 }1268 }
M internal/agent/modes/swarm_slash.go +2 -2
@@ -172,9 +172,9 @@ func (i *Interactive) runSwarm(ctx context.Context, args []string) {
172172 case 0:
173173 i.swarmStatus("", "/swarm resume: no resumable agents (none detached or terminated)")
174174 case 1:
175- i.swarmStatus("1 resumable agent · press R to resume", "")
175+ i.swarmStatus("1 resumable agent, press R to resume", "")
176176 default:
177- i.swarmStatus(fmt.Sprintf("%d resumable agents · ↑/↓ to pick · R to resume", count), "")
177+ i.swarmStatus(fmt.Sprintf("%d resumable agents, ↑/↓ to pick, R to resume", count), "")
178178 }
179179 return
180180 }
@@ -172,9 +172,9 @@ func (i *Interactive) runSwarm(ctx context.Context, args []string) {
172 case 0:172 case 0:
173 i.swarmStatus("", "/swarm resume: no resumable agents (none detached or terminated)")173 i.swarmStatus("", "/swarm resume: no resumable agents (none detached or terminated)")
174 case 1:174 case 1:
175- i.swarmStatus("1 resumable agent · press R to resume", "")175+ i.swarmStatus("1 resumable agent, press R to resume", "")
176 default:176 default:
177- i.swarmStatus(fmt.Sprintf("%d resumable agents · ↑/↓ to pick · R to resume", count), "")177+ i.swarmStatus(fmt.Sprintf("%d resumable agents, ↑/↓ to pick, R to resume", count), "")
178 }178 }
179 return179 return
180 }180 }
M internal/agent/swarm_agent.go +7 -7
@@ -11,8 +11,8 @@ import (
1111
1212 "github.com/patriceckhart/zot/internal/agent/modes"
1313 "github.com/patriceckhart/zot/internal/core"
14- "github.com/patriceckhart/zot/internal/swarm"
1514 "github.com/patriceckhart/zot/internal/provider"
15+ "github.com/patriceckhart/zot/internal/swarm"
1616 )
1717
1818 // runSwarmAgentMode is the daemon-mode entry point used by every
@@ -85,12 +85,12 @@ func runSwarmAgentMode(ctx context.Context, args Args, version string) error {
8585 // cancel so the "cancel" inbox message can interrupt an
8686 // in-flight turn without tearing down the whole daemon.
8787 var (
88- mu sync.Mutex
89- turnCtx context.Context = ctx
90- cancelFn context.CancelFunc
91- busyTurn bool
92- turnNo int
93- shutdown = make(chan struct{})
88+ mu sync.Mutex
89+ turnCtx context.Context = ctx
90+ cancelFn context.CancelFunc
91+ busyTurn bool
92+ turnNo int
93+ shutdown = make(chan struct{})
9494 )
9595
9696 runOne := func(prompt string) {
@@ -11,8 +11,8 @@ import (
11 11
12 "github.com/patriceckhart/zot/internal/agent/modes"12 "github.com/patriceckhart/zot/internal/agent/modes"
13 "github.com/patriceckhart/zot/internal/core"13 "github.com/patriceckhart/zot/internal/core"
14- "github.com/patriceckhart/zot/internal/swarm"
15 "github.com/patriceckhart/zot/internal/provider"14 "github.com/patriceckhart/zot/internal/provider"
15+ "github.com/patriceckhart/zot/internal/swarm"
16 )16 )
17 17
18 // runSwarmAgentMode is the daemon-mode entry point used by every18 // runSwarmAgentMode is the daemon-mode entry point used by every
@@ -85,12 +85,12 @@ func runSwarmAgentMode(ctx context.Context, args Args, version string) error {
85 // cancel so the "cancel" inbox message can interrupt an85 // cancel so the "cancel" inbox message can interrupt an
86 // in-flight turn without tearing down the whole daemon.86 // in-flight turn without tearing down the whole daemon.
87 var (87 var (
88- mu sync.Mutex88+ mu sync.Mutex
89- turnCtx context.Context = ctx89+ turnCtx context.Context = ctx
90- cancelFn context.CancelFunc90+ cancelFn context.CancelFunc
91- busyTurn bool91+ busyTurn bool
92- turnNo int92+ turnNo int
93- shutdown = make(chan struct{})93+ shutdown = make(chan struct{})
94 )94 )
95 95
96 runOne := func(prompt string) {96 runOne := func(prompt string) {
M internal/swarm/event.go +4 -4
@@ -28,10 +28,10 @@ import (
2828 // stat the file size and read from their last offset on every
2929 // poll.
3030 type Event struct {
31- Time time.Time `json:"time"`
32- Type string `json:"type"`
33- Data map[string]any `json:"-"`
34- Raw map[string]any `json:"-"` // includes type+time+data for replay
31+ Time time.Time `json:"time"`
32+ Type string `json:"type"`
33+ Data map[string]any `json:"-"`
34+ Raw map[string]any `json:"-"` // includes type+time+data for replay
3535 }
3636
3737 // MarshalJSON flattens Data into the top-level object so consumers
@@ -28,10 +28,10 @@ import (
28 // stat the file size and read from their last offset on every28 // stat the file size and read from their last offset on every
29 // poll.29 // poll.
30 type Event struct {30 type Event struct {
31- Time time.Time `json:"time"`31+ Time time.Time `json:"time"`
32- Type string `json:"type"`32+ Type string `json:"type"`
33- Data map[string]any `json:"-"`33+ Data map[string]any `json:"-"`
34- Raw map[string]any `json:"-"` // includes type+time+data for replay34+ Raw map[string]any `json:"-"` // includes type+time+data for replay
35 }35 }
36 36
37 // MarshalJSON flattens Data into the top-level object so consumers37 // MarshalJSON flattens Data into the top-level object so consumers
M internal/swarm/persist_test.go +5 -5
@@ -179,7 +179,7 @@ func TestReloadReplaysTranscriptFromEventLog(t *testing.T) {
179179 m := agentMeta{
180180 ID: id, Task: "do thing", Branch: "swarm/alpha-9",
181181 Dir: filepath.Join(root, "wt", id), Started: time.Now().Add(-time.Hour),
182- InboxPath: filepath.Join(stateDir, "in.sock"),
182+ InboxPath: filepath.Join(stateDir, "in.sock"),
183183 EventLogPath: filepath.Join(stateDir, "events.jsonl"),
184184 SessionPath: filepath.Join(stateDir, "session.json"),
185185 }
@@ -606,11 +606,11 @@ func TestStopOnDetachedAgentIsNoopAndDoesNotPanic(t *testing.T) {
606606 }
607607 m := agentMeta{
608608 ID: id, Task: "t", Branch: "swarm/" + id,
609- Dir: filepath.Join(root, "wt", id),
610- Started: time.Now().Add(-time.Hour),
611- InboxPath: filepath.Join(stateDir, "in.sock"),
609+ Dir: filepath.Join(root, "wt", id),
610+ Started: time.Now().Add(-time.Hour),
611+ InboxPath: filepath.Join(stateDir, "in.sock"),
612612 EventLogPath: filepath.Join(stateDir, "events.jsonl"),
613- SessionPath: filepath.Join(stateDir, "session.json"),
613+ SessionPath: filepath.Join(stateDir, "session.json"),
614614 }
615615 mb, _ := json.MarshalIndent(m, "", " ")
616616 if err := os.WriteFile(filepath.Join(stateDir, "meta.json"), mb, 0o644); err != nil {
@@ -179,7 +179,7 @@ func TestReloadReplaysTranscriptFromEventLog(t *testing.T) {
179 m := agentMeta{179 m := agentMeta{
180 ID: id, Task: "do thing", Branch: "swarm/alpha-9",180 ID: id, Task: "do thing", Branch: "swarm/alpha-9",
181 Dir: filepath.Join(root, "wt", id), Started: time.Now().Add(-time.Hour),181 Dir: filepath.Join(root, "wt", id), Started: time.Now().Add(-time.Hour),
182- InboxPath: filepath.Join(stateDir, "in.sock"),182+ InboxPath: filepath.Join(stateDir, "in.sock"),
183 EventLogPath: filepath.Join(stateDir, "events.jsonl"),183 EventLogPath: filepath.Join(stateDir, "events.jsonl"),
184 SessionPath: filepath.Join(stateDir, "session.json"),184 SessionPath: filepath.Join(stateDir, "session.json"),
185 }185 }
@@ -606,11 +606,11 @@ func TestStopOnDetachedAgentIsNoopAndDoesNotPanic(t *testing.T) {
606 }606 }
607 m := agentMeta{607 m := agentMeta{
608 ID: id, Task: "t", Branch: "swarm/" + id,608 ID: id, Task: "t", Branch: "swarm/" + id,
609- Dir: filepath.Join(root, "wt", id),609+ Dir: filepath.Join(root, "wt", id),
610- Started: time.Now().Add(-time.Hour),610+ Started: time.Now().Add(-time.Hour),
611- InboxPath: filepath.Join(stateDir, "in.sock"),611+ InboxPath: filepath.Join(stateDir, "in.sock"),
612 EventLogPath: filepath.Join(stateDir, "events.jsonl"),612 EventLogPath: filepath.Join(stateDir, "events.jsonl"),
613- SessionPath: filepath.Join(stateDir, "session.json"),613+ SessionPath: filepath.Join(stateDir, "session.json"),
614 }614 }
615 mb, _ := json.MarshalIndent(m, "", " ")615 mb, _ := json.MarshalIndent(m, "", " ")
616 if err := os.WriteFile(filepath.Join(stateDir, "meta.json"), mb, 0o644); err != nil {616 if err := os.WriteFile(filepath.Join(stateDir, "meta.json"), mb, 0o644); err != nil {
M internal/swarm/swarm.go +1 -1
@@ -509,7 +509,7 @@ func (f *Swarm) SnapshotAll() []AgentSnapshot {
509509 // agentSink is the Sink the Swarm hands to each Runner.
510510 type agentSink struct{ a *Agent }
511511
512-func (s agentSink) Activity(msg string) { s.a.setActivity(msg) }
512+func (s agentSink) Activity(msg string) { s.a.setActivity(msg) }
513513 func (s agentSink) Transcript(chunk string) { s.a.appendTranscript(chunk) }
514514
515515 func truncate(s string, n int) string {
@@ -509,7 +509,7 @@ func (f *Swarm) SnapshotAll() []AgentSnapshot {
509 // agentSink is the Sink the Swarm hands to each Runner.509 // agentSink is the Sink the Swarm hands to each Runner.
510 type agentSink struct{ a *Agent }510 type agentSink struct{ a *Agent }
511 511
512-func (s agentSink) Activity(msg string) { s.a.setActivity(msg) }512+func (s agentSink) Activity(msg string) { s.a.setActivity(msg) }
513 func (s agentSink) Transcript(chunk string) { s.a.appendTranscript(chunk) }513 func (s agentSink) Transcript(chunk string) { s.a.appendTranscript(chunk) }
514 514
515 func truncate(s string, n int) string {515 func truncate(s string, n int) string {
M internal/swarm/swarm_test.go +3 -3
@@ -207,9 +207,9 @@ func TestSnapshotIsStableAcrossAccess(t *testing.T) {
207207
208208 func TestTaskSlug(t *testing.T) {
209209 cases := map[string]string{
210- "fix the login form": "fix-the-login-form",
211- " weird --- spaces!! ": "weird-spaces",
212- "": "agent",
210+ "fix the login form": "fix-the-login-form",
211+ " weird --- spaces!! ": "weird-spaces",
212+ "": "agent",
213213 "a-very-long-task-name-that-overflows": "a-very-long-task-name-th",
214214 }
215215 for in, want := range cases {
@@ -207,9 +207,9 @@ func TestSnapshotIsStableAcrossAccess(t *testing.T) {
207 207
208 func TestTaskSlug(t *testing.T) {208 func TestTaskSlug(t *testing.T) {
209 cases := map[string]string{209 cases := map[string]string{
210- "fix the login form": "fix-the-login-form",210+ "fix the login form": "fix-the-login-form",
211- " weird --- spaces!! ": "weird-spaces",211+ " weird --- spaces!! ": "weird-spaces",
212- "": "agent",212+ "": "agent",
213 "a-very-long-task-name-that-overflows": "a-very-long-task-name-th",213 "a-very-long-task-name-that-overflows": "a-very-long-task-name-th",
214 }214 }
215 for in, want := range cases {215 for in, want := range cases {

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 1

Tags 476

↑↓ moveenter openesc close