╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────╮ │12 │ │13 pub const Home = @import("./ui/Home.zig"); │ │14 pub const User = @import("./ui/User.zig"); │ │15 +pub const Repo = @import("./ui/Repo.zig"); │ │16 pub const Title = @import("./ui/Title.zig");│ │17 pub const Quit = @import("./ui/Quit.zig"); │ │18 │ ╰────────────────────────────────────────────────╯ ╭──────────────────────────────────────╮ │19 pub const PageKind = enum { │ │20 home, │ │21 user, │ │22 + repo, │ │23 }; │ │24 │ │25 pub const Page = union(PageKind) {│ ╰──────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 home: Home, │ │27 user: User, │ │28 + repo: Repo, │ │29 │ │30 pub fn init(arena: *std.heap.ArenaAllocator, haxy_moment: evt.AdminDB.HashMap(.read_only), route: RoutablePage) !Page {│ │31 return switch (route.parent()) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │34 .user, .user_settings, .user_auth => |name| .{ .user = try User.init(arena, haxy_moment, name) },│ │35 else => return error.UnexpectedRoute, │ │36 }, │ │37 + .repo => switch (route) { │ │38 + .repo, .repo_settings, .repo_auth => |name| .{ .repo = try Repo.init(arena, haxy_moment, name) },│ │39 + else => return error.UnexpectedRoute, │ │40 + }, │ │41 }; │ │42 } │ │43 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │61 user: Array(evt.User.name_max_len), │ │62 user_settings: Array(evt.User.name_max_len), │ │63 user_auth: Array(evt.User.name_max_len), │ │64 + repo: Array(repo_name_max_len), │ │65 + repo_settings: Array(repo_name_max_len), │ │66 + repo_auth: Array(repo_name_max_len), │ │67 │ │68 pub const default: RoutablePage = .home_users;│ │69 │ ╰──────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │70 const user_segment = "/user/"; │ │71 + const repo_segment = "/repo/"; │ │72 + │ │73 + // a repo is identified by "username/reponame"; the route carries both names │ │74 + // joined by '/', so the inline name must fit both plus the separator. │ │75 + pub const repo_name_max_len = evt.User.name_max_len + 1 + evt.Repo.name_max_len;│ │76 │ │77 // an inline, owned array of data. keeping it in the route (rather than a │ │78 // borrowed slice) makes RoutablePage a plain value: it can be copied, stored │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │101 .home_repos => "/repos", │ │102 .home_settings => "/settings", │ │103 .home_auth => "/auth", │ │ 89 - // user routes carry a runtime name; build them with urlAlloc. │ │104 .user, .user_settings, .user_auth => @compileError("user routes are dynamic; use urlAlloc"),│ │105 + .repo, .repo_settings, .repo_auth => @compileError("repo routes are dynamic; use urlAlloc"),│ │106 }; │ │107 } │ │108 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │111 .user => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}", .{name.slice()}), │ │112 .user_settings => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/settings", .{name.slice()}),│ │113 .user_auth => |name| try std.fmt.allocPrint(arena.allocator(), user_segment ++ "{s}/auth", .{name.slice()}), │ │114 + .repo => |name| try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}", .{name.slice()}), │ │115 + .repo_settings => |name| try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/settings", .{name.slice()}),│ │116 + .repo_auth => |name| try std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}/auth", .{name.slice()}), │ │117 inline else => |_, tag| url(tag), │ │118 }; │ │119 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │122 return switch (self) { │ │123 .home_users, .home_repos, .home_settings, .home_auth => .home,│ │124 .user, .user_settings, .user_auth => .user, │ │125 + .repo, .repo_settings, .repo_auth => .repo, │ │126 }; │ │127 } │ │128 │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │132 if (std.mem.eql(u8, path, "/repos")) return .home_repos; │ │133 if (std.mem.eql(u8, path, "/settings")) return .home_settings;│ │134 if (std.mem.eql(u8, path, "/auth")) return .home_auth; │ │135 + // /user/<name>[/settings|/auth] │ │136 if (std.mem.startsWith(u8, path, user_segment)) { │ │137 const rest = path[user_segment.len..]; │ │138 const slash = std.mem.indexOfScalar(u8, rest, '/'); │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯