Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/gh_llm/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2942,7 +2942,7 @@ def _render_review_thread_block(
header = f"- Thread[{thread_index}] {thread_id}"
if force_full_context and timeline_window is not None and timeline_window.active:
noun = "update" if matching_comment_count == 1 else "updates"
header += f" ({matching_comment_count} {noun} in selected window; full context shown)"
header += f" ({matching_comment_count} {noun} in selected window; thread kept for context)"
lines = [header]
visible_comments = 0
minimized_hidden_count = 0
Expand All @@ -2953,7 +2953,7 @@ def _render_review_thread_block(
comment = _as_dict(raw_comment, context="review comment")
comment_id = _as_optional_str(comment.get("id")) or "(unknown comment id)"
is_minimized = bool(comment.get("isMinimized"))
if is_minimized and not show_minimized_details and not force_full_context:
if is_minimized and not show_minimized_details:
minimized_hidden_count += 1
reasons: list[str] = []
reason = _format_minimized_reason(comment.get("minimizedReason"))
Expand Down
4 changes: 4 additions & 0 deletions src/gh_llm/pager.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,10 @@ def _matching_items(page: TimelinePage, timeline_window: TimelineWindow) -> list
def _event_matches_window(event: TimelineEvent, timeline_window: TimelineWindow) -> bool:
if _matches_window(event.timestamp, timeline_window):
return True
if timeline_window.after is None:
return False
if event.timestamp > timeline_window.after:
return False
return any(_matches_window(timestamp, timeline_window) for timestamp in event.related_timestamps)


Expand Down
273 changes: 272 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,278 @@ def spanning_thread_payload(after: str | None) -> dict[str, Any]:
assert "reply inside window" in out
assert "[before selected window]" in out
assert "[within selected window]" in out
assert "Thread[1] PRRT_spanning (1 update in selected window; full context shown)" in out
assert "Thread[1] PRRT_spanning (1 update in selected window; thread kept for context)" in out


def test_pr_view_before_does_not_include_later_review_via_older_thread_history(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
def before_window_events() -> list[dict[str, Any]]:
return [
{
"__typename": "IssueComment",
"id": "c_before",
"url": "https://example.com/c-before",
"createdAt": "2026-02-14T14:20:00Z",
"body": "older standalone comment",
"author": {"login": "alice"},
"reactionGroups": [],
},
{
"__typename": "PullRequestReview",
"id": "PRR_before_old",
"submittedAt": "2026-02-14T14:40:00Z",
"state": "COMMENTED",
"body": "older review body",
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "reviewer"},
},
{
"__typename": "PullRequestReview",
"id": "PRR_before_late",
"submittedAt": "2026-02-14T15:10:00Z",
"state": "COMMENTED",
"body": "late review body",
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "late-reviewer"},
},
{
"__typename": "IssueComment",
"id": "c_after",
"url": "https://example.com/c-after",
"createdAt": "2026-02-14T15:20:00Z",
"body": "late standalone comment",
"author": {"login": "bob"},
"reactionGroups": [],
},
]

def before_window_thread_payload(after: str | None) -> dict[str, Any]:
if after is not None:
return {
"data": {
"repository": {
"pullRequest": {
"reviewThreads": {
"pageInfo": {"hasNextPage": False, "endCursor": None},
"nodes": [],
}
}
}
}
}
return {
"data": {
"repository": {
"pullRequest": {
"reviewThreads": {
"pageInfo": {"hasNextPage": False, "endCursor": None},
"nodes": [
{
"id": "PRRT_before_window",
"isResolved": False,
"comments": {
"nodes": [
{
"id": "rc_before_old",
"path": "python/test_file.py",
"body": "older thread root",
"line": 21,
"originalLine": 21,
"startLine": None,
"originalStartLine": None,
"diffHunk": "@@ -20,1 +20,1 @@\n-old_name\n+new_name",
"createdAt": "2026-02-14T14:40:01Z",
"outdated": False,
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "reviewer"},
"reactionGroups": [],
"pullRequestReview": {"id": "PRR_before_old"},
},
{
"id": "rc_before_late",
"path": "python/test_file.py",
"body": "late thread reply",
"line": 21,
"originalLine": 21,
"startLine": None,
"originalStartLine": None,
"diffHunk": "@@ -20,1 +20,1 @@\n-old_name\n+new_name",
"createdAt": "2026-02-14T15:10:01Z",
"outdated": False,
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "late-reviewer"},
"reactionGroups": [],
"pullRequestReview": {"id": "PRR_before_late"},
},
]
},
}
],
}
}
}
}
}

responder = GhResponder()
monkeypatch.setattr(github_api.subprocess, "run", responder.run)
monkeypatch.setattr(sys.modules[__name__], "_events", before_window_events)
monkeypatch.setattr(sys.modules[__name__], "_review_threads_payload", before_window_thread_payload)

code = cli.run(
[
"pr",
"view",
"77928",
"--repo",
"PaddlePaddle/Paddle",
"--page-size",
"2",
"--before",
"2026-02-14T15:00:00Z",
]
)
assert code == 0

out = capsys.readouterr().out
assert "timeline_before: 2026-02-14T15:00:00Z" in out
assert "timeline_events: 2" in out
assert "older review body" in out
assert "late review body" not in out
assert "[2026-02-14 15:10 UTC] review/commented by @late-reviewer" not in out
assert "late standalone comment" not in out


def test_pr_view_after_keeps_minimized_comments_collapsed_in_context_threads(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
def minimized_thread_events() -> list[dict[str, Any]]:
return [
{
"__typename": "PullRequestReview",
"id": "PRR_minimized_old",
"submittedAt": "2026-02-14T14:40:00Z",
"state": "COMMENTED",
"body": "older review body",
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "reviewer"},
},
{
"__typename": "IssueComment",
"id": "c_visible",
"url": "https://example.com/c-visible",
"createdAt": "2026-02-14T15:10:00Z",
"body": "fresh standalone comment",
"author": {"login": "ShigureNyako"},
"reactionGroups": [],
},
]

def minimized_thread_payload(after: str | None) -> dict[str, Any]:
if after is not None:
return {
"data": {
"repository": {
"pullRequest": {
"reviewThreads": {
"pageInfo": {"hasNextPage": False, "endCursor": None},
"nodes": [],
}
}
}
}
}
return {
"data": {
"repository": {
"pullRequest": {
"reviewThreads": {
"pageInfo": {"hasNextPage": False, "endCursor": None},
"nodes": [
{
"id": "PRRT_minimized_context",
"isResolved": False,
"comments": {
"nodes": [
{
"id": "rc_hidden_context",
"path": "python/test_file.py",
"body": "hidden minimized root",
"line": 21,
"originalLine": 21,
"startLine": None,
"originalStartLine": None,
"diffHunk": "@@ -20,1 +20,1 @@\n-old_name\n+new_name",
"createdAt": "2026-02-14T14:40:01Z",
"outdated": False,
"isMinimized": True,
"minimizedReason": "OUTDATED",
"author": {"login": "reviewer"},
"reactionGroups": [],
"pullRequestReview": {"id": "PRR_minimized_old"},
},
{
"id": "rc_visible_context",
"path": "python/test_file.py",
"body": "visible in-window reply",
"line": 21,
"originalLine": 21,
"startLine": None,
"originalStartLine": None,
"diffHunk": "@@ -20,1 +20,1 @@\n-old_name\n+new_name",
"createdAt": "2026-02-14T15:00:02Z",
"outdated": False,
"isMinimized": False,
"minimizedReason": None,
"author": {"login": "ShigureNyako"},
"reactionGroups": [],
"pullRequestReview": {"id": "PRR_minimized_followup"},
},
]
},
}
],
}
}
}
}
}

responder = GhResponder()
monkeypatch.setattr(github_api.subprocess, "run", responder.run)
monkeypatch.setattr(sys.modules[__name__], "_events", minimized_thread_events)
monkeypatch.setattr(sys.modules[__name__], "_review_threads_payload", minimized_thread_payload)

code = cli.run(
[
"pr",
"view",
"77928",
"--repo",
"PaddlePaddle/Paddle",
"--page-size",
"1",
"--show",
"timeline",
"--after",
"2026-02-14T14:55:00Z",
]
)
assert code == 0

out = capsys.readouterr().out
assert "Thread[1] PRRT_minimized_context (1 update in selected window; thread kept for context)" in out
assert "(hidden comment: outdated)" in out
assert "hidden minimized root" not in out
assert "visible in-window reply" in out


def test_issue_view_before_filters_older_events(
Expand Down