wip: pr file diffing
This commit is contained in:
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/agent-tooling",
|
||||
"base_ref": "8c79c9054e0c28b7ff4b79dd55b04c9af0d81132",
|
||||
"head_branch_name": "feat/worker-context-envelope",
|
||||
"head_repo_slug": "kennethnym/agent-tooling",
|
||||
"head_ref": "4a8df12be732c0f9e5d194cd2af7430c0d2fb8d4",
|
||||
"body": "## Goal\n\nSplit context loading from execution workers so delegation stays predictable while this pull request is still in draft.\n\n### Why\n- workers should receive a compact payload\n- prompt packing should be testable without spawning a worker\n- retry policy should stay in one place\n\n### Proposed flow\n1. Load repository context once.\n2. Normalize file excerpts and metadata.\n3. Hand workers a stable execution envelope.\n\n```text\nContextLoader -> PromptAssembler -> WorkerRunner\n```\n\n> Draft status stays until we decide whether token counts belong in the worker response.\n\n### Questions\n- Should `ContextLoader` expose cache hit metrics?\n- Should worker retries carry the same prompt hash?\n- [ ] Add a regression test for interrupted workers"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/design-notes",
|
||||
"base_ref": "7ab2d10c6f0f244ab18a28fd04c669b47e9bc611",
|
||||
"head_branch_name": "chore/dashboard-spacing-scale",
|
||||
"head_repo_slug": "kennethnym/design-notes",
|
||||
"head_ref": "5b0cf338ec46d581af0d582da6427a3dfbce9018",
|
||||
"body": "## Summary\n\nTightens the dashboard spacing scale before the next visual refresh.\n\n### Updated tokens\n- `space.3` for compact sidebar gaps\n- `space.5` for section rhythm\n- `space.8` for page-level separation\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- verify heading baselines still align with list content\n- compare 1280px and 1440px screenshots side by side\n- [ ] revisit compact mode once the nav collapse lands\n\n**Design intent:** make dense screens feel more deliberate without looking cramped."
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/infra-scripts",
|
||||
"base_ref": "4c0de7c2d9c38e7ab1cfe273d03c08bbcbf71740",
|
||||
"head_branch_name": "docs/manual-failover-steps",
|
||||
"head_repo_slug": "kennethnym/infra-scripts",
|
||||
"head_ref": "6fd11baf0d9d53d18f6d7b7dc265d9b09e6f4217",
|
||||
"body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated recovery path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers.\n4. Warm the cache before reopening traffic.\n\n```bash\n./scripts/failover promote-standby --env staging\n./scripts/failover repoint-workers --env staging\n./scripts/failover verify --env staging\n```\n\n> This pull request was closed because the final DNS validation steps were still changing underneath the runbook.\n\n### Remaining gaps\n- secrets rotation is still manual\n- rollback screenshots are missing\n- [ ] add the final post-cutover checklist"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/novem",
|
||||
"base_ref": "5e8745bfcc0c90c226d3c6af84226d6d4a5ec2d1",
|
||||
"head_branch_name": "feat/cached-issue-pane",
|
||||
"head_repo_slug": "kennethnym/novem",
|
||||
"head_ref": "2bc41de7731b9ef48f7d64ee9f0d5f497dbe0a51",
|
||||
"body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Rendering coverage\n- [x] headings\n- [x] bullet lists\n- [x] task list items\n- [x] inline code like `use_query`\n- [x] tables\n\n### Implementation sketch\n```rust\nlet cached = query_store.read(key);\nlet selection = cached.and_then(|data| data.selected_issue_id.clone());\n```\n\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- [ ] mirror the same cache behavior in the pull request detail pane\n- [ ] add a smoke test around keyboard navigation during refetch\n\nSee also the [query store](src/query.rs) integration notes."
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/novem",
|
||||
"base_ref": "5e8745bfcc0c90c226d3c6af84226d6d4a5ec2d1",
|
||||
"head_branch_name": "feat/cached-repo-picker",
|
||||
"head_repo_slug": "kennethnym/novem",
|
||||
"head_ref": "13af7d0b48a6ce0b22d48c9b6c1c78dfcd94e6a0",
|
||||
"body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories visible during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Cache rules\n- explicit refresh invalidates the cached list\n- fresh network data still wins when available\n- empty responses should not overwrite a warm cache\n\n```text\nopen picker -> read cache -> render immediately -> refresh in background\n```\n\n### Follow-up\n1. Measure cache hit rate in debug builds.\n2. Add eviction telemetry.\n3. [ ] Consider persisting the last successful repository list across launches."
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
},
|
||||
"base_branch_name": "main",
|
||||
"base_repo_slug": "kennethnym/sprint-planner",
|
||||
"base_ref": "c6d7e91ef84a8ce6c14f8a06f5588d60962d0af7",
|
||||
"head_branch_name": "feat/release-handoff-checklist",
|
||||
"head_repo_slug": "kennethnym/sprint-planner",
|
||||
"head_ref": "be7a8114a57f3e9d214cb9af457c10fd6c5a0b21",
|
||||
"body": "## Release handoff checklist\n\nAdds the release checklist views and closes the loop for the May rollout.\n\n### Included\n- launch readiness checklist for QA, docs, and release engineering\n- handoff status badges in the weekly planner\n- empty-state copy for weeks without a scheduled release\n\n| Stage | Owner | Status |\n| --- | --- | --- |\n| QA sign-off | `@mariahops` | Done |\n| Docs publish | `@rorycraft` | Done |\n| Release window confirm | `@kennethnym` | Done |\n\n### Verification\n1. Open a release week and confirm checklist sections render in order.\n2. Mark each handoff item complete and confirm the summary badge updates.\n3. Review the planner on a narrow viewport.\n\n> The merged version intentionally keeps the checklist readable even when one section has no pending items.\n\n- [x] QA sign-off state is visible\n- [x] Docs handoff state is visible\n- [ ] Add screenshot coverage for the compact layout"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"node": {
|
||||
"__typename": "PullRequest",
|
||||
"files": {
|
||||
"edges": [
|
||||
{
|
||||
"cursor": "file:PR_kwDONovem84:1",
|
||||
"node": {
|
||||
"changeType": "MODIFIED",
|
||||
"additions": 38,
|
||||
"deletions": 11,
|
||||
"path": "src/query.rs",
|
||||
"viewerViewedState": "UNVIEWED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cursor": "file:PR_kwDONovem84:2",
|
||||
"node": {
|
||||
"changeType": "MODIFIED",
|
||||
"additions": 54,
|
||||
"deletions": 17,
|
||||
"path": "src/screen/dashboard/issue_list.rs",
|
||||
"viewerViewedState": "VIEWED"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::cmp::Reverse;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Repository {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub full_name: String,
|
||||
pub description: Option<String>,
|
||||
pub default_branch: String,
|
||||
pub private: bool,
|
||||
pub owner: Owner,
|
||||
pub html_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Owner {
|
||||
pub login: String,
|
||||
pub id: u64,
|
||||
pub avatar_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RepoMatch {
|
||||
pub full_name: String,
|
||||
pub score: usize,
|
||||
pub highlighted_description: Option<String>,
|
||||
}
|
||||
|
||||
pub fn score_repositories(repos: Vec<Repository>, needle: &str) -> Vec<RepoMatch> {
|
||||
let needle = needle.trim().to_ascii_lowercase();
|
||||
let mut matches = repos
|
||||
.into_iter()
|
||||
.filter_map(|repo| {
|
||||
let score = match (
|
||||
repo.full_name.eq_ignore_ascii_case(&needle),
|
||||
repo.name.eq_ignore_ascii_case(&needle),
|
||||
) {
|
||||
(true, _) => 400,
|
||||
(_, true) => 300,
|
||||
_ if repo.full_name.to_ascii_lowercase().contains(&needle) => 200,
|
||||
_ if repo
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|description| description.to_ascii_lowercase().contains(&needle))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
100
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(RepoMatch {
|
||||
full_name: repo.full_name,
|
||||
score,
|
||||
highlighted_description: repo.description.map(|description| {
|
||||
description.replace(needle.as_str(), &needle.to_ascii_uppercase())
|
||||
}),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
matches.sort_by_key(|repo| (Reverse(repo.score), repo.full_name.clone()));
|
||||
matches
|
||||
}
|
||||
|
||||
pub fn build_content_path(owner: &str, repo: &str, path: &str, reff: Option<&str>) -> String {
|
||||
match reff {
|
||||
Some(reff) => format!("/repos/{owner}/{repo}/contents/{path}?ref={reff}"),
|
||||
None => format!("/repos/{owner}/{repo}/contents/{path}"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use std::{collections::HashMap, time::SystemTime};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CachedQueryState {
|
||||
pub selected_issue_id: Option<String>,
|
||||
pub selected_pull_request_id: Option<String>,
|
||||
pub scroll_anchor: Option<String>,
|
||||
pub scroll_offset: usize,
|
||||
pub stale_at: Option<SystemTime>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct QueryStore {
|
||||
entries: HashMap<String, CachedQueryState>,
|
||||
}
|
||||
|
||||
impl QueryStore {
|
||||
pub fn remember_issue_state(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
selected_issue_id: Option<String>,
|
||||
selected_pull_request_id: Option<String>,
|
||||
scroll_anchor: Option<String>,
|
||||
scroll_offset: usize,
|
||||
) {
|
||||
self.entries.insert(
|
||||
key.into(),
|
||||
CachedQueryState {
|
||||
selected_issue_id,
|
||||
selected_pull_request_id,
|
||||
scroll_anchor,
|
||||
scroll_offset,
|
||||
stale_at: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn snapshot(&self, key: &str) -> Option<CachedQueryState> {
|
||||
self.entries.get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn clear_stale(&mut self) {
|
||||
self.entries
|
||||
.retain(|_, state| state.stale_at.is_none() || state.selected_issue_id.is_some());
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &str) -> Option<CachedQueryState> {
|
||||
self.entries.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconcile_selected_issue(
|
||||
cached: Option<&CachedQueryState>,
|
||||
visible_ids: &[String],
|
||||
) -> Option<String> {
|
||||
let cached_issue = cached.and_then(|state| state.selected_issue_id.as_ref());
|
||||
|
||||
cached_issue
|
||||
.filter(|issue_id| visible_ids.iter().any(|visible| visible == *issue_id))
|
||||
.cloned()
|
||||
.or_else(|| visible_ids.first().cloned())
|
||||
}
|
||||
|
||||
pub fn reconcile_scroll_anchor(
|
||||
cached: Option<&CachedQueryState>,
|
||||
visible_ids: &[String],
|
||||
) -> Option<String> {
|
||||
cached
|
||||
.and_then(|state| state.scroll_anchor.as_ref())
|
||||
.filter(|anchor| visible_ids.iter().any(|visible| visible == *anchor))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn should_repaint(
|
||||
cached: Option<&CachedQueryState>,
|
||||
next_issue_id: Option<&str>,
|
||||
next_pull_request_id: Option<&str>,
|
||||
) -> bool {
|
||||
cached.and_then(|state| state.selected_issue_id.as_deref()) != next_issue_id
|
||||
|| cached.and_then(|state| state.selected_pull_request_id.as_deref())
|
||||
!= next_pull_request_id
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use gpui::{
|
||||
InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div, list,
|
||||
point, prelude::FluentBuilder, px,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api::{self},
|
||||
app,
|
||||
component::{
|
||||
font_icon::{FontIcon, FontIconSvg, font_icon},
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
};
|
||||
|
||||
pub(crate) struct IssueList {
|
||||
pr_query: query::Entity<api::issues::ListPullRequests>,
|
||||
|
||||
list_state: gpui::ListState,
|
||||
list_items: Vec<IssueListItem>,
|
||||
selected_item: Option<(usize, gpui::SharedString)>,
|
||||
}
|
||||
|
||||
pub(crate) enum Event {
|
||||
ItemSelected(api::issues::Id),
|
||||
}
|
||||
|
||||
#[derive(gpui::IntoElement, Clone)]
|
||||
pub(crate) struct IssueListItem {
|
||||
id: gpui::SharedString,
|
||||
repo_name: Option<gpui::SharedString>,
|
||||
title: gpui::SharedString,
|
||||
description: Option<gpui::SharedString>,
|
||||
status: api::issues::PullRequestState,
|
||||
is_selected: bool,
|
||||
is_last: bool,
|
||||
is_draft: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> IssueList {
|
||||
let mut list = IssueList {
|
||||
pr_query: use_query(
|
||||
api::issues::ListPullRequests {
|
||||
filter: Some("author:@me state:open"),
|
||||
page: 1,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)),
|
||||
list_items: Vec::new(),
|
||||
selected_item: None,
|
||||
};
|
||||
list.on_create(cx);
|
||||
list
|
||||
}
|
||||
|
||||
impl IssueList {
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
cx.observe(&self.pr_query, |this, _, cx| {
|
||||
let data = read_query(&this.pr_query, cx);
|
||||
if let QueryStatus::Loaded(res) = data {
|
||||
let old_len = this.list_state.item_count();
|
||||
let new_len = res.items.len();
|
||||
|
||||
let new_items = res.items.iter().enumerate().map(|(i, it)| IssueListItem {
|
||||
id: gpui::SharedString::from(it.id.deref()),
|
||||
repo_name: Some(gpui::SharedString::new(it.repo_slug.as_str())),
|
||||
title: gpui::SharedString::new(it.title.as_str()),
|
||||
description: None,
|
||||
status: it.state,
|
||||
is_selected: this
|
||||
.selected_item
|
||||
.as_ref()
|
||||
.map(|(_, id)| id.as_str() == it.id.as_str())
|
||||
.unwrap_or(false),
|
||||
is_last: i == new_len - 1,
|
||||
is_draft: it.is_draft,
|
||||
});
|
||||
|
||||
this.list_items.splice(old_len..old_len, new_items);
|
||||
this.list_state.splice(old_len..old_len, new_len);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {
|
||||
let Some(item_id) = self.list_items.get(i).map(|item| item.id.clone()) else {
|
||||
return;
|
||||
};
|
||||
for (j, item) in self.list_items.iter_mut().enumerate() {
|
||||
item.is_selected = i == j;
|
||||
}
|
||||
cx.notify();
|
||||
cx.emit(Event::ItemSelected(item_id.as_str().into()));
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for IssueList {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let weak = cx.entity();
|
||||
|
||||
list(self.list_state.clone(), move |i, _, cx| {
|
||||
let item = {
|
||||
let this = weak.read(cx);
|
||||
this.list_items[i].clone()
|
||||
};
|
||||
let weak = weak.clone();
|
||||
|
||||
div()
|
||||
.id(item.id.clone())
|
||||
.on_click(move |_, _, cx| {
|
||||
_ = weak.update(cx, |this, cx| {
|
||||
this.on_item_click(i, cx);
|
||||
})
|
||||
})
|
||||
.child(item)
|
||||
.into_any_element()
|
||||
})
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::EventEmitter<Event> for IssueList {}
|
||||
@@ -0,0 +1,33 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Repository {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub full_name: String,
|
||||
pub description: Option<String>,
|
||||
pub default_branch: String,
|
||||
pub private: bool,
|
||||
pub owner: Owner,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Owner {
|
||||
pub login: String,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
pub fn filter_repositories(repos: Vec<Repository>, needle: &str) -> Vec<Repository> {
|
||||
let needle = needle.trim().to_ascii_lowercase();
|
||||
|
||||
repos.into_iter()
|
||||
.filter(|repo| {
|
||||
repo.name.to_ascii_lowercase().contains(&needle)
|
||||
|| repo.full_name.to_ascii_lowercase().contains(&needle)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn primary_repo_name(repo: &Repository) -> &str {
|
||||
repo.name.as_str()
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CachedSelection {
|
||||
pub issue_id: Option<String>,
|
||||
pub scroll_offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct QueryStore {
|
||||
entries: HashMap<String, CachedSelection>,
|
||||
}
|
||||
|
||||
impl QueryStore {
|
||||
pub fn snapshot(&self, key: &str) -> Option<CachedSelection> {
|
||||
self.entries.get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn remember(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
issue_id: Option<String>,
|
||||
scroll_offset: usize,
|
||||
) {
|
||||
self.entries.insert(
|
||||
key.into(),
|
||||
CachedSelection {
|
||||
issue_id,
|
||||
scroll_offset,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, key: &str) {
|
||||
self.entries.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconcile_selection(
|
||||
cached: Option<&CachedSelection>,
|
||||
visible_ids: &[String],
|
||||
) -> Option<String> {
|
||||
let current = cached.and_then(|state| state.issue_id.clone());
|
||||
|
||||
if let Some(id) = current.as_ref() {
|
||||
if visible_ids.iter().any(|visible| visible == id) {
|
||||
return Some(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
visible_ids.first().cloned()
|
||||
}
|
||||
|
||||
pub fn should_repaint(cached: Option<&CachedSelection>, next_issue_id: Option<&str>) -> bool {
|
||||
cached.and_then(|state| state.issue_id.as_deref()) != next_issue_id
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use gpui::{IntoElement, ParentElement, Styled, div, list, px};
|
||||
|
||||
use crate::{
|
||||
api,
|
||||
component::{
|
||||
font_icon::{FontIcon, font_icon},
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
};
|
||||
|
||||
pub(crate) struct IssueList {
|
||||
pr_query: query::Entity<api::issues::ListPullRequests>,
|
||||
list_state: gpui::ListState,
|
||||
list_items: Vec<IssueListItem>,
|
||||
}
|
||||
|
||||
#[derive(gpui::IntoElement, Clone)]
|
||||
pub(crate) struct IssueListItem {
|
||||
id: gpui::SharedString,
|
||||
title: gpui::SharedString,
|
||||
status: api::issues::PullRequestState,
|
||||
}
|
||||
|
||||
pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> IssueList {
|
||||
let mut list = IssueList {
|
||||
pr_query: use_query(
|
||||
api::issues::ListPullRequests {
|
||||
filter: Some("author:@me state:open"),
|
||||
page: 1,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)),
|
||||
list_items: Vec::new(),
|
||||
};
|
||||
list.on_create(cx);
|
||||
list
|
||||
}
|
||||
|
||||
impl IssueList {
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
cx.observe(&self.pr_query, |this, _, cx| {
|
||||
if let QueryStatus::Loaded(res) = read_query(&this.pr_query, cx) {
|
||||
this.list_items = res
|
||||
.items
|
||||
.iter()
|
||||
.map(|it| IssueListItem {
|
||||
id: gpui::SharedString::from(it.id.deref()),
|
||||
title: gpui::SharedString::new(it.title.as_str()),
|
||||
status: it.state,
|
||||
})
|
||||
.collect();
|
||||
this.list_state.reset(this.list_items.len());
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for IssueList {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
list(self.list_state.clone(), move |_, _, _| {
|
||||
div().child(text("pull request row")).into_any_element()
|
||||
})
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user