feat: subtext under pr title

This commit is contained in:
2026-05-12 01:34:33 +08:00
parent bfcfac61e8
commit 2fe3f7b94f
12 changed files with 290 additions and 105 deletions

View File

@@ -2,5 +2,11 @@
"title": "feat(prompts): split context loading from execution workers", "title": "feat(prompts): split context loading from execution workers",
"state": "OPEN", "state": "OPEN",
"is_draft": true, "is_draft": true,
"author": {
"login": "leaferiksen",
"avatar_url": "https://avatars.githubusercontent.com/u/5151?v=4"
},
"base_branch_name": "main",
"head_branch_name": "feat/worker-context-envelope",
"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" "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"
} }

View File

@@ -2,5 +2,11 @@
"title": "chore(tokens): tighten dashboard spacing scale", "title": "chore(tokens): tighten dashboard spacing scale",
"state": "OPEN", "state": "OPEN",
"is_draft": false, "is_draft": false,
"author": {
"login": "mariahops",
"avatar_url": "https://avatars.githubusercontent.com/u/6161?v=4"
},
"base_branch_name": "main",
"head_branch_name": "chore/dashboard-spacing-scale",
"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." "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."
} }

View File

@@ -2,5 +2,11 @@
"title": "docs(deploy): document manual failover steps", "title": "docs(deploy): document manual failover steps",
"state": "CLOSED", "state": "CLOSED",
"is_draft": false, "is_draft": false,
"author": {
"login": "kennethnym",
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
},
"base_branch_name": "main",
"head_branch_name": "docs/manual-failover-steps",
"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" "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"
} }

View File

@@ -2,5 +2,11 @@
"title": "feat(dashboard): hydrate issue pane from cached query state", "title": "feat(dashboard): hydrate issue pane from cached query state",
"state": "OPEN", "state": "OPEN",
"is_draft": false, "is_draft": false,
"author": {
"login": "kennethnym",
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
},
"base_branch_name": "main",
"head_branch_name": "feat/cached-issue-pane",
"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." "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."
} }

View File

@@ -2,5 +2,11 @@
"title": "feat(repo): add cached repository query for titlebar picker", "title": "feat(repo): add cached repository query for titlebar picker",
"state": "OPEN", "state": "OPEN",
"is_draft": false, "is_draft": false,
"author": {
"login": "kennethnym",
"avatar_url": "https://avatars.githubusercontent.com/u/4242?v=4"
},
"base_branch_name": "main",
"head_branch_name": "feat/cached-repo-picker",
"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." "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."
} }

View File

@@ -2,5 +2,11 @@
"title": "feat(calendar): ship release handoff checklist in weekly planner", "title": "feat(calendar): ship release handoff checklist in weekly planner",
"state": "MERGED", "state": "MERGED",
"is_draft": false, "is_draft": false,
"author": {
"login": "rorycraft",
"avatar_url": "https://avatars.githubusercontent.com/u/7171?v=4"
},
"base_branch_name": "main",
"head_branch_name": "feat/release-handoff-checklist",
"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" "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"
} }

View File

@@ -6,6 +6,17 @@ query PullRequestQuery($id: ID!) {
body body
state state
isDraft isDraft
baseRef {
name
}
headRef {
name
}
author {
__typename
login
avatarUrl(size: 32)
}
} }
} }
} }

View File

@@ -70,6 +70,9 @@ pub(crate) struct DetailedPullRequest {
pub(crate) state: PullRequestState, pub(crate) state: PullRequestState,
pub(crate) is_draft: bool, pub(crate) is_draft: bool,
pub(crate) body: String, pub(crate) body: String,
pub(crate) author: Option<super::user::Actor>,
pub(crate) base_branch_name: Option<String>,
pub(crate) head_branch_name: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -285,8 +288,8 @@ impl query::QueryFn for ListPullRequests {
} }
let query_string = match self.filter { let query_string = match self.filter {
Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter), | Some(filter) => format!("is:pr archived:false sort:updated-desc {}", filter),
None => "is:pr archived:false sort:updated-desc".into(), | None => "is:pr archived:false sort:updated-desc".into(),
}; };
let gql = let gql =
@@ -308,19 +311,19 @@ impl query::QueryFn for ListPullRequests {
.flatten() .flatten()
.filter_map(|edge| { .filter_map(|edge| {
edge.node.and_then(|n| match n { edge.node.and_then(|n| match n {
PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => { | PullRequestPaginationQuerySearchEdgesNode::PullRequest(p) => {
Some(PullRequest { Some(PullRequest {
id: p.id.into(), id: p.id.into(),
title: p.title, title: p.title,
state: p.state, state: p.state,
is_draft: p.is_draft, is_draft: p.is_draft,
repo_slug: format!( repo_slug: format!(
"{}/{}", "{}/{}",
p.repository.owner.login, p.repository.name p.repository.owner.login, p.repository.name
), ),
}) })
} }
_ => None, | _ => None,
}) })
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -365,15 +368,21 @@ impl query::QueryFn for FetchPullRequest {
"missing 'node' field on PullRequestQuery response".into(), "missing 'node' field on PullRequestQuery response".into(),
)) ))
.and_then(|n| match n { .and_then(|n| match n {
PullRequestQueryNode::PullRequest(p) => Ok(DetailedPullRequest { | PullRequestQueryNode::PullRequest(p) => Ok(DetailedPullRequest {
title: p.title, title: p.title,
state: p.state, state: p.state,
is_draft: p.is_draft, is_draft: p.is_draft,
body: p.body, body: p.body,
author: p.author.map(|it| api::user::Actor {
login: it.login,
avatar_url: it.avatar_url,
}), }),
_ => Err(api::Error::MalformedResponse( base_branch_name: p.base_ref.map(|r| r.name),
"unexpected node type on PullRequestQuery".into(), head_branch_name: p.head_ref.map(|r| r.name),
)), }),
| _ => Err(api::Error::MalformedResponse(
"unexpected node type on PullRequestQuery".into(),
)),
}) })
} }
} }
@@ -416,11 +425,11 @@ impl query::QueryFn for FetchPullRequestTimeline {
TimelineActor { TimelineActor {
kind: match on { kind: match on {
actorFieldsOn::Bot => "Bot", | actorFieldsOn::Bot => "Bot",
actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount", | actorFieldsOn::EnterpriseUserAccount => "EnterpriseUserAccount",
actorFieldsOn::Mannequin => "Mannequin", | actorFieldsOn::Mannequin => "Mannequin",
actorFieldsOn::Organization => "Organization", | actorFieldsOn::Organization => "Organization",
actorFieldsOn::User => "User", | actorFieldsOn::User => "User",
} }
.into(), .into(),
name: login, name: login,
@@ -430,62 +439,62 @@ impl query::QueryFn for FetchPullRequestTimeline {
fn normalize_assignee(actor: assigneeFields) -> TimelineActor { fn normalize_assignee(actor: assigneeFields) -> TimelineActor {
match actor { match actor {
assigneeFields::Bot(actor) => TimelineActor { | assigneeFields::Bot(actor) => TimelineActor {
kind: "Bot".into(), kind: "Bot".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
assigneeFields::Mannequin(actor) => TimelineActor { | assigneeFields::Mannequin(actor) => TimelineActor {
kind: "Mannequin".into(), kind: "Mannequin".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
assigneeFields::Organization(actor) => TimelineActor { | assigneeFields::Organization(actor) => TimelineActor {
kind: "Organization".into(), kind: "Organization".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
assigneeFields::User(actor) => TimelineActor { | assigneeFields::User(actor) => TimelineActor {
kind: "User".into(), kind: "User".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
} }
} }
fn normalize_requested_reviewer(actor: requestedReviewerFields) -> TimelineActor { fn normalize_requested_reviewer(actor: requestedReviewerFields) -> TimelineActor {
match actor { match actor {
requestedReviewerFields::Bot(actor) => TimelineActor { | requestedReviewerFields::Bot(actor) => TimelineActor {
kind: "Bot".into(), kind: "Bot".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
requestedReviewerFields::Mannequin(actor) => TimelineActor { | requestedReviewerFields::Mannequin(actor) => TimelineActor {
kind: "Mannequin".into(), kind: "Mannequin".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
requestedReviewerFields::Team(actor) => TimelineActor { | requestedReviewerFields::Team(actor) => TimelineActor {
kind: "Team".into(), kind: "Team".into(),
name: actor.name, name: actor.name,
avatar_url: None, avatar_url: None,
}, },
requestedReviewerFields::User(actor) => TimelineActor { | requestedReviewerFields::User(actor) => TimelineActor {
kind: "User".into(), kind: "User".into(),
name: actor.login, name: actor.login,
avatar_url: Some(actor.avatar_url), avatar_url: Some(actor.avatar_url),
}, },
} }
} }
fn normalize_review_state(state: PullRequestReviewState) -> String { fn normalize_review_state(state: PullRequestReviewState) -> String {
match state { match state {
PullRequestReviewState::PENDING => "PENDING", | PullRequestReviewState::PENDING => "PENDING",
PullRequestReviewState::COMMENTED => "COMMENTED", | PullRequestReviewState::COMMENTED => "COMMENTED",
PullRequestReviewState::APPROVED => "APPROVED", | PullRequestReviewState::APPROVED => "APPROVED",
PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED", | PullRequestReviewState::CHANGES_REQUESTED => "CHANGES_REQUESTED",
PullRequestReviewState::DISMISSED => "DISMISSED", | PullRequestReviewState::DISMISSED => "DISMISSED",
_ => "OTHER", | _ => "OTHER",
} }
.into() .into()
} }
@@ -705,10 +714,10 @@ impl query::QueryFn for FetchPullRequestTimeline {
"missing 'node' field on PullRequestTimelineQuery response".into(), "missing 'node' field on PullRequestTimelineQuery response".into(),
)) ))
.and_then(|node| match node { .and_then(|node| match node {
PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request), | PullRequestTimelineQueryNode::PullRequest(pull_request) => Ok(pull_request),
_ => Err(api::Error::MalformedResponse( | _ => Err(api::Error::MalformedResponse(
"unexpected node type on PullRequestTimelineQuery".into(), "unexpected node type on PullRequestTimelineQuery".into(),
)), )),
})?; })?;
let timeline = pull_request.timeline_items; let timeline = pull_request.timeline_items;

View File

@@ -133,15 +133,80 @@ mod tests {
.expect("closed pull request fixture should parse"); .expect("closed pull request fixture should parse");
let dashboard_markdown = fetch_pull_request(&issues::Id::from("PR_kwDONovem84")) let dashboard_markdown = fetch_pull_request(&issues::Id::from("PR_kwDONovem84"))
.expect("dashboard pull request fixture should parse"); .expect("dashboard pull request fixture should parse");
let cached_repo_picker = fetch_pull_request(&issues::Id::from("PR_kwDONovem85"))
.expect("repo picker pull request fixture should parse");
let worker_split = fetch_pull_request(&issues::Id::from("PR_kwDOAgent47"))
.expect("worker split pull request fixture should parse");
let spacing_tokens = fetch_pull_request(&issues::Id::from("PR_kwDODesign31"))
.expect("spacing token pull request fixture should parse");
assert_eq!(merged.state, issues::PullRequestState::Merged); assert_eq!(merged.state, issues::PullRequestState::Merged);
assert!(merged.body.contains("| Stage | Owner | Status |")); assert!(merged.body.contains("| Stage | Owner | Status |"));
assert_eq!(
merged.author.as_ref().map(|author| author.login.as_str()),
Some("rorycraft")
);
assert_eq!(merged.base_branch_name.as_deref(), Some("main"));
assert_eq!(
merged.head_branch_name.as_deref(),
Some("feat/release-handoff-checklist")
);
assert!( assert!(
documented_failover documented_failover
.body .body
.contains("./scripts/failover promote-standby") .contains("./scripts/failover promote-standby")
); );
assert_eq!(
documented_failover
.author
.as_ref()
.map(|author| author.login.as_str()),
Some("kennethnym")
);
assert_eq!(documented_failover.base_branch_name.as_deref(), Some("main"));
assert_eq!(
documented_failover.head_branch_name.as_deref(),
Some("docs/manual-failover-steps")
);
assert!(dashboard_markdown.body.contains("```rust")); assert!(dashboard_markdown.body.contains("```rust"));
assert_eq!(dashboard_markdown.base_branch_name.as_deref(), Some("main"));
assert_eq!(
dashboard_markdown.head_branch_name.as_deref(),
Some("feat/cached-issue-pane")
);
assert_eq!(
cached_repo_picker
.author
.as_ref()
.map(|author| author.login.as_str()),
Some("kennethnym")
);
assert_eq!(cached_repo_picker.base_branch_name.as_deref(), Some("main"));
assert_eq!(
cached_repo_picker.head_branch_name.as_deref(),
Some("feat/cached-repo-picker")
);
assert_eq!(
worker_split.author.as_ref().map(|author| author.login.as_str()),
Some("leaferiksen")
);
assert_eq!(worker_split.base_branch_name.as_deref(), Some("main"));
assert_eq!(
worker_split.head_branch_name.as_deref(),
Some("feat/worker-context-envelope")
);
assert_eq!(
spacing_tokens
.author
.as_ref()
.map(|author| author.login.as_str()),
Some("mariahops")
);
assert_eq!(spacing_tokens.base_branch_name.as_deref(), Some("main"));
assert_eq!(
spacing_tokens.head_branch_name.as_deref(),
Some("chore/dashboard-spacing-scale")
);
} }
#[test] #[test]

View File

@@ -20,6 +20,12 @@ pub struct User {
pub email: Option<String>, pub email: Option<String>,
} }
#[derive(Debug, Deserialize)]
pub(crate) struct Actor {
pub(crate) login: String,
pub(crate) avatar_url: String,
}
impl Deref for Id { impl Deref for Id {
type Target = u64; type Target = u64;

View File

@@ -1,6 +1,6 @@
use gpui::{ use gpui::{
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
div, prelude::FluentBuilder, div, img, prelude::FluentBuilder,
}; };
use crate::{ use crate::{
@@ -118,20 +118,80 @@ impl PullRequestView {
} }
} }
let author_pill = div() let merge_text = match (
.px_2() pr.author.as_ref(),
.border_1() pr.base_branch_name.as_ref(),
.border_color(theme.colors.border) pr.head_branch_name.as_ref(),
.rounded_full() ) {
.bg(theme.colors.surface_elevated) | (Some(author), Some(base_branch), Some(head_branch)) => {
.child(text("kennethnym").text_xs()); let str = format!(
"{} requested to merge {} into {}",
author.login, head_branch, base_branch
);
let row = div() let head_branch_text_offset = author.login.len() + 20;
.flex() let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
.flex_row()
.gap_2() let highlights = [
.child(status_pill) (
.child(author_pill); 0..author.login.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
..Default::default()
},
),
(
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()),
..Default::default()
},
),
(
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
gpui::HighlightStyle {
font_weight: Some(gpui::FontWeight::BOLD),
color: Some(theme.colors.accent.into()),
..Default::default()
},
),
];
Some((
author,
gpui::StyledText::new(str).with_highlights(highlights),
))
}
| _ => None,
};
let metadata_line =
div()
.flex()
.flex_row()
.gap_2()
.when_some(merge_text, |it, (author, t)| {
it.child(
div()
.flex()
.flex_row()
.items_center()
.gap_1p5()
.child(img(author.avatar_url.clone()).size_4().rounded_full())
.child(
div()
.min_w_0()
.w_full()
.text_color(theme.colors.text)
.text_xs()
.font_weight(gpui::FontWeight::LIGHT)
.opacity(0.8)
.child(t),
),
)
});
div() div()
.size_full() .size_full()
@@ -145,8 +205,8 @@ impl PullRequestView {
.py_3() .py_3()
.border_b_1() .border_b_1()
.border_color(theme.colors.border) .border_color(theme.colors.border)
.child(text(pr.title.clone()).w_full().text_xl().mb_2()) .child(text(pr.title.clone()).w_full().text_xl().mb_1())
.child(row), .child(metadata_line),
) )
.child( .child(
div().flex_1().min_h_0().w_full().child( div().flex_1().min_h_0().w_full().child(

View File

@@ -43,9 +43,9 @@ impl Screen {
_ = cx _ = cx
.subscribe(&self.issue_list, |this, _, event, cx| match event { .subscribe(&self.issue_list, |this, _, event, cx| match event {
issue_list::Event::ItemSelected(pr_id) => { | issue_list::Event::ItemSelected(pr_id) => {
this.handle_issue_list_item_selected(pr_id, cx); this.handle_issue_list_item_selected(pr_id, cx);
} }
}) })
.detach(); .detach();
} }
@@ -56,17 +56,18 @@ impl Screen {
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) { ) {
match value { match value {
SidebarItemValue::PullRequest { filter } => { | SidebarItemValue::PullRequest { filter } => {
self.issue_filter = Some(*filter); self.issue_filter = Some(*filter);
cx.notify(); cx.notify();
} }
} }
} }
fn handle_issue_list_item_selected( fn handle_issue_list_item_selected(
&mut self, &mut self,
id: &api::issues::Id, id: &api::issues::Id,
cx: &mut gpui::Context<Self>, ) { cx: &mut gpui::Context<Self>,
) {
println!("handle issue list item selected: {:?}", id); println!("handle issue list item selected: {:?}", id);
self.pull_request_view.update(cx, |view, cx| { self.pull_request_view.update(cx, |view, cx| {
view.change_displayed_pull_request(id.clone(), cx); view.change_displayed_pull_request(id.clone(), cx);
@@ -111,7 +112,6 @@ impl gpui::Render for Screen {
.bg(theme.colors.surface) .bg(theme.colors.surface)
.border_x_1() .border_x_1()
.border_color(theme.colors.border) .border_color(theme.colors.border)
.mr_2()
.overflow_hidden() .overflow_hidden()
.child(self.issue_list.clone()), .child(self.issue_list.clone()),
) )
@@ -123,8 +123,6 @@ impl gpui::Render for Screen {
.h_full() .h_full()
.overflow_hidden() .overflow_hidden()
.bg(theme.colors.surface) .bg(theme.colors.surface)
.border_l_1()
.border_color(theme.colors.border)
.child(self.pull_request_view.clone()), .child(self.pull_request_view.clone()),
), ),
) )