tui: after /cd, offer the /link add that links the folder the session left
a notice with the folder as stored, ready to paste; a later move replaces it, and a move away from a missing folder offers nothing. Signed-off-by: dawn <90008@klbr.net>
120 unmodified linesExpand all12112212312412512612712812512612712812913013113213310 unmodified lines144145146147148149150151152153154155156157158162 unmodified linesExpand all120 unmodified linesExpand allhost in every layout that shows a place, and colors the host with a stablehue derived from its label, lifted to the theme's contrast. the host goesfaint while the session's kernel boots or reattaches (the status line says`connecting to chernobog…` with an animated face, the same one the pickershows beside a host still warming, picked by the host's label) and takes the error color once it is lost (theport owner gave up and forgot the kernel, so the status line says the nextturn starts a fresh one), fromthe session status's `kernel.link` (kernel.md). recent folders and theshows beside a host still warming, picked by the host's label, or `copyingthe kernel to chernobog…` while albedo is staged there) and takes the errorcolor once it is lost (the port owner gave up and forgot the kernel, so thestatus line says the next turn starts a fresh one), from the sessionstatus's `kernel.link` (kernel.md). recent folders and thesessions view lead remote entries with their host.
## moving a session
10 unmodified linesonce; a running one moves when its current turn ends. descendants that wereworking somewhere else keep their own folder. if the session itself cannotmove, nobody moves.
after a move the tui offers linking the folder the session left, as a noticeto paste: `/link add <folder as stored>` (absolute, `user@` kept, so the linklands on the key that folder's memory and work items use). a later movereplaces it, and a move away from a folder that went missing offers nothing.it does not check whether the two are already linked; `/link add` refusesthat.
## browsing
all three routes take `path`: absolute, or starting with `~`, which expandsto the daemon's home, or a remote `[user@]host:/abs` or `[user@]host:~/x`162 unmodified linesExpand all1645 unmodified linesExpand all16461647164816491650165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679857 unmodified linesExpand all1645 unmodified linesExpand all
// Moved takes the session's new folder, then sends again the turn a// missing folder refused.func (m *ChatModel) Moved(moved daemon.Session, retry *WorkspaceRetry) tea.Cmd { workspace := moved.Workspace workspace, from := moved.Workspace, m.Workspace m.dropSignIn() // the offer was for the old folder's host // a hint from an earlier move names a folder this one did not leave m.Notices = slices.DeleteFunc(m.Notices, func(n Notice) bool { return strings.HasPrefix(n.Message, linkHintText) }) m.Notices.AddNotice(linkHint(from, workspace, retry)) m.syncViewportHeight() m.Workspace, m.Host, m.hostHome = workspace, sessionHost(moved), "" m.Renderer.Workspace = workspace m.rebuildSettledLines() // settled rows name paths from the old workspace m.refreshViewportContent() return m.resend(retry)}
const linkHintText = "to share memory and work with the folder you left: /link add "
// linkHint offers linking the folder a session just left with the one it// moved to, as a command to paste. The folder is named as stored, so the// link lands on the key the left folder's memory and work items use. A// session that moved because its folder went missing has nothing to link.func linkHint(from, to string, retry *WorkspaceRetry) string { if from == "" || from == to || retry != nil { return "" } return linkHintText + from}
// resend sends again a turn the daemon refused, once what refused it is fixed.func (m *ChatModel) resend(retry *WorkspaceRetry) tea.Cmd { if retry == nil { return nil857 unmodified linesExpand all60 unmodified lines61626364656667686970717273747576777879808182277 unmodified linesExpand all60 unmodified lines d.Dispatch(tui.ChatExecuteCommandMsg{Name: "/cd", Args: "../other"}) if got := daemonSession(t, id).Workspace; !same(got, filepath.Join(root, "other")) || !same(d.App.Chat.Workspace, got) { t.Fatalf("/cd ../other left the daemon at %q and the chat at %q", got, d.App.Chat.Workspace) } // The chat offers linking the folder it left, as a command to paste. view := d.View() _, offered, ok := strings.Cut(view, "/link add ") if !ok { t.Fatalf("moving did not offer /link add:\n%s", view) } offered = strings.Fields(offered)[0] if !same(offered, filepath.Join(root, "picked")) { t.Fatalf("the hint offers %q, not the folder the session left", offered) } d.Dispatch(tui.ChatExecuteCommandMsg{Name: "/link", Args: "add " + offered}) if view := d.View(); !strings.Contains(view, "linked with "+offered) { t.Fatalf("pasting the hint did not link the folders:\n%s", view) }}
func TestTUIMissingWorkspaceSendsTheTurnFromThePickedFolder(t *testing.T) { t.Parallel()277 unmodified linesExpand all