╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Auth/Login.zig│ ╰─────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │239 }, │ │240 } │ │241 } │ │242 + │ │243 + fn rememberFailure(self: *View, failure: ui.Session.FormFeedback.LoginFailure, username: []const u8) !void {│ │244 + self.session.data.form_feedback = .{ .login = .{ │ │245 + .failure = failure, │ │246 + .username = try self.session.arena.allocator().dupe(u8, username), │ │247 + } }; │ │248 + } │ │249 }; │ │250 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1212 const Window = Data.Window; │ │1213 const Entry = Data.Entry; │ │1214 const Event = Data.Event; │ │1215 + const feedback_tag: std.meta.Tag(ui.Session.FormFeedback) = switch (kind) { │ │1216 + .issue => .issue, │ │1217 + .patch => .patch, │ │1218 + .discuss => .discussion, │ │1219 + else => unreachable, │ │1220 + }; │ │1221 + const FeedbackFailure = if (kind == .patch) ui.Session.FormFeedback.PatchFailure else ui.Session.FormFeedback.ThreadFailure;│ │1222 const Status = Data.Status; │ │1223 const has_status = @hasField(Event, "status"); │ │1224 const supports_conflicts = Event.merge_policy == .field_conflicts; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1606 errdefer box.deinit(allocator); │ │1607 box.getFocus().kind = .{ .custom = action }; │ │1608 │ │1609 + const saved_form = session.formFeedback(feedback_tag); │ │1610 + const saved_fields = if (saved_form) |saved| saved.fields else null; │ │1611 + │ │1612 { │ │1613 var title = try wgt.TextInput.init(allocator, .{ .label = " title ", .name = "title", .visible_width = null, .rounded_corners = true, .render_content = session.is_terminal });│ │1614 errdefer title.deinit(allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1615 title.getFocus().focusable = true; │ │1606 - if (record) |r| try title.setContent(allocator, r.event.title); │ │1616 + if (saved_fields) |saved| │ │1617 + try title.setContent(allocator, saved.title) │ │1618 + else if (record) |r| │ │1619 + try title.setContent(allocator, r.event.title); │ │1620 try box.children.put(allocator, title.getFocus().id, .{ .widget = .{ .text_input = title }, .rect = null, .min_size = .{ .width = null, .height = 3 } });│ │1621 } │ │1622 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1624 var tags = try wgt.TextInput.init(allocator, .{ .label = " tags (separate with spaces) ", .name = "tags", .visible_width = null, .rounded_corners = true, .render_content = session.is_terminal });│ │1625 errdefer tags.deinit(allocator); │ │1626 tags.getFocus().focusable = true; │ │1614 - if (record) |r| try tags.setContent(allocator, r.event.tags); │ │1627 + if (saved_fields) |saved| │ │1628 + try tags.setContent(allocator, saved.tags) │ │1629 + else if (record) |r| │ │1630 + try tags.setContent(allocator, r.event.tags); │ │1631 try box.children.put(allocator, tags.getFocus().id, .{ .widget = .{ .text_input = tags }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │1632 } │ │1633 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1635 var description = try wgt.TextInput.init(allocator, .{ .label = " description ", .name = "description", .visible_width = null, .rounded_corners = true, .render_content = session.is_terminal, .multiline = true, .scroll = .{ .fill = true } });│ │1636 errdefer description.deinit(allocator); │ │1637 description.getFocus().focusable = true; │ │1622 - if (record) |r| try description.setContent(allocator, r.event.description); │ │1638 + if (saved_fields) |saved| │ │1639 + try description.setContent(allocator, saved.description) │ │1640 + else if (record) |r| │ │1641 + try description.setContent(allocator, r.event.description); │ │1642 try box.children.put(allocator, description.getFocus().id, .{ .widget = .{ .text_input = description }, .rect = null, .min_size = null }); │ │1643 } │ │1644 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1646 var target = try wgt.TextInput.init(allocator, .{ .label = " target branch ", .name = "target_branch", .visible_width = null, .rounded_corners = true, .render_content = session.is_terminal });│ │1647 errdefer target.deinit(allocator); │ │1648 target.getFocus().focusable = true; │ │1630 - const content = if (record) |r| r.event.target_branch else data.default_target_branch; │ │1649 + const content = if (saved_fields) |saved| saved.target_branch else if (record) |r| r.event.target_branch else data.default_target_branch; │ │1650 try target.setContent(allocator, content); │ │1651 try box.children.put(allocator, target.getFocus().id, .{ .widget = .{ .text_input = target }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │1652 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2099 // map with this frame's addresses, so the web/wasm form handling can │ │2100 // find them by focus id │ │2101 if (self.threadForm()) |form| { │ │2102 + const failure = if (self.session.formFeedback(feedback_tag)) |saved| saved.failure else null; │ │2103 + form.children.values()[title_field_index].widget.text_input.options.label = if (failure == .required_title) " title (required) " else " title "; │ │2104 + if (comptime supports_drafts) { │ │2105 + const invalid_target = failure == .invalid_target_branch; │ │2106 + form.children.values()[target_branch_field_index].widget.text_input.options.label = if (invalid_target) " target branch (not found) " else " target branch ";│ │2107 + } │ │2108 const inputs_arena = self.session.arena.allocator(); │ │2109 for (form.children.values()) |*child| switch (child.widget) { │ │2110 .text_input => |*ti| try self.session.text_inputs.put(inputs_arena, ti.getFocus().id, ti), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2501 const target_branch = if (comptime supports_drafts) try form.children.values()[target_branch_field_index].widget.text_input.text(allocator) else "";│ │2502 defer if (comptime supports_drafts) allocator.free(target_branch); │ │2503 │ │2479 - if (!Event.fieldsValid(title, tags)) return; │ │2504 + if (!Event.fieldsValid(title, tags)) { │ │2505 + if (!evt.titleValid(title)) try self.rememberThreadFeedback(.required_title, title, tags, description, target_branch); │ │2506 + return; │ │2507 + } │ │2508 │ │2509 if (comptime supports_drafts) { │ │2482 - const event_id_hex = Data.createDraft(self.data, self.session, allocator, title, tags, description, target_branch) catch return; │ │2510 + const event_id_hex = Data.createDraft(self.data, self.session, allocator, title, tags, description, target_branch) catch |err| switch (err) { │ │2511 + error.InvalidFields => { │ │2512 + try self.rememberThreadFeedback(.invalid_target_branch, title, tags, description, target_branch); │ │2513 + return; │ │2514 + }, │ │2515 + else => |other| return other, │ │2516 + }; │ │2517 title_input.clear(allocator); │ │2518 tags_input.clear(allocator); │ │2519 description_input.clear(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2579 const target_branch = if (comptime supports_drafts) try form.children.values()[target_branch_field_index].widget.text_input.text(allocator) else "";│ │2580 defer if (comptime supports_drafts) allocator.free(target_branch); │ │2581 │ │2548 - if (!Event.fieldsValid(title, tags)) return; │ │2582 + if (!Event.fieldsValid(title, tags)) { │ │2583 + if (!evt.titleValid(title)) try self.rememberThreadFeedback(.required_title, title, tags, description, target_branch); │ │2584 + return; │ │2585 + } │ │2586 + const src = self.data.repo_source orelse return; │ │2587 │ │2588 if (comptime supports_drafts) { │ │2589 if (entryDraft(entry.*)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯