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
68 changes: 52 additions & 16 deletions .claude/skills/verify-t3term/features/pull-request-code.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ The TUI follows the desktop app closely, because muscle memory is worth more tha
| Sidebar | ↑/↓ or j/k select (↓ past the last thread scrolls on to the headings under it), Enter open, w, d and e open or close the Working, Done and Settled shelves, [ and ] narrow or widen the sidebar, 0 back to its usual width, q quit |
| Composer | Enter send (queues if the thread is busy), Alt+Enter or Ctrl+J newline, Ctrl+R swap in an unsent message, Esc to transcript |
| Transcript | ↑/↓ scroll, g/G top/bottom, t open or close every row of tool calls, p expand or collapse the first long plan in view, w open or close the worktree setup's details while its card is in view, Enter compose, Esc, ← or h sidebar, showing it if hidden |
| Thread details | → or l the Pull requests tab, ← or h back a step, Esc close the dialog or leave the pane. On the Thread tab ↑/↓ or j/k choose a Lineage row, or scroll when there is none to choose, Enter open the chosen row, PgUp/PgDn page, Home/g top, End/G bottom. On the Pull requests tab the same keys select a link, and Enter shows its details in the list's place, where they scroll and Enter or ← shows the list again. Once the host has answered, → or l shows the details' Timeline, then their Code where the host gives a diff, and ← or h goes back a tab. In the details ↑/↓ also choose a button or group of the Comments section or the Timeline, a commit on the Timeline, or the commit list's row or entries, a file, a run of unmodified lines, a review thread, a thread's Load more comments, the list of conversations off the diff or Retry on the Code, once it is in view, and Enter does what the chosen one does. r reads the pull request from its host again |
| Thread details | → or l the Pull requests tab, ← or h back a step, Esc close the dialog or leave the pane. On the Thread tab ↑/↓ or j/k choose a Lineage row, or scroll when there is none to choose, Enter open the chosen row, PgUp/PgDn page, Home/g top, End/G bottom. On the Pull requests tab the same keys select a link, and Enter shows its details in the list's place, where they scroll and Enter or ← shows the list again. Once the host has answered, → or l shows the details' Timeline, then their Code where the host gives a diff, and ← or h goes back a tab. In the details ↑/↓ also choose a button or group of the Comments section or the Timeline, a commit on the Timeline, or the commit list's row or entries, Expand all files or Collapse all files, a file, a run of unmodified lines, a review thread, a thread's Load more comments, the list of conversations off the diff or Retry on the Code, once it is in view, and Enter does what the chosen one does. r reads the pull request from its host again |

The wheel scrolls the transcript and the sidebar. Clicking ◧ at the top left of the conversation hides or shows the sidebar, clicking ◨ at the top right opens or closes the thread's details, and dragging the sidebar's right edge resizes it. Clicking a thread opens it, clicking the Working, Done or Settled heading opens or closes that shelf, clicking a chip under the composer opens its menu, clicking the top row of the tasks drawer opens or closes its list, clicking × on a provider or error banner dismisses it, clicking elsewhere on a long notice or error opens or closes it, and clicking a row of tool calls opens that row. Clicking the top edge of a long plan, or its Expand plan or Collapse plan button, expands or collapses that plan. Clicking a worktree setup card's Details row opens or closes its details. Clicking a tab of the thread's details shows that tab. On the Thread tab, clicking a Lineage row opens that thread, shows more rows or opens or closes Previous agents. On the Pull requests tab, clicking a linked pull request selects it, or shows its details when it is selected already, and clicking `← List` above those details shows the list again. Clicking Summary, Timeline or Code beside it shows that tab, and clicking a commit on the Timeline shows its diff on the Code. On the Code, clicking the row above the files opens or closes the list of commits, clicking a commit or All commits in it shows that diff, clicking Show more lists more commits, clicking a file unfolds or folds it, clicking a run of unmodified lines shows up to 100 more of them at each end that touches a change, reading the file's text the first time, clicking a review thread's first row or the list of conversations off the diff opens or closes it, clicking a thread's Load more comments reads its next page of comments, and clicking Retry reads the rest of the diff again.
The wheel scrolls the transcript and the sidebar. Clicking ◧ at the top left of the conversation hides or shows the sidebar, clicking ◨ at the top right opens or closes the thread's details, and dragging the sidebar's right edge resizes it. Clicking a thread opens it, clicking the Working, Done or Settled heading opens or closes that shelf, clicking a chip under the composer opens its menu, clicking the top row of the tasks drawer opens or closes its list, clicking × on a provider or error banner dismisses it, clicking elsewhere on a long notice or error opens or closes it, and clicking a row of tool calls opens that row. Clicking the top edge of a long plan, or its Expand plan or Collapse plan button, expands or collapses that plan. Clicking a worktree setup card's Details row opens or closes its details. Clicking a tab of the thread's details shows that tab. On the Thread tab, clicking a Lineage row opens that thread, shows more rows or opens or closes Previous agents. On the Pull requests tab, clicking a linked pull request selects it, or shows its details when it is selected already, and clicking `← List` above those details shows the list again. Clicking Summary, Timeline or Code beside it shows that tab, and clicking a commit on the Timeline shows its diff on the Code. On the Code, clicking the row above the files opens or closes the list of commits, clicking a commit or All commits in it shows that diff, clicking Show more lists more commits, clicking Expand all files or Collapse all files unfolds or folds every file, clicking a file unfolds or folds it, clicking a run of unmodified lines shows up to 100 more of them at each end that touches a change, reading the file's text the first time, clicking a review thread's first row or the list of conversations off the diff opens or closes it, clicking a thread's Load more comments reads its next page of comments, and clicking Retry reads the rest of the diff again.

On macOS the Alt keys need Option to send Meta: "Use Option as Meta key" in Terminal, "Esc+" for the Option key in iTerm2, or `macos-option-as-alt = true` in Ghostty.

Expand Down
3 changes: 2 additions & 1 deletion docs/pull-request-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Where the detail's `capabilities.diff` is true, the details have a third tab, Co
| Commits | Where the change has commits, the toolbar's menu says `All commits` or the picked commit's headline. It lists `All commits`, then the commits newest first by when each was committed, 10 at a time with `Show more (13 left)`, each with its id's first seven characters (PullRequestCodeTab.tsx:683-692, 1063-1119). Picking one reads its own diff from its first slice, folds the files and closes the list off the diff (268-277), and places no thread on its lines, since a commit counts its lines apart from the change (454-458). A commit the change no longer lists goes back to All commits (1050-1062). The query of a diff picked before stays alive for 5 minutes once nothing shows it, so a read of it under way runs on and its answer is kept (packages/client-runtime/src/state/runtime.ts:534 and 590) | `› All commits` above the count, or `› Read the schema first · abc1234`. Enter or a click opens the list under it: `✓ All commits`, then each commit's headline cut to its row with the seven characters at the end, ✓ before the diff shown, and `Show more (13 left)`. ↑ and ↓ choose an entry, and Enter or a click shows its diff, reading it from its first slice, and closes the list with its row chosen. Commits at the same instant keep T3's order, and one whose time t3term can't read goes last. An empty headline reads `Untitled commit`, as on the Timeline. While a commit's diff shows, every thread is in the list off the diff. A read of the conversation that no longer lists the commit reads the whole change again. Picking another diff stops the read of the one before, whose answer t3term would drop. |
| Count | `12 files`, with `+` while more slices come (PullRequestCodeTab.tsx:1124-1125), and a note when the host withheld part of the diff (1183-1195) | `12 files+`, then `The host withheld part of this diff, such as a binary file or a change too large to show.` in yellow where a slice says the host left something out or has text the parser found no file in. |
| Files | Folded at first, as the `diffFilesCollapsed` setting is true until changed (pullRequestDiff.logic.ts:36-43, PullRequestCodeTab.tsx:230-232). The header has a chevron, an icon for how the file changed, the path, both paths of a rename, and `+3 -1` unless both are 0, from the host's own counts where the hunks it sent count none (822-823) | `› M src/lib.rs +3 -1`, with A, D, R or M in green, red, blue or yellow for an added, deleted, renamed or changed file. The path wraps under itself. Enter or a click unfolds it, `⌄`, and folds it again. A path one slice holds twice, as git writes a file whose type changed, shows once as its last part, as the desktop's `orderDiffFiles` keeps one file for each path (pullRequestFileOrder.logic.ts:152-153). |
| Fold all | The toolbar's `Expand all files` button while every file is folded, and `Collapse all files` once any shows unfolded (PullRequestCodeTab.tsx:1239-1258, apps/web/src/lib/diffCollapse.ts). It sets one fold for every file and forgets each file's own, so a file from a later slice follows it (pullRequestDiff.logic.ts:25-50, PullRequestCodeTab.tsx:675-681). Another diff picked starts folded again (268-277) | `⇊ Expand all files` under the count and its notes, or `⇈ Collapse all files`, once the tab has files. Enter or a click unfolds them all, and folds them all once any shows unfolded. A file read later follows it, a file folded or unfolded after keeps its own fold through a read again, and another diff picked shows its files folded. Every file unfolded is laid out at once: 3,000 files of 200,000 lines took 0.2 seconds on an Apple M5 in a release build, and 20 milliseconds each time the details are laid out again after. |
| Viewed | Where the detail's `capabilities.viewedFiles` names who keeps the marks, the tab reads them through `pullRequests.filesViewed`, under a commit's diff too, since a mark belongs to the change (PullRequestCodeTab.tsx:414-423). The query is fresh for 15 seconds (packages/client-runtime/src/state/pullRequests.ts:301-306), and a refresh reads it again with the diff (431-441). Beside the count, `2 / 12` and `viewed`, or `viewed in T3 Code` with an info icon where the environment keeps the marks, and a warning icon when the read failed or the host sent only part of them, each explaining itself in a tooltip (1127-1180). Each header has a checkbox after the stat, `Viewed`, or `Changed` in yellow for a file pushed to since, with the tooltip `This file has been pushed to since you marked it viewed.` (826-866) | `12 files · 2 / 12 viewed`, or `viewed in T3 Code`, once the marks came. `› M src/lib.rs +3 -1 · ✓ Viewed` in grey, or `· Changed since viewed` in yellow, since a terminal has no tooltip to explain `Changed`. A file not viewed shows nothing, as t3term can't mark one. The marks are read the first time the Code shows, when it shows again 15 seconds after they came or after they failed, and with each read of the diff again, after the `r` read is done as for the diff. A commit's diff keeps them. A failed read shows `t3term couldn't read which files you viewed.` and the reason, or `The viewed marks may be out of date: t3term couldn't read them again.` over the marks read before. A short read says `This change has more files than the host reports viewed marks for in one read, so the count is short.`, as [pull request reads](pull-request-reads.md#viewed-files) says. |
| Order | `orderDiffFiles` (pullRequestFileOrder.logic.ts:145-197) orders each slice on its own, so a later slice never moves the files above it (PullRequestCodeTab.tsx:404-411). Source comes first, each file after the changed files it imports. A quoted specifier after `from`, `import` or `require(` in any line of a file's hunks names a changed file by its path, with `./` and `../` resolved and a script's extension dropped, or by a name only one source file has. Where a cycle leaves no file ready, the lowest path goes next. Then the tests (`.test.`, `.spec.`, or a `test`, `tests` or `__tests__` directory), each after the source file whose name it has, then lockfiles, snapshots, minified files and files under `dist`, `build`, `vendor`, `__generated__` or `__snapshots__`, by path. Paths compare with `localeCompare` | The same order, worked out once when a slice comes. The diff library the desktop uses puts each unchanged line of a hunk on both sides, so an import in an unchanged line counts too (@pierre/diffs 1.5.2, dist/utils/parsePatchFiles.js:208-222), and t3term counts it. Paths compare as the root collation does for ASCII: punctuation, then digits, then letters with case ignored, then lowercase before uppercase. |
| Lines | The stacked view: each line with its number, and `N unmodified lines` between hunks (apps/web/src/lib/diffRendering.ts:82) | Each line's number, the new one or the old for a removed line, then `+`, `-` or a space, in green, red or the text's color. A line that wraps leaves the number and the mark blank. Tabs stop every 4 columns, control characters go, and a line past 2,000 characters is cut with `…`. `⋯ 9 unmodified lines` comes before each hunk that doesn't start at the file's first line. A binary file, a file whose changes the host withheld and a rename that changed no line each say so. So does a file past t3term's 200,000 lines of a slice, or with lines t3term couldn't read, such as a hunk shorter or longer than its header counts. Only the files unfolded are laid out, each once for each width, so folding one or the minute changing doesn't lay out the others again. |
Expand Down Expand Up @@ -202,7 +203,7 @@ Linking, unlinking, watching, reviewing, commenting, merging and every other wri
- t3term reads only the text a file has after the change, and the read fails where that text is past 2 MiB. GitLab and Bitbucket can't read a file's text at f570bd2, so a run there says T3's reason.
- The Code tab compares paths as the desktop's `localeCompare` does for ASCII. A character outside ASCII comes after the letters, where the desktop's collation puts an accented letter with its base letter, so `é.ts` comes after `z.ts` rather than before it.
- t3term shows which files the reader viewed but can't mark or clear one. `pullRequests.setFilesViewed` needs `source-control:write`, which a saved login lacks (src/auth.rs), so a reader marks files in the desktop or on the host. A mark made there shows once t3term reads the marks again, when the Code shows again 15 seconds after the last read or after `r`.
- The Code tab has no file tree, raw patch, or the desktop's whitespace, split view, wrap and fold-all toggles. Its lines don't wrap at word breaks or show syntax colors.
- The Code tab has no file tree, raw patch, or the desktop's whitespace, split view and wrap toggles. Its lines don't wrap at word breaks or show syntax colors.
- A thread's pages of comments stay with the details' read, so they show again after the conversation is read again and when the same link's details open again. The desktop's card keeps them only while it is mounted. Once a thread's pages run out, a read again that counts more comments than it holds offers `Load more comments` again, from the page after the comments the read sent, and the comments it holds show once. The desktop's card reopens its pages only after the reader's own reply. A failed page says so in the thread, where the desktop shows a toast.
- A thread holds at most 1,000 comments, and offers no page past them. Its id and cursors are cut at 1,024 bytes, as t3term cuts every id, so a longer cursor would read the wrong page or fail. GitHub's are far shorter.
- Nothing resolves, replies to, edits or reacts to a thread, and a thread has no `Fix in thread`.
Expand Down
14 changes: 11 additions & 3 deletions src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14441,14 +14441,20 @@ mod tests {
app.on_action_result(ActionResult::PullRequestDiff { ticket: 4, answer });
let rows = body_after(&mut app, 200, 50);
let text: Vec<&str> = rows.iter().map(|row| row.trim()).collect();
assert_eq!(text[4..8], ["1 file", "", "› M src/lib.rs +1 -1", ""]);
assert_eq!(
text[4..8],
["1 file", "⇊ Expand all files", "", "› M src/lib.rs +1 -1"]
);
press(&mut app, KeyCode::Down);
let shown = status(&mut app);
assert!(shown.contains("Enter expand all files · "), "{shown}");
press(&mut app, KeyCode::Down);
let shown = status(&mut app);
assert!(shown.contains("Enter expand file · "), "{shown}");
press(&mut app, KeyCode::Enter);
let rows = body_after(&mut app, 200, 50);
let text: Vec<&str> = rows.iter().map(|row| row.trim()).collect();
assert_eq!(text[6..9], ["⌄ M src/lib.rs +1 -1", "1 - old", "1 + new"]);
assert_eq!(text[7..10], ["⌄ M src/lib.rs +1 -1", "1 - old", "1 + new"]);
press(&mut app, KeyCode::Right);
assert_eq!(app.details.pulls.tabs(), Some(Tab::Code));
assert_eq!(*diffs.borrow(), [named, again]);
Expand Down Expand Up @@ -14976,7 +14982,9 @@ mod tests {
.iter()
.position(|row| *row == "2 files")
.unwrap_or_else(|| panic!("{rows:#?}"));
let notes = rows[at + 1..].iter().take_while(|row| !row.is_empty());
let notes = rows[at + 1..]
.iter()
.take_while(|row| !row.starts_with('⇊'));
let notes: Vec<&str> = notes.copied().collect();
let says = format!("t3term couldn't read which files you viewed. {reason}");
assert_eq!(notes.join(" "), says);
Expand Down
Loading
Loading