fix(presentation): handle force-push, branch-create, empty pushes

PushEvent payloads carry `created`, `forced`, `distinct_size`, and
`ref` flags that I wasn't consulting — the result on the timeline
was "pushed 0 commits" for what were actually branch creations
(distinct_size 0 because the commits already existed elsewhere)
and force-pushes that didn't change the resulting tree.

  * created=true        → "created branch X in repo" + GitBranchCreate icon
  * forced + size>0     → "force-pushed N commits to repo:branch"
  * forced + size==0    → "force-pushed repo:branch"
  * normal + size>0     → "pushed N commits to repo:branch" (unchanged)
  * normal + size==0    → "pushed to repo:branch" (no awkward "0 commits")

Also: drop the instagram, facebook, and steel-horse-adventures
links from the UI header — those represent personae the user no
longer wants to surface from rob.tn.

Tests: +3 in presentation/github.rs covering the new push
branches — 21 total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 19:37:40 +03:00
parent bf04f8a1ff
commit 4355353395
2 changed files with 119 additions and 14 deletions

View File

@@ -91,24 +91,58 @@ type Reshaped = (
fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
let repo = repo.unwrap_or("(unknown repo)");
let size = p
.and_then(|v| v.get("distinct_size").or_else(|| v.get("size")))
let distinct_size = p
.and_then(|v| v.get("distinct_size"))
.and_then(Value::as_i64)
.unwrap_or(0);
let forced = p
.and_then(|v| v.get("forced"))
.and_then(Value::as_bool)
.unwrap_or(false);
let created = p
.and_then(|v| v.get("created"))
.and_then(Value::as_bool)
.unwrap_or(false);
let branch = p
.and_then(|v| v.get("ref"))
.and_then(Value::as_str)
.map(ref_branch)
.unwrap_or("");
let title = vec![
TitleSegment::text(format!(
"pushed {size} commit{} to ",
if size == 1 { "" } else { "s" }
)),
repo_link(repo),
TitleSegment::text(format!(":{branch}")),
];
// Branch-creation pushes have distinct_size = 0 (the commits already
// existed on another branch) and a different intent than a code push.
// Force-pushes and ordinary pushes both render with the GitPush icon
// but are phrased differently.
let (icon, title) = if created {
(
TimelineIcon::GitBranchCreate,
vec![
TitleSegment::text(format!("created branch {branch} in ")),
repo_link(repo),
],
)
} else if distinct_size == 0 {
let verb = if forced { "force-pushed" } else { "pushed to" };
(
TimelineIcon::GitPush,
vec![
TitleSegment::text(format!("{verb} ")),
repo_link(repo),
TitleSegment::text(format!(":{branch}")),
],
)
} else {
let verb = if forced { "force-pushed" } else { "pushed" };
let plural = if distinct_size == 1 { "" } else { "s" };
(
TimelineIcon::GitPush,
vec![
TitleSegment::text(format!("{verb} {distinct_size} commit{plural} to ")),
repo_link(repo),
TitleSegment::text(format!(":{branch}")),
],
)
};
let commits: Vec<CommitSummary> = p
.and_then(|v| v.get("commits"))
@@ -148,7 +182,7 @@ fn push(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
Some(TimelineBody::Commits { commits })
};
(TimelineIcon::GitPush, title, None, body)
(icon, title, None, body)
}
fn pull_request(repo: Option<&str>, p: Option<&Value>) -> Reshaped {
@@ -555,6 +589,80 @@ mod tests {
}
}
fn render(item: &TimelineItem) -> String {
item.title
.iter()
.map(|s| match s {
TitleSegment::Text { text } => text.clone(),
TitleSegment::Link { text, .. } => text.clone(),
})
.collect()
}
#[test]
fn push_branch_creation_uses_create_icon() {
let raw = json!({
"actor": { "login": "grenade" },
"repo": { "name": "grenade/vortex" },
"payload": {
"ref": "refs/heads/fix-double-panic",
"size": 0,
"distinct_size": 0,
"created": true,
"forced": false,
"commits": []
}
});
let item = reshape(&ev("PushEvent", raw));
assert_eq!(item.icon, TimelineIcon::GitBranchCreate);
let r = render(&item);
assert!(
r.contains("created branch fix-double-panic in grenade/vortex"),
"got: {r}"
);
}
#[test]
fn force_push_with_commits_says_force_pushed() {
let raw = json!({
"actor": { "login": "grenade" },
"repo": { "name": "grenade/x" },
"payload": {
"ref": "refs/heads/main",
"size": 1,
"distinct_size": 1,
"created": false,
"forced": true,
"commits": [{ "sha": "deadbeefcafe1234", "message": "rebase" }]
}
});
let item = reshape(&ev("PushEvent", raw));
assert_eq!(item.icon, TimelineIcon::GitPush);
let r = render(&item);
assert!(r.contains("force-pushed 1 commit to grenade/x:main"), "got: {r}");
}
#[test]
fn empty_push_omits_commit_count() {
let raw = json!({
"actor": { "login": "grenade" },
"repo": { "name": "grenade/x" },
"payload": {
"ref": "refs/heads/main",
"size": 0,
"distinct_size": 0,
"created": false,
"forced": false,
"commits": []
}
});
let item = reshape(&ev("PushEvent", raw));
assert_eq!(item.icon, TimelineIcon::GitPush);
let r = render(&item);
assert!(r.contains("pushed to grenade/x:main"), "got: {r}");
assert!(!r.contains("0 commit"), "should not say '0 commits': {r}");
}
#[test]
fn merged_pr_uses_merge_icon() {
let raw = json!({

View File

@@ -18,13 +18,10 @@ const RANGE_MIN = new Date('2010-01-01T00:00:00Z').getTime();
const RANGE_MAX = Date.now();
const externalLinks: { url: string; alt: string }[] = [
{ url: 'https://instagram.com/rob_thij', alt: 'instagram' },
{ url: 'https://www.facebook.com/rob.thijssen', alt: 'facebook' },
{ url: 'https://linkedin.com/in/thijssen/', alt: 'linkedin' },
{ url: 'https://stackoverflow.com/users/68115/grenade', alt: 'stackoverflow' },
{ url: 'https://github.com/grenade', alt: 'github' },
{ url: 'https://git.lair.cafe/grenade', alt: 'gitea' },
{ url: 'https://steelhorseadventures.com', alt: 'steel horse adventures' },
];
export default function App() {