From 7de0039d3839c770a7742a84e4d484015b9acc9d Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 6 May 2026 01:42:38 +0800 Subject: [PATCH] feat: impl dashboard & issue list --- Cargo.toml | 5 +- build.rs | 82 +++- .../issues.pull_requests.all.page1.json | 439 ++++++++++++++++++ .../issues.pull_requests.all.page2.json | 203 ++++++++ .../issues.pull_requests.all.page3.json | 1 + .../issues.pull_requests.assigned.page1.json | 341 ++++++++++++++ .../issues.pull_requests.assigned.page2.json | 1 + .../issues.pull_requests.created.page1.json | 205 ++++++++ .../issues.pull_requests.created.page2.json | 98 ++++ .../issues.pull_requests.created.page3.json | 1 + fixtures/github/repo.list.json | 77 +++ fixtures/github/user.fetch.json | 8 + src/api.rs | 21 +- src/api/auth.rs | 13 +- src/api/issues.rs | 226 +++++++++ src/api/mock.rs | 47 ++ src/api/repo.rs | 51 +- src/api/user.rs | 9 +- src/app.rs | 28 ++ src/asset/font_icon/list.svg | 1 + src/asset/font_icon/pencil_line.svg | 1 + src/asset/font_icon/pull_request_arrow.svg | 1 + src/asset/font_icon/pull_request_closed.svg | 1 + src/asset/font_icon/pull_request_draft.svg | 1 + src/asset/font_icon/user_plus.svg | 1 + src/component/font_icon.rs | 15 +- src/component/text.rs | 9 +- src/main.rs | 33 +- src/query.rs | 58 ++- src/screen/dashboard/issue_list.rs | 165 +++++++ src/screen/dashboard/mod.rs | 24 +- src/screen/dashboard/screen.rs | 77 ++- src/screen/dashboard/sidebar.rs | 195 ++++++++ src/screen/dashboard/titlebar.rs | 9 +- src/screen/setup_wizard/mod.rs | 19 +- src/storage.rs | 22 +- 36 files changed, 2381 insertions(+), 107 deletions(-) create mode 100644 fixtures/github/issues.pull_requests.all.page1.json create mode 100644 fixtures/github/issues.pull_requests.all.page2.json create mode 100644 fixtures/github/issues.pull_requests.all.page3.json create mode 100644 fixtures/github/issues.pull_requests.assigned.page1.json create mode 100644 fixtures/github/issues.pull_requests.assigned.page2.json create mode 100644 fixtures/github/issues.pull_requests.created.page1.json create mode 100644 fixtures/github/issues.pull_requests.created.page2.json create mode 100644 fixtures/github/issues.pull_requests.created.page3.json create mode 100644 fixtures/github/repo.list.json create mode 100644 fixtures/github/user.fetch.json create mode 100644 src/api/issues.rs create mode 100644 src/api/mock.rs create mode 100644 src/asset/font_icon/list.svg create mode 100644 src/asset/font_icon/pencil_line.svg create mode 100644 src/asset/font_icon/pull_request_arrow.svg create mode 100644 src/asset/font_icon/pull_request_closed.svg create mode 100644 src/asset/font_icon/pull_request_draft.svg create mode 100644 src/asset/font_icon/user_plus.svg create mode 100644 src/screen/dashboard/issue_list.rs create mode 100644 src/screen/dashboard/sidebar.rs diff --git a/Cargo.toml b/Cargo.toml index 42c1ce5..2f187ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,10 @@ futures-lite = "2.6.1" gpui = { version = "*" } paste = "1.0" rand = "0.10.1" -reqwest = { version = "0.13.2", features = ["form"] } +reqwest = { version = "0.13.2", features = ["form", "query"] } serde = "1.0.228" serde_json = "1.0.149" tokio = { version = "1.52.1", features = ["rt-multi-thread", "net", "time"] } + +[build-dependencies] +serde_json = "1.0.149" diff --git a/build.rs b/build.rs index 2c4eef5..5dbde73 100644 --- a/build.rs +++ b/build.rs @@ -13,9 +13,13 @@ fn main() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR")); let asset_root = manifest_dir.join("src/asset"); - let out_file = PathBuf::from(env::var("OUT_DIR").expect("missing OUT_DIR")).join("asset.rs"); + let fixture_root = manifest_dir.join("fixtures/github"); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("missing OUT_DIR")); + let asset_out_file = out_dir.join("asset.rs"); + let fixture_out_file = out_dir.join("github_fixtures.rs"); println!("cargo::rerun-if-changed={}", asset_root.display()); + println!("cargo::rerun-if-changed={}", fixture_root.display()); let mut directory_entries = BTreeMap::>::new(); directory_entries @@ -32,8 +36,11 @@ fn main() { ); let generated = render_assets(&asset_files, &directory_entries); + let fixture_module = render_github_fixtures(&fixture_root); - fs::write(out_file, generated).expect("failed to write generated assets module"); + fs::write(asset_out_file, generated).expect("failed to write generated assets module"); + fs::write(fixture_out_file, fixture_module) + .expect("failed to write generated github fixtures module"); } fn collect_assets( @@ -117,6 +124,77 @@ fn render_assets( output } +fn render_github_fixtures(fixture_root: &Path) -> String { + let user_fetch = read_json_fixture(&fixture_root.join("user.fetch.json")); + let repo_list = read_json_fixture(&fixture_root.join("repo.list.json")); + + let mut issue_fixtures = BTreeMap::<(String, u32), String>::new(); + let mut entries = fs::read_dir(fixture_root) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", fixture_root.display())) + .map(|entry| entry.expect("failed to read github fixture entry")) + .collect::>(); + entries.sort_by_key(|entry| entry.file_name()); + + for entry in entries { + let file_name = entry + .file_name() + .into_string() + .unwrap_or_else(|_| panic!("non-utf8 fixture name in {}", fixture_root.display())); + + let Some((filter, page)) = parse_issue_fixture_name(&file_name) else { + continue; + }; + + issue_fixtures.insert((filter, page), read_json_fixture(&entry.path())); + } + + let mut output = String::new(); + + output.push_str("pub fn user_fetch() -> &'static str {\n"); + output.push_str(" "); + output.push_str(&string_literal(&user_fetch)); + output.push_str("\n}\n\n"); + + output.push_str("pub fn repo_list() -> &'static str {\n"); + output.push_str(" "); + output.push_str(&string_literal(&repo_list)); + output.push_str("\n}\n\n"); + + output.push_str("pub fn issues_pull_requests(filter: &str, page: u32) -> Option<&'static str> {\n"); + output.push_str(" match (filter, page) {\n"); + for ((filter, page), json) in issue_fixtures { + output.push_str(" ("); + output.push_str(&string_literal(&filter)); + output.push_str(", "); + output.push_str(&page.to_string()); + output.push_str(") => Some("); + output.push_str(&string_literal(&json)); + output.push_str("),\n"); + } + output.push_str(" _ => None,\n"); + output.push_str(" }\n"); + output.push_str("}\n"); + + output +} + +fn read_json_fixture(path: &Path) -> String { + let raw = fs::read_to_string(path) + .unwrap_or_else(|err| panic!("failed to read fixture {}: {err}", path.display())); + let value: serde_json::Value = serde_json::from_str(&raw) + .unwrap_or_else(|err| panic!("invalid json fixture {}: {err}", path.display())); + serde_json::to_string(&value) + .unwrap_or_else(|err| panic!("failed to serialize fixture {}: {err}", path.display())) +} + +fn parse_issue_fixture_name(file_name: &str) -> Option<(String, u32)> { + let name = file_name.strip_suffix(".json")?; + let rest = name.strip_prefix("issues.pull_requests.")?; + let (filter, page) = rest.rsplit_once(".page")?; + let page = page.parse::().ok()?; + Some((filter.to_owned(), page)) +} + fn string_literal(value: &str) -> String { format!("{value:?}") } diff --git a/fixtures/github/issues.pull_requests.all.page1.json b/fixtures/github/issues.pull_requests.all.page1.json new file mode 100644 index 0000000..f1c1b9e --- /dev/null +++ b/fixtures/github/issues.pull_requests.all.page1.json @@ -0,0 +1,439 @@ +[ + { + "id": 9001, + "node_id": "PR_kwDONovem84", + "url": "https://api.github.com/repos/kennethnym/novem/issues/84", + "repository_url": "https://api.github.com/repos/kennethnym/novem", + "labels_url": "https://api.github.com/repos/kennethnym/novem/issues/84/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/novem/issues/84/comments", + "events_url": "https://api.github.com/repos/kennethnym/novem/issues/84/events", + "html_url": "https://github.com/kennethnym/novem/pull/84", + "number": 84, + "state": "open", + "state_reason": null, + "title": "feat(dashboard): hydrate issue pane from cached query state", + "body": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", + "body_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 11001, + "node_id": "LA_kwDONovem_feature", + "url": "https://api.github.com/repos/kennethnym/novem/labels/feature", + "name": "feature", + "description": "New product capability.", + "color": "0E8A16", + "default": false + }, + { + "id": 11002, + "node_id": "LA_kwDONovem_dashboard", + "url": "https://api.github.com/repos/kennethnym/novem/labels/dashboard", + "name": "dashboard", + "description": "Dashboard experience.", + "color": "1D76DB", + "default": false + } + ], + "assignee": null, + "assignees": [], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 3, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/novem/pulls/84", + "html_url": "https://github.com/kennethnym/novem/pull/84", + "diff_url": "https://github.com/kennethnym/novem/pull/84.diff", + "patch_url": "https://github.com/kennethnym/novem/pull/84.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-01T09:12:00Z", + "updated_at": "2026-05-05T02:40:00Z", + "closed_by": null, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/novem/issues/84/timeline", + "repository": { + "id": 101, + "node_id": "R_kgDONovem", + "name": "novem", + "full_name": "kennethnym/novem", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/novem", + "description": "Desktop workspace for triaging GitHub work.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/novem" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9005, + "node_id": "PR_kwDOSprint62", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62", + "repository_url": "https://api.github.com/repos/kennethnym/sprint-planner", + "labels_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/comments", + "events_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/events", + "html_url": "https://github.com/kennethnym/sprint-planner/pull/62", + "number": 62, + "state": "closed", + "state_reason": "completed", + "title": "feat(calendar): ship release handoff checklist in weekly planner", + "body": "Adds the release checklist views and marks the handoff flow complete for the May rollout.", + "body_text": "Adds the release checklist views and marks the handoff flow complete for the May rollout.", + "body_html": null, + "user": { + "login": "rorycraft", + "id": 7171, + "avatar_url": "https://avatars.githubusercontent.com/u/7171?v=4", + "html_url": "https://github.com/rorycraft", + "name": "Rory Craft", + "email": "rory@example.com" + }, + "labels": [ + { + "id": 14001, + "node_id": "LA_kwDOSprint_release_blocker", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/release-blocker", + "name": "release-blocker", + "description": "Required before the next release can ship.", + "color": "B60205", + "default": false + }, + { + "id": 14002, + "node_id": "LA_kwDOSprint_planning", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/planning", + "name": "planning", + "description": "Roadmap and planning workflow.", + "color": "0E8A16", + "default": false + }, + { + "id": 14003, + "node_id": "LA_kwDOSprint_tests", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/tests", + "name": "tests", + "description": "Verification and test coverage.", + "color": "FBCA04", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + { + "login": "mariahops", + "id": 6161, + "avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4", + "html_url": "https://github.com/mariahops", + "name": "Maria Hops", + "email": "maria@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 12, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/sprint-planner/pulls/62", + "html_url": "https://github.com/kennethnym/sprint-planner/pull/62", + "diff_url": "https://github.com/kennethnym/sprint-planner/pull/62.diff", + "patch_url": "https://github.com/kennethnym/sprint-planner/pull/62.patch", + "merged_at": "2026-05-04T18:10:00Z" + }, + "closed_at": "2026-05-04T18:15:00Z", + "created_at": "2026-04-28T10:20:00Z", + "updated_at": "2026-05-05T01:10:00Z", + "closed_by": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "author_association": "MEMBER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/timeline", + "repository": { + "id": 104, + "node_id": "R_kgDOSprint", + "name": "sprint-planner", + "full_name": "kennethnym/sprint-planner", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/sprint-planner", + "description": "Weekly planning board and release calendar.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/sprint-planner" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9004, + "node_id": "PR_kwDONovem85", + "url": "https://api.github.com/repos/kennethnym/novem/issues/85", + "repository_url": "https://api.github.com/repos/kennethnym/novem", + "labels_url": "https://api.github.com/repos/kennethnym/novem/issues/85/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/novem/issues/85/comments", + "events_url": "https://api.github.com/repos/kennethnym/novem/issues/85/events", + "html_url": "https://github.com/kennethnym/novem/pull/85", + "number": 85, + "state": "open", + "state_reason": null, + "title": "feat(repo): add cached repository query for titlebar picker", + "body": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", + "body_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 11003, + "node_id": "LA_kwDONovem_performance", + "url": "https://api.github.com/repos/kennethnym/novem/labels/performance", + "name": "performance", + "description": "Performance-sensitive change.", + "color": "5319E7", + "default": false + }, + { + "id": 11004, + "node_id": "LA_kwDONovem_cache", + "url": "https://api.github.com/repos/kennethnym/novem/labels/cache", + "name": "cache", + "description": "Caching and persistence work.", + "color": "0052CC", + "default": false + }, + { + "id": 11005, + "node_id": "LA_kwDONovem_needs_review", + "url": "https://api.github.com/repos/kennethnym/novem/labels/needs-review", + "name": "needs-review", + "description": "Awaiting reviewer attention.", + "color": "FBCA04", + "default": false + } + ], + "assignee": { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + }, + "assignees": [ + { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 2, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/novem/pulls/85", + "html_url": "https://github.com/kennethnym/novem/pull/85", + "diff_url": "https://github.com/kennethnym/novem/pull/85.diff", + "patch_url": "https://github.com/kennethnym/novem/pull/85.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-03T07:40:00Z", + "updated_at": "2026-05-05T00:15:00Z", + "closed_by": null, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/novem/issues/85/timeline", + "repository": { + "id": 101, + "node_id": "R_kgDONovem", + "name": "novem", + "full_name": "kennethnym/novem", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/novem", + "description": "Desktop workspace for triaging GitHub work.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/novem" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9002, + "node_id": "PR_kwDOAgent47", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47", + "repository_url": "https://api.github.com/repos/kennethnym/agent-tooling", + "labels_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/comments", + "events_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/events", + "html_url": "https://github.com/kennethnym/agent-tooling/pull/47", + "number": 47, + "state": "open", + "state_reason": null, + "title": "feat(prompts): split context loading from execution workers", + "body": "Separates prompt packing from worker orchestration to make delegation easier to reason about.", + "body_text": "Separates prompt packing from worker orchestration to make delegation easier to reason about.", + "body_html": null, + "user": { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + }, + "labels": [ + { + "id": 12001, + "node_id": "LA_kwDOAgent_dx", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/labels/developer-experience", + "name": "developer-experience", + "description": "Improves contributor ergonomics.", + "color": "7057FF", + "default": false + }, + { + "id": 12002, + "node_id": "LA_kwDOAgent_prompts", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/labels/prompts", + "name": "prompts", + "description": "Prompt and agent coordination.", + "color": "1D76DB", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 1, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/agent-tooling/pulls/47", + "html_url": "https://github.com/kennethnym/agent-tooling/pull/47", + "diff_url": "https://github.com/kennethnym/agent-tooling/pull/47.diff", + "patch_url": "https://github.com/kennethnym/agent-tooling/pull/47.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-04-30T14:22:00Z", + "updated_at": "2026-05-04T23:10:00Z", + "closed_by": null, + "author_association": "COLLABORATOR", + "draft": true, + "timeline_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/timeline", + "repository": { + "id": 102, + "node_id": "R_kgDOAgent", + "name": "agent-tooling", + "full_name": "kennethnym/agent-tooling", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/agent-tooling", + "description": "Experiments for agent-driven developer workflows.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/agent-tooling" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + } +] diff --git a/fixtures/github/issues.pull_requests.all.page2.json b/fixtures/github/issues.pull_requests.all.page2.json new file mode 100644 index 0000000..d3f25ca --- /dev/null +++ b/fixtures/github/issues.pull_requests.all.page2.json @@ -0,0 +1,203 @@ +[ + { + "id": 9003, + "node_id": "PR_kwDODesign31", + "url": "https://api.github.com/repos/kennethnym/design-notes/issues/31", + "repository_url": "https://api.github.com/repos/kennethnym/design-notes", + "labels_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/comments", + "events_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/events", + "html_url": "https://github.com/kennethnym/design-notes/pull/31", + "number": 31, + "state": "open", + "state_reason": null, + "title": "chore(tokens): tighten dashboard spacing scale", + "body": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", + "body_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", + "body_html": null, + "user": { + "login": "mariahops", + "id": 6161, + "avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4", + "html_url": "https://github.com/mariahops", + "name": "Maria Hops", + "email": "maria@example.com" + }, + "labels": [ + { + "id": 13001, + "node_id": "LA_kwDODesign_system", + "url": "https://api.github.com/repos/kennethnym/design-notes/labels/design-system", + "name": "design-system", + "description": "Shared UI language and tokens.", + "color": "C5DEF5", + "default": false + }, + { + "id": 13002, + "node_id": "LA_kwDODesign_spacing", + "url": "https://api.github.com/repos/kennethnym/design-notes/labels/spacing", + "name": "spacing", + "description": "Layout rhythm and spacing.", + "color": "BFDADC", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 0, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/design-notes/pulls/31", + "html_url": "https://github.com/kennethnym/design-notes/pull/31", + "diff_url": "https://github.com/kennethnym/design-notes/pull/31.diff", + "patch_url": "https://github.com/kennethnym/design-notes/pull/31.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-02T11:05:00Z", + "updated_at": "2026-05-03T18:30:00Z", + "closed_by": null, + "author_association": "CONTRIBUTOR", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/timeline", + "repository": { + "id": 103, + "node_id": "R_kgDODesign", + "name": "design-notes", + "full_name": "kennethnym/design-notes", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/design-notes", + "description": "Product and UI explorations.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/design-notes" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9007, + "node_id": "PR_kwDOInfra19", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19", + "repository_url": "https://api.github.com/repos/kennethnym/infra-scripts", + "labels_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/comments", + "events_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/events", + "html_url": "https://github.com/kennethnym/infra-scripts/pull/19", + "number": 19, + "state": "closed", + "state_reason": "not_planned", + "title": "docs(deploy): document manual failover steps", + "body": null, + "body_text": null, + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 15001, + "node_id": "LA_kwDOInfra_docs", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/labels/docs", + "name": "docs", + "description": "Documentation updates.", + "color": "0075CA", + "default": true + }, + { + "id": 15002, + "node_id": "LA_kwDOInfra_infra", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/labels/infra", + "name": "infra", + "description": "Infrastructure or deployment work.", + "color": "D4C5F9", + "default": false + } + ], + "assignee": null, + "assignees": [], + "milestone": null, + "locked": true, + "active_lock_reason": "resolved", + "comments": 5, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/infra-scripts/pulls/19", + "html_url": "https://github.com/kennethnym/infra-scripts/pull/19", + "diff_url": "https://github.com/kennethnym/infra-scripts/pull/19.diff", + "patch_url": "https://github.com/kennethnym/infra-scripts/pull/19.patch", + "merged_at": null + }, + "closed_at": "2026-05-02T12:05:00Z", + "created_at": "2026-04-24T06:40:00Z", + "updated_at": "2026-05-02T12:05:00Z", + "closed_by": { + "login": "piperlane", + "id": 8181, + "avatar_url": "https://avatars.githubusercontent.com/u/8181?v=4", + "html_url": "https://github.com/piperlane", + "name": "Piper Lane", + "email": "piper@example.com" + }, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/timeline", + "repository": { + "id": 105, + "node_id": "R_kgDOInfra", + "name": "infra-scripts", + "full_name": "kennethnym/infra-scripts", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": true, + "html_url": "https://github.com/kennethnym/infra-scripts", + "description": "Deployment and environment automation.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/infra-scripts" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + } +] diff --git a/fixtures/github/issues.pull_requests.all.page3.json b/fixtures/github/issues.pull_requests.all.page3.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fixtures/github/issues.pull_requests.all.page3.json @@ -0,0 +1 @@ +[] diff --git a/fixtures/github/issues.pull_requests.assigned.page1.json b/fixtures/github/issues.pull_requests.assigned.page1.json new file mode 100644 index 0000000..d7392ae --- /dev/null +++ b/fixtures/github/issues.pull_requests.assigned.page1.json @@ -0,0 +1,341 @@ +[ + { + "id": 9005, + "node_id": "PR_kwDOSprint62", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62", + "repository_url": "https://api.github.com/repos/kennethnym/sprint-planner", + "labels_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/comments", + "events_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/events", + "html_url": "https://github.com/kennethnym/sprint-planner/pull/62", + "number": 62, + "state": "closed", + "state_reason": "completed", + "title": "feat(calendar): ship release handoff checklist in weekly planner", + "body": "Adds the release checklist views and marks the handoff flow complete for the May rollout.", + "body_text": "Adds the release checklist views and marks the handoff flow complete for the May rollout.", + "body_html": null, + "user": { + "login": "rorycraft", + "id": 7171, + "avatar_url": "https://avatars.githubusercontent.com/u/7171?v=4", + "html_url": "https://github.com/rorycraft", + "name": "Rory Craft", + "email": "rory@example.com" + }, + "labels": [ + { + "id": 14001, + "node_id": "LA_kwDOSprint_release_blocker", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/release-blocker", + "name": "release-blocker", + "description": "Required before the next release can ship.", + "color": "B60205", + "default": false + }, + { + "id": 14002, + "node_id": "LA_kwDOSprint_planning", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/planning", + "name": "planning", + "description": "Roadmap and planning workflow.", + "color": "0E8A16", + "default": false + }, + { + "id": 14003, + "node_id": "LA_kwDOSprint_tests", + "url": "https://api.github.com/repos/kennethnym/sprint-planner/labels/tests", + "name": "tests", + "description": "Verification and test coverage.", + "color": "FBCA04", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + { + "login": "mariahops", + "id": 6161, + "avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4", + "html_url": "https://github.com/mariahops", + "name": "Maria Hops", + "email": "maria@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 12, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/sprint-planner/pulls/62", + "html_url": "https://github.com/kennethnym/sprint-planner/pull/62", + "diff_url": "https://github.com/kennethnym/sprint-planner/pull/62.diff", + "patch_url": "https://github.com/kennethnym/sprint-planner/pull/62.patch", + "merged_at": "2026-05-04T18:10:00Z" + }, + "closed_at": "2026-05-04T18:15:00Z", + "created_at": "2026-04-28T10:20:00Z", + "updated_at": "2026-05-05T01:10:00Z", + "closed_by": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "author_association": "MEMBER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/sprint-planner/issues/62/timeline", + "repository": { + "id": 104, + "node_id": "R_kgDOSprint", + "name": "sprint-planner", + "full_name": "kennethnym/sprint-planner", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/sprint-planner", + "description": "Weekly planning board and release calendar.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/sprint-planner" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9002, + "node_id": "PR_kwDOAgent47", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47", + "repository_url": "https://api.github.com/repos/kennethnym/agent-tooling", + "labels_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/comments", + "events_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/events", + "html_url": "https://github.com/kennethnym/agent-tooling/pull/47", + "number": 47, + "state": "open", + "state_reason": null, + "title": "feat(prompts): split context loading from execution workers", + "body": "Separates prompt packing from worker orchestration to make delegation easier to reason about.", + "body_text": "Separates prompt packing from worker orchestration to make delegation easier to reason about.", + "body_html": null, + "user": { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + }, + "labels": [ + { + "id": 12001, + "node_id": "LA_kwDOAgent_dx", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/labels/developer-experience", + "name": "developer-experience", + "description": "Improves contributor ergonomics.", + "color": "7057FF", + "default": false + }, + { + "id": 12002, + "node_id": "LA_kwDOAgent_prompts", + "url": "https://api.github.com/repos/kennethnym/agent-tooling/labels/prompts", + "name": "prompts", + "description": "Prompt and agent coordination.", + "color": "1D76DB", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 1, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/agent-tooling/pulls/47", + "html_url": "https://github.com/kennethnym/agent-tooling/pull/47", + "diff_url": "https://github.com/kennethnym/agent-tooling/pull/47.diff", + "patch_url": "https://github.com/kennethnym/agent-tooling/pull/47.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-04-30T14:22:00Z", + "updated_at": "2026-05-04T23:10:00Z", + "closed_by": null, + "author_association": "COLLABORATOR", + "draft": true, + "timeline_url": "https://api.github.com/repos/kennethnym/agent-tooling/issues/47/timeline", + "repository": { + "id": 102, + "node_id": "R_kgDOAgent", + "name": "agent-tooling", + "full_name": "kennethnym/agent-tooling", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/agent-tooling", + "description": "Experiments for agent-driven developer workflows.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/agent-tooling" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9003, + "node_id": "PR_kwDODesign31", + "url": "https://api.github.com/repos/kennethnym/design-notes/issues/31", + "repository_url": "https://api.github.com/repos/kennethnym/design-notes", + "labels_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/comments", + "events_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/events", + "html_url": "https://github.com/kennethnym/design-notes/pull/31", + "number": 31, + "state": "open", + "state_reason": null, + "title": "chore(tokens): tighten dashboard spacing scale", + "body": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", + "body_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", + "body_html": null, + "user": { + "login": "mariahops", + "id": 6161, + "avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4", + "html_url": "https://github.com/mariahops", + "name": "Maria Hops", + "email": "maria@example.com" + }, + "labels": [ + { + "id": 13001, + "node_id": "LA_kwDODesign_system", + "url": "https://api.github.com/repos/kennethnym/design-notes/labels/design-system", + "name": "design-system", + "description": "Shared UI language and tokens.", + "color": "C5DEF5", + "default": false + }, + { + "id": 13002, + "node_id": "LA_kwDODesign_spacing", + "url": "https://api.github.com/repos/kennethnym/design-notes/labels/spacing", + "name": "spacing", + "description": "Layout rhythm and spacing.", + "color": "BFDADC", + "default": false + } + ], + "assignee": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "assignees": [ + { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 0, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/design-notes/pulls/31", + "html_url": "https://github.com/kennethnym/design-notes/pull/31", + "diff_url": "https://github.com/kennethnym/design-notes/pull/31.diff", + "patch_url": "https://github.com/kennethnym/design-notes/pull/31.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-02T11:05:00Z", + "updated_at": "2026-05-03T18:30:00Z", + "closed_by": null, + "author_association": "CONTRIBUTOR", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/design-notes/issues/31/timeline", + "repository": { + "id": 103, + "node_id": "R_kgDODesign", + "name": "design-notes", + "full_name": "kennethnym/design-notes", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/design-notes", + "description": "Product and UI explorations.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/design-notes" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + } +] diff --git a/fixtures/github/issues.pull_requests.assigned.page2.json b/fixtures/github/issues.pull_requests.assigned.page2.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fixtures/github/issues.pull_requests.assigned.page2.json @@ -0,0 +1 @@ +[] diff --git a/fixtures/github/issues.pull_requests.created.page1.json b/fixtures/github/issues.pull_requests.created.page1.json new file mode 100644 index 0000000..e9575ad --- /dev/null +++ b/fixtures/github/issues.pull_requests.created.page1.json @@ -0,0 +1,205 @@ +[ + { + "id": 9004, + "node_id": "PR_kwDONovem85", + "url": "https://api.github.com/repos/kennethnym/novem/issues/85", + "repository_url": "https://api.github.com/repos/kennethnym/novem", + "labels_url": "https://api.github.com/repos/kennethnym/novem/issues/85/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/novem/issues/85/comments", + "events_url": "https://api.github.com/repos/kennethnym/novem/issues/85/events", + "html_url": "https://github.com/kennethnym/novem/pull/85", + "number": 85, + "state": "open", + "state_reason": null, + "title": "feat(repo): add cached repository query for titlebar picker", + "body": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", + "body_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 11003, + "node_id": "LA_kwDONovem_performance", + "url": "https://api.github.com/repos/kennethnym/novem/labels/performance", + "name": "performance", + "description": "Performance-sensitive change.", + "color": "5319E7", + "default": false + }, + { + "id": 11004, + "node_id": "LA_kwDONovem_cache", + "url": "https://api.github.com/repos/kennethnym/novem/labels/cache", + "name": "cache", + "description": "Caching and persistence work.", + "color": "0052CC", + "default": false + }, + { + "id": 11005, + "node_id": "LA_kwDONovem_needs_review", + "url": "https://api.github.com/repos/kennethnym/novem/labels/needs-review", + "name": "needs-review", + "description": "Awaiting reviewer attention.", + "color": "FBCA04", + "default": false + } + ], + "assignee": { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + }, + "assignees": [ + { + "login": "leaferiksen", + "id": 5151, + "avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4", + "html_url": "https://github.com/leaferiksen", + "name": "Leaf Eriksen", + "email": "leaf@example.com" + } + ], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 2, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/novem/pulls/85", + "html_url": "https://github.com/kennethnym/novem/pull/85", + "diff_url": "https://github.com/kennethnym/novem/pull/85.diff", + "patch_url": "https://github.com/kennethnym/novem/pull/85.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-03T07:40:00Z", + "updated_at": "2026-05-05T00:15:00Z", + "closed_by": null, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/novem/issues/85/timeline", + "repository": { + "id": 101, + "node_id": "R_kgDONovem", + "name": "novem", + "full_name": "kennethnym/novem", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/novem", + "description": "Desktop workspace for triaging GitHub work.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/novem" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + }, + { + "id": 9001, + "node_id": "PR_kwDONovem84", + "url": "https://api.github.com/repos/kennethnym/novem/issues/84", + "repository_url": "https://api.github.com/repos/kennethnym/novem", + "labels_url": "https://api.github.com/repos/kennethnym/novem/issues/84/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/novem/issues/84/comments", + "events_url": "https://api.github.com/repos/kennethnym/novem/issues/84/events", + "html_url": "https://github.com/kennethnym/novem/pull/84", + "number": 84, + "state": "open", + "state_reason": null, + "title": "feat(dashboard): hydrate issue pane from cached query state", + "body": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", + "body_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 11001, + "node_id": "LA_kwDONovem_feature", + "url": "https://api.github.com/repos/kennethnym/novem/labels/feature", + "name": "feature", + "description": "New product capability.", + "color": "0E8A16", + "default": false + }, + { + "id": 11002, + "node_id": "LA_kwDONovem_dashboard", + "url": "https://api.github.com/repos/kennethnym/novem/labels/dashboard", + "name": "dashboard", + "description": "Dashboard experience.", + "color": "1D76DB", + "default": false + } + ], + "assignee": null, + "assignees": [], + "milestone": null, + "locked": false, + "active_lock_reason": null, + "comments": 3, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/novem/pulls/84", + "html_url": "https://github.com/kennethnym/novem/pull/84", + "diff_url": "https://github.com/kennethnym/novem/pull/84.diff", + "patch_url": "https://github.com/kennethnym/novem/pull/84.patch", + "merged_at": null + }, + "closed_at": null, + "created_at": "2026-05-01T09:12:00Z", + "updated_at": "2026-05-05T02:40:00Z", + "closed_by": null, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/novem/issues/84/timeline", + "repository": { + "id": 101, + "node_id": "R_kgDONovem", + "name": "novem", + "full_name": "kennethnym/novem", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": false, + "html_url": "https://github.com/kennethnym/novem", + "description": "Desktop workspace for triaging GitHub work.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/novem" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + } +] diff --git a/fixtures/github/issues.pull_requests.created.page2.json b/fixtures/github/issues.pull_requests.created.page2.json new file mode 100644 index 0000000..6e73e3a --- /dev/null +++ b/fixtures/github/issues.pull_requests.created.page2.json @@ -0,0 +1,98 @@ +[ + { + "id": 9007, + "node_id": "PR_kwDOInfra19", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19", + "repository_url": "https://api.github.com/repos/kennethnym/infra-scripts", + "labels_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/labels{/name}", + "comments_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/comments", + "events_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/events", + "html_url": "https://github.com/kennethnym/infra-scripts/pull/19", + "number": 19, + "state": "closed", + "state_reason": "not_planned", + "title": "docs(deploy): document manual failover steps", + "body": null, + "body_text": null, + "body_html": null, + "user": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "labels": [ + { + "id": 15001, + "node_id": "LA_kwDOInfra_docs", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/labels/docs", + "name": "docs", + "description": "Documentation updates.", + "color": "0075CA", + "default": true + }, + { + "id": 15002, + "node_id": "LA_kwDOInfra_infra", + "url": "https://api.github.com/repos/kennethnym/infra-scripts/labels/infra", + "name": "infra", + "description": "Infrastructure or deployment work.", + "color": "D4C5F9", + "default": false + } + ], + "assignee": null, + "assignees": [], + "milestone": null, + "locked": true, + "active_lock_reason": "resolved", + "comments": 5, + "pull_request": { + "url": "https://api.github.com/repos/kennethnym/infra-scripts/pulls/19", + "html_url": "https://github.com/kennethnym/infra-scripts/pull/19", + "diff_url": "https://github.com/kennethnym/infra-scripts/pull/19.diff", + "patch_url": "https://github.com/kennethnym/infra-scripts/pull/19.patch", + "merged_at": null + }, + "closed_at": "2026-05-02T12:05:00Z", + "created_at": "2026-04-24T06:40:00Z", + "updated_at": "2026-05-02T12:05:00Z", + "closed_by": { + "login": "piperlane", + "id": 8181, + "avatar_url": "https://avatars.githubusercontent.com/u/8181?v=4", + "html_url": "https://github.com/piperlane", + "name": "Piper Lane", + "email": "piper@example.com" + }, + "author_association": "OWNER", + "draft": false, + "timeline_url": "https://api.github.com/repos/kennethnym/infra-scripts/issues/19/timeline", + "repository": { + "id": 105, + "node_id": "R_kgDOInfra", + "name": "infra-scripts", + "full_name": "kennethnym/infra-scripts", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" + }, + "private": true, + "html_url": "https://github.com/kennethnym/infra-scripts", + "description": "Deployment and environment automation.", + "fork": false, + "url": "https://api.github.com/repos/kennethnym/infra-scripts" + }, + "performed_via_github_app": null, + "reactions": null, + "pinned_comment": null, + "type": null, + "sub_issues_summary": null + } +] diff --git a/fixtures/github/issues.pull_requests.created.page3.json b/fixtures/github/issues.pull_requests.created.page3.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fixtures/github/issues.pull_requests.created.page3.json @@ -0,0 +1 @@ +[] diff --git a/fixtures/github/repo.list.json b/fixtures/github/repo.list.json new file mode 100644 index 0000000..8483c83 --- /dev/null +++ b/fixtures/github/repo.list.json @@ -0,0 +1,77 @@ +[ + { + "id": 101, + "name": "novem", + "full_name": "kennethnym/novem", + "private": false, + "html_url": "https://github.com/kennethnym/novem", + "description": "Desktop workspace for triaging GitHub work.", + "default_branch": "main", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym" + } + }, + { + "id": 104, + "name": "sprint-planner", + "full_name": "kennethnym/sprint-planner", + "private": false, + "html_url": "https://github.com/kennethnym/sprint-planner", + "description": "Weekly planning board and release calendar.", + "default_branch": "main", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym" + } + }, + { + "id": 102, + "name": "agent-tooling", + "full_name": "kennethnym/agent-tooling", + "private": false, + "html_url": "https://github.com/kennethnym/agent-tooling", + "description": "Experiments for agent-driven developer workflows.", + "default_branch": "main", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym" + } + }, + { + "id": 103, + "name": "design-notes", + "full_name": "kennethnym/design-notes", + "private": false, + "html_url": "https://github.com/kennethnym/design-notes", + "description": "Product and UI explorations.", + "default_branch": "main", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym" + } + }, + { + "id": 105, + "name": "infra-scripts", + "full_name": "kennethnym/infra-scripts", + "private": true, + "html_url": "https://github.com/kennethnym/infra-scripts", + "description": "Deployment and environment automation.", + "default_branch": "trunk", + "owner": { + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym" + } + } +] diff --git a/fixtures/github/user.fetch.json b/fixtures/github/user.fetch.json new file mode 100644 index 0000000..c1496c1 --- /dev/null +++ b/fixtures/github/user.fetch.json @@ -0,0 +1,8 @@ +{ + "login": "kennethnym", + "id": 4242, + "avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4", + "html_url": "https://github.com/kennethnym", + "name": "Kenneth Ng", + "email": "kenneth@example.com" +} diff --git a/src/api.rs b/src/api.rs index 84d0053..b8a51c6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,11 +1,11 @@ -use std::fmt::format; - -use reqwest::{Response, dns::Resolving}; use serde::{Deserialize, Serialize}; use crate::query; pub(crate) mod auth; +pub(crate) mod issues; +#[cfg(debug_assertions)] +mod mock; pub(crate) mod repo; pub(crate) mod user; @@ -14,6 +14,9 @@ pub struct QueryContext { pub(crate) http: reqwest::Client, pub(crate) auth: Option, pub(crate) github: GithubCredentials, + + #[cfg(debug_assertions)] + pub(crate) should_use_fixtures: bool, } #[derive(Clone, Serialize, Deserialize)] @@ -30,6 +33,8 @@ pub(crate) struct GithubCredentials { #[derive(Debug)] pub(crate) enum Error { Unauthenticated, + #[cfg(debug_assertions)] + MissingMockFixture(String), Github(GithubError), MalformedResponse(serde_json::Error), HttpError(reqwest::Error), @@ -63,6 +68,16 @@ impl QueryContext { } } +#[cfg(debug_assertions)] +pub(crate) fn use_github_fixtures() -> bool { + mock::is_enabled() +} + +#[cfg(not(debug_assertions))] +pub(crate) fn use_github_fixtures() -> bool { + false +} + impl query::Context for QueryContext {} impl From for Error { diff --git a/src/api/auth.rs b/src/api/auth.rs index 90d9fcc..201a086 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -2,10 +2,7 @@ use std::collections::HashMap; use serde::Deserialize; -use crate::{ - api, - query::{self, use_query}, -}; +use crate::{api, query}; pub(crate) const DEVICE_LOGIN_FLOW_URL: &str = "https://github.com/login/device"; @@ -28,8 +25,8 @@ impl query::QueryFn for CreateDeviceCode { type Error = api::Error; type Context = api::QueryContext; - fn key(&self) -> &'static str { - "auth.device_code" + fn key(&self) -> query::Key { + "auth/device_code".into() } async fn run(&self, c: &Self::Context) -> Result { @@ -64,8 +61,8 @@ impl query::QueryFn for RequestAccessToken { type Error = api::Error; type Context = api::QueryContext; - fn key(&self) -> &'static str { - "auth.access_token" + fn key(&self) -> query::Key { + "auth.access_token".into() } async fn run(&self, c: &Self::Context) -> Result { diff --git a/src/api/issues.rs b/src/api/issues.rs new file mode 100644 index 0000000..9d5e609 --- /dev/null +++ b/src/api/issues.rs @@ -0,0 +1,226 @@ +use std::ops::Deref; + +use reqwest::Method; +use serde::Deserialize; + +use crate::{ + api::{self, user}, + query, +}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub(crate) struct Id(u64); + +#[derive(Debug, Deserialize)] +pub(crate) struct Issue { + pub(crate) id: Id, + pub(crate) node_id: String, + pub(crate) url: String, + pub(crate) repository_url: String, + pub(crate) labels_url: String, + pub(crate) comments_url: String, + pub(crate) events_url: String, + pub(crate) html_url: String, + pub(crate) number: u64, + pub(crate) state: String, + pub(crate) state_reason: Option, + pub(crate) title: String, + pub(crate) body: Option, + pub(crate) body_text: Option, + pub(crate) body_html: Option, + pub(crate) user: Option, + pub(crate) labels: Vec