╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#53cdbb77e7b4316ccfe033499316c72991acc52a",│ │ 7 - .hash = "xit-0.0.0-Ub3nDFomVAG7x7MdcyRL74mlr1KHjSaL6h2q6Co8zrH6", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#67e37f5c37a04db17f5d985424639b7a20157c85",│ │ 7 + .hash = "xit-0.0.0-Ub3nDFksVAFHFM9QQaSYFCL7OjF5PVKwfIkNiursLxn1", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 8 name: []const u8, │ │ 9 email: []const u8, │ │10 password_hash: []const u8, │ │11 -enable_ansi: bool = true, │ │11 ssh_keys: []const u8 = "", // newline-separated authorized_keys lines (one OpenSSH public key per line)│ │12 │ │13 // what the db stores: the event's data plus the commit-derived fields │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │244 const user_id = try readIdByName(evt.AdminDB, evt.admin_repo_opts.hash, moment, name) orelse return null; │ │245 │ │246 return try readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, &user_id); │ │248 -} │ │249 - │ │250 -// flip a user's ANSI-art preference by re-emitting their User event with │ │251 -// enable_ansi negated. a no-op for an unknown user. `repo` must be writable. │ │252 -pub fn toggleAnsi( │ │253 - comptime repo_opts: rp.RepoOpts(.xit), │ │254 - io: std.Io, │ │255 - allocator: std.mem.Allocator, │ │256 - repo: *rp.Repo(.xit, repo_opts), │ │257 - users_dir: []const u8, │ │258 - user_id: []const u8, │ │259 -) !void { │ │260 - const DB = rp.Repo(.xit, repo_opts).DB; │ │261 - var arena = std.heap.ArenaAllocator.init(allocator); │ │262 - defer arena.deinit(); │ │263 - │ │264 - const moment = try evt.currentMoment(repo_opts, repo); │ │265 - const user = (try readById(DB, repo_opts.hash, moment, &arena, user_id)) orelse return; │ │266 - │ │267 - var updated = user.event; │ │268 - updated.enable_ansi = !updated.enable_ansi; │ │269 - try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .admin, .xit, repo_opts, io, allocator, repo, evt.events_ref, &[_]evt.EventWithId{.{│ │270 - .id = std.fmt.bytesToHex(user_id[0..evt.event_id_size].*, .lower), │ │271 - .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │272 - .author = .{ .name = user.event.name, .email = user.event.email }, │ │273 - .event = .{ .user = updated }, │ │274 - }}); │ │247 } │ │248 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │49 fn tick(min_height: u32, max_width: u32) !void { │ │50 const root_ptr = if (root) |*root_value| root_value else return error.NotStarted;│ │51 │ │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(); │ │52 // a widget asked to move to another page, so hand it to the host instead of │ │53 // rendering this one again. │ │54 if (session.next_page) |route| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │251 defer if (terminal_maybe) |*terminal| terminal.deinit(); │ │252 │ │253 // initial render — user sees the page immediately │ │254 - if (terminal_maybe) |*terminal| _ = try terminal.render(&nav.root); │ │254 + if (terminal_maybe) |*terminal| { │ │255 + try terminal.queryBackground(); │ │256 + _ = try terminal.render(&nav.root); │ │257 + } │ │258 │ │259 // event loop. nextEvent blocks until something interesting arrives;│ │260 // for each event, rebuild the widget tree and re-render so the user│ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │299 } │ │300 } │ │301 │ │299 - try ui_session.applyAndWritePending(io, allocator, &repo); │ │300 - │ │302 // pick up data written by other handles so the next navigation│ │303 // builds its page from a current moment │ │304 try ui_session.reloadMoment(allocator, &repo); │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │305 │ │305 - // reconcile navigation: forward to a new page, or back on escape │ │306 - try nav.sync(allocator, &ui_session); │ │306 + // reconcile navigation: forward to a new page, or back on escape. │ │307 + // the terminal background can change at any time, so recheck it on each page│ │308 + if (try nav.sync(allocator, &ui_session)) { │ │309 + if (terminal_maybe) |*terminal| try terminal.queryBackground(); │ │310 + } │ │311 │ │312 // the quit button (on the quit tab) asks the host to tear down │ │313 if (ui_session.quit_requested) break :event_loop; │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │320 } │ │321 } │ │322 │ │319 -// only a lone ESC needs a deadline; longer CSI/SS3 fragments must remain │ │320 -// buffered until complete. a timeout leaves partial SSH packets in the reader. │ │323 +// only ambiguous input (a lone ESC or ESC ]) needs a deadline; longer │ │324 +// fragments must remain buffered until complete. a timeout leaves partial SSH │ │325 +// packets in the reader. │ │326 pub fn nextTuiEvent(sess: *ssh.SessionCtx, terminal: ?*StreamTerminal) !?ssh.Event {│ │327 if (terminal) |t| { │ │323 - if (t.parser.esc_len == 1) { │ │328 + if (t.parser.isAmbiguous()) { │ │329 sess.conn.read_timeout = escape_timeout.toDeadline(sess.conn.io); │ │330 defer sess.conn.read_timeout = .none; │ │331 return sess.nextEvent() catch |err| switch (err) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │2149 // (haxy invoked with no arguments inside a repo). null on the server paths, │ │2150 // which resolve repos from the admin db instead. │ │2151 local: ?RepoSource = null, │ │2152 - pending: std.ArrayList(Action) = .empty, // actions queued by widgets this frame│ │2152 // focus id -> the live TextInput, refreshed each frame by the views that own │ │2153 // inputs. web/wasm form handling looks widgets up here by focus id. │ │2154 text_inputs: std.AutoHashMapUnmanaged(usize, *wgt.TextInput) = .empty, │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │2160 // does not navigate. │ │2161 next_page: ?RoutablePage = null, │ │2162 is_terminal: bool = false, // true on remote SSH and local TUI │ │2163 + // the terminal's reported default background, which the art blends toward │ │2164 + terminal_background: ?Grid.Color.Rgb = null, │ │2165 // port the web UI is served on, for the TUI/SSH footer's "http://localhost:<port>..."│ │2166 // url. null on the web itself (no footer there). │ │2167 web_port: ?u16 = null, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯