@@ -37,6 +37,7 @@ pub struct GithubItem {
3737 pub title : String ,
3838 pub repo : String ,
3939 pub kind : GithubItemKind ,
40+ pub updated_at : String ,
4041}
4142
4243#[ derive( Parser ) ]
@@ -200,6 +201,8 @@ struct GraphQlNode {
200201 title : String ,
201202 #[ serde( rename = "headRefName" , default ) ]
202203 head_ref_name : Option < String > ,
204+ #[ serde( rename = "updatedAt" , default ) ]
205+ updated_at : Option < String > ,
203206 repository : GraphQlRepo ,
204207}
205208
@@ -235,6 +238,7 @@ fn parse_search_response(json: &str) -> anyhow::Result<Vec<GithubItem>> {
235238 title : node. title ,
236239 repo,
237240 kind,
241+ updated_at : node. updated_at . unwrap_or_default ( ) ,
238242 }
239243 } )
240244 . collect ( ) )
@@ -304,6 +308,10 @@ fn search_github(repos: &[String], type_filter: &str) -> anyhow::Result<Vec<Gith
304308 Ok ( items)
305309}
306310
311+ fn sort_items_by_date ( items : & mut [ GithubItem ] ) {
312+ items. sort_by ( |a, b| b. updated_at . cmp ( & a. updated_at ) ) ;
313+ }
314+
307315fn list_recipes ( ) -> anyhow:: Result < ( ) > {
308316 let repos = discover_github_repos ( ) ?;
309317 let repo_names: Vec < String > = repos. iter ( ) . map ( |r| r. repo . clone ( ) ) . collect ( ) ;
@@ -312,13 +320,17 @@ fn list_recipes() -> anyhow::Result<()> {
312320 // Issues are scoped to `assignee:@me` so only actionable work appears,
313321 // unlike PRs which show all open PRs on configured repos.
314322 let issues = search_github ( & repo_names, "is:issue is:open assignee:@me" ) ?;
315- for item in prs {
316- let safe_title = item . title . replace ( [ '\t' , '\n' , '\0' , '\x1f' ] , " " ) ;
317- println ! ( "{}#{} \t [PR] {}" , item . repo , item . number , safe_title ) ;
318- }
319- for item in issues {
323+
324+ let mut items : Vec < GithubItem > = prs . into_iter ( ) . chain ( issues ) . collect ( ) ;
325+ sort_items_by_date ( & mut items ) ;
326+
327+ for item in items {
320328 let safe_title = item. title . replace ( [ '\t' , '\n' , '\0' , '\x1f' ] , " " ) ;
321- println ! ( "{}#{}\t [issue] {}" , item. repo, item. number, safe_title) ;
329+ let prefix = match & item. kind {
330+ GithubItemKind :: PullRequest { .. } => "[PR]" ,
331+ GithubItemKind :: Issue => "[issue]" ,
332+ } ;
333+ println ! ( "{}#{}\t {} {}" , item. repo, item. number, prefix, safe_title) ;
322334 }
323335 Ok ( ( ) )
324336}
@@ -1027,6 +1039,73 @@ mod tests {
10271039 assert_eq ! ( branch, "master" ) ;
10281040 }
10291041
1042+ #[ test]
1043+ fn test_parse_search_response_captures_updated_at ( ) {
1044+ let json = r#"{
1045+ "data": {
1046+ "search": {
1047+ "nodes": [
1048+ {
1049+ "number": 42,
1050+ "title": "Fix the thing",
1051+ "headRefName": "fix-thing",
1052+ "updatedAt": "2026-02-14T13:10:29Z",
1053+ "repository": { "nameWithOwner": "kantord/enwiro" }
1054+ },
1055+ {
1056+ "number": 225,
1057+ "title": "Bug report",
1058+ "updatedAt": "2026-02-12T09:00:00Z",
1059+ "repository": { "nameWithOwner": "kantord/enwiro" }
1060+ }
1061+ ]
1062+ }
1063+ }
1064+ }"# ;
1065+ let items = parse_search_response ( json) . unwrap ( ) ;
1066+ assert_eq ! ( items[ 0 ] . updated_at, "2026-02-14T13:10:29Z" ) ;
1067+ assert_eq ! ( items[ 1 ] . updated_at, "2026-02-12T09:00:00Z" ) ;
1068+ }
1069+
1070+ #[ test]
1071+ fn test_list_recipes_sorts_combined_items_by_date ( ) {
1072+ // When PRs and issues are combined, they should be sorted by
1073+ // updated_at descending (newest first), not grouped by type.
1074+ let mut items = vec ! [
1075+ GithubItem {
1076+ number: 10 ,
1077+ title: "Old PR" . to_string( ) ,
1078+ repo: "enwiro" . to_string( ) ,
1079+ kind: GithubItemKind :: PullRequest {
1080+ head_ref_name: "old-pr" . to_string( ) ,
1081+ } ,
1082+ updated_at: "2026-02-01T00:00:00Z" . to_string( ) ,
1083+ } ,
1084+ GithubItem {
1085+ number: 20 ,
1086+ title: "Recent issue" . to_string( ) ,
1087+ repo: "enwiro" . to_string( ) ,
1088+ kind: GithubItemKind :: Issue ,
1089+ updated_at: "2026-02-15T00:00:00Z" . to_string( ) ,
1090+ } ,
1091+ GithubItem {
1092+ number: 30 ,
1093+ title: "Newest PR" . to_string( ) ,
1094+ repo: "enwiro" . to_string( ) ,
1095+ kind: GithubItemKind :: PullRequest {
1096+ head_ref_name: "newest-pr" . to_string( ) ,
1097+ } ,
1098+ updated_at: "2026-02-18T00:00:00Z" . to_string( ) ,
1099+ } ,
1100+ ] ;
1101+
1102+ sort_items_by_date ( & mut items) ;
1103+
1104+ assert_eq ! ( items[ 0 ] . number, 30 , "Newest PR should be first" ) ;
1105+ assert_eq ! ( items[ 1 ] . number, 20 , "Recent issue should be second" ) ;
1106+ assert_eq ! ( items[ 2 ] . number, 10 , "Old PR should be last" ) ;
1107+ }
1108+
10301109 #[ test]
10311110 fn test_get_default_branch_errors_when_no_candidates ( ) {
10321111 let tmp = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
0 commit comments