╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │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, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │2298 // a failed terminal undo retained on the current tab │ │2299 undo_failure: ?[]const u8 = null, │ │2300 current_page: RoutablePage = .default, │ │2300 - // whether to render the ANSI art backdrop │ │2301 - enable_ansi: bool = true, │ │2301 // the selected ANSI art. the server serializes one image for WASM so│ │2302 // the browser does not need the whole build-generated collection. │ │2303 ansi_art: []const u8 = "", │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │2311 // the wasm side also parses elided link urls and hides the multi-user │ │2312 // chrome. │ │2313 host_kind: evt.HostKind = .server, │ │2315 - }; │ │2316 - │ │2317 - // a user-initiated state change. widgets enqueue these on the session during │ │2318 - // input instead of mutating state or touching the DB themselves; the host │ │2319 - // drains them each frame (see applyAndWritePending / applyPending). this keeps DB│ │2320 - // side effects out of the widget tree and gives every render path one place to │ │2321 - // turn a UI action into a state change + event. │ │2322 - pub const Action = union(enum) { │ │2323 - toggle_ansi, │ │2314 }; │ │2315 │ │2316 pub fn init( │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2338 const moment = self.haxy_moment orelse return; │ │2339 const user = (try activeUser(moment, self.arena, user_id)) orelse return self.logOut();│ │2340 self.data.user_name = user.event.name; │ │2351 - self.data.enable_ansi = user.event.enable_ansi; │ │2341 } │ │2342 │ │2343 // forget the logged-in user, on a logout or once their account is removed │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2394 .local => .local, │ │2395 .server => .{ .server = .{ .users_dir = self.users_dir orelse unreachable } }, │ │2396 }; │ │2408 - } │ │2409 - │ │2410 - // queue an action for the host to drain this frame. │ │2411 - pub fn push(self: *Self, action: Action) !void { │ │2412 - try self.pending.append(self.arena.allocator(), action); │ │2397 } │ │2398 │ │2415 - // apply a single action's in-memory effect only (no persistence). │ │2416 - fn apply(self: *Self, action: Action) void { │ │2417 - switch (action) { │ │2418 - .toggle_ansi => self.data.enable_ansi = !self.data.enable_ansi, │ │2419 - } │ │2420 - } │ │2421 - │ │2422 - // drain the session's queued actions by applying them to the session. │ │2423 - // used on the wasm path, which has no repo to persist to. │ │2424 - pub fn applyPending(self: *Self) void { │ │2425 - for (self.pending.items) |action| self.apply(action); │ │2426 - self.pending.clearRetainingCapacity(); │ │2427 - } │ │2428 - │ │2429 - // drain the session's queued actions by applying them to the session and │ │2430 - // writing them to the db │ │2431 - pub fn applyAndWritePending( │ │2432 - self: *Self, │ │2433 - io: std.Io, │ │2434 - allocator: std.mem.Allocator, │ │2435 - repo: *rp.Repo(.xit, evt.admin_repo_opts), │ │2436 - ) !void { │ │2437 - defer self.pending.clearRetainingCapacity(); │ │2438 - for (self.pending.items) |action| { │ │2439 - self.apply(action); │ │2440 - switch (action) { │ │2441 - .toggle_ansi => if (self.data.user_id) |user_id| { │ │2442 - try evt.User.toggleAnsi(evt.admin_repo_opts, io, allocator, repo, self.users_dir orelse unreachable, user_id);│ │2443 - }, │ │2444 - } │ │2445 - } │ │2446 - } │ │2447 - │ │2448 - // reload the moment from the admin repo and re-read user preferences │ │2399 + // reload the moment from the admin repo and log out a removed user │ │2400 pub fn reloadMoment(self: *Self, allocator: std.mem.Allocator, repo: *rp.Repo(.xit, evt.admin_repo_opts)) !void { │ │2401 const moment = try evt.currentMoment(evt.admin_repo_opts, repo); │ │2402 self.haxy_moment = moment; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │2404 const user_id = self.userId() orelse return; │ │2405 var arena = std.heap.ArenaAllocator.init(allocator); │ │2406 defer arena.deinit(); │ │2456 - const user = (try activeUser(moment, &arena, user_id)) orelse return self.logOut();│ │2457 - self.data.enable_ansi = user.event.enable_ansi; │ │2407 + if (try activeUser(moment, &arena, user_id) == null) self.logOut(); │ │2408 } │ │2409 │ │2410 // request a forward navigation to `route`; the host consumes next_page │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │2428 term.setActive(&terminal); │ │2429 defer term.setActive(null); │ │2430 │ │2431 + try terminal.queryBackground(); │ │2432 + │ │2433 while (!terminal.shouldQuit()) { │ │2434 const grid_changed = try terminal.render(&nav.root);│ │2435 │ ╰──────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │2478 } │ │2479 } │ │2480 │ │2529 - // persist queued actions when there's a repo; local mode has none, │ │2530 - // so just apply in-memory │ │2531 - if (repo_maybe) |repo| { │ │2532 - try session.applyAndWritePending(io, allocator, repo); │ │2533 - // reload so the next navigation builds its page from the current moment │ │2534 - try session.reloadMoment(allocator, repo); │ │2535 - } else { │ │2536 - session.applyPending(); │ │2537 - } │ │2481 + // reload so the next navigation builds its page from the current moment │ │2482 + if (repo_maybe) |repo| try session.reloadMoment(allocator, repo); │ │2483 │ │2484 // reconcile navigation: forward to a new page, or back on escape. │ │2540 - try nav.sync(allocator, session); │ │2485 + // the terminal background can change at any time, so recheck it on each page│ │2486 + if (try nav.sync(allocator, session)) try terminal.queryBackground(); │ │2487 │ │2488 // the quit button (on the quit tab) asks the host to tear down. │ │2489 if (session.quit_requested) terminal.requestQuit(); │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │2577 } else { │ │2578 try root.input(allocator, key, root_focus); │ │2579 } │ │2580 + }, │ │2581 + .event => |event| switch (event) { │ │2582 + .background => |rgb| session.terminal_background = rgb,│ │2583 + .resize => try root.input(allocator, key, root_focus), │ │2584 }, │ │2585 else => try root.input(allocator, key, root_focus), │ │2586 } │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯