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
32 changes: 22 additions & 10 deletions .claude/skills/verify-t3term/features/pull-request-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- **Reading the diff.** The first time the Code shows after the details open, t3term POSTs `/api/pull-requests/diff` with the link's project, host, repository and number and no cursor. `Loading pull request diff…` shows until the first slice comes. Once a frame shows the end of the files within two pages of the view, t3term reads the next slice with the cursor the last one gave. One slice is read at a time. Nothing reads the diff before the Code shows. Going to another tab and back within the minute reads nothing, and coming back a minute or more after the first slice came reads it again.
- **The count and the note.** The count reads `5 files+` while more slices are left, with `Loading more files…` under the files while the next one is read, then `8 files`. `The host withheld part of this diff, such as a binary file or a change too large to show.` shows in yellow under it when a slice says the host left something out.
- **Files.** Each file starts folded: `› M src/config/reader.rs +7 -5`, with `A` green, `D` red, `R` blue and `M` yellow, and the counts in green and red. A rename shows both paths, `› R src/config/source.rs → src/config/sources.rs`, with the counts on the next row where they don't fit. A file whose hunks the host left out takes the host's counts, as in `› M Cargo.lock +40 -12`.
- **Order.** Each slice's files show in the desktop's reading order, not the patch's. Source files come first, each after the changed files it imports and otherwise by path. Tests come next, each after the source file whose name it has, then lockfiles, snapshots and files under `dist`, `build`, `vendor`, `__generated__` or `__snapshots__`, by path. A later slice never moves the files above it. So the first slice reads `assets/config-flow.png`, `src/config/reader.rs`, `src/config/schema.rs`, the rename and `tests/config_reload.rs`, and the second `docs/config.md`, `src/legacy/reader_v1.rs` and `Cargo.lock`.
- **Unfolding.** Enter on the chosen file, or a click on it, turns `›` into `⌄` and shows its lines. Each line has its number in grey, the new one or the old for a removed line, then `-` and the line in red, `+` and the line in green, or a space and the line. `⋯ 14 unmodified lines` comes before a hunk that doesn't start at line 1. A line that wraps leaves the number and the mark blank on its later rows, and a tab stops every 4 columns. A binary file reads `A binary file, whose changes have no lines to show.` and a file the host withheld `The host withheld this file's changes.` Enter or a click again folds the file.
- **Choosing.** Once a file or `↻ Retry` is in view, ↓ chooses the next one and ↑ the one before, and the status line leads with what Enter does: `Enter expand file`, `Enter collapse file` or `Enter retry`. Where none is in view, they scroll.
- **Reading again.** A new refresh number, `r` and a detail whose `updatedAt` changed each read the first slice again. The files read before stay, each with its fold, until the new slice replaces them, and the count goes back to `5 files+` until the end of the files comes near again. After `r` the diff waits for the refresh number `pullRequests.invalidate` raises, or for the end of the read that sent it, so T3 has forgotten the diff it held.
Expand All @@ -17,7 +18,7 @@ Open a thread, open its details (Alt+B), press → for `Pull requests`, choose a

## Driving it with t3v

Scenario `links` at 100x30. The diff route answers at once with two slices. The first holds `src/config/reader.rs`, `src/config/schema.rs`, the rename `src/config/source.rs → src/config/sources.rs`, the binary `assets/config-flow.png` and the new `tests/config_reload.rs`, with `truncated` and the cursor `s2`. The second holds `docs/config.md`, the deleted `src/legacy/reader_v1.rs` and a `Cargo.lock` with a header and no hunks, which the host counted at +40 -12. Files in the run directory change the reads: `diff-slow` holds each slice 4 s, `diff-fail` fails the first slice and `diff-fail-rest` the second with `GitHub took too long to answer.`, and `diff-off` makes the detail say the host gives no diff. The server logs each read of the diff in events.jsonl as `pr.diff` with its cursor, and `reads` lists those cursors, `null` for the first slice.
Scenario `links` at 100x30. The diff route answers at once with two slices. The first holds `src/config/reader.rs`, `src/config/schema.rs`, the rename `src/config/source.rs → src/config/sources.rs`, the binary `assets/config-flow.png` and the new `tests/config_reload.rs`, with `truncated` and the cursor `s2`. The second holds `docs/config.md`, the deleted `src/legacy/reader_v1.rs` and a `Cargo.lock` with a header and no hunks, which the host counted at +40 -12. Files in the run directory change the reads: `diff-slow` holds each slice 4 s, `diff-fail` fails the first slice and `diff-fail-rest` the second with `GitHub took too long to answer.`, `diff-off` makes the detail say the host gives no diff, and `diff-ts` serves a TypeScript diff in one slice. Its patch has `src/settings/panel.tsx`, which imports `./store` and `../lib/format`, then `src/settings/panel.test.tsx`, `src/settings/store.ts`, which imports `../lib/format`, `pnpm-lock.yaml` and the new `src/lib/format.ts`. The server logs each read of the diff in events.jsonl as `pr.diff` with its cursor, and `reads` lists those cursors, `null` for the first slice.

```bash
$T3V start --cols 100 --rows 30
Expand All @@ -41,23 +42,25 @@ $T3V capture more-100x30
$T3V wait --text "8 files" --text "› M docs/config.md +2 -1" --text "› D src/legacy/reader_v1.rs +0 -4" --gone "Loading more files…" --idle 300
rm "$RUN/diff-slow"
[ "$(reads)" = '[null,"s2"]' ]
S=$($T3V capture files-100x30 | grep 'ans$')
C=$($T3V capture files-100x30); S=$(echo "$C" | grep 'ans$'); T=$(echo "$C" | grep 'txt$')
# Each slice in reading order: source by path, as none imports another, then the test. Cargo.lock is below the view.
[ "$(grep -o '› [AMDR] [^ ]*' "$T" | paste -sd' ' -)" = '› M assets/config-flow.png › M src/config/reader.rs › M src/config/schema.rs › R src/config/source.rs › A tests/config_reload.rs › M docs/config.md › D src/legacy/reader_v1.rs' ]
grep -q '38;2;255;185;0mM.\[38;2;245;245;245m src/config/reader.rs .\[38;2;0;212;146m+7.\[38;2;245;245;245m .\[38;2;255;100;103m-5' "$S" && grep -q '38;2;43;127;255mR.\[38;2;245;245;245m src/config/source.rs → src/config/sources.rs' "$S" && grep -q '38;2;0;212;146mA.\[38;2;245;245;245m tests/config_reload.rs' "$S" && grep -q '38;2;255;100;103mD.\[38;2;245;245;245m src/legacy/reader_v1.rs' "$S" && grep -q '38;2;255;185;0mThe host withheld part' "$S"

# ↓ chooses the first file and Enter unfolds it: numbers in grey, removed lines red, added green.
$T3V keys Down && $T3V wait --text "Enter expand file"
$T3V keys Enter && $T3V wait --text "⌄ M src/config/reader.rs +7 -5" --text " 4 - use crate::config::Source;" --text " 4 + use crate::config::schema::Schema;" --text "⋯ 14 unmodified lines" --text "Enter collapse file"
# ↓ chooses the first file, the binary, and ↓ again the reader. Enter unfolds it: numbers in grey, removed lines red, added green.
$T3V keys Down Down && $T3V wait --text "Enter expand file"
$T3V keys Enter && $T3V wait --text "⌄ M src/config/reader.rs +7 -5" --text " 4 - use crate::config::Source;" --text " 4 + use crate::config::schema::Schema;" --text "Enter collapse file"
S=$($T3V capture reader-100x30 | grep 'ans$')
grep -q '38;2;129;129;129m 4 .\[38;2;255;100;103m- use crate::config::Source;' "$S" && grep -q '38;2;129;129;129m 4 .\[38;2;0;212;146m+ use crate::config::schema::Schema;' "$S" && grep -q '38;2;129;129;129m 1 .\[38;2;245;245;245m use std::fs;' "$S"
# Its tab stops at 4 columns, and a wrapped line leaves the number and the mark blank.
$T3V keys PgDn && $T3V wait --text "28 - Ok(config)" --text "33 + // A reload reads and checks the file a" --text " s a load does, so a file edited on disk since t"
$T3V keys Home && $T3V keys Down && $T3V wait --text "Enter collapse file"
# The unmodified lines before its second hunk, a tab stopping at 4 columns, and a wrapped line leaving the number and the mark blank.
$T3V keys PgDn && $T3V wait --text "⋯ 14 unmodified lines" --text "28 - Ok(config)" --text "33 + // A reload reads and checks the file a" --text " s a load does, so a file edited on disk since t"
$T3V keys Home && $T3V keys Down Down && $T3V wait --text "Enter collapse file"
$T3V keys Enter && $T3V wait --text "› M src/config/reader.rs +7 -5" --gone "⋯ 14 unmodified lines"

# A click unfolds the others: the binary, the rename and the new file with its tabs.
$T3V click --text "tests/config_reload.rs" && $T3V wait --text "⌄ A tests/config_reload.rs +8 -0" --text "4 + fn a_reload_checks_the_schema() {"
$T3V click --text "assets/config-flow.png" && $T3V wait --text "⌄ M assets/config-flow.png" --text "A binary file, whose changes have no lines to show."
$T3V click --text "src/config/source.rs" && $T3V wait --text "⌄ R src/config/source.rs → src/config/sources.rs" --text "1 - /// Where a config comes from." --text "1 + /// Where a config comes from: a file, the envir"
$T3V click --text "assets/config-flow.png" && $T3V wait --text "⌄ M assets/config-flow.png" --text "A binary file, whose changes have no lines to show."
$T3V capture kinds-100x30
$T3V keys End && $T3V wait --text "› M Cargo.lock +40 -12" --text "5 + let reader = Reader::new(\"tests/newer.toml\")"
$T3V click --text "Cargo.lock" && $T3V wait --text "⌄ M Cargo.lock +40 -12"
Expand Down Expand Up @@ -115,6 +118,15 @@ $T3V refresh && $T3V wait --text "← List Summary Timeline Code" --text "r r
$T3V keys Right Right && $T3V wait --text "5 files+" --text "⌄ M src/config/reader.rs +7 -5"
settle '[null]'

# A TypeScript diff: format.ts, which the others import, then store.ts, which imports it, then panel.tsx, which imports both, then its test and the lockfile.
touch "$RUN/diff-ts"
mark
$T3V refresh && settle '[null,null]'
$T3V wait --text "› A src/lib/format.ts" --text "› M pnpm-lock.yaml" --gone "src/config/reader.rs" --idle 300
rm "$RUN/diff-ts"
T=$($T3V capture typescript-100x30 | grep 'txt$')
[ "$(grep -o '› [AMDR] [^ ]*' "$T" | paste -sd' ' -)" = '› A src/lib/format.ts › M src/settings/store.ts › M src/settings/panel.tsx › M src/settings/panel.test.tsx › M pnpm-lock.yaml' ]

# Another thread's link, whose first slice fails: the failure alone. Showing the Code again reads it again, and so does opening the details again.
touch "$RUN/diff-fail"
$T3V keys Escape && $T3V wait --gone "Thread details"
Expand All @@ -136,7 +148,7 @@ $T3V keys Right Right && $T3V wait --text "8 files" --text "› M src/config/rea

# At 80x24 every row still fits the pane.
$T3V resize 80 24 && $T3V wait --text "8 files" --idle 300
$T3V keys Down Enter && $T3V wait --text "⌄ M src/config/reader.rs +7 -5" --text " 4 + use crate::config::schema::Schema;" --text "Enter collapse file"
$T3V keys Down Down Enter && $T3V wait --text "⌄ M src/config/reader.rs +7 -5" --text " 4 + use crate::config::schema::Schema;" --text "Enter collapse file"
$T3V capture reader-80x24
$T3V status --json | jq -e '.scopeViolations == 0 and .unknownRpcs == 0 and .fixtureErrors == 0'
```
Expand Down
5 changes: 3 additions & 2 deletions docs/pull-request-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Where the detail's `capabilities.diff` is true, the details have a third tab, Co
| Reading again | `codeRefreshToken` (PullRequestDetailPanel.tsx:832) moves with each refresh number, a person's Refresh and a detail whose `updatedAt` changed (836-847), and the tab reads the first slice again | The same three read the first slice again. After `r`, the read waits until T3's refresh number moves or the read that sent `pullRequests.invalidate` is done, so T3 has forgotten the diff it held. The files read before show until the new ones come, and a file keeps its fold. A read again that fails keeps them under `May be out of date: t3term couldn't read the diff again.` and the reason. |
| 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). |
| 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. |
| Waiting and failing | `Loading pull request diff...`, `Loading more files...` after the files, the first slice's error alone, and `The rest of this diff could not be loaded.` with Retry for a later slice (760-776) | `Loading pull request diff…` and `Loading more files…`. The first slice's failure shows alone, and `r` or showing the Code after the details open again reads it again. A later slice's failure shows `t3term couldn't read the rest of this diff.`, the reason and `↻ Retry`, which ↑ and ↓ choose. |
| Empty | `This pull request has no file changes.` (1366-1372) | The same, or `t3term couldn't split this patch into files.` where the patch has text but no file the parser could read. |
Expand Down Expand Up @@ -152,7 +153,7 @@ The desktop opens a link in its own pull request view only when the environment'
4. **Refreshes.** Done: while the details hold a read, t3term follows `pullRequests.subscribeRefreshes` and reads the link again on each new number, and `r` sends `pullRequests.invalidate` with the link's reference before it reads again, as above. The invalidate spends host requests and makes every reader read again, so t3term sends it only for that key. The details also read the detail again every 5 minutes while you use t3term, and when you arrive at them: you come back after 6 minutes away, open the link's details again or the terminal gains focus, as the desktop's live refresh does (apps/web/src/hooks/useLiveRefresh.ts).
5. **The stack.** Done in part: where the host keeps stacks, a link's details read `pullRequests.stack` and list its layers and their states on the Summary, as above. The stack saved on the link shows until the first read of it came. Opening a layer's details from the stack is left.
6. **The project's list.** `pullRequests.list` and `pullRequests.listStats` list a project's pull requests outside any thread. That is a separate view, after the page.
7. **The diff.** Done in part: the Code tab reads the diff a slice at a time and shows its files, as above. Next come the desktop's file order, the review threads beside their lines, the commit picker, more lines around a hunk from `pullRequests.diffFileContents`, viewed files, and the toolbar's toggles.
7. **The diff.** Done in part: the Code tab reads the diff a slice at a time and shows its files, as above. Each slice's files show in the desktop's reading order. Next come the review threads beside their lines, the commit picker, more lines around a hunk from `pullRequests.diffFileContents`, viewed files, and the toolbar's toggles.

Linking, unlinking, watching, reviewing, commenting, merging and every other write stay out until a person can confirm each one in t3term.

Expand Down Expand Up @@ -191,7 +192,7 @@ Linking, unlinking, watching, reviewing, commenting, merging and every other wri
- The stack shows only on the Summary, where the desktop's header shows it on every tab. A layer doesn't open its pull request, where the desktop's menu opens it in the panel.
- The saved stack comes from the open thread's links. The desktop looks through the links of every thread on the environment and takes the newest by `syncedAt`, so a layer linked only from another thread shows its branch in t3term where the desktop shows its title. Of two links to one pull request on a thread, t3term keeps the first, where the desktop takes the newest.
- t3term can't merge or rebase a stack, and doesn't keep the layers' head commits that the desktop's stack merge sends.
- The Code tab shows files in the order the patch has them. The desktop puts source first, in the order its imports need, then the tests that cover it, then what a tool wrote (pullRequestFileOrder.logic.ts:145-149).
- 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.
- The Code tab has no review threads, commit picker, more lines around a hunk, viewed files, 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 reads the diff only while it shows. The desktop's stays mounted and reads on a refresh while another tab shows, so its files are ready when the reader comes back. t3term instead reads the first slice again when the Code shows where it came a minute or more ago, so the files show the old diff until it comes.
- The files read before show while the first slice is read again. The desktop drops them and shows `Loading pull request diff...`.
Loading
Loading