╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1681 sync_events, │ │1682 }; │ │1683 │ │1684 + pub const FormFeedback = union(enum) { │ │1685 + pub const LoginFailure = enum { unknown_user, wrong_password }; │ │1686 + pub const ThreadFailure = enum { required_title }; │ │1687 + pub const PatchFailure = enum { required_title, invalid_target_branch };│ │1688 + │ │1689 + login: struct { │ │1690 + failure: LoginFailure, │ │1691 + username: []const u8, │ │1692 + }, │ │1693 + issue: struct { │ │1694 + failure: ThreadFailure, │ │1695 + fields: ?struct { │ │1696 + title: []const u8, │ │1697 + tags: []const u8, │ │1698 + description: []const u8, │ │1699 + } = null, │ │1700 + }, │ │1701 + patch: struct { │ │1702 + failure: PatchFailure, │ │1703 + fields: ?struct { │ │1704 + title: []const u8, │ │1705 + tags: []const u8, │ │1706 + description: []const u8, │ │1707 + target_branch: []const u8, │ │1708 + } = null, │ │1709 + }, │ │1710 + discussion: struct { │ │1711 + failure: ThreadFailure, │ │1712 + fields: ?struct { │ │1713 + title: []const u8, │ │1714 + tags: []const u8, │ │1715 + description: []const u8, │ │1716 + } = null, │ │1717 + }, │ │1718 + }; │ │1719 + │ │1720 const Self = @This(); │ │1721 │ │1722 // serializable data sent down to web client │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1724 user_id: ?[]const u8 = null, │ │1725 // the logged-in user's name, for the views that show it │ │1726 user_name: ?[]const u8 = null, │ │1691 - // a transient outcome to surface from the last /login POST attempt│ │1692 - login_failure: ?Home.Auth.Login.Failure = null, │ │1727 + // a failed form retained long enough to render it for correction │ │1728 + form_feedback: ?FormFeedback = null, │ │1729 // a transient local event-sync error │ │1730 sync_failure: ?[]const u8 = null, │ │1731 current_page: RoutablePage = .default, │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1790 var id: [evt.event_id_size]u8 = undefined; │ │1791 @memcpy(&id, bytes); │ │1792 return id; │ │1793 + } │ │1794 + │ │1795 + pub fn formFeedback(self: *const Self, comptime tag: std.meta.Tag(FormFeedback)) ?@FieldType(FormFeedback, @tagName(tag)) {│ │1796 + const feedback = self.data.form_feedback orelse return null; │ │1797 + if (std.meta.activeTag(feedback) != tag) return null; │ │1798 + return @field(feedback, @tagName(tag)); │ │1799 + } │ │1800 + │ │1801 + pub fn clearFormFeedback(self: *Self) void { │ │1802 + self.data.form_feedback = null; │ │1803 } │ │1804 │ │1805 // the commit author for an event this session creates, or null when it may │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1870 // request a forward navigation to `route`; the host consumes next_page│ │1871 // (Nav.sync on the terminal, the wasm tick on the web). │ │1872 pub fn navigate(self: *Session, route: RoutablePage) !void { │ │1873 + self.clearFormFeedback(); │ │1874 self.next_page = route; │ │1875 } │ │1876 }; │ ╰─────────────────────────────────────────────────────────────────────────────────╯