╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │101 │ │102 pub const RefKind = enum { branch, tag }; │ │103 │ │104 - pub const IssuesView = enum { open, closed, tags, new }; │ │104 + pub const IssuesView = enum { open, closed, tags, new, edit };│ │105 │ │106 pub const RefOrOid = enum { │ │107 branch, │ ╰───────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │323 return .{ .repo_issues = .{ │ │324 .name = Array(repo_route_max_len).from(identity) orelse return null, │ │325 .view = .new, │ │326 + } }; │ │327 + } │ │328 + │ │329 + // build a `.repo_issues` route showing the edit form for the issue with │ │330 + // hex event id `selected`. │ │331 + pub fn repoIssuesEditRoute(identity: []const u8, selected: []const u8) ?RoutablePage {│ │332 + if (selected.len == 0) return null; │ │333 + return .{ .repo_issues = .{ │ │334 + .name = Array(repo_route_max_len).from(identity) orelse return null, │ │335 + .selected = Array(evt.event_id_size * 2).from(selected) orelse return null, │ │336 + .view = .edit, │ │337 } }; │ │338 } │ │339 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │409 }, │ │410 .repo_issues => |i| blk: { │ │411 const prefix = try repoUrlPrefix(arena, i.name.slice()); │ │412 + if (i.view == .edit) break :blk try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/edit", .{ prefix, i.selected.slice() });│ │413 // a selected issue's url carries its id instead of a view │ │414 // name, so it survives status changes │ │415 const word = if (i.selected.len != 0) i.selected.slice() else @tagName(i.view); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │517 return repoRefsRoute(pair, kind, ref.value); │ │518 } │ │519 if (std.mem.eql(u8, tab, "issues")) { │ │520 + // "<id>/edit" is the edit form for that issue, with no params. │ │521 + const after_tab = segments.rest(); │ │522 + if (std.mem.endsWith(u8, after_tab, "/edit")) { │ │523 + const id = after_tab[0 .. after_tab.len - "/edit".len]; │ │524 + if (id.len == 0 or std.mem.indexOfScalar(u8, id, '/') != null) return null;│ │525 + return repoIssuesEditRoute(pair, id); │ │526 + } │ │527 // the word is a view name or an issue event id (whose status │ │528 // picks the view); a filter with neither is never emitted. │ │529 const word = params.scanTail(&segments) catch return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │735 // focus id -> the live TextInput, refreshed each frame by the views that own │ │736 // inputs. web/wasm form handling looks widgets up here by focus id. │ │737 text_inputs: std.AutoHashMapUnmanaged(usize, *wgt.TextInput(Widget)) = .empty, │ │738 + // focus id -> the page-constant text the web overlay renders into an │ │739 + // input initially, refreshed each frame alongside `text_inputs`. never │ │740 + // the input's live content: the overlay html must not change as the │ │741 + // user types, or the diff in _setOverlay would rebuild the element. │ │742 + input_values: std.AutoHashMapUnmanaged(usize, []const u8) = .empty, │ │743 nav_back: bool = false, // set by input (escape) to request the native TUI pop a page; see Nav│ │744 refresh_requested: bool = false, // set by input (ctrl+r) │ │745 // a requested forward navigation. set this (via navigate) to move to a new │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1172 // input-owning views build their TextInputs in init — so reset the│ │1173 // focus-id -> *TextInput map here │ │1174 session.text_inputs.clearRetainingCapacity(); │ │1175 + session.input_values.clearRetainingCapacity(); │ │1176 │ │1177 try root.build(allocator, .{ │ │1178 .min_size = .{ .width = null, .height = 40 }, │ ╰─────────────────────────────────────────────────────────────────────────────╯