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
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ $T3V capability on && $T3V wait --text "Pull requests 11"
- `thr-many` has 1,005 links and `thr-hostile` has text full of control characters and huge fields. `t3v check` fails with `screenLeaks` if text that must stay hidden reaches the screen.
- The footer's age (`synced 1m ago`) and each link's age move with time, so wait on the count, not the age.
- A click right after a key, a wheel notch or a change from the server does nothing until the next frame.
- `capability off` closes T3's refresh numbers at once and `capability on` opens them again while a link's read is held, so the recipe ends with two `pullRequests.subscribeRefreshes` in `rpcRequestsByTag`.
2 changes: 1 addition & 1 deletion docs/pull-request-panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ On an environment whose capabilities have `pullRequests: true`, opening a link's
| --- | --- | --- |
| The project the ref names | A project in the environment whose repository is the link's, else the thread's project (ThreadPullRequestsPanel.tsx:420-425) | The thread's project, with the link's host as T3 sent it. T3 serves the ref from that project when its repository is the link's. Otherwise it serves it from a project on the link's host whose repository matches, else from any project on that host (apps/server/src/pullRequest/PullRequestService.ts:888-950). Both choices reach the same project unless two projects share the link's repository. |
| When it reads | The detail query is fresh for 60 s (packages/client-runtime/src/state/pullRequests.ts:181-189) and reads again on each refresh | Each open of a link's details asks for both, unless the same link's pull request came under 60 s ago or its read is still on its way. Within that minute, an open 10 s or more after it came reads the detail again, as the live refresh row says. The minute counts from when the pull request came, as `Atom.swr` counts from its result's timestamp (effect 4.0.1, src/reactivity/Atom.ts:2491-2512), and so does the age the details show. A failure of either is never reused. The conversation is fresh for 60 s too (client-runtime pullRequests.ts:172-180), and t3term reads it again with the pull request. One read is under way at a time, in the whole TUI. A link opened during it reads `Reading from…` and is asked for once both of that read's answers came, and leaving its details first drops it, so a run of links opened one after another starts two reads at most. So does closing the pane or the dialog, or showing the Thread tab, and showing the link's details again asks for it then. t3term keeps one answer, for the link whose details opened last, and drops an answer to any earlier ask. A read again of the same link keeps its last answers on screen, with `Refreshing from github.com…` where the time of the read was, until both new ones came, as the desktop's queries show what they hold while they read again (`Atom.swr`, packages/client-runtime/src/state/runtime.ts). Each new answer then replaces its own, and a failure replaces only a failure, as the desktop shows an error only when it has no detail (PullRequestDetailPanel.tsx:2728). A read again that missed either answer is never reused, so the next open asks again. |
| Refreshes | `pullRequests.subscribeRefreshes` (rpc.ts:1064) streams a number T3 raises after each turn, each change made through T3 and each `pullRequests.invalidate` that tells readers (apps/server/src/pullRequest/PullRequestService.ts:3327-3378 and 3431). A query that something shows reads again when the number changes, and one nothing shows is only marked stale (`refreshOnSignalWhileRead`, client-runtime runtime.ts:478, wired in pullRequests.ts:116-196) | While the details hold a read, t3term follows the subscription through the watcher the setup card uses, and a reconnection sends the same empty request. The first number it hears is where T3 stands. Each later number that differs reads the held link again at once while its details show, and otherwise marks the read so that its details read again the next time they show, even within the minute. The list, a closed pane or dialog and the Thread tab don't show them, and the subscription stays open meanwhile. A number that changed while T3 was out of reach counts. Once the details hold no read, as after another thread opens, t3term closes the subscription. One that T3 refuses or ends isn't asked for again in the session, and reads go on as before. T3's stream never ends on its own (`SubscriptionRef.changes`, PullRequestService.ts:3431), so a successful `Exit` stops it too, while a dropped connection subscribes again. An open run on the Timeline stays open across a read again, since a run is known by its newest remark, as the desktop keys a run by its first remark newest first (PullRequestTimelineTab.tsx:588). A run that a newer remark joins starts closed, as the desktop's does. Each read again forgets the open runs and finished remarks the new conversation no longer has, so what t3term keeps stays within the conversation's size. A read again that brings a merged or closed detail but misses the conversation keeps the last conversation and lays its Timeline out again with the new ending. |
| Refreshes | `pullRequests.subscribeRefreshes` (rpc.ts:1064) streams a number T3 raises after each turn, each change made through T3 and each `pullRequests.invalidate` that tells readers (apps/server/src/pullRequest/PullRequestService.ts:3327-3378 and 3431). A query that something shows reads again when the number changes, and one nothing shows is only marked stale (`refreshOnSignalWhileRead`, client-runtime runtime.ts:478, wired in pullRequests.ts:116-196) | While the details hold a read, t3term follows the subscription through the watcher the setup card uses, and a reconnection sends the same empty request. The first number it hears is where T3 stands. Each later number that differs reads the held link again at once while its details show, and otherwise marks the read so that its details read again the next time they show, even within the minute. The list, a closed pane or dialog and the Thread tab don't show them, and the subscription stays open meanwhile. A number that changed while T3 was out of reach counts. Once the details hold no read, as after another thread opens, t3term closes the subscription. A config that turns `pullRequests` off closes it at once too, and one that turns it on again opens it while the details hold a read, as the desktop's queries follow the server config on each render (apps/web/src/components/RightPanelTabs.tsx:749-760). One that T3 refuses or ends isn't asked for again in the session, and reads go on as before. T3's stream never ends on its own (`SubscriptionRef.changes`, PullRequestService.ts:3431), so a successful `Exit` stops it too, while a dropped connection subscribes again. An open run on the Timeline stays open across a read again, since a run is known by its newest remark, as the desktop keys a run by its first remark newest first (PullRequestTimelineTab.tsx:588). A run that a newer remark joins starts closed, as the desktop's does. Each read again forgets the open runs and finished remarks the new conversation no longer has, so what t3term keeps stays within the conversation's size. A read again that brings a merged or closed detail but misses the conversation keeps the last conversation and lays its Timeline out again with the new ending. |
| A person's refresh | The Refresh item in the details' menu (PullRequestDetailPanel.tsx:2037-2040) sends `pullRequests.invalidate` with the link's reference, then reads the detail, the conversation and the stack again (857-875). If the invalidate fails, the reads still go and at worst answer from T3's cache. The item is disabled while the invalidate or the detail's read is under way, and the menu's button spins meanwhile. T3 takes the invalidate with read access (apps/server/src/auth/RpcAuthorization.ts:132-134), forgets its cached answers for that pull request, raises the refresh number and asks for a sync of the link (apps/server/src/ws.ts:2506-2519) | `r` in a link's details does the same as one read: `pullRequests.invalidate`, whose failure t3term ignores, then the detail and the conversation. The details read `Refreshing from github.com…` over the last answers meanwhile, or `Reading from github.com…` after a read that failed. `r` does nothing while a read of the link waits or is under way, and the status line offers `r refresh` only when it would read. The first refresh number heard during that read is the invalidate's own, so it doesn't read again, where the desktop's subscription reads the mounted queries once more. A number after it does, and a read that heard none forgets to expect one once it is done. `r` reads the stack again too, after the detail, where the host keeps stacks. |
| Live refresh | `useLiveRefresh` (apps/web/src/hooks/useLiveRefresh.ts) calls the panel's `detailQuery.refresh()` (PullRequestDetailPanel.tsx:851-856) every 5 minutes while the window shows, unless no pointer, key or wheel input came in the last 6 minutes. The first input after those 6 minutes reads at once. So do arriving at a pull request the session read before and the window gaining focus or coming into view, while input came in the last 6 minutes. Each of these reads only 10 seconds or more after the hook's last read for that pull request. The conversation is read again only when the detail's `updatedAt` changed (836-847, `shouldRefreshPullRequestActivity` in pullRequestDetail.logic.ts:163-168) | While a link's details show a read that is done, t3term reads the detail again 5 minutes after the last read was asked for, if a key, click, wheel, mouse move or paste came in the 6 minutes before it reads, as the desktop checks at the time its timer runs. Otherwise it waits, as after a Mac slept past the 5 minutes, and the first input after 6 minutes without one reads at once, unless the detail they show came, or the last read was asked for, under 10 seconds ago. So do opening the link's details again while its answers stand, and the terminal gaining focus while you used t3term in the last 6 minutes. Focus after an idle spell waits for the next input, as the desktop's does. t3term turns on the terminal's focus reports while it runs and off when it ends. The 10 seconds count from when the detail came or the last read was asked for, whichever is later. So a host that took long to answer isn't asked again the moment it did, and a read again that failed isn't asked again at once. The read sends the `updatedAt` the details show and asks for the conversation after the detail only when the new one differs. A detail with the same `updatedAt` keeps the last conversation, and the read stands for a minute as any read does. After a read whose detail or conversation failed, the next read asks for both, as a first read does. A refresh number or `r` while one is due reads both instead. Every read, whatever asked for it, starts the 5 minutes again. The event loop waits for the next one only while you use t3term and the environment reads pull requests, so an idle TUI wakes for nothing. |
| Out-of-date warning | `This branch is out-of-date with {base} by N commits.` (apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx:366), shown when `resolveBaseFreshness` finds an open, mergeable pull request behind its base (pullRequestDetail.logic.ts:1247) | The same sentence under the same conditions, with the count grouped, as in `1,234 commits`, and `1 commit` for one. |
Expand Down
37 changes: 36 additions & 1 deletion src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3142,9 +3142,13 @@ impl App {
/// keeps what depends on it in step. A change can add or drop menu rows, so an open menu's
/// highlight follows its entry, not its row number. The sidebar is laid out again when the
/// change turns snooze or settlement on or off, which decide whether those shelves apply.
/// The provider banner follows the selected provider's new status.
/// The provider banner follows the selected provider's new status. A change that turns
/// `pullRequests` on or off opens or closes T3's refresh numbers at once, rather than at
/// the next key or answer, as the desktop's queries follow the server config on each
/// render (apps/web/src/components/RightPanelTabs.tsx:749-760).
fn update_config(&mut self, change: impl FnOnce(&mut Option<Value>) -> bool) -> bool {
let (pick, capabilities) = (self.selected_pick(), self.capabilities());
let reads_pulls = self.environment_can("pullRequests");
if !change(&mut self.config) {
return false;
}
Expand All @@ -3154,6 +3158,9 @@ impl App {
if self.capabilities() != capabilities {
self.rebuild_rows();
}
if self.environment_can("pullRequests") != reads_pulls {
self.read_opened_pull();
}
self.follow_provider(true);
true
}
Expand Down Expand Up @@ -15619,6 +15626,34 @@ mod tests {
assert_eq!(reads(&asked), [(1, false), (2, true), (3, true)]);
}

#[test]
fn the_refresh_numbers_follow_a_config_that_turns_pull_requests_off_and_on() {
let mut app = readable_pulls_tui(vec![linked(31, "Bound titles")]);
let asked = note_reads(&mut app);
let opened = note_refreshes(&mut app);
press(&mut app, KeyCode::Enter);
let detail = Part::Detail(Ok(Box::new(host_detail())));
let activity = Part::Activity(Ok(Box::new(host_activity())));
hear(&mut app, 1, vec![detail, activity, Part::Done]);
// On the list, nothing reads the details again on a timer.
press(&mut app, KeyCode::Left);
assert_eq!(opened.borrow().len(), 1);
let capabilities = |reads: bool| json!({"threadPullRequests": true, "pullRequests": reads});

// A config that turns `pullRequests` off closes the subscription before any key.
app.on_config_event(config_snapshot(vec![], capabilities(false)));
assert!(app.refreshes.is_none());
assert!(opened.borrow()[0].is_closed());

// One that turns it on again opens another, and one that leaves it on keeps that.
app.on_config_event(config_snapshot(vec![], capabilities(true)));
assert_eq!(opened.borrow().len(), 2);
app.on_config_event(config_snapshot(vec![], capabilities(true)));
assert_eq!(opened.borrow().len(), 2);
assert!(!opened.borrow()[1].is_closed());
assert_eq!(asked.borrow().len(), 1);
}

#[test]
fn a_read_that_keeps_details_current_reads_the_conversation_once_the_detail_changed() {
let detail = Ok(Box::new(host_detail()));
Expand Down
Loading