This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Show remote create new session when applicable

Nathan Herald (Apr 15, 2026, 10:26 AM +0200) bdebedeb ea404018

+111 -5
+111 -5
src/tui/interactive.ts
··· 43 43 const sessions = signal<SessionInfo[]>([]); 44 44 const filterText = signal(""); 45 45 const selectedIndex = signal(0); 46 - const currentScreen = signal<"list" | "create">("list"); 46 + const currentScreen = signal<"list" | "create" | "remote-create">("list"); 47 47 48 48 // Theme — persisted to ~/.local/state/pty/theme 49 49 const themeNames = Object.keys(themes); ··· 85 85 const nameManuallyEdited = signal(false); 86 86 const focusedField = signal<"name" | "command">("command"); 87 87 const existingNames = signal<Set<string>>(new Set()); 88 + 89 + // Remote create wizard state 90 + const remoteCreateHost = signal<RelayHost | null>(null); 91 + const remoteSessionName = signal(""); 92 + const remoteSessionCommand = signal("bash"); 93 + const remoteFocusedField = signal<"name" | "command">("name"); 88 94 89 95 // ============================================================ 90 96 // Relay integration ··· 136 142 // ============================================================ 137 143 138 144 interface ListItem { 139 - type: "session" | "create" | "remote"; 145 + type: "session" | "create" | "remote" | "remote-create"; 140 146 session?: SessionInfo; 141 147 remote?: { host: RelayHost; session: RemoteSession }; 148 + remoteHost?: RelayHost; 142 149 } 143 150 144 151 const sortedSessions = computed<SessionInfo[]>(() => sortSessions(sessions.get())); ··· 198 205 remote: { host, session: s }, 199 206 })); 200 207 const filtered = filter ? filterAndSort(filter, remoteItems) : remoteItems; 201 - if (filtered.length > 0 || !filter) { 202 - groups.push({ title: host.label, items: filtered }); 208 + const items: ListItem[] = [...filtered]; 209 + if (host.spawn_enabled) { 210 + items.push({ type: "remote-create", remoteHost: host }); 211 + } 212 + if (items.length > 0 || !filter) { 213 + groups.push({ title: host.label, items }); 203 214 } 204 215 } 205 216 ··· 219 230 const sel = selected ? "\u25b8 " : " "; 220 231 if (item.type === "create") { 221 232 return [text(sel + "+ Create new session...", selected ? "accent" : "muted", { bold: selected, truncate: true })]; 233 + } 234 + 235 + if (item.type === "remote-create") { 236 + return [text(sel + "+ Spawn remote session...", selected ? "accent" : "muted", { bold: selected, truncate: true })]; 222 237 } 223 238 224 239 if (item.type === "remote" && item.remote) { ··· 336 351 }); 337 352 return true; 338 353 } 354 + if (item.type === "remote-create" && item.remoteHost) { 355 + batch(() => { 356 + remoteCreateHost.set(item.remoteHost!); 357 + remoteSessionName.set(""); 358 + remoteSessionCommand.set("bash"); 359 + remoteFocusedField.set("name"); 360 + currentScreen.set("remote-create"); 361 + }); 362 + return true; 363 + } 339 364 if (item.type === "remote" && item.remote) { 340 365 doAttachRemote(item.remote.host, item.remote.session); 341 366 return true; ··· 678 703 })(); 679 704 } 680 705 706 + function doSpawnRemote(host: RelayHost, name: string): void { 707 + if (!relayBin) return; 708 + myApp?.pause(); 709 + 710 + const result = spawnSync(relayBin, ["connect", host.url, "--spawn", name], { 711 + stdio: "inherit", 712 + }); 713 + 714 + (async () => { 715 + const updated = await listSessions(); 716 + refreshRelayHosts(); 717 + batch(() => { 718 + sessions.set(updated); 719 + currentScreen.set("list"); 720 + filterText.set(""); 721 + const maxIdx = Math.max(0, totalItems.get() - 1); 722 + if (selectedIndex.peek() > maxIdx) selectedIndex.set(maxIdx); 723 + }); 724 + myApp?.resume(); 725 + })(); 726 + } 727 + 681 728 function doAttach(name: string): void { 682 729 myApp?.pause(); 683 730 attach({ ··· 770 817 } 771 818 772 819 // ============================================================ 820 + // Remote create screen 821 + // ============================================================ 822 + 823 + const remoteCreateScreen = screen({ 824 + id: "remote-create", 825 + 826 + render(_ctx: ScreenContext): UINode[] { 827 + const host = remoteCreateHost.get(); 828 + if (!host) return [text("No host selected", "error")]; 829 + 830 + const name = remoteSessionName.get(); 831 + 832 + return [ 833 + panel(`Spawn on ${host.label}`, [ 834 + text("", "muted"), 835 + text(" Session name: " + name + "\u2588", "primary"), 836 + text("", "muted"), 837 + text(" Enter to spawn, Escape to cancel", "muted", { dim: true }), 838 + ]), 839 + ]; 840 + }, 841 + 842 + handleKey(key: KeyEvent, _ctx: ScreenContext): boolean { 843 + if (key.name === "escape") { 844 + batch(() => { 845 + currentScreen.set("list"); 846 + remoteCreateHost.set(null); 847 + }); 848 + return true; 849 + } 850 + 851 + if (key.name === "return") { 852 + const host = remoteCreateHost.peek(); 853 + const name = remoteSessionName.peek().trim(); 854 + if (!host || !name) return true; 855 + doSpawnRemote(host, name); 856 + return true; 857 + } 858 + 859 + if (key.name === "backspace") { 860 + remoteSessionName.set(remoteSessionName.peek().slice(0, -1)); 861 + return true; 862 + } 863 + 864 + if (key.char && !key.ctrl && !key.alt) { 865 + remoteSessionName.set(remoteSessionName.peek() + key.char); 866 + return true; 867 + } 868 + 869 + return true; 870 + }, 871 + }); 872 + 873 + // ============================================================ 773 874 // Entry point 774 875 // ============================================================ 775 876 ··· 781 882 refreshRelayHosts(); 782 883 783 884 myApp = app({ 784 - screen: () => currentScreen.get() === "list" ? listScreen : createScreen, 885 + screen: () => { 886 + const s = currentScreen.get(); 887 + if (s === "remote-create") return remoteCreateScreen; 888 + if (s === "create") return createScreen; 889 + return listScreen; 890 + }, 785 891 theme: () => currentTheme(), 786 892 onKey: (key) => { 787 893 if (key.name === "g" && key.ctrl) { cycleTheme(); return true; }