╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────╮ │1 const std = @import("std"); │ │2 const xit = @import("xit"); │ │3 const rp = xit.repo; │ │4 +const hash = xit.hash; │ │5 const xitui = xit.xitui; │ │6 const term = xitui.terminal;│ │7 const wgt = xitui.widget; │ ╰───────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │26 home_repos, │ │27 home_auth, │ │28 │ │29 + pub const default: RoutablePage = .home_users;│ │30 + │ │31 pub fn url(self: RoutablePage) []const u8 { │ │32 return switch (self) { │ │33 .home_users => "/users", │ ╰──────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │37 } │ │38 │ │39 pub fn fromUrl(path: []const u8) ?RoutablePage { │ │37 - if (std.mem.eql(u8, path, "/") or std.mem.eql(u8, path, "/users")) return .home_users;│ │40 + if (std.mem.eql(u8, path, "/")) return default; │ │41 + if (std.mem.eql(u8, path, "/users")) return .home_users; │ │42 if (std.mem.eql(u8, path, "/repos")) return .home_repos; │ │43 if (std.mem.eql(u8, path, "/auth")) return .home_auth; │ │44 return null; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │46 }; │ │47 │ │48 // per-connection mutable state. each SSH session / web session / local TUI │ │45 -// run gets its own. `data` is the subset that round-trips between server │ │46 -// and wasm; the other fields are runtime context only and stay local. │ │49 +// run gets its own │ │50 pub const Session = struct { │ │48 - data: SessionData = .{}, │ │49 - arena: ?*std.heap.ArenaAllocator = null, │ │50 - haxy_moment: ?DB.HashMap(.read_only) = null, │ │51 -}; │ │51 + data: Data = .{}, // serializable data sent down to web client │ │52 + arena: *std.heap.ArenaAllocator, │ │53 + haxy_moment: ?DB.HashMap(.read_only) = null, // db cursor (null on the wasm side) │ │54 + │ │55 + pub const Data = struct { │ │56 + user_id: ?[]const u8 = null, │ │57 + // a transient outcome to surface from the last /login POST attempt │ │58 + login_failure: ?Home.Auth.Login.Failure = null, │ │59 + current_page: RoutablePage = .default, │ │60 + }; │ │61 + │ │62 + pub fn init( │ │63 + comptime repo_opts: rp.RepoOpts(.xit), │ │64 + arena: *std.heap.ArenaAllocator, │ │65 + repo: *rp.Repo(.xit, repo_opts), │ │66 + data: Data, │ │67 + ) !Session { │ │68 + const RepoDB = rp.Repo(.xit, repo_opts).DB; │ │69 + │ │70 + const history = try RepoDB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ │71 + │ │72 + const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │73 + const moment = try RepoDB.HashMap(.read_only).init(moment_cursor); │ │74 + │ │75 + const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound;│ │76 + var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │77 + _ = try last_object_id_cursor.readBytes(&last_object_id); │ │78 │ │53 -// the serializable part of session │ │54 -pub const SessionData = struct { │ │55 - user_id: ?[]const u8 = null, │ │56 - // a transient outcome to surface from the last /login POST attempt │ │57 - login_failure: ?Home.Auth.Login.Failure = null, │ │58 - current_page: RoutablePage = .home_users, │ │79 + const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; │ │80 + const haxy = try RepoDB.ArrayList(.read_only).init(haxy_cursor); │ │81 + │ │82 + const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │83 + const haxy_moments = try RepoDB.HashMap(.read_only).init(haxy_moments_cursor); │ │84 + │ │85 + const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound;│ │86 + const haxy_moment = try RepoDB.HashMap(.read_only).init(haxy_moment_cursor); │ │87 + │ │88 + return .{ │ │89 + .data = data, │ │90 + .arena = arena, │ │91 + .haxy_moment = haxy_moment, │ │92 + }; │ │93 + } │ │94 }; │ │95 │ │96 // what the server hands to the client (and what main_wasm parses on _start). │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 97 // keeps Page free of any per-request session state. │ │ 98 pub const Snapshot = struct { │ │ 99 page: Page, │ │ 65 - session: SessionData = .{}, │ │100 + session: Session.Data = .{}, │ │101 }; │ │102 │ │103 pub fn run(io: std.Io, allocator: std.mem.Allocator, page: *const Page, session: *Session) !void {│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │169 │ │170 pub fn initRoot(allocator: std.mem.Allocator, page: *const Page, session: *Session) !Widget {│ │171 var root: Widget = switch (page.*) { │ │137 - .home => |*p| .{ .home = try Home.View.init(allocator, p, session) }, │ │172 + .home => |*p| .{ .home = try .init(allocator, p, session) }, │ │173 }; │ │174 errdefer root.deinit(allocator); │ │175 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Home.zig│ ╰───────────────╯ ╭───────────────────────────────────╮ │2 const ui = @import("../ui.zig");│ │3 const xit = @import("xit"); │ │4 const rp = xit.repo; │ │5 -const hash = xit.hash; │ │5 const xitui = xit.xitui; │ │6 const wgt = xitui.widget; │ │7 const layout = xitui.layout; │ ╰───────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │24 pub fn init( │ │25 comptime repo_opts: rp.RepoOpts(.xit), │ │26 arena: *std.heap.ArenaAllocator, │ │28 - repo: *rp.Repo(.xit, repo_opts), │ │29 - session: *ui.Session, │ │27 + haxy_moment: rp.Repo(.xit, repo_opts).DB.HashMap(.read_only), │ │28 ) !Self { │ │31 - const DB = rp.Repo(.xit, repo_opts).DB; │ │32 - │ │33 - const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ │34 - │ │35 - const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; │ │36 - const moment = try DB.HashMap(.read_only).init(moment_cursor); │ │37 - │ │38 - const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound;│ │39 - var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │40 - _ = try last_object_id_cursor.readBytes(&last_object_id); │ │41 - │ │42 - const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; │ │43 - const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); │ │44 - │ │45 - const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; │ │46 - const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); │ │47 - │ │48 - const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound;│ │49 - const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │50 - │ │51 - // hand the cursor + arena off to the session so Login (and any future │ │52 - // page that needs DB access at input time) can use them on demand. │ │53 - session.arena = arena; │ │54 - session.haxy_moment = haxy_moment; │ │55 - │ │29 return .{ │ │30 .header = Header.init(), │ │31 .users = try Users.init(repo_opts, arena, haxy_moment), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────────╮ │src/ui/Home/Auth/Login.zig│ ╰──────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │240 │ │241 const haxy_moment = self.session.haxy_moment orelse { │ │242 // no DB context (e.g. wasm/web rendering path); treat as unknown.│ │243 - self.session.data.login_failure = .unknown_user; │ │244 - return; │ │245 - }; │ │246 - const arena = self.session.arena orelse { │ │243 self.session.data.login_failure = .unknown_user; │ │244 return; │ │245 }; │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │246 │ │251 - const result = try evt.User.verifyCredentials(DB, repo_opts.hash, haxy_moment, arena, username, password); │ │247 + const result = try evt.User.verifyCredentials(DB, repo_opts.hash, haxy_moment, self.session.arena, username, password);│ │248 switch (result) { │ │249 .unknown_user => { │ │250 self.session.data.login_failure = .unknown_user; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯