╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │143 const sub = rest[s + 1 ..]; │ │144 if (std.mem.eql(u8, sub, "settings")) return .{ .user_settings = parsed };│ │145 if (std.mem.eql(u8, sub, "auth")) return .{ .user_auth = parsed }; │ │126 - return null; // unknown user sub-path │ │146 + return null; // unknown sub-path │ │147 } │ │148 return .{ .user = parsed }; │ │149 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │150 + // /repo/<username>/<reponame>[/settings|/auth] — the name carried by the │ │151 + // route is the "username/reponame" pair (both segments, joined). │ │152 + if (std.mem.startsWith(u8, path, repo_segment)) { │ │153 + const rest = path[repo_segment.len..]; │ │154 + const user_slash = std.mem.indexOfScalar(u8, rest, '/') orelse return null; │ │155 + const after_user = rest[user_slash + 1 ..]; │ │156 + const repo_slash = std.mem.indexOfScalar(u8, after_user, '/'); │ │157 + const repo_name = if (repo_slash) |s| after_user[0..s] else after_user; │ │158 + // reject an empty username or reponame │ │159 + if (user_slash == 0 or repo_name.len == 0) return null; │ │160 + const pair = rest[0 .. user_slash + 1 + repo_name.len]; │ │161 + const parsed = Array(repo_name_max_len).from(pair) orelse return null; // name too long│ │162 + if (repo_slash) |s| { │ │163 + const sub = after_user[s + 1 ..]; │ │164 + if (std.mem.eql(u8, sub, "settings")) return .{ .repo_settings = parsed }; │ │165 + if (std.mem.eql(u8, sub, "auth")) return .{ .repo_auth = parsed }; │ │166 + return null; // unknown sub-path │ │167 + } │ │168 + return .{ .repo = parsed }; │ │169 + } │ │170 return null; │ │171 } │ │172 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │176 .user => |a_name| std.mem.eql(u8, a_name.slice(), b.user.slice()), │ │177 .user_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.user_settings.slice()),│ │178 .user_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.user_auth.slice()), │ │179 + .repo => |a_name| std.mem.eql(u8, a_name.slice(), b.repo.slice()), │ │180 + .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()),│ │181 + .repo_auth => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_auth.slice()), │ │182 else => true, │ │183 }; │ │184 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │193 try jw.write(@tagName(self)); │ │194 switch (self) { │ │195 .user, .user_settings, .user_auth => |name| {│ │196 + try jw.objectField("name"); │ │197 + try jw.write(name.slice()); │ │198 + }, │ │199 + .repo, .repo_settings, .repo_auth => |name| {│ │200 try jw.objectField("name"); │ │201 try jw.write(name.slice()); │ │202 }, │ ╰──────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │211 const parseName = struct { │ │212 // errors must stay within std.json's ParseError set; ValueTooLong │ │213 // is its member for an over-long field. │ │167 - fn f(maybe: ?[]const u8) !Array(evt.User.name_max_len) { │ │168 - return Array(evt.User.name_max_len).from(maybe orelse return error.MissingField) orelse error.ValueTooLong;│ │214 + fn f(comptime max_len: usize, maybe: ?[]const u8) !Array(max_len) { │ │215 + return Array(max_len).from(maybe orelse return error.MissingField) orelse error.ValueTooLong; │ │216 } │ │217 }.f; │ │218 return switch (helper.kind) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │220 .home_repos => .home_repos, │ │221 .home_settings => .home_settings, │ │222 .home_auth => .home_auth, │ │176 - .user => .{ .user = try parseName(helper.name) }, │ │177 - .user_settings => .{ .user_settings = try parseName(helper.name) }, │ │178 - .user_auth => .{ .user_auth = try parseName(helper.name) }, │ │223 + .user => .{ .user = try parseName(evt.User.name_max_len, helper.name) }, │ │224 + .user_settings => .{ .user_settings = try parseName(evt.User.name_max_len, helper.name) },│ │225 + .user_auth => .{ .user_auth = try parseName(evt.User.name_max_len, helper.name) }, │ │226 + .repo => .{ .repo = try parseName(repo_name_max_len, helper.name) }, │ │227 + .repo_settings => .{ .repo_settings = try parseName(repo_name_max_len, helper.name) }, │ │228 + .repo_auth => .{ .repo_auth = try parseName(repo_name_max_len, helper.name) }, │ │229 }; │ │230 } │ │231 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │583 const page_widget: Widget = switch (page.*) { │ │584 .home => |*p| .{ .home = try .init(allocator, p, session) },│ │585 .user => |*p| .{ .user = try .init(allocator, p, session) },│ │586 + .repo => |*p| .{ .repo = try .init(allocator, p, session) },│ │587 }; │ │588 │ │589 const demon_art = @embedFile("embed/demon.ans"); │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────╮ │617 background: AnsiBackground, │ │618 home: Home.View, │ │619 user: User.View, │ │620 + repo: Repo.View, │ │621 quit: Quit.View, │ │622 title: Title.View, │ │623 home_header: Home.Header.View,│ ╰───────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │624 user_header: User.Header.View, │ │625 + repo_header: Repo.Header.View, │ │626 home_users: Home.Users.View, │ │627 home_repos: Home.Repos.View, │ │628 auth_tab: Home.Header.AuthTab.View,│ ╰────────────────────────────────────────────╯