╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │54 }; │ │55 }, │ │56 .repo => switch (route) { │ │57 - .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) }, │ │57 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_events, .repo_settings, .repo_auth => .{ .repo = try Repo.init(arena, session, route) },│ │58 else => return error.UnexpectedRoute, │ │59 }, │ │60 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │107 // the comment shown in the detail pane (empty = the issue thread).│ │108 comment: Array(evt.event_id_size * 2) = .{}, │ │109 }, │ │110 + repo_events: RepoEventsRoute, │ │111 repo_settings: Array(repo_route_max_len), │ │112 repo_auth: Array(repo_route_max_len), │ │113 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │285 }, │ │286 message, │ │287 }; │ │288 + }; │ │289 + │ │290 + pub const RepoEventsRoute = struct { │ │291 + name: Array(repo_identity_max_len), │ │292 + kind: ?evt.EventKind = null, │ │293 + selected: Array(evt.event_id_size * 2) = .{}, │ │294 }; │ │295 │ │296 // build a `.repo_files` route (a null ref_kind = the bare default-branch│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │346 } }; │ │347 } │ │348 │ │349 + // build the events list or a window rooted at one event. │ │350 + pub fn repoEventsRoute(identity: []const u8, kind: ?evt.EventKind, selected: []const u8) ?RoutablePage {│ │351 + if ((kind == null) != (selected.len == 0)) return null; │ │352 + return .{ .repo_events = .{ │ │353 + .name = Array(repo_identity_max_len).from(identity) orelse return null, │ │354 + .kind = kind, │ │355 + .selected = Array(evt.event_id_size * 2).from(selected) orelse return null, │ │356 + } }; │ │357 + } │ │358 + │ │359 // build a `.repo_issues` route for "owner/name" (identity) showing │ │360 // `status`'s list, filtered to the url-encoded `tag` ("" = unfiltered) and │ │361 // rooted at the issue with hex event id `selected` ("" = the first window). │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │571 else │ │572 try std.fmt.allocPrint(arena.allocator(), "{s}/issues/{s}/" ++ tag_filter_seg ++ "{s}", .{ prefix, @tagName(i.view), i.tag.slice() });│ │573 }, │ │574 + .repo_events => |e| blk: { │ │575 + const prefix = try repoUrlPrefix(arena, e.name.slice()); │ │576 + break :blk if (e.kind) |kind| │ │577 + try std.fmt.allocPrint(arena.allocator(), "{s}/event:{s}/kind:{s}", .{ prefix, e.selected.slice(), @tagName(kind) }) │ │578 + else │ │579 + try std.fmt.allocPrint(arena.allocator(), "{s}/events", .{prefix}); │ │580 + }, │ │581 .repo_settings => |name| try std.fmt.allocPrint(arena.allocator(), "{s}/settings", .{try repoUrlPrefix(arena, name.slice())}), │ │582 .repo_auth => |name| try std.fmt.allocPrint(arena.allocator(), "{s}/auth", .{try repoUrlPrefix(arena, name.slice())}), │ │583 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │652 .repo_commits => |*c| c.name.slice(), │ │653 .repo_refs => |*r| r.name.slice(), │ │654 .repo_issues => |*i| i.name.slice(), │ │655 + .repo_events => |*e| e.name.slice(), │ │656 .repo_settings, .repo_auth => |*name| name.slice(),│ │657 else => null, │ │658 }; │ ╰────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │677 return switch (self) { │ │678 .home_users, .home_repos, .home_settings, .home_auth => .home, │ │679 .user_repos, .user_settings, .user_auth => .user, │ │655 - .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_settings, .repo_auth => .repo, │ │680 + .repo_files, .repo_commits, .repo_refs, .repo_issues, .repo_events, .repo_settings, .repo_auth => .repo,│ │681 }; │ │682 } │ │683 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │763 if (std.mem.eql(u8, w, "tags")) return repoIssuesTagsRoute(pair, tag_value); │ │764 if (std.mem.eql(u8, w, "new")) return if (tag_value.len == 0) repoIssuesNewRoute(pair) else null; │ │765 return null; │ │766 + } │ │767 + if (std.mem.eql(u8, tab, "events")) │ │768 + return if (segments.next() == null) repoEventsRoute(pair, null, "") else null; │ │769 + if (std.mem.startsWith(u8, tab, "event:")) { │ │770 + const event_id = tab["event:".len..]; │ │771 + const kind_segment = segments.next() orelse return null; │ │772 + if (!std.mem.startsWith(u8, kind_segment, "kind:")) return null; │ │773 + const kind = std.meta.stringToEnum(evt.EventKind, kind_segment["kind:".len..]) orelse return null;│ │774 + if (event_id.len == 0 or segments.next() != null) return null; │ │775 + return repoEventsRoute(pair, kind, event_id); │ │776 } │ │777 if (std.mem.eql(u8, tab, files_seg)) { │ │778 params.scanPairs(&segments) catch return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │831 a_i.view == b.repo_issues.view and │ │832 a_i.comments_start == b.repo_issues.comments_start and │ │833 std.mem.eql(u8, a_i.comment.slice(), b.repo_issues.comment.slice()), │ │834 + .repo_events => |a_e| std.mem.eql(u8, a_e.name.slice(), b.repo_events.name.slice()) and│ │835 + a_e.kind == b.repo_events.kind and │ │836 + std.mem.eql(u8, a_e.selected.slice(), b.repo_events.selected.slice()), │ │837 .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()), │ │838 .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │839 else => true, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │1006 // port the web UI is served on, for the TUI/SSH footer's "http://localhost:<port>..."│ │1007 // url. null on the web itself (no footer there). │ │1008 web_port: ?u16 = null, │ │ 971 - // a host operation requested by a widget. terminals present copyable text on │ │ 972 - // their restored screen; wasm asks the browser to copy them. │ │1009 + // a host operation requested by a widget │ │1010 host_request: ?HostRequest = null, │ │1011 quit_requested: bool = false, │ │1012 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯