feat: subtext under pr title
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ query PullRequestQuery($id: ID!) {
|
|||||||
body
|
body
|
||||||
state
|
state
|
||||||
isDraft
|
isDraft
|
||||||
|
baseRef {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
headRef {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
author {
|
||||||
|
__typename
|
||||||
|
login
|
||||||
|
avatarUrl(size: 32)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user