╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │243 const tag = document.activeElement.tagName; │ │244 const isArrow = event.key === "ArrowUp" || event.key === "ArrowDown" ||│ │245 event.key === "ArrowLeft" || event.key === "ArrowRight"; │ │246 + if (tag === "INPUT" && document.activeElement.readOnly && │ │247 + ["Enter", "ArrowLeft", "ArrowRight"].includes(event.key)) { │ │248 + event.preventDefault(); │ │249 + if (event.key === "Enter") { │ │250 + document.activeElement.select(); │ │251 + return; │ │252 + } │ │253 + wasmInstance.exports._onKeyDown(event.keyCode); │ │254 + wasmInstance.exports._tick(minRows(), maxCols()); │ │255 + return; │ │256 + } │ │257 if (tag === "INPUT" && event.key === "Enter") { │ │258 // enter in a text input never submits the form; only the │ │259 // submit button does │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │308 const t = event.target; │ │309 if (!t || (t.tagName !== "INPUT" && t.tagName !== "TEXTAREA") || !t.dataset.focusId) return;│ │310 sendTextInputValue(Number(t.dataset.focusId), t.value); │ │311 + }); │ │312 + │ │313 + document.addEventListener("click", (event) => { │ │314 + const t = event.target; │ │315 + if (t && t.tagName === "INPUT" && t.readOnly) t.select(); │ │316 }); │ │317 │ │318 document.addEventListener("focusin", (event) => { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │52 // apply actions queued during input. the wasm path has no repo, so this is│ │53 // in-memory only; logged-in web persistence goes through the /ansi POST. │ │54 session.applyPending(); │ │55 - │ │55 // bind the UI to the browser viewport (cols x rows) like the terminal: │ │56 // min == max height fills it exactly, and each Scroll clips to its │ │57 // viewport while handing its full content to a native-scrollable element. │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │168 extern fn _focusInput(focus_id: u32) void; │ │169 extern fn _navigate(arg: [*]const u8, len: u32) void; │ │170 extern fn _scrollToFocus(focus_id: u32) void; │ │172 - │ │171 /// js calls this first to get a wasm pointer it can write the page json into.│ │172 export fn _alloc(len: u32) ?[*]u8 { │ │173 const slice = allocator.alloc(u8, len) catch return null; │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │100 .admin_repo_path = admin_repo_path, │ │101 .repo_root_path = repo_root_path, │ │102 .wui_port = wui_server.socket.address.getPort(), │ │103 + .clone_http_port = http_server.socket.address.getPort(), │ │104 + .clone_ssh_port = ssh_server.socket.address.getPort(), │ │105 .err = err, │ │106 }; │ │107 serve_ssh.runListener(io, allocator, &host_key, &ssh_session_handler, &ssh_server, &tasks, err);│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │112 runWebListener(io, allocator, &wui_server, &tasks, .{ .remote = .{│ │113 .admin_repo_path = admin_repo_path, │ │114 .session_store = session_store, │ │115 + .clone_http_port = http_server.socket.address.getPort(), │ │116 + .clone_ssh_port = ssh_server.socket.address.getPort(), │ │117 } }, err); │ │118 │ │119 if (@TypeOf(runnable) != void) { │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │116 - try runnable.run(wui_server.socket.address.getPort(), ssh_server.socket.address.getPort()); │ │120 + try runnable.run(wui_server.socket.address.getPort(), http_server.socket.address.getPort(), ssh_server.socket.address.getPort());│ │121 } else { │ │122 try tasks.await(io); │ │123 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 admin_repo_path: []const u8, │ │27 repo_root_path: []const u8, │ │28 wui_port: u16, // port the web UI is served on, shown in the TUI footer's url │ │29 + clone_http_port: u16, │ │30 + clone_ssh_port: u16, │ │31 err: *std.Io.Writer, │ │32 │ │33 pub fn handleSession(self: *const SessionHandler, sess: *ssh.SessionCtx, request: ssh.Request) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │196 var ui_session = try ui.Session.init(&session_arena, &repo, .{}); │ │197 ui_session.is_terminal = true; │ │198 ui_session.web_port = handler.wui_port; │ │199 + ui_session.data.clone_http_port = handler.clone_http_port; │ │200 + ui_session.data.clone_ssh_port = handler.clone_ssh_port; │ │201 // let page builders open on-disk repos (sibling "repos" dir of the admin repo) │ │202 ui_session.io = io; │ │203 ui_session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ std.fs.path.dirname(handler.admin_repo_path) orelse ".", "repos" });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │212 // flushes, restoring the client's screen. as a function-scoped defer it runs │ │213 // before runTui returns — on the normal path and on any error — so the │ │214 // caller can write to the client afterward (and sess.exit can close cleanly). │ │211 - var terminal = try StreamTerminal.init(allocator, &session_writer.interface, .{ │ │215 + var terminal_size = Size{ │ │216 .width = effective_pty.width_cells, │ │217 .height = effective_pty.height_cells, │ │214 - }); │ │215 - defer terminal.deinit(); │ │218 + }; │ │219 + var terminal_maybe: ?StreamTerminal = try StreamTerminal.init(allocator, &session_writer.interface, terminal_size);│ │220 + defer if (terminal_maybe) |*terminal| terminal.deinit(); │ │221 │ │222 var last_size = Size{ .width = 0, .height = 0 }; │ │223 var last_grid = try Grid.init(allocator, last_size); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯