╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Auth/Login.zig│ ╰─────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │44 var username = try wgt.TextInput.init(allocator, .{ .label = " username ", .name = "username", .rounded_corners = true, .render_content = session.is_terminal });│ │45 errdefer username.deinit(allocator); │ │46 username.getFocus().focusable = true; │ │47 + if (session.formFeedback(.login)) |feedback| try username.setContent(allocator, feedback.username); │ │48 nav_ids[username_index] = username.getFocus().id; │ │49 try box.children.put(allocator, username.getFocus().id, .{ │ │50 .widget = .{ .text_input = username }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 97 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │ 98 const box = &self.center.child.box; │ │ 99 │ │103 - const failure = self.session.data.login_failure; │ │100 + const failure = if (self.session.formFeedback(.login)) |feedback| feedback.failure else null; │ │101 │ │102 const username_input = &box.children.values()[username_index].widget.text_input; │ │103 username_input.options.label = if (failure == .unknown_user) │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │157 else => {}, │ │158 } │ │159 } │ │163 - │ │164 - const username_input = &box.children.values()[username_index].widget.text_input;│ │165 - const password_input = &box.children.values()[password_index].widget.text_input;│ │166 - const username_len_before = username_input.content.items.len; │ │167 - const password_len_before = password_input.content.items.len; │ │160 │ │161 if (box.children.getIndex(child_id)) |idx| { │ │162 try box.children.values()[idx].widget.input(allocator, key, root_focus); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │163 } │ │172 - │ │173 - if (username_input.content.items.len != username_len_before or │ │174 - password_input.content.items.len != password_len_before) │ │175 - { │ │176 - // any edit clears the failure flag so the "(invalid)" label│ │177 - // doesn't linger after the user has typed a correction. │ │178 - self.session.data.login_failure = null; │ │179 - } │ │164 } │ │165 │ │166 pub fn clearGrid(self: *View) void { │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │188 } │ │189 │ │190 fn submit(self: *View, allocator: std.mem.Allocator) !void { │ │207 - if (comptime wasm) { │ │208 - // no DB cursor available on the wasm render path │ │209 - self.session.data.login_failure = .unknown_user; │ │210 - return; │ │211 - } │ │212 - │ │191 const box = &self.center.child.box; │ │192 const username_input = &box.children.values()[username_index].widget.text_input;│ │193 const password_input = &box.children.values()[password_index].widget.text_input;│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │197 │ │198 const password = try password_input.text(allocator); │ │199 defer allocator.free(password); │ │200 + │ │201 + if (comptime wasm) { │ │202 + // no DB cursor available on the wasm render path │ │203 + try self.rememberFailure(.unknown_user, username); │ │204 + return; │ │205 + } │ │206 │ │207 const haxy_moment = self.session.haxy_moment orelse { │ │208 // no DB context (e.g. wasm/web rendering path); treat as unknown.│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────╮ │225 - self.session.data.login_failure = .unknown_user; │ │209 + try self.rememberFailure(.unknown_user, username);│ │210 return; │ │211 }; │ │212 │ ╰───────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │213 const result = try evt.User.verifyCredentials(evt.AdminDB, evt.admin_repo_opts.hash, haxy_moment, self.session.arena, username, password);│ │214 switch (result) { │ │215 .unknown_user => { │ │232 - self.session.data.login_failure = .unknown_user; │ │216 + try self.rememberFailure(.unknown_user, username); │ │217 return; │ │218 }, │ │219 .wrong_password => { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │236 - self.session.data.login_failure = .wrong_password; │ │220 + try self.rememberFailure(.wrong_password, username);│ │221 return; │ │222 }, │ │223 .success => |user_id| { │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │224 // dupe user_id into the arena so the session can hold a stable slice │ │225 const user_id_stable = try self.session.arena.allocator().dupe(u8, &user_id);│ │226 self.session.data.user_id = user_id_stable; │ │243 - self.session.data.login_failure = null; │ │227 │ │228 // adopt the user's name and persisted prefs │ │229 try self.session.loadUser(); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯